提交 639cb405 编写于 作者: M Marco Goertz

Addressed PR feedback including:

Replacing ILanguageServerProtocol with AbstractRequestHandlerProvider.
Refactoring common Extensions method code into generic helpers.
上级 0d5d2a9f
// 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.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer
{
internal abstract class AbstractRequestHandlerProvider
{
private readonly ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> _requestHandlers;
public AbstractRequestHandlerProvider(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, string? languageName = null)
=> _requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.LanguageName == languageName));
private static ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> CreateMethodToHandlerMap(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
{
var requestHandlerDictionary = ImmutableDictionary.CreateBuilder<string, Lazy<IRequestHandler, IRequestHandlerMetadata>>();
foreach (var lazyHandler in requestHandlers)
{
requestHandlerDictionary.Add(lazyHandler.Metadata.MethodName, lazyHandler);
}
return requestHandlerDictionary.ToImmutable();
}
public Task<ResponseType> ExecuteRequestAsync<RequestType, ResponseType>(string methodName, RequestType request, LSP.ClientCapabilities clientCapabilities,
string? clientName, CancellationToken cancellationToken) where RequestType : class
{
Contract.ThrowIfNull(request);
Contract.ThrowIfTrue(string.IsNullOrEmpty(methodName), "Invalid method name");
var handler = (IRequestHandler<RequestType, ResponseType>?)_requestHandlers[methodName]?.Value;
Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName));
return handler.HandleRequestAsync(request, clientCapabilities, clientName, cancellationToken);
}
}
}
...@@ -27,76 +27,15 @@ public static Uri GetURI(this TextDocument document) ...@@ -27,76 +27,15 @@ public static Uri GetURI(this TextDocument document)
public static ImmutableArray<Document> GetDocuments(this Solution solution, Uri documentUri) public static ImmutableArray<Document> GetDocuments(this Solution solution, Uri documentUri)
{ {
// TODO: we need to normalize this. but for now, we check both absolute and local path return GetDocuments<Document>(solution, documentUri, (s, i) => s.GetRequiredDocument(i));
// right now, based on who calls this, solution might has "/" or "\\" as directory
// separator
var documentIds = solution.GetDocumentIdsWithFilePath(documentUri.AbsolutePath);
if (!documentIds.Any())
{
documentIds = solution.GetDocumentIdsWithFilePath(documentUri.LocalPath);
}
return documentIds.SelectAsArray(id => solution.GetRequiredDocument(id));
} }
public static ImmutableArray<Document> GetDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName) public static ImmutableArray<TextDocument> GetTextDocuments(this Solution solution, Uri documentUri)
{ {
var documents = solutionProvider.GetDocuments(uri); return GetDocuments<TextDocument>(solution, documentUri, (s, i) => s.GetRequiredTextDocument(i));
// If we don't have a client name, then we're done filtering
if (clientName == null)
{
return documents;
}
// We have a client name, so we need to filter to only documents that match that name
return documents.WhereAsArray(document =>
{
var documentPropertiesService = document.Services.GetService<DocumentPropertiesService>();
// When a client name is specified, only return documents that have a matching client name.
// Allows the razor lsp server to return results only for razor documents.
// This workaround should be removed when https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1106064/
// is fixed (so that the razor language server is only asked about razor buffers).
return Equals(documentPropertiesService?.DiagnosticsLspClientName, clientName);
});
} }
public static Document? GetDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null) private static ImmutableArray<T> GetDocuments<T>(this Solution solution, Uri documentUri, Func<Solution, DocumentId, T> getDocument) where T : TextDocument
{
var documents = solutionProvider.GetDocuments(documentIdentifier.Uri, clientName);
if (documents.Length == 0)
{
return null;
}
if (documents.Length > 1)
{
// We have more than one document; try to find the one that matches the right context
if (documentIdentifier is VSTextDocumentIdentifier vsDocumentIdentifier)
{
if (vsDocumentIdentifier.ProjectContext != null)
{
var projectId = ProtocolConversions.ProjectContextToProjectId(vsDocumentIdentifier.ProjectContext);
var matchingDocument = documents.FirstOrDefault(d => d.Project.Id == projectId);
if (matchingDocument != null)
{
return matchingDocument;
}
}
}
}
// We either have only one document or have multiple, but none of them matched our context. In the
// latter case, we'll just return the first one arbitrarily since this might just be some temporary mis-sync
// of client and server state.
return documents[0];
}
public static ImmutableArray<TextDocument> GetTextDocuments(this Solution solution, Uri documentUri)
{ {
// TODO: we need to normalize this. but for now, we check both absolute and local path // TODO: we need to normalize this. but for now, we check both absolute and local path
// right now, based on who calls this, solution might has "/" or "\\" as directory // right now, based on who calls this, solution might has "/" or "\\" as directory
...@@ -108,12 +47,22 @@ public static ImmutableArray<TextDocument> GetTextDocuments(this Solution soluti ...@@ -108,12 +47,22 @@ public static ImmutableArray<TextDocument> GetTextDocuments(this Solution soluti
documentIds = solution.GetDocumentIdsWithFilePath(documentUri.LocalPath); documentIds = solution.GetDocumentIdsWithFilePath(documentUri.LocalPath);
} }
return documentIds.SelectAsArray(id => solution.GetRequiredTextDocument(id)); return documentIds.SelectAsArray(id => getDocument(solution, id));
}
public static ImmutableArray<Document> GetDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName)
{
return GetDocuments<Document>(solutionProvider, uri, (s, u, c) => s.GetDocuments(u), clientName);
} }
public static ImmutableArray<TextDocument> GetTextDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName) public static ImmutableArray<TextDocument> GetTextDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName)
{ {
var documents = solutionProvider.GetTextDocuments(uri, clientName); return GetDocuments<TextDocument>(solutionProvider, uri, (s, u, c) => s.GetTextDocuments(u), clientName);
}
private static ImmutableArray<T> GetDocuments<T>(this ILspSolutionProvider solutionProvider, Uri uri, Func<ILspSolutionProvider, Uri, string?, ImmutableArray<T>> getDocuments, string? clientName) where T : TextDocument
{
var documents = getDocuments(solutionProvider, uri, clientName);
// If we don't have a client name, then we're done filtering // If we don't have a client name, then we're done filtering
if (clientName == null) if (clientName == null)
...@@ -134,9 +83,19 @@ public static ImmutableArray<TextDocument> GetTextDocuments(this ILspSolutionPro ...@@ -134,9 +83,19 @@ public static ImmutableArray<TextDocument> GetTextDocuments(this ILspSolutionPro
}); });
} }
public static Document? GetDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null)
{
return GetDocument<Document>(solutionProvider, documentIdentifier, (s, d, c) => s.GetDocuments(d, c), clientName);
}
public static TextDocument? GetTextDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null) public static TextDocument? GetTextDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null)
{ {
var documents = solutionProvider.GetTextDocuments(documentIdentifier.Uri, clientName); return GetDocument<TextDocument>(solutionProvider, documentIdentifier, (s, d, c) => s.GetTextDocuments(d, c), clientName);
}
private static T? GetDocument<T>(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, Func<ILspSolutionProvider, Uri, string?, ImmutableArray<T>> getDocuments, string? clientName = null) where T : TextDocument
{
var documents = getDocuments(solutionProvider, documentIdentifier.Uri, clientName);
if (documents.Length == 0) if (documents.Length == 0)
{ {
......
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer
{
internal interface ILanguageServerProtocol
{
Task<ResponseType> ExecuteRequestAsync<RequestType, ResponseType>(string methodName, RequestType request, ClientCapabilities clientCapabilities, string clientName, CancellationToken cancellationToken) where RequestType : class;
}
}
...@@ -24,36 +24,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer ...@@ -24,36 +24,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer
/// </summary> /// </summary>
[Shared] [Shared]
[Export(typeof(LanguageServerProtocol))] [Export(typeof(LanguageServerProtocol))]
internal sealed class LanguageServerProtocol : ILanguageServerProtocol internal sealed class LanguageServerProtocol : AbstractRequestHandlerProvider
{ {
private readonly ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> _requestHandlers;
[ImportingConstructor] [ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public LanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers) public LanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
=> _requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.LanguageName == null)); : base(requestHandlers)
private static ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> CreateMethodToHandlerMap(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
{
var requestHandlerDictionary = ImmutableDictionary.CreateBuilder<string, Lazy<IRequestHandler, IRequestHandlerMetadata>>();
foreach (var lazyHandler in requestHandlers)
{
requestHandlerDictionary.Add(lazyHandler.Metadata.MethodName, lazyHandler);
}
return requestHandlerDictionary.ToImmutable();
}
public Task<ResponseType> ExecuteRequestAsync<RequestType, ResponseType>(string methodName, RequestType request, LSP.ClientCapabilities clientCapabilities,
string? clientName, CancellationToken cancellationToken) where RequestType : class
{ {
Contract.ThrowIfNull(request);
Contract.ThrowIfTrue(string.IsNullOrEmpty(methodName), "Invalid method name");
var handler = (IRequestHandler<RequestType, ResponseType>?)_requestHandlers[methodName]?.Value;
Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName));
return handler.HandleRequestAsync(request, clientCapabilities, clientName, cancellationToken);
} }
} }
} }
...@@ -22,7 +22,7 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient ...@@ -22,7 +22,7 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient
{ {
private readonly string? _diagnosticsClientName; private readonly string? _diagnosticsClientName;
private readonly IDiagnosticService _diagnosticService; private readonly IDiagnosticService _diagnosticService;
private readonly ILanguageServerProtocol _languageServerProtocol; private readonly AbstractRequestHandlerProvider _requestHandlerProvider;
private readonly Workspace _workspace; private readonly Workspace _workspace;
private InProcLanguageServer? _languageServer; private InProcLanguageServer? _languageServer;
...@@ -57,12 +57,12 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient ...@@ -57,12 +57,12 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient
/// </summary> /// </summary>
public event AsyncEventHandler<EventArgs>? StopAsync { add { } remove { } } public event AsyncEventHandler<EventArgs>? StopAsync { add { } remove { } }
public AbstractLanguageServerClient(ILanguageServerProtocol languageServerProtocol, public AbstractLanguageServerClient(AbstractRequestHandlerProvider requestHandlerProvider,
VisualStudioWorkspace workspace, VisualStudioWorkspace workspace,
IDiagnosticService diagnosticService, IDiagnosticService diagnosticService,
string? diagnosticsClientName) string? diagnosticsClientName)
{ {
_languageServerProtocol = languageServerProtocol; _requestHandlerProvider = requestHandlerProvider;
_workspace = workspace; _workspace = workspace;
_diagnosticService = diagnosticService; _diagnosticService = diagnosticService;
_diagnosticsClientName = diagnosticsClientName; _diagnosticsClientName = diagnosticsClientName;
...@@ -73,7 +73,7 @@ public Task<Connection> ActivateAsync(CancellationToken token) ...@@ -73,7 +73,7 @@ public Task<Connection> ActivateAsync(CancellationToken token)
Contract.ThrowIfTrue(_languageServer?.Running == true, "The language server has not yet shutdown."); Contract.ThrowIfTrue(_languageServer?.Running == true, "The language server has not yet shutdown.");
var (clientStream, serverStream) = FullDuplexStream.CreatePair(); var (clientStream, serverStream) = FullDuplexStream.CreatePair();
_languageServer = new InProcLanguageServer(serverStream, serverStream, _languageServerProtocol, _workspace, _languageServer = new InProcLanguageServer(serverStream, serverStream, _requestHandlerProvider, _workspace,
_diagnosticService, clientName: _diagnosticsClientName); _diagnosticService, clientName: _diagnosticsClientName);
return Task.FromResult(new Connection(clientStream, clientStream)); return Task.FromResult(new Connection(clientStream, clientStream));
} }
......
...@@ -36,7 +36,7 @@ internal class InProcLanguageServer ...@@ -36,7 +36,7 @@ internal class InProcLanguageServer
private readonly IDiagnosticService _diagnosticService; private readonly IDiagnosticService _diagnosticService;
private readonly string? _clientName; private readonly string? _clientName;
private readonly JsonRpc _jsonRpc; private readonly JsonRpc _jsonRpc;
private readonly ILanguageServerProtocol _protocol; private readonly AbstractRequestHandlerProvider _requestHandlerProvider;
private readonly CodeAnalysis.Workspace _workspace; private readonly CodeAnalysis.Workspace _workspace;
private VSClientCapabilities _clientCapabilities; private VSClientCapabilities _clientCapabilities;
...@@ -44,12 +44,12 @@ internal class InProcLanguageServer ...@@ -44,12 +44,12 @@ internal class InProcLanguageServer
public InProcLanguageServer(Stream inputStream, public InProcLanguageServer(Stream inputStream,
Stream outputStream, Stream outputStream,
ILanguageServerProtocol protocol, AbstractRequestHandlerProvider requestHandlerProvider,
CodeAnalysis.Workspace workspace, CodeAnalysis.Workspace workspace,
IDiagnosticService diagnosticService, IDiagnosticService diagnosticService,
string? clientName) string? clientName)
{ {
_protocol = protocol; _requestHandlerProvider = requestHandlerProvider;
_workspace = workspace; _workspace = workspace;
var jsonMessageFormatter = new JsonMessageFormatter(); var jsonMessageFormatter = new JsonMessageFormatter();
...@@ -79,7 +79,7 @@ public async Task<InitializeResult> InitializeAsync(InitializeParams initializeP ...@@ -79,7 +79,7 @@ public async Task<InitializeResult> InitializeAsync(InitializeParams initializeP
{ {
_clientCapabilities = (VSClientCapabilities)initializeParams.Capabilities; _clientCapabilities = (VSClientCapabilities)initializeParams.Capabilities;
var serverCapabilities = await _protocol.ExecuteRequestAsync<InitializeParams, InitializeResult>(Methods.InitializeName, var serverCapabilities = await _requestHandlerProvider.ExecuteRequestAsync<InitializeParams, InitializeResult>(Methods.InitializeName,
initializeParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false); initializeParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false);
// Always support hover - if any LSP client for a content type advertises support, // Always support hover - if any LSP client for a content type advertises support,
...@@ -98,7 +98,7 @@ public async Task InitializedAsync() ...@@ -98,7 +98,7 @@ public async Task InitializedAsync()
var openDocuments = _workspace.GetOpenDocumentIds(); var openDocuments = _workspace.GetOpenDocumentIds();
foreach (var documentId in openDocuments) foreach (var documentId in openDocuments)
{ {
var document = solution.GetTextDocument(documentId); var document = solution.GetDocument(documentId);
if (document != null) if (document != null)
{ {
await PublishDiagnosticsAsync(document).ConfigureAwait(false); await PublishDiagnosticsAsync(document).ConfigureAwait(false);
...@@ -137,78 +137,78 @@ public Task ExitAsync(CancellationToken _) ...@@ -137,78 +137,78 @@ public Task ExitAsync(CancellationToken _)
[JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)]
public Task<LSP.Location[]> GetTextDocumentDefinitionAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) public Task<LSP.Location[]> GetTextDocumentDefinitionAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<TextDocumentPositionParams, LSP.Location[]>(Methods.TextDocumentDefinitionName, => _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, LSP.Location[]>(Methods.TextDocumentDefinitionName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)]
public Task<WorkspaceEdit> GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken) public Task<WorkspaceEdit> GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<RenameParams, WorkspaceEdit>(Methods.TextDocumentRenameName, => _requestHandlerProvider.ExecuteRequestAsync<RenameParams, WorkspaceEdit>(Methods.TextDocumentRenameName,
renameParams, _clientCapabilities, _clientName, cancellationToken); renameParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)]
public Task<VSReferenceItem[]> GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken) public Task<VSReferenceItem[]> GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<ReferenceParams, VSReferenceItem[]>(Methods.TextDocumentReferencesName, => _requestHandlerProvider.ExecuteRequestAsync<ReferenceParams, VSReferenceItem[]>(Methods.TextDocumentReferencesName,
referencesParams, _clientCapabilities, _clientName, cancellationToken); referencesParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentCompletionName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentCompletionName, UseSingleObjectParameterDeserialization = true)]
public async Task<SumType<CompletionList, CompletionItem[]>> GetTextDocumentCompletionAsync(CompletionParams completionParams, CancellationToken cancellationToken) public async Task<SumType<CompletionList, CompletionItem[]>> GetTextDocumentCompletionAsync(CompletionParams completionParams, CancellationToken cancellationToken)
// Convert to sumtype before reporting to work around https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1107698 // Convert to sumtype before reporting to work around https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1107698
=> await _protocol.ExecuteRequestAsync<CompletionParams, CompletionItem[]>(Methods.TextDocumentCompletionName, => await _requestHandlerProvider.ExecuteRequestAsync<CompletionParams, CompletionItem[]>(Methods.TextDocumentCompletionName,
completionParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false); completionParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false);
[JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)]
public Task<CompletionItem> ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken) public Task<CompletionItem> ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<CompletionItem, CompletionItem>(Methods.TextDocumentCompletionResolveName, => _requestHandlerProvider.ExecuteRequestAsync<CompletionItem, CompletionItem>(Methods.TextDocumentCompletionResolveName,
completionItem, _clientCapabilities, _clientName, cancellationToken); completionItem, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)]
public Task<DocumentHighlight[]> GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) public Task<DocumentHighlight[]> GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<TextDocumentPositionParams, DocumentHighlight[]>(Methods.TextDocumentDocumentHighlightName, => _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, DocumentHighlight[]>(Methods.TextDocumentDocumentHighlightName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)]
public Task<Hover?> GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) public Task<Hover?> GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<TextDocumentPositionParams, Hover?>(Methods.TextDocumentHoverName, => _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, Hover?>(Methods.TextDocumentHoverName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)]
public Task<object[]> GetTextDocumentDocumentSymbolsAsync(DocumentSymbolParams documentSymbolParams, CancellationToken cancellationToken) public Task<object[]> GetTextDocumentDocumentSymbolsAsync(DocumentSymbolParams documentSymbolParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<DocumentSymbolParams, object[]>(Methods.TextDocumentDocumentSymbolName, => _requestHandlerProvider.ExecuteRequestAsync<DocumentSymbolParams, object[]>(Methods.TextDocumentDocumentSymbolName,
documentSymbolParams, _clientCapabilities, _clientName, cancellationToken); documentSymbolParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentFormattingName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentFormattingAsync(DocumentFormattingParams documentFormattingParams, CancellationToken cancellationToken) public Task<TextEdit[]> GetTextDocumentFormattingAsync(DocumentFormattingParams documentFormattingParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<DocumentFormattingParams, TextEdit[]>(Methods.TextDocumentFormattingName, => _requestHandlerProvider.ExecuteRequestAsync<DocumentFormattingParams, TextEdit[]>(Methods.TextDocumentFormattingName,
documentFormattingParams, _clientCapabilities, _clientName, cancellationToken); documentFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentOnTypeFormattingName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentOnTypeFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentFormattingOnTypeAsync(DocumentOnTypeFormattingParams documentOnTypeFormattingParams, CancellationToken cancellationToken) public Task<TextEdit[]> GetTextDocumentFormattingOnTypeAsync(DocumentOnTypeFormattingParams documentOnTypeFormattingParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<DocumentOnTypeFormattingParams, TextEdit[]>(Methods.TextDocumentOnTypeFormattingName, => _requestHandlerProvider.ExecuteRequestAsync<DocumentOnTypeFormattingParams, TextEdit[]>(Methods.TextDocumentOnTypeFormattingName,
documentOnTypeFormattingParams, _clientCapabilities, _clientName, cancellationToken); documentOnTypeFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentImplementationName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentImplementationName, UseSingleObjectParameterDeserialization = true)]
public Task<LSP.Location[]> GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) public Task<LSP.Location[]> GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<TextDocumentPositionParams, LSP.Location[]>(Methods.TextDocumentImplementationName, => _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, LSP.Location[]>(Methods.TextDocumentImplementationName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentRangeFormattingName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentRangeFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentRangeFormattingAsync(DocumentRangeFormattingParams documentRangeFormattingParams, CancellationToken cancellationToken) public Task<TextEdit[]> GetTextDocumentRangeFormattingAsync(DocumentRangeFormattingParams documentRangeFormattingParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<DocumentRangeFormattingParams, TextEdit[]>(Methods.TextDocumentRangeFormattingName, => _requestHandlerProvider.ExecuteRequestAsync<DocumentRangeFormattingParams, TextEdit[]>(Methods.TextDocumentRangeFormattingName,
documentRangeFormattingParams, _clientCapabilities, _clientName, cancellationToken); documentRangeFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentSignatureHelpName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.TextDocumentSignatureHelpName, UseSingleObjectParameterDeserialization = true)]
public Task<SignatureHelp> GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) public Task<SignatureHelp> GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<TextDocumentPositionParams, SignatureHelp>(Methods.TextDocumentSignatureHelpName, => _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, SignatureHelp>(Methods.TextDocumentSignatureHelpName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)]
public Task<SymbolInformation[]> GetWorkspaceSymbolsAsync(WorkspaceSymbolParams workspaceSymbolParams, CancellationToken cancellationToken) public Task<SymbolInformation[]> GetWorkspaceSymbolsAsync(WorkspaceSymbolParams workspaceSymbolParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<WorkspaceSymbolParams, SymbolInformation[]>(Methods.WorkspaceSymbolName, => _requestHandlerProvider.ExecuteRequestAsync<WorkspaceSymbolParams, SymbolInformation[]>(Methods.WorkspaceSymbolName,
workspaceSymbolParams, _clientCapabilities, _clientName, cancellationToken); workspaceSymbolParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(MSLSPMethods.ProjectContextsName, UseSingleObjectParameterDeserialization = true)] [JsonRpcMethod(MSLSPMethods.ProjectContextsName, UseSingleObjectParameterDeserialization = true)]
public Task<ActiveProjectContexts?> GetProjectContextsAsync(GetTextDocumentWithContextParams textDocumentWithContextParams, CancellationToken cancellationToken) public Task<ActiveProjectContexts?> GetProjectContextsAsync(GetTextDocumentWithContextParams textDocumentWithContextParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<GetTextDocumentWithContextParams, ActiveProjectContexts?>(MSLSPMethods.ProjectContextsName, => _requestHandlerProvider.ExecuteRequestAsync<GetTextDocumentWithContextParams, ActiveProjectContexts?>(MSLSPMethods.ProjectContextsName,
textDocumentWithContextParams, _clientCapabilities, _clientName, cancellationToken); textDocumentWithContextParams, _clientCapabilities, _clientName, cancellationToken);
#pragma warning disable VSTHRD100 // Avoid async void methods #pragma warning disable VSTHRD100 // Avoid async void methods
...@@ -275,7 +275,7 @@ private async void DiagnosticService_DiagnosticsUpdated(object sender, Diagnosti ...@@ -275,7 +275,7 @@ private async void DiagnosticService_DiagnosticsUpdated(object sender, Diagnosti
private static readonly Comparer<Uri> s_uriComparer = Comparer<Uri>.Create((uri1, uri2) private static readonly Comparer<Uri> s_uriComparer = Comparer<Uri>.Create((uri1, uri2)
=> Uri.Compare(uri1, uri2, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); => Uri.Compare(uri1, uri2, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase));
internal async Task PublishDiagnosticsAsync(CodeAnalysis.TextDocument document) internal async Task PublishDiagnosticsAsync(CodeAnalysis.Document document)
{ {
// Retrieve all diagnostics for the current document grouped by their actual file uri. // Retrieve all diagnostics for the current document grouped by their actual file uri.
var fileUriToDiagnostics = await GetDiagnosticsAsync(document, CancellationToken.None).ConfigureAwait(false); var fileUriToDiagnostics = await GetDiagnosticsAsync(document, CancellationToken.None).ConfigureAwait(false);
...@@ -348,7 +348,7 @@ private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray<Lang ...@@ -348,7 +348,7 @@ private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray<Lang
await _jsonRpc.NotifyWithParameterObjectAsync(Methods.TextDocumentPublishDiagnosticsName, publishDiagnosticsParams).ConfigureAwait(false); await _jsonRpc.NotifyWithParameterObjectAsync(Methods.TextDocumentPublishDiagnosticsName, publishDiagnosticsParams).ConfigureAwait(false);
} }
private async Task<Dictionary<Uri, ImmutableArray<LanguageServer.Protocol.Diagnostic>>> GetDiagnosticsAsync(CodeAnalysis.TextDocument document, CancellationToken cancellationToken) private async Task<Dictionary<Uri, ImmutableArray<LanguageServer.Protocol.Diagnostic>>> GetDiagnosticsAsync(CodeAnalysis.Document document, CancellationToken cancellationToken)
{ {
var diagnostics = _diagnosticService.GetDiagnostics(document.Project.Solution.Workspace, document.Project.Id, document.Id, null, false, cancellationToken) var diagnostics = _diagnosticService.GetDiagnostics(document.Project.Solution.Workspace, document.Project.Id, document.Id, null, false, cancellationToken)
.Where(IncludeDiagnostic); .Where(IncludeDiagnostic);
...@@ -367,7 +367,7 @@ private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray<Lang ...@@ -367,7 +367,7 @@ private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray<Lang
group => group.Select(diagnostic => ConvertToLspDiagnostic(diagnostic, text)).ToImmutableArray()); group => group.Select(diagnostic => ConvertToLspDiagnostic(diagnostic, text)).ToImmutableArray());
return fileUriToDiagnostics; return fileUriToDiagnostics;
static Uri GetDiagnosticUri(TextDocument document, DiagnosticData diagnosticData) static Uri GetDiagnosticUri(Document document, DiagnosticData diagnosticData)
{ {
Contract.ThrowIfNull(diagnosticData.DataLocation, "Diagnostic data location should not be null here"); Contract.ThrowIfNull(diagnosticData.DataLocation, "Diagnostic data location should not be null here");
......
// 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.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.ServiceHub.Client;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServices.Remote;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Xaml.Implementation.LanguageService
{
//[ContentType(ContentTypeNames.XamlContentType)]
//[Export(typeof(ILanguageClient))]
//[Export(typeof(LanguageServerClient))]
internal sealed class LanguageServerClient : ILanguageClient
{
private const string ServiceHubClientName = "Xaml.IDE.LanguageServer";
private readonly IThreadingContext _threadingContext;
private readonly HostWorkspaceServices _services;
private readonly IEnumerable<Lazy<IOptionPersister>> _lazyOptions;
/// <summary>
/// Gets the name of the language client (displayed to the user).
/// </summary>
public string Name => Resources.Xaml_Language_Server_Client;
/// <summary>
/// Gets the configuration section names for the language client. This may be null if the language client
/// does not provide settings.
/// </summary>
public IEnumerable<string> ConfigurationSections { get; }
/// <summary>
/// Gets the initialization options object the client wants to send when 'initialize' message is sent.
/// This may be null if the client does not need custom initialization options.
/// </summary>
public object InitializationOptions { get; }
/// <summary>
/// Gets the list of file names to watch for changes. Changes will be sent to the server via 'workspace/didChangeWatchedFiles'
/// message. The files to watch must be under the current active workspace. The file names can be specified as a relative
/// paths to the exact file, or as glob patterns following the standard in .gitignore see https://www.kernel.org/pub/software/scm/git/docs/gitignore.html files.
/// </summary>
public IEnumerable<string> FilesToWatch { get; }
public event AsyncEventHandler<EventArgs> StartAsync;
public event AsyncEventHandler<EventArgs> StopAsync { add { } remove { } }
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public LanguageServerClient(
IThreadingContext threadingContext,
VisualStudioWorkspace workspace,
[ImportMany] IEnumerable<Lazy<IOptionPersister>> lazyOptions)
{
_threadingContext = threadingContext;
_services = workspace.Services;
_lazyOptions = lazyOptions;
}
public async Task<Connection> ActivateAsync(CancellationToken cancellationToken)
{
var client = await RemoteHostClient.TryGetClientAsync(_services, cancellationToken).ConfigureAwait(false);
if (client == null)
{
// There is no OOP. either user turned it off, or process got killed.
// We should have already gotten a gold bar + nfw already if the OOP is missing.
// so just log telemetry here so we can connect the two with session explorer.
Logger.Log(FunctionId.LanguageServer_ActivateFailed, KeyValueLogMessage.NoProperty);
return null;
}
var hostGroup = new HostGroup(client.ClientId);
var hubClient = new HubClient(ServiceHubClientName);
var stream = await ServiceHubRemoteHostClient.RequestServiceAsync(
_services,
hubClient,
WellKnownServiceHubService.LanguageServer,
hostGroup,
cancellationToken).ConfigureAwait(false);
return new Connection(stream, stream);
}
/// <summary>
/// Signals that the extension has been loaded.
/// The caller expects that <see cref="ActivateAsync(CancellationToken)"/> can be called
/// immediately following the completion of this method.
/// </summary>
public async Task OnLoadedAsync()
{
// initialize things on UI thread
await InitializeOnUIAsync().ConfigureAwait(false);
// wait until remote host is available before let platform know that they can activate our LSP
var client = await RemoteHostClient.TryGetClientAsync(_services, CancellationToken.None).ConfigureAwait(false);
if (client == null)
{
// There is no OOP. either user turned it off, or process got killed.
// We should have already gotten a gold bar + nfw already if the OOP is missing.
// so just log telemetry here so we can connect the two with session explorer.
Logger.Log(FunctionId.LanguageServer_OnLoadedFailed, KeyValueLogMessage.NoProperty);
// don't ask platform to start LSP.
// we shouldn't throw as the LSP client does not expect exceptions here.
return;
}
// let platform know that they can start us
await StartAsync.InvokeAsync(this, EventArgs.Empty).ConfigureAwait(false);
async Task InitializeOnUIAsync()
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();
// this doesn't attempt to solve our JTF and some services being not free-thread issue here, but
// try to fix this particular deadlock issue only. we already have long discussion on
// how we need to deal with JTF, Roslyn service requirements and VS services reality conflicting
// each others. architectural fix should come from the result of that discussion.
// Ensure the options persisters are loaded since we have to fetch options from the shell
_lazyOptions.Select(o => o.Value);
// experimentation service unfortunately uses JTF to jump to UI thread in certain cases
// which can cause deadlock if 2 parties try to enable OOP from BG and then FG before
// experimentation service tries to jump to UI thread.
var experimentationService = _services.GetService<IExperimentationService>();
}
}
/// <summary>
/// Signals the extension that the language server has been successfully initialized.
/// </summary>
public Task OnServerInitializedAsync()
=> Task.CompletedTask;
/// <summary>
/// Signals the extension that the language server failed to initialize.
/// </summary>
public Task OnServerInitializeFailedAsync(Exception e)
=> Task.CompletedTask;
}
}
...@@ -17,17 +17,14 @@ ...@@ -17,17 +17,14 @@
namespace Microsoft.VisualStudio.LanguageServices.Xaml namespace Microsoft.VisualStudio.LanguageServices.Xaml
{ {
[ContentType(ContentTypeNames.XamlContentType)] [ContentType(ContentTypeNames.XamlContentType)]
[ClientName(ClientName)]
[DisableUserExperience(disableUserExperience: true)] // Remove this when we are ready to use LSP everywhere [DisableUserExperience(disableUserExperience: true)] // Remove this when we are ready to use LSP everywhere
[Export(typeof(ILanguageClient))] [Export(typeof(ILanguageClient))]
internal class XamlLanguageServerClient : AbstractLanguageServerClient internal class XamlLanguageServerClient : AbstractLanguageServerClient
{ {
public const string ClientName = "XAML";
[ImportingConstructor] [ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, true)] [Obsolete(MefConstruction.ImportingConstructorMessage, true)]
public XamlLanguageServerClient(XamlLanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService) public XamlLanguageServerClient(XamlLanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService)
: base(languageServerProtocol, workspace, diagnosticService, ClientName) : base(languageServerProtocol, workspace, diagnosticService, diagnosticsClientName: null)
{ {
} }
......
...@@ -32,21 +32,9 @@ public Task<InitializeResult> HandleRequestAsync(InitializeParams request, Clien ...@@ -32,21 +32,9 @@ public Task<InitializeResult> HandleRequestAsync(InitializeParams request, Clien
{ {
Capabilities = new ServerCapabilities Capabilities = new ServerCapabilities
{ {
CompletionProvider = new CompletionOptions { ResolveProvider = true, TriggerCharacters = new string[] { "<", " ", ":", ".", "=", "\"", "'", "{", "," } }, CompletionProvider = new CompletionOptions { ResolveProvider = true, TriggerCharacters = new string[] { "<", " ", ":", ".", "=", "\"", "'", "{", ",", "(" } },
HoverProvider = true, HoverProvider = true,
//FoldingRangeProvider = new FoldingRangeProviderOptions(), FoldingRangeProvider = new FoldingRangeProviderOptions { },
//DocumentHighlightProvider = true,
//DocumentFormattingProvider = true,
//DocumentRangeFormattingProvider = true,
//DocumentOnTypeFormattingProvider = new LSP.DocumentOnTypeFormattingOptions { FirstTriggerCharacter = "}", MoreTriggerCharacter = new[] { ";", "\n" } },
//DefinitionProvider = true,
//ImplementationProvider = true,
//ReferencesProvider = true,
//ProjectContextProvider = true,
//RenameProvider = true,
//DocumentSymbolProvider = true,
//WorkspaceSymbolProvider = true,
//SignatureHelpProvider = new LSP.SignatureHelpOptions { TriggerCharacters = new[] { "{", "," } },
TextDocumentSync = new TextDocumentSyncOptions TextDocumentSync = new TextDocumentSyncOptions
{ {
Change = TextDocumentSyncKind.None Change = TextDocumentSyncKind.None
......
...@@ -6,18 +6,11 @@ ...@@ -6,18 +6,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition; using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Xaml; using Microsoft.CodeAnalysis.Editor.Xaml;
using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer
{ {
...@@ -26,36 +19,13 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer ...@@ -26,36 +19,13 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer
/// </summary> /// </summary>
[Shared] [Shared]
[Export(typeof(XamlLanguageServerProtocol))] [Export(typeof(XamlLanguageServerProtocol))]
internal sealed class XamlLanguageServerProtocol : ILanguageServerProtocol internal sealed class XamlLanguageServerProtocol : AbstractRequestHandlerProvider
{ {
private readonly ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> _requestHandlers;
[ImportingConstructor] [ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public XamlLanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers) public XamlLanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
=> _requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.LanguageName == StringConstants.XamlLanguageName)); : base(requestHandlers, languageName: StringConstants.XamlLanguageName)
private static ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> CreateMethodToHandlerMap(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
{
var requestHandlerDictionary = ImmutableDictionary.CreateBuilder<string, Lazy<IRequestHandler, IRequestHandlerMetadata>>();
foreach (var lazyHandler in requestHandlers)
{
requestHandlerDictionary.Add(lazyHandler.Metadata.MethodName, lazyHandler);
}
return requestHandlerDictionary.ToImmutable();
}
public Task<ResponseType> ExecuteRequestAsync<RequestType, ResponseType>(string methodName, RequestType request, LSP.ClientCapabilities clientCapabilities,
string? clientName, CancellationToken cancellationToken) where RequestType : class
{ {
Contract.ThrowIfNull(request);
Contract.ThrowIfTrue(string.IsNullOrEmpty(methodName), "Invalid method name");
var handler = (IRequestHandler<RequestType, ResponseType>?)_requestHandlers[methodName]?.Value;
Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName));
return handler.HandleRequestAsync(request, clientCapabilities, clientName, cancellationToken);
} }
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册