File: Services\MissingImportDiscovery\RemoteMissingImportDiscoveryService.cs
Web Access
Project: src\src\Workspaces\Remote\ServiceHub\Microsoft.CodeAnalysis.Remote.ServiceHub.csproj (Microsoft.CodeAnalysis.Remote.ServiceHub)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.Packaging;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SymbolSearch;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Remote;
 
internal sealed class RemoteMissingImportDiscoveryService(
    in BrokeredServiceBase.ServiceConstructionArguments arguments,
    RemoteCallback<IRemoteMissingImportDiscoveryService.ICallback> callback)
    : BrokeredServiceBase(arguments), IRemoteMissingImportDiscoveryService
{
    internal sealed class Factory : FactoryBase<IRemoteMissingImportDiscoveryService, IRemoteMissingImportDiscoveryService.ICallback>
    {
        protected override IRemoteMissingImportDiscoveryService CreateService(in ServiceConstructionArguments arguments, RemoteCallback<IRemoteMissingImportDiscoveryService.ICallback> callback)
            => new RemoteMissingImportDiscoveryService(arguments, callback);
    }
 
    private readonly RemoteCallback<IRemoteMissingImportDiscoveryService.ICallback> _callback = callback;
 
    public ValueTask<ImmutableArray<AddImportFixData>> GetFixesAsync(
        Checksum solutionChecksum,
        RemoteServiceCallbackId callbackId,
        DocumentId documentId,
        TextSpan span,
        string diagnosticId,
        int maxResults,
        AddImportOptions options,
        ImmutableArray<PackageSource> packageSources,
        CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var document = solution.GetDocument(documentId);
            if (document is null)
                return [];
 
            var service = document.GetRequiredLanguageService<IAddImportFeatureService>();
 
            var symbolSearchService = new SymbolSearchService(_callback, callbackId);
 
            var result = await service.GetFixesAsync(
                document, span, diagnosticId, maxResults,
                symbolSearchService, options,
                packageSources, cancellationToken).ConfigureAwait(false);
 
            return result;
        }, cancellationToken);
    }
 
    public ValueTask<ImmutableArray<AddImportFixData>> GetUniqueFixesAsync(
        Checksum solutionChecksum,
        RemoteServiceCallbackId callbackId,
        DocumentId documentId,
        TextSpan span,
        ImmutableArray<string> diagnosticIds,
        AddImportOptions options,
        ImmutableArray<PackageSource> packageSources,
        CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var document = solution.GetDocument(documentId);
            if (document is null)
                return [];
 
            var service = document.GetRequiredLanguageService<IAddImportFeatureService>();
 
            var symbolSearchService = new SymbolSearchService(_callback, callbackId);
 
            var result = await service.GetUniqueFixesAsync(
                document, span, diagnosticIds,
                symbolSearchService, options,
                packageSources, cancellationToken).ConfigureAwait(false);
 
            return result;
        }, cancellationToken);
    }
 
    /// <summary>
    /// Provides an implementation of the <see cref="ISymbolSearchService"/> on the remote side so that
    /// Add-Import can find results in nuget packages/reference assemblies.  This works
    /// by remoting *from* the OOP server back to the host, which can then forward this 
    /// appropriately to wherever the real <see cref="ISymbolSearchService"/> is running.  This is necessary
    /// because it's not guaranteed that the real <see cref="ISymbolSearchService"/> will be running in 
    /// the same process that is supplying the <see cref="RemoteMissingImportDiscoveryService"/>.
    /// 
    /// Ideally we would not need to bounce back to the host for this.
    /// </summary>
    private sealed class SymbolSearchService(
        RemoteCallback<IRemoteMissingImportDiscoveryService.ICallback> callback,
        RemoteServiceCallbackId callbackId)
        : ISymbolSearchService
    {
        private readonly RemoteCallback<IRemoteMissingImportDiscoveryService.ICallback> _callback = callback;
        private readonly RemoteServiceCallbackId _callbackId = callbackId;
 
        public ValueTask<ImmutableArray<PackageResult>> FindPackagesAsync(string source, TypeQuery typeQuery, NamespaceQuery namespaceQuery, CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.FindPackagesAsync(_callbackId, source, typeQuery, namespaceQuery, cancellationToken), cancellationToken);
 
        public ValueTask<ImmutableArray<PackageWithAssemblyResult>> FindPackagesWithAssemblyAsync(string source, string assemblyName, CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.FindPackagesWithAssemblyAsync(_callbackId, source, assemblyName, cancellationToken), cancellationToken);
 
        public ValueTask<ImmutableArray<ReferenceAssemblyResult>> FindReferenceAssembliesAsync(TypeQuery typeQuery, NamespaceQuery namespaceQuery, CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.FindReferenceAssembliesAsync(_callbackId, typeQuery, namespaceQuery, cancellationToken), cancellationToken);
    }
}