Skip to content

Commit e3278b8

Browse files
committed
wip
1 parent 27443d4 commit e3278b8

File tree

2 files changed

+66
-33
lines changed

2 files changed

+66
-33
lines changed

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

Lines changed: 65 additions & 32 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,22 @@ 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 = case module_expression do
89+
{_, location, _} -> location
90+
_ -> nil
91+
end
92+
{extract_module_name(module_expression), module_name_location, module_body}
8993
end
9094

9195
mod_defns =
@@ -105,7 +109,13 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
105109
:defprotocol -> :interface
106110
end
107111

108-
%Info{type: type, name: module_name, location: location, children: module_symbols}
112+
%Info{
113+
type: type,
114+
name: module_name,
115+
location: location,
116+
selection_location: module_name_location,
117+
children: module_symbols
118+
}
109119
end
110120

111121
# Protocol implementations
@@ -132,6 +142,8 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
132142
[]
133143
end
134144

145+
# TODO there is no end/closing metadata in the AST
146+
135147
%Info{
136148
type: :struct,
137149
name: "#{defname} #{module_name}",
@@ -144,7 +156,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
144156
defp extract_symbol(_, {:@, _, [{kind, _, _}]}) when kind in @supplementing_attributes, do: nil
145157

146158
# Types
147-
defp extract_symbol(_current_module, {:@, _, [{type_kind, location, type_expression}]})
159+
defp extract_symbol(_current_module, {:@, location, [{type_kind, type_head_location, type_expression}]})
148160
when type_kind in [:type, :typep, :opaque, :callback, :macrocallback] do
149161
type_name =
150162
case type_expression do
@@ -162,26 +174,27 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
162174
type: type,
163175
name: "@#{type_kind} #{type_name}",
164176
location: location,
177+
selection_location: type_head_location,
165178
children: []
166179
}
167180
end
168181

169182
# @behaviour BehaviourModule
170-
defp extract_symbol(_current_module, {:@, _, [{:behaviour, location, [behaviour_expression]}]}) do
183+
defp extract_symbol(_current_module, {:@, location, [{:behaviour, _, [behaviour_expression]}]}) do
171184
module_name = extract_module_name(behaviour_expression)
172185

173186
%Info{type: :interface, name: "@behaviour #{module_name}", location: location, children: []}
174187
end
175188

176189
# Other attributes
177-
defp extract_symbol(_current_module, {:@, _, [{name, location, _}]}) do
190+
defp extract_symbol(_current_module, {:@, location, [{name, _, _}]}) do
178191
%Info{type: :constant, name: "@#{name}", location: location, children: []}
179192
end
180193

181194
# Function, macro, guard with when
182195
defp extract_symbol(
183196
_current_module,
184-
{defname, _, [{:when, _, [{_, location, _} = fn_head, _]} | _]}
197+
{defname, location, [{:when, _, [{_, head_location, _} = fn_head, _]} | _]}
185198
)
186199
when defname in @defs do
187200
name = Macro.to_string(fn_head) |> String.replace("\n", "")
@@ -190,34 +203,45 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
190203
type: :function,
191204
name: "#{defname} #{name}",
192205
location: location,
206+
selection_location: head_location,
193207
children: []
194208
}
195209
end
196210

197211
# Function, macro, delegate
198-
defp extract_symbol(_current_module, {defname, _, [{_, location, _} = fn_head | _]})
212+
defp extract_symbol(_current_module, {defname, location, [{_, head_location, _} = fn_head | _]})
199213
when defname in @defs do
200214
name = Macro.to_string(fn_head) |> String.replace("\n", "")
201215

202216
%Info{
203217
type: :function,
204218
name: "#{defname} #{name}",
205219
location: location,
220+
selection_location: head_location,
206221
children: []
207222
}
208223
end
209224

