// 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.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Utilities.Internal;
using Roslyn.Utilities;
using StreamJsonRpc;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService
{
///
/// Defines the language server to be hooked up to an using StreamJsonRpc.
/// This runs in proc as not all features provided by this server are available out of proc (e.g. some diagnostics).
///
internal class InProcLanguageServer
{
private readonly IDiagnosticService _diagnosticService;
private readonly string? _clientName;
private readonly JsonRpc _jsonRpc;
private readonly AbstractRequestHandlerProvider _requestHandlerProvider;
private readonly CodeAnalysis.Workspace _workspace;
private VSClientCapabilities _clientCapabilities;
private bool _shuttingDown;
public InProcLanguageServer(Stream inputStream,
Stream outputStream,
AbstractRequestHandlerProvider requestHandlerProvider,
CodeAnalysis.Workspace workspace,
IDiagnosticService diagnosticService,
string? clientName)
{
_requestHandlerProvider = requestHandlerProvider;
_workspace = workspace;
var jsonMessageFormatter = new JsonMessageFormatter();
jsonMessageFormatter.JsonSerializer.Converters.Add(new VSExtensionConverter());
jsonMessageFormatter.JsonSerializer.Converters.Add(new VSExtensionConverter());
_jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter));
_jsonRpc.AddLocalRpcTarget(this);
_jsonRpc.StartListening();
_diagnosticService = diagnosticService;
_clientName = clientName;
_diagnosticService.DiagnosticsUpdated += DiagnosticService_DiagnosticsUpdated;
_clientCapabilities = new VSClientCapabilities();
}
public bool Running => !_shuttingDown && !_jsonRpc.IsDisposed;
///
/// Handle the LSP initialize request by storing the client capabilities
/// and responding with the server capabilities.
/// The specification assures that the initialize request is sent only once.
///
[JsonRpcMethod(Methods.InitializeName, UseSingleObjectParameterDeserialization = true)]
public async Task InitializeAsync(InitializeParams initializeParams, CancellationToken cancellationToken)
{
_clientCapabilities = (VSClientCapabilities)initializeParams.Capabilities;
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,
// then the liveshare provider is disabled. So we must provide for both C# and razor
// until https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1106064/ is fixed
// or we have different content types.
serverCapabilities.Capabilities.HoverProvider = true;
return serverCapabilities;
}
[JsonRpcMethod(Methods.InitializedName)]
public async Task InitializedAsync()
{
// Publish diagnostics for all open documents immediately following initialization.
var solution = _workspace.CurrentSolution;
var openDocuments = _workspace.GetOpenDocumentIds();
foreach (var documentId in openDocuments)
{
var document = solution.GetDocument(documentId);
if (document != null)
{
await PublishDiagnosticsAsync(document).ConfigureAwait(false);
}
}
}
[JsonRpcMethod(Methods.ShutdownName)]
public Task ShutdownAsync(CancellationToken _)
{
Contract.ThrowIfTrue(_shuttingDown, "Shutdown has already been called.");
_shuttingDown = true;
_diagnosticService.DiagnosticsUpdated -= DiagnosticService_DiagnosticsUpdated;
return Task.CompletedTask;
}
[JsonRpcMethod(Methods.ExitName)]
public Task ExitAsync(CancellationToken _)
{
Contract.ThrowIfFalse(_shuttingDown, "Shutdown has not been called yet.");
try
{
_jsonRpc.Dispose();
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
// Swallow exceptions thrown by disposing our JsonRpc object. Disconnected events can potentially throw their own exceptions so
// we purposefully ignore all of those exceptions in an effort to shutdown gracefully.
}
return Task.CompletedTask;
}
[JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)]
public Task GetTextDocumentDefinitionAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentDefinitionName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)]
public Task GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentRenameName,
renameParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)]
public Task GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken)
=> _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 _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentCompletionName,
completionParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false);
[JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)]
public Task ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentCompletionResolveName,
completionItem, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)]
public Task GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentDocumentHighlightName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)]
public Task GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentHoverName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)]
public Task