Skip to content

Commit 3d49b27

Browse files
authored
Merge branch 'master' into fix-io-error
2 parents 5b8f246 + e2823f7 commit 3d49b27

File tree

10 files changed

+411
-66
lines changed

10 files changed

+411
-66
lines changed

src/LanguageServer.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ include("languageserverinstance.jl")
1919
include("runserver.jl")
2020
include("staticlint.jl")
2121

22-
include("requests/init.jl")
2322
include("requests/misc.jl")
2423
include("requests/textdocument.jl")
2524
include("requests/features.jl")
2625
include("requests/hover.jl")
2726
include("requests/completions.jl")
2827
include("requests/workspace.jl")
2928
include("requests/actions.jl")
29+
include("requests/init.jl")
3030
include("utilities.jl")
3131

3232
end

src/requests/actions.jl

Lines changed: 32 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
struct ServerAction
2+
command::Command
3+
when::Function
4+
handler::Function
5+
end
6+
17
function textDocument_codeAction_request(params::CodeActionParams, server::LanguageServerInstance, conn)
28
commands = Command[]
39
doc = getdocument(server, URI2(params.textDocument.uri))
@@ -6,28 +12,12 @@ function textDocument_codeAction_request(params::CodeActionParams, server::Langu
612
x = get_expr(getcst(doc), offset)
713
arguments = Any[params.textDocument.uri, offset, offset1] # use the same arguments for all commands
814
if x isa EXPR
9-
if refof(x) isa StaticLint.Binding && refof(x).val isa SymbolServer.ModuleStore
10-
push!(commands, Command("Explicitly import used package variables.", "ExplicitPackageVarImport", arguments))
11-
end
12-
if parentof(x) isa EXPR && typof(parentof(x)) === CSTParser.Using && refof(x) isa StaticLint.Binding
13-
if 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
14-
push!(commands, Command("Re-export package variables.", "ReexportModule", arguments))
15+
for (_,sa) in LSActions
16+
if sa.when(x, params)
17+
push!(commands, Command(sa.command.title, sa.command.command, arguments))
1518
end
1619
end
17-
if is_in_fexpr(x, is_single_line_func)
18-
push!(commands, Command("Expand function definition.", "ExpandFunction", arguments))
19-
end
20-
if is_in_fexpr(x, CSTParser.defines_struct)
21-
push!(commands, Command("Add default constructor", "AddDefaultConstructor", arguments))
22-
end
23-
if is_fixable_missing_ref(x, params.context)
24-
push!(commands, Command("Fix missing reference", "FixMissingRef", arguments))
25-
end
26-
# if params.range.start.line != params.range.stop.line # selection across _line_offsets
27-
# push!(commands, Command("Wrap in `if` block.", "WrapIfBlock", arguments))
28-
# end
2920
end
30-
3121
return commands
3222
end
3323

@@ -36,22 +26,8 @@ function workspace_executeCommand_request(params::ExecuteCommandParams, server::
3626
offset = params.arguments[2]
3727
doc = getdocument(server, URI2(uri))
3828
x = get_expr(getcst(doc), offset)
39-
if params.command == "ExplicitPackageVarImport"
40-
explicitly_import_used_variables(x, server, conn)
41-
elseif params.command == "ExpandFunction"
42-
expand_inline_func(x, server, conn)
43-
elseif params.command == "AddDefaultConstructor"
44-
add_default_constructor(x, server, conn)
45-
elseif params.command == "ReexportModule"
46-
if refof(x).type === StaticLint.CoreTypes.Module || (refof(x).val isa StaticLint.Binding && refof(x).val.type === StaticLint.CoreTypes.Module)
47-
reexport_module(x, server, conn)
48-
elseif refof(x).val isa SymbolServer.ModuleStore
49-
reexport_package(x, server, conn)
50-
end
51-
elseif params.command == "WrapIfBlock"
52-
wrap_block(get_expr(getcst(doc), params.arguments[2]:params.arguments[3]), server, :if, conn)
53-
elseif params.command == "FixMissingRef"
54-
applymissingreffix(x, server, conn)
29+
if haskey(LSActions, params.command)
30+
LSActions[params.command].handler(x, server, conn)
5531
end
5632
end
5733

@@ -72,14 +48,13 @@ function explicitly_import_used_variables(x::EXPR, server, conn)
7248

7349
tdes = Dict{String,TextDocumentEdit}()
7450
vars = Set{String}() # names that need to be imported
75-
7651
# Find uses of `x` and mark edits
7752
for ref in refof(x).refs
7853
if parentof(ref) isa EXPR && typof(parentof(ref)) == CSTParser.BinaryOpCall && length(parentof(ref).args) == 3 && kindof(parentof(ref).args[2]) === CSTParser.Tokens.DOT && parentof(ref).args[1] == ref
7954
typof(parentof(ref).args[3]) !== CSTParser.Quotenode && continue # some malformed EXPR, skip
8055
childname = parentof(ref).args[3].args[1]
8156
StaticLint.hasref(childname) && refof(childname) isa StaticLint.Binding && continue # check this isn't the name of something being explictly overwritten
82-
!haskey(refof(x).val.vals, valof(childname)) && continue # skip, perhaps mark as missing ref ?
57+
!haskey(refof(x).val.vals, Symbol(valof(childname))) && continue # skip, perhaps mark as missing ref ?
8358

8459
file, offset = get_file_loc(ref)
8560
if !haskey(tdes, file._uri)
@@ -147,33 +122,6 @@ function expand_inline_func(x, server, conn)
147122
end
148123
end
149124

150-
151-
function add_default_constructor(x::EXPR, server, conn)
152-
sexpr = _get_parent_fexpr(x, CSTParser.defines_struct)
153-
!(sexpr.args isa Vector{EXPR}) && return
154-
ismutable = length(sexpr.args) == 5
155-
name = CSTParser.get_name(sexpr)
156-
sig = sexpr.args[2 + ismutable]
157-
block = sexpr.args[3 + ismutable]
158-
159-
isempty(block.args) && return
160-
any(CSTParser.defines_function(a) for a in block.args) && return # constructor already exists
161-
162-
newtext = string("\n function $(valof(name))(args...)\n\n new")
163-
# if DataType is parameterised do something here
164-
165-
newtext = string(newtext, "(")
166-
for i in 1:length(block.args)
167-
newtext = string(newtext, "", valof(CSTParser.get_arg_name(block.args[i])))
168-
newtext = string(newtext, i < length(block.args) ? ", " : ")\n end")
169-
end
170-
file, offset = get_file_loc(last(block.args))
171-
offset += last(block.args).span
172-
tde = TextDocumentEdit(VersionedTextDocumentIdentifier(file._uri, file._version), TextEdit[TextEdit(Range(file, offset:offset), newtext)])
173-
174-
JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, TextDocumentEdit[tde])))
175-
end
176-
177125
function is_in_fexpr(x::EXPR, f)
178126
if f(x)
179127
return true
@@ -205,6 +153,7 @@ function get_next_line_offset(x)
205153
end
206154

