Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/JsonRpc/IRequestRouter.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System;
using System;
using System.Threading.Tasks;
using OmniSharp.Extensions.JsonRpc.Server;

namespace OmniSharp.Extensions.JsonRpc
{
public interface IRequestRouter
{
void RouteNotification(Notification notification);
Task RouteNotification(Notification notification);
Task<ErrorResponse> RouteRequest(Request request);
}
}
5 changes: 2 additions & 3 deletions src/JsonRpc/InputHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ private void HandleRequest(string request)
_scheduler.Add(
type,
item.Notification.Method,
() => {
async () => {
try
{
_requestRouter.RouteNotification(item.Notification);
await _requestRouter.RouteNotification(item.Notification);
}
catch (Exception e)
{
Expand All @@ -202,7 +202,6 @@ private void HandleRequest(string request)
// If an exception happens... the whole system could be in a bad state, hence this throwing currently.
throw;
}
return Task.CompletedTask;
}
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/JsonRpc/ProcessScheduler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
Expand Down Expand Up @@ -128,7 +128,7 @@ public void Dispose()
}
}

static class Events
public static class Events
{
public static EventId UnhandledException = new EventId(1337_100);
public static EventId UnhandledRequest = new EventId(1337_101);
Expand Down
2 changes: 1 addition & 1 deletion src/JsonRpc/RequestRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public IDisposable Add(IJsonRpcHandler handler)
return _collection.Add(handler);
}

public async void RouteNotification(Notification notification)
public async Task RouteNotification(Notification notification)
{
var handler = _collection.FirstOrDefault(x => x.Method == notification.Method);

Expand Down
11 changes: 7 additions & 4 deletions src/Lsp/ClientCapabilityProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public ClientCapabilityProvider(IHandlerCollection collection)
_collection = collection;
}

public bool HasHandler<T>(Supports<T> capability)
public bool HasStaticHandler<T>(Supports<T> capability)
where T : DynamicCapability, ConnectedCapability<IJsonRpcHandler>
{
if (!capability.IsSupported) return false;
Expand All @@ -26,13 +26,16 @@ public bool HasHandler<T>(Supports<T> capability)
var handlerType = typeof(T).GetTypeInfo().ImplementedInterfaces
.Single(x => x.GetTypeInfo().IsGenericType && x.GetTypeInfo().GetGenericTypeDefinition() == typeof(ConnectedCapability<>))
.GetTypeInfo().GetGenericArguments()[0].GetTypeInfo();
return !capability.Value.DynamicRegistration && _collection.Any(z => z.HandlerType.GetTypeInfo().IsAssignableFrom(handlerType));
return !capability.Value.DynamicRegistration &&
_collection.Any(z =>
z.HandlerType.GetTypeInfo().IsAssignableFrom(handlerType) ||
z.Handler.GetType().GetTypeInfo().IsAssignableFrom(handlerType));
}

public IOptionsGetter GetOptions<T>(Supports<T> capability)
public IOptionsGetter GetStaticOptions<T>(Supports<T> capability)
where T : DynamicCapability, ConnectedCapability<IJsonRpcHandler>
{
return !HasHandler(capability) ? Null : new OptionsGetter(_collection);
return !HasStaticHandler(capability) ? Null : new OptionsGetter(_collection);
}

private static readonly IOptionsGetter Null = new NullOptionsGetter();
Expand Down
53 changes: 29 additions & 24 deletions src/Lsp/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,22 +130,22 @@ async Task<InitializeResult> IRequestHandler<InitializeParams, InitializeResult>
var ccp = new ClientCapabilityProvider(_collection);

