|
// 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()
{
}
}
}
}
|