Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
lucacervello committed Mar 12, 2024
1 parent d5c9c0a commit 64c53c7
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 23 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)
end
end)

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

def handle_request(%Shutdown{}, lsp) do
{:reply, nil, assign(lsp, exit_code: 0)}
end
Expand Down
22 changes: 22 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,26 @@ defmodule NextLS.ASTHelpers do
end
end)
end

defmodule Functions do
@moduledoc false
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
end
end
117 changes: 117 additions & 0 deletions lib/next_ls/signature_help.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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) do
symbol = fetch_symbol(file, line, col, db)

case symbol do
nil ->
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: 0,
active_parameter: 0
}
else
nil
end
end
end

defp fetch_symbol(file, line, col, db) do
rows =
DB.query(
db,
~Q"""
SELECT
*
FROM
'references' AS refs
WHERE
refs.file = ?
AND refs.start_line <= ?
AND ? <= refs.end_line
AND refs.start_column <= ?
AND ? <= refs.end_column
ORDER BY refs.id asc
LIMIT 1;
""",
[file, line, line, col, col]
)

reference =
case rows do
[[_pk, identifier, _arity, _file, type, module, _start_l, _start_c, _end_l, _end_c | _]] ->
%{identifier: identifier, type: type, module: module}

[] ->
nil
end

with %{identifier: identifier, type: type, module: module} <- reference do
query =
~Q"""
SELECT
*
FROM
symbols
WHERE
symbols.module = ?
AND symbols.name = ?;
"""

args =
case type do
"alias" ->
[module, module]

"function" ->
[module, identifier]

"attribute" ->
[module, identifier]

_ ->
nil
end

if args do
DB.query(db, query, args)
else
nil
end
end
end
end
14 changes: 14 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,18 @@ 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
end
94 changes: 94 additions & 0 deletions test/next_ls/signature_help_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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) do
boom1
end
end
""")

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

File.write!(bar, """
defmodule Bar do
import Imported
def run() do
Remote.bang!("‼️")
process()
end
defp process() do
boom("💣")
:ok
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: 15},
textDocument: %{uri: uri}
}
})

assert_result 4, %{
"activeParameter" => 0,
"activeSignature" => 0,
"signatures" => [
%{
"activeParameter" => 0,
"parameters" => [
%{"label" => "bang"}
],
"documentation" => "need help",
"label" => "bang!"
}
]
}
end
end
end
46 changes: 23 additions & 23 deletions test/next_ls_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,29 +70,29 @@ defmodule NextLSTest do
assert_result 2, nil
end

test "returns method not found for unimplemented requests", %{client: client} do
id = System.unique_integer([:positive])

assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})

assert :ok ==
request(client, %{
method: "textDocument/signatureHelp",
id: id,
jsonrpc: "2.0",
params: %{position: %{line: 0, character: 0}, textDocument: %{uri: ""}}
})

assert_notification "window/logMessage", %{
"message" => "[Next LS] Method Not Found: textDocument/signatureHelp",
"type" => 2
}

assert_error ^id, %{
"code" => -32_601,
"message" => "Method Not Found: textDocument/signatureHelp"
}
end
# test "returns method not found for unimplemented requests", %{client: client} do
# id = System.unique_integer([:positive])

# assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})

# assert :ok ==
# request(client, %{
# method: "textDocument/signatureHelp",
# id: id,
# jsonrpc: "2.0",
# params: %{position: %{line: 0, character: 0}, textDocument: %{uri: ""}}
# })

# assert_notification "window/logMessage", %{
# "message" => "[Next LS] Method Not Found: textDocument/signatureHelp",
# "type" => 2
# }

# assert_error ^id, %{
# "code" => -32_601,
# "message" => "Method Not Found: textDocument/signatureHelp"
# }
# end

test "can initialize the server" do
assert_result 1, %{
Expand Down

0 comments on commit 64c53c7

Please sign in to comment.