提交 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)
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
// 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<Document>(solution, documentUri, (s, i) => s.GetRequiredDocument(i));
}
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);
// 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);
});
return GetDocuments<TextDocument>(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<TextDocument> GetTextDocuments(this Solution solution, Uri documentUri)
private static ImmutableArray<T> GetDocuments<T>(this Solution solution, Uri documentUri, Func<Solution, DocumentId, T> 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<TextDocument> 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<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)
{
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 (clientName == null)
......@@ -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)
{
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)
{
......
// 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
/// </summary>
[Shared]
[Export(typeof(LanguageServerProtocol))]
internal sealed class LanguageServerProtocol : ILanguageServerProtocol
internal sealed class LanguageServerProtocol : AbstractRequestHandlerProvider
{
private readonly ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> _requestHandlers;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public LanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
=> _requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.LanguageName == null));
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
: base(requestHandlers)
{
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
{
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
/// </summary>
public event AsyncEventHandler<EventArgs>? 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<Connection> 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));
}
......
......@@ -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<InitializeResult> InitializeAsync(InitializeParams initializeP
{
_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);
// 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<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);
[JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)]
public Task<WorkspaceEdit> GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<RenameParams, WorkspaceEdit>(Methods.TextDocumentRenameName,
=> _requestHandlerProvider.ExecuteRequestAsync<RenameParams, WorkspaceEdit>(Methods.TextDocumentRenameName,
renameParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)]
public Task<VSReferenceItem[]> GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<ReferenceParams, VSReferenceItem[]>(Methods.TextDocumentReferencesName,
=> _requestHandlerProvider.ExecuteRequestAsync<ReferenceParams, VSReferenceItem[]>(Methods.TextDocumentReferencesName,
referencesParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentCompletionName, UseSingleObjectParameterDeserialization = true)]
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
=> await _protocol.ExecuteRequestAsync<CompletionParams, CompletionItem[]>(Methods.TextDocumentCompletionName,
=> await _requestHandlerProvider.ExecuteRequestAsync<CompletionParams, CompletionItem[]>(Methods.TextDocumentCompletionName,
completionParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false);
[JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)]
public Task<CompletionItem> ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<CompletionItem, CompletionItem>(Methods.TextDocumentCompletionResolveName,
=> _requestHandlerProvider.ExecuteRequestAsync<CompletionItem, CompletionItem>(Methods.TextDocumentCompletionResolveName,
completionItem, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)]
public Task<DocumentHighlight[]> GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<TextDocumentPositionParams, DocumentHighlight[]>(Methods.TextDocumentDocumentHighlightName,
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, DocumentHighlight[]>(Methods.TextDocumentDocumentHighlightName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)]
public Task<Hover?> GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<TextDocumentPositionParams, Hover?>(Methods.TextDocumentHoverName,
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, Hover?>(Methods.TextDocumentHoverName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)]
public Task<object[]> GetTextDocumentDocumentSymbolsAsync(DocumentSymbolParams documentSymbolParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<DocumentSymbolParams, object[]>(Methods.TextDocumentDocumentSymbolName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentSymbolParams, object[]>(Methods.TextDocumentDocumentSymbolName,
documentSymbolParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentFormattingAsync(DocumentFormattingParams documentFormattingParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<DocumentFormattingParams, TextEdit[]>(Methods.TextDocumentFormattingName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentFormattingParams, TextEdit[]>(Methods.TextDocumentFormattingName,
documentFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentOnTypeFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentFormattingOnTypeAsync(DocumentOnTypeFormattingParams documentOnTypeFormattingParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<DocumentOnTypeFormattingParams, TextEdit[]>(Methods.TextDocumentOnTypeFormattingName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentOnTypeFormattingParams, TextEdit[]>(Methods.TextDocumentOnTypeFormattingName,
documentOnTypeFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentImplementationName, UseSingleObjectParameterDeserialization = true)]
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);
[JsonRpcMethod(Methods.TextDocumentRangeFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentRangeFormattingAsync(DocumentRangeFormattingParams documentRangeFormattingParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<DocumentRangeFormattingParams, TextEdit[]>(Methods.TextDocumentRangeFormattingName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentRangeFormattingParams, TextEdit[]>(Methods.TextDocumentRangeFormattingName,
documentRangeFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentSignatureHelpName, UseSingleObjectParameterDeserialization = true)]
public Task<SignatureHelp> GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<TextDocumentPositionParams, SignatureHelp>(Methods.TextDocumentSignatureHelpName,
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, SignatureHelp>(Methods.TextDocumentSignatureHelpName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)]
public Task<SymbolInformation[]> GetWorkspaceSymbolsAsync(WorkspaceSymbolParams workspaceSymbolParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<WorkspaceSymbolParams, SymbolInformation[]>(Methods.WorkspaceSymbolName,
=> _requestHandlerProvider.ExecuteRequestAsync<WorkspaceSymbolParams, SymbolInformation[]>(Methods.WorkspaceSymbolName,
workspaceSymbolParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(MSLSPMethods.ProjectContextsName, UseSingleObjectParameterDeserialization = true)]
public Task<ActiveProjectContexts?> GetProjectContextsAsync(GetTextDocumentWithContextParams textDocumentWithContextParams, CancellationToken cancellationToken)
=> _protocol.ExecuteRequestAsync<GetTextDocumentWithContextParams, ActiveProjectContexts?>(MSLSPMethods.ProjectContextsName,
=> _requestHandlerProvider.ExecuteRequestAsync<GetTextDocumentWithContextParams, ActiveProjectContexts?>(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<Uri> s_uriComparer = Comparer<Uri>.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<Lang
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)
.Where(IncludeDiagnostic);
......@@ -367,7 +367,7 @@ private async Task SendDiagnosticsNotificationAsync(Uri uri, ImmutableArray<Lang
group => 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");
......
// 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 @@
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)
{
}
......
......@@ -32,21 +32,9 @@ public Task<InitializeResult> 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
......
......@@ -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
/// </summary>
[Shared]
[Export(typeof(XamlLanguageServerProtocol))]
internal sealed class XamlLanguageServerProtocol : ILanguageServerProtocol
internal sealed class XamlLanguageServerProtocol : AbstractRequestHandlerProvider
{
private readonly ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> _requestHandlers;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public XamlLanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
=> _requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.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
: base(requestHandlers, languageName: StringConstants.XamlLanguageName)
{
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.
先完成此消息的编辑!
想要评论请 注册