From 19e2ae86a144fb6ae87c01e684b6c0507b585c94 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Thu, 30 Jul 2020 03:17:36 -0400 Subject: [PATCH 1/6] Fixup commands to have a more reliable experience --- src/JsonRpc.Generators/Helpers.cs | 1 + .../ExecuteCommandRegistrationOptions.cs | 4 +- .../Workspace/IExecuteCommandHandler.cs | 8 + src/Server/Matchers/TextDocumentMatcher.cs | 5 +- src/Shared/LspRequestRouter.cs | 12 +- src/Shared/SharedHandlerCollection.cs | 5 + .../AutoNSubstitute/TestExtensions.cs | 10 +- test/Lsp.Tests/FoundationTests.cs | 3 +- .../Integration/ExecuteCommandTests.cs | 192 ++++++++++++++++++ .../Integration/InitializationTests.cs | 49 +++++ .../Integration/RequestCancellationTests.cs | 36 ---- test/Lsp.Tests/LspRequestRouterTests.cs | 5 +- 12 files changed, 284 insertions(+), 46 deletions(-) create mode 100644 test/Lsp.Tests/Integration/ExecuteCommandTests.cs create mode 100644 test/Lsp.Tests/Integration/InitializationTests.cs diff --git a/src/JsonRpc.Generators/Helpers.cs b/src/JsonRpc.Generators/Helpers.cs index 401cb808f..e404bb301 100644 --- a/src/JsonRpc.Generators/Helpers.cs +++ b/src/JsonRpc.Generators/Helpers.cs @@ -828,6 +828,7 @@ public static string GetSendMethodName(INamedTypeSymbol symbol, AttributeData at var name = SpecialCasedHandlerName(symbol); if ( name.StartsWith("Run") + || name.StartsWith("Execute") // TODO: Change this next breaking change // || name.StartsWith("Set") // || name.StartsWith("Attach") diff --git a/src/Protocol/Models/ExecuteCommandRegistrationOptions.cs b/src/Protocol/Models/ExecuteCommandRegistrationOptions.cs index 5b6c0b0b0..aaa0bbbe5 100644 --- a/src/Protocol/Models/ExecuteCommandRegistrationOptions.cs +++ b/src/Protocol/Models/ExecuteCommandRegistrationOptions.cs @@ -1,9 +1,9 @@ -namespace OmniSharp.Extensions.LanguageServer.Protocol.Models +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models { /// /// Execute command registration options. /// - public class ExecuteCommandRegistrationOptions : WorkDoneTextDocumentRegistrationOptions, IExecuteCommandOptions + public class ExecuteCommandRegistrationOptions : WorkDoneProgressOptions, IExecuteCommandOptions { /// /// The commands to be executed on the server diff --git a/src/Protocol/Workspace/IExecuteCommandHandler.cs b/src/Protocol/Workspace/IExecuteCommandHandler.cs index 0ce19f1d5..5ac28557a 100644 --- a/src/Protocol/Workspace/IExecuteCommandHandler.cs +++ b/src/Protocol/Workspace/IExecuteCommandHandler.cs @@ -27,4 +27,12 @@ public ExecuteCommandHandler(ExecuteCommandRegistrationOptions registrationOptio public virtual void SetCapability(ExecuteCommandCapability capability) => Capability = capability; protected ExecuteCommandCapability Capability { get; private set; } } + + public static partial class ExecuteCommandExtensions + { + public static Task ExecuteCommand(this IWorkspaceLanguageClient mediator, Command @params, CancellationToken cancellationToken = default) + => mediator.ExecuteCommand(new ExecuteCommandParams() {Arguments = @params.Arguments, Command = @params.Name}, cancellationToken); + public static Task ExecuteCommand(this ILanguageClient mediator, Command @params, CancellationToken cancellationToken = default) + => mediator.ExecuteCommand(new ExecuteCommandParams() {Arguments = @params.Arguments, Command = @params.Name}, cancellationToken); + } } diff --git a/src/Server/Matchers/TextDocumentMatcher.cs b/src/Server/Matchers/TextDocumentMatcher.cs index d1c636dc3..fec9ccd53 100644 --- a/src/Server/Matchers/TextDocumentMatcher.cs +++ b/src/Server/Matchers/TextDocumentMatcher.cs @@ -17,7 +17,7 @@ class TextDocumentMatcher : IHandlerMatcher public TextDocumentMatcher(ILogger logger, TextDocumentIdentifiers textDocumentIdentifiers) { _logger = logger; - _textDocumentIdentifiers = textDocumentIdentifiers;; + _textDocumentIdentifiers = textDocumentIdentifiers; ; } public IEnumerable FindHandler(object parameters, IEnumerable descriptors) @@ -26,6 +26,7 @@ public IEnumerable FindHandler(object parameters, IEnumer { case ITextDocumentIdentifierParams textDocumentIdentifierParams: { + if (textDocumentIdentifierParams.TextDocument?.Uri == null) break; var attributes = GetTextDocumentAttributes(textDocumentIdentifierParams.TextDocument.Uri); _logger.LogTrace("Found attributes {Count}, {Attributes}", attributes.Count, attributes.Select(x => $"{x.LanguageId}:{x.Scheme}:{x.Uri}")); @@ -34,6 +35,7 @@ public IEnumerable FindHandler(object parameters, IEnumer } case DidOpenTextDocumentParams openTextDocumentParams: { + if (openTextDocumentParams.TextDocument?.Uri == null) break; var attributes = new TextDocumentAttributes(openTextDocumentParams.TextDocument.Uri, openTextDocumentParams.TextDocument.LanguageId); _logger.LogTrace("Created attribute {Attribute}", $"{attributes.LanguageId}:{attributes.Scheme}:{attributes.Uri}"); @@ -42,6 +44,7 @@ public IEnumerable FindHandler(object parameters, IEnumer } case DidChangeTextDocumentParams didChangeDocumentParams: { + if (didChangeDocumentParams.TextDocument?.Uri == null) break; // TODO: Do something with document version here? var attributes = GetTextDocumentAttributes(didChangeDocumentParams.TextDocument.Uri); diff --git a/src/Shared/LspRequestRouter.cs b/src/Shared/LspRequestRouter.cs index 80d794ec1..7b59bed39 100644 --- a/src/Shared/LspRequestRouter.cs +++ b/src/Shared/LspRequestRouter.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Shared; using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; @@ -57,13 +58,20 @@ private ILspHandlerDescriptor FindDescriptor(string method, JToken @params) return null; } + if (@params == null || descriptor.Params == null) return descriptor; var lspHandlerDescriptors = _collection.Where(handler => handler.Method == method).ToList(); - if (lspHandlerDescriptors.Count == 1) return descriptor; var paramsValue = @params.ToObject(descriptor.Params, _serializer.JsonSerializer); - return _handlerMatchers.SelectMany(strat => strat.FindHandler(paramsValue, lspHandlerDescriptors)).FirstOrDefault() ?? descriptor; + var matchDescriptor = _handlerMatchers.SelectMany(strat => strat.FindHandler(paramsValue, lspHandlerDescriptors)).FirstOrDefault(); + if (matchDescriptor != null) return matchDescriptor; + // execute command is a special case + // if no command was found to execute this must error + // this is not great coupling but other options require api changes + if (paramsValue is ExecuteCommandParams) return null; + if (lspHandlerDescriptors.Count == 1) return descriptor; + return null; } IHandlerDescriptor IRequestRouter.GetDescriptor(Notification notification) => GetDescriptor(notification); diff --git a/src/Shared/SharedHandlerCollection.cs b/src/Shared/SharedHandlerCollection.cs index 0ae541444..cec5dbdf6 100644 --- a/src/Shared/SharedHandlerCollection.cs +++ b/src/Shared/SharedHandlerCollection.cs @@ -263,6 +263,11 @@ private LspHandlerDescriptor GetDescriptor(string method, Type handlerType, IJso } } + if (handler is IRegistration commandRegistration) + { + key += "|" + string.Join("|", commandRegistration.GetRegistrationOptions()?.Commands ?? Array.Empty()); + } + if (string.IsNullOrWhiteSpace(key)) key = "default"; var requestProcessType = diff --git a/test/JsonRpc.Tests/AutoNSubstitute/TestExtensions.cs b/test/JsonRpc.Tests/AutoNSubstitute/TestExtensions.cs index e308b43a7..c0e417781 100644 --- a/test/JsonRpc.Tests/AutoNSubstitute/TestExtensions.cs +++ b/test/JsonRpc.Tests/AutoNSubstitute/TestExtensions.cs @@ -1,5 +1,6 @@ using System.Threading; using OmniSharp.Extensions.JsonRpc.Testing; +using Serilog.Events; using Xunit.Abstractions; // ReSharper disable once CheckNamespace @@ -12,11 +13,14 @@ public static void Wait(this CancellationTokenSource cancellationTokenSource) cancellationTokenSource.Token.WaitHandle.WaitOne(); } - public static JsonRpcTestOptions ConfigureForXUnit(this JsonRpcTestOptions jsonRpcTestOptions, ITestOutputHelper outputHelper) + public static JsonRpcTestOptions ConfigureForXUnit( + this JsonRpcTestOptions jsonRpcTestOptions, + ITestOutputHelper outputHelper, + LogEventLevel logEventLevel = LogEventLevel.Debug) { return jsonRpcTestOptions - .WithClientLoggerFactory(new TestLoggerFactory(outputHelper, "{Timestamp:yyyy-MM-dd HH:mm:ss} [Client] [{Level}] {Message}{NewLine}{Exception}")) - .WithServerLoggerFactory(new TestLoggerFactory(outputHelper, "{Timestamp:yyyy-MM-dd HH:mm:ss} [Server] [{Level}] {Message}{NewLine}{Exception}")); + .WithClientLoggerFactory(new TestLoggerFactory(outputHelper, "{Timestamp:yyyy-MM-dd HH:mm:ss} [Client] [{Level}] {Message}{NewLine}{Exception}", logEventLevel)) + .WithServerLoggerFactory(new TestLoggerFactory(outputHelper, "{Timestamp:yyyy-MM-dd HH:mm:ss} [Server] [{Level}] {Message}{NewLine}{Exception}", logEventLevel)); } } } diff --git a/test/Lsp.Tests/FoundationTests.cs b/test/Lsp.Tests/FoundationTests.cs index bc6a53d59..cbe73c303 100644 --- a/test/Lsp.Tests/FoundationTests.cs +++ b/test/Lsp.Tests/FoundationTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -575,6 +575,7 @@ private static string GetSendMethodName(ILspHandlerTypeDescriptor descriptor) || name.StartsWith("Prepare") || name.StartsWith("Publish") || name.StartsWith("ApplyWorkspaceEdit") + || name.StartsWith("Execute") || name.StartsWith("Unregister")) { return name; diff --git a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs b/test/Lsp.Tests/Integration/ExecuteCommandTests.cs new file mode 100644 index 000000000..3210501c7 --- /dev/null +++ b/test/Lsp.Tests/Integration/ExecuteCommandTests.cs @@ -0,0 +1,192 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using NSubstitute; +using OmniSharp.Extensions.JsonRpc.Server; +using OmniSharp.Extensions.JsonRpc.Testing; +using OmniSharp.Extensions.LanguageProtocol.Testing; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; +using Serilog.Events; +using Xunit; +using Xunit.Abstractions; + +namespace Lsp.Tests.Integration +{ + public class ExecuteCommandTests : LanguageProtocolTestBase + { + public ExecuteCommandTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(outputHelper, LogEventLevel.Verbose)) + { + } + + [Fact] + public async Task Should_Execute_A_Command() + { + var command = Substitute.For>(); + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand(command, new ExecuteCommandRegistrationOptions() { + Commands = new Container("execute-a") + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + await client.ExecuteCommand(item.Command); + + await command.Received(1).Invoke(Arg.Any()); + } + + [Fact] + public async Task Should_Execute_The_Correct_Command() + { + var commanda = Substitute.For>(); + var commandb = Substitute.For>(); + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-b", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand(commanda, new ExecuteCommandRegistrationOptions() { + Commands = new Container("execute-a") + }); + + options.OnExecuteCommand(commandb, new ExecuteCommandRegistrationOptions() { + Commands = new Container("execute-b") + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + await client.ExecuteCommand(item.Command); + + await commanda.Received(0).Invoke(Arg.Any()); + await commandb.Received(1).Invoke(Arg.Any()); + } + + [Fact] + public async Task Should_Fail_To_Execute_A_Command_When_No_Command_Is_Defined() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + } + })); + }, new CompletionRegistrationOptions() { + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().ThrowAsync(); + } + + [Fact] + public async Task Should_Fail_To_Execute_A_Command_When_No_Command_Name_Is_Given() + { + var command = Substitute.For>(); + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand(command, new ExecuteCommandRegistrationOptions() { + Commands = new Container("execute-a") + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().ThrowAsync(); + + await command.Received(0).Invoke(Arg.Any()); + } + + [Fact] + public async Task Should_Fail_To_Execute_A_Command() + { + var commandc = Substitute.For>(); + var commandb = Substitute.For>(); + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] {1, "2", false}) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand(commandb, new ExecuteCommandRegistrationOptions() { + Commands = new Container("execute-b") + }); + + options.OnExecuteCommand(commandc, new ExecuteCommandRegistrationOptions() { + Commands = new Container("execute-c") + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().ThrowAsync(); + + await commandc.Received(0).Invoke(Arg.Any()); + await commandb.Received(0).Invoke(Arg.Any()); + } + } +} diff --git a/test/Lsp.Tests/Integration/InitializationTests.cs b/test/Lsp.Tests/Integration/InitializationTests.cs new file mode 100644 index 000000000..a4f24ea3d --- /dev/null +++ b/test/Lsp.Tests/Integration/InitializationTests.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using NSubstitute; +using OmniSharp.Extensions.JsonRpc.Testing; +using OmniSharp.Extensions.LanguageProtocol.Testing; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Window; +using OmniSharp.Extensions.LanguageServer.Server; +using Xunit; +using Xunit.Abstractions; + +namespace Lsp.Tests.Integration +{ + public class InitializationTests : LanguageProtocolTestBase + { + public InitializationTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(outputHelper)) { } + + [Fact] + public async Task Logs_should_be_allowed_during_startup() + { + var (client, server) = await Initialize(ConfigureClient, ConfigureServer); + + _logs.Should().HaveCount(2); + _logs.Should().ContainInOrder("OnInitialize", "OnInitialized"); + } + + private List _logs = new List(); + + private void ConfigureClient(LanguageClientOptions options) + { + options.OnLogMessage(log => { + _logs.Add(log.Message); + }); + } + + private void ConfigureServer(LanguageServerOptions options) + { + options.OnInitialize((server, request, token) => { + server.Window.LogInfo("OnInitialize"); + return Task.CompletedTask; + }); + options.OnInitialized((server, request, response, token) => { + server.Window.LogInfo("OnInitialized"); + return Task.CompletedTask; + }); + } + } +} \ No newline at end of file diff --git a/test/Lsp.Tests/Integration/RequestCancellationTests.cs b/test/Lsp.Tests/Integration/RequestCancellationTests.cs index 34d80a98d..92fe147ee 100644 --- a/test/Lsp.Tests/Integration/RequestCancellationTests.cs +++ b/test/Lsp.Tests/Integration/RequestCancellationTests.cs @@ -13,7 +13,6 @@ using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Protocol.Window; using OmniSharp.Extensions.LanguageServer.Server; using Xunit; using Xunit.Abstractions; @@ -142,39 +141,4 @@ private void ConfigureServer(LanguageServerOptions options) options.OnDidChangeTextDocument(async x => { await Task.Delay(20); }, new TextDocumentChangeRegistrationOptions()); } } - - public class InitializationTests : LanguageProtocolTestBase - { - public InitializationTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(outputHelper)) { } - - [Fact] - public async Task Logs_should_be_allowed_during_startup() - { - var (client, server) = await Initialize(ConfigureClient, ConfigureServer); - - _logs.Should().HaveCount(2); - _logs.Should().ContainInOrder("OnInitialize", "OnInitialized"); - } - - private List _logs = new List(); - - private void ConfigureClient(LanguageClientOptions options) - { - options.OnLogMessage(log => { - _logs.Add(log.Message); - }); - } - - private void ConfigureServer(LanguageServerOptions options) - { - options.OnInitialize((server, request, token) => { - server.Window.LogInfo("OnInitialize"); - return Task.CompletedTask; - }); - options.OnInitialized((server, request, response, token) => { - server.Window.LogInfo("OnInitialized"); - return Task.CompletedTask; - }); - } - } } diff --git a/test/Lsp.Tests/LspRequestRouterTests.cs b/test/Lsp.Tests/LspRequestRouterTests.cs index 6e345983e..b58c82bb0 100644 --- a/test/Lsp.Tests/LspRequestRouterTests.cs +++ b/test/Lsp.Tests/LspRequestRouterTests.cs @@ -17,6 +17,7 @@ using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer; using Serializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.Serializer; using System.Reactive.Disposables; +using Microsoft.Extensions.Logging; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.General; @@ -267,11 +268,13 @@ public async Task ShouldRouteToCorrect_Request_WithManyHandlers_CodeLensHandler( .Handle(Arg.Any(), Arg.Any()) .Returns(new CodeLensContainer()); + var tdi = new TextDocumentIdentifiers(); var collection = - new SharedHandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()) + new SharedHandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, tdi) {textDocumentSyncHandler, textDocumentSyncHandler2, codeActionHandler, codeActionHandler2}; AutoSubstitute.Provide(collection); AutoSubstitute.Provide>(collection); + AutoSubstitute.Provide(new TextDocumentMatcher(LoggerFactory.CreateLogger(), tdi)); var mediator = AutoSubstitute.Resolve(); var id = Guid.NewGuid().ToString(); From 6e96efdbd2e29778662173059df9f278d47fdaf9 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Thu, 30 Jul 2020 03:59:55 -0400 Subject: [PATCH 2/6] Added helper base classes and helper methods for handling single commands with a known set of arguments up to 6 --- .../Workspace/IExecuteCommandHandler.cs | 278 +++++++++++- .../Integration/ExecuteCommandTests.cs | 418 +++++++++++++++++- 2 files changed, 693 insertions(+), 3 deletions(-) diff --git a/src/Protocol/Workspace/IExecuteCommandHandler.cs b/src/Protocol/Workspace/IExecuteCommandHandler.cs index 5ac28557a..87dcf6577 100644 --- a/src/Protocol/Workspace/IExecuteCommandHandler.cs +++ b/src/Protocol/Workspace/IExecuteCommandHandler.cs @@ -1,11 +1,14 @@ +using System; using System.Threading; using System.Threading.Tasks; using MediatR; +using Microsoft.Extensions.DependencyInjection; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Generation; using OmniSharp.Extensions.LanguageServer.Protocol.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace OmniSharp.Extensions.LanguageServer.Protocol.Workspace { @@ -28,11 +31,282 @@ public ExecuteCommandHandler(ExecuteCommandRegistrationOptions registrationOptio protected ExecuteCommandCapability Capability { get; private set; } } + public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandler + { + private readonly ISerializer _serializer; + + public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container(command) }) + { + _serializer = serializer; + } + + public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) + { + T arg1 = default; + if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + return Handle(arg1, cancellationToken); + } + + public abstract Task Handle(T arg1, CancellationToken cancellationToken); + } + + public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandler + { + private readonly ISerializer _serializer; + + public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container(command) }) + { + _serializer = serializer; + } + + public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) + { + T arg1 = default; + if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + T2 arg2 = default; + if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + return Handle(arg1, arg2, cancellationToken); + } + + public abstract Task Handle(T arg1, T2 arg2, CancellationToken cancellationToken); + } + + public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandler + { + private readonly ISerializer _serializer; + + public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container(command) }) + { + _serializer = serializer; + } + + public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) + { + T arg1 = default; + if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + T2 arg2 = default; + if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + T3 arg3 = default; + if (request.Arguments.Count > 2) arg3 = request.Arguments[2].ToObject(_serializer.JsonSerializer); + return Handle(arg1, arg2, arg3, cancellationToken); + } + + public abstract Task Handle(T arg1, T2 arg2, T3 arg3, CancellationToken cancellationToken); + } + + public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandler + { + private readonly ISerializer _serializer; + + public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container(command) }) + { + _serializer = serializer; + } + + public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) + { + T arg1 = default; + if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + T2 arg2 = default; + if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + T3 arg3 = default; + if (request.Arguments.Count > 2) arg3 = request.Arguments[2].ToObject(_serializer.JsonSerializer); + T4 arg4 = default; + if (request.Arguments.Count > 3) arg4 = request.Arguments[3].ToObject(_serializer.JsonSerializer); + return Handle(arg1, arg2, arg3, arg4, cancellationToken); + } + + public abstract Task Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, CancellationToken cancellationToken); + } + + public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandler + { + private readonly ISerializer _serializer; + + public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container(command) }) + { + _serializer = serializer; + } + + public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) + { + T arg1 = default; + if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + T2 arg2 = default; + if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + T3 arg3 = default; + if (request.Arguments.Count > 2) arg3 = request.Arguments[2].ToObject(_serializer.JsonSerializer); + T4 arg4 = default; + if (request.Arguments.Count > 3) arg4 = request.Arguments[3].ToObject(_serializer.JsonSerializer); + T5 arg5 = default; + if (request.Arguments.Count > 4) arg5 = request.Arguments[4].ToObject(_serializer.JsonSerializer); + return Handle(arg1, arg2, arg3, arg4, arg5, cancellationToken); + } + + public abstract Task Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, CancellationToken cancellationToken); + } + + public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandler + { + private readonly ISerializer _serializer; + + public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container(command) }) + { + _serializer = serializer; + } + + public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) + { + T arg1 = default; + if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + T2 arg2 = default; + if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + T3 arg3 = default; + if (request.Arguments.Count > 2) arg3 = request.Arguments[2].ToObject(_serializer.JsonSerializer); + T4 arg4 = default; + if (request.Arguments.Count > 3) arg4 = request.Arguments[3].ToObject(_serializer.JsonSerializer); + T5 arg5 = default; + if (request.Arguments.Count > 4) arg5 = request.Arguments[4].ToObject(_serializer.JsonSerializer); + T6 arg6 = default; + if (request.Arguments.Count > 5) arg6 = request.Arguments[5].ToObject(_serializer.JsonSerializer); + return Handle(arg1, arg2, arg3, arg4, arg5, arg6, cancellationToken); + } + + public abstract Task Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, CancellationToken cancellationToken); + } + public static partial class ExecuteCommandExtensions { public static Task ExecuteCommand(this IWorkspaceLanguageClient mediator, Command @params, CancellationToken cancellationToken = default) - => mediator.ExecuteCommand(new ExecuteCommandParams() {Arguments = @params.Arguments, Command = @params.Name}, cancellationToken); + => mediator.ExecuteCommand(new ExecuteCommandParams() { Arguments = @params.Arguments, Command = @params.Name }, cancellationToken); + public static Task ExecuteCommand(this ILanguageClient mediator, Command @params, CancellationToken cancellationToken = default) - => mediator.ExecuteCommand(new ExecuteCommandParams() {Arguments = @params.Arguments, Command = @params.Name}, cancellationToken); + => mediator.ExecuteCommand(new ExecuteCommandParams() { Arguments = @params.Arguments, Command = @params.Name }, cancellationToken); + + public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, string command, Func handler) + { + return registry.AddHandler(_ => new Handler(command, handler, _.GetRequiredService())); + } + + class Handler : ExecuteCommandHandlerBase + { + private readonly Func _handler; + + public Handler(string command, Func handler, ISerializer serializer) : base(command, serializer) + { + _handler = handler; + } + + public override async Task Handle(T arg1, CancellationToken cancellationToken) + { + await _handler(arg1); + return Unit.Value; + } + } + + public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, string command, Func handler) + { + return registry.AddHandler(_ => new Handler(command, handler, _.GetRequiredService())); + } + + class Handler : ExecuteCommandHandlerBase + { + private readonly Func _handler; + + public Handler(string command, Func handler, ISerializer serializer) : base(command, serializer) + { + _handler = handler; + } + + public override async Task Handle(T arg1, T2 arg2, CancellationToken cancellationToken) + { + await _handler(arg1, arg2); + return Unit.Value; + } + } + + public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, string command, Func handler) + { + return registry.AddHandler(_ => new Handler(command, handler, _.GetRequiredService())); + } + + class Handler : ExecuteCommandHandlerBase + { + private readonly Func _handler; + + public Handler(string command, Func handler, ISerializer serializer) : base(command, serializer) + { + _handler = handler; + } + + public override async Task Handle(T arg1, T2 arg2, T3 arg3, CancellationToken cancellationToken) + { + await _handler(arg1, arg2, arg3); + return Unit.Value; + } + } + + public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, string command, Func handler) + { + return registry.AddHandler(_ => new Handler(command, handler, _.GetRequiredService())); + } + + class Handler : ExecuteCommandHandlerBase + { + private readonly Func _handler; + + public Handler(string command, Func handler, ISerializer serializer) : base(command, serializer) + { + _handler = handler; + } + + public override async Task Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, CancellationToken cancellationToken) + { + await _handler(arg1, arg2, arg3, arg4); + return Unit.Value; + } + } + + public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, string command, Func handler) + { + return registry.AddHandler(_ => new Handler(command, handler, _.GetRequiredService())); + } + + class Handler : ExecuteCommandHandlerBase + { + private readonly Func _handler; + + public Handler(string command, Func handler, ISerializer serializer) : base(command, serializer) + { + _handler = handler; + } + + public override async Task Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, CancellationToken cancellationToken) + { + await _handler(arg1, arg2, arg3, arg4, arg5); + return Unit.Value; + } + } + + public static ILanguageServerRegistry OnExecuteCommand(this ILanguageServerRegistry registry, string command, Func handler) + { + return registry.AddHandler(_ => new Handler(command, handler, _.GetRequiredService())); + } + + class Handler : ExecuteCommandHandlerBase + { + private readonly Func _handler; + + public Handler(string command, Func handler, ISerializer serializer) : base(command, serializer) + { + _handler = handler; + } + + public override async Task Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, CancellationToken cancellationToken) + { + await _handler(arg1, arg2, arg3, arg4, arg5, arg6); + return Unit.Value; + } + } } } diff --git a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs b/test/Lsp.Tests/Integration/ExecuteCommandTests.cs index 3210501c7..702d98900 100644 --- a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs +++ b/test/Lsp.Tests/Integration/ExecuteCommandTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; @@ -13,6 +14,7 @@ using Serilog.Events; using Xunit; using Xunit.Abstractions; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; namespace Lsp.Tests.Integration { @@ -161,7 +163,7 @@ public async Task Should_Fail_To_Execute_A_Command() return Task.FromResult(new CompletionList(new CompletionItem() { Command = new Command() { Name = "execute-a", - Arguments = JArray.FromObject(new object[] {1, "2", false}) + Arguments = JArray.FromObject(new object[] { 1, "2", false }) } })); }, new CompletionRegistrationOptions() { @@ -188,5 +190,419 @@ public async Task Should_Fail_To_Execute_A_Command() await commandc.Received(0).Invoke(Arg.Any()); await commandb.Received(0).Invoke(Arg.Any()); } + + [Fact] + public async Task Should_Execute_1_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1 }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i) => { + i.Should().Be(1); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_2_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2" }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s) => { + i.Should().Be(1); + s.Should().Be("2"); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_3_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", true }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s, arg3) => { + i.Should().Be(1); + s.Should().Be("2"); + arg3.Should().BeTrue(); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_4_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", true, new Range((0, 1), (1, 1)) }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s, arg3, arg4) => { + i.Should().Be(1); + s.Should().Be("2"); + arg3.Should().BeTrue(); + arg4.Should().Be(new Range((0, 1), (1, 1))); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_5_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", true, new Range((0, 1), (1, 1)), new Dictionary() { ["a"] = "123", ["b"] = "456" } }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand>("execute-a", (i, s, arg3, arg4, arg5) => { + i.Should().Be(1); + s.Should().Be("2"); + arg3.Should().BeTrue(); + arg4.Should().Be(new Range((0, 1), (1, 1))); + arg5.Should().ContainKeys("a", "b"); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_6_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", true, new Range((0, 1), (1, 1)), new Dictionary() { ["a"] = "123", ["b"] = "456" }, Guid.NewGuid() }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand, Guid>("execute-a", (i, s, arg3, arg4, arg5, arg6) => { + i.Should().Be(1); + s.Should().Be("2"); + arg3.Should().BeTrue(); + arg4.Should().Be(new Range((0, 1), (1, 1))); + arg5.Should().ContainKeys("a", "b"); + arg6.Should().NotBeEmpty(); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_1_With_Missing_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] {}) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i) => { + i.Should().Be(default); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_2_With_Missing_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s) => { + i.Should().Be(default); + s.Should().Be(default); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_3_With_Missing_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s, arg3) => { + i.Should().Be(default); + s.Should().Be(default); + arg3.Should().Be(default); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_4_With_Missing_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s, arg3, arg4) => { + i.Should().Be(default); + s.Should().Be(default); + arg3.Should().Be(default); + arg4.Should().Be(default); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_5_With_Missing_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand>("execute-a", (i, s, arg3, arg4, arg5) => { + i.Should().Be(default); + s.Should().Be(default); + arg3.Should().Be(default); + arg4.Should().Be(default); + arg5.Should().BeNull(); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_6_With_Missing_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand, Guid>("execute-a", (i, s, arg3, arg4, arg5, arg6) => { + i.Should().Be(default); + s.Should().Be(default); + arg3.Should().Be(default); + arg4.Should().Be(default); + arg5.Should().BeNull(); + arg6.Should().BeEmpty(); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } } } From 0d7f515be0ccbd13859bf8f1ec375035831df4e0 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Thu, 30 Jul 2020 04:07:31 -0400 Subject: [PATCH 3/6] fixed null arguments --- .../Workspace/IExecuteCommandHandler.cs | 49 ++-- .../Integration/ExecuteCommandTests.cs | 211 +++++++++++++++++- 2 files changed, 234 insertions(+), 26 deletions(-) diff --git a/src/Protocol/Workspace/IExecuteCommandHandler.cs b/src/Protocol/Workspace/IExecuteCommandHandler.cs index 87dcf6577..b16287dca 100644 --- a/src/Protocol/Workspace/IExecuteCommandHandler.cs +++ b/src/Protocol/Workspace/IExecuteCommandHandler.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediatR; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.JsonRpc.Generation; using OmniSharp.Extensions.LanguageServer.Protocol.Client; @@ -42,8 +43,9 @@ public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandler public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) { + var args = request.Arguments ?? new JArray(); T arg1 = default; - if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + if (args.Count > 0) arg1 = args[0].ToObject(_serializer.JsonSerializer); return Handle(arg1, cancellationToken); } @@ -61,10 +63,11 @@ public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandler public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) { + var args = request.Arguments ?? new JArray(); T arg1 = default; - if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + if (args.Count > 0) arg1 = args[0].ToObject(_serializer.JsonSerializer); T2 arg2 = default; - if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + if (args.Count > 1) arg2 = args[1].ToObject(_serializer.JsonSerializer); return Handle(arg1, arg2, cancellationToken); } @@ -82,12 +85,13 @@ public abstract class ExecuteCommandHandlerBase : ExecuteCommandHandl public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) { + var args = request.Arguments ?? new JArray(); T arg1 = default; - if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + if (args.Count > 0) arg1 = args[0].ToObject(_serializer.JsonSerializer); T2 arg2 = default; - if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + if (args.Count > 1) arg2 = args[1].ToObject(_serializer.JsonSerializer); T3 arg3 = default; - if (request.Arguments.Count > 2) arg3 = request.Arguments[2].ToObject(_serializer.JsonSerializer); + if (args.Count > 2) arg3 = args[2].ToObject(_serializer.JsonSerializer); return Handle(arg1, arg2, arg3, cancellationToken); } @@ -105,14 +109,15 @@ public abstract class ExecuteCommandHandlerBase : ExecuteCommandH public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) { + var args = request.Arguments ?? new JArray(); T arg1 = default; - if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + if (args.Count > 0) arg1 = args[0].ToObject(_serializer.JsonSerializer); T2 arg2 = default; - if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + if (args.Count > 1) arg2 = args[1].ToObject(_serializer.JsonSerializer); T3 arg3 = default; - if (request.Arguments.Count > 2) arg3 = request.Arguments[2].ToObject(_serializer.JsonSerializer); + if (args.Count > 2) arg3 = args[2].ToObject(_serializer.JsonSerializer); T4 arg4 = default; - if (request.Arguments.Count > 3) arg4 = request.Arguments[3].ToObject(_serializer.JsonSerializer); + if (args.Count > 3) arg4 = args[3].ToObject(_serializer.JsonSerializer); return Handle(arg1, arg2, arg3, arg4, cancellationToken); } @@ -130,16 +135,17 @@ public abstract class ExecuteCommandHandlerBase : ExecuteComm public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) { + var args = request.Arguments ?? new JArray(); T arg1 = default; - if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + if (args.Count > 0) arg1 = args[0].ToObject(_serializer.JsonSerializer); T2 arg2 = default; - if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + if (args.Count > 1) arg2 = args[1].ToObject(_serializer.JsonSerializer); T3 arg3 = default; - if (request.Arguments.Count > 2) arg3 = request.Arguments[2].ToObject(_serializer.JsonSerializer); + if (args.Count > 2) arg3 = args[2].ToObject(_serializer.JsonSerializer); T4 arg4 = default; - if (request.Arguments.Count > 3) arg4 = request.Arguments[3].ToObject(_serializer.JsonSerializer); + if (args.Count > 3) arg4 = args[3].ToObject(_serializer.JsonSerializer); T5 arg5 = default; - if (request.Arguments.Count > 4) arg5 = request.Arguments[4].ToObject(_serializer.JsonSerializer); + if (args.Count > 4) arg5 = args[4].ToObject(_serializer.JsonSerializer); return Handle(arg1, arg2, arg3, arg4, arg5, cancellationToken); } @@ -157,18 +163,19 @@ public abstract class ExecuteCommandHandlerBase : Execute public sealed override Task Handle(ExecuteCommandParams request, CancellationToken cancellationToken) { + var args = request.Arguments ?? new JArray(); T arg1 = default; - if (request.Arguments.Count > 0) arg1 = request.Arguments[0].ToObject(_serializer.JsonSerializer); + if (args.Count > 0) arg1 = args[0].ToObject(_serializer.JsonSerializer); T2 arg2 = default; - if (request.Arguments.Count > 1) arg2 = request.Arguments[1].ToObject(_serializer.JsonSerializer); + if (args.Count > 1) arg2 = args[1].ToObject(_serializer.JsonSerializer); T3 arg3 = default; - if (request.Arguments.Count > 2) arg3 = request.Arguments[2].ToObject(_serializer.JsonSerializer); + if (args.Count > 2) arg3 = args[2].ToObject(_serializer.JsonSerializer); T4 arg4 = default; - if (request.Arguments.Count > 3) arg4 = request.Arguments[3].ToObject(_serializer.JsonSerializer); + if (args.Count > 3) arg4 = args[3].ToObject(_serializer.JsonSerializer); T5 arg5 = default; - if (request.Arguments.Count > 4) arg5 = request.Arguments[4].ToObject(_serializer.JsonSerializer); + if (args.Count > 4) arg5 = args[4].ToObject(_serializer.JsonSerializer); T6 arg6 = default; - if (request.Arguments.Count > 5) arg6 = request.Arguments[5].ToObject(_serializer.JsonSerializer); + if (args.Count > 5) arg6 = args[5].ToObject(_serializer.JsonSerializer); return Handle(arg1, arg2, arg3, arg4, arg5, arg6, cancellationToken); } diff --git a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs b/test/Lsp.Tests/Integration/ExecuteCommandTests.cs index 702d98900..c34db9c91 100644 --- a/test/Lsp.Tests/Integration/ExecuteCommandTests.cs +++ b/test/Lsp.Tests/Integration/ExecuteCommandTests.cs @@ -407,7 +407,7 @@ public async Task Should_Execute_1_With_Missing_Args() return Task.FromResult(new CompletionList(new CompletionItem() { Command = new Command() { Name = "execute-a", - Arguments = JArray.FromObject(new object[] {}) + Arguments = JArray.FromObject(new object[] { }) } })); }, new CompletionRegistrationOptions() { @@ -439,7 +439,7 @@ public async Task Should_Execute_2_With_Missing_Args() return Task.FromResult(new CompletionList(new CompletionItem() { Command = new Command() { Name = "execute-a", - Arguments = JArray.FromObject(new object[] { }) + Arguments = JArray.FromObject(new object[] { }) } })); }, new CompletionRegistrationOptions() { @@ -506,7 +506,7 @@ public async Task Should_Execute_4_With_Missing_Args() return Task.FromResult(new CompletionList(new CompletionItem() { Command = new Command() { Name = "execute-a", - Arguments = JArray.FromObject(new object[] { }) + Arguments = JArray.FromObject(new object[] { }) } })); }, new CompletionRegistrationOptions() { @@ -541,7 +541,7 @@ public async Task Should_Execute_5_With_Missing_Args() return Task.FromResult(new CompletionList(new CompletionItem() { Command = new Command() { Name = "execute-a", - Arguments = JArray.FromObject(new object[] { }) + Arguments = JArray.FromObject(new object[] { }) } })); }, new CompletionRegistrationOptions() { @@ -577,7 +577,208 @@ public async Task Should_Execute_6_With_Missing_Args() return Task.FromResult(new CompletionList(new CompletionItem() { Command = new Command() { Name = "execute-a", - Arguments = JArray.FromObject(new object[] { }) + Arguments = JArray.FromObject(new object[] { }) + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand, Guid>("execute-a", (i, s, arg3, arg4, arg5, arg6) => { + i.Should().Be(default); + s.Should().Be(default); + arg3.Should().Be(default); + arg4.Should().Be(default); + arg5.Should().BeNull(); + arg6.Should().BeEmpty(); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_1_Null_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a" + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i) => { + i.Should().Be(default); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_2_Null_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a" + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s) => { + i.Should().Be(default); + s.Should().Be(default); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_3_Null_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a" + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s, arg3) => { + i.Should().Be(default); + s.Should().Be(default); + arg3.Should().Be(default); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_4_Null_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a" + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand("execute-a", (i, s, arg3, arg4) => { + i.Should().Be(default); + s.Should().Be(default); + arg3.Should().Be(default); + arg4.Should().Be(default); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_5_Null_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a" + } + })); + }, new CompletionRegistrationOptions() { + }); + + options.OnExecuteCommand>("execute-a", (i, s, arg3, arg4, arg5) => { + i.Should().Be(default); + s.Should().Be(default); + arg3.Should().Be(default); + arg4.Should().Be(default); + arg5.Should().BeNull(); + + return Task.CompletedTask; + }); + }); + + var items = await client.RequestCompletion(new CompletionParams()); + + var item = items.Items.Single(); + + item.Command.Should().NotBeNull(); + + Func action = () => client.ExecuteCommand(item.Command); + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_Execute_6_Null_Args() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCompletion(x => { + return Task.FromResult(new CompletionList(new CompletionItem() { + Command = new Command() { + Name = "execute-a" } })); }, new CompletionRegistrationOptions() { From 4773b6507cdce1acf6d630d1144a13779f6521e4 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Thu, 30 Jul 2020 04:09:28 -0400 Subject: [PATCH 4/6] Updated key for execute command --- src/Shared/SharedHandlerCollection.cs | 37 +++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/Shared/SharedHandlerCollection.cs b/src/Shared/SharedHandlerCollection.cs index cec5dbdf6..efb060ec4 100644 --- a/src/Shared/SharedHandlerCollection.cs +++ b/src/Shared/SharedHandlerCollection.cs @@ -46,7 +46,7 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - IDisposable IHandlersManager.Add(IJsonRpcHandler handler, JsonRpcHandlerOptions options) => Add(new[] {handler}, options); + IDisposable IHandlersManager.Add(IJsonRpcHandler handler, JsonRpcHandlerOptions options) => Add(new[] { handler }, options); IDisposable IHandlersManager.Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) => Add(method, handler, options); @@ -58,7 +58,7 @@ IDisposable IHandlersManager.AddLink(string sourceMethod, string destinationMeth destinationMethod, source.HandlerType, source.Handler, - source.RequestProcessType.HasValue ? new JsonRpcHandlerOptions() {RequestProcessType = source.RequestProcessType.Value} : null, + source.RequestProcessType.HasValue ? new JsonRpcHandlerOptions() { RequestProcessType = source.RequestProcessType.Value } : null, source.TypeDescriptor, source.HandlerType, source.RegistrationType, @@ -70,7 +70,7 @@ IDisposable IHandlersManager.AddLink(string sourceMethod, string destinationMeth cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); } - return new LspHandlerDescriptorDisposable(new[] {descriptor}, cd); + return new LspHandlerDescriptorDisposable(new[] { descriptor }, cd); } public LspHandlerDescriptorDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) @@ -83,7 +83,7 @@ public LspHandlerDescriptorDisposable Add(string method, IJsonRpcHandler handler cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); } - return new LspHandlerDescriptorDisposable(new[] {descriptor}, cd); + return new LspHandlerDescriptorDisposable(new[] { descriptor }, cd); } public LspHandlerDescriptorDisposable Add(string method, Func handlerFunc, JsonRpcHandlerOptions options) @@ -97,7 +97,7 @@ public LspHandlerDescriptorDisposable Add(string method, Func { - public bool Equals((string method, Type implementedInterface) x, (string method, Type implementedInterface) y) - { - return x.method?.Equals(y.method) == true; - } + public bool Equals((string method, Type implementedInterface) x, (string method, Type implementedInterface) y) + { + return x.method?.Equals(y.method) == true; + } - public int GetHashCode((string method, Type implementedInterface) obj) - { - return obj.method?.GetHashCode() ?? 0; + public int GetHashCode((string method, Type implementedInterface) obj) + { + return obj.method?.GetHashCode() ?? 0; + } } - } private LspHandlerDescriptorDisposable Add(IJsonRpcHandler[] handlers, JsonRpcHandlerOptions options) { @@ -241,7 +241,7 @@ private LspHandlerDescriptor GetDescriptor(string method, Type handlerType, IJso { registrationOptions = GetRegistrationMethod .MakeGenericMethod(registrationType) - .Invoke(null, new object[] {handler}); + .Invoke(null, new object[] { handler }); } var key = "default"; @@ -262,10 +262,9 @@ private LspHandlerDescriptor GetDescriptor(string method, Type handlerType, IJso key = handlerRegistration?.GetRegistrationOptions()?.DocumentSelector ?? key; } } - - if (handler is IRegistration commandRegistration) + else if (handler is IRegistration commandRegistration) { - key += "|" + string.Join("|", commandRegistration.GetRegistrationOptions()?.Commands ?? Array.Empty()); + key = string.Join("|", commandRegistration.GetRegistrationOptions()?.Commands ?? Array.Empty()); } if (string.IsNullOrWhiteSpace(key)) key = "default"; @@ -286,7 +285,7 @@ private LspHandlerDescriptor GetDescriptor(string method, Type handlerType, IJso @params, registrationType, registrationOptions, - (registrationType == null ? (Func) (() => false) : (() => _supportedCapabilities.AllowsDynamicRegistration(capabilityType))), + (registrationType == null ? (Func)(() => false) : (() => _supportedCapabilities.AllowsDynamicRegistration(capabilityType))), capabilityType, requestProcessType, () => { _handlers.RemoveWhere(d => d.Handler == handler); }, From 9bd479e756b3edf51ec06a48f0199c9ee2c827be Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Thu, 30 Jul 2020 14:28:07 -0400 Subject: [PATCH 5/6] fixed test to ignore generic types --- test/Lsp.Tests/FoundationTests.cs | 6 +++++- test/Lsp.Tests/Integration/DynamicRegistrationTests.cs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/Lsp.Tests/FoundationTests.cs b/test/Lsp.Tests/FoundationTests.cs index cbe73c303..162e6ff21 100644 --- a/test/Lsp.Tests/FoundationTests.cs +++ b/test/Lsp.Tests/FoundationTests.cs @@ -106,7 +106,11 @@ public void HandlersShouldAbstractClass(ILspHandlerTypeDescriptor descriptor) var abstractHandler = descriptor.HandlerType.Assembly.ExportedTypes.FirstOrDefault(z => z.IsAbstract && z.IsClass && descriptor.HandlerType.IsAssignableFrom(z)); abstractHandler.Should().NotBeNull($"{descriptor.HandlerType.FullName} is missing abstract base class"); - var delegatingHandler = descriptor.HandlerType.Assembly.DefinedTypes.FirstOrDefault(z => abstractHandler.IsAssignableFrom(z) && abstractHandler != z); + var delegatingHandler = descriptor.HandlerType.Assembly.DefinedTypes.FirstOrDefault(z => + abstractHandler.IsAssignableFrom(z) + && abstractHandler != z + && !z.IsGenericTypeDefinition + ); if (delegatingHandler != null) { _logger.LogInformation("Delegating Handler: {Type}", delegatingHandler); diff --git a/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs b/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs index 9c8c52521..5c31b8c4d 100644 --- a/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs +++ b/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs @@ -83,7 +83,7 @@ public async Task Should_Register_Links_Dynamically_While_Server_Is_Running() }) ); - await SettleNext(); + await SettleNext().Take(2); client.RegistrationManager.CurrentRegistrations.Should().Contain(x => x.Method == TextDocumentNames.Completion && SelectorMatches(x, z=> z.HasLanguage && z.Language == "vb") From 65869f1845ab4a1883aa3e3d1d3c046b2fb86312 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Thu, 30 Jul 2020 20:00:39 -0400 Subject: [PATCH 6/6] maybe fixed it all? --- test/Lsp.Tests/Integration/DynamicRegistrationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs b/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs index 5c31b8c4d..a85bc7e1d 100644 --- a/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs +++ b/test/Lsp.Tests/Integration/DynamicRegistrationTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; @@ -83,7 +83,7 @@ public async Task Should_Register_Links_Dynamically_While_Server_Is_Running() }) ); - await SettleNext().Take(2); + await Settle().Take(2); client.RegistrationManager.CurrentRegistrations.Should().Contain(x => x.Method == TextDocumentNames.Completion && SelectorMatches(x, z=> z.HasLanguage && z.Language == "vb")