Skip to content

Commit d226e81

Browse files
committed
Merge branch 'document-symbol-location'
2 parents 539ba88 + d59b939 commit d226e81

File tree

5 files changed

+415
-954
lines changed

5 files changed

+415
-954
lines changed

apps/language_server/lib/language_server/build.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ defmodule ElixirLS.LanguageServer.Build do
5555
end
5656

5757
defp reload_project do
58-
mixfile = Path.absname(MixfileHelpers.mix_exs)
58+
mixfile = Path.absname(MixfileHelpers.mix_exs())
5959

6060
if File.exists?(mixfile) do
6161
# FIXME: Private API
@@ -112,7 +112,7 @@ defmodule ElixirLS.LanguageServer.Build do
112112
# it was added in https://github.com/elixir-lsp/elixir-ls/pull/227
113113
# removing it doesn't break tests and I'm not able to reproduce
114114
# https://github.com/elixir-lsp/elixir-ls/issues/209 on recent elixir (1.13)
115-
def load_all_mix_applications do
115+
defp load_all_mix_applications do
116116
apps =
117117
cond do
118118
Mix.Project.umbrella?() ->

apps/language_server/lib/language_server/diagnostics.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
238238

239239
%Mix.Task.Compiler.Diagnostic{
240240
compiler_name: "ElixirLS",
241-
file: Path.absname(MixfileHelpers.mix_exs),
241+
file: Path.absname(MixfileHelpers.mix_exs()),
242242
# 0 means unknown
243243
position: 0,
244244
message: msg,

apps/language_server/lib/language_server/dialyzer/analyzer.ex

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,22 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
2828
:warn_undefined_callbacks
2929
]
3030
@non_default_warns [
31-
:warn_contract_not_equal,
32-
:warn_contract_subtype,
33-
:warn_contract_supertype,
34-
:warn_return_only_exit,
35-
:warn_umatched_return,
36-
:warn_unknown
37-
] ++ (
38-
if String.to_integer(System.otp_release()) >= 25 do
39-
[
40-
# OTP >= 25 options
41-
:warn_contract_missing_return,
42-
:warn_contract_extra_return
43-
]
44-
else
45-
[]
46-
end
47-
)
31+
:warn_contract_not_equal,
32+
:warn_contract_subtype,
33+
:warn_contract_supertype,
34+
:warn_return_only_exit,
35+
:warn_umatched_return,
36+
:warn_unknown
37+
] ++
38+
(if String.to_integer(System.otp_release()) >= 25 do
39+
[
40+
# OTP >= 25 options
41+
:warn_contract_missing_return,
42+
:warn_contract_extra_return
43+
]
44+
else
45+
[]
46+
end)
4847
@log_cache_length 10
4948

5049
defstruct [

apps/language_server/lib/language_server/providers/document_symbols.ex

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
1010
require ElixirLS.LanguageServer.Protocol, as: Protocol
1111

1212
defmodule Info do
13-
defstruct [:type, :name, :location, :children]
13+
defstruct [:type, :name, :location, :children, :selection_location]
1414
end
1515

1616
@defs [:def, :defp, :defmacro, :defmacrop, :defguard, :defguardp, :defdelegate]
@@ -49,7 +49,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
4949
end
5050

5151
defp list_symbols(src) do
52-
case ElixirSense.string_to_quoted(src, 1, @max_parser_errors, line: 1) do
52+
case ElixirSense.string_to_quoted(src, 1, @max_parser_errors, line: 1, token_metadata: true) do
5353
{:ok, quoted_form} -> {:ok, extract_modules(quoted_form)}
5454
{:error, _error} -> {:error, :compilation_error}
5555
end
@@ -74,18 +74,25 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
7474
# Modules, protocols
7575
defp extract_symbol(_module_name, {defname, location, arguments})
7676
when defname in [:defmodule, :defprotocol] do
77-
{module_name, module_body} =
77+
{module_name, module_name_location, module_body} =
7878
case arguments do
7979
# Handles `defmodule do\nend` type compile errors
8080
[[do: module_body]] ->
8181
# The LSP requires us to return a non-empty name
8282
case defname do
83-
:defmodule -> {"MISSING_MODULE_NAME", module_body}
84-
:defprotocol -> {"MISSING_PROTOCOL_NAME", module_body}
83+
:defmodule -> {"MISSING_MODULE_NAME", nil, module_body}
84+
:defprotocol -> {"MISSING_PROTOCOL_NAME", nil, module_body}
8585
end
8686

8787
[module_expression, [do: module_body]] ->
88-
{extract_module_name(module_expression), module_body}
88+
module_name_location =
89+
case module_expression do
90+
{_, location, _} -> location
91+
_ -> nil
92+
end
93+
94+
# TODO extract module name location from Code.Fragment.surround_context?
95+
{extract_module_name(module_expression), module_name_location, module_body}
8996
end
9097

9198
mod_defns =
@@ -105,7 +112,13 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
105112
:defprotocol -> :interface
106113
end
107114

108-
%Info{type: type, name: module_name, location: location, children: module_symbols}
115+
%Info{
116+
type: type,
117+
name: module_name,
118+
location: location,
119+
selection_location: module_name_location,
120+
children: module_symbols
121+
}
109122
end
110123

111124
# Protocol implementations
@@ -132,6 +145,8 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
132145
[]
133146
end
134147

148+
# TODO there is no end/closing metadata in the AST
149+
135150
%Info{
136151
type: :struct,
137152
name: "#{defname} #{module_name}",
@@ -144,44 +159,48 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
144159
defp extract_symbol(_, {:@, _, [{kind, _, _}]}) when kind in @supplementing_attributes, do: nil
145160

146161
# Types
147-
defp extract_symbol(_current_module, {:@, _, [{type_kind, location, type_expression}]})
162+
defp extract_symbol(_current_module, {:@, location, [{type_kind, _, type_expression}]})
148163
when type_kind in [:type, :typep, :opaque, :callback, :macrocallback] do
149-
type_name =
164+
{type_name, type_head_location} =
150165
case type_expression do
151-
[{:"::", _, [{_, _, _} = type_head | _]}] ->
152-
Macro.to_string(type_head)
166+
[{:"::", _, [{_, type_head_location, _} = type_head | _]}] ->
167+
{Macro.to_string(type_head), type_head_location}
153168

154-
[{:when, _, [{:"::", _, [{_, _, _} = type_head, _]}, _]}] ->
155-
Macro.to_string(type_head)
169+
[{:when, _, [{:"::", _, [{_, type_head_location, _} = type_head, _]}, _]}] ->
170+
{Macro.to_string(type_head), type_head_location}
156171
end
172+
173+
type_name =
174+
type_name
157175
|> String.replace("\n", "")
158176

159177
type = if type_kind in [:type, :typep, :opaque], do: :class, else: :event
160-
178+
# TODO no closing metdata in type expressions
161179
%Info{
162180
type: type,
163181
name: "@#{type_kind} #{type_name}",
164182
location: location,
183+
selection_location: type_head_location,
165184
children: []
166185
}
167186
end
168187

169188
# @behaviour BehaviourModule
170-
defp extract_symbol(_current_module, {:@, _, [{:behaviour, location, [behaviour_expression]}]}) do
189+
defp extract_symbol(_current_module, {:@, location, [{:behaviour, _, [behaviour_expression]}]}) do
171190
module_name = extract_module_name(behaviour_expression)
172191

173192
%Info{type: :interface, name: "@behaviour #{module_name}", location: location, children: []}
174193
end
175194

176195
# Other attributes
177-
defp extract_symbol(_current_module, {:@, _, [{name, location, _}]}) do
196+
defp extract_symbol(_current_module, {:@, location, [{name, _, _}]}) do
178197
%Info{type: :constant, name: "@#{name}", location: location, children: []}
179198
end
180199

181200
# Function, macro, guard with when
182201
defp extract_symbol(
183202
_current_module,
184-
{defname, _, [{:when, _, [{_, location, _} = fn_head, _]} | _]}
203+
{defname, location, [{:when, _, [{_, head_location, _} = fn_head, _]} | _]}
185204
)
186205
when defname in @defs do
187206
name = Macro.to_string(fn_head) |> String.replace("\n", "")
@@ -190,34 +209,46 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
190209
type: :function,
191210
name: "#{defname} #{name}",
192211
location: location,
212+
selection_location: head_location,
193213
children: []
194214
}
195215
end
196216

197217
# Function, macro, delegate
198-
defp extract_symbol(_current_module, {defname, _, [{_, location, _} = fn_head | _]})
218+
defp extract_symbol(_current_module, {defname, location, [{_, head_location, _} = fn_head | _]})
199219
when defname in @defs do
200220
name = Macro.to_string(fn_head) |> String.replace("\n", "")
201221

202222
%Info{
203223
type: :function,
204224
name: "#{defname} #{name}",
205225
location: location,
226+
selection_location: head_location,
206227
children: []
207228
}
208229
end
209230

210231
defp extract_symbol(
211232
_current_module,
212-
{{:., location, [{:__aliases__, _, [:Record]}, :defrecord]}, _, [record_name, _]}
233+
{{:., _, [{:__aliases__, alias_location, [:Record]}, :defrecord]}, location,
234+
[record_name, properties]}
213235
) do
214236
name = Macro.to_string(record_name) |> String.replace("\n", "")
215237

238+
children =
239+
if is_list(properties) do
240+
properties
241+
|> Enum.map(&extract_property(&1, location))
242+
|> Enum.reject(&is_nil/1)
243+
else
244+
[]
245+
end
246+
216247
%Info{
217248
type: :class,
218249
name: "defrecord #{name}",
219-
location: location,
220-
children: []
250+
location: location |> Keyword.merge(Keyword.take(alias_location, [:line, :column])),
251+
children: children
221252
}
222253
end
223254

@@ -269,21 +300,22 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
269300
keys =
270301
case config_entry do
271302
list when is_list(list) ->
272-
list
273-
|> Enum.map(fn {key, _} -> Macro.to_string(key) end)
303+
string_list =
304+
list
305+
|> Enum.map_join(", ", fn {key, _} -> Macro.to_string(key) end)
306+
307+
"[#{string_list}]"
274308

275309
key ->
276-
[Macro.to_string(key)]
310+
Macro.to_string(key)
277311
end
278312

279-
for key <- keys do
280-
%Info{
281-
type: :key,
282-
name: "config :#{app} #{key}",
283-
location: location,
284-
children: []
285-
}
286-
end
313+
%Info{
314+
type: :key,
315+
name: "config :#{app} #{keys}",
316+
location: location,
317+
children: []
318+
}
287319
end
288320

289321
defp extract_symbol(_, _), do: nil
@@ -292,13 +324,11 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
292324
do: Enum.map(info, &build_symbol_information_hierarchical(uri, text, &1))
293325

294326
defp build_symbol_information_hierarchical(uri, text, %Info{} = info) do
295-
range = location_to_range(info.location, text)
296-
297327
%Protocol.DocumentSymbol{
298328
name: info.name,
299329
kind: SymbolUtils.symbol_kind_to_code(info.type),
300-
range: range,
301-
selectionRange: range,
330+
range: location_to_range(info.location, text),
331+
selectionRange: location_to_range(info.selection_location || info.location, text),
302332
children: build_symbol_information_hierarchical(uri, text, info.children)
303333
}
304334
end
@@ -338,10 +368,32 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
338368
end
339369

340370
defp location_to_range(location, text) do
341-
{line, character} =
371+
{start_line, start_character} =
342372
SourceFile.elixir_position_to_lsp(text, {location[:line], location[:column]})
343373

344-
Protocol.range(line, character, line, character)
374+
{end_line, end_character} =
375+
cond do
376+
end_location = location[:end_of_expression] ->
377+
SourceFile.elixir_position_to_lsp(text, {end_location[:line], end_location[:column]})
378+
379+
end_location = location[:end] ->
380+
SourceFile.elixir_position_to_lsp(
381+
text,
382+
{end_location[:line], end_location[:column] + 3}
383+
)
384+
385+
end_location = location[:closing] ->
386+
# all closing tags we expect hera are 1 char width
387+
SourceFile.elixir_position_to_lsp(
388+
text,
389+
{end_location[:line], end_location[:column] + 1}
390+
)
391+
392+
true ->
393+
{start_line, start_character}
394+
end
395+
396+
Protocol.range(start_line, start_character, end_line, end_character)
345397
end
346398

347399
defp extract_module_name(protocol: protocol, implementations: implementations) do

0 commit comments

Comments
 (0)