207155
function reexport_package(x::EXPR, server, conn)
156+
(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) || return
208157
mod::SymbolServer.ModuleStore = refof(x).val
209158
using_stmt = parentof(x)
210159
file, offset = get_file_loc(x)
@@ -301,3 +250,22 @@ function applymissingreffix(x, server, conn)
301250
end
302251
end
303252
end
253+
254+
# Adding a CodeAction requires defining:
255+
# * a Command (title and description);
256+
# * a function (.when) called on the currently selected expression and parameters of the CodeAction call;
257+
# * a function (.handler) called on three arguments (current expression, server and the jr connection) to implement the command.
258+
const LSActions = Dict(
259+
"ExplicitPackageVarImport" => ServerAction(Command("Explicitly import used package variables.", "ExplicitPackageVarImport", missing),
260+
(x, params) -> refof(x) isa StaticLint.Binding && refof(x).val isa SymbolServer.ModuleStore,
261+
explicitly_import_used_variables),
262+
"ExpandFunction" => ServerAction(Command("Expand function definition.", "ExpandFunction", missing),
263+
(x, params) -> is_in_fexpr(x, is_single_line_func),
264+
expand_inline_func),
265+
"FixMissingRef" => ServerAction(Command("Fix missing reference", "FixMissingRef", missing),
266+
(x, params) -> is_fixable_missing_ref(x, params.context),
267+
applymissingreffix),
268+
"ReexportModule" => ServerAction(Command("Re-export package variables.", "ReexportModule", missing),
269+
(x, params) -> parentof(x) isa EXPR && typof(parentof(x)) === CSTParser.Using && 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),
270+
reexport_package)
271+
)

