File: Handler\Symbols\WorkspaceSymbolsHandler.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// 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.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.NavigateTo;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.LanguageServer.Handler;
 
/// <summary>
/// TODO - This must be moved to the MS.CA.LanguageServer.Protocol project once
/// we no longer reference VS icon types.
/// </summary>
[ExportCSharpVisualBasicStatelessLspService(typeof(WorkspaceSymbolsHandler)), Shared]
[Method(Methods.WorkspaceSymbolName)]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class WorkspaceSymbolsHandler(IAsynchronousOperationListenerProvider listenerProvider)
    : ILspServiceRequestHandler<WorkspaceSymbolParams, SumType<SymbolInformation[], WorkspaceSymbol[]>?>
{
    private static readonly IImmutableSet<string> s_supportedKinds = [
        NavigateToItemKind.Class,
        NavigateToItemKind.Constant,
        NavigateToItemKind.Delegate,
        NavigateToItemKind.Enum,
        NavigateToItemKind.EnumItem,
        NavigateToItemKind.Event,
        NavigateToItemKind.Field,
        NavigateToItemKind.Interface,
        NavigateToItemKind.Method,
        NavigateToItemKind.Module,
        NavigateToItemKind.Property,
        NavigateToItemKind.Structure
    ];
 
    private readonly IAsynchronousOperationListener _asyncListener = listenerProvider.GetListener(FeatureAttribute.NavigateTo);
 
    public bool MutatesSolutionState => false;
    public bool RequiresLSPSolution => true;
 
    public async Task<SumType<SymbolInformation[], WorkspaceSymbol[]>?> HandleRequestAsync(WorkspaceSymbolParams request, RequestContext context, CancellationToken cancellationToken)
    {
        Contract.ThrowIfNull(context.Solution);
 
        var solution = context.Solution;
 
        using var progress = BufferedProgress.Create(
            request.PartialResultToken,
            (SymbolInformation[] t) => new SumType<SymbolInformation[], WorkspaceSymbol[]>(t));
 
        var searcher = NavigateToSearcher.Create(
            solution,
            _asyncListener,
            new LSPNavigateToCallback(context, progress),
            request.Query,
            s_supportedKinds,
            cancellationToken);
 
        await searcher.SearchAsync(NavigateToSearchScope.Solution, cancellationToken).ConfigureAwait(false);
        return progress.GetValues()?.Flatten().ToArray();
    }
 
    private sealed class LSPNavigateToCallback(
        RequestContext context,
        BufferedProgress<SymbolInformation[]> progress)
        : INavigateToSearchCallback
    {
        public async Task AddResultsAsync(ImmutableArray<INavigateToSearchResult> results, CancellationToken cancellationToken)
        {
            Contract.ThrowIfNull(context.Solution);
            var solution = context.Solution;
 
            var clientCapabilities = context.GetRequiredClientCapabilities();
            var supportsVSExtensions = clientCapabilities.HasVisualStudioLspCapability();
 
            foreach (var result in results)
            {
                var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false);
 
                var location = await ProtocolConversions.TextSpanToLocationAsync(
                    document, result.NavigableItem.SourceSpan, result.NavigableItem.IsStale, context, cancellationToken).ConfigureAwait(false);
                if (location == null)
                    return;
 
                var symbolInfo = SymbolInformationFactory.Create(
                    result.Name,
                    result.AdditionalInformation,
                    ProtocolConversions.NavigateToKindToSymbolKind(result.Kind),
                    location,
                    result.NavigableItem.Glyph,
                    supportsVSExtensions);
 
                progress.Report(symbolInfo);
            }
        }
 
        public void Done(bool isFullyLoaded)
        {
            // do nothing, we already await the SearchAsync method which calls this in a finally right before returning.
            // used by non-LSP editor API.
        }
 
        public void ReportProgress(int current, int maximum)
        {
            // do nothing, LSP doesn't support reporting progress towards completion.
            // used by non-LSP editor API.
        }
 
        public void ReportIncomplete()
        {
        }
    }
}