210225
defp extract_symbol(
211226
_current_module,
212-
{{:., location, [{:__aliases__, _, [:Record]}, :defrecord]}, _, [record_name, _]}
227+
{{:., _, [{:__aliases__, alias_location, [:Record]}, :defrecord]}, location, [record_name, properties]}
213228
) do
214229
name = Macro.to_string(record_name) |> String.replace("\n", "")
215230

231+
children =
232+
if is_list(properties) do
233+
properties
234+
|> Enum.map(&extract_property(&1, location))
235+
|> Enum.reject(&is_nil/1)
236+
else
237+
[]
238+
end
239+
216240
%Info{
217241
type: :class,
218242
name: "defrecord #{name}",
219-
location: location,
220-
children: []
243+
location: location |> Keyword.merge(Keyword.take(alias_location, [:line, :column])),
244+
children: children
221245
}
222246
end
223247

@@ -269,21 +293,20 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
269293
keys =
270294
case config_entry do
271295
list when is_list(list) ->
272-
list
273-
|> Enum.map(fn {key, _} -> Macro.to_string(key) end)
296+
string_list = list
297+
|> Enum.map_join(", ", fn {key, _} -> Macro.to_string(key) end)
298+
"[#{string_list}]"
274299

275300
key ->
276-
[Macro.to_string(key)]
301+
Macro.to_string(key)
277302
end
278303

279-
for key <- keys do
280-
%Info{
281-
type: :key,
282-
name: "config :#{app} #{key}",
283-
location: location,
284-
children: []
285-
}
286-
end
304+
%Info{
305+
type: :key,
306+
name: "config :#{app} #{keys}",
307+
location: location,
308+
children: []
309+
}
287310
end
288311

289312
defp extract_symbol(_, _), do: nil
@@ -292,13 +315,11 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
292315
do: Enum.map(info, &build_symbol_information_hierarchical(uri, text, &1))
293316

294317
defp build_symbol_information_hierarchical(uri, text, %Info{} = info) do
295-
range = location_to_range(info.location, text)
296-
297318
%Protocol.DocumentSymbol{
298319
name: info.name,
299320
kind: SymbolUtils.symbol_kind_to_code(info.type),
300-
range: range,
301-
selectionRange: range,
321+
range: location_to_range(info.location, text),
322+
selectionRange: location_to_range(info.selection_location || info.location, text),
302323
children: build_symbol_information_hierarchical(uri, text, info.children)
303324
}
304325
end
@@ -338,10 +359,22 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
338359
end
339360

340361
defp location_to_range(location, text) do
341-
{line, character} =
362+
{start_line, start_character} =
342363
SourceFile.elixir_position_to_lsp(text, {location[:line], location[:column]})
343364

344-
Protocol.range(line, character, line, character)
365+
{end_line, end_character} = cond do
366+
end_location = location[:end_of_expression] ->
367+
SourceFile.elixir_position_to_lsp(text, {end_location[:line], end_location[:column]})
368+
end_location = location[:end] ->
369+
SourceFile.elixir_position_to_lsp(text, {end_location[:line], end_location[:column] + 3})
370+
end_location = location[:closing] ->
371+
# all closing tags we expect hera are 1 char width
372+
SourceFile.elixir_position_to_lsp(text, {end_location[:line], end_location[:column] + 1})
373+
true ->
374+
{start_line, start_character}
375+
end
376+
377+
Protocol.range(start_line, start_character, end_line, end_character)
345378
end
346379

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

apps/language_server/test/providers/document_symbols_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3032,7 +3032,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbolsTest do
30323032
name: "MISSING_PROTOCOL_NAME",
30333033
range: %{
30343034
"start" => %{"line" => 0, "character" => 0},
3035-
"end" => %{"line" => 0, "character" => 0}
3035+
"end" => %{"line" => 1, "character" => 3}
30363036
},
30373037
selectionRange: %{
30383038
"start" => %{"line" => 0, "character" => 0},

0 commit comments

Comments
 (0)