diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fc8ee429c..8afa01207b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog All changes to the project will be documented in this file. -## [1.35.3] - Not yet released +## [1.35.3] - not yet released +* Added LSP handler for `textDocument/codeAction` request. (PR: [#1795](https://github.com/OmniSharp/omnisharp-roslyn/pull/1795)) ## [1.35.2] - 2020-05-20 * Added support for `WarningsAsErrors` in csproj files (PR: [#1779](https://github.com/OmniSharp/omnisharp-roslyn/pull/1779)) diff --git a/src/OmniSharp.LanguageServerProtocol/Handlers/OmniSharpCodeActionHandler.cs b/src/OmniSharp.LanguageServerProtocol/Handlers/OmniSharpCodeActionHandler.cs new file mode 100644 index 0000000000..ff04622646 --- /dev/null +++ b/src/OmniSharp.LanguageServerProtocol/Handlers/OmniSharpCodeActionHandler.cs @@ -0,0 +1,101 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Models; +using OmniSharp.Models.V2.CodeActions; + +namespace OmniSharp.LanguageServerProtocol.Handlers +{ + internal sealed class OmniSharpCodeActionHandler: CodeActionHandler + { + public static IEnumerable Enumerate(RequestHandlers handlers) + { + foreach (var (selector, getActionsHandler, runActionHandler) in handlers + .OfType, + Mef.IRequestHandler>()) + { + yield return new OmniSharpCodeActionHandler(getActionsHandler, runActionHandler, selector); + } + } + + private readonly Mef.IRequestHandler _getActionsHandler; + private readonly Mef.IRequestHandler _runActionHandler; + + public OmniSharpCodeActionHandler( + Mef.IRequestHandler getActionsHandler, + Mef.IRequestHandler runActionHandler, + DocumentSelector documentSelector) + : base(new CodeActionRegistrationOptions() + { + DocumentSelector = documentSelector, + CodeActionKinds = new Container( + CodeActionKind.SourceOrganizeImports, + CodeActionKind.Refactor, + CodeActionKind.RefactorExtract), + }) + { + _getActionsHandler = getActionsHandler; + _runActionHandler = runActionHandler; + } + + public async override Task Handle(CodeActionParams request, CancellationToken cancellationToken) + { + var omnisharpRequest = new GetCodeActionsRequest { + FileName = Helpers.FromUri(request.TextDocument.Uri), + Column = (int)request.Range.Start.Character, + Line = (int)request.Range.Start.Line, + Selection = Helpers.FromRange(request.Range), + }; + + var omnisharpResponse = await _getActionsHandler.Handle(omnisharpRequest); + + var codeActions = new List(); + + foreach (var ca in omnisharpResponse.CodeActions) + { + var omnisharpCaRequest = new RunCodeActionRequest { + Identifier = ca.Identifier, + FileName = Helpers.FromUri(request.TextDocument.Uri), + Column = Convert.ToInt32(request.Range.Start.Character), + Line = Convert.ToInt32(request.Range.Start.Line), + Selection = Helpers.FromRange(request.Range), + ApplyTextChanges = false, + WantsTextChanges = true, + }; + + var omnisharpCaResponse = await _runActionHandler.Handle(omnisharpCaRequest); + + var changes = omnisharpCaResponse.Changes.ToDictionary( + x => Helpers.ToUri(x.FileName), + x => ((ModifiedFileResponse)x).Changes.Select(edit => new TextEdit + { + NewText = edit.NewText, + Range = Helpers.ToRange((edit.StartColumn, edit.StartLine), (edit.EndColumn, edit.EndLine)) + })); + + CodeActionKind kind; + if (ca.Identifier.StartsWith("using ")) { kind = CodeActionKind.SourceOrganizeImports; } + else if (ca.Identifier.StartsWith("Inline ")) { kind = CodeActionKind.RefactorInline; } + else if (ca.Identifier.StartsWith("Extract ")) { kind = CodeActionKind.RefactorExtract; } + else if (ca.Identifier.StartsWith("Change ")) { kind = CodeActionKind.QuickFix; } + else { kind = CodeActionKind.Refactor; } + + codeActions.Add( + new CodeAction { + Title = ca.Name, + Kind = kind, + Diagnostics = new Container(), + Edit = new WorkspaceEdit { Changes = changes, } + }); + } + + return new CommandOrCodeActionContainer( + codeActions.Select(ca => new CommandOrCodeAction(ca))); + } + } +} diff --git a/src/OmniSharp.LanguageServerProtocol/Helpers.cs b/src/OmniSharp.LanguageServerProtocol/Helpers.cs index 1180e12683..67b5118300 100644 --- a/src/OmniSharp.LanguageServerProtocol/Helpers.cs +++ b/src/OmniSharp.LanguageServerProtocol/Helpers.cs @@ -40,6 +40,23 @@ public static Range ToRange(this QuickFix location) }; } + public static OmniSharp.Models.V2.Range FromRange(Range range) + { + return new OmniSharp.Models.V2.Range + { + Start = new OmniSharp.Models.V2.Point + { + Column = Convert.ToInt32(range.Start.Character), + Line = Convert.ToInt32(range.Start.Line), + }, + End = new OmniSharp.Models.V2.Point + { + Column = Convert.ToInt32(range.End.Character), + Line = Convert.ToInt32(range.End.Line), + }, + }; + } + public static DiagnosticSeverity ToDiagnosticSeverity(string logLevel) { // We stringify this value and pass to clients diff --git a/src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs b/src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs index 58d8b3e70c..536b83758d 100644 --- a/src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs +++ b/src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs @@ -166,6 +166,7 @@ private Task Initialize(Extensions.LanguageServer.Server.ILanguageServer server, .Concat(OmniSharpDocumentSymbolHandler.Enumerate(_handlers)) .Concat(OmniSharpReferencesHandler.Enumerate(_handlers)) .Concat(OmniSharpCodeLensHandler.Enumerate(_handlers)) + .Concat(OmniSharpCodeActionHandler.Enumerate(_handlers)) .Concat(OmniSharpDocumentFormattingHandler.Enumerate(_handlers)) .Concat(OmniSharpDocumentFormatRangeHandler.Enumerate(_handlers)) .Concat(OmniSharpDocumentOnTypeFormatHandler.Enumerate(_handlers)))