var serverCapabilities = new ServerCapabilities() {
CodeActionProvider = ccp.HasHandler(textDocumentCapabilities.CodeAction),
CodeLensProvider = ccp.GetOptions(textDocumentCapabilities.CodeLens).Get<ICodeLensOptions, CodeLensOptions>(CodeLensOptions.Of),
CompletionProvider = ccp.GetOptions(textDocumentCapabilities.Completion).Get<ICompletionOptions, CompletionOptions>(CompletionOptions.Of),
DefinitionProvider = ccp.HasHandler(textDocumentCapabilities.Definition),
DocumentFormattingProvider = ccp.HasHandler(textDocumentCapabilities.Formatting),
DocumentHighlightProvider = ccp.HasHandler(textDocumentCapabilities.DocumentHighlight),
DocumentLinkProvider = ccp.GetOptions(textDocumentCapabilities.DocumentLink).Get<IDocumentLinkOptions, DocumentLinkOptions>(DocumentLinkOptions.Of),
DocumentOnTypeFormattingProvider = ccp.GetOptions(textDocumentCapabilities.OnTypeFormatting).Get<IDocumentOnTypeFormattingOptions, DocumentOnTypeFormattingOptions>(DocumentOnTypeFormattingOptions.Of),
DocumentRangeFormattingProvider = ccp.HasHandler(textDocumentCapabilities.RangeFormatting),
DocumentSymbolProvider = ccp.HasHandler(textDocumentCapabilities.DocumentSymbol),
ExecuteCommandProvider = ccp.GetOptions(workspaceCapabilities.ExecuteCommand).Get<IExecuteCommandOptions, ExecuteCommandOptions>(ExecuteCommandOptions.Of),
HoverProvider = ccp.HasHandler(textDocumentCapabilities.Hover),
ReferencesProvider = ccp.HasHandler(textDocumentCapabilities.References),
RenameProvider = ccp.HasHandler(textDocumentCapabilities.Rename),
SignatureHelpProvider = ccp.GetOptions(textDocumentCapabilities.SignatureHelp).Get<ISignatureHelpOptions, SignatureHelpOptions>(SignatureHelpOptions.Of),
WorkspaceSymbolProvider = ccp.HasHandler(workspaceCapabilities.Symbol)
CodeActionProvider = ccp.HasStaticHandler(textDocumentCapabilities.CodeAction),
CodeLensProvider = ccp.GetStaticOptions(textDocumentCapabilities.CodeLens).Get<ICodeLensOptions, CodeLensOptions>(CodeLensOptions.Of),
CompletionProvider = ccp.GetStaticOptions(textDocumentCapabilities.Completion).Get<ICompletionOptions, CompletionOptions>(CompletionOptions.Of),
DefinitionProvider = ccp.HasStaticHandler(textDocumentCapabilities.Definition),
DocumentFormattingProvider = ccp.HasStaticHandler(textDocumentCapabilities.Formatting),
DocumentHighlightProvider = ccp.HasStaticHandler(textDocumentCapabilities.DocumentHighlight),
DocumentLinkProvider = ccp.GetStaticOptions(textDocumentCapabilities.DocumentLink).Get<IDocumentLinkOptions, DocumentLinkOptions>(DocumentLinkOptions.Of),
DocumentOnTypeFormattingProvider = ccp.GetStaticOptions(textDocumentCapabilities.OnTypeFormatting).Get<IDocumentOnTypeFormattingOptions, DocumentOnTypeFormattingOptions>(DocumentOnTypeFormattingOptions.Of),
DocumentRangeFormattingProvider = ccp.HasStaticHandler(textDocumentCapabilities.RangeFormatting),
DocumentSymbolProvider = ccp.HasStaticHandler(textDocumentCapabilities.DocumentSymbol),
ExecuteCommandProvider = ccp.GetStaticOptions(workspaceCapabilities.ExecuteCommand).Get<IExecuteCommandOptions, ExecuteCommandOptions>(ExecuteCommandOptions.Of),
HoverProvider = ccp.HasStaticHandler(textDocumentCapabilities.Hover),
ReferencesProvider = ccp.HasStaticHandler(textDocumentCapabilities.References),
RenameProvider = ccp.HasStaticHandler(textDocumentCapabilities.Rename),
SignatureHelpProvider = ccp.GetStaticOptions(textDocumentCapabilities.SignatureHelp).Get<ISignatureHelpOptions, SignatureHelpOptions>(SignatureHelpOptions.Of),
WorkspaceSymbolProvider = ccp.HasStaticHandler(workspaceCapabilities.Symbol)
};

var textSyncHandlers = _collection
Expand All @@ -168,16 +168,21 @@ async Task<InitializeResult> IRequestHandler<InitializeParams, InitializeResult>
}
else
{
if (ccp.HasHandler(textDocumentCapabilities.Synchronization))
if (ccp.HasStaticHandler(textDocumentCapabilities.Synchronization))
{
// TODO: Merge options
serverCapabilities.TextDocumentSync = textSyncHandlers.FirstOrDefault()?.Options ?? new TextDocumentSyncOptions() {
Change = TextDocumentSyncKind.None,
OpenClose = false,
Save = new SaveOptions() { IncludeText = false },
WillSave = false,
WillSaveWaitUntil = false
};
serverCapabilities.TextDocumentSync =
textSyncHandlers.FirstOrDefault()?.Options ?? new TextDocumentSyncOptions() {
Change = TextDocumentSyncKind.None,
OpenClose = false,
Save = new SaveOptions() { IncludeText = false },
WillSave = false,
WillSaveWaitUntil = false
};
}
else
{
serverCapabilities.TextDocumentSync = TextDocumentSyncKind.None;
}
}

