Skip to content

Commit dcea55f

Browse files
Saulius Menkeviciusrazzmatazz
authored andcommitted
LSP: update OmniSharpCodeHandler so we support codeAction/resolve
This feature was introduced in LSP 3.16.0 https://microsoft.github.io/language-server-protocol/specification#codeAction_resolve and allows us to drop the hack where we were introducing a special "omnisharp/executeCodeAction" command to delay the resolution of code action changesets until user actually selets that code action: see OmniSharp#1814. Related to: - OmniSharp#2068 - OmniSharp#1814
1 parent aa1018c commit dcea55f

3 files changed

Lines changed: 78 additions & 86 deletions

File tree

src/OmniSharp.LanguageServerProtocol/Handlers/OmniSharpCodeActionHandler.cs

Lines changed: 45 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,59 @@
11
using System.Linq;
22
using System.Collections.Generic;
3-
using System.Diagnostics;
43
using System.Threading;
54
using System.Threading.Tasks;
6-
using MediatR;
7-
using Microsoft.CodeAnalysis;
85
using Newtonsoft.Json.Linq;
96
using OmniSharp.Extensions.JsonRpc;
107
using OmniSharp.Extensions.LanguageServer.Protocol;
118
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
129
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1310
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
14-
using OmniSharp.Models.V2.CodeActions;
1511
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
16-
using OmniSharp.Extensions.LanguageServer.Protocol.Workspace;
17-
using OmniSharp.Models;
1812
using Diagnostic = OmniSharp.Extensions.LanguageServer.Protocol.Models.Diagnostic;
13+
using OmniSharp.Models.V2.CodeActions;
1914

