Skip to content

Commit 640a857

Browse files
authored
Merge pull request elixir-editors#50 from mattbaker/reference-support
Reference support
2 parents a05b520 + 8b41747 commit 640a857

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

apps/language_server/lib/language_server/protocol.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ defmodule ElixirLS.LanguageServer.Protocol do
6464
end
6565
end
6666

67+
defmacro references_req(id, uri, line, character, include_declaration) do
68+
quote do
69+
request(unquote(id), "textDocument/references", %{
70+
"textDocument" => %{"uri" => unquote(uri)},
71+
"position" => %{"line" => unquote(line), "character" => unquote(character)},
72+
"context" => %{"includeDeclaration" => unquote(include_declaration)}
73+
})
74+
end
75+
end
76+
6777
defmacro initialize_req(id, root_uri, client_capabilities) do
6878
quote do
6979
request(unquote(id), "initialize", %{
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
defmodule ElixirLS.LanguageServer.Providers.References do
2+
@moduledoc """
3+
This module provides References support by using
4+
the `Mix.Tasks.Xref.call/0` task to find all references to
5+
any function or module identified at the provided location.
6+
"""
7+
8+
alias ElixirLS.LanguageServer.SourceFile
9+
alias ElixirSense.Core.{Metadata, Parser, Source, Introspection}
10+
11+
def references(text, line, character, _include_declaration) do
12+
xref_at_cursor(text, line, character)
13+
|> Enum.filter(fn %{line: line} -> is_integer(line) end)
14+
|> Enum.map(&build_location/1)
15+
end
16+
17+
def supported? do
18+
Mix.Tasks.Xref.__info__(:functions) |> Enum.member?({:calls, 0})
19+
end
20+
21+
defp xref_at_cursor(text, line, character) do
22+
env_at_cursor = line_environment(text, line)
23+
24+
subject_at_cursor(text, line, character)
25+
|> Introspection.split_mod_fun_call()
26+
|> expand_mod_fun(env_at_cursor)
27+
|> add_arity(env_at_cursor)
28+
|> callers()
29+
end
30+
31+
defp line_environment(text, line) do
32+
Parser.parse_string(text, true, true, line + 1) |> Metadata.get_env(line + 1)
33+
end
34+
35+
defp subject_at_cursor(text, line, character) do
36+
Source.subject(text, line + 1, character + 1)
37+
end
38+
39+
defp expand_mod_fun({nil, nil}, _environment), do: nil
40+
41+
defp expand_mod_fun(mod_fun, %{imports: imports, aliases: aliases, module: module}) do
42+
case Introspection.actual_mod_fun(mod_fun, imports, aliases, module) do
43+
{mod, nil} -> {mod, nil}
44+
{mod, fun} -> {mod, fun}
45+
end
46+
end
47+
48+
defp add_arity({mod, fun}, %{scope: {fun, arity}, module: mod}), do: {mod, fun, arity}
49+
defp add_arity({mod, fun}, _env), do: {mod, fun, nil}
50+
51+
def callers(nil), do: []
52+
def callers(mfa), do: Mix.Tasks.Xref.calls() |> Enum.filter(caller_filter(mfa))
53+
54+
defp caller_filter({module, nil, nil}), do: &match?(%{callee: {^module, _, _}}, &1)
55+
defp caller_filter({module, func, nil}), do: &match?(%{callee: {^module, ^func, _}}, &1)
56+
defp caller_filter({module, func, arity}), do: &match?(%{callee: {^module, ^func, ^arity}}, &1)
57+
58+
defp build_location(call) do
59+
%{
60+
"uri" => SourceFile.path_to_uri(call.file),
61+
"range" => %{
62+
"start" => %{"line" => call.line - 1, "character" => 0},
63+
"end" => %{"line" => call.line - 1, "character" => 0}
64+
}
65+
}
66+
end
67+
end

apps/language_server/lib/language_server/server.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ defmodule ElixirLS.LanguageServer.Server do
2323
Completion,
2424
Hover,
2525
Definition,
26+
References,
2627
Formatting,
2728
SignatureHelp
2829
}
@@ -322,6 +323,17 @@ defmodule ElixirLS.LanguageServer.Server do
322323
{:async, fun, state}
323324
end
324325

326+
defp handle_request(references_req(_id, uri, line, character, include_declaration), state) do
327+
fun = fn ->
328+
{
329+
:ok,
330+
References.references(state.source_files[uri].text, line, character, include_declaration)
331+
}
332+
end
333+
334+
{:async, fun, state}
335+
end
336+
325337
defp handle_request(hover_req(_id, uri, line, character), state) do
326338
fun = fn ->
327339
Hover.hover(state.source_files[uri].text, line, character)
@@ -391,6 +403,7 @@ defmodule ElixirLS.LanguageServer.Server do
391403
"hoverProvider" => true,
392404
"completionProvider" => %{"triggerCharacters" => ["."]},
393405
"definitionProvider" => true,
406+
"referencesProvider" => References.supported?(),
394407
"documentFormattingProvider" => Formatting.supported?(),
395408
"signatureHelpProvider" => %{"triggerCharacters" => ["("]}
396409
}

0 commit comments

Comments
 (0)