Skip to content

Commit

Permalink
Allow language specific handlers to use their own types for serializa…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
dibarbet committed Feb 22, 2024
1 parent 25aa74d commit 0edf3bc
Show file tree
Hide file tree
Showing 20 changed files with 574 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.Threading;
using Nerdbank.Streams;
using Newtonsoft.Json;
using Roslyn.LanguageServer.Protocol;
using StreamJsonRpc;

Expand Down Expand Up @@ -213,6 +214,7 @@ internal async Task<AbstractLanguageServer<RequestContext>> CreateAsync<TRequest
var hostServices = VisualStudioMefHostServices.Create(_exportProvider);
var server = Create(
jsonRpc,
jsonMessageFormatter.JsonSerializer,
languageClient,
serverKind,
logger,
Expand All @@ -224,6 +226,7 @@ internal async Task<AbstractLanguageServer<RequestContext>> CreateAsync<TRequest

public virtual AbstractLanguageServer<RequestContext> Create(
JsonRpc jsonRpc,
JsonSerializer jsonSerializer,
ICapabilitiesProvider capabilitiesProvider,
WellKnownLspServerKinds serverKind,
AbstractLspLogger logger,
Expand All @@ -232,6 +235,7 @@ public virtual AbstractLanguageServer<RequestContext> Create(
var server = new RoslynLanguageServer(
LspServiceProvider,
jsonRpc,
jsonSerializer,
capabilitiesProvider,
logger,
hostServices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,13 @@ private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(U
}
};

internal static JsonMessageFormatter CreateJsonMessageFormatter()
{
var messageFormatter = new JsonMessageFormatter();
LSP.VSInternalExtensionUtilities.AddVSInternalExtensionConverters(messageFormatter.JsonSerializer);
return messageFormatter;
}

internal sealed class TestLspServer : IAsyncDisposable
{
public readonly EditorTestWorkspace TestWorkspace;
Expand Down Expand Up @@ -569,13 +576,6 @@ private void InitializeClientRpc()
Assert.False(workspaceWaiter.HasPendingWork);
}

private static JsonMessageFormatter CreateJsonMessageFormatter()
{
var messageFormatter = new JsonMessageFormatter();
LSP.VSInternalExtensionUtilities.AddVSInternalExtensionConverters(messageFormatter.JsonSerializer);
return messageFormatter;
}

internal static async Task<TestLspServer> CreateAsync(EditorTestWorkspace testWorkspace, InitializationOptions initializationOptions, AbstractLspLogger logger)
{
var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution);
Expand Down Expand Up @@ -617,20 +617,15 @@ internal static async Task<TestLspServer> CreateAsync(EditorTestWorkspace testWo
private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, EditorTestWorkspace workspace, WellKnownLspServerKinds serverKind, AbstractLspLogger logger)
{
var capabilitiesProvider = workspace.ExportProvider.GetExportedValue<ExperimentalCapabilitiesProvider>();
var servicesProvider = workspace.ExportProvider.GetExportedValue<CSharpVisualBasicLspServiceProvider>();
var factory = workspace.ExportProvider.GetExportedValue<ILanguageServerFactory>();

var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, CreateJsonMessageFormatter()))
var jsonMessageFormatter = CreateJsonMessageFormatter();
var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter))
{
ExceptionStrategy = ExceptionProcessing.ISerializable,
};

var languageServer = new RoslynLanguageServer(
servicesProvider, jsonRpc,
capabilitiesProvider,
logger,
workspace.Services.HostServices,
ProtocolConstants.RoslynLspLanguages,
serverKind);
var languageServer = (RoslynLanguageServer)factory.Create(jsonRpc, jsonMessageFormatter.JsonSerializer, capabilitiesProvider, serverKind, logger, workspace.Services.HostServices);

jsonRpc.StartListening();
return languageServer;
Expand All @@ -650,6 +645,16 @@ private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Str
return result;
}

public Task ExecuteNotificationAsync<RequestType>(string methodName, RequestType request) where RequestType : class
{
return _clientRpc.NotifyWithParameterObjectAsync(methodName, request);
}

public Task ExecuteNotification0Async(string methodName)
{
return _clientRpc.NotifyWithParameterObjectAsync(methodName);
}

public async Task OpenDocumentAsync(Uri documentUri, string? text = null, string languageId = "")
{
if (text == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ internal sealed class LanguageServerHost

public LanguageServerHost(Stream inputStream, Stream outputStream, ExportProvider exportProvider, ILogger logger)
{
var handler = new HeaderDelimitedMessageHandler(outputStream, inputStream, new JsonMessageFormatter());
var messageFormatter = new JsonMessageFormatter();
var handler = new HeaderDelimitedMessageHandler(outputStream, inputStream, messageFormatter);

// If there is a jsonrpc disconnect or server shutdown, that is handled by the AbstractLanguageServer. No need to do anything here.
_jsonRpc = new JsonRpc(handler)
Expand All @@ -43,7 +44,7 @@ public LanguageServerHost(Stream inputStream, Stream outputStream, ExportProvide
var lspLogger = new LspServiceLogger(_logger);

var hostServices = exportProvider.GetExportedValue<HostServicesProvider>().HostServices;
_roslynLanguageServer = roslynLspFactory.Create(_jsonRpc, capabilitiesProvider, WellKnownLspServerKinds.CSharpVisualBasicLspServer, lspLogger, hostServices);
_roslynLanguageServer = roslynLspFactory.Create(_jsonRpc, messageFormatter.JsonSerializer, capabilitiesProvider, WellKnownLspServerKinds.CSharpVisualBasicLspServer, lspLogger, hostServices);
}

public void Start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using Microsoft.CommonLanguageServerProtocol.Framework.Handlers;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Roslyn.LanguageServer.Protocol;
using StreamJsonRpc;

Expand All @@ -14,7 +15,7 @@ public class ExampleLanguageServer : AbstractLanguageServer<ExampleRequestContex
{
private readonly Action<IServiceCollection>? _addExtraHandlers;

public ExampleLanguageServer(JsonRpc jsonRpc, ILspLogger logger, Action<IServiceCollection>? addExtraHandlers) : base(jsonRpc, logger)
public ExampleLanguageServer(JsonRpc jsonRpc, JsonSerializer jsonSerializer, ILspLogger logger, Action<IServiceCollection>? addExtraHandlers) : base(jsonRpc, jsonSerializer, logger)
{
_addExtraHandlers = addExtraHandlers;
// This spins up the queue and ensure the LSP is ready to start receiving requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading;
using System.Threading.Tasks;
using Nerdbank.Streams;
using Newtonsoft.Json;
using StreamJsonRpc;
using Xunit;

Expand All @@ -15,7 +16,7 @@ public class RequestExecutionQueueTests
{
private class MockServer : AbstractLanguageServer<TestRequestContext>
{
public MockServer() : base(new JsonRpc(new HeaderDelimitedMessageHandler(FullDuplexStream.CreatePair().Item1)), NoOpLspLogger.Instance)
public MockServer() : base(new JsonRpc(new HeaderDelimitedMessageHandler(FullDuplexStream.CreatePair().Item1)), JsonSerializer.CreateDefault(), NoOpLspLogger.Instance)
{
}

Expand Down Expand Up @@ -50,7 +51,7 @@ public async Task ExecuteAsync_ThrowCompletes()
var lspServices = GetLspServices();

// Act & Assert
await Assert.ThrowsAsync<NotImplementedException>(() => requestExecutionQueue.ExecuteAsync<int, string>(1, ThrowingHandler.Name, lspServices, CancellationToken.None));
await Assert.ThrowsAsync<NotImplementedException>(() => requestExecutionQueue.ExecuteAsync<int, string>(1, ThrowingHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None));
}

[Fact]
Expand All @@ -71,12 +72,12 @@ public async Task ExecuteAsync_WithCancelInProgressWork_CancelsInProgressWorkWhe
var cancellingRequestCancellationToken = new CancellationToken();
var completingRequestCancellationToken = new CancellationToken();

var _ = requestExecutionQueue.ExecuteAsync<int, string>(1, CancellingHandler.Name, lspServices, cancellingRequestCancellationToken);
var _1 = requestExecutionQueue.ExecuteAsync<int, string>(1, CompletingHandler.Name, lspServices, completingRequestCancellationToken);
var _ = requestExecutionQueue.ExecuteAsync<int, string>(1, CancellingHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, cancellingRequestCancellationToken);
var _1 = requestExecutionQueue.ExecuteAsync<int, string>(1, CompletingHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, completingRequestCancellationToken);

// Act & Assert
// A Debug.Assert would throw if the tasks hadn't completed when the mutating request is called.
await requestExecutionQueue.ExecuteAsync<int, string>(1, MutatingHandler.Name, lspServices, CancellationToken.None);
await requestExecutionQueue.ExecuteAsync<int, string>(1, MutatingHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None);
}
}

Expand All @@ -99,7 +100,7 @@ public async Task ExecuteAsync_CompletesTask()
var requestExecutionQueue = GetRequestExecutionQueue(false, (TestMethodHandler.Metadata, TestMethodHandler.Instance));
var lspServices = GetLspServices();

var response = await requestExecutionQueue.ExecuteAsync<int, string>(request: 1, TestMethodHandler.Name, lspServices, CancellationToken.None);
var response = await requestExecutionQueue.ExecuteAsync<int, string>(request: 1, TestMethodHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None);
Assert.Equal("stuff", response);
}

Expand All @@ -109,7 +110,7 @@ public async Task ExecuteAsync_CompletesTask_Parameterless()
var requestExecutionQueue = GetRequestExecutionQueue(false, (TestParameterlessMethodHandler.Metadata, TestParameterlessMethodHandler.Instance));
var lspServices = GetLspServices();

var response = await requestExecutionQueue.ExecuteAsync<NoValue, bool>(request: NoValue.Instance, TestParameterlessMethodHandler.Name, lspServices, CancellationToken.None);
var response = await requestExecutionQueue.ExecuteAsync<NoValue, bool>(request: NoValue.Instance, TestParameterlessMethodHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None);
Assert.True(response);
}

Expand All @@ -119,7 +120,7 @@ public async Task ExecuteAsync_CompletesTask_Notification()
var requestExecutionQueue = GetRequestExecutionQueue(false, (TestNotificationHandler.Metadata, TestNotificationHandler.Instance));
var lspServices = GetLspServices();

var response = await requestExecutionQueue.ExecuteAsync<bool, NoValue>(request: true, TestNotificationHandler.Name, lspServices, CancellationToken.None);
var response = await requestExecutionQueue.ExecuteAsync<bool, NoValue>(request: true, TestNotificationHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None);
Assert.Same(NoValue.Instance, response);
}

Expand All @@ -129,7 +130,7 @@ public async Task ExecuteAsync_CompletesTask_Notification_Parameterless()
var requestExecutionQueue = GetRequestExecutionQueue(false, (TestParameterlessNotificationHandler.Metadata, TestParameterlessNotificationHandler.Instance));
var lspServices = GetLspServices();

var response = await requestExecutionQueue.ExecuteAsync<NoValue, NoValue>(request: NoValue.Instance, TestParameterlessNotificationHandler.Name, lspServices, CancellationToken.None);
var response = await requestExecutionQueue.ExecuteAsync<NoValue, NoValue>(request: NoValue.Instance, TestParameterlessNotificationHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None);
Assert.Same(NoValue.Instance, response);
}

