Skip to content

Commit

Permalink
feat: find references, reverts "fix: revert 0.7 (#142)" (#150)
Browse files Browse the repository at this point in the history
This reverts commit 5a1713c.
  • Loading branch information
mhanberg authored Aug 9, 2023
1 parent 06704bc commit 1b8f72b
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 0 deletions.
113 changes: 113 additions & 0 deletions lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ defmodule NextLS do
alias GenLSP.Requests.TextDocumentDefinition
alias GenLSP.Requests.TextDocumentDocumentSymbol
alias GenLSP.Requests.TextDocumentFormatting
alias GenLSP.Requests.TextDocumentReferences
alias GenLSP.Requests.WorkspaceSymbol
alias GenLSP.Structures.DidChangeWatchedFilesParams
alias GenLSP.Structures.DidChangeWorkspaceFoldersParams
Expand Down Expand Up @@ -108,6 +109,7 @@ defmodule NextLS do
document_formatting_provider: true,
workspace_symbol_provider: true,
document_symbol_provider: true,
references_provider: true,
definition_provider: true,
workspace: %{
workspace_folders: %GenLSP.Structures.WorkspaceFoldersServerCapabilities{
Expand Down Expand Up @@ -171,6 +173,64 @@ defmodule NextLS do
{:reply, symbols, lsp}
end

# TODO handle `context: %{includeDeclaration: true}` to include the current symbol definition among
# the results.
def handle_request(%TextDocumentReferences{params: %{position: position, text_document: %{uri: uri}}}, lsp) do
file = URI.parse(uri).path
line = position.line + 1
col = position.character + 1

locations =
dispatch(lsp.assigns.registry, :databases, fn databases ->
Enum.flat_map(databases, fn {database, _} ->
references =
case symbol_info(file, line, col, database) do
{:function, module, function} ->
DB.query(
database,
~Q"""
SELECT file, start_line, end_line, start_column, end_column
FROM "references" as refs
WHERE refs.identifier = ?
AND refs.type = ?
AND refs.module = ?
AND NOT like('/home/runner/work/elixir/%', refs.file)
""",
[function, "function", module]
)

{:module, module} ->
DB.query(
database,
~Q"""
SELECT file, start_line, end_line, start_column, end_column
FROM "references" as refs
WHERE refs.module = ?
AND refs.type = ?
AND NOT like('/home/runner/work/elixir/%', refs.file)
""",
[module, "alias"]
)

:unknown ->
[]
end

for [file, start_line, end_line, start_column, end_column] <- references do
%Location{
uri: "file://#{file}",
range: %Range{
start: %Position{line: clamp(start_line - 1), character: clamp(start_column - 1)},
end: %Position{line: clamp(end_line - 1), character: clamp(end_column - 1)}
}
}
end
end)
end)

{:reply, locations, lsp}
end

def handle_request(%WorkspaceSymbol{params: %{query: query}}, lsp) do
filter = fn sym ->
if query == "" do
Expand Down Expand Up @@ -603,4 +663,57 @@ defmodule NextLS do
{^ref, result} -> result
end
end

defp symbol_info(file, line, col, database) do
definition_query =
~Q"""
SELECT module, type, name
FROM "symbols" sym
WHERE sym.file = ?
AND sym.line = ?
ORDER BY sym.id ASC
LIMIT 1
"""

reference_query = ~Q"""
SELECT identifier, type, module
FROM "references" 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
"""

case DB.query(database, definition_query, [file, line]) do
[[module, "defmodule", _]] ->
{:module, module}

[[module, "defstruct", _]] ->
{:module, module}

[[module, "def", function]] ->
{:function, module, function}

[[module, "defp", function]] ->
{:function, module, function}

[[module, "defmacro", function]] ->
{:function, module, function}

_unknown_definition ->
case DB.query(database, reference_query, [file, line, line, col, col]) do
[[function, "function", module]] ->
{:function, module, function}

[[_alias, "alias", module]] ->
{:module, module}

_unknown_reference ->
:unknown
end
end
end

defp clamp(line), do: max(line, 0)
end
98 changes: 98 additions & 0 deletions test/next_ls_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,104 @@ defmodule NextLSTest do
end
end

describe "find references" 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
peace = Path.join(cwd, "my_proj/lib/peace.ex")

File.write!(peace, """
defmodule MyApp.Peace do
def and_love() do
"✌️"
end
end
""")

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

File.write!(bar, """
defmodule Bar do
alias MyApp.Peace
def run() do
Peace.and_love()
end
end
""")

[bar: bar, peace: peace]
end

setup :with_lsp

test "list function references", %{client: client, bar: bar, peace: peace} do
assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})
assert_request(client, "client/registerCapability", fn _params -> nil end)
assert_notification "window/logMessage", %{"message" => "[NextLS] Runtime for folder my_proj is ready..."}
assert_notification "window/logMessage", %{"message" => "[NextLS] Compiled!"}

request(client, %{
method: "textDocument/references",
id: 4,
jsonrpc: "2.0",
params: %{
position: %{line: 1, character: 6},
textDocument: %{uri: uri(peace)},
context: %{includeDeclaration: true}
}
})

uri = uri(bar)

assert_result 4,
[
%{
"uri" => ^uri,
"range" => %{
"start" => %{"line" => 3, "character" => 10},
"end" => %{"line" => 3, "character" => 18}
}
}
]
end

test "list module references", %{client: client, bar: bar, peace: peace} do
assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})
assert_request(client, "client/registerCapability", fn _params -> nil end)
assert_notification "window/logMessage", %{"message" => "[NextLS] Runtime for folder my_proj is ready..."}
assert_notification "window/logMessage", %{"message" => "[NextLS] Compiled!"}

request(client, %{
method: "textDocument/references",
id: 4,
jsonrpc: "2.0",
params: %{
position: %{line: 0, character: 10},
textDocument: %{uri: uri(peace)},
context: %{includeDeclaration: true}
}
})

uri = uri(bar)

assert_result 4,
[
%{
"uri" => ^uri,
"range" => %{
"start" => %{"line" => 3, "character" => 4},
"end" => %{"line" => 3, "character" => 9}
}
}
]
end
end

describe "workspaces" do
setup %{tmp_dir: tmp_dir} do
[cwd: tmp_dir]
Expand Down

0 comments on commit 1b8f72b

Please sign in to comment.