src/requests/init.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const serverCapabilities = ServerCapabilities(
2323
missing,
2424
true,
2525
false,
26-
ExecuteCommandOptions(missing, String["ExplicitPackageVarImport", "ExpandFunction", "AddDefaultConstructor", "ReexportModule", "FixMissingRef"]),
26+
ExecuteCommandOptions(missing, collect(keys(LSActions))),
2727
false,
2828
true,
2929
WorkspaceOptions(WorkspaceFoldersOptions(true, true)),

test/requests/actions.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
action_request_test(line0, char0, line1 = line0, char1 = char0; diags = []) = LanguageServer.textDocument_codeAction_request(LanguageServer.CodeActionParams(LanguageServer.TextDocumentIdentifier("testdoc"), LanguageServer.Range(LanguageServer.Position(line0, char0), LanguageServer.Position(line1, char1)), LanguageServer.CodeActionContext(diags, missing)), server, server.jr_endpoint)
2+
3+
@testset "reexport" begin
4+
settestdoc("using Base.Meta\n")
5+
@test any(c.command == "ReexportModule" for c in action_request_test(0, 15))
6+
c = filter(c->c.command == "ReexportModule", action_request_test(0, 15))[1]
7+
8+
LanguageServer.workspace_executeCommand_request(LanguageServer.ExecuteCommandParams(missing, c.command, c.arguments), server, server.jr_endpoint)
9+
end
10+
11+
@testset "inline expand" begin
12+
settestdoc("f(x) = x")
13+
@test any(c.command == "ExpandFunction" for c in action_request_test(0, 5))
14+
c = filter(c->c.command == "ExpandFunction", action_request_test(0, 5))[1]
15+
16+
LanguageServer.workspace_executeCommand_request(LanguageServer.ExecuteCommandParams(missing, c.command, c.arguments), server, server.jr_endpoint)
17+
18+
settestdoc("f(x) = begin x end")
19+
@test any(c.command == "ExpandFunction" for c in action_request_test(0, 5))
20+
c = filter(c->c.command == "ExpandFunction", action_request_test(0, 5))[1]
21+
22+
LanguageServer.workspace_executeCommand_request(LanguageServer.ExecuteCommandParams(missing, c.command, c.arguments), server, server.jr_endpoint)
23+
end
24+
25+
@testset "fixmissingref" begin
26+
doc = settestdoc("argtail\n")
27+
e = LanguageServer.mark_errors(doc)[1]
28+
@test any(c.command == "FixMissingRef" for c in action_request_test(0, 5, diags = [e]))
29+
c = filter(c->c.command == "FixMissingRef", action_request_test(0, 5, diags = [e]))[1]
30+
31+
LanguageServer.workspace_executeCommand_request(LanguageServer.ExecuteCommandParams(missing, c.command, c.arguments), server, server.jr_endpoint)
32+
end
33+
34+
@testset "explicit import" begin
35+
doc = settestdoc("using Base.Meta\nMeta.quot")
36+
@test LanguageServer.find_using_statement(doc.cst[2][1]) !== nothing
37+
38+
@test any(c.command == "ExplicitPackageVarImport" for c in action_request_test(1, 1))
39+
c = filter(c->c.command == "ExplicitPackageVarImport", action_request_test(1, 1))[1]
40+
41+
LanguageServer.workspace_executeCommand_request(LanguageServer.ExecuteCommandParams(missing, c.command, c.arguments), server, server.jr_endpoint)
42+
end
43+
44+
45+

test/requests/completions.jl

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
completion_test(line, char) = LanguageServer.textDocument_completion_request(LanguageServer.CompletionParams(LanguageServer.TextDocumentIdentifier("testdoc"), LanguageServer.Position(line, char), missing), server, server.jr_endpoint)
2+
3+
@testset "latex completions" begin
4+
settestdoc("""
5+
\\therefor
6+
.\\therefor
7+
#\\therefor
8+
"\\therefor"
9+
\"\"\"\\therefor\"\"\"
10+
^\\therefor
11+
""")
12+
@test completion_test(0, 9).items[1].textEdit.newText == ""
13+
@test completion_test(0, 9).items[1].textEdit.range == LanguageServer.Range(0, 0, 0, 9)
14+
15+
@test completion_test(1, 10).items[1].textEdit.newText == ""
16+
@test completion_test(1, 10).items[1].textEdit.range == LanguageServer.Range(1, 1, 1, 10)
17+
18+
@test completion_test(2, 10).items[1].textEdit.newText == ""
19+
@test completion_test(2, 10).items[1].textEdit.range == LanguageServer.Range(2, 1, 2, 10)
20+
21+
@test completion_test(3, 10).items[1].textEdit.newText == ""
22+
@test completion_test(3, 10).items[1].textEdit.range == LanguageServer.Range(3, 1, 3, 10)
23+
24+
@test completion_test(4, 12).items[1].textEdit.newText == ""
25+
@test completion_test(4, 12).items[1].textEdit.range == LanguageServer.Range(4, 3, 4, 12)
26+
27+
@test completion_test(5, 10).items[1].textEdit.newText == ""
28+
@test completion_test(5, 10).items[1].textEdit.range == LanguageServer.Range(5, 1, 5, 10)
29+
end
30+
31+
@testset "path completions" begin
32+
end
33+
34+
@testset "import completions" begin
35+
settestdoc("import Base: r")
36+
@test any(item.label == "rand" for item in completion_test(0, 14).items)
37+
38+
settestdoc("import ")
39+
@test all(item.label in ("Main", "Base", "Core") for item in completion_test(0, 7).items)
40+
41+
settestdoc("""module M end
42+
import .""")
43+
@test_broken completion_test(1, 8).items[1].label == "M"
44+
45+
settestdoc("import Base.")
46+
@test any(item.label == "Meta" for item in completion_test(0, 12).items)
47+
48+
settestdoc("import Base.M")
49+
@test any(item.label == "Meta" for item in completion_test(0, 13).items)
50+
51+
settestdoc("import Bas")
52+
@test any(item.label == "Base" for item in completion_test(0, 10).items)
53+
end
54+
55+
@testset "getfield completions" begin
56+
settestdoc("Base.")
57+
@test any(item.label == "Base" for item in completion_test(0, 5).items)
58+
59+
settestdoc("Base.r")
60+
@test any(item.label == "rand" for item in completion_test(0, 6).items)
61+
62+
settestdoc("""
63+
using Base.Meta
64+
Base.Meta.
65+
""")
66+
@test any(item.label == "quot" for item in completion_test(1, 10).items)
67+
68+
settestdoc("""
69+
module M
70+
inner = 1
71+
end
72+
M.
73+
""")
74+
@test any(item.label == "inner" for item in completion_test(3, 2).items)
75+
76+
settestdoc("""
77+
x = Expr()
78+
x.
79+
""")
80+
@test all(item.label in ("head", "args") for item in completion_test(1, 2).items)
81+
82+
settestdoc("""
83+
struct T
84+
f1
85+
f2
86+
end
87+
x = T()
88+
x.
89+
""")
90+
@test all(item.label in ("f1", "f2") for item in completion_test(1, 2).items)
91+
end
92+
93+
94+
95+
@testset "token completions" begin
96+
settestdoc("B")
97+
@test any(item.label == "Base" for item in completion_test(0, 1).items)
98+
99+
settestdoc("r")
100+
@test any(item.label == "rand" for item in completion_test(0, 1).items)
101+
102+
settestdoc("@t")
103+
@test any(item.label == "@time" for item in completion_test(0, 2).items)
104+
105+
settestdoc("i")
106+
@test any(item.label == "if" for item in completion_test(0, 1).items)
107+
108+
settestdoc("i")
109+
@test any(item.label == "in" for item in completion_test(0, 1).items)
110+
111+
settestdoc("for")
112+
@test any(item.label == "for" for item in completion_test(0, 3).items)
113+
114+
settestdoc("in")
115+
@test any(item.label == "in" for item in completion_test(0, 2).items)
116+
117+
settestdoc("isa")
118+
@test any(item.label == "isa" for item in completion_test(0, 3).items)
119+
end
120+
121+
@testset "scope var completions" begin
122+
settestdoc("""myvar = 1
123+
myv""")
124+
@test any(item.label == "myvar" for item in completion_test(1, 3).items)
125+
end

0 commit comments

Comments
 (0)