Expand All @@ -140,8 +141,8 @@ public async Task Queue_DrainsOnShutdown()
var request = 1;
var lspServices = GetLspServices();

var task1 = requestExecutionQueue.ExecuteAsync<int, string>(request, TestMethodHandler.Name, lspServices, CancellationToken.None);
var task2 = requestExecutionQueue.ExecuteAsync<int, string>(request, TestMethodHandler.Name, lspServices, CancellationToken.None);
var task1 = requestExecutionQueue.ExecuteAsync<int, string>(request, TestMethodHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None);
var task2 = requestExecutionQueue.ExecuteAsync<int, string>(request, TestMethodHandler.Name, LanguageServerConstants.DefaultLanguageName, lspServices, CancellationToken.None);

await requestExecutionQueue.DisposeAsync();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.CommonLanguageServerProtocol.Framework.Example;
using Microsoft.Extensions.DependencyInjection;
using Nerdbank.Streams;
using Newtonsoft.Json;
using Roslyn.LanguageServer.Protocol;
using StreamJsonRpc;

Expand All @@ -18,7 +19,8 @@ internal class TestExampleLanguageServer : ExampleLanguageServer
{
private readonly JsonRpc _clientRpc;

public TestExampleLanguageServer(Stream clientSteam, JsonRpc jsonRpc, ILspLogger logger, Action<IServiceCollection>? addExtraHandlers) : base(jsonRpc, logger, addExtraHandlers)
public TestExampleLanguageServer(Stream clientSteam, JsonRpc jsonRpc, JsonSerializer jsonSerializer, ILspLogger logger, Action<IServiceCollection>? addExtraHandlers)
: base(jsonRpc, jsonSerializer, logger, addExtraHandlers)
{
_clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientSteam, clientSteam, CreateJsonMessageFormatter()))
{
Expand Down Expand Up @@ -111,14 +113,15 @@ internal static TestExampleLanguageServer CreateBadLanguageServer(ILspLogger log
{
var (clientStream, serverStream) = FullDuplexStream.CreatePair();

var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream, CreateJsonMessageFormatter()));
var messageFormatter = CreateJsonMessageFormatter();
var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream, messageFormatter));

var extraHandlers = (IServiceCollection serviceCollection) =>
{
serviceCollection.AddSingleton<IMethodHandler, ExtraDidOpenHandler>();
};

var server = new TestExampleLanguageServer(clientStream, jsonRpc, logger, extraHandlers);
var server = new TestExampleLanguageServer(clientStream, jsonRpc, messageFormatter.JsonSerializer, logger, extraHandlers);

jsonRpc.StartListening();
server.InitializeTest();
Expand All @@ -129,9 +132,10 @@ internal static TestExampleLanguageServer CreateLanguageServer(ILspLogger logger
{
var (clientStream, serverStream) = FullDuplexStream.CreatePair();

var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream, CreateJsonMessageFormatter()));
var messageFormatter = CreateJsonMessageFormatter();
var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverStream, serverStream, messageFormatter));

var server = new TestExampleLanguageServer(clientStream, jsonRpc, logger, addExtraHandlers: null);
var server = new TestExampleLanguageServer(clientStream, jsonRpc, messageFormatter.JsonSerializer, logger, addExtraHandlers: null);

jsonRpc.StartListening();
server.InitializeTest();
Expand Down
Loading

0 comments on commit 0edf3bc

Please sign in to comment.