Skip to content

Commit 404d8fd

Browse files
committed
WIP
1 parent d5c9c0a commit 404d8fd

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed

lib/next_ls.ex

+21
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ defmodule NextLS do
2424
alias GenLSP.Requests.TextDocumentFormatting
2525
alias GenLSP.Requests.TextDocumentHover
2626
alias GenLSP.Requests.TextDocumentReferences
27+
alias GenLSP.Requests.TextDocumentSignatureHelp
2728
alias GenLSP.Requests.WorkspaceApplyEdit
2829
alias GenLSP.Requests.WorkspaceSymbol
2930
alias GenLSP.Structures.ApplyWorkspaceEditParams
@@ -41,6 +42,8 @@ defmodule NextLS do
4142
alias GenLSP.Structures.Range
4243
alias GenLSP.Structures.SaveOptions
4344
alias GenLSP.Structures.ServerCapabilities
45+
alias GenLSP.Structures.SignatureHelp
46+
alias GenLSP.Structures.SignatureHelpParams
4447
alias GenLSP.Structures.SymbolInformation
4548
alias GenLSP.Structures.TextDocumentIdentifier
4649
alias GenLSP.Structures.TextDocumentItem
@@ -53,6 +56,7 @@ defmodule NextLS do
5356
alias NextLS.DiagnosticCache
5457
alias NextLS.Progress
5558
alias NextLS.Runtime
59+
alias NextLS.SignatureHelp
5660

5761
def start_link(args) do
5862
{args, opts} =
@@ -146,6 +150,9 @@ defmodule NextLS do
146150
"from-pipe"
147151
]
148152
},
153+
signature_help_provider: %GenLSP.Structures.SignatureHelpOptions{
154+
trigger_characters: ["(", ","]
155+
},
149156
hover_provider: true,
150157
workspace_symbol_provider: true,
151158
document_symbol_provider: true,
@@ -699,6 +706,20 @@ defmodule NextLS do
699706
{:reply, nil, lsp}
700707
end
701708

709+
def handle_request(
710+
%TextDocumentSignatureHelp{params: %SignatureHelpParams{text_document: %{uri: uri}, position: position}},
711+
lsp
712+
) do
713+
result =
714+
dispatch(lsp.assigns.registry, :databases, fn entries ->
715+
for {pid, _} <- entries do
716+
SignatureHelp.fetch(URI.parse(uri).path, {position.line + 1, position.character + 1}, pid)
717+
end
718+
end)
719+
720+
{:reply, List.first(result), lsp}
721+
end
722+
702723
def handle_request(%Shutdown{}, lsp) do
703724
{:reply, nil, assign(lsp, exit_code: 0)}
704725
end

lib/next_ls/helpers/ast_helpers.ex

+22
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,26 @@ defmodule NextLS.ASTHelpers do
152152
end
153153
end)
154154
end
155+
156+
defmodule Functions do
157+
@moduledoc false
158+
def get_function_params(code, identifier, line, _col) do
159+
ast =
160+
NextLS.Parser.parse!(code, columns: true)
161+
162+
identifier = String.to_atom(identifier)
163+
164+
{_ast, args} =
165+
Macro.prewalk(ast, nil, fn
166+
{^identifier, [line: ^line, column: _], args} = ast, _acc -> {ast, args}
167+
other, acc -> {other, acc}
168+
end)
169+
170+
if args do
171+
args
172+
else
173+
[]
174+
end
175+
end
176+
end
155177
end

