Skip to content

Commit 9ebffe7

Browse files
committed
feat: add signature help
1 parent 9c7ff4d commit 9ebffe7

File tree

4 files changed

+386
-3
lines changed

4 files changed

+386
-3
lines changed

lib/next_ls.ex

Lines changed: 35 additions & 0 deletions
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} =
@@ -148,6 +152,9 @@ defmodule NextLS do
148152
"from-pipe"
149153
]
150154
},
155+
signature_help_provider: %GenLSP.Structures.SignatureHelpOptions{
156+
trigger_characters: ["(", ","]
157+
},
151158
hover_provider: true,
152159
workspace_symbol_provider: true,
153160
document_symbol_provider: true,
@@ -764,6 +771,34 @@ defmodule NextLS do
764771
{:reply, nil, lsp}
765772
end
766773

774+
def handle_request(
775+
%TextDocumentSignatureHelp{params: %SignatureHelpParams{text_document: %{uri: uri}, position: position}},
776+
lsp
777+
) do
778+
signature_help =
779+
case SignatureHelp.fetch_mod_and_name(uri, {position.line + 1, position.character + 1}) do
780+
{:ok, {mod, name}} ->
781+
docs =
782+
dispatch(lsp.assigns.registry, :runtimes, fn entries ->
783+
[result] =
784+
for {runtime, %{uri: wuri}} <- entries, String.starts_with?(uri, wuri) do
785+
Runtime.call(runtime, {Code, :fetch_docs, [mod]})
786+
end
787+
788+
result
789+
end)
790+
791+
docs
792+
|> SignatureHelp.format(name)
793+
|> List.first()
794+
795+
{:error, :not_found} ->
796+
nil
797+
end
798+
799+
{:reply, signature_help, lsp}
800+
end
801+
767802
def handle_request(%Shutdown{}, lsp) do
768803
{:reply, nil, assign(lsp, exit_code: 0)}
769804
end

lib/next_ls/signature_help.ex

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
defmodule NextLS.SignatureHelp do
2+
@moduledoc false
3+
4+
alias GenLSP.Enumerations.MarkupKind
5+
alias GenLSP.Structures.MarkupContent
6+
alias GenLSP.Structures.ParameterInformation
7+
alias GenLSP.Structures.SignatureHelp
8+
alias GenLSP.Structures.SignatureInformation
9+
alias Sourceror.Zipper
10+
11+
def fetch_mod_and_name(uri, position) do
12+
with {:ok, text} <- File.read(URI.parse(uri).path),
13+
ast =
14+
text
15+
|> Spitfire.parse()
16+
|> then(fn
17+
{:ok, ast} -> ast
18+
{:error, ast, _} -> ast
19+
end),
20+
{:ok, result} <- find_node(ast, position) do
21+
case result do
22+
{{:., _, [{:__aliases__, _, modules}, name]}, _, _} -> {:ok, {Module.concat(modules), name}}
23+
end
24+
end
25+
end
26+
27+
def format({:ok, {:docs_v1, _, :elixir, _, _, _, docs}}, func_name) do
28+
docs
29+
|> Enum.filter(fn
30+
{{_, name, _arity}, _, _, _, _} -> name == func_name
31+
end)
32+
|> Enum.map(fn
33+
{{_, _name, _arity}, _, [signature], _, _} ->
34+
params_info =
35+
signature
36+
|> Spitfire.parse!()
37+
|> then(fn {_, _, args} ->
38+
Enum.map(args, fn {name, _, _} -> name end)
39+
end)
40+
|> Enum.map(fn name ->
41+
%ParameterInformation{
42+
label: Atom.to_string(name)
43+
}
44+
end)
45+
46+
%SignatureHelp{
47+
signatures: [
48+
%SignatureInformation{
49+
label: signature,
50+
parameters: params_info,
51+
documentation: %MarkupContent{
52+
kind: MarkupKind.markdown(),
53+
value: ""
54+
}
55+
}
56+
]
57+
}
58+
59+
# {{_, _name, _arity}, _, [], _, _} ->
60+
# []
61+
62+
_otherwise ->
63+
[]
64+
end)
65+
end
66+
67+
def format({:ok, {:error, :module_not_found}}, _func_name) do
68+
[]
69+
end
70+
71+
defp find_node(ast, {line, column}) do
72+
position = [line: line, column: column]
73+
74+
result =
75+
ast
76+
|> Zipper.zip()
77+
|> Zipper.find(fn
78+
{{:., _, _}, _metadata, _} = node ->
79+
range = Sourceror.get_range(node)
80+
81+
Sourceror.compare_positions(range.start, position) == :lt &&
82+
Sourceror.compare_positions(range.end, position) == :gt
83+
84+
_ ->
85+
false
86+
end)
87+
88+
if result do
89+
{:ok, Zipper.node(result)}
90+
else
91+
{:error, :not_found}
92+
end
93+
end
94+
end

0 commit comments

Comments
 (0)