|
1 | 1 | struct ServerAction |
2 | | - command::Command |
| 2 | + id::String |
| 3 | + desc::String |
| 4 | + kind::Union{CodeActionKind,Missing} |
| 5 | + preferred::Union{Bool,Missing} |
3 | 6 | when::Function |
4 | 7 | handler::Function |
5 | 8 | end |
6 | 9 |
|
| 10 | +function client_support_action_kind(s::LanguageServerInstance, _::CodeActionKind=CodeActionKinds.Empty) |
| 11 | + if s.clientCapabilities !== missing && |
| 12 | + s.clientCapabilities.textDocument !== missing && |
| 13 | + s.clientCapabilities.textDocument.codeAction !== missing && |
| 14 | + s.clientCapabilities.textDocument.codeAction.codeActionLiteralSupport !== missing |
| 15 | + s.clientCapabilities.textDocument.codeAction.codeActionLiteralSupport.codeActionKind !== missing |
| 16 | + # From the spec of CodeActionKind (https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionClientCapabilities): |
| 17 | + # |
| 18 | + # The code action kind values the client supports. When this |
| 19 | + # property exists the client also guarantees that it will |
| 20 | + # handle values outside its set gracefully and falls back |
| 21 | + # to a default value when unknown. |
| 22 | + # |
| 23 | + # so we can always return true here(?). |
| 24 | + return true |
| 25 | + else |
| 26 | + return false |
| 27 | + end |
| 28 | +end |
| 29 | + |
| 30 | +function client_preferred_support(s::LanguageServerInstance)::Bool |
| 31 | + if s.clientCapabilities !== missing && |
| 32 | + s.clientCapabilities.textDocument !== missing && |
| 33 | + s.clientCapabilities.textDocument.codeAction !== missing && |
| 34 | + s.clientCapabilities.textDocument.codeAction.isPreferredSupport !== missing |
| 35 | + return s.clientCapabilities.textDocument.codeAction.isPreferredSupport |
| 36 | + else |
| 37 | + return false |
| 38 | + end |
| 39 | +end |
| 40 | + |
| 41 | +# TODO: All currently supported CodeActions in LS.jl can be converted "losslessly" to |
| 42 | +# Commands but this might not be true in the future so unless the client support |
| 43 | +# literal code actions those need to be filtered out. |
| 44 | +function convert_to_command(ca::CodeAction) |
| 45 | + return ca.command |
| 46 | +end |
| 47 | + |
7 | 48 | function textDocument_codeAction_request(params::CodeActionParams, server::LanguageServerInstance, conn) |
8 | | - commands = Command[] |
| 49 | + actions = CodeAction[] |
9 | 50 | doc = getdocument(server, params.textDocument.uri) |
10 | | - offset = get_offset(doc, params.range.start) # Should usef get_offset2? |
| 51 | + offset = get_offset2(doc, params.range.start) |
11 | 52 | x = get_expr(getcst(doc), offset) |
12 | 53 | arguments = Any[params.textDocument.uri, offset] # use the same arguments for all commands |
13 | 54 | if x isa EXPR |
14 | 55 | for (_, sa) in LSActions |
15 | 56 | if sa.when(x, params) |
16 | | - push!(commands, Command(sa.command.title, sa.command.command, arguments)) |
| 57 | + action = CodeAction( |
| 58 | + sa.desc, # title |
| 59 | + sa.kind, # kind |
| 60 | + missing, # diagnostics |
| 61 | + client_preferred_support(server) ? sa.preferred : missing, # isPreferred |
| 62 | + missing, # edit |
| 63 | + Command(sa.desc, sa.id, arguments), # command |
| 64 | + ) |
| 65 | + push!(actions, action) |
17 | 66 | end |
18 | 67 | end |
19 | 68 | end |
20 | | - return commands |
| 69 | + if client_support_action_kind(server) |
| 70 | + return actions |
| 71 | + else |
| 72 | + # TODO: Future CodeActions might have to be filtered here. |
| 73 | + return convert_to_command.(actions) |
| 74 | + end |
21 | 75 | end |
22 | 76 |
|
23 | 77 | function workspace_executeCommand_request(params::ExecuteCommandParams, server::LanguageServerInstance, conn) |
@@ -261,28 +315,97 @@ function remove_farg_name(x, server, conn) |
261 | 315 | ]) |
262 | 316 | else |
263 | 317 | tde = TextDocumentEdit(VersionedTextDocumentIdentifier(file._uri, file._version), TextEdit[ |
264 | | - TextEdit(Range(file, offset .+ (0:x1.fullspan)), "::Any") |
| 318 | + TextEdit(Range(file, offset .+ (0:x1.fullspan)), "_") |
265 | 319 | ]) |
266 | 320 | end |
267 | 321 | JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, TextDocumentEdit[tde]))) |
268 | 322 | end |
269 | 323 |
|
| 324 | +function remove_unused_assignment_name(x, _, conn) |
| 325 | + x1 = StaticLint.get_parent_fexpr(x, x -> StaticLint.haserror(x) && StaticLint.errorof(x) == StaticLint.UnusedBinding && x isa EXPR && x.head === :IDENTIFIER) |
| 326 | + file, offset = get_file_loc(x1) |
| 327 | + tde = TextDocumentEdit(VersionedTextDocumentIdentifier(file._uri, file._version), TextEdit[ |
| 328 | + TextEdit(Range(file, offset .+ (0:x1.span)), "_") |
| 329 | + ]) |
| 330 | + JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, TextDocumentEdit[tde]))) |
| 331 | +end |
| 332 | + |
| 333 | +function double_to_triple_equal(x, _, conn) |
| 334 | + x1 = StaticLint.get_parent_fexpr(x, y -> StaticLint.haserror(y) && StaticLint.errorof(y) in (StaticLint.NothingEquality, StaticLint.NothingNotEq)) |
| 335 | + file, offset = get_file_loc(x1) |
| 336 | + tde = TextDocumentEdit(VersionedTextDocumentIdentifier(file._uri, file._version), TextEdit[ |
| 337 | + TextEdit(Range(file, offset .+ (0:x1.span)), StaticLint.errorof(x1) == StaticLint.NothingEquality ? "===" : "!==") |
| 338 | + ]) |
| 339 | + JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, TextDocumentEdit[tde]))) |
| 340 | +end |
| 341 | + |
270 | 342 | # Adding a CodeAction requires defining: |
271 | | -# * a Command (title and description); |
| 343 | +# * a unique id |
| 344 | +# * a description |
| 345 | +# * an action kind (optionally) |
272 | 346 | # * a function (.when) called on the currently selected expression and parameters of the CodeAction call; |
273 | 347 | # * a function (.handler) called on three arguments (current expression, server and the jr connection) to implement the command. |
274 | | -const LSActions = Dict( |
275 | | - "ExplicitPackageVarImport" => ServerAction(Command("Explicitly import used package variables.", "ExplicitPackageVarImport", missing), |
276 | | - (x, params) -> refof(x) isa StaticLint.Binding && refof(x).val isa SymbolServer.ModuleStore, |
277 | | - explicitly_import_used_variables), |
278 | | - "ExpandFunction" => ServerAction(Command("Expand function definition.", "ExpandFunction", missing), |
279 | | - (x, params) -> is_in_fexpr(x, is_single_line_func), |
280 | | - expand_inline_func), |
281 | | - "FixMissingRef" => ServerAction(Command("Fix missing reference", "FixMissingRef", missing), |
282 | | - (x, params) -> is_fixable_missing_ref(x, params.context), |
283 | | - applymissingreffix), |
284 | | - "ReexportModule" => ServerAction(Command("Re-export package variables.", "ReexportModule", missing), |
285 | | - (x, params) -> StaticLint.is_in_fexpr(x, x -> headof(x) === :using || headof(x) === :import) && (refof(x) isa StaticLint.Binding && (refof(x).type === StaticLint.CoreTypes.Module || (refof(x).val isa StaticLint.Binding && refof(x).val.type === StaticLint.CoreTypes.Module) || refof(x).val isa SymbolServer.ModuleStore) || refof(x) isa SymbolServer.ModuleStore), |
286 | | - reexport_package), |
287 | | - "DeleteUnusedFunctionArgumentName" => ServerAction(Command("Delete name of unused function argument.", "DeleteUnusedFunctionArgumentName", missing), (x, params) -> StaticLint.is_in_fexpr(x, x -> StaticLint.haserror(x) && StaticLint.errorof(x) == StaticLint.UnusedFunctionArgument), remove_farg_name) |
| 348 | +const LSActions = Dict{String,ServerAction}() |
| 349 | + |
| 350 | +LSActions["ExplicitPackageVarImport"] = ServerAction( |
| 351 | + "ExplicitPackageVarImport", |
| 352 | + "Explicitly import used package variables.", |
| 353 | + missing, |
| 354 | + missing, |
| 355 | + (x, params) -> refof(x) isa StaticLint.Binding && refof(x).val isa SymbolServer.ModuleStore, |
| 356 | + explicitly_import_used_variables |
| 357 | +) |
| 358 | + |
| 359 | +LSActions["ExpandFunction"] = ServerAction( |
| 360 | + "ExpandFunction", |
| 361 | + "Expand function definition.", |
| 362 | + CodeActionKinds.Refactor, |
| 363 | + missing, |
| 364 | + (x, params) -> is_in_fexpr(x, is_single_line_func), |
| 365 | + expand_inline_func, |
| 366 | +) |
| 367 | + |
| 368 | +LSActions["FixMissingRef"] = ServerAction( |
| 369 | + "FixMissingRef", |
| 370 | + "Fix missing reference", |
| 371 | + missing, |
| 372 | + missing, |
| 373 | + (x, params) -> is_fixable_missing_ref(x, params.context), |
| 374 | + applymissingreffix, |
| 375 | +) |
| 376 | + |
| 377 | +LSActions["ReexportModule"] = ServerAction( |
| 378 | + "ReexportModule", |
| 379 | + "Re-export package variables.", |
| 380 | + missing, |
| 381 | + missing, |
| 382 | + (x, params) -> StaticLint.is_in_fexpr(x, x -> headof(x) === :using || headof(x) === :import) && (refof(x) isa StaticLint.Binding && (refof(x).type === StaticLint.CoreTypes.Module || (refof(x).val isa StaticLint.Binding && refof(x).val.type === StaticLint.CoreTypes.Module) || refof(x).val isa SymbolServer.ModuleStore) || refof(x) isa SymbolServer.ModuleStore), |
| 383 | + reexport_package, |
| 384 | +) |
| 385 | + |
| 386 | +LSActions["DeleteUnusedFunctionArgumentName"] = ServerAction( |
| 387 | + "DeleteUnusedFunctionArgumentName", |
| 388 | + "Delete name of unused function argument.", |
| 389 | + CodeActionKinds.QuickFix, |
| 390 | + missing, |
| 391 | + (x, params) -> StaticLint.is_in_fexpr(x, x -> StaticLint.haserror(x) && StaticLint.errorof(x) == StaticLint.UnusedFunctionArgument), |
| 392 | + remove_farg_name, |
| 393 | +) |
| 394 | + |
| 395 | +LSActions["ReplaceUnusedAssignmentName"] = ServerAction( |
| 396 | + "ReplaceUnusedAssignmentName", |
| 397 | + "Replace unused assignment name with _.", |
| 398 | + CodeActionKinds.QuickFix, |
| 399 | + missing, |
| 400 | + (x, params) -> StaticLint.is_in_fexpr(x, x -> StaticLint.haserror(x) && StaticLint.errorof(x) == StaticLint.UnusedBinding && x isa EXPR && x.head === :IDENTIFIER), |
| 401 | + remove_unused_assignment_name, |
| 402 | +) |
| 403 | + |
| 404 | +LSActions["CompareNothingWithTripleEqual"] = ServerAction( |
| 405 | + "CompareNothingWithTripleEqual", |
| 406 | + "Change ==/!= to ===/!==.", |
| 407 | + CodeActionKinds.QuickFix, |
| 408 | + true, |
| 409 | + (x, _) -> StaticLint.is_in_fexpr(x, y -> StaticLint.haserror(y) && (StaticLint.errorof(y) in (StaticLint.NothingEquality, StaticLint.NothingNotEq))), |
| 410 | + double_to_triple_equal, |
288 | 411 | ) |
0 commit comments