Skip to content

Commit a40c9ce

Browse files
Commands, bugs, helpers and more! (#268)
* Fixup commands to have a more reliable experience * Added helper base classes and helper methods for handling single commands with a known set of arguments up to 6
1 parent 7d4ecc5 commit a40c9ce

File tree

13 files changed

+1204
-65
lines changed

13 files changed

+1204
-65
lines changed

src/JsonRpc.Generators/Helpers.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ public static string GetSendMethodName(INamedTypeSymbol symbol, AttributeData at
828828
var name = SpecialCasedHandlerName(symbol);
829829
if (
830830
name.StartsWith("Run")
831+
|| name.StartsWith("Execute")
831832
// TODO: Change this next breaking change
832833
// || name.StartsWith("Set")
833834
// || name.StartsWith("Attach")

src/Protocol/Models/ExecuteCommandRegistrationOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
namespace OmniSharp.Extensions.LanguageServer.Protocol.Models
1+
namespace OmniSharp.Extensions.LanguageServer.Protocol.Models
22
{
33
/// <summary>
44
/// Execute command registration options.
55
/// </summary>
6-
public class ExecuteCommandRegistrationOptions : WorkDoneTextDocumentRegistrationOptions, IExecuteCommandOptions
6+
public class ExecuteCommandRegistrationOptions : WorkDoneProgressOptions, IExecuteCommandOptions
77
{
88
/// <summary>
99
/// The commands to be executed on the server

src/Protocol/Workspace/IExecuteCommandHandler.cs

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
using System;
12
using System.Threading;
23
using System.Threading.Tasks;
34
using MediatR;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Newtonsoft.Json.Linq;
47
using OmniSharp.Extensions.JsonRpc;
58
using OmniSharp.Extensions.JsonRpc.Generation;
69
using OmniSharp.Extensions.LanguageServer.Protocol.Client;
710
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
811
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
12+
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
913

1014
namespace OmniSharp.Extensions.LanguageServer.Protocol.Workspace
1115
{
@@ -27,4 +31,289 @@ public ExecuteCommandHandler(ExecuteCommandRegistrationOptions registrationOptio
2731
public virtual void SetCapability(ExecuteCommandCapability capability) => Capability = capability;
2832
protected ExecuteCommandCapability Capability { get; private set; }
2933
}
34+
35+
public abstract class ExecuteCommandHandlerBase<T> : ExecuteCommandHandler
36+
{
37+
private readonly ISerializer _serializer;
38+
39+
public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container<string>(command) })
40+
{
41+
_serializer = serializer;
42+
}
43+
44+
public sealed override Task<Unit> Handle(ExecuteCommandParams request, CancellationToken cancellationToken)
45+
{
46+
var args = request.Arguments ?? new JArray();
47+
T arg1 = default;
48+
if (args.Count > 0) arg1 = args[0].ToObject<T>(_serializer.JsonSerializer);
49+
return Handle(arg1, cancellationToken);
50+
}
51+
52+
public abstract Task<Unit> Handle(T arg1, CancellationToken cancellationToken);
53+
}
54+
55+
public abstract class ExecuteCommandHandlerBase<T, T2> : ExecuteCommandHandler
56+
{
57+
private readonly ISerializer _serializer;
58+
59+
public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container<string>(command) })
60+
{
61+
_serializer = serializer;
62+
}
63+
64+
public sealed override Task<Unit> Handle(ExecuteCommandParams request, CancellationToken cancellationToken)
65+
{
66+
var args = request.Arguments ?? new JArray();
67+
T arg1 = default;
68+
if (args.Count > 0) arg1 = args[0].ToObject<T>(_serializer.JsonSerializer);
69+
T2 arg2 = default;
70+
if (args.Count > 1) arg2 = args[1].ToObject<T2>(_serializer.JsonSerializer);
71+
return Handle(arg1, arg2, cancellationToken);
72+
}
73+
74+
public abstract Task<Unit> Handle(T arg1, T2 arg2, CancellationToken cancellationToken);
75+
}
76+
77+
public abstract class ExecuteCommandHandlerBase<T, T2, T3> : ExecuteCommandHandler
78+
{
79+
private readonly ISerializer _serializer;
80+
81+
public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container<string>(command) })
82+
{
83+
_serializer = serializer;
84+
}
85+
86+
public sealed override Task<Unit> Handle(ExecuteCommandParams request, CancellationToken cancellationToken)
87+
{
88+
var args = request.Arguments ?? new JArray();
89+
T arg1 = default;
90+
if (args.Count > 0) arg1 = args[0].ToObject<T>(_serializer.JsonSerializer);
91+
T2 arg2 = default;
92+
if (args.Count > 1) arg2 = args[1].ToObject<T2>(_serializer.JsonSerializer);
93+
T3 arg3 = default;
94+
if (args.Count > 2) arg3 = args[2].ToObject<T3>(_serializer.JsonSerializer);
95+
return Handle(arg1, arg2, arg3, cancellationToken);
96+
}
97+
98+
public abstract Task<Unit> Handle(T arg1, T2 arg2, T3 arg3, CancellationToken cancellationToken);
99+
}
100+
101+
public abstract class ExecuteCommandHandlerBase<T, T2, T3, T4> : ExecuteCommandHandler
102+
{
103+
private readonly ISerializer _serializer;
104+
105+
public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container<string>(command) })
106+
{
107+
_serializer = serializer;
108+
}
109+
110+
public sealed override Task<Unit> Handle(ExecuteCommandParams request, CancellationToken cancellationToken)
111+
{
112+
var args = request.Arguments ?? new JArray();
113+
T arg1 = default;
114+
if (args.Count > 0) arg1 = args[0].ToObject<T>(_serializer.JsonSerializer);
115+
T2 arg2 = default;
116+
if (args.Count > 1) arg2 = args[1].ToObject<T2>(_serializer.JsonSerializer);
117+
T3 arg3 = default;
118+
if (args.Count > 2) arg3 = args[2].ToObject<T3>(_serializer.JsonSerializer);
119+
T4 arg4 = default;
120+
if (args.Count > 3) arg4 = args[3].ToObject<T4>(_serializer.JsonSerializer);
121+
return Handle(arg1, arg2, arg3, arg4, cancellationToken);
122+
}
123+
124+
public abstract Task<Unit> Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, CancellationToken cancellationToken);
125+
}
126+
127+
public abstract class ExecuteCommandHandlerBase<T, T2, T3, T4, T5> : ExecuteCommandHandler
128+
{
129+
private readonly ISerializer _serializer;
130+
131+
public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container<string>(command) })
132+
{
133+
_serializer = serializer;
134+
}
135+
136+
public sealed override Task<Unit> Handle(ExecuteCommandParams request, CancellationToken cancellationToken)
137+
{
138+
var args = request.Arguments ?? new JArray();
139+
T arg1 = default;
140+
if (args.Count > 0) arg1 = args[0].ToObject<T>(_serializer.JsonSerializer);
141+
T2 arg2 = default;
142+
if (args.Count > 1) arg2 = args[1].ToObject<T2>(_serializer.JsonSerializer);
143+
T3 arg3 = default;
144+
if (args.Count > 2) arg3 = args[2].ToObject<T3>(_serializer.JsonSerializer);
145+
T4 arg4 = default;
146+
if (args.Count > 3) arg4 = args[3].ToObject<T4>(_serializer.JsonSerializer);
147+
T5 arg5 = default;
148+
if (args.Count > 4) arg5 = args[4].ToObject<T5>(_serializer.JsonSerializer);
149+
return Handle(arg1, arg2, arg3, arg4, arg5, cancellationToken);
150+
}
151+
152+
public abstract Task<Unit> Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, CancellationToken cancellationToken);
153+
}
154+
155+
public abstract class ExecuteCommandHandlerBase<T, T2, T3, T4, T5, T6> : ExecuteCommandHandler
156+
{
157+
private readonly ISerializer _serializer;
158+
159+
public ExecuteCommandHandlerBase(string command, ISerializer serializer) : base(new ExecuteCommandRegistrationOptions() { Commands = new Container<string>(command) })
160+
{
161+
_serializer = serializer;
162+
}
163+
164+
public sealed override Task<Unit> Handle(ExecuteCommandParams request, CancellationToken cancellationToken)
165+
{
166+
var args = request.Arguments ?? new JArray();
167+
T arg1 = default;
168+
if (args.Count > 0) arg1 = args[0].ToObject<T>(_serializer.JsonSerializer);
169+
T2 arg2 = default;
170+
if (args.Count > 1) arg2 = args[1].ToObject<T2>(_serializer.JsonSerializer);
171+
T3 arg3 = default;
172+
if (args.Count > 2) arg3 = args[2].ToObject<T3>(_serializer.JsonSerializer);
173+
T4 arg4 = default;
174+
if (args.Count > 3) arg4 = args[3].ToObject<T4>(_serializer.JsonSerializer);
175+
T5 arg5 = default;
176+
if (args.Count > 4) arg5 = args[4].ToObject<T5>(_serializer.JsonSerializer);
177+
T6 arg6 = default;
178+
if (args.Count > 5) arg6 = args[5].ToObject<T6>(_serializer.JsonSerializer);
179+
return Handle(arg1, arg2, arg3, arg4, arg5, arg6, cancellationToken);
180+
}
181+
182+
public abstract Task<Unit> Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, CancellationToken cancellationToken);
183+
}
184+
185+
public static partial class ExecuteCommandExtensions
186+
{
187+
public static Task ExecuteCommand(this IWorkspaceLanguageClient mediator, Command @params, CancellationToken cancellationToken = default)
188+
=> mediator.ExecuteCommand(new ExecuteCommandParams() { Arguments = @params.Arguments, Command = @params.Name }, cancellationToken);
189+
190+
public static Task ExecuteCommand(this ILanguageClient mediator, Command @params, CancellationToken cancellationToken = default)
191+
=> mediator.ExecuteCommand(new ExecuteCommandParams() { Arguments = @params.Arguments, Command = @params.Name }, cancellationToken);
192+
193+
public static ILanguageServerRegistry OnExecuteCommand<T>(this ILanguageServerRegistry registry, string command, Func<T, Task> handler)
194+
{
195+
return registry.AddHandler(_ => new Handler<T>(command, handler, _.GetRequiredService<ISerializer>()));
196+
}
197+
198+
class Handler<T> : ExecuteCommandHandlerBase<T>
199+
{
200+
private readonly Func<T, Task> _handler;
201+
202+
public Handler(string command, Func<T, Task> handler, ISerializer serializer) : base(command, serializer)
203+
{
204+
_handler = handler;
205+
}
206+
207+
public override async Task<Unit> Handle(T arg1, CancellationToken cancellationToken)
208+
{
209+
await _handler(arg1);
210+
return Unit.Value;
211+
}
212+
}
213+
214+
public static ILanguageServerRegistry OnExecuteCommand<T, T2>(this ILanguageServerRegistry registry, string command, Func<T, T2, Task> handler)
215+
{
216+
return registry.AddHandler(_ => new Handler<T, T2>(command, handler, _.GetRequiredService<ISerializer>()));
217+
}
218+
219+
class Handler<T, T2> : ExecuteCommandHandlerBase<T, T2>
220+
{
221+
private readonly Func<T, T2, Task> _handler;
222+
223+
public Handler(string command, Func<T, T2, Task> handler, ISerializer serializer) : base(command, serializer)
224+
{
225+
_handler = handler;
226+
}
227+
228+
public override async Task<Unit> Handle(T arg1, T2 arg2, CancellationToken cancellationToken)
229+
{
230+
await _handler(arg1, arg2);
231+
return Unit.Value;
232+
}
233+
}
234+
235+
public static ILanguageServerRegistry OnExecuteCommand<T, T2, T3>(this ILanguageServerRegistry registry, string command, Func<T, T2, T3, Task> handler)
236+
{
237+
return registry.AddHandler(_ => new Handler<T, T2, T3>(command, handler, _.GetRequiredService<ISerializer>()));
238+
}
239+
240+
class Handler<T, T2, T3> : ExecuteCommandHandlerBase<T, T2, T3>
241+
{
242+
private readonly Func<T, T2, T3, Task> _handler;
243+
244+
public Handler(string command, Func<T, T2, T3, Task> handler, ISerializer serializer) : base(command, serializer)
245+
{
246+
_handler = handler;
247+
}
248+
249+
public override async Task<Unit> Handle(T arg1, T2 arg2, T3 arg3, CancellationToken cancellationToken)
250+
{
251+
await _handler(arg1, arg2, arg3);
252+
return Unit.Value;
253+
}
254+
}
255+
256+
public static ILanguageServerRegistry OnExecuteCommand<T, T2, T3, T4>(this ILanguageServerRegistry registry, string command, Func<T, T2, T3, T4, Task> handler)
257+
{
258+
return registry.AddHandler(_ => new Handler<T, T2, T3, T4>(command, handler, _.GetRequiredService<ISerializer>()));
259+
}
260+
261+
class Handler<T, T2, T3, T4> : ExecuteCommandHandlerBase<T, T2, T3, T4>
262+
{
263+
private readonly Func<T, T2, T3, T4, Task> _handler;
264+
265+
public Handler(string command, Func<T, T2, T3, T4, Task> handler, ISerializer serializer) : base(command, serializer)
266+
{
267+
_handler = handler;
268+
}
269+
270+
public override async Task<Unit> Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, CancellationToken cancellationToken)
271+
{
272+
await _handler(arg1, arg2, arg3, arg4);
273+
return Unit.Value;
274+
}
275+
}
276+
277+
public static ILanguageServerRegistry OnExecuteCommand<T, T2, T3, T4, T5>(this ILanguageServerRegistry registry, string command, Func<T, T2, T3, T4, T5, Task> handler)
278+
{
279+
return registry.AddHandler(_ => new Handler<T, T2, T3, T4, T5>(command, handler, _.GetRequiredService<ISerializer>()));
280+
}
281+
282+
class Handler<T, T2, T3, T4, T5> : ExecuteCommandHandlerBase<T, T2, T3, T4, T5>
283+
{
284+
private readonly Func<T, T2, T3, T4, T5, Task> _handler;
285+
286+
public Handler(string command, Func<T, T2, T3, T4, T5, Task> handler, ISerializer serializer) : base(command, serializer)
287+
{
288+
_handler = handler;
289+
}
290+
291+
public override async Task<Unit> Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, CancellationToken cancellationToken)
292+
{
293+
await _handler(arg1, arg2, arg3, arg4, arg5);
294+
return Unit.Value;
295+
}
296+
}
297+
298+
public static ILanguageServerRegistry OnExecuteCommand<T, T2, T3, T4, T5, T6>(this ILanguageServerRegistry registry, string command, Func<T, T2, T3, T4, T5, T6, Task> handler)
299+
{
300+
return registry.AddHandler(_ => new Handler<T, T2, T3, T4, T5, T6>(command, handler, _.GetRequiredService<ISerializer>()));
301+
}
302+
303+
class Handler<T, T2, T3, T4, T5, T6> : ExecuteCommandHandlerBase<T, T2, T3, T4, T5, T6>
304+
{
305+
private readonly Func<T, T2, T3, T4, T5, T6, Task> _handler;
306+
307+
public Handler(string command, Func<T, T2, T3, T4, T5, T6, Task> handler, ISerializer serializer) : base(command, serializer)
308+
{
309+
_handler = handler;
310+
}
311+
312+
public override async Task<Unit> Handle(T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, CancellationToken cancellationToken)
313+
{
314+
await _handler(arg1, arg2, arg3, arg4, arg5, arg6);
315+
return Unit.Value;
316+
}
317+
}
318+
}
30319
}