2015
namespace OmniSharp.LanguageServerProtocol.Handlers
2116
{
22-
internal sealed class OmniSharpCodeActionHandler : CodeActionHandlerBase, IExecuteCommandHandler
17+
internal sealed class OmniSharpCodeActionHandler : CodeActionHandlerBase
2318
{
2419
public static IEnumerable<IJsonRpcHandler> Enumerate(
2520
RequestHandlers handlers,
26-
ISerializer serializer,
2721
ILanguageServer mediator,
2822
DocumentVersions versions)
2923
{
3024
foreach (var (selector, getActionsHandler, runActionHandler) in handlers
3125
.OfType<Mef.IRequestHandler<GetCodeActionsRequest, GetCodeActionsResponse>,
3226
Mef.IRequestHandler<RunCodeActionRequest, RunCodeActionResponse>>())
3327
{
34-
yield return new OmniSharpCodeActionHandler(getActionsHandler, runActionHandler, selector, serializer, mediator, versions);
28+
yield return new OmniSharpCodeActionHandler(getActionsHandler, runActionHandler, selector, mediator, versions);
3529
}
3630
}
3731

3832
private readonly Mef.IRequestHandler<GetCodeActionsRequest, GetCodeActionsResponse> _getActionsHandler;
39-
private readonly ExecuteCommandRegistrationOptions _executeCommandRegistrationOptions;
40-
private ExecuteCommandCapability _executeCommandCapability;
41-
private Mef.IRequestHandler<RunCodeActionRequest, RunCodeActionResponse> _runActionHandler;
33+
private readonly Mef.IRequestHandler<RunCodeActionRequest, RunCodeActionResponse> _runActionHandler;
4234
private readonly DocumentSelector _documentSelector;
43-
private readonly ISerializer _serializer;
4435
private readonly ILanguageServer _server;
4536
private readonly DocumentVersions _documentVersions;
4637

4738
public OmniSharpCodeActionHandler(
4839
Mef.IRequestHandler<GetCodeActionsRequest, GetCodeActionsResponse> getActionsHandler,
4940
Mef.IRequestHandler<RunCodeActionRequest, RunCodeActionResponse> runActionHandler,
5041
DocumentSelector documentSelector,
51-
ISerializer serializer,
5242
ILanguageServer server,
5343
DocumentVersions documentVersions)
5444
{
5545
_getActionsHandler = getActionsHandler;
5646
_runActionHandler = runActionHandler;
5747
_documentSelector = documentSelector;
58-
_serializer = serializer;
5948
_server = server;
6049
_documentVersions = documentVersions;
61-
_executeCommandRegistrationOptions = new ExecuteCommandRegistrationOptions()
62-
{
63-
Commands = new Container<string>("omnisharp/executeCodeAction"),
64-
};
6550
}
6651

6752
public override async Task<CommandOrCodeActionContainer> Handle(CodeActionParams request, CancellationToken cancellationToken)
6853
{
54+
var codeActionCaps = _server.ClientSettings.Capabilities.TextDocument.CodeAction.Value;
55+
bool clientCanResolveEditProp = codeActionCaps.ResolveSupport?.Properties.Contains("edit") ?? false;
56+
6957
var omnisharpRequest = new GetCodeActionsRequest
7058
{
7159
FileName = Helpers.FromUri(request.TextDocument.Uri),
@@ -87,40 +75,44 @@ public override async Task<CommandOrCodeActionContainer> Handle(CodeActionParams
8775
else if (ca.Identifier.StartsWith("Change ")) { kind = CodeActionKind.QuickFix; }
8876
else { kind = CodeActionKind.Refactor; }
8977

90-
codeActions.Add(
91-
new CodeAction
92-
{
93-
Title = ca.Name,
94-
Kind = kind,
95-
Diagnostics = new Container<Diagnostic>(),
96-
Edit = new WorkspaceEdit(),
97-
Command = Command.Create("omnisharp/executeCodeAction")
98-
.WithArguments(new CommandData()
99-
{
100-
Uri = request.TextDocument.Uri,
101-
Identifier = ca.Identifier,
102-
Name = ca.Name,
103-
Range = request.Range,
104-
})
105-
with { Title = ca.Name }
106-
});
78+
var codeAction = new CodeAction {
79+
Title = ca.Name,
80+
Kind = kind,
81+
Diagnostics = new Container<Diagnostic>(),
82+
Edit = null,
83+
Data = JObject.FromObject(
84+
new CommandData()
85+
{
86+
Uri = request.TextDocument.Uri,
87+
Identifier = ca.Identifier,
88+
Name = ca.Name,
89+
Range = request.Range,
90+
})
91+
};
92+
93+
if (!clientCanResolveEditProp)
94+
{
95+
var codeActionResolution = await this.Handle(codeAction, cancellationToken);
96+
97+
codeAction = codeAction with {
98+
Edit = codeActionResolution.Edit,
99+
Data = null,
100+
};
101+
}
102+
103+
codeActions.Add(codeAction);
107104
}
108105

109106
return new CommandOrCodeActionContainer(
110107
codeActions.Select(ca => new CommandOrCodeAction(ca)));
111108
}
112109

113-
public override Task<CodeAction> Handle(CodeAction request, CancellationToken cancellationToken)
110+
public override async Task<CodeAction> Handle(CodeAction request, CancellationToken cancellationToken)
114111
{
115-
return Task.FromResult(request);
116-
}
112+
var data = request.Data.ToObject<CommandData>();
117113

118-
public async Task<Unit> Handle(ExecuteCommandParams request, CancellationToken cancellationToken)
119-
{
120-
Debug.Assert(request.Command == "omnisharp/executeCodeAction");
121-
var data = request.ExtractArguments<CommandData>(_serializer);
122-
123-
var omnisharpCaRequest = new RunCodeActionRequest {
114+
var omnisharpCaRequest = new RunCodeActionRequest
115+
{
124116
Identifier = data.Identifier,
125117
FileName = data.Uri.GetFileSystemPath(),
126118
Column = data.Range.Start.Character,
@@ -139,19 +131,16 @@ public async Task<Unit> Handle(ExecuteCommandParams request, CancellationToken c
139131
_server.ClientSettings.Capabilities.Workspace!.WorkspaceEdit.Value,
140132
_documentVersions
141133
);
142-
;
143134

144-
await _server.Workspace.ApplyWorkspaceEdit(new ApplyWorkspaceEditParams()
135+
return new CodeAction
145136
{
146-
Label = data.Name,
147-
Edit = edit
148-
}, cancellationToken);
149-
150-
// Do something with response?
151-
//if (response.Applied)
137+
Edit = edit,
138+
};
139+
}
140+
else
141+
{
142+
return new CodeAction();
152143
}
153-
154-
return Unit.Value;
155144
}
156145

157146
class CommandData
@@ -162,12 +151,6 @@ class CommandData
162151
public Range Range { get; set;}
163152
}
164153

165-
ExecuteCommandRegistrationOptions IRegistration<ExecuteCommandRegistrationOptions, ExecuteCommandCapability>.GetRegistrationOptions(ExecuteCommandCapability capability, ClientCapabilities clientCapabilities)
166-
{
167-
_executeCommandCapability = capability;
168-
return _executeCommandRegistrationOptions;
169-
}
170-
171154
protected override CodeActionRegistrationOptions CreateRegistrationOptions(CodeActionCapability capability, ClientCapabilities clientCapabilities)
172155
{
173156
return new CodeActionRegistrationOptions()
@@ -177,6 +160,7 @@ protected override CodeActionRegistrationOptions CreateRegistrationOptions(CodeA
177160
CodeActionKind.SourceOrganizeImports,
178161
CodeActionKind.Refactor,
179162
CodeActionKind.RefactorExtract),
163+
ResolveProvider = true,
180164
};
181165
}
182166
}

src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ internal static void RegisterHandlers(ILanguageServer server, CompositionHost co
381381
.Concat(OmniSharpReferencesHandler.Enumerate(handlers))
382382
.Concat(OmniSharpImplementationHandler.Enumerate(handlers))
383383
.Concat(OmniSharpCodeLensHandler.Enumerate(handlers))
384-
.Concat(OmniSharpCodeActionHandler.Enumerate(handlers, serializer, server, documentVersions))
384+
.Concat(OmniSharpCodeActionHandler.Enumerate(handlers, server, documentVersions))
385385
.Concat(OmniSharpDocumentFormattingHandler.Enumerate(handlers))
386386
.Concat(OmniSharpDocumentFormatRangeHandler.Enumerate(handlers))
387387
.Concat(OmniSharpDocumentOnTypeFormattingHandler.Enumerate(handlers)))

