11defmodule IEx.Autocomplete do
22 @ moduledoc false
33
4- def expand ( '' ) do
4+ def expand ( expr , server \\ IEx.Server )
5+
6+ def expand ( '' , _server ) do
57 expand_import ( "" )
68 end
79
8- def expand ( [ h | t ] = expr ) do
10+ def expand ( [ h | t ] = expr , server ) do
911 cond do
1012 h === ?. and t != [ ] ->
11- expand_dot ( reduce ( t ) )
13+ expand_dot ( reduce ( t ) , server )
1214 h === ?: and t == [ ] ->
1315 expand_erlang_modules ( )
1416 identifier? ( h ) ->
15- expand_expr ( reduce ( expr ) )
17+ expand_expr ( reduce ( expr ) , server )
1618 ( h == ?/ ) and t != [ ] and identifier? ( hd ( t ) ) ->
17- expand_expr ( reduce ( t ) )
19+ expand_expr ( reduce ( t ) , server )
1820 h in '([{' ->
1921 expand ( '' )
2022 true ->
@@ -26,31 +28,33 @@ defmodule IEx.Autocomplete do
2628 ( h in ?a .. ?z ) or ( h in ?A .. ?Z ) or ( h in ?0 .. ?9 ) or h in [ ?_ , ?? , ?! ]
2729 end
2830
29- defp expand_dot ( expr ) do
31+ defp expand_dot ( expr , server ) do
3032 case Code . string_to_quoted expr do
3133 { :ok , atom } when is_atom ( atom ) ->
32- expand_call ( atom , "" )
34+ expand_call ( atom , "" , server )
3335 { :ok , { :__aliases__ , _ , list } } ->
34- expand_elixir_modules ( list , "" )
36+ expand_elixir_modules ( list , "" , server )
37+ { :ok , { _ , _ , _ } = ast_node } ->
38+ expand_call ( ast_node , "" , server )
3539 _ ->
3640 no ( )
3741 end
3842 end
3943
40- defp expand_expr ( expr ) do
44+ defp expand_expr ( expr , server ) do
4145 case Code . string_to_quoted expr do
4246 { :ok , atom } when is_atom ( atom ) ->
4347 expand_erlang_modules ( Atom . to_string ( atom ) )
4448 { :ok , { atom , _ , nil } } when is_atom ( atom ) ->
4549 expand_import ( Atom . to_string ( atom ) )
4650 { :ok , { :__aliases__ , _ , [ root ] } } ->
47- expand_elixir_modules ( [ ] , Atom . to_string ( root ) )
51+ expand_elixir_modules ( [ ] , Atom . to_string ( root ) , server )
4852 { :ok , { :__aliases__ , _ , [ h | _ ] = list } } when is_atom ( h ) ->
4953 hint = Atom . to_string ( List . last ( list ) )
5054 list = Enum . take ( list , length ( list ) - 1 )
51- expand_elixir_modules ( list , hint )
52- { :ok , { { :. , _ , [ mod , fun ] } , _ , [ ] } } when is_atom ( fun ) ->
53- expand_call ( mod , Atom . to_string ( fun ) )
55+ expand_elixir_modules ( list , hint , server )
56+ { :ok , { { :. , _ , [ ast_node , fun ] } , _ , [ ] } } when is_atom ( fun ) ->
57+ expand_call ( ast_node , Atom . to_string ( fun ) , server )
5458 _ ->
5559 no ( )
5660 end
@@ -105,21 +109,37 @@ defmodule IEx.Autocomplete do
105109 ## Expand calls
106110
107111 # :atom.fun
108- defp expand_call ( mod , hint ) when is_atom ( mod ) do
112+ defp expand_call ( mod , hint , _server ) when is_atom ( mod ) do
109113 expand_require ( mod , hint )
110114 end
111115
112116 # Elixir.fun
113- defp expand_call ( { :__aliases__ , _ , list } , hint ) do
114- expand_alias ( list )
117+ defp expand_call ( { :__aliases__ , _ , list } , hint , server ) do
118+ expand_alias ( list , server )
115119 |> normalize_module
116120 |> expand_require ( hint )
117121 end
118122
119- defp expand_call ( _ , _ ) do
123+ # variable.fun_or_key
124+ defp expand_call ( { _ , _ , _ } = ast_node , hint , server ) do
125+ case value_from_binding ( ast_node , server ) do
126+ { :ok , mod } when is_atom ( mod ) -> expand_call ( mod , hint , server )
127+ { :ok , map } when is_map ( map ) -> expand_map_field_access ( map , hint )
128+ _otherwise -> no ( )
129+ end
130+ end
131+
132+ defp expand_call ( _ , _ , _ ) do
120133 no ( )
121134 end
122135
136+ defp expand_map_field_access ( map , hint ) do
137+ case match_map_fields ( map , hint ) do
138+ [ % { kind: :map_key , name: name , value_is_map: false } ] when name == hint -> no ( )
139+ map_fields when is_list ( map_fields ) -> format_expansion ( map_fields , hint )
140+ end
141+ end
142+
123143 defp expand_require ( mod , hint ) do
124144 format_expansion match_module_funs ( mod , hint ) , hint
125145 end
@@ -145,26 +165,27 @@ defmodule IEx.Autocomplete do
145165
146166 ## Elixir modules
147167
148- defp expand_elixir_modules ( [ ] , hint ) do
149- expand_elixir_modules ( Elixir , hint , match_aliases ( hint ) )
168+ defp expand_elixir_modules ( [ ] , hint , server ) do
169+ aliases = match_aliases ( hint , server )
170+ expand_elixir_modules_from_aliases ( Elixir , hint , aliases )
150171 end
151172
152- defp expand_elixir_modules ( list , hint ) do
153- expand_alias ( list )
173+ defp expand_elixir_modules ( list , hint , server ) do
174+ expand_alias ( list , server )
154175 |> normalize_module
155- |> expand_elixir_modules ( hint , [ ] )
176+ |> expand_elixir_modules_from_aliases ( hint , [ ] )
156177 end
157178
158- defp expand_elixir_modules ( mod , hint , aliases ) do
179+ defp expand_elixir_modules_from_aliases ( mod , hint , aliases ) do
159180 aliases
160181 |> Kernel . ++ ( match_elixir_modules ( mod , hint ) )
161182 |> Kernel . ++ ( match_module_funs ( mod , hint ) )
162183 |> format_expansion ( hint )
163184 end
164185
165- defp expand_alias ( [ name | rest ] = list ) do
186+ defp expand_alias ( [ name | rest ] = list , server ) do
166187 module = Module . concat ( Elixir , name )
167- Enum . find_value env_aliases ( ) , list , fn { alias , mod } ->
188+ Enum . find_value env_aliases ( server ) , list , fn { alias , mod } ->
168189 if alias === module do
169190 case Atom . to_string ( mod ) do
170191 "Elixir." <> mod ->
@@ -176,12 +197,12 @@ defmodule IEx.Autocomplete do
176197 end
177198 end
178199
179- defp env_aliases do
180- Application . get_env ( :iex , :autocomplete_server ) . current_env . aliases
181- end
200+ defp env_aliases ( server ) , do: server . current_env . aliases
201+
202+ defp get_evaluator ( server ) , do: server . evaluator
182203
183- defp match_aliases ( hint ) do
184- for { alias , _mod } <- env_aliases ( ) ,
204+ defp match_aliases ( hint , server ) do
205+ for { alias , _mod } <- env_aliases ( server ) ,
185206 [ name ] = Module . split ( alias ) ,
186207 starts_with? ( name , hint ) do
187208 % { kind: :module , type: :alias , name: name }
@@ -269,6 +290,14 @@ defmodule IEx.Autocomplete do
269290 end
270291 end
271292
293+ defp match_map_fields ( map , hint ) do
294+ for { key , value } <- map ,
295+ is_atom ( key ) ,
296+ key = to_string ( key ) ,
297+ String . starts_with? ( key , hint ) ,
298+ do: % { kind: :map_key , name: key , value_is_map: is_map ( value ) }
299+ end
300+
272301 defp get_module_funs ( mod ) do
273302 docs = Code . get_docs ( mod , :docs ) || [ ]
274303 module_info_funs ( mod ) |> Enum . reject ( & hidden_fun? ( & 1 , docs ) )
@@ -316,6 +345,10 @@ defmodule IEx.Autocomplete do
316345 for a <- :lists . sort ( arities ) , do: "#{ name } /#{ a } "
317346 end
318347
348+ defp to_entries ( % { kind: :map_key , name: name } ) do
349+ [ name ]
350+ end
351+
319352 defp to_uniq_entries ( % { kind: :module } ) do
320353 [ ]
321354 end
@@ -324,6 +357,10 @@ defmodule IEx.Autocomplete do
324357 to_entries ( fun )
325358 end
326359
360+ defp to_uniq_entries ( % { kind: :map_key } ) do
361+ [ ]
362+ end
363+
327364 defp to_hint ( % { kind: :module , name: name } , hint ) when name == hint do
328365 format_hint ( name , name ) <> "."
329366 end
@@ -336,8 +373,41 @@ defmodule IEx.Autocomplete do
336373 format_hint ( name , hint )
337374 end
338375
376+ defp to_hint ( % { kind: :map_key , name: name , value_is_map: true } , hint ) when name == hint do
377+ format_hint ( name , hint ) <> "."
378+ end
379+
380+ defp to_hint ( % { kind: :map_key , name: name } , hint ) do
381+ format_hint ( name , hint )
382+ end
383+
339384 defp format_hint ( name , hint ) do
340385 hint_size = byte_size ( hint )
341386 :binary . part ( name , hint_size , byte_size ( name ) - hint_size )
342387 end
388+
389+ defp value_from_binding ( ast_node , server ) do
390+ with evaluator when is_pid ( evaluator ) <- get_evaluator ( server ) ,
391+ { var , map_key_path } <- extract_from_ast ( ast_node , [ ] ) do
392+ IEx.Evaluator . value_from_binding ( evaluator , var , map_key_path )
393+ else
394+ _ -> :error
395+ end
396+ end
397+
398+ defp extract_from_ast ( var_name , acc ) when is_atom ( var_name ) do
399+ { var_name , acc }
400+ end
401+
402+ defp extract_from_ast ( { var_name , _ , nil } , acc ) when is_atom ( var_name ) do
403+ { var_name , acc }
404+ end
405+
406+ defp extract_from_ast ( { { :. , _ , [ ast_node , fun ] } , _ , [ ] } , acc ) when is_atom ( fun ) do
407+ extract_from_ast ( ast_node , [ fun | acc ] )
408+ end
409+
410+ defp extract_from_ast ( _ast_node , _acc ) do
411+ :error
412+ end
343413end
0 commit comments