@@ -11,18 +11,20 @@ defmodule ElixirLS.LanguageServer.Location do
11
11
12
12
alias ElixirSense.Core.Metadata
13
13
alias ElixirSense.Core.Parser
14
- alias ElixirSense.Core.Source
15
- alias ElixirSense.Core.State.ModFunInfo
14
+ alias ElixirSense.Core.State . { ModFunInfo , TypeInfo }
16
15
alias ElixirSense.Core.Normalized.Code , as: CodeNormalized
17
16
require ElixirSense.Core.Introspection , as: Introspection
17
+ alias ElixirLS.LanguageServer.Location.Erl
18
18
19
19
@ type t :: % __MODULE__ {
20
20
type: :module | :function | :variable | :typespec | :macro | :attribute ,
21
21
file: String . t ( ) | nil ,
22
22
line: pos_integer ,
23
- column: pos_integer
23
+ column: pos_integer ,
24
+ end_line: pos_integer ,
25
+ end_column: pos_integer
24
26
}
25
- defstruct [ :type , :file , :line , :column ]
27
+ defstruct [ :type , :file , :line , :column , :end_line , :end_column ]
26
28
27
29
@ spec find_mod_fun_source ( module , atom , non_neg_integer | { :gte , non_neg_integer } | :any ) ::
28
30
t ( ) | nil
@@ -111,86 +113,93 @@ defmodule ElixirLS.LanguageServer.Location do
111
113
# module docs point to the begin of a file
112
114
# we get better results by regex
113
115
# the downside is we don't handle arity well
114
- find_fun_position_in_erl_file ( file , fun )
116
+ # TODO check if this is still the case on OTP 27+
117
+ if fun != nil do
118
+ range = Erl . find_fun_range ( file , fun )
119
+
120
+ if range do
121
+ { range , :function }
122
+ end
123
+ else
124
+ range = Erl . find_module_range ( file , mod )
125
+
126
+ if range do
127
+ { range , :module }
128
+ end
129
+ end
115
130
else
116
131
% Metadata { mods_funs_to_positions: mods_funs_to_positions } =
117
132
Parser . parse_file ( file , false , false , nil )
118
133
119
134
case get_function_position_using_metadata ( mod , fun , arity , mods_funs_to_positions ) do
120
- % ModFunInfo { } = mi ->
121
- # assume function head or first clause is last in metadata
122
- { List . last ( mi . positions ) , ModFunInfo . get_category ( mi ) }
123
-
124
135
nil ->
125
136
# not found in metadata, fall back to docs
126
137
get_function_position_using_docs ( mod , fun , arity )
138
+
139
+ % ModFunInfo { } = info ->
140
+ { info_to_range ( info ) , ModFunInfo . get_category ( info ) }
127
141
end
128
142
end
129
143
130
144
case result do
145
+ { { { line , column } , { end_line , end_column } } , category } ->
146
+ % __MODULE__ {
147
+ type: category ,
148
+ file: file ,
149
+ line: line ,
150
+ column: column ,
151
+ end_line: end_line ,
152
+ end_column: end_column
153
+ }
154
+
155
+ # TODO remove
131
156
{ { line , column } , category } ->
132
- % __MODULE__ { type: category , file: file , line: line , column: column }
157
+ % __MODULE__ {
158
+ type: category ,
159
+ file: file ,
160
+ line: line ,
161
+ column: column ,
162
+ end_line: line ,
163
+ end_column: column
164
+ }
133
165
134
166
_ ->
135
167
nil
136
168
end
137
169
end
138
170
139
- defp find_fun_position_in_erl_file ( file , nil ) do
140
- case find_line_by_regex ( file , ~r/ ^-module/ u ) do
141
- nil -> nil
142
- position -> { position , :module }
143
- end
144
- end
145
-
146
- defp find_fun_position_in_erl_file ( file , name ) do
147
- escaped =
148
- name
149
- |> Atom . to_string ( )
150
- |> Regex . escape ( )
151
-
152
- case find_line_by_regex ( file , ~r/ ^#{ escaped } \b \( / u ) do
153
- nil -> nil
154
- position -> { position , :function }
155
- end
156
- end
157
-
158
- defp find_type_position_in_erl_file ( file , name ) do
159
- escaped =
160
- name
161
- |> Atom . to_string ( )
162
- |> Regex . escape ( )
163
-
164
- find_line_by_regex ( file , ~r/ ^-(typep?|opaque)\s #{ escaped } \b / u )
165
- end
166
-
167
- defp find_line_by_regex ( file , regex ) do
168
- index =
169
- file
170
- |> File . read! ( )
171
- |> Source . split_lines ( )
172
- |> Enum . find_index ( & String . match? ( & 1 , regex ) )
173
-
174
- case index do
175
- nil -> nil
176
- i -> { i + 1 , 1 }
177
- end
178
- end
179
-
180
171
defp find_type_position ( _ , nil , _ ) , do: nil
181
172
182
173
defp find_type_position ( { mod , file } , name , arity ) do
183
174
result =
184
175
if String . ends_with? ( file , ".erl" ) do
185
- find_type_position_in_erl_file ( file , name )
176
+ Erl . find_type_range ( file , name )
186
177
else
187
178
file_metadata = Parser . parse_file ( file , false , false , nil )
188
179
get_type_position ( file_metadata , mod , name , arity )
189
180
end
190
181
191
182
case result do
183
+ { { line , column } , { end_line , end_column } } ->
184
+ % __MODULE__ {
185
+ type: :typespec ,
186
+ file: file ,
187
+ line: line ,
188
+ column: column ,
189
+ end_line: end_line ,
190
+ end_column: end_column
191
+ }
192
+
193
+ # TODO remove
192
194
{ line , column } ->
193
- % __MODULE__ { type: :typespec , file: file , line: line , column: column }
195
+ % __MODULE__ {
196
+ type: :typespec ,
197
+ file: file ,
198
+ line: line ,
199
+ column: column ,
200
+ end_line: line ,
201
+ end_column: column
202
+ }
194
203
195
204
_ ->
196
205
nil
@@ -242,72 +251,58 @@ defmodule ElixirLS.LanguageServer.Location do
242
251
false
243
252
end )
244
253
|> Enum . map ( fn
245
- { { category , _function , _arity } , line , _ , _ , _ } when is_integer ( line ) ->
246
- { { line , 1 } , category }
247
-
248
- { { category , _function , _arity } , keyword , _ , _ , _ } when is_list ( keyword ) ->
249
- { { Keyword . get ( keyword , :location , 1 ) , 1 } , category }
254
+ { { category , _function , _arity } , anno , _ , _ , _ } ->
255
+ { anno_to_range ( anno ) , category }
256
+ end )
257
+ |> Enum . min_by ( fn { { position , _end_position } , _category } -> position end , & <= / 2 , fn ->
258
+ nil
250
259
end )
251
- |> Enum . min_by ( fn { { line , 1 } , _category } -> line end , & <= / 2 , fn -> nil end )
252
260
end
253
261
end
254
262
255
- def get_type_position ( metadata , module , type , arity ) do
263
+ defp get_type_position ( metadata , module , type , arity ) do
256
264
case get_type_position_using_metadata ( module , type , arity , metadata . types ) do
257
265
nil ->
258
266
get_type_position_using_docs ( module , type , arity )
259
267
260
- % ElixirSense.Core.State. TypeInfo{ positions: positions } ->
261
- List . last ( positions )
268
+ % TypeInfo { } = info ->
269
+ info_to_range ( info )
262
270
end
263
271
end
264
272
265
- def get_type_position_using_docs ( module , type_name , arity ) do
273
+ defp get_type_position_using_docs ( module , type_name , arity ) do
266
274
case CodeNormalized . fetch_docs ( module ) do
267
275
{ :error , _ } ->
268
276
nil
269
277
270
278
{ _ , _ , _ , _ , _ , _ , docs } ->
271
279
docs
272
280
|> Enum . filter ( fn
273
- { { :type , ^ type_name , doc_arity } , _line , _ , _ , _meta } ->
281
+ { { :type , ^ type_name , doc_arity } , _ , _ , _ , _meta } ->
274
282
Introspection . matches_arity? ( doc_arity , arity )
275
283
276
284
_ ->
277
285
false
278
286
end )
279
287
|> Enum . map ( fn
280
- { { _category , _function , _arity } , line , _ , _ , _ } when is_integer ( line ) ->
281
- { line , 1 }
282
-
283
- { { _category , _function , _arity } , keyword , _ , _ , _ } when is_list ( keyword ) ->
284
- { Keyword . get ( keyword , :location , 1 ) , 1 }
288
+ { { _category , _function , _arity } , anno , _ , _ , _ } ->
289
+ anno_to_range ( anno )
285
290
end )
286
- |> Enum . min_by ( fn { line , 1 } -> line end , & <= / 2 , fn -> nil end )
291
+ |> Enum . min_by ( fn { position , _end_position } -> position end , & <= / 2 , fn -> nil end )
287
292
end
288
293
end
289
294
290
- def get_function_position_using_metadata (
291
- mod ,
292
- fun ,
293
- call_arity ,
294
- mods_funs_to_positions ,
295
- predicate \\ fn _ -> true end
296
- )
297
-
295
+ # TODO move to Metadata
298
296
def get_function_position_using_metadata (
299
297
mod ,
300
298
nil ,
301
299
_call_arity ,
302
- mods_funs_to_positions ,
303
- predicate
300
+ mods_funs_to_positions
304
301
) do
305
302
mods_funs_to_positions
306
303
|> Enum . find_value ( fn
307
304
{ { ^ mod , nil , nil } , fun_info } ->
308
- if predicate . ( fun_info ) do
309
- fun_info
310
- end
305
+ fun_info
311
306
312
307
_ ->
313
308
false
@@ -318,30 +313,29 @@ defmodule ElixirLS.LanguageServer.Location do
318
313
mod ,
319
314
fun ,
320
315
call_arity ,
321
- mods_funs_to_positions ,
322
- predicate
316
+ mods_funs_to_positions
323
317
) do
324
318
mods_funs_to_positions
325
319
|> Enum . filter ( fn
326
320
{ { ^ mod , ^ fun , fn_arity } , fun_info } when not is_nil ( fn_arity ) ->
327
321
# assume function head is first in code and last in metadata
328
322
default_args = fun_info . params |> Enum . at ( - 1 ) |> Introspection . count_defaults ( )
329
323
330
- Introspection . matches_arity_with_defaults? ( fn_arity , default_args , call_arity ) and
331
- predicate . ( fun_info )
324
+ Introspection . matches_arity_with_defaults? ( fn_arity , default_args , call_arity )
332
325
333
326
_ ->
334
327
false
335
328
end )
336
329
|> min_by_line
337
330
end
338
331
339
- def get_type_position_using_metadata ( mod , fun , call_arity , types , predicate \\ fn _ -> true end ) do
332
+ # TODO move to Metadata
333
+ def get_type_position_using_metadata ( mod , fun , call_arity , types ) do
340
334
types
341
335
|> Enum . filter ( fn
342
- { { ^ mod , ^ fun , type_arity } , type_info }
336
+ { { ^ mod , ^ fun , type_arity } , _type_info }
343
337
when not is_nil ( type_arity ) and Introspection . matches_arity? ( type_arity , call_arity ) ->
344
- predicate . ( type_info )
338
+ true
345
339
346
340
_ ->
347
341
false
@@ -365,4 +359,30 @@ defmodule ElixirLS.LanguageServer.Location do
365
359
nil -> nil
366
360
end
367
361
end
362
+
363
+ defp anno_to_range ( anno ) do
364
+ line = :erl_anno . line ( anno )
365
+
366
+ column =
367
+ case :erl_anno . column ( anno ) do
368
+ :undefined -> 1
369
+ column -> column
370
+ end
371
+
372
+ { end_line , end_column } =
373
+ case :erl_anno . end_location ( anno ) do
374
+ :undefined -> { line , column }
375
+ { line , column } -> { line , column }
376
+ line -> { line , 1 }
377
+ end
378
+
379
+ { { line , column } , { end_line , end_column } }
380
+ end
381
+
382
+ def info_to_range ( % { positions: positions , end_positions: end_positions } ) do
383
+ begin_position = List . last ( positions )
384
+ end_position = List . last ( end_positions ) || begin_position
385
+
386
+ { begin_position , end_position }
387
+ end
368
388
end
0 commit comments