Skip to content

Commit 2db7a34

Browse files
committed
Support go to definition in experimental project
1 parent 1e815bd commit 2db7a34

File tree

4 files changed

+278
-0
lines changed

4 files changed

+278
-0
lines changed

apps/language_server/lib/language_server/experimental/protocol/requests.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ defmodule ElixirLS.LanguageServer.Experimental.Protocol.Requests do
2727
position: Types.Position
2828
end
2929

30+
defmodule GotoDefinition do
31+
use Proto
32+
33+
defrequest "textDocument/definition", :exclusive,
34+
text_document: Types.TextDocument.Identifier,
35+
position: Types.Position
36+
end
37+
3038
defmodule Formatting do
3139
use Proto
3240

apps/language_server/lib/language_server/experimental/protocol/responses.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ defmodule ElixirLS.LanguageServer.Experimental.Protocol.Responses do
88
defresponse optional(list_of(Types.Location))
99
end
1010

11+
defmodule GotoDefinition do
12+
use Proto
13+
14+
defresponse optional(Types.Location)
15+
end
16+
1117
defmodule Formatting do
1218
use Proto
1319

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
defmodule ElixirLS.LanguageServer.Experimental.Provider.Handlers.GotoDefinition do
2+
alias ElixirLS.LanguageServer.Experimental.Protocol.Requests.GotoDefinition
3+
alias ElixirLS.LanguageServer.Experimental.Protocol.Responses
4+
alias ElixirLS.LanguageServer.Experimental.Protocol.Types.Location
5+
alias ElixirLS.LanguageServer.Experimental.Protocol.Types.Range, as: LSRange
6+
alias ElixirLS.LanguageServer.Experimental.SourceFile
7+
alias ElixirLS.LanguageServer.Experimental.SourceFile.Conversions
8+
9+
def handle(%GotoDefinition{} = request, _) do
10+
source_file = request.source_file
11+
pos = request.position
12+
13+
source_file_string = source_file |> SourceFile.to_string()
14+
15+
with %ElixirSense.Location{} = location <-
16+
ElixirSense.definition(source_file_string, pos.line, pos.character + 1),
17+
{:ok, definition} <- build_definition(location, source_file) do
18+
{:reply, Responses.GotoDefinition.new(request.id, definition)}
19+
else
20+
nil ->
21+
{:reply, nil}
22+
23+
{:error, reason} ->
24+
{:error, Responses.GotoDefinition.error(request.id, :request_failed, reason)}
25+
end
26+
end
27+
28+
defp build_definition(
29+
%{line: line, column: column} = elixir_sense_definition,
30+
current_source_file
31+
) do
32+
position = SourceFile.Position.new(line, column - 1)
33+
34+
with {:ok, source_file} <- get_source_file(elixir_sense_definition, current_source_file),
35+
{:ok, ls_position} <- Conversions.to_lsp(position, source_file) do
36+
ls_range = %LSRange{start: ls_position, end: ls_position}
37+
{:ok, Location.new(uri: source_file.uri, range: ls_range)}
38+
end
39+
end
40+
41+
defp get_source_file(%{file: nil}, current_source_file) do
42+
{:ok, current_source_file}
43+
end
44+
45+
defp get_source_file(%{file: path}, _) do
46+
uri = Conversions.ensure_uri(path)
47+
SourceFile.Store.open_temporary(uri)
48+
end
49+
end
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
defmodule ElixirLS.LanguageServer.Experimental.Provider.Handlers.GotoDefinitionTest do
2+
use ExUnit.Case, async: true
3+
4+
alias ElixirLS.LanguageServer.Experimental.Protocol.Requests.GotoDefinition
5+
alias ElixirLS.LanguageServer.Experimental.Protocol.Responses
6+
alias ElixirLS.LanguageServer.Experimental.Provider.Env
7+
alias ElixirLS.LanguageServer.Experimental.Provider.Handlers
8+
alias ElixirLS.LanguageServer.Experimental.SourceFile
9+
alias ElixirLS.LanguageServer.Experimental.SourceFile.Conversions
10+
11+
alias ElixirLS.LanguageServer.Fixtures.LspProtocol
12+
alias ElixirLS.LanguageServer.Test.FixtureHelpers
13+
14+
import LspProtocol
15+
import ElixirLS.Test.TextLoc, only: [annotate_assert: 4]
16+
17+
setup do
18+
{:ok, _} = start_supervised(SourceFile.Store)
19+
:ok
20+
end
21+
22+
def request(file_path, line, char) do
23+
uri = Conversions.ensure_uri(file_path)
24+
25+
params = [
26+
text_document: [uri: uri],
27+
position: [line: line, character: char]
28+
]
29+
30+
with {:ok, contents} <- File.read(file_path),
31+
:ok <- SourceFile.Store.open(uri, contents, 1),
32+
{:ok, _source_file} <- SourceFile.Store.fetch(uri),
33+
{:ok, req} <- build(GotoDefinition, params) do
34+
GotoDefinition.to_elixir(req)
35+
end
36+
end
37+
38+
def handle(request) do
39+
Handlers.GotoDefinition.handle(request, Env.new())
40+
end
41+
42+
defp arrange_referenced_file do
43+
file_path = FixtureHelpers.get_path("references_referenced.ex")
44+
uri = Conversions.ensure_uri(file_path)
45+
%{uri: uri}
46+
end
47+
48+
test "find definition remote function call" do
49+
b_file = arrange_referenced_file()
50+
file_path = FixtureHelpers.get_path("references_remote.ex")
51+
{line, char} = {4, 28}
52+
53+
{:ok, request} = request(file_path, line, char)
54+
55+
annotate_assert(file_path, line, char, """
56+
ReferencesReferenced.referenced_fun()
57+
^
58+
""")
59+
60+
{:reply, %Responses.GotoDefinition{result: definition}} = handle(request)
61+
62+
assert definition.uri == b_file.uri
63+
assert definition.range.start.line == 1
64+
assert definition.range.start.character == 6
65+
assert definition.range.end.line == 1
66+
assert definition.range.end.character == 6
67+
end
68+
69+
test "find definition remote macro call" do
70+
b_file = arrange_referenced_file()
71+
file_path = FixtureHelpers.get_path("references_remote.ex")
72+
{line, char} = {8, 28}
73+
74+
{:ok, request} = request(file_path, line, char)
75+
76+
annotate_assert(file_path, line, char, """
77+
ReferencesReferenced.referenced_macro a do
78+
^
79+
""")
80+
81+
{:reply, %Responses.GotoDefinition{result: definition}} = handle(request)
82+
83+
assert definition.uri == b_file.uri
84+
assert definition.range.start.line == 8
85+
assert definition.range.start.character == 11
86+
assert definition.range.end.line == 8
87+
assert definition.range.end.character == 11
88+
end
89+
90+
test "find definition imported function call" do
91+
b_file = arrange_referenced_file()
92+
file_path = FixtureHelpers.get_path("references_imported.ex")
93+
{line, char} = {4, 5}
94+
95+
{:ok, request} = request(file_path, line, char)
96+
97+
annotate_assert(file_path, line, char, """
98+
referenced_fun()
99+
^
100+
""")
101+
102+
{:reply, %Responses.GotoDefinition{result: definition}} = handle(request)
103+
104+
assert definition.uri == b_file.uri
105+
assert definition.range.start.line == 1
106+
assert definition.range.start.character == 6
107+
assert definition.range.end.line == 1
108+
assert definition.range.end.character == 6
109+
end
110+
111+
test "find definition imported macro call" do
112+
b_file = arrange_referenced_file()
113+
file_path = FixtureHelpers.get_path("references_imported.ex")
114+
{line, char} = {8, 5}
115+
116+
{:ok, request} = request(file_path, line, char)
117+
118+
annotate_assert(file_path, line, char, """
119+
referenced_macro a do
120+
^
121+
""")
122+
123+
{:reply, %Responses.GotoDefinition{result: definition}} = handle(request)
124+
125+
assert definition.uri == b_file.uri
126+
assert definition.range.start.line == 8
127+
assert definition.range.start.character == 11
128+
assert definition.range.end.line == 8
129+
assert definition.range.end.character == 11
130+
end
131+
132+
test "find definition local function call" do
133+
b_file = arrange_referenced_file()
134+
file_path = FixtureHelpers.get_path("references_referenced.ex")
135+
{line, char} = {15, 5}
136+
137+
{:ok, request} = request(file_path, line, char)
138+
139+
annotate_assert(file_path, line, char, """
140+
referenced_fun()
141+
^
142+
""")
143+
144+
{:reply, %Responses.GotoDefinition{result: definition}} = handle(request)
145+
146+
assert definition.uri == b_file.uri
147+
assert definition.range.start.line == 1
148+
assert definition.range.start.character == 6
149+
assert definition.range.end.line == 1
150+
assert definition.range.end.character == 6
151+
end
152+
153+
test "find definition local macro call" do
154+
b_file = arrange_referenced_file()
155+
file_path = FixtureHelpers.get_path("references_referenced.ex")
156+
{line, char} = {19, 5}
157+
158+
{:ok, request} = request(file_path, line, char)
159+
160+
annotate_assert(file_path, line, char, """
161+
referenced_macro a do
162+
^
163+
""")
164+
165+
{:reply, %Responses.GotoDefinition{result: definition}} = handle(request)
166+
167+
assert definition.uri == b_file.uri
168+
assert definition.range.start.line == 8
169+
assert definition.range.start.character == 11
170+
assert definition.range.end.line == 8
171+
assert definition.range.end.character == 11
172+
end
173+
174+
test "find definition variable" do
175+
b_file = arrange_referenced_file()
176+
file_path = FixtureHelpers.get_path("references_referenced.ex")
177+
{line, char} = {4, 13}
178+
179+
{:ok, request} = request(file_path, line, char)
180+
181+
annotate_assert(file_path, line, char, """
182+
IO.puts(referenced_variable + 1)
183+
^
184+
""")
185+
186+
{:reply, %Responses.GotoDefinition{result: definition}} = handle(request)
187+
188+
assert definition.uri == b_file.uri
189+
assert definition.range.start.line == 2
190+
assert definition.range.start.character == 4
191+
assert definition.range.end.line == 2
192+
assert definition.range.end.character == 4
193+
end
194+
195+
test "find definition attribute" do
196+
b_file = arrange_referenced_file()
197+
file_path = FixtureHelpers.get_path("references_referenced.ex")
198+
{line, char} = {27, 5}
199+
200+
{:ok, request} = request(file_path, line, char)
201+
202+
annotate_assert(file_path, line, char, """
203+
@referenced_attribute
204+
^
205+
""")
206+
207+
{:reply, %Responses.GotoDefinition{result: definition}} = handle(request)
208+
209+
assert definition.uri == b_file.uri
210+
assert definition.range.start.line == 24
211+
assert definition.range.start.character == 2
212+
assert definition.range.end.line == 24
213+
assert definition.range.end.character == 2
214+
end
215+
end

0 commit comments

Comments
 (0)