@@ -31,111 +31,270 @@ defmodule ElixirLS.LanguageServer.Providers.DocumentSymbols do
31
31
type_parameter: 26
32
32
}
33
33
34
+ @ defs [ :def , :defp , :defmacro , :defmacrop , :defguard , :defguardp , :defdelegate ]
35
+
36
+ @ docs [
37
+ :doc ,
38
+ :moduledoc ,
39
+ :typedoc
40
+ ]
41
+
34
42
def symbols ( uri , text ) do
35
43
symbols = list_symbols ( text ) |> Enum . map ( & build_symbol_information ( uri , & 1 ) )
36
44
{ :ok , symbols }
37
45
end
38
46
39
47
defp list_symbols ( src ) do
40
- { _ast , symbol_list } =
41
- Code . string_to_quoted! ( src , columns: true , line: 0 )
42
- |> Macro . prewalk ( [ ] , fn ast , symbols ->
43
- { ast , extract_module ( ast ) ++ symbols }
44
- end )
45
-
46
- symbol_list
48
+ Code . string_to_quoted! ( src , columns: true , line: 0 )
49
+ |> extract_modules ( )
47
50
end
48
51
49
52
# Identify and extract the module symbol, and the symbols contained within the module
50
- defp extract_module ( { :defmodule , _ , _child_ast } = ast ) do
51
- { _ , _ , [ { :__aliases__ , location , module_name } , [ do: module_body ] ] } = ast
53
+ defp extract_modules ( { :__block__ , [ ] , ast } ) do
54
+ ast |> Enum . map ( & extract_modules ( & 1 ) ) |> List . flatten ( )
55
+ end
56
+
57
+ defp extract_modules ( { defname , _ , _child_ast } = ast )
58
+ when defname in [ :defmodule , :defprotocol , :defimpl ] do
59
+ [ extract_symbol ( "" , ast ) ]
60
+ end
61
+
62
+ defp extract_modules ( { :config , _ , _ } = ast ) do
63
+ [ extract_symbol ( "" , ast ) ]
64
+ end
52
65
66
+ defp extract_modules ( _ast ) , do: [ ]
67
+
68
+ # Modules, protocols
69
+ defp extract_symbol ( _module_name , { defname , location , [ module_expression , [ do: module_body ] ] } )
70
+ when defname in [ :defmodule , :defprotocol ] do
53
71
mod_defns =
54
72
case module_body do
55
73
{ :__block__ , [ ] , mod_defns } -> mod_defns
56
74
stmt -> [ stmt ]
57
75
end
58
76
59
- module_name = Enum . join ( module_name , "." )
77
+ module_name = extract_module_name ( module_expression )
60
78
61
79
module_symbols =
62
80
mod_defns
63
81
|> Enum . map ( & extract_symbol ( module_name , & 1 ) )
64
82
|> Enum . reject ( & is_nil / 1 )
65
83
66
- [ % { type: :module , name: module_name , location: location , container: nil } ] ++ module_symbols
84
+ type =
85
+ case defname do
86
+ :defmodule -> :module
87
+ :defprotocol -> :interface
88
+ end
89
+
90
+ % { type: type , name: module_name , location: location , children: module_symbols }
91
+ end
92
+
93
+ # Protocol implementations
94
+ defp extract_symbol (
95
+ module_name ,
96
+ { :defimpl , location , [ protocol_expression , [ for: for_expression ] , [ do: module_body ] ] }
97
+ ) do
98
+ extract_symbol (
99
+ module_name ,
100
+ { :defmodule , location ,
101
+ [ [ protocol: protocol_expression , implementations: for_expression ] , [ do: module_body ] ] }
102
+ )
103
+ end
104
+
105
+ # Struct and exception
106
+ defp extract_symbol ( _module_name , { defname , location , [ properties | _ ] } )
107
+ when defname in [ :defstruct , :defexception ] do
108
+ name =
109
+ case defname do
110
+ :defstruct -> "struct"
111
+ :defexception -> "exception"
112
+ end
113
+
114
+ children =
115
+ if is_list ( properties ) do
116
+ properties
117
+ |> Enum . map ( & extract_property ( & 1 , location ) )
118
+ |> Enum . reject ( & is_nil / 1 )
119
+ else
120
+ [ ]
121
+ end
122
+
123
+ % { type: :struct , name: name , location: location , children: children }
67
124
end
68
125
69
- defp extract_module ( _ast ) , do: [ ]
126
+ # Docs
127
+ defp extract_symbol ( _ , { :@ , _ , [ { kind , _ , _ } ] } ) when kind in @ docs , do: nil
70
128
71
- # Module Variable
72
- defp extract_symbol ( _ , { :@ , _ , [ { :moduledoc , _ , _ } ] } ) , do: nil
73
- defp extract_symbol ( _ , { :@ , _ , [ { :doc , _ , _ } ] } ) , do: nil
74
- defp extract_symbol ( _ , { :@ , _ , [ { :spec , _ , _ } ] } ) , do: nil
75
- defp extract_symbol ( _ , { :@ , _ , [ { :behaviour , _ , _ } ] } ) , do: nil
76
- defp extract_symbol ( _ , { :@ , _ , [ { :impl , _ , _ } ] } ) , do: nil
77
- defp extract_symbol ( _ , { :@ , _ , [ { :type , _ , _ } ] } ) , do: nil
78
- defp extract_symbol ( _ , { :@ , _ , [ { :typedoc , _ , _ } ] } ) , do: nil
79
- defp extract_symbol ( _ , { :@ , _ , [ { :enforce_keys , _ , _ } ] } ) , do: nil
129
+ # Types
130
+ defp extract_symbol ( _current_module , { :@ , _ , [ { type_kind , location , type_expression } ] } )
131
+ when type_kind in [ :type , :typep , :opaque , :spec , :callback , :macrocallback ] do
132
+ type_name =
133
+ case type_expression do
134
+ [ { :"::" , _ , [ { _ , _ , _ } = type_head | _ ] } ] ->
135
+ Macro . to_string ( type_head )
80
136
81
- defp extract_symbol ( current_module , { :@ , _ , [ { name , location , _ } ] } ) do
82
- % { type: :constant , name: "@#{ name } " , location: location , container: current_module }
137
+ [ { :when , _ , [ { :"::" , _ , [ { _ , _ , _ } = type_head , _ ] } , _ ] } ] ->
138
+ Macro . to_string ( type_head )
139
+ end
140
+
141
+ type = if type_kind in [ :type , :typep , :opaque ] , do: :class , else: :event
142
+
143
+ % {
144
+ type: type ,
145
+ name: type_name ,
146
+ location: location ,
147
+ children: [ ]
148
+ }
149
+ end
150
+
151
+ # Other attributes
152
+ defp extract_symbol ( _current_module , { :@ , _ , [ { name , location , _ } ] } ) do
153
+ % { type: :constant , name: "@#{ name } " , location: location , children: [ ] }
83
154
end
84
155
85
- # Function
86
- defp extract_symbol ( current_module , { :def , _ , [ { _ , location , _ } = fn_head | _ ] } ) do
156
+ # Function, macro, guard, delegate
157
+ defp extract_symbol ( _current_module , { defname , _ , [ { _ , location , _ } = fn_head | _ ] } )
158
+ when defname in @ defs do
87
159
% {
88
160
type: :function ,
89
161
name: Macro . to_string ( fn_head ) ,
90
162
location: location ,
91
- container: current_module
163
+ children: [ ]
92
164
}
93
165
end
94
166
95
- # Private Function
96
- defp extract_symbol ( current_module , { :defp , _ , [ { _ , location , _ } = fn_head | _ ] } ) do
167
+ # ExUnit test
168
+ defp extract_symbol ( _current_module , { :test , location , [ name | _ ] } ) do
97
169
% {
98
170
type: :function ,
99
- name: Macro . to_string ( fn_head ) ,
171
+ name: ~s ( test " #{ name } " ) ,
100
172
location: location ,
101
- container: current_module
173
+ children: [ ]
102
174
}
103
175
end
104
176
105
- # Macro
106
- defp extract_symbol ( current_module , { :defmacro , _ , [ { _ , location , _ } = fn_head | _ ] } ) do
177
+ # ExUnit setup and setup_all callbacks
178
+ defp extract_symbol ( _current_module , { name , location , [ _name | _ ] } )
179
+ when name in [ :setup , :setup_all ] do
107
180
% {
108
181
type: :function ,
109
- name: Macro . to_string ( fn_head ) ,
182
+ name: " #{ name } " ,
110
183
location: location ,
111
- container: current_module
184
+ children: [ ]
112
185
}
113
186
end
114
187
115
- # Test
116
- defp extract_symbol ( current_module , { :test , location , [ name | _ ] } ) do
188
+ # ExUnit describe
189
+ defp extract_symbol ( current_module , { :describe , location , [ name | ast ] } ) do
190
+ [ [ do: module_body ] ] = ast
191
+
192
+ mod_defns =
193
+ case module_body do
194
+ { :__block__ , [ ] , mod_defns } -> mod_defns
195
+ stmt -> [ stmt ]
196
+ end
197
+
198
+ module_symbols =
199
+ mod_defns
200
+ |> Enum . map ( & extract_symbol ( current_module , & 1 ) )
201
+ |> Enum . reject ( & is_nil / 1 )
202
+
117
203
% {
118
204
type: :function ,
119
- name: ~s( test "#{ name } ") ,
205
+ name: ~s( describe "#{ name } ") ,
120
206
location: location ,
121
- container: current_module
207
+ children: module_symbols
122
208
}
123
209
end
124
210
211
+ # Config entry
212
+ defp extract_symbol ( _current_module , { :config , location , [ app , config_entry | _ ] } )
213
+ when is_atom ( app ) do
214
+ keys =
215
+ case config_entry do
216
+ list when is_list ( list ) ->
217
+ list
218
+ |> Enum . map ( fn { key , _ } -> Macro . to_string ( key ) end )
219
+
220
+ key ->
221
+ [ Macro . to_string ( key ) ]
222
+ end
223
+
224
+ for key <- keys do
225
+ % {
226
+ type: :key ,
227
+ name: "config :#{ app } #{ key } " ,
228
+ location: location ,
229
+ children: [ ]
230
+ }
231
+ end
232
+ end
233
+
125
234
defp extract_symbol ( _ , _ ) , do: nil
126
235
236
+ defp build_symbol_information ( uri , info ) when is_list ( info ) ,
237
+ do: Enum . map ( info , & build_symbol_information ( uri , & 1 ) )
238
+
127
239
defp build_symbol_information ( uri , info ) do
128
240
% {
129
241
name: info . name ,
130
242
kind: @ symbol_enum [ info . type ] ,
131
- containerName: info . container ,
132
- location: % {
133
- uri: uri ,
134
- range: % {
135
- start: % { line: info . location [ :line ] , character: info . location [ :column ] - 1 } ,
136
- end: % { line: info . location [ :line ] , character: info . location [ :column ] - 1 }
137
- }
138
- }
243
+ range: location_to_range ( info . location ) ,
244
+ selectionRange: location_to_range ( info . location ) ,
245
+ children: build_symbol_information ( uri , info . children )
139
246
}
140
247
end
248
+
249
+ defp location_to_range ( location ) do
250
+ % {
251
+ start: % { line: location [ :line ] , character: location [ :column ] - 1 } ,
252
+ end: % { line: location [ :line ] , character: location [ :column ] - 1 }
253
+ }
254
+ end
255
+
256
+ defp extract_module_name ( protocol: protocol , implementations: implementations ) do
257
+ extract_module_name ( protocol ) <> ", for: " <> extract_module_name ( implementations )
258
+ end
259
+
260
+ defp extract_module_name ( list ) when is_list ( list ) do
261
+ list_stringified = list |> Enum . map_join ( ", " , & extract_module_name / 1 )
262
+
263
+ "[" <> list_stringified <> "]"
264
+ end
265
+
266
+ defp extract_module_name ( { :__aliases__ , location , [ { :__MODULE__ , _ , nil } = head | tail ] } ) do
267
+ extract_module_name ( head ) <> "." <> extract_module_name ( { :__aliases__ , location , tail } )
268
+ end
269
+
270
+ defp extract_module_name ( { :__aliases__ , _location , module_names } ) do
271
+ Enum . join ( module_names , "." )
272
+ end
273
+
274
+ defp extract_module_name ( { :__MODULE__ , _location , nil } ) do
275
+ "__MODULE__"
276
+ end
277
+
278
+ defp extract_module_name ( module ) when is_atom ( module ) do
279
+ case Atom . to_string ( module ) do
280
+ "Elixir." <> elixir_module_rest -> elixir_module_rest
281
+ erlang_module -> erlang_module
282
+ end
283
+ end
284
+
285
+ defp extract_module_name ( _ ) , do: "# unknown"
286
+
287
+ defp extract_property ( property_name , location ) when is_atom ( property_name ) do
288
+ % {
289
+ type: :property ,
290
+ name: "#{ property_name } " ,
291
+ location: location ,
292
+ children: [ ]
293
+ }
294
+ end
295
+
296
+ defp extract_property ( { property_name , _default } , location ) ,
297
+ do: extract_property ( property_name , location )
298
+
299
+ defp extract_property ( _ , _ ) , do: nil
141
300
end
0 commit comments