提交 8b756d1e 编写于 作者: D David Barbet

Use workspace symbol instead of custom request.

上级 809bfbae
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Immutable;
using System.Composition;
......@@ -21,9 +23,9 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
/// </summary>
[Shared]
[ExportLspMethod(LSP.Methods.TextDocumentCompletionName)]
internal class CompletionHandler : IRequestHandler<LSP.CompletionParams, object>
internal class CompletionHandler : IRequestHandler<LSP.CompletionParams, LSP.SumType<LSP.CompletionItem[], LSP.CompletionList>?>
{
public async Task<object> HandleRequestAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities,
public async Task<LSP.SumType<LSP.CompletionItem[], LSP.CompletionList>?> HandleRequestAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities,
CancellationToken cancellationToken)
{
var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
......@@ -34,7 +36,7 @@ internal class CompletionHandler : IRequestHandler<LSP.CompletionParams, object>
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var completionService = document.Project.LanguageServices.GetService<CompletionService>();
var completionService = document.Project.LanguageServices.GetRequiredService<CompletionService>();
var list = await completionService.GetCompletionsAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false);
if (list == null)
{
......
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
......@@ -12,14 +14,14 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
[Shared]
[ExportLspMethod(LSP.Methods.TextDocumentDefinitionName)]
internal class GoToDefinitionHandler : GoToDefinitionHandlerBase, IRequestHandler<LSP.TextDocumentPositionParams, object>
internal class GoToDefinitionHandler : GoToDefinitionHandlerBase, IRequestHandler<LSP.TextDocumentPositionParams, LSP.SumType<LSP.Location, LSP.Location[]>?>
{
[ImportingConstructor]
public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileService) : base(metadataAsSourceFileService)
{
}
public async Task<object> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
public async Task<LSP.SumType<LSP.Location, LSP.Location[]>?> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
{
return await GetDefinitionAsync(solution, request, typeOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false);
......
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Composition;
using System.Linq;
using System.Threading;
......@@ -14,9 +16,9 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
[Shared]
[ExportLspMethod(LSP.Methods.TextDocumentImplementationName)]
internal class FindImplementationsHandler : IRequestHandler<LSP.TextDocumentPositionParams, object>
internal class FindImplementationsHandler : IRequestHandler<LSP.TextDocumentPositionParams, LSP.SumType<LSP.Location, LSP.Location[]>?>
{
public async Task<object> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
public async Task<LSP.SumType<LSP.Location, LSP.Location[]>?> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
{
var locations = ArrayBuilder<LSP.Location>.GetInstance();
......@@ -27,7 +29,7 @@ internal class FindImplementationsHandler : IRequestHandler<LSP.TextDocumentPosi
return locations.ToArrayAndFree();
}
var findUsagesService = document.Project.LanguageServices.GetService<IFindUsagesService>();
var findUsagesService = document.Project.LanguageServices.GetRequiredService<IFindUsagesService>();
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
var context = new SimpleFindUsagesContext(cancellationToken);
......
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
......@@ -51,7 +53,7 @@ public LanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRe
Contract.Fail("Invalid method name");
}
var handler = (IRequestHandler<RequestType, ResponseType>)_requestHandlers[methodName]?.Value;
var handler = (IRequestHandler<RequestType, ResponseType>?)_requestHandlers[methodName]?.Value;
Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName));
return handler.HandleRequestAsync(solution, request, clientCapabilities, cancellationToken);
......@@ -78,8 +80,8 @@ public Task<object> ExecuteWorkspaceCommandAsync(Solution solution, LSP.ExecuteC
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the location(s) of the implementations of the symbol.</returns>
public Task<object> FindImplementationsAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.TextDocumentPositionParams, object>(LSP.Methods.TextDocumentImplementationName, solution, request, clientCapabilities, cancellationToken);
public Task<LSP.SumType<LSP.Location, LSP.Location[]>?> FindImplementationsAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.SumType<LSP.Location, LSP.Location[]>?>(LSP.Methods.TextDocumentImplementationName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a format document request to format the entire document.
......@@ -138,8 +140,8 @@ public Task<object[]> GetCodeActionsAsync(Solution solution, LSP.CodeActionParam
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>a list of completions.</returns>
public Task<object> GetCompletionsAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.CompletionParams, object>(LSP.Methods.TextDocumentCompletionName, solution, request, clientCapabilities, cancellationToken);
public Task<LSP.SumType<LSP.CompletionItem[], LSP.CompletionList>?> GetCompletionsAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.CompletionParams, LSP.SumType<LSP.CompletionItem[], LSP.CompletionList>?>(LSP.Methods.TextDocumentCompletionName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a document highlights request by returning the highlights for a given document location.
......@@ -222,8 +224,8 @@ public Task<LSP.SymbolInformation[]> GetWorkspaceSymbolsAsync(Solution solution,
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the location(s) of a given symbol.</returns>
public Task<object> GoToDefinitionAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.TextDocumentPositionParams, object>(LSP.Methods.TextDocumentDefinitionName, solution, request, clientCapabilities, cancellationToken);
public Task<LSP.SumType<LSP.Location, LSP.Location[]>?> GoToDefinitionAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.SumType<LSP.Location, LSP.Location[]>?>(LSP.Methods.TextDocumentDefinitionName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a goto type definition request by returning the location of a given type definition.
......
......@@ -9,7 +9,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.LanguageServer;
......@@ -30,12 +29,12 @@ internal class InProcLanguageServer
private readonly IDiagnosticService _diagnosticService;
private readonly JsonRpc _jsonRpc;
private readonly LanguageServerProtocol _protocol;
private readonly Workspace _workspace;
private readonly CodeAnalysis.Workspace _workspace;
private VSClientCapabilities? _clientCapabilities;
private VSClientCapabilities _clientCapabilities;
public InProcLanguageServer(Stream inputStream, Stream outputStream, LanguageServerProtocol protocol,
Workspace workspace, IDiagnosticService diagnosticService)
CodeAnalysis.Workspace workspace, IDiagnosticService diagnosticService)
{
_protocol = protocol;
_workspace = workspace;
......@@ -45,6 +44,8 @@ internal class InProcLanguageServer
_diagnosticService = diagnosticService;
_diagnosticService.DiagnosticsUpdated += DiagnosticService_DiagnosticsUpdated;
_clientCapabilities = new VSClientCapabilities();
}
/// <summary>
......@@ -90,14 +91,14 @@ public void Exit()
}
[JsonRpcMethod(Methods.TextDocumentDefinitionName)]
public Task<object> GetTextDocumentDefinitionAsync(JToken input, CancellationToken cancellationToken)
public Task<SumType<Location, Location[]>?> GetTextDocumentDefinitionAsync(JToken input, CancellationToken cancellationToken)
{
var textDocumentPositionParams = input.ToObject<TextDocumentPositionParams>();
return _protocol.GoToDefinitionAsync(_workspace.CurrentSolution, textDocumentPositionParams, _clientCapabilities, cancellationToken);
}
[JsonRpcMethod(Methods.TextDocumentCompletionName)]
public Task<object> GetTextDocumentCompletionAsync(JToken input, CancellationToken cancellationToken)
public Task<SumType<CompletionItem[], CompletionList>?> GetTextDocumentCompletionAsync(JToken input, CancellationToken cancellationToken)
{
var completionParams = input.ToObject<CompletionParams>();
return _protocol.GetCompletionsAsync(_workspace.CurrentSolution, completionParams, _clientCapabilities, cancellationToken);
......@@ -139,7 +140,7 @@ public Task<TextEdit[]> GetTextDocumentFormattingOnTypeAsync(JToken input, Cance
}
[JsonRpcMethod(Methods.TextDocumentImplementationName)]
public Task<object> GetTextDocumentImplementationsAsync(JToken input, CancellationToken cancellationToken)
public Task<SumType<Location, Location[]>?> GetTextDocumentImplementationsAsync(JToken input, CancellationToken cancellationToken)
{
var textDocumentPositionParams = input.ToObject<TextDocumentPositionParams>();
return _protocol.FindImplementationsAsync(_workspace.CurrentSolution, textDocumentPositionParams, _clientCapabilities, cancellationToken);
......@@ -184,7 +185,7 @@ private async void DiagnosticService_DiagnosticsUpdated(object sender, Diagnosti
}
// Only publish document diagnostics for the languages this provider supports.
if (document.Project.Language != LanguageNames.CSharp && document.Project.Language != LanguageNames.VisualBasic)
if (document.Project.Language != CodeAnalysis.LanguageNames.CSharp && document.Project.Language != CodeAnalysis.LanguageNames.VisualBasic)
{
return;
}
......@@ -200,7 +201,7 @@ private async void DiagnosticService_DiagnosticsUpdated(object sender, Diagnosti
}
}
private async Task<LanguageServer.Protocol.Diagnostic[]> GetDiagnosticsAsync(Solution solution, Document document, CancellationToken cancellationToken)
private async Task<LanguageServer.Protocol.Diagnostic[]> GetDiagnosticsAsync(CodeAnalysis.Solution solution, CodeAnalysis.Document document, CancellationToken cancellationToken)
{
var diagnostics = _diagnosticService.GetDiagnostics(solution.Workspace, document.Project.Id, document.Id, null, false, cancellationToken);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
......
......@@ -4,15 +4,18 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Threading;
using Newtonsoft.Json.Linq;
using Roslyn.Test.Utilities.Remote;
using StreamJsonRpc;
......@@ -28,17 +31,13 @@ public async Task CSharpLanguageServiceTest()
{
var code = @"class Test { void Method() { } }";
using (var workspace = TestWorkspace.CreateCSharp(code))
{
var solution = workspace.CurrentSolution;
var results = await GetVsSearchResultsAsync(solution, WellKnownServiceHubServices.LanguageServer, "met");
using var workspace = TestWorkspace.CreateCSharp(code);
var solution = workspace.CurrentSolution;
Assert.Equal(1, results.Count);
Assert.Equal(1, results[0].Symbols.Length);
var results = await GetVsSearchResultsAsync(solution, WellKnownServiceHubServices.LanguageServer, "met");
Assert.Equal("Method", results[0].Symbols[0].Name);
}
Assert.Equal(1, results.Length);
Assert.Equal("Method", results[0].Name);
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
......@@ -53,15 +52,12 @@ public async Task CSharpLanguageServiceTest_MultipleResults()
int methodProperty { get; }
}";
using (var workspace = TestWorkspace.CreateCSharp(code))
{
var solution = workspace.CurrentSolution;
using var workspace = TestWorkspace.CreateCSharp(code);
var solution = workspace.CurrentSolution;
var results = await GetVsSearchResultsAsync(solution, WellKnownServiceHubServices.LanguageServer, "met");
var results = await GetVsSearchResultsAsync(solution, WellKnownServiceHubServices.LanguageServer, "met");
Assert.Equal(1, results.Count);
Assert.Equal(4, results[0].Symbols.Length);
}
Assert.Equal(4, results.Length);
}
......@@ -73,43 +69,52 @@ Sub Method()
End Sub
End Class";
using (var workspace = TestWorkspace.CreateVisualBasic(code))
{
var solution = workspace.CurrentSolution;
var results = await GetVsSearchResultsAsync(solution, WellKnownServiceHubServices.LanguageServer, "met");
using var workspace = TestWorkspace.CreateVisualBasic(code);
var solution = workspace.CurrentSolution;
Assert.Equal(1, results.Count);
Assert.Equal(1, results[0].Symbols.Length);
var results = await GetVsSearchResultsAsync(solution, WellKnownServiceHubServices.LanguageServer, "met");
Assert.Equal("Method", results[0].Symbols[0].Name);
}
Assert.Equal(1, results.Length);
Assert.Equal("Method", results[0].Name);
}
private async Task<List<VSPublishSymbolParams>> GetVsSearchResultsAsync(Solution solution, string server, string query)
private async Task<ImmutableArray<SymbolInformation>> GetVsSearchResultsAsync(Solution solution, string server, string query)
{
var client = (InProcRemoteHostClient)(await InProcRemoteHostClient.CreateAsync(solution.Workspace, runCacheCleanup: false));
var client = (InProcRemoteHostClient)await InProcRemoteHostClient.CreateAsync(solution.Workspace, runCacheCleanup: false);
var document = solution.Projects.First().Documents.First();
await UpdatePrimaryWorkspace(client, solution.WithDocumentFilePath(document.Id, @"c:\" + document.FilePath));
var callback = new Callback();
using (var jsonRpc = JsonRpc.Attach(await client.RequestServiceAsync(server), callback))
var workspaceSymbolParams = new WorkspaceSymbolParams
{
Query = query,
};
var symbolResultsBuilder = ArrayBuilder<SymbolInformation>.GetInstance();
#pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed
var awaitableProgress = new ProgressWithCompletion<SymbolInformation[]>(symbols =>
#pragma warning restore VSTHRD012 // Provide JoinableTaskFactory where allowed
{
symbolResultsBuilder.AddRange(symbols);
});
workspaceSymbolParams.Progress = awaitableProgress;
using (var jsonRpc = JsonRpc.Attach(await client.RequestServiceAsync(server)))
{
var result = await jsonRpc.InvokeWithCancellationAsync<JObject>(
Methods.InitializeName,
new object[] { new InitializeParams() },
CancellationToken.None);
Assert.True(result["capabilities"]["workspaceStreamingSymbolProvider"].ToObject<bool>());
Assert.True(result["capabilities"]["workspaceSymbolProvider"].ToObject<bool>());
var symbolResult = await jsonRpc.InvokeWithCancellationAsync<VSBeginSymbolParams>(
VSSymbolMethods.WorkspaceBeginSymbolName,
new object[] { query, 0 },
var symbolResult = await jsonRpc.InvokeWithCancellationAsync<SymbolInformation[]>(
Methods.WorkspaceSymbolName,
new object[] { workspaceSymbolParams },
CancellationToken.None);
}
return callback.Results;
return symbolResultsBuilder.ToImmutableAndFree();
}
// make sure we always move remote workspace forward
......@@ -125,18 +130,5 @@ private async Task UpdatePrimaryWorkspace(InProcRemoteHostClient client, Solutio
callbackTarget: null,
CancellationToken.None));
}
private class Callback
{
public List<VSPublishSymbolParams> Results = new List<VSPublishSymbolParams>();
[JsonRpcMethod(VSSymbolMethods.WorkspacePublishSymbolName)]
public Task WorkspacePublishSymbol(VSPublishSymbolParams symbols)
{
Results.Add(symbols);
return Task.CompletedTask;
}
}
}
}
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Immutable;
using System.IO;
......@@ -48,7 +50,8 @@ public Task<InitializeResult> Initialize(JToken input, CancellationToken cancell
{
Capabilities = new VSServerCapabilities()
{
WorkspaceStreamingSymbolProvider = true
DisableGoToWorkspaceSymbols = true,
WorkspaceSymbolProvider = true,
}
});
}
......@@ -60,7 +63,7 @@ public Task Initialized()
}
[JsonRpcMethod(Methods.ShutdownName)]
public void Shutdown(CancellationToken cancellationToken)
public void Shutdown(CancellationToken _)
{
// our language server shutdown when VS shutdown
// we have this so that we don't get log file every time VS shutdown
......@@ -73,8 +76,8 @@ public void Exit()
// we have this so that we don't get log file every time VS shutdown
}
[JsonRpcMethod(VSSymbolMethods.WorkspaceBeginSymbolName)]
public Task<VSBeginSymbolParams> BeginWorkspaceSymbolAsync(string query, int searchId, CancellationToken cancellationToken)
[JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)]
public Task<SymbolInformation[]> WorkspaceSymbolAsync(WorkspaceSymbolParams args, CancellationToken cancellationToken)
{
return RunServiceAsync(async () =>
{
......@@ -83,36 +86,52 @@ public Task<VSBeginSymbolParams> BeginWorkspaceSymbolAsync(string query, int sea
// for now, we use whatever solution we have currently. in future, we will add an ability to sync VS's current solution
// on demand from OOP side
// https://github.com/dotnet/roslyn/issues/37424
await SearchAsync(SolutionService.PrimaryWorkspace.CurrentSolution, query, searchId, cancellationToken).ConfigureAwait(false);
return new VSBeginSymbolParams();
var results = await SearchAsync(SolutionService.PrimaryWorkspace.CurrentSolution, args, cancellationToken).ConfigureAwait(false);
return results;
}
}, cancellationToken);
}
private async Task SearchAsync(Solution solution, string query, int searchId, CancellationToken cancellationToken)
private async Task<SymbolInformation[]> SearchAsync(Solution solution, WorkspaceSymbolParams args, CancellationToken cancellationToken)
{
var tasks = solution.Projects.Select(p => SearchProjectAsync(p, cancellationToken)).ToArray();
await Task.WhenAll(tasks).ConfigureAwait(false);
return;
async Task SearchProjectAsync(Project project, CancellationToken cancellationToken)
// When progress reporting is supported, report incrementally per project and return an empty result at the end.
// Otherwise aggregate and return the results for all projects at the end.
if (args.Progress != null)
{
var tasks = solution.Projects.Select(p => SearchProjectAndReportSymbolsAsync(p, args, cancellationToken)).ToArray();
await Task.WhenAll(tasks).ConfigureAwait(false);
return Array.Empty<SymbolInformation>();
}
else
{
cancellationToken.ThrowIfCancellationRequested();
var tasks = solution.Projects.Select(p => SearchProjectAsync(p, args.Query, cancellationToken)).ToArray();
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
return results.SelectMany(a => a).ToArray();
}
}
var results = await AbstractNavigateToSearchService.SearchProjectInCurrentProcessAsync(
project,
ImmutableArray<Document>.Empty,
query,
s_supportedKinds,
cancellationToken).ConfigureAwait(false);
private static async Task<SymbolInformation[]> SearchProjectAsync(Project project, string query, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var convertedResults = await ConvertAsync(results, cancellationToken).ConfigureAwait(false);
var results = await AbstractNavigateToSearchService.SearchProjectInCurrentProcessAsync(
project,
ImmutableArray<Document>.Empty,
query,
s_supportedKinds,
cancellationToken).ConfigureAwait(false);
await EndPoint.InvokeAsync(
VSSymbolMethods.WorkspacePublishSymbolName,
new object[] { new VSPublishSymbolParams() { SearchId = searchId, Symbols = convertedResults } },
cancellationToken).ConfigureAwait(false);
}
return await ConvertAsync(results, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Search the project and report the results back using <see cref="IProgress{T}"/>
/// <see cref="IProgress{T}.Report(T)"/> implementation for symbol search is threadsafe.
/// </summary>
private static async Task SearchProjectAndReportSymbolsAsync(Project project, WorkspaceSymbolParams args, CancellationToken cancellationToken)
{
var convertedResults = await SearchProjectAsync(project, args.Query, cancellationToken).ConfigureAwait(false);
args.Progress.Report(convertedResults);
}
private static async Task<VSSymbolInformation[]> ConvertAsync(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册