@@ -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,25 @@ 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 =
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 }
89
96
end
90
97
91
98
mod_defns =
@@ -105,7 +112,13 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
105
112
:defprotocol -> :interface
106
113
end
107
114
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
+ }
109
122
end
110
123
111
124
# Protocol implementations
@@ -132,6 +145,8 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
132
145
[ ]
133
146
end
134
147
148
+ # TODO there is no end/closing metadata in the AST
149
+
135
150
% Info {
136
151
type: :struct ,
137
152
name: "#{ defname } #{ module_name } " ,
@@ -144,44 +159,48 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
144
159
defp extract_symbol ( _ , { :@ , _ , [ { kind , _ , _ } ] } ) when kind in @ supplementing_attributes , do: nil
145
160
146
161
# Types
147
- defp extract_symbol ( _current_module , { :@ , _ , [ { type_kind , location , type_expression } ] } )
162
+ defp extract_symbol ( _current_module , { :@ , location , [ { type_kind , _ , type_expression } ] } )
148
163
when type_kind in [ :type , :typep , :opaque , :callback , :macrocallback ] do
149
- type_name =
164
+ { type_name , type_head_location } =
150
165
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 }
153
168
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 }
156
171
end
172
+
173
+ type_name =
174
+ type_name
157
175
|> String . replace ( "\n " , "" )
158
176
159
177
type = if type_kind in [ :type , :typep , :opaque ] , do: :class , else: :event
160
-
178
+ # TODO no closing metdata in type expressions
161
179
% Info {
162
180
type: type ,
163
181
name: "@#{ type_kind } #{ type_name } " ,
164
182
location: location ,
183
+ selection_location: type_head_location ,
165
184
children: [ ]
166
185
}
167
186
end
168
187
169
188
# @behaviour BehaviourModule
170
- defp extract_symbol ( _current_module , { :@ , _ , [ { :behaviour , location , [ behaviour_expression ] } ] } ) do
189
+ defp extract_symbol ( _current_module , { :@ , location , [ { :behaviour , _ , [ behaviour_expression ] } ] } ) do
171
190
module_name = extract_module_name ( behaviour_expression )
172
191
173
192
% Info { type: :interface , name: "@behaviour #{ module_name } " , location: location , children: [ ] }
174
193
end
175
194
176
195
# Other attributes
177
- defp extract_symbol ( _current_module , { :@ , _ , [ { name , location , _ } ] } ) do
196
+ defp extract_symbol ( _current_module , { :@ , location , [ { name , _ , _ } ] } ) do
178
197
% Info { type: :constant , name: "@#{ name } " , location: location , children: [ ] }
179
198
end
180
199
181
200
# Function, macro, guard with when
182
201
defp extract_symbol (
183
202
_current_module ,
184
- { defname , _ , [ { :when , _ , [ { _ , location , _ } = fn_head , _ ] } | _ ] }
203
+ { defname , location , [ { :when , _ , [ { _ , head_location , _ } = fn_head , _ ] } | _ ] }
185
204
)
186
205
when defname in @ defs do
187
206
name = Macro . to_string ( fn_head ) |> String . replace ( "\n " , "" )
@@ -190,34 +209,46 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
190
209
type: :function ,
191
210
name: "#{ defname } #{ name } " ,
192
211
location: location ,
212
+ selection_location: head_location ,
193
213
children: [ ]
194
214
}
195
215
end
196
216
197
217
# 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 | _ ] } )
199
219
when defname in @ defs do
200
220
name = Macro . to_string ( fn_head ) |> String . replace ( "\n " , "" )
201
221
202
222
% Info {
203
223
type: :function ,
204
224
name: "#{ defname } #{ name } " ,
205
225
location: location ,
226
+ selection_location: head_location ,
206
227
children: [ ]
207
228
}
208
229
end
209
230
210
231
defp extract_symbol (
211
232
_current_module ,
212
- { { :. , location , [ { :__aliases__ , _ , [ :Record ] } , :defrecord ] } , _ , [ record_name , _ ] }
233
+ { { :. , _ , [ { :__aliases__ , alias_location , [ :Record ] } , :defrecord ] } , location ,
234
+ [ record_name , properties ] }
213
235
) do
214
236
name = Macro . to_string ( record_name ) |> String . replace ( "\n " , "" )
215
237
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
+
216
247
% Info {
217
248
type: :class ,
218
249
name: "defrecord #{ name } " ,
219
- location: location ,
220
- children: [ ]
250
+ location: location |> Keyword . merge ( Keyword . take ( alias_location , [ :line , :column ] ) ) ,
251
+ children: children
221
252
}
222
253
end
223
254
@@ -269,21 +300,22 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
269
300
keys =
270
301
case config_entry do
271
302
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 } ]"
274
308
275
309
key ->
276
- [ Macro . to_string ( key ) ]
310
+ Macro . to_string ( key )
277
311
end
278
312
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
+ }
287
319
end
288
320
289
321
defp extract_symbol ( _ , _ ) , do: nil
@@ -292,13 +324,11 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
292
324
do: Enum . map ( info , & build_symbol_information_hierarchical ( uri , text , & 1 ) )
293
325
294
326
defp build_symbol_information_hierarchical ( uri , text , % Info { } = info ) do
295
- range = location_to_range ( info . location , text )
296
-
297
327
% Protocol.DocumentSymbol {
298
328
name: info . name ,
299
329
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 ) ,
302
332
children: build_symbol_information_hierarchical ( uri , text , info . children )
303
333
}
304
334
end
@@ -338,10 +368,32 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
338
368
end
339
369
340
370
defp location_to_range ( location , text ) do
341
- { line , character } =
371
+ { start_line , start_character } =
342
372
SourceFile . elixir_position_to_lsp ( text , { location [ :line ] , location [ :column ] } )
343
373
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 )
345
397
end
346
398
347
399
defp extract_module_name ( protocol: protocol , implementations: implementations ) do
0 commit comments