1defmodule IEx.Autocomplete do
2  @moduledoc false
3
4  @doc """
5  Provides one helper function that is injected into connecting
6  remote nodes to properly handle autocompletion.
7  """
8  def remsh(node) do
9    fn e ->
10      case :rpc.call(node, IEx.Autocomplete, :expand, [e, IEx.Broker.shell()]) do
11        {:badrpc, _} -> {:no, '', []}
12        r -> r
13      end
14    end
15  end
16
17  @doc """
18  The expansion logic.
19
20  Some of the expansion has to be use the current shell
21  environment, which is found via the broker.
22  """
23  def expand(code, shell \\ IEx.Broker.shell()) do
24    code = Enum.reverse(code)
25    helper = get_helper(code)
26
27    case Code.cursor_context(code) do
28      {:alias, alias} ->
29        expand_aliases(List.to_string(alias), shell)
30
31      {:unquoted_atom, unquoted_atom} ->
32        expand_erlang_modules(List.to_string(unquoted_atom))
33
34      expansion when helper == ?b ->
35        expand_typespecs(expansion, shell, &get_module_callbacks/1)
36
37      expansion when helper == ?t ->
38        expand_typespecs(expansion, shell, &get_module_types/1)
39
40      {:dot, path, hint} ->
41        expand_dot(path, List.to_string(hint), shell)
42
43      {:dot_arity, path, hint} ->
44        expand_dot(path, List.to_string(hint), shell)
45
46      {:dot_call, path, hint} ->
47        expand_dot_call(path, List.to_atom(hint), shell)
48
49      :expr ->
50        expand_local_or_var("", shell)
51
52      {:local_or_var, local_or_var} ->
53        expand_local_or_var(List.to_string(local_or_var), shell)
54
55      {:local_arity, local} ->
56        expand_local(List.to_string(local), shell)
57
58      {:local_call, local} ->
59        expand_local_call(List.to_atom(local), shell)
60
61      # {:module_attribute, charlist}
62      # :none
63      _ ->
64        no()
65    end
66  end
67
68  defp get_helper(expr) do
69    with [helper | rest] when helper in 'bt' <- expr,
70         [space_or_paren, char | _] <- squeeze_spaces(rest),
71         true <-
72           space_or_paren in ' (' and
73             (char in ?A..?Z or char in ?a..?z or char in ?0..?9 or char in '_:') do
74      helper
75    else
76      _ -> nil
77    end
78  end
79
80  defp squeeze_spaces('  ' ++ rest), do: squeeze_spaces([?\s | rest])
81  defp squeeze_spaces(rest), do: rest
82
83  @doc false
84  def exports(mod) do
85    if Code.ensure_loaded?(mod) and function_exported?(mod, :__info__, 1) do
86      mod.__info__(:macros) ++ (mod.__info__(:functions) -- [__info__: 1])
87    else
88      mod.module_info(:exports) -- [module_info: 0, module_info: 1]
89    end
90  end
91
92  ## Typespecs
93
94  defp expand_typespecs({:dot, path, hint}, shell, fun) do
95    hint = List.to_string(hint)
96
97    case expand_dot_path(path, shell) do
98      {:ok, mod} when is_atom(mod) ->
99        mod
100        |> fun.()
101        |> match_module_funs(hint)
102        |> format_expansion(hint)
103
104      _ ->
105        no()
106    end
107  end
108
109  defp expand_typespecs(_, _, _), do: no()
110
111  ## Expand call
112
113  defp expand_local_call(fun, shell) do
114    imports_from_env(shell)
115    |> Enum.filter(fn {_, funs} -> List.keymember?(funs, fun, 0) end)
116    |> Enum.flat_map(fn {module, _} -> get_signatures(fun, module) end)
117    |> expand_signatures(shell)
118  end
119
120  defp expand_dot_call(path, fun, shell) do
121    case expand_dot_path(path, shell) do
122      {:ok, mod} when is_atom(mod) -> get_signatures(fun, mod) |> expand_signatures(shell)
123      _ -> no()
124    end
125  end
126
127  defp get_signatures(name, module) when is_atom(module) do
128    with docs when is_list(docs) <- get_docs(module, [:function, :macro], name) do
129      Enum.map(docs, fn {_, _, signatures, _, _} -> Enum.join(signatures, " ") end)
130    else
131      _ -> []
132    end
133  end
134
135  defp expand_signatures([_ | _] = signatures, _shell) do
136    [head | tail] = Enum.sort(signatures, &(String.length(&1) <= String.length(&2)))
137    if tail != [], do: IO.write("\n" <> (tail |> Enum.reverse() |> Enum.join("\n")))
138    yes("", [head])
139  end
140
141  defp expand_signatures([], shell), do: expand_local_or_var("", shell)
142
143  ## Expand dot
144
145  defp expand_dot(path, hint, shell) do
146    case expand_dot_path(path, shell) do
147      {:ok, mod} when is_atom(mod) and hint == "" -> expand_aliases(mod, "", [], true)
148      {:ok, mod} when is_atom(mod) -> expand_require(mod, hint)
149      {:ok, map} when is_map(map) -> expand_map_field_access(map, hint)
150      _ -> no()
151    end
152  end
153
154  defp expand_dot_path({:var, var}, shell) do
155    value_from_binding({List.to_atom(var), [], nil}, shell)
156  end
157
158  defp expand_dot_path({:alias, var}, shell) do
159    var |> List.to_string() |> String.split(".") |> value_from_alias(shell)
160  end
161
162  defp expand_dot_path({:unquoted_atom, var}, _shell) do
163    {:ok, List.to_atom(var)}
164  end
165
166  defp expand_dot_path({:module_attribute, _}, _shell) do
167    :error
168  end
169
170  defp expand_dot_path({:dot, parent, call}, shell) do
171    case expand_dot_path(parent, shell) do
172      {:ok, %{} = map} -> Map.fetch(map, List.to_atom(call))
173      _ -> :error
174    end
175  end
176
177  defp expand_map_field_access(map, hint) do
178    case match_map_fields(map, hint) do
179      [%{kind: :map_key, name: ^hint, value_is_map: false}] -> no()
180      map_fields when is_list(map_fields) -> format_expansion(map_fields, hint)
181    end
182  end
183
184  defp expand_require(mod, hint) do
185    format_expansion(match_module_funs(get_module_funs(mod), hint), hint)
186  end
187
188  ## Expand local or var
189
190  defp expand_local_or_var(hint, shell) do
191    format_expansion(match_var(hint, shell) ++ match_local(hint, shell), hint)
192  end
193
194  defp expand_local(hint, shell) do
195    format_expansion(match_local(hint, shell), hint)
196  end
197
198  defp match_local(hint, shell) do
199    imports = imports_from_env(shell) |> Enum.flat_map(&elem(&1, 1))
200    module_funs = get_module_funs(Kernel.SpecialForms)
201    match_module_funs(imports ++ module_funs, hint)
202  end
203
204  defp match_var(hint, shell) do
205    variables_from_binding(hint, shell)
206    |> Enum.sort()
207    |> Enum.map(&%{kind: :variable, name: &1})
208  end
209
210  ## Erlang modules
211
212  defp expand_erlang_modules(hint) do
213    format_expansion(match_erlang_modules(hint), hint)
214  end
215
216  defp match_erlang_modules(hint) do
217    for mod <- match_modules(hint, true), usable_as_unquoted_module?(mod) do
218      %{kind: :module, name: mod, type: :erlang}
219    end
220  end
221
222  ## Elixir modules
223
224  defp expand_aliases(all, shell) do
225    case String.split(all, ".") do
226      [hint] ->
227        aliases = match_aliases(hint, shell)
228        expand_aliases(Elixir, hint, aliases, false)
229
230      parts ->
231        hint = List.last(parts)
232        list = Enum.take(parts, length(parts) - 1)
233
234        case value_from_alias(list, shell) do
235          {:ok, alias} -> expand_aliases(alias, hint, [], false)
236          :error -> no()
237        end
238    end
239  end
240
241  defp expand_aliases(mod, hint, aliases, include_funs?) do
242    aliases
243    |> Kernel.++(match_elixir_modules(mod, hint))
244    |> Kernel.++(if include_funs?, do: match_module_funs(get_module_funs(mod), hint), else: [])
245    |> format_expansion(hint)
246  end
247
248  defp value_from_alias([name | rest], shell) when is_binary(name) do
249    name = String.to_atom(name)
250
251    case Keyword.fetch(aliases_from_env(shell), Module.concat(Elixir, name)) do
252      {:ok, name} when rest == [] -> {:ok, name}
253      {:ok, name} -> {:ok, Module.concat([name | rest])}
254      :error -> {:ok, Module.concat([name | rest])}
255    end
256  end
257
258  defp value_from_alias([_ | _], _) do
259    :error
260  end
261
262  defp match_aliases(hint, shell) do
263    for {alias, _mod} <- aliases_from_env(shell),
264        [name] = Module.split(alias),
265        String.starts_with?(name, hint) do
266      %{kind: :module, type: :alias, name: name}
267    end
268  end
269
270  defp match_elixir_modules(module, hint) do
271    name = Atom.to_string(module)
272    depth = length(String.split(name, ".")) + 1
273    base = name <> "." <> hint
274
275    for mod <- match_modules(base, module == Elixir),
276        parts = String.split(mod, "."),
277        depth <= length(parts),
278        name = Enum.at(parts, depth - 1),
279        valid_alias_piece?("." <> name),
280        uniq: true,
281        do: %{kind: :module, type: :elixir, name: name}
282  end
283
284  defp valid_alias_piece?(<<?., char, rest::binary>>) when char in ?A..?Z,
285    do: valid_alias_rest?(rest)
286
287  defp valid_alias_piece?(_), do: false
288
289  defp valid_alias_rest?(<<char, rest::binary>>)
290       when char in ?A..?Z
291       when char in ?a..?z
292       when char in ?0..?9
293       when char == ?_,
294       do: valid_alias_rest?(rest)
295
296  defp valid_alias_rest?(<<>>), do: true
297  defp valid_alias_rest?(rest), do: valid_alias_piece?(rest)
298
299  ## Formatting
300
301  defp format_expansion([], _) do
302    no()
303  end
304
305  defp format_expansion([uniq], hint) do
306    case to_hint(uniq, hint) do
307      "" -> yes("", to_uniq_entries(uniq))
308      hint -> yes(hint, [])
309    end
310  end
311
312  defp format_expansion([first | _] = entries, hint) do
313    binary = Enum.map(entries, & &1.name)
314    length = byte_size(hint)
315    prefix = :binary.longest_common_prefix(binary)
316
317    if prefix in [0, length] do
318      yes("", Enum.flat_map(entries, &to_entries/1))
319    else
320      yes(binary_part(first.name, prefix, length - prefix), [])
321    end
322  end
323
324  defp yes(hint, entries) do
325    {:yes, String.to_charlist(hint), Enum.map(entries, &String.to_charlist/1)}
326  end
327
328  defp no do
329    {:no, '', []}
330  end
331
332  ## Helpers
333
334  defp usable_as_unquoted_module?(name) do
335    # Conversion to atom is not a problem because
336    # it is only called with existing modules names.
337    Code.Identifier.classify(String.to_atom(name)) != :other
338  end
339
340  defp match_modules(hint, root) do
341    get_modules(root)
342    |> Enum.sort()
343    |> Enum.dedup()
344    |> Enum.drop_while(&(not String.starts_with?(&1, hint)))
345    |> Enum.take_while(&String.starts_with?(&1, hint))
346  end
347
348  defp get_modules(true) do
349    ["Elixir.Elixir"] ++ get_modules(false)
350  end
351
352  defp get_modules(false) do
353    modules = Enum.map(:code.all_loaded(), &Atom.to_string(elem(&1, 0)))
354
355    case :code.get_mode() do
356      :interactive -> modules ++ get_modules_from_applications()
357      _otherwise -> modules
358    end
359  end
360
361  defp get_modules_from_applications do
362    for [app] <- loaded_applications(),
363        {:ok, modules} = :application.get_key(app, :modules),
364        module <- modules do
365      Atom.to_string(module)
366    end
367  end
368
369  defp loaded_applications do
370    # If we invoke :application.loaded_applications/0,
371    # it can error if we don't call safe_fixtable before.
372    # Since in both cases we are reaching over the
373    # application controller internals, we choose to match
374    # for performance.
375    :ets.match(:ac_tab, {{:loaded, :"$1"}, :_})
376  end
377
378  defp match_module_funs(funs, hint) do
379    for {fun, arity} <- funs, name = Atom.to_string(fun), String.starts_with?(name, hint) do
380      %{
381        kind: :function,
382        name: name,
383        arity: arity
384      }
385    end
386    |> Enum.sort_by(&{&1.name, &1.arity})
387  end
388
389  defp match_map_fields(map, hint) do
390    for {key, value} when is_atom(key) <- Map.to_list(map),
391        key = Atom.to_string(key),
392        String.starts_with?(key, hint) do
393      %{kind: :map_key, name: key, value_is_map: is_map(value)}
394    end
395    |> Enum.sort_by(& &1.name)
396  end
397
398  defp get_module_funs(mod) do
399    cond do
400      not ensure_loaded?(mod) ->
401        []
402
403      docs = get_docs(mod, [:function, :macro]) ->
404        exports(mod)
405        |> Kernel.--(default_arg_functions_with_doc_false(docs))
406        |> Enum.reject(&hidden_fun?(&1, docs))
407
408      true ->
409        exports(mod)
410    end
411  end
412
413  defp get_module_types(mod) do
414    if ensure_loaded?(mod) do
415      case Code.Typespec.fetch_types(mod) do
416        {:ok, types} ->
417          for {kind, {name, _, args}} <- types,
418              kind in [:type, :opaque] do
419            {name, length(args)}
420          end
421
422        :error ->
423          []
424      end
425    else
426      []
427    end
428  end
429
430  defp get_module_callbacks(mod) do
431    if ensure_loaded?(mod) do
432      case Code.Typespec.fetch_callbacks(mod) do
433        {:ok, callbacks} ->
434          for {name_arity, _} <- callbacks do
435            {_kind, name, arity} = IEx.Introspection.translate_callback_name_arity(name_arity)
436
437            {name, arity}
438          end
439
440        :error ->
441          []
442      end
443    else
444      []
445    end
446  end
447
448  defp get_docs(mod, kinds, fun \\ nil) do
449    case Code.fetch_docs(mod) do
450      {:docs_v1, _, _, _, _, _, docs} ->
451        if is_nil(fun) do
452          for {{kind, _, _}, _, _, _, _} = doc <- docs, kind in kinds, do: doc
453        else
454          for {{kind, ^fun, _}, _, _, _, _} = doc <- docs, kind in kinds, do: doc
455        end
456
457      {:error, _} ->
458        nil
459    end
460  end
461
462  defp default_arg_functions_with_doc_false(docs) do
463    for {{_, fun_name, arity}, _, _, :hidden, %{defaults: count}} <- docs,
464        new_arity <- (arity - count)..arity,
465        do: {fun_name, new_arity}
466  end
467
468  defp hidden_fun?({name, arity}, docs) do
469    case Enum.find(docs, &match?({{_, ^name, ^arity}, _, _, _, _}, &1)) do
470      nil -> hd(Atom.to_charlist(name)) == ?_
471      {_, _, _, %{}, _} -> false
472      {_, _, _, _, _} -> true
473    end
474  end
475
476  defp ensure_loaded?(Elixir), do: false
477  defp ensure_loaded?(mod), do: Code.ensure_loaded?(mod)
478
479  ## Ad-hoc conversions
480
481  defp to_entries(%{kind: kind, name: name})
482       when kind in [:map_key, :module, :variable] do
483    [name]
484  end
485
486  defp to_entries(%{kind: :function, name: name, arity: arity}) do
487    ["#{name}/#{arity}"]
488  end
489
490  defp to_uniq_entries(%{kind: kind})
491       when kind in [:map_key, :module, :variable] do
492    []
493  end
494
495  defp to_uniq_entries(%{kind: :function} = fun) do
496    to_entries(fun)
497  end
498
499  defp to_hint(%{kind: :module, name: name}, hint) when name == hint do
500    format_hint(name, name) <> "."
501  end
502
503  defp to_hint(%{kind: :map_key, name: name, value_is_map: true}, hint) when name == hint do
504    format_hint(name, hint) <> "."
505  end
506
507  defp to_hint(%{kind: kind, name: name}, hint)
508       when kind in [:function, :map_key, :module, :variable] do
509    format_hint(name, hint)
510  end
511
512  defp format_hint(name, hint) do
513    hint_size = byte_size(hint)
514    binary_part(name, hint_size, byte_size(name) - hint_size)
515  end
516
517  ## Evaluator interface
518
519  defp imports_from_env(shell) do
520    with {evaluator, server} <- IEx.Broker.evaluator(shell),
521         env_fields = IEx.Evaluator.fields_from_env(evaluator, server, [:functions, :macros]),
522         %{functions: funs, macros: macros} <- env_fields do
523      funs ++ macros
524    else
525      _ -> []
526    end
527  end
528
529  defp aliases_from_env(shell) do
530    with {evaluator, server} <- IEx.Broker.evaluator(shell),
531         %{aliases: aliases} <- IEx.Evaluator.fields_from_env(evaluator, server, [:aliases]) do
532      aliases
533    else
534      _ -> []
535    end
536  end
537
538  defp variables_from_binding(hint, shell) do
539    with {evaluator, server} <- IEx.Broker.evaluator(shell) do
540      IEx.Evaluator.variables_from_binding(evaluator, server, hint)
541    else
542      _ -> []
543    end
544  end
545
546  defp value_from_binding(ast_node, shell) do
547    with {evaluator, server} <- IEx.Broker.evaluator(shell),
548         {var, map_key_path} <- extract_from_ast(ast_node, []) do
549      IEx.Evaluator.value_from_binding(evaluator, server, var, map_key_path)
550    else
551      _ -> :error
552    end
553  end
554
555  defp extract_from_ast(var_name, acc) when is_atom(var_name) do
556    {var_name, acc}
557  end
558
559  defp extract_from_ast({var_name, _, nil}, acc) when is_atom(var_name) do
560    {var_name, acc}
561  end
562
563  defp extract_from_ast({{:., _, [ast_node, fun]}, _, []}, acc) when is_atom(fun) do
564    extract_from_ast(ast_node, [fun | acc])
565  end
566
567  defp extract_from_ast(_ast_node, _acc) do
568    :error
569  end
570end
571