Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: find references, reverts "fix: revert 0.7 (#142)" #150

Merged
merged 1 commit into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading