Skip to content

Commit

Permalink
feat: add signature help
Browse files Browse the repository at this point in the history
  • Loading branch information
lucacervello committed Mar 29, 2024
1 parent 1ae8ead commit 15e9b98
Show file tree
Hide file tree
Showing 4 changed files with 386 additions and 3 deletions.
35 changes: 35 additions & 0 deletions lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule NextLS do
alias GenLSP.Requests.TextDocumentFormatting
alias GenLSP.Requests.TextDocumentHover
alias GenLSP.Requests.TextDocumentReferences
alias GenLSP.Requests.TextDocumentSignatureHelp
alias GenLSP.Requests.WorkspaceApplyEdit
alias GenLSP.Requests.WorkspaceSymbol
alias GenLSP.Structures.ApplyWorkspaceEditParams
Expand All @@ -41,6 +42,8 @@ defmodule NextLS do
alias GenLSP.Structures.Range
alias GenLSP.Structures.SaveOptions
alias GenLSP.Structures.ServerCapabilities
alias GenLSP.Structures.SignatureHelp
alias GenLSP.Structures.SignatureHelpParams
alias GenLSP.Structures.SymbolInformation
alias GenLSP.Structures.TextDocumentIdentifier
alias GenLSP.Structures.TextDocumentItem
Expand All @@ -53,6 +56,7 @@ defmodule NextLS do
alias NextLS.DiagnosticCache
alias NextLS.Progress
alias NextLS.Runtime
alias NextLS.SignatureHelp

def start_link(args) do
{args, opts} =
Expand Down Expand Up @@ -148,6 +152,9 @@ defmodule NextLS do
"from-pipe"
]
},
signature_help_provider: %GenLSP.Structures.SignatureHelpOptions{
trigger_characters: ["(", ","]
},
hover_provider: true,
workspace_symbol_provider: true,
document_symbol_provider: true,
Expand Down Expand Up @@ -746,6 +753,34 @@ defmodule NextLS do
{:reply, nil, lsp}
end

def handle_request(
%TextDocumentSignatureHelp{params: %SignatureHelpParams{text_document: %{uri: uri}, position: position}},
lsp
) do
signature_help =
case SignatureHelp.fetch_mod_and_name(uri, {position.line + 1, position.character + 1}) do
{:ok, {mod, name}} ->
docs =
dispatch(lsp.assigns.registry, :runtimes, fn entries ->
[result] =
for {runtime, %{uri: wuri}} <- entries, String.starts_with?(uri, wuri) do
Runtime.call(runtime, {Code, :fetch_docs, [mod]})
end

result
end)

docs
|> SignatureHelp.format(name)
|> List.first()

{:error, :not_found} ->
nil
end

{:reply, signature_help, lsp}
end

def handle_request(%Shutdown{}, lsp) do
{:reply, nil, assign(lsp, exit_code: 0)}
end
Expand Down
94 changes: 94 additions & 0 deletions lib/next_ls/signature_help.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
defmodule NextLS.SignatureHelp do
@moduledoc false

alias GenLSP.Enumerations.MarkupKind
alias GenLSP.Structures.MarkupContent
alias GenLSP.Structures.ParameterInformation
alias GenLSP.Structures.SignatureHelp
alias GenLSP.Structures.SignatureInformation
alias Sourceror.Zipper

def fetch_mod_and_name(uri, position) do
with {:ok, text} <- File.read(URI.parse(uri).path),
ast =
text
|> Spitfire.parse()
|> then(fn
{:ok, ast} -> ast
{:error, ast, _} -> ast
end),
{:ok, result} <- find_node(ast, position) do
case result do
{{:., _, [{:__aliases__, _, modules}, name]}, _, _} -> {:ok, {Module.concat(modules), name}}
end
end
end

def format({:ok, {:docs_v1, _, :elixir, _, _, _, docs}}, func_name) do
docs
|> Enum.filter(fn
{{_, name, _arity}, _, _, _, _} -> name == func_name
end)
|> Enum.map(fn
{{_, _name, _arity}, _, [signature], _, _} ->
params_info =
signature
|> Spitfire.parse!()
|> then(fn {_, _, args} ->
Enum.map(args, fn {name, _, _} -> name end)
end)
|> Enum.map(fn name ->
%ParameterInformation{
label: Atom.to_string(name)
}
end)

%SignatureHelp{
signatures: [
%SignatureInformation{
label: signature,
parameters: params_info,
documentation: %MarkupContent{
kind: MarkupKind.markdown(),
value: ""
}
}
]
}

# {{_, _name, _arity}, _, [], _, _} ->
# []

_otherwise ->
[]
end)
end

def format({:ok, {:error, :module_not_found}}, _func_name) do
[]
end

defp find_node(ast, {line, column}) do
position = [line: line, column: column]

result =
ast
|> Zipper.zip()
|> Zipper.find(fn
{{:., _, _}, _metadata, _} = node ->
range = Sourceror.get_range(node)

Sourceror.compare_positions(range.start, position) == :lt &&
Sourceror.compare_positions(range.end, position) == :gt

_ ->
false
end)

if result do
{:ok, Zipper.node(result)}
else
{:error, :not_found}
end
end
end
Loading

0 comments on commit 15e9b98

Please sign in to comment.