Expand Down
66 changes: 46 additions & 20 deletions src/Lsp/LspRequestRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,35 +57,48 @@ private ILspHandlerDescriptor FindDescriptor(string method, JToken @params)
if (typeof(ITextDocumentIdentifierParams).GetTypeInfo().IsAssignableFrom(descriptor.Params))
{
var textDocumentIdentifierParams = @params.ToObject(descriptor.Params) as ITextDocumentIdentifierParams;
var textDocumentSyncHandlers = _collection
.Select(x => x.Handler is ITextDocumentSyncHandler r ? r : null)
.Where(x => x != null)
.Distinct();
var attributes = textDocumentSyncHandlers
.Select(x => x.GetTextDocumentAttributes(textDocumentIdentifierParams.TextDocument.Uri))
.Where(x => x != null)
.Distinct()
.ToList();
var attributes = GetTextDocumentAttributes(textDocumentIdentifierParams.TextDocument.Uri);

_logger.LogTrace("Found attributes {Count}, {Attributes}", attributes.Count, attributes.Select(x => $"{x.LanguageId}:{x.Scheme}:{x.Uri}"));

return GetHandler(method, attributes);
}
else if (typeof(DidOpenTextDocumentParams).GetTypeInfo().IsAssignableFrom(descriptor.Params))
else if (@params?.ToObject(descriptor.Params) is DidOpenTextDocumentParams openTextDocumentParams)
{
var openTextDocumentParams = @params.ToObject(descriptor.Params) as DidOpenTextDocumentParams;
var attributes = new TextDocumentAttributes(openTextDocumentParams.TextDocument.Uri, openTextDocumentParams.TextDocument.LanguageId);

_logger.LogTrace("Created attribute {Attribute}", $"{attributes.LanguageId}:{attributes.Scheme}:{attributes.Uri}");

return GetHandler(method, attributes);
}
else if (@params?.ToObject(descriptor.Params) is DidChangeTextDocumentParams didChangeDocumentParams)
{
// TODO: Do something with document version here?
var attributes = GetTextDocumentAttributes(didChangeDocumentParams.TextDocument.Uri);

_logger.LogTrace("Found attributes {Count}, {Attributes}", attributes.Count, attributes.Select(x => $"{x.LanguageId}:{x.Scheme}:{x.Uri}"));

return GetHandler(method, attributes);
}

// TODO: How to split these
// Do they fork and join?
return descriptor;
}

private List<TextDocumentAttributes> GetTextDocumentAttributes(Uri uri)
{
var textDocumentSyncHandlers = _collection
.Select(x => x.Handler is ITextDocumentSyncHandler r ? r : null)
.Where(x => x != null)
.Distinct();
return textDocumentSyncHandlers
.Select(x => x.GetTextDocumentAttributes(uri))
.Where(x => x != null)
.Distinct()
.ToList();
}

private ILspHandlerDescriptor GetHandler(string method, IEnumerable<TextDocumentAttributes> attributes)
{
return attributes
Expand All @@ -105,29 +118,37 @@ private ILspHandlerDescriptor GetHandler(string method, TextDocumentAttributes a
_logger.LogTrace("Document Selector {DocumentSelector}", registrationOptions.DocumentSelector.ToString());
if (registrationOptions.DocumentSelector == null || registrationOptions.DocumentSelector.IsMatch(attributes))
{
_logger.LogTrace("Handler Selected: {Handler} via {DocumentSelector} (targeting {HandlerInterface})", handler.Handler.GetType().FullName, registrationOptions.DocumentSelector.ToString(), handler.HandlerType.GetType().FullName);
return handler;
}
}
return null;
}

public async void RouteNotification(Notification notification)
public async Task RouteNotification(Notification notification)
{
var handler = FindDescriptor(notification.Method, notification.Params);
if (handler is null) { return; }

Task result;
if (handler.Params is null)
try
{
result = ReflectionRequestHandlers.HandleNotification(handler);
Task result;
if (handler.Params is null)
{
result = ReflectionRequestHandlers.HandleNotification(handler);
}
else
{
var @params = notification.Params.ToObject(handler.Params);
result = ReflectionRequestHandlers.HandleNotification(handler, @params);
}

await result;
}
else
catch (Exception e)
{
var @params = notification.Params.ToObject(handler.Params);
result = ReflectionRequestHandlers.HandleNotification(handler, @params);
_logger.LogCritical(Events.UnhandledRequest, e, "Failed to handle request {Method}", notification.Method);
}

await result;
}

public async Task<ErrorResponse> RouteRequest(Request request)
Expand Down Expand Up @@ -184,6 +205,11 @@ public async Task<ErrorResponse> RouteRequest(Request request)
{
return new RequestCancelled();
}
catch (Exception e)
{
_logger.LogCritical(Events.UnhandledRequest, e, "Failed to handle notification {Method}", request.Method);
return new InternalError(id);
}
finally
{
_requests.TryRemove(id, out var _);
Expand Down
4 changes: 2 additions & 2 deletions src/Lsp/Models/DidChangeTextDocumentParams.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace OmniSharp.Extensions.LanguageServer.Models
Expand All @@ -18,4 +18,4 @@ public class DidChangeTextDocumentParams
/// </summary>
public Container<TextDocumentContentChangeEvent> ContentChanges { get; set; }
}
}
}
4 changes: 2 additions & 2 deletions test/JsonRpc.Tests/InputHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public void ShouldHandleError()
}

[Fact]
public void ShouldHandleNotification()
public async Task ShouldHandleNotification()
{
var inputStream = new MemoryStream(Encoding.ASCII.GetBytes("Content-Length: 2\r\n\r\n{}"));
var outputHandler = Substitute.For<IOutputHandler>();
Expand Down Expand Up @@ -234,7 +234,7 @@ public void ShouldHandleNotification()
});
}))
{
incomingRequestRouter.Received().RouteNotification(notification);
await incomingRequestRouter.Received().RouteNotification(notification);
}
}

Expand Down
6 changes: 3 additions & 3 deletions test/JsonRpc.Tests/MediatorTestsNotificationHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Reflection;
using System.Threading.Tasks;
using NSubstitute;
Expand All @@ -23,10 +23,10 @@ public async Task ExecutesHandler()

var notification = new Notification("exit", null);

mediator.RouteNotification(notification);
await mediator.RouteNotification(notification);

await exitHandler.Received(1).Handle();
}

}
}
}
6 changes: 3 additions & 3 deletions test/JsonRpc.Tests/MediatorTestsNotificationHandlerOfT.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Reflection;
using System.Threading.Tasks;
using Newtonsoft.Json;
Expand Down Expand Up @@ -33,10 +33,10 @@ public async Task ExecutesHandler()
var @params = new CancelParams() { Id = Guid.NewGuid() };
var notification = new Notification("$/cancelRequest", JObject.Parse(JsonConvert.SerializeObject(@params)));

mediator.RouteNotification(notification);
await mediator.RouteNotification(notification);

await cancelRequestHandler.Received(1).Handle(Arg.Any<CancelParams>());
}

}
}
}
2 changes: 1 addition & 1 deletion test/Lsp.Tests/ClientCapabilityProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private static bool HasHandler(ClientCapabilityProvider provider, Type type)
private static bool GenericHasHandler<T>(ClientCapabilityProvider provider, Supports<T> supports)
where T : DynamicCapability, ConnectedCapability<IJsonRpcHandler>
{
return provider.HasHandler(supports);
return provider.HasStaticHandler(supports);
}

private static IEnumerable<object[]> GetItems<T>(IEnumerable<T> types, Func<T, IEnumerable<object>> func)
Expand Down
14 changes: 7 additions & 7 deletions test/Lsp.Tests/LspRequestRouterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public LspRequestRouterTests(ITestOutputHelper testOutputHelper)
}

[Fact]
public void ShouldRouteToCorrect_Notification()
public async Task ShouldRouteToCorrect_Notification()
{
var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs"));
textDocumentSyncHandler.Handle(Arg.Any<DidSaveTextDocumentParams>()).Returns(Task.CompletedTask);
Expand All @@ -44,13 +44,13 @@ public void ShouldRouteToCorrect_Notification()

var request = new Notification("textDocument/didSave", JObject.Parse(JsonConvert.SerializeObject(@params)));

mediator.RouteNotification(request);
await mediator.RouteNotification(request);

textDocumentSyncHandler.Received(1).Handle(Arg.Any<DidSaveTextDocumentParams>());
await textDocumentSyncHandler.Received(1).Handle(Arg.Any<DidSaveTextDocumentParams>());
}

[Fact]
public void ShouldRouteToCorrect_Notification_WithManyHandlers()
public async Task ShouldRouteToCorrect_Notification_WithManyHandlers()
{
var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs"));
var textDocumentSyncHandler2 = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cake"));
Expand All @@ -66,10 +66,10 @@ public void ShouldRouteToCorrect_Notification_WithManyHandlers()

var request = new Notification("textDocument/didSave", JObject.Parse(JsonConvert.SerializeObject(@params)));

mediator.RouteNotification(request);
await mediator.RouteNotification(request);

textDocumentSyncHandler.Received(0).Handle(Arg.Any<DidSaveTextDocumentParams>());
textDocumentSyncHandler2.Received(1).Handle(Arg.Any<DidSaveTextDocumentParams>());
await textDocumentSyncHandler.Received(0).Handle(Arg.Any<DidSaveTextDocumentParams>());
await textDocumentSyncHandler2.Received(1).Handle(Arg.Any<DidSaveTextDocumentParams>());
}

[Fact]
Expand Down
Loading