Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
lucacervello committed Mar 13, 2024
1 parent d5c9c0a commit 9a4860a
Show file tree
Hide file tree
Showing 7 changed files with 348 additions and 24 deletions.
21 changes: 21 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 @@ -146,6 +150,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 @@ -699,6 +706,20 @@ defmodule NextLS do
{:reply, nil, lsp}
end

def handle_request(
%TextDocumentSignatureHelp{params: %SignatureHelpParams{text_document: %{uri: uri}, position: position}},
lsp
) do
result =
dispatch(lsp.assigns.registry, :databases, fn entries ->
for {pid, _} <- entries do
SignatureHelp.fetch(URI.parse(uri).path, {position.line + 1, position.character + 1}, pid, lsp.assigns.logger)
end
end)

{:reply, List.first(result), lsp}
end

def handle_request(%Shutdown{}, lsp) do
{:reply, nil, assign(lsp, exit_code: 0)}
end
Expand Down
60 changes: 60 additions & 0 deletions lib/next_ls/helpers/ast_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,64 @@ defmodule NextLS.ASTHelpers do
end
end)
end

defmodule Functions do
@moduledoc false

alias Sourceror.Zipper, as: Z

def get_function_params(code, identifier, line, _col) do
ast =
NextLS.Parser.parse!(code, columns: true)

identifier = String.to_atom(identifier)

{_ast, args} =
Macro.prewalk(ast, nil, fn
{^identifier, [line: ^line, column: _], args} = ast, _acc -> {ast, args}
other, acc -> {other, acc}
end)

if args do
args
else
[]
end
end

def get_function_name_from_params(code, line, col) do
pos = [line: line + 1, column: col + 1]

ast =
case Spitfire.parse(code) do
{:ok, ast} ->
ast

{:error, ast, _errors} ->
ast
end

{_ast, result} =
ast
|> Z.zip()
|> Z.traverse(nil, fn tree, acc ->
node = Z.node(tree)
range = Sourceror.get_range(node)

if not is_nil(range) and
match?({:., _, [{:__aliases__, _, _aliases}, _identifier]}, node) do
if Sourceror.compare_positions(range.end, pos) == :lt do
{:., _, [{:__aliases__, _, aliases}, identifier]} = node
{tree, {aliases, identifier}}
else
{tree, acc}
end
else
{tree, acc}
end
end)

result
end
end
end
2 changes: 1 addition & 1 deletion lib/next_ls/logger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule NextLS.Logger do

def handle_cast({:log, type, msg}, state) do
apply(GenLSP, type, [state.lsp, String.trim("[Next LS] #{msg}")])

case type do
:log -> Logger.debug(msg)
:warning -> Logger.warning(msg)
Expand Down
73 changes: 73 additions & 0 deletions lib/next_ls/signature_help.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
defmodule NextLS.SignatureHelp do
@moduledoc false

import NextLS.DB.Query

alias GenLSP.Structures.ParameterInformation
alias GenLSP.Structures.SignatureHelp
alias GenLSP.Structures.SignatureInformation
alias NextLS.ASTHelpers
alias NextLS.DB

def fetch(file, {line, col}, db, _logger) do
code = File.read!(file)

{mod, func} =
ASTHelpers.Functions.get_function_name_from_params(code, line, col)

query =
~Q"""
SELECT
*
FROM
symbols
WHERE
symbols.module = ?
AND symbols.name = ?;
"""

args = [Enum.map_join(mod, ".", &Atom.to_string/1), Atom.to_string(func)]

symbol = DB.query(db, query, args)

result =
case symbol do
nil ->

Check warning on line 35 in lib/next_ls/signature_help.ex

View workflow job for this annotation

GitHub Actions / dialyzer

pattern_match

The pattern can never match the type [any()].
nil

[] ->
nil

[[_, _mod, file, type, label, line, col | _] | _] = _definition ->
if type in ["def", "defp"] do
code = File.read!(file)

params =
code
|> ASTHelpers.Functions.get_function_params(label, line, col)
|> Enum.map(fn {name, _, _} ->
%ParameterInformation{
label: Atom.to_string(name)
}
end)

