File: Services\SymbolFinder\RemoteSymbolFinderService.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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.SymbolTree;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Remote;
 
internal sealed class RemoteSymbolFinderService : BrokeredServiceBase, IRemoteSymbolFinderService
{
    internal sealed class Factory : FactoryBase<IRemoteSymbolFinderService, IRemoteSymbolFinderService.ICallback>
    {
        protected override IRemoteSymbolFinderService CreateService(in ServiceConstructionArguments arguments, RemoteCallback<IRemoteSymbolFinderService.ICallback> callback)
            => new RemoteSymbolFinderService(arguments, callback);
    }
 
    private readonly RemoteCallback<IRemoteSymbolFinderService.ICallback> _callback;
 
    public RemoteSymbolFinderService(in ServiceConstructionArguments arguments, RemoteCallback<IRemoteSymbolFinderService.ICallback> callback)
        : base(arguments)
    {
        _callback = callback;
    }
 
    public ValueTask FindReferencesAsync(
        Checksum solutionChecksum,
        RemoteServiceCallbackId callbackId,
        SerializableSymbolAndProjectId symbolAndProjectIdArg,
        ImmutableArray<DocumentId> documentArgs,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var symbol = await symbolAndProjectIdArg.TryRehydrateAsync(
                solution, cancellationToken).ConfigureAwait(false);
 
            var progressCallback = new FindReferencesProgressCallback(solution, _callback, callbackId);
 
            if (symbol == null)
            {
                await progressCallback.OnStartedAsync(cancellationToken).ConfigureAwait(false);
                await progressCallback.OnCompletedAsync(cancellationToken).ConfigureAwait(false);
                return;
            }
 
            // NOTE: In projection scenarios, we might get a set of documents to search
            // that are not all the same language and might not exist in the OOP process
            // (like the JS parts of a .cshtml file). Filter them out here.  This will
            // need to be revisited if we someday support FAR between these languages.
            var documents = documentArgs.IsDefault ? null :
                documentArgs.Select(solution.GetDocument).WhereNotNull().ToImmutableHashSet();
 
            await SymbolFinder.FindReferencesInCurrentProcessAsync(
                symbol, solution, progressCallback,
                documents, options, cancellationToken).ConfigureAwait(false);
        }, cancellationToken);
    }
 
    public ValueTask FindLiteralReferencesAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, object value, TypeCode typeCode, CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var convertedType = System.Convert.ChangeType(value, typeCode);
 
            var progressCallback = new FindLiteralReferencesProgressCallback(_callback, callbackId);
            await SymbolFinder.FindLiteralReferencesInCurrentProcessAsync(
                convertedType, solution, progressCallback, cancellationToken).ConfigureAwait(false);
        }, cancellationToken);
    }
 
    private static ImmutableArray<SerializableSymbolAndProjectId> Convert(ImmutableArray<ISymbol> items, Solution solution, CancellationToken cancellationToken)
    {
        var result = new FixedSizeArrayBuilder<SerializableSymbolAndProjectId>(items.Length);
 
        foreach (var item in items)
            result.Add(SerializableSymbolAndProjectId.Dehydrate(solution, item, cancellationToken));
 
        return result.MoveToImmutable();
    }
 
    public ValueTask<ImmutableArray<SerializableSymbolAndProjectId>> FindAllDeclarationsWithNormalQueryAsync(
        Checksum solutionChecksum,
        ProjectId projectId,
        string name,
        SearchKind searchKind,
        SymbolFilter criteria,
        CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var project = solution.GetRequiredProject(projectId);
 
            using var query = SearchQuery.Create(name, searchKind);
 
            var result = await DeclarationFinder.FindAllDeclarationsWithNormalQueryInCurrentProcessAsync(
                project, query, criteria, cancellationToken).ConfigureAwait(false);
 
            return Convert(result, solution, cancellationToken);
        }, cancellationToken);
    }
 
    public ValueTask<ImmutableArray<SerializableSymbolAndProjectId>> FindSolutionSourceDeclarationsWithNormalQueryAsync(
        Checksum solutionChecksum,
        string name,
        bool ignoreCase,
        SymbolFilter criteria,
        CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var result = await DeclarationFinder.FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
                solution, name, ignoreCase, criteria, cancellationToken).ConfigureAwait(false);
 
            return Convert(result, solution, cancellationToken);
        }, cancellationToken);
    }
 
    public ValueTask<ImmutableArray<SerializableSymbolAndProjectId>> FindProjectSourceDeclarationsWithNormalQueryAsync(
        Checksum solutionChecksum,
        ProjectId projectId,
        string name,
        bool ignoreCase,
        SymbolFilter criteria,
        CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var project = solution.GetRequiredProject(projectId);
 
            var result = await DeclarationFinder.FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
                project, name, ignoreCase, criteria, cancellationToken).ConfigureAwait(false);
 
            return Convert(result, solution, cancellationToken);
        }, cancellationToken);
    }
 
    public ValueTask<ImmutableArray<SerializableSymbolAndProjectId>> FindSolutionSourceDeclarationsWithPatternAsync(
        Checksum solutionChecksum, string pattern, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var result = await DeclarationFinder.FindSourceDeclarationsWithPatternInCurrentProcessAsync(
                solution, pattern, criteria, cancellationToken).ConfigureAwait(false);
 
            return Convert(result, solution, cancellationToken);
        }, cancellationToken);
    }
 
    public ValueTask<ImmutableArray<SerializableSymbolAndProjectId>> FindProjectSourceDeclarationsWithPatternAsync(
        Checksum solutionChecksum, ProjectId projectId, string pattern, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        return RunServiceAsync(solutionChecksum, async solution =>
        {
            var project = solution.GetRequiredProject(projectId);
 
            var result = await DeclarationFinder.FindSourceDeclarationsWithPatternInCurrentProcessAsync(
                project, pattern, criteria, cancellationToken).ConfigureAwait(false);
 
            return Convert(result, solution, cancellationToken);
        }, cancellationToken);
    }
 
    private sealed class FindLiteralReferencesProgressCallback : IStreamingFindLiteralReferencesProgress, IStreamingProgressTracker
    {
        private readonly RemoteCallback<IRemoteSymbolFinderService.ICallback> _callback;
        private readonly RemoteServiceCallbackId _callbackId;
 
        public IStreamingProgressTracker ProgressTracker { get; }
 
        public FindLiteralReferencesProgressCallback(RemoteCallback<IRemoteSymbolFinderService.ICallback> callback, RemoteServiceCallbackId callbackId)
        {
            _callback = callback;
            _callbackId = callbackId;
            ProgressTracker = this;
        }
 
        public ValueTask OnReferenceFoundAsync(Document document, TextSpan span, CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.OnLiteralReferenceFoundAsync(_callbackId, document.Id, span, cancellationToken), cancellationToken);
 
        public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.AddLiteralItemsAsync(_callbackId, count, cancellationToken), cancellationToken);
 
        public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.LiteralItemsCompletedAsync(_callbackId, count, cancellationToken), cancellationToken);
    }
 
    private sealed class FindReferencesProgressCallback : IStreamingFindReferencesProgress, IStreamingProgressTracker
    {
        private readonly Solution _solution;
        private readonly RemoteCallback<IRemoteSymbolFinderService.ICallback> _callback;
        private readonly RemoteServiceCallbackId _callbackId;
 
        public IStreamingProgressTracker ProgressTracker { get; }
 
        public FindReferencesProgressCallback(Solution solution, RemoteCallback<IRemoteSymbolFinderService.ICallback> callback, RemoteServiceCallbackId callbackId)
        {
            _solution = solution;
            _callback = callback;
            _callbackId = callbackId;
            ProgressTracker = this;
        }
 
        public ValueTask OnStartedAsync(CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.OnStartedAsync(_callbackId, cancellationToken), cancellationToken);
 
        public ValueTask OnCompletedAsync(CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.OnCompletedAsync(_callbackId, cancellationToken), cancellationToken);
 
        public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken)
        {
            var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, cancellationToken);
            return _callback.InvokeAsync(
                (callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedGroup, cancellationToken), cancellationToken);
        }
 
        public async ValueTask OnReferencesFoundAsync(
            ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references,
            CancellationToken cancellationToken)
        {
            var dehydrated = references.SelectAsArray(
                static (reference, tuple) => (
                    SerializableSymbolGroup.Dehydrate(tuple.solution, reference.group, tuple.cancellationToken),
                    SerializableSymbolAndProjectId.Dehydrate(tuple.solution, reference.symbol, tuple.cancellationToken),
                    SerializableReferenceLocation.Dehydrate(reference.location, tuple.cancellationToken)),
                (solution: _solution, cancellationToken));
 
            await _callback.InvokeAsync(
                (callback, cancellationToken) => callback.OnReferencesFoundAsync(
                    _callbackId, dehydrated, cancellationToken), cancellationToken).ConfigureAwait(false);
        }
 
        public ValueTask AddItemsAsync(int count, CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.AddReferenceItemsAsync(_callbackId, count, cancellationToken), cancellationToken);
 
        public ValueTask ItemsCompletedAsync(int count, CancellationToken cancellationToken)
            => _callback.InvokeAsync((callback, cancellationToken) => callback.ReferenceItemsCompletedAsync(_callbackId, count, cancellationToken), cancellationToken);
    }
}