From 639cb405d361a1974a19ccff9de8e1aaadbaa3a4 Mon Sep 17 00:00:00 2001 From: Marco Goertz Date: Wed, 22 Jul 2020 16:44:23 -0700 Subject: [PATCH] Addressed PR feedback including: Replacing ILanguageServerProtocol with AbstractRequestHandlerProvider. Refactoring common Extensions method code into generic helpers. --- .../AbstractRequestHandlerProvider.cs | 49 ++++++ .../Protocol/Extensions/Extensions.cs | 95 +++-------- .../Protocol/ILanguageServerProtocol.cs | 15 -- .../Protocol/LanguageServerProtocol.cs | 27 +-- .../AbstractLanguageServerClient.cs | 8 +- .../LanguageClient/InProcLanguageServer.cs | 46 ++--- .../LanguageClient/LanguageServerClient.cs | 160 ------------------ .../XamlLanguageServerClient.cs | 5 +- .../Handler/Initialize/InitializeHandler.cs | 16 +- .../XamlLanguageServerProtocol.cs | 34 +--- 10 files changed, 110 insertions(+), 345 deletions(-) create mode 100644 src/Features/LanguageServer/Protocol/AbstractRequestHandlerProvider.cs delete mode 100644 src/Features/LanguageServer/Protocol/ILanguageServerProtocol.cs delete mode 100644 src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/LanguageServerClient.cs diff --git a/src/Features/LanguageServer/Protocol/AbstractRequestHandlerProvider.cs b/src/Features/LanguageServer/Protocol/AbstractRequestHandlerProvider.cs new file mode 100644 index 00000000000..0f1da80995e --- /dev/null +++ b/src/Features/LanguageServer/Protocol/AbstractRequestHandlerProvider.cs @@ -0,0 +1,49 @@ +// 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> _requestHandlers; + + public AbstractRequestHandlerProvider(IEnumerable> requestHandlers, string? languageName = null) + => _requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.LanguageName == languageName)); + + private static ImmutableDictionary> CreateMethodToHandlerMap(IEnumerable> requestHandlers) + { + var requestHandlerDictionary = ImmutableDictionary.CreateBuilder>(); + foreach (var lazyHandler in requestHandlers) + { + requestHandlerDictionary.Add(lazyHandler.Metadata.MethodName, lazyHandler); + } + + return requestHandlerDictionary.ToImmutable(); + } + + public Task ExecuteRequestAsync(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?)_requestHandlers[methodName]?.Value; + Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName)); + + return handler.HandleRequestAsync(request, clientCapabilities, clientName, cancellationToken); + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index f3170b64a99..df50b325a74 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -27,76 +27,15 @@ public static Uri GetURI(this TextDocument document) public static ImmutableArray GetDocuments(this Solution solution, Uri documentUri) { - // 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 - // separator - var documentIds = solution.GetDocumentIdsWithFilePath(documentUri.AbsolutePath); - - if (!documentIds.Any()) - { - documentIds = solution.GetDocumentIdsWithFilePath(documentUri.LocalPath); - } - - return documentIds.SelectAsArray(id => solution.GetRequiredDocument(id)); + return GetDocuments(solution, documentUri, (s, i) => s.GetRequiredDocument(i)); } - public static ImmutableArray GetDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName) + public static ImmutableArray GetTextDocuments(this Solution solution, Uri documentUri) { - var documents = solutionProvider.GetDocuments(uri); - - // 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(); - - // 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); - }); + return GetDocuments(solution, documentUri, (s, i) => s.GetRequiredTextDocument(i)); } - public static Document? GetDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null) - { - 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 GetTextDocuments(this Solution solution, Uri documentUri) + private static ImmutableArray GetDocuments(this Solution solution, Uri documentUri, Func getDocument) where T : TextDocument { // 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 @@ -108,12 +47,22 @@ public static ImmutableArray GetTextDocuments(this Solution soluti documentIds = solution.GetDocumentIdsWithFilePath(documentUri.LocalPath); } - return documentIds.SelectAsArray(id => solution.GetRequiredTextDocument(id)); + return documentIds.SelectAsArray(id => getDocument(solution, id)); + } + + public static ImmutableArray GetDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName) + { + return GetDocuments(solutionProvider, uri, (s, u, c) => s.GetDocuments(u), clientName); } public static ImmutableArray GetTextDocuments(this ILspSolutionProvider solutionProvider, Uri uri, string? clientName) { - var documents = solutionProvider.GetTextDocuments(uri, clientName); + return GetDocuments(solutionProvider, uri, (s, u, c) => s.GetTextDocuments(u), clientName); + } + + private static ImmutableArray GetDocuments(this ILspSolutionProvider solutionProvider, Uri uri, Func> 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 (clientName == null) @@ -134,9 +83,19 @@ public static ImmutableArray GetTextDocuments(this ILspSolutionPro }); } + public static Document? GetDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null) + { + return GetDocument(solutionProvider, documentIdentifier, (s, d, c) => s.GetDocuments(d, c), clientName); + } + public static TextDocument? GetTextDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, string? clientName = null) { - var documents = solutionProvider.GetTextDocuments(documentIdentifier.Uri, clientName); + return GetDocument(solutionProvider, documentIdentifier, (s, d, c) => s.GetTextDocuments(d, c), clientName); + } + + private static T? GetDocument(this ILspSolutionProvider solutionProvider, TextDocumentIdentifier documentIdentifier, Func> getDocuments, string? clientName = null) where T : TextDocument + { + var documents = getDocuments(solutionProvider, documentIdentifier.Uri, clientName); if (documents.Length == 0) { diff --git a/src/Features/LanguageServer/Protocol/ILanguageServerProtocol.cs b/src/Features/LanguageServer/Protocol/ILanguageServerProtocol.cs deleted file mode 100644 index 28fd913841c..00000000000 --- a/src/Features/LanguageServer/Protocol/ILanguageServerProtocol.cs +++ /dev/null @@ -1,15 +0,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 ExecuteRequestAsync(string methodName, RequestType request, ClientCapabilities clientCapabilities, string clientName, CancellationToken cancellationToken) where RequestType : class; - } -} diff --git a/src/Features/LanguageServer/Protocol/LanguageServerProtocol.cs b/src/Features/LanguageServer/Protocol/LanguageServerProtocol.cs index c830ffc4327..43e0558cb79 100644 --- a/src/Features/LanguageServer/Protocol/LanguageServerProtocol.cs +++ b/src/Features/LanguageServer/Protocol/LanguageServerProtocol.cs @@ -24,36 +24,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer /// [Shared] [Export(typeof(LanguageServerProtocol))] - internal sealed class LanguageServerProtocol : ILanguageServerProtocol + internal sealed class LanguageServerProtocol : AbstractRequestHandlerProvider { - private readonly ImmutableDictionary> _requestHandlers; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public LanguageServerProtocol([ImportMany] IEnumerable> requestHandlers) - => _requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.LanguageName == null)); - - private static ImmutableDictionary> CreateMethodToHandlerMap(IEnumerable> requestHandlers) - { - var requestHandlerDictionary = ImmutableDictionary.CreateBuilder>(); - foreach (var lazyHandler in requestHandlers) - { - requestHandlerDictionary.Add(lazyHandler.Metadata.MethodName, lazyHandler); - } - - return requestHandlerDictionary.ToImmutable(); - } - - public Task ExecuteRequestAsync(string methodName, RequestType request, LSP.ClientCapabilities clientCapabilities, - string? clientName, CancellationToken cancellationToken) where RequestType : class + : base(requestHandlers) { - Contract.ThrowIfNull(request); - Contract.ThrowIfTrue(string.IsNullOrEmpty(methodName), "Invalid method name"); - - var handler = (IRequestHandler?)_requestHandlers[methodName]?.Value; - Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName)); - - return handler.HandleRequestAsync(request, clientCapabilities, clientName, cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractLanguageServerClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractLanguageServerClient.cs index f9ca809ca24..676c1d08d77 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractLanguageServerClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractLanguageServerClient.cs @@ -22,7 +22,7 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient { private readonly string? _diagnosticsClientName; private readonly IDiagnosticService _diagnosticService; - private readonly ILanguageServerProtocol _languageServerProtocol; + private readonly AbstractRequestHandlerProvider _requestHandlerProvider; private readonly Workspace _workspace; private InProcLanguageServer? _languageServer; @@ -57,12 +57,12 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient /// public event AsyncEventHandler? StopAsync { add { } remove { } } - public AbstractLanguageServerClient(ILanguageServerProtocol languageServerProtocol, + public AbstractLanguageServerClient(AbstractRequestHandlerProvider requestHandlerProvider, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService, string? diagnosticsClientName) { - _languageServerProtocol = languageServerProtocol; + _requestHandlerProvider = requestHandlerProvider; _workspace = workspace; _diagnosticService = diagnosticService; _diagnosticsClientName = diagnosticsClientName; @@ -73,7 +73,7 @@ public Task ActivateAsync(CancellationToken token) Contract.ThrowIfTrue(_languageServer?.Running == true, "The language server has not yet shutdown."); var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - _languageServer = new InProcLanguageServer(serverStream, serverStream, _languageServerProtocol, _workspace, + _languageServer = new InProcLanguageServer(serverStream, serverStream, _requestHandlerProvider, _workspace, _diagnosticService, clientName: _diagnosticsClientName); return Task.FromResult(new Connection(clientStream, clientStream)); } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs index c0818320557..f5158afaf56 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs @@ -36,7 +36,7 @@ internal class InProcLanguageServer private readonly IDiagnosticService _diagnosticService; private readonly string? _clientName; private readonly JsonRpc _jsonRpc; - private readonly ILanguageServerProtocol _protocol; + private readonly AbstractRequestHandlerProvider _requestHandlerProvider; private readonly CodeAnalysis.Workspace _workspace; private VSClientCapabilities _clientCapabilities; @@ -44,12 +44,12 @@ internal class InProcLanguageServer public InProcLanguageServer(Stream inputStream, Stream outputStream, - ILanguageServerProtocol protocol, + AbstractRequestHandlerProvider requestHandlerProvider, CodeAnalysis.Workspace workspace, IDiagnosticService diagnosticService, string? clientName) { - _protocol = protocol; + _requestHandlerProvider = requestHandlerProvider; _workspace = workspace; var jsonMessageFormatter = new JsonMessageFormatter(); @@ -79,7 +79,7 @@ public async Task InitializeAsync(InitializeParams initializeP { _clientCapabilities = (VSClientCapabilities)initializeParams.Capabilities; - var serverCapabilities = await _protocol.ExecuteRequestAsync(Methods.InitializeName, + var serverCapabilities = await _requestHandlerProvider.ExecuteRequestAsync(Methods.InitializeName, initializeParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false); // Always support hover - if any LSP client for a content type advertises support, @@ -98,7 +98,7 @@ public async Task InitializedAsync() var openDocuments = _workspace.GetOpenDocumentIds(); foreach (var documentId in openDocuments) { - var document = solution.GetTextDocument(documentId); + var document = solution.GetDocument(documentId); if (document != null) { await PublishDiagnosticsAsync(document).ConfigureAwait(false); @@ -137,78 +137,78 @@ public Task ExitAsync(CancellationToken _) [JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentDefinitionAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentDefinitionName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentDefinitionName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentRenameName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentRenameName, renameParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentReferencesName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentReferencesName, referencesParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentCompletionName, UseSingleObjectParameterDeserialization = true)] public async Task> GetTextDocumentCompletionAsync(CompletionParams completionParams, CancellationToken cancellationToken) // Convert to sumtype before reporting to work around https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1107698 - => await _protocol.ExecuteRequestAsync(Methods.TextDocumentCompletionName, + => await _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentCompletionName, completionParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false); [JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)] public Task ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentCompletionResolveName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentCompletionResolveName, completionItem, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentDocumentHighlightName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentDocumentHighlightName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentHoverName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentHoverName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentDocumentSymbolsAsync(DocumentSymbolParams documentSymbolParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentDocumentSymbolName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentDocumentSymbolName, documentSymbolParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentFormattingName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentFormattingAsync(DocumentFormattingParams documentFormattingParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentFormattingName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentFormattingName, documentFormattingParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentOnTypeFormattingName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentFormattingOnTypeAsync(DocumentOnTypeFormattingParams documentOnTypeFormattingParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentOnTypeFormattingName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentOnTypeFormattingName, documentOnTypeFormattingParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentImplementationName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentImplementationName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentImplementationName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentRangeFormattingName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentRangeFormattingAsync(DocumentRangeFormattingParams documentRangeFormattingParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentRangeFormattingName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentRangeFormattingName, documentRangeFormattingParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.TextDocumentSignatureHelpName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.TextDocumentSignatureHelpName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentSignatureHelpName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)] public Task GetWorkspaceSymbolsAsync(WorkspaceSymbolParams workspaceSymbolParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(Methods.WorkspaceSymbolName, + => _requestHandlerProvider.ExecuteRequestAsync(Methods.WorkspaceSymbolName, workspaceSymbolParams, _clientCapabilities, _clientName, cancellationToken); [JsonRpcMethod(MSLSPMethods.ProjectContextsName, UseSingleObjectParameterDeserialization = true)] public Task GetProjectContextsAsync(GetTextDocumentWithContextParams textDocumentWithContextParams, CancellationToken cancellationToken) - => _protocol.ExecuteRequestAsync(MSLSPMethods.ProjectContextsName, + => _requestHandlerProvider.ExecuteRequestAsync(MSLSPMethods.ProjectContextsName, textDocumentWithContextParams, _clientCapabilities, _clientName, cancellationToken); #pragma warning disable VSTHRD100 // Avoid async void methods @@ -275,7 +275,7 @@ private async void DiagnosticService_DiagnosticsUpdated(object sender, Diagnosti private static readonly Comparer s_uriComparer = Comparer.Create((uri1, uri2) => 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. var fileUriToDiagnostics = await GetDiagnosticsAsync(document, CancellationToken.None).ConfigureAwait(false); @@ -348,7 +348,7 @@ private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray>> GetDiagnosticsAsync(CodeAnalysis.TextDocument document, CancellationToken cancellationToken) + private async Task>> GetDiagnosticsAsync(CodeAnalysis.Document document, CancellationToken cancellationToken) { var diagnostics = _diagnosticService.GetDiagnostics(document.Project.Solution.Workspace, document.Project.Id, document.Id, null, false, cancellationToken) .Where(IncludeDiagnostic); @@ -367,7 +367,7 @@ private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray group.Select(diagnostic => ConvertToLspDiagnostic(diagnostic, text)).ToImmutableArray()); 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"); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/LanguageServerClient.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/LanguageServerClient.cs deleted file mode 100644 index 001a4d1cb15..00000000000 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/LanguageServerClient.cs +++ /dev/null @@ -1,160 +0,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; -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> _lazyOptions; - - /// - /// Gets the name of the language client (displayed to the user). - /// - public string Name => Resources.Xaml_Language_Server_Client; - - /// - /// Gets the configuration section names for the language client. This may be null if the language client - /// does not provide settings. - /// - public IEnumerable ConfigurationSections { get; } - - /// - /// 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. - /// - public object InitializationOptions { get; } - - /// - /// 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. - /// - public IEnumerable FilesToWatch { get; } - - public event AsyncEventHandler StartAsync; - public event AsyncEventHandler StopAsync { add { } remove { } } - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public LanguageServerClient( - IThreadingContext threadingContext, - VisualStudioWorkspace workspace, - [ImportMany] IEnumerable> lazyOptions) - { - _threadingContext = threadingContext; - _services = workspace.Services; - _lazyOptions = lazyOptions; - } - - public async Task 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); - } - - /// - /// Signals that the extension has been loaded. - /// The caller expects that can be called - /// immediately following the completion of this method. - /// - 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(); - } - } - - /// - /// Signals the extension that the language server has been successfully initialized. - /// - public Task OnServerInitializedAsync() - => Task.CompletedTask; - - /// - /// Signals the extension that the language server failed to initialize. - /// - public Task OnServerInitializeFailedAsync(Exception e) - => Task.CompletedTask; - } -} diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlLanguageServerClient.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlLanguageServerClient.cs index f64544fb00c..5c94f549e2e 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlLanguageServerClient.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageClient/XamlLanguageServerClient.cs @@ -17,17 +17,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml { [ContentType(ContentTypeNames.XamlContentType)] - [ClientName(ClientName)] [DisableUserExperience(disableUserExperience: true)] // Remove this when we are ready to use LSP everywhere [Export(typeof(ILanguageClient))] internal class XamlLanguageServerClient : AbstractLanguageServerClient { - public const string ClientName = "XAML"; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, true)] public XamlLanguageServerClient(XamlLanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService) - : base(languageServerProtocol, workspace, diagnosticService, ClientName) + : base(languageServerProtocol, workspace, diagnosticService, diagnosticsClientName: null) { } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Initialize/InitializeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Initialize/InitializeHandler.cs index ca6a9c9e379..9cc943e4c52 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Initialize/InitializeHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Initialize/InitializeHandler.cs @@ -32,21 +32,9 @@ public Task HandleRequestAsync(InitializeParams request, Clien { Capabilities = new ServerCapabilities { - CompletionProvider = new CompletionOptions { ResolveProvider = true, TriggerCharacters = new string[] { "<", " ", ":", ".", "=", "\"", "'", "{", "," } }, + CompletionProvider = new CompletionOptions { ResolveProvider = true, TriggerCharacters = new string[] { "<", " ", ":", ".", "=", "\"", "'", "{", ",", "(" } }, HoverProvider = true, - //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[] { "{", "," } }, + FoldingRangeProvider = new FoldingRangeProviderOptions { }, TextDocumentSync = new TextDocumentSyncOptions { Change = TextDocumentSyncKind.None diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlLanguageServerProtocol.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlLanguageServerProtocol.cs index 06213990897..9564e047c33 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlLanguageServerProtocol.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/XamlLanguageServerProtocol.cs @@ -6,18 +6,11 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Xaml; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Roslyn.Utilities; -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer { @@ -26,36 +19,13 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer /// [Shared] [Export(typeof(XamlLanguageServerProtocol))] - internal sealed class XamlLanguageServerProtocol : ILanguageServerProtocol + internal sealed class XamlLanguageServerProtocol : AbstractRequestHandlerProvider { - private readonly ImmutableDictionary> _requestHandlers; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public XamlLanguageServerProtocol([ImportMany] IEnumerable> requestHandlers) - => _requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.LanguageName == StringConstants.XamlLanguageName)); - - private static ImmutableDictionary> CreateMethodToHandlerMap(IEnumerable> requestHandlers) - { - var requestHandlerDictionary = ImmutableDictionary.CreateBuilder>(); - foreach (var lazyHandler in requestHandlers) - { - requestHandlerDictionary.Add(lazyHandler.Metadata.MethodName, lazyHandler); - } - - return requestHandlerDictionary.ToImmutable(); - } - - public Task ExecuteRequestAsync(string methodName, RequestType request, LSP.ClientCapabilities clientCapabilities, - string? clientName, CancellationToken cancellationToken) where RequestType : class + : base(requestHandlers, languageName: StringConstants.XamlLanguageName) { - Contract.ThrowIfNull(request); - Contract.ThrowIfTrue(string.IsNullOrEmpty(methodName), "Invalid method name"); - - var handler = (IRequestHandler?)_requestHandlers[methodName]?.Value; - Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName)); - - return handler.HandleRequestAsync(request, clientCapabilities, clientName, cancellationToken); } } } -- GitLab