src/Server/Matchers/TextDocumentMatcher.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class TextDocumentMatcher : IHandlerMatcher
1717
public TextDocumentMatcher(ILogger<TextDocumentMatcher> logger, TextDocumentIdentifiers textDocumentIdentifiers)
1818
{
1919
_logger = logger;
20-
_textDocumentIdentifiers = textDocumentIdentifiers;;
20+
_textDocumentIdentifiers = textDocumentIdentifiers; ;
2121
}
2222

2323
public IEnumerable<ILspHandlerDescriptor> FindHandler(object parameters, IEnumerable<ILspHandlerDescriptor> descriptors)
@@ -26,6 +26,7 @@ public IEnumerable<ILspHandlerDescriptor> FindHandler(object parameters, IEnumer
2626
{
2727
case ITextDocumentIdentifierParams textDocumentIdentifierParams:
2828
{
29+
if (textDocumentIdentifierParams.TextDocument?.Uri == null) break;
2930
var attributes = GetTextDocumentAttributes(textDocumentIdentifierParams.TextDocument.Uri);
3031

3132
_logger.LogTrace("Found attributes {Count}, {Attributes}", attributes.Count, attributes.Select(x => $"{x.LanguageId}:{x.Scheme}:{x.Uri}"));
@@ -34,6 +35,7 @@ public IEnumerable<ILspHandlerDescriptor> FindHandler(object parameters, IEnumer
3435
}
3536
case DidOpenTextDocumentParams openTextDocumentParams:
3637
{
38+
if (openTextDocumentParams.TextDocument?.Uri == null) break;
3739
var attributes = new TextDocumentAttributes(openTextDocumentParams.TextDocument.Uri, openTextDocumentParams.TextDocument.LanguageId);
3840

3941
_logger.LogTrace("Created attribute {Attribute}", $"{attributes.LanguageId}:{attributes.Scheme}:{attributes.Uri}");
@@ -42,6 +44,7 @@ public IEnumerable<ILspHandlerDescriptor> FindHandler(object parameters, IEnumer
4244
}
4345
case DidChangeTextDocumentParams didChangeDocumentParams:
4446
{
47+
if (didChangeDocumentParams.TextDocument?.Uri == null) break;
4548
// TODO: Do something with document version here?
4649
var attributes = GetTextDocumentAttributes(didChangeDocumentParams.TextDocument.Uri);
4750

src/Shared/LspRequestRouter.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Newtonsoft.Json.Linq;
99
using OmniSharp.Extensions.JsonRpc;
1010
using OmniSharp.Extensions.JsonRpc.Server;
11+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1112
using OmniSharp.Extensions.LanguageServer.Protocol.Shared;
1213
using ISerializer = OmniSharp.Extensions.LanguageServer.Protocol.Serialization.ISerializer;
1314

@@ -57,13 +58,20 @@ private ILspHandlerDescriptor FindDescriptor(string method, JToken @params)
5758
return null;
5859
}
5960

61+
6062
if (@params == null || descriptor.Params == null) return descriptor;
6163

6264
var lspHandlerDescriptors = _collection.Where(handler => handler.Method == method).ToList();
63-
if (lspHandlerDescriptors.Count == 1) return descriptor;
6465

6566
var paramsValue = @params.ToObject(descriptor.Params, _serializer.JsonSerializer);
66-
return _handlerMatchers.SelectMany(strat => strat.FindHandler(paramsValue, lspHandlerDescriptors)).FirstOrDefault() ?? descriptor;
67+
var matchDescriptor = _handlerMatchers.SelectMany(strat => strat.FindHandler(paramsValue, lspHandlerDescriptors)).FirstOrDefault();
68+
if (matchDescriptor != null) return matchDescriptor;
69+
// execute command is a special case
70+
// if no command was found to execute this must error
71+
// this is not great coupling but other options require api changes
72+
if (paramsValue is ExecuteCommandParams) return null;
73+
if (lspHandlerDescriptors.Count == 1) return descriptor;
74+
return null;
6775
}
6876

6977
IHandlerDescriptor IRequestRouter<IHandlerDescriptor>.GetDescriptor(Notification notification) => GetDescriptor(notification);

0 commit comments

Comments
 (0)