Skip to content

Commit 1b8f72b

Browse files
authored
feat: find references, reverts "fix: revert 0.7 (#142)" (#150)
This reverts commit 5a1713c.
1 parent 06704bc commit 1b8f72b

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

lib/next_ls.ex

+113
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ defmodule NextLS do
1919
alias GenLSP.Requests.TextDocumentDefinition
2020
alias GenLSP.Requests.TextDocumentDocumentSymbol
2121
alias GenLSP.Requests.TextDocumentFormatting
22+
alias GenLSP.Requests.TextDocumentReferences
2223
alias GenLSP.Requests.WorkspaceSymbol
2324
alias GenLSP.Structures.DidChangeWatchedFilesParams
2425
alias GenLSP.Structures.DidChangeWorkspaceFoldersParams
@@ -108,6 +109,7 @@ defmodule NextLS do
108109
document_formatting_provider: true,
109110
workspace_symbol_provider: true,
110111
document_symbol_provider: true,
112+
references_provider: true,
111113
definition_provider: true,
112114
workspace: %{
113115
workspace_folders: %GenLSP.Structures.WorkspaceFoldersServerCapabilities{
@@ -171,6 +173,64 @@ defmodule NextLS do
171173
{:reply, symbols, lsp}
172174
end
173175

176+
# TODO handle `context: %{includeDeclaration: true}` to include the current symbol definition among
177+
# the results.
178+
def handle_request(%TextDocumentReferences{params: %{position: position, text_document: %{uri: uri}}}, lsp) do
179+
file = URI.parse(uri).path
180+
line = position.line + 1
181+
col = position.character + 1
182+
183+
locations =
184+
dispatch(lsp.assigns.registry, :databases, fn databases ->
185+
Enum.flat_map(databases, fn {database, _} ->
186+
references =
187+
case symbol_info(file, line, col, database) do
188+
{:function, module, function} ->
189+
DB.query(
190+
database,
191+
~Q"""
192+
SELECT file, start_line, end_line, start_column, end_column
193+
FROM "references" as refs
194+
WHERE refs.identifier = ?
195+
AND refs.type = ?
196+
AND refs.module = ?
197+
AND NOT like('/home/runner/work/elixir/%', refs.file)
198+
""",
199+
[function, "function", module]
200+
)
201+
202+
{:module, module} ->
203+
DB.query(
204+
database,
205+
~Q"""
206+
SELECT file, start_line, end_line, start_column, end_column
207+
FROM "references" as refs
208+
WHERE refs.module = ?
209+
AND refs.type = ?
210+
AND NOT like('/home/runner/work/elixir/%', refs.file)
211+
""",
212+
[module, "alias"]
213+
)
214+
215+
:unknown ->
216+
[]
217+
end
218+
219+
for [file, start_line, end_line, start_column, end_column] <- references do
220+
%Location{
221+
uri: "file://#{file}",
222+
range: %Range{
223+
start: %Position{line: clamp(start_line - 1), character: clamp(start_column - 1)},
224+
end: %Position{line: clamp(end_line - 1), character: clamp(end_column - 1)}
225+
}
226+
}
227+
end
228+
end)
229+
end)
230+
231+
{:reply, locations, lsp}
232+
end
233+
174234
def handle_request(%WorkspaceSymbol{params: %{query: query}}, lsp) do
175235
filter = fn sym ->
176236
if query == "" do
@@ -603,4 +663,57 @@ defmodule NextLS do
603663
{^ref, result} -> result
604664
end
605665
end
666+
667+
defp symbol_info(file, line, col, database) do
668+
definition_query =
669+
~Q"""
670+
SELECT module, type, name
671+
FROM "symbols" sym
672+
WHERE sym.file = ?
673+
AND sym.line = ?
674+
ORDER BY sym.id ASC
675+
LIMIT 1
676+
"""
677+
678+
reference_query = ~Q"""
679+
SELECT identifier, type, module
680+
FROM "references" refs
681+
WHERE refs.file = ?
682+
AND refs.start_line <= ? AND refs.end_line >= ?
683+
AND refs.start_column <= ? AND refs.end_column >= ?
684+
ORDER BY refs.id ASC
685+
LIMIT 1
686+
"""
687+
688+
case DB.query(database, definition_query, [file, line]) do
689+
[[module, "defmodule", _]] ->
690+
{:module, module}
691+
692+
[[module, "defstruct", _]] ->
693+
{:module, module}
694+
695+
[[module, "def", function]] ->
696+
{:function, module, function}
697+
698+
[[module, "defp", function]] ->
699+
{:function, module, function}
700+
701+
[[module, "defmacro", function]] ->
702+
{:function, module, function}
703+
704+
_unknown_definition ->
705+
case DB.query(database, reference_query, [file, line, line, col, col]) do
706+
[[function, "function", module]] ->
707+
{:function, module, function}
708+
709+
[[_alias, "alias", module]] ->
710+
{:module, module}
711+
712+
_unknown_reference ->
713+
:unknown
714+
end
715+
end
716+
end
717+
718+
defp clamp(line), do: max(line, 0)
606719
end

test/next_ls_test.exs

+98
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,104 @@ defmodule NextLSTest do
888888
end
889889
end
890890

891+
describe "find references" do
892+
@describetag root_paths: ["my_proj"]
893+
setup %{tmp_dir: tmp_dir} do
894+
File.mkdir_p!(Path.join(tmp_dir, "my_proj/lib"))
895+
File.write!(Path.join(tmp_dir, "my_proj/mix.exs"), mix_exs())
896+
[cwd: tmp_dir]
897+
end
898+
899+
setup %{cwd: cwd} do
900+
peace = Path.join(cwd, "my_proj/lib/peace.ex")
901+
902+
File.write!(peace, """
903+
defmodule MyApp.Peace do
904+
def and_love() do
905+
"✌️"
906+
end
907+
end
908+
""")
909+
910+
bar = Path.join(cwd, "my_proj/lib/bar.ex")
911+
912+
File.write!(bar, """
913+
defmodule Bar do
914+
alias MyApp.Peace
915+
def run() do
916+
Peace.and_love()
917+
end
918+
end
919+
""")
920+
921+
[bar: bar, peace: peace]
922+
end
923+
924+
setup :with_lsp
925+
926+
test "list function references", %{client: client, bar: bar, peace: peace} do
927+
assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})
928+
assert_request(client, "client/registerCapability", fn _params -> nil end)
929+
assert_notification "window/logMessage", %{"message" => "[NextLS] Runtime for folder my_proj is ready..."}
930+
assert_notification "window/logMessage", %{"message" => "[NextLS] Compiled!"}
931+
932+
request(client, %{
933+
method: "textDocument/references",
934+
id: 4,
935+
jsonrpc: "2.0",
936+
params: %{
937+
position: %{line: 1, character: 6},
938+
textDocument: %{uri: uri(peace)},
939+
context: %{includeDeclaration: true}
940+
}
941+
})
942+
943+
uri = uri(bar)
944+
945+
assert_result 4,
946+
[
947+
%{
948+
"uri" => ^uri,
949+
"range" => %{
950+
"start" => %{"line" => 3, "character" => 10},
951+
"end" => %{"line" => 3, "character" => 18}
952+
}
953+
}
954+
]
955+
end
956+
957+
test "list module references", %{client: client, bar: bar, peace: peace} do
958+
assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})
959+
assert_request(client, "client/registerCapability", fn _params -> nil end)
960+
assert_notification "window/logMessage", %{"message" => "[NextLS] Runtime for folder my_proj is ready..."}
961+
assert_notification "window/logMessage", %{"message" => "[NextLS] Compiled!"}
962+
963+
request(client, %{
964+
method: "textDocument/references",
965+
id: 4,
966+
jsonrpc: "2.0",
967+
params: %{
968+
position: %{line: 0, character: 10},
969+
textDocument: %{uri: uri(peace)},
970+
context: %{includeDeclaration: true}
971+
}
972+
})
973+
974+
uri = uri(bar)
975+
976+
assert_result 4,
977+
[
978+
%{
979+
"uri" => ^uri,
980+
"range" => %{
981+
"start" => %{"line" => 3, "character" => 4},
982+
"end" => %{"line" => 3, "character" => 9}
983+
}
984+
}
985+
]
986+
end
987+
end
988+
891989
describe "workspaces" do
892990
setup %{tmp_dir: tmp_dir} do
893991
[cwd: tmp_dir]

0 commit comments

Comments
 (0)