Skip to content

Commit

Permalink
Merge pull request #41 from OmniSharp/fix/didchange-callback
Browse files Browse the repository at this point in the history
Fix/didchange callback
  • Loading branch information
david-driscoll authored Oct 26, 2017
2 parents b2bb198 + f128b27 commit d1ad86d
Show file tree
Hide file tree
Showing 14 changed files with 109 additions and 77 deletions.
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

0 comments on commit d1ad86d

Please sign in to comment.