@@ -54,10 +54,10 @@ defmodule IEx.Autocomplete do
54
54
expand_dot_call ( path , List . to_atom ( hint ) , shell )
55
55
56
56
:expr ->
57
- expand_local_or_var ( "" , shell )
57
+ expand_struct_fields_or_local_or_var ( code , "" , shell )
58
58
59
59
{ :local_or_var , local_or_var } ->
60
- expand_local_or_var ( List . to_string ( local_or_var ) , shell )
60
+ expand_struct_fields_or_local_or_var ( code , List . to_string ( local_or_var ) , shell )
61
61
62
62
{ :local_arity , local } ->
63
63
expand_local ( List . to_string ( local ) , true , shell )
@@ -267,31 +267,71 @@ defmodule IEx.Autocomplete do
267
267
end
268
268
end
269
269
270
- ## Elixir modules
270
+ ## Structs
271
271
272
272
defp expand_structs ( hint , shell ) do
273
273
aliases =
274
274
for { alias , mod } <- aliases_from_env ( shell ) ,
275
275
[ name ] = Module . split ( alias ) ,
276
276
String . starts_with? ( name , hint ) ,
277
- has_struct ?( mod ) ,
277
+ struct ?( mod ) and not function_exported? ( mod , :exception , 1 ) ,
278
278
do: % { kind: :struct , name: name }
279
279
280
280
modules =
281
281
for "Elixir." <> name = full_name <- match_modules ( "Elixir." <> hint , true ) ,
282
282
String . starts_with? ( name , hint ) ,
283
283
mod = String . to_atom ( full_name ) ,
284
- has_struct ?( mod ) ,
284
+ struct ?( mod ) and not function_exported? ( mod , :exception , 1 ) ,
285
285
do: % { kind: :struct , name: name }
286
286
287
287
format_expansion ( aliases ++ modules , hint )
288
288
end
289
289
290
- defp has_struct? ( mod ) do
291
- Code . ensure_loaded? ( mod ) and function_exported? ( mod , :__struct__ , 1 ) and
292
- not function_exported? ( mod , :exception , 1 )
290
+ defp struct? ( mod ) do
291
+ Code . ensure_loaded? ( mod ) and function_exported? ( mod , :__struct__ , 1 )
292
+ end
293
+
294
+ defp expand_struct_fields_or_local_or_var ( code , hint , shell ) do
295
+ with { :ok , quoted } <- Code.Fragment . container_cursor_to_quoted ( code ) ,
296
+ { aliases , pairs } <- find_struct_fields ( quoted ) ,
297
+ { :ok , alias } <- value_from_alias ( aliases , shell ) ,
298
+ true <- struct? ( alias ) do
299
+ pairs =
300
+ Enum . reduce ( pairs , Map . from_struct ( alias . __struct__ ) , fn { key , _ } , map ->
301
+ Map . delete ( map , key )
302
+ end )
303
+
304
+ entries =
305
+ for { key , _value } <- pairs ,
306
+ name = Atom . to_string ( key ) ,
307
+ if ( hint == "" ,
308
+ do: not String . starts_with? ( name , "_" ) ,
309
+ else: String . starts_with? ( name , hint )
310
+ ) ,
311
+ do: % { kind: :keyword , name: name }
312
+
313
+ format_expansion ( entries , hint )
314
+ else
315
+ _ -> expand_local_or_var ( hint , shell )
316
+ end
317
+ end
318
+
319
+ defp find_struct_fields ( ast ) do
320
+ ast
321
+ |> Macro . prewalker ( )
322
+ |> Enum . find_value ( fn node ->
323
+ with { :% , _ , [ { :__aliases__ , _ , aliases } , { :%{} , _ , pairs } ] } <- node ,
324
+ { pairs , [ { :__cursor__ , _ , [ ] } ] } <- Enum . split ( pairs , - 1 ) ,
325
+ true <- Keyword . keyword? ( pairs ) do
326
+ { aliases , pairs }
327
+ else
328
+ _ -> nil
329
+ end
330
+ end )
293
331
end
294
332
333
+ ## Aliases and modules
334
+
295
335
defp expand_aliases ( all , shell ) do
296
336
case String . split ( all , "." ) do
297
337
[ hint ] ->
@@ -309,20 +349,14 @@ defmodule IEx.Autocomplete do
309
349
end
310
350
end
311
351
312
- defp value_from_alias ( [ name | rest ] , shell ) when is_binary ( name ) do
313
- name = String . to_atom ( name )
314
-
352
+ defp value_from_alias ( [ name | rest ] , shell ) do
315
353
case Keyword . fetch ( aliases_from_env ( shell ) , Module . concat ( Elixir , name ) ) do
316
354
{ :ok , name } when rest == [ ] -> { :ok , name }
317
355
{ :ok , name } -> { :ok , Module . concat ( [ name | rest ] ) }
318
356
:error -> { :ok , Module . concat ( [ name | rest ] ) }
319
357
end
320
358
end
321
359
322
- defp value_from_alias ( [ _ | _ ] , _ ) do
323
- :error
324
- end
325
-
326
360
defp match_aliases ( hint , shell ) do
327
361
for { alias , module } <- aliases_from_env ( shell ) ,
328
362
[ name ] = Module . split ( alias ) ,
@@ -552,6 +586,10 @@ defmodule IEx.Autocomplete do
552
586
[ "~#{ name } (sigil_#{ name } )" ]
553
587
end
554
588
589
+ defp to_entries ( % { kind: :keyword , name: name } ) do
590
+ [ "#{ name } :" ]
591
+ end
592
+
555
593
defp to_entries ( % { kind: _ , name: name } ) do
556
594
[ name ]
557
595
end
@@ -578,6 +616,10 @@ defmodule IEx.Autocomplete do
578
616
format_hint ( name , hint ) <> "{"
579
617
end
580
618
619
+ defp to_hint ( % { kind: :keyword , name: name } , hint ) do
620
+ format_hint ( name , hint ) <> ": "
621
+ end
622
+
581
623
defp to_hint ( % { kind: _ , name: name } , hint ) do
582
624
format_hint ( name , hint )
583
625
end
0 commit comments