tests/OmniSharp.Lsp.Tests/OmniSharpCodeActionHandlerFacts.cs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using Microsoft.Extensions.Configuration;
88
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Linq;
910
using OmniSharp.Extensions.LanguageServer.Protocol;
1011
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
1112
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
@@ -228,14 +229,19 @@ await Configuration.Update("omnisharp",
228229
var project = await AddProjectToWorkspace(testProject);
229230
var document = project.Documents.First();
230231

231-
await Client.ExecuteCommand(Command.Create("omnisharp/executeCodeAction")
232-
.WithArguments(new
233-
{
232+
var resolved = await Client.ResolveCodeAction(new CodeAction
233+
{
234+
Title = "N/A",
235+
Data = JObject.FromObject(new {
234236
Uri = DocumentUri.FromFileSystemPath(document.FilePath),
235237
Identifier = "Generate class 'Z' in new file",
236238
Name = "N/A",
237239
Range = new Range((8, 12), (8, 12)),
238-
}), CancellationToken);
240+
})
241+
});
242+
243+
await Server.Workspace.ApplyWorkspaceEdit(
244+
new ApplyWorkspaceEditParams { Edit = resolved.Edit });
239245

240246
var updatedDocument = OmniSharpTestHost.Workspace.GetDocument(Path.Combine(Path.GetDirectoryName(document.FilePath), "Z.cs"));
241247
var updateDocumentText = await updatedDocument.GetTextAsync(CancellationToken);
@@ -258,14 +264,19 @@ public async Task Can_send_rename_and_fileOpen_responses_when_codeAction_renames
258264
var project = await AddProjectToWorkspace(testProject);
259265
var document = project.Documents.First();
260266

261-
await Client.ExecuteCommand(Command.Create("omnisharp/executeCodeAction")
262-
.WithArguments(new
263-
{
267+
var resolved = await Client.ResolveCodeAction(new CodeAction
268+
{
269+
Title = "N/A",
270+
Data = JObject.FromObject(new {
264271
Uri = DocumentUri.FromFileSystemPath(document.FilePath),
265272
Identifier = "Rename file to Class1.cs",
266273
Name = "N/A",
267274
Range = new Range((4, 10), (4, 10)),
268-
}), CancellationToken);
275+
})
276+
});
277+
278+
await Server.Workspace.ApplyWorkspaceEdit(
279+
new ApplyWorkspaceEditParams { Edit = resolved.Edit });
269280

270281
Assert.Empty(OmniSharpTestHost.Workspace.GetDocuments(document.FilePath));
271282

@@ -281,9 +292,19 @@ private async Task<IEnumerable<TestFile>> RunRefactoringAsync(string code, strin
281292
configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(isAnalyzersEnabled));
282293
Assert.Contains(refactoringName, refactorings.Select(x => x.Title), StringComparer.OrdinalIgnoreCase);
283294

284-
var command = refactorings
285-
.First(action => action.Title.Equals(refactoringName, StringComparison.OrdinalIgnoreCase)).Command;
286-
return await RunRefactoringsAsync(code, command);
295+
var codeAction = refactorings.First(action => action.Title.Equals(refactoringName, StringComparison.OrdinalIgnoreCase));
296+
var resolved = await Client.ResolveCodeAction(codeAction);
297+
298+
var bufferPath =
299+
$"{Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}somepath{Path.DirectorySeparatorChar}buffer.cs";
300+
var testFile = new TestFile(bufferPath, code);
301+
302+
OmniSharpTestHost.AddFilesToWorkspace(testFile);
303+
304+
await Server.Workspace.ApplyWorkspaceEdit(
305+
new ApplyWorkspaceEditParams { Edit = resolved.Edit });
306+
307+
return new[] {testFile};
287308
}
288309

289310
private async Task<IEnumerable<string>> FindRefactoringNamesAsync(string code, bool isAnalyzersEnabled = true)
@@ -315,19 +336,6 @@ private async Task<IEnumerable<CodeAction>> FindRefactoringsAsync(string code,
315336
return response.Where(z => z.IsCodeAction).Select(z => z.CodeAction);
316337
}
317338

318-
private async Task<IEnumerable<TestFile>> RunRefactoringsAsync(string code, Command command)
319-
{
320-
var bufferPath =
321-
$"{Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}somepath{Path.DirectorySeparatorChar}buffer.cs";
322-
var testFile = new TestFile(bufferPath, code);
323-
324-
OmniSharpTestHost.AddFilesToWorkspace(testFile);
325-
326-
await Client.Workspace.ExecuteCommand(command, CancellationToken);
327-
328-
return new[] {testFile};
329-
}
330-
331339
private static Models.V2.Range GetSelection(TextRange range)
332340
{
333341
if (range.IsEmpty)

0 commit comments

Comments
 (0)