lib/next_ls/signature_help.ex

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
defmodule NextLS.SignatureHelp do
2+
@moduledoc false
3+
4+
import NextLS.DB.Query
5+
6+
alias GenLSP.Structures.ParameterInformation
7+
alias GenLSP.Structures.SignatureHelp
8+
alias GenLSP.Structures.SignatureInformation
9+
alias NextLS.ASTHelpers
10+
alias NextLS.DB
11+
12+
def fetch(file, {line, col}, db) do
13+
symbol = fetch_symbol(file, line, col, db)
14+
15+
case symbol do
16+
nil ->
17+
nil
18+
19+
[] ->
20+
nil
21+
22+
[[_, _mod, file, type, label, line, col | _] | _] = _definition ->
23+
if type in ["def", "defp"] do
24+
code = File.read!(file)
25+
26+
params =
27+
code
28+
|> ASTHelpers.Functions.get_function_params(label, line, col)
29+
|> Enum.map(fn {name, _, _} ->
30+
%ParameterInformation{
31+
label: Atom.to_string(name)
32+
}
33+
end)
34+
35+
%SignatureHelp{
36+
signatures: [
37+
%SignatureInformation{
38+
label: label,
39+
documentation: "need help",
40+
parameters: params,
41+
active_parameter: 0
42+
}
43+
],
44+
active_signature: 0,
45+
active_parameter: 0
46+
}
47+
else
48+
nil
49+
end
50+
end
51+
end
52+
53+
defp fetch_symbol(file, line, col, db) do
54+
rows =
55+
DB.query(
56+
db,
57+
~Q"""
58+
SELECT
59+
*
60+
FROM
61+
'references' AS refs
62+
WHERE
63+
refs.file = ?
64+
AND refs.start_line <= ?
65+
AND ? <= refs.end_line
66+
AND refs.start_column <= ?
67+
AND ? <= refs.end_column
68+
ORDER BY refs.id asc
69+
LIMIT 1;
70+
""",
71+
[file, line, line, col, col]
72+
)
73+
74+
reference =
75+
case rows do
76+
[[_pk, identifier, _arity, _file, type, module, _start_l, _start_c, _end_l, _end_c | _]] ->
77+
%{identifier: identifier, type: type, module: module}
78+
79+
[] ->
80+
nil
81+
end
82+
83+
with %{identifier: identifier, type: type, module: module} <- reference do
84+
query =
85+
~Q"""
86+
SELECT
87+
*
88+
FROM
89+
symbols
90+
WHERE
91+
symbols.module = ?
92+
AND symbols.name = ?;
93+
"""
94+
95+
args =
96+
case type do
97+
"alias" ->
98+
[module, module]
99+
100+
"function" ->
101+
[module, identifier]
102+
103+
"attribute" ->
104+
[module, identifier]
105+
106+
_ ->
107+
nil
108+
end
109+
110+
if args do
111+
DB.query(db, query, args)
112+
else
113+
nil
114+
end
115+
end
116+
end
117+
end

test/next_ls/helpers/ast_helpers_test.exs

+14
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,18 @@ defmodule NextLS.ASTHelpersTest do
7474
assert {{5, 5}, {5, 8}} == Aliases.extract_alias_range(code, {start, stop}, :Four)
7575
end
7676
end
77+
78+
describe "extract function params" do
79+
test "simple function params" do
80+
code = """
81+
@doc "foo doc"
82+
def foo(bar, baz) do
83+
:ok
84+
end
85+
"""
86+
87+
assert [{:bar, [line: 2, column: 9], nil}, {:baz, [line: 2, column: 14], nil}] ==
88+
ASTHelpers.Functions.get_function_params(code, "foo", 2, 5)
89+
end
90+
end
7791
end

test/next_ls/signature_help_test.exs

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
defmodule NextLS.SignatureHelpTest do
2+
use ExUnit.Case, async: true
3+
4+
import GenLSP.Test
5+
import NextLS.Support.Utils
6+
7+
@moduletag :tmp_dir
8+
9+
describe "function" do
10+
@describetag root_paths: ["my_proj"]
11+
setup %{tmp_dir: tmp_dir} do
12+
File.mkdir_p!(Path.join(tmp_dir, "my_proj/lib"))
13+
File.write!(Path.join(tmp_dir, "my_proj/mix.exs"), mix_exs())
14+
[cwd: tmp_dir]
15+
end
16+
17+
setup %{cwd: cwd} do
18+
remote = Path.join(cwd, "my_proj/lib/remote.ex")
19+
20+
File.write!(remote, """
21+
defmodule Remote do
22+
def bang!(bang) do
23+
bang
24+
end
25+
end
26+
""")
27+
28+
imported = Path.join(cwd, "my_proj/lib/imported.ex")
29+
30+
File.write!(imported, """
31+
defmodule Imported do
32+
def boom(boom1) do
33+
boom1
34+
end
35+
end
36+
""")
37+
38+
bar = Path.join(cwd, "my_proj/lib/bar.ex")
39+
40+
File.write!(bar, """
41+
defmodule Bar do
42+
import Imported
43+
def run() do
44+
Remote.bang!("‼️")
45+
process()
46+
end
47+
48+
defp process() do
49+
boom("💣")
50+
:ok
51+
end
52+
end
53+
""")
54+
55+
[bar: bar, imported: imported, remote: remote]
56+
end
57+
58+
setup :with_lsp
59+
60+
test "get signature help", %{client: client, bar: bar} = context do
61+
assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})
62+
63+
assert_is_ready(context, "my_proj")
64+
assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}}
65+
66+
uri = uri(bar)
67+
68+
request(client, %{
69+
method: "textDocument/signatureHelp",
70+
id: 4,
71+
jsonrpc: "2.0",
72+
params: %{
73+
position: %{line: 3, character: 15},
74+
textDocument: %{uri: uri}
75+
}
76+
})
77+
78+
assert_result 4, %{
79+
"activeParameter" => 0,
80+
"activeSignature" => 0,
81+
"signatures" => [
82+
%{
83+
"activeParameter" => 0,
84+
"parameters" => [
85+
%{"label" => "bang"}
86+
],
87+
"documentation" => "need help",
88+
"label" => "bang!"
89+
}
90+
]
91+
}
92+
end
93+
end
94+
end

0 commit comments

Comments
 (0)