%SignatureHelp{
signatures: [
%SignatureInformation{
label: label,
documentation: "need help",
parameters: params
# active_parameter: 0
}
],
# active_signature: 1,
active_parameter: 0
}
else
nil
end
end

result
end
end
49 changes: 49 additions & 0 deletions test/next_ls/helpers/ast_helpers_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,53 @@ defmodule NextLS.ASTHelpersTest do
assert {{5, 5}, {5, 8}} == Aliases.extract_alias_range(code, {start, stop}, :Four)
end
end

describe "extract function params" do
test "simple function params" do
code = """
@doc "foo doc"
def foo(bar, baz) do
:ok
end
"""

assert [{:bar, [line: 2, column: 9], nil}, {:baz, [line: 2, column: 14], nil}] ==
ASTHelpers.Functions.get_function_params(code, "foo", 2, 5)
end
end

describe "extract function name from params" do
test "alias function" do
code = """
defmodule MyModule do
List.starts_with?(
end
"""

assert {[:List], :starts_with?} ==
ASTHelpers.Functions.get_function_name_from_params(code, 2, 21)
end

test "nested alias function" do
code = """
defmodule MyModule do
List.starts_with?(String.trim()
end
"""

assert {[:String], :trim} ==
ASTHelpers.Functions.get_function_name_from_params(code, 2, 37)
end

test "simple function" do
code = """
defmodule MyModule do
put_in(
end
"""

assert :put_in ==
ASTHelpers.Functions.get_function_name_from_params(code, 2, 21)
end
end
end
121 changes: 121 additions & 0 deletions test/next_ls/signature_help_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
defmodule NextLS.SignatureHelpTest do
use ExUnit.Case, async: true

import GenLSP.Test
import NextLS.Support.Utils

@moduletag :tmp_dir

describe "function" do
@describetag root_paths: ["my_proj"]
setup %{tmp_dir: tmp_dir} do
File.mkdir_p!(Path.join(tmp_dir, "my_proj/lib"))
File.write!(Path.join(tmp_dir, "my_proj/mix.exs"), mix_exs())
[cwd: tmp_dir]
end

setup %{cwd: cwd} do
remote = Path.join(cwd, "my_proj/lib/remote.ex")

File.write!(remote, """
defmodule Remote do
def bang!(bang) do
bang
end
end
""")

imported = Path.join(cwd, "my_proj/lib/imported.ex")

File.write!(imported, """
defmodule Imported do
def boom(boom1, _boom2) do
boom1
end
end
""")

bar = Path.join(cwd, "my_proj/lib/bar.ex")

File.write!(bar, """
defmodule Bar do
def run() do
Remote.bang!()
end
end
""")

[bar: bar, imported: imported, remote: remote]
end

setup :with_lsp

test "get signature help", %{client: client, bar: bar} = context do
assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})

assert_is_ready(context, "my_proj")
assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}}

uri = uri(bar)

request(client, %{
method: "textDocument/signatureHelp",
id: 4,
jsonrpc: "2.0",
params: %{
position: %{line: 3, character: 16},
textDocument: %{uri: uri}
}
})

assert_result 4, %{
"activeParameter" => 0,
"activeSignature" => 0,
"signatures" => [
%{
"activeParameter" => 0,
"parameters" => [
%{"label" => "bang"}
],
"documentation" => "need help",
"label" => "bang!"
}
]
}
end

test "get signature help 2", %{client: client, bar: bar} = context do
assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})

assert_is_ready(context, "my_proj")
assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}}

uri = uri(bar)

request(client, %{
method: "textDocument/signatureHelp",
id: 4,
jsonrpc: "2.0",
params: %{
position: %{line: 8, character: 10},
textDocument: %{uri: uri}
}
})

assert_result 4, %{
"activeParameter" => 0,
"activeSignature" => 0,
"signatures" => [
%{
"activeParameter" => 0,
"parameters" => [
%{"label" => "bang"}
],
"documentation" => "need help",
"label" => "bang!"
}
]
}
end
end
end
Loading

0 comments on commit 9a4860a

Please sign in to comment.