// 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 Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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 LanguageServerProtocol _protocol;
private readonly CodeAnalysis.Workspace _workspace;
// The VS LSP client supports streaming using IProgress on various requests.
// However, this is not yet supported through Live Share, so deserialization fails on the IProgress property.
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1043376 tracks Live Share support for this (committed for 16.6).
// Additionally, the VS LSP client will be changing the type of 'tagSupport' from bool to an object in future LSP package versions.
// Since updating our package versions is extraordinarily difficult, add this workaround so we aren't broken when they change the type.
// https://github.com/dotnet/roslyn/issues/40829 tracks updating our package versions to remove this workaround.
internal static readonly JsonSerializer JsonSerializer = JsonSerializer.Create(new JsonSerializerSettings
{
Error = (sender, args) =>
{
if (object.Equals(args.ErrorContext.Member, "partialResultToken")
|| (object.Equals(args.ErrorContext.Member, "tagSupport") && args.ErrorContext.OriginalObject.GetType() == typeof(PublishDiagnosticsSetting)))
{
args.ErrorContext.Handled = true;
}
}
});
private VSClientCapabilities _clientCapabilities;
public InProcLanguageServer(Stream inputStream, Stream outputStream, LanguageServerProtocol protocol,
CodeAnalysis.Workspace workspace, IDiagnosticService diagnosticService, string? clientName)
{
_protocol = protocol;
_workspace = workspace;
_jsonRpc = new JsonRpc(outputStream, inputStream, this);
_jsonRpc.StartListening();
_diagnosticService = diagnosticService;
_clientName = clientName;
_diagnosticService.DiagnosticsUpdated += DiagnosticService_DiagnosticsUpdated;
_clientCapabilities = new VSClientCapabilities();
}
///
/// 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)]
public Task InitializeAsync(JToken input, CancellationToken cancellationToken)
{
// InitializeParams only references ClientCapabilities, but the VS LSP client
// sends additional VS specific capabilities, so directly deserialize them into the VSClientCapabilities
// to avoid losing them.
_clientCapabilities = input["capabilities"].ToObject(JsonSerializer);
return _protocol.InitializeAsync(_workspace.CurrentSolution, input.ToObject(JsonSerializer), _clientCapabilities, cancellationToken);
}
[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 object? Shutdown(CancellationToken _) => null;
[JsonRpcMethod(Methods.ExitName)]
public void Exit()
{
}
[JsonRpcMethod(Methods.TextDocumentDefinitionName)]
public Task?> GetTextDocumentDefinitionAsync(JToken input, CancellationToken cancellationToken)
{
var textDocumentPositionParams = input.ToObject(JsonSerializer);
return _protocol.GoToDefinitionAsync(_workspace.CurrentSolution, textDocumentPositionParams, _clientCapabilities, cancellationToken);
}
[JsonRpcMethod(Methods.TextDocumentRenameName)]
public Task GetTextDocumentRenameAsync(JToken input, CancellationToken cancellationToken)
{
var renameParams = input.ToObject();
return _protocol.RenameAsync(_workspace.CurrentSolution, renameParams, _clientCapabilities, cancellationToken);
}
[JsonRpcMethod(Methods.TextDocumentCompletionName)]
public Task?> GetTextDocumentCompletionAsync(JToken input, CancellationToken cancellationToken)
{
var completionParams = input.ToObject(JsonSerializer);
return _protocol.GetCompletionsAsync(_workspace.CurrentSolution, completionParams, _clientCapabilities, cancellationToken);
}
[JsonRpcMethod(Methods.TextDocumentCompletionResolveName)]
public Task ResolveCompletionItemAsync(JToken input, CancellationToken cancellationToken)
{
var completionItem = input.ToObject(JsonSerializer);
return _protocol.ResolveCompletionItemAsync(_workspace.CurrentSolution, completionItem, _clientCapabilities, cancellationToken);
}
[JsonRpcMethod(Methods.TextDocumentDocumentHighlightName)]
public Task GetTextDocumentDocumentHighlightsAsync(JToken input, CancellationToken cancellationToken)
{
var textDocumentPositionParams = input.ToObject(JsonSerializer);
return _protocol.GetDocumentHighlightAsync(_workspace.CurrentSolution, textDocumentPositionParams, _clientCapabilities, cancellationToken);
}
[JsonRpcMethod(Methods.TextDocumentDocumentSymbolName)]
public Task