@@ -10,7 +10,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
10
10
require ElixirLS.LanguageServer.Protocol , as: Protocol
11
11
12
12
defmodule Info do
13
- defstruct [ :type , :name , :location , :children ]
13
+ defstruct [ :type , :name , :location , :children , :selection_location ]
14
14
end
15
15
16
16
@ defs [ :def , :defp , :defmacro , :defmacrop , :defguard , :defguardp , :defdelegate ]
@@ -49,7 +49,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
49
49
end
50
50
51
51
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
53
53
{ :ok , quoted_form } -> { :ok , extract_modules ( quoted_form ) }
54
54
{ :error , _error } -> { :error , :compilation_error }
55
55
end
@@ -74,18 +74,22 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
74
74
# Modules, protocols
75
75
defp extract_symbol ( _module_name , { defname , location , arguments } )
76
76
when defname in [ :defmodule , :defprotocol ] do
77
- { module_name , module_body } =
77
+ { module_name , module_name_location , module_body } =
78
78
case arguments do
79
79
# Handles `defmodule do\nend` type compile errors
80
80
[ [ do: module_body ] ] ->
81
81
# The LSP requires us to return a non-empty name
82
82
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 }
85
85
end
86
86
87
87
[ 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 }
89
93
end
90
94
91
95
mod_defns =
@@ -105,7 +109,13 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
105
109
:defprotocol -> :interface
106
110
end
107
111
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
+ }
109
119
end
110
120
111
121
# Protocol implementations
@@ -132,6 +142,8 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
132
142
[ ]
133
143
end
134
144
145
+ # TODO there is no end/closing metadata in the AST
146
+
135
147
% Info {
136
148
type: :struct ,
137
149
name: "#{ defname } #{ module_name } " ,
@@ -144,7 +156,7 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
144
156
defp extract_symbol ( _ , { :@ , _ , [ { kind , _ , _ } ] } ) when kind in @ supplementing_attributes , do: nil
145
157
146
158
# 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 } ] } )
148
160
when type_kind in [ :type , :typep , :opaque , :callback , :macrocallback ] do
149
161
type_name =
150
162
case type_expression do
@@ -162,26 +174,27 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
162
174
type: type ,
163
175
name: "@#{ type_kind } #{ type_name } " ,
164
176
location: location ,
177
+ selection_location: type_head_location ,
165
178
children: [ ]
166
179
}
167
180
end
168
181
169
182
# @behaviour BehaviourModule
170
- defp extract_symbol ( _current_module , { :@ , _ , [ { :behaviour , location , [ behaviour_expression ] } ] } ) do
183
+ defp extract_symbol ( _current_module , { :@ , location , [ { :behaviour , _ , [ behaviour_expression ] } ] } ) do
171
184
module_name = extract_module_name ( behaviour_expression )
172
185
173
186
% Info { type: :interface , name: "@behaviour #{ module_name } " , location: location , children: [ ] }
174
187
end
175
188
176
189
# Other attributes
177
- defp extract_symbol ( _current_module , { :@ , _ , [ { name , location , _ } ] } ) do
190
+ defp extract_symbol ( _current_module , { :@ , location , [ { name , _ , _ } ] } ) do
178
191
% Info { type: :constant , name: "@#{ name } " , location: location , children: [ ] }
179
192
end
180
193
181
194
# Function, macro, guard with when
182
195
defp extract_symbol (
183
196
_current_module ,
184
- { defname , _ , [ { :when , _ , [ { _ , location , _ } = fn_head , _ ] } | _ ] }
197
+ { defname , location , [ { :when , _ , [ { _ , head_location , _ } = fn_head , _ ] } | _ ] }
185
198
)
186
199
when defname in @ defs do
187
200
name = Macro . to_string ( fn_head ) |> String . replace ( "\n " , "" )
@@ -190,34 +203,45 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
190
203
type: :function ,
191
204
name: "#{ defname } #{ name } " ,
192
205
location: location ,
206
+ selection_location: head_location ,
193
207
children: [ ]
194
208
}
195
209
end
196
210
197
211
# 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 | _ ] } )
199
213
when defname in @ defs do
200
214
name = Macro . to_string ( fn_head ) |> String . replace ( "\n " , "" )
201
215
202
216
% Info {
203
217
type: :function ,
204
218
name: "#{ defname } #{ name } " ,
205
219
location: location ,
220
+ selection_location: head_location ,
206
221
children: [ ]
207
222
}
208
223
end
209
224
210
225
defp extract_symbol (
211
226
_current_module ,
212
- { { :. , location , [ { :__aliases__ , _ , [ :Record ] } , :defrecord ] } , _ , [ record_name , _ ] }
227
+ { { :. , _ , [ { :__aliases__ , alias_location , [ :Record ] } , :defrecord ] } , location , [ record_name , properties ] }
213
228
) do
214
229
name = Macro . to_string ( record_name ) |> String . replace ( "\n " , "" )
215
230
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
+
216
240
% Info {
217
241
type: :class ,
218
242
name: "defrecord #{ name } " ,
219
- location: location ,
220
- children: [ ]
243
+ location: location |> Keyword . merge ( Keyword . take ( alias_location , [ :line , :column ] ) ) ,
244
+ children: children
221
245
}
222
246
end
223
247
@@ -269,21 +293,20 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
269
293
keys =
270
294
case config_entry do
271
295
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 } ]"
274
299
275
300
key ->
276
- [ Macro . to_string ( key ) ]
301
+ Macro . to_string ( key )
277
302
end
278
303
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
+ }
287
310
end
288
311
289
312
defp extract_symbol ( _ , _ ) , do: nil
@@ -292,13 +315,11 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
292
315
do: Enum . map ( info , & build_symbol_information_hierarchical ( uri , text , & 1 ) )
293
316
294
317
defp build_symbol_information_hierarchical ( uri , text , % Info { } = info ) do
295
- range = location_to_range ( info . location , text )
296
-
297
318
% Protocol.DocumentSymbol {
298
319
name: info . name ,
299
320
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 ) ,
302
323
children: build_symbol_information_hierarchical ( uri , text , info . children )
303
324
}
304
325
end
@@ -338,10 +359,22 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
338
359
end
339
360
340
361
defp location_to_range ( location , text ) do
341
- { line , character } =
362
+ { start_line , start_character } =
342
363
SourceFile . elixir_position_to_lsp ( text , { location [ :line ] , location [ :column ] } )
343
364
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 )
345
378
end
346
379
347
380
defp extract_module_name ( protocol: protocol , implementations: implementations ) do
0 commit comments