Skip to content

Commit 50ac792

Browse files
author
José Valim
committed
Optimize and also add completion for Erlang modules
Signed-off-by: José Valim <[email protected]>
1 parent 7d433e8 commit 50ac792

File tree

2 files changed

+72
-77
lines changed

2 files changed

+72
-77
lines changed

lib/iex/lib/iex/autocomplete.ex

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ defmodule IEx.Autocomplete do
8787
length = byte_size(hint)
8888
prefix = :binary.longest_common_prefix(binary)
8989
if prefix in [0, length] do
90-
entries = Enum.reduce(entries, [], fn e, acc -> to_entries(e) ++ acc end)
91-
yes("", entries)
90+
yes("", Enum.flat_map(entries, &to_entries/1))
9291
else
9392
yes(:binary.part(first.name, prefix, length-prefix), [])
9493
end
@@ -111,13 +110,13 @@ defmodule IEx.Autocomplete do
111110
end
112111

113112
defp expand_require(mod, hint) do
114-
format_expansion module_funs(mod, hint), hint
113+
format_expansion match_module_funs(mod, hint), hint
115114
end
116115

117116
defp expand_import(hint) do
118-
funs = module_funs(IEx.Helpers, hint) ++
119-
module_funs(Kernel, hint) ++
120-
module_funs(Kernel.SpecialForms, hint)
117+
funs = match_module_funs(IEx.Helpers, hint) ++
118+
match_module_funs(Kernel, hint) ++
119+
match_module_funs(Kernel.SpecialForms, hint)
121120
format_expansion funs, hint
122121
end
123122

@@ -128,10 +127,7 @@ defmodule IEx.Autocomplete do
128127
end
129128

130129
defp match_erlang_modules(hint) do
131-
for {mod, _} <- :code.all_loaded,
132-
mod = Atom.to_string(mod),
133-
not match?("Elixir." <> _, mod),
134-
String.starts_with?(mod, hint) do
130+
for mod <- match_modules(hint, true) do
135131
%{kind: :module, name: mod, type: :erlang}
136132
end
137133
end
@@ -140,57 +136,58 @@ defmodule IEx.Autocomplete do
140136

141137
defp expand_elixir_modules(list, hint) do
142138
mod = Module.concat(list)
143-
format_expansion elixir_aliases(mod, hint, list == []) ++ module_funs(mod, hint), hint
144-
end
145-
146-
defp elixir_aliases(mod, hint, root) do
147-
modname = Atom.to_string(mod)
148-
depth = length(String.split(modname, ".")) + 1
149-
base = modname <> "." <> hint
150-
151-
Enum.reduce modules_as_lists(root), [], fn(m, acc) ->
152-
if String.starts_with?(m, base) do
153-
tokens = String.split(m, ".")
154-
if length(tokens) == depth do
155-
name = List.last(tokens)
156-
[%{kind: :module, type: :elixir, name: name}|acc]
157-
else
158-
acc
159-
end
160-
else
161-
acc
162-
end
139+
format_expansion match_elixir_modules(mod, hint, list == []) ++
140+
match_module_funs(mod, hint), hint
141+
end
142+
143+
defp match_elixir_modules(module, hint, root) do
144+
module = Atom.to_string(module)
145+
depth = length(String.split(module, ".")) + 1
146+
base = module <> "." <> hint
147+
148+
for mod <- match_modules(base, root),
149+
tokens = String.split(mod, "."),
150+
length(tokens) == depth do
151+
name = List.last(tokens)
152+
%{kind: :module, type: :elixir, name: name}
163153
end
164154
end
165155

166-
defp modules_as_lists(true) do
167-
["Elixir.Elixir"] ++ modules_as_lists(false)
156+
## Helpers
157+
158+
defp match_modules(hint, root) do
159+
get_modules(root)
160+
|> :lists.usort()
161+
|> Enum.drop_while(& not String.starts_with?(&1, hint))
162+
|> Enum.take_while(& String.starts_with?(&1, hint))
163+
end
164+
165+
defp get_modules(true) do
166+
["Elixir.Elixir"] ++ get_modules(false)
168167
end
169168

170-
defp modules_as_lists(false) do
169+
defp get_modules(false) do
171170
modules = Enum.map(:code.all_loaded, fn({m, _}) -> Atom.to_string(m) end)
172171

173172
if :code.get_mode() === :interactive do
174-
Enum.uniq(modules ++ loaded_applications_as_lists())
173+
modules ++ get_modules_from_applications()
175174
else
176175
modules
177176
end
178177
end
179178

180-
defp loaded_applications_as_lists do
179+
defp get_modules_from_applications do
181180
for {app, _, _} <- :application.loaded_applications,
182181
{_, modules} = :application.get_key(app, :modules),
183182
module <- modules do
184183
Atom.to_string(module)
185184
end
186185
end
187186

188-
## Helpers
189-
190-
defp module_funs(mod, hint) do
187+
defp match_module_funs(mod, hint) do
191188
case ensure_loaded(mod) do
192189
{:module, _} ->
193-
falist = get_funs(mod)
190+
falist = get_module_funs(mod)
194191

195192
list = Enum.reduce falist, [], fn {f, a}, acc ->
196193
case :lists.keyfind(f, 1, acc) do
@@ -203,13 +200,13 @@ defmodule IEx.Autocomplete do
203200
name = Atom.to_string(fun),
204201
String.starts_with?(name, hint) do
205202
%{kind: :function, name: name, arities: arities}
206-
end
203+
end |> :lists.sort()
207204
_ ->
208205
[]
209206
end
210207
end
211208

212-
defp get_funs(mod) do
209+
defp get_module_funs(mod) do
213210
if function_exported?(mod, :__info__, 1) do
214211
if docs = Code.get_docs(mod, :docs) do
215212
for {tuple, _line, _kind, _sign, doc} <- docs, doc != false, do: tuple
@@ -231,7 +228,7 @@ defmodule IEx.Autocomplete do
231228
end
232229

233230
defp to_entries(%{kind: :function, name: name, arities: arities}) do
234-
for a <- arities, do: "#{name}/#{a}"
231+
for a <- :lists.sort(arities), do: "#{name}/#{a}"
235232
end
236233

237234
defp to_uniq_entries(%{kind: :module}) do

lib/iex/test/iex/autocomplete_test.exs

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,54 @@ defmodule IEx.AutocompleteTest do
77
IEx.Autocomplete.expand(Enum.reverse expr)
88
end
99

10-
test :erlang_module_simple_completion do
11-
assert expand(':z') == {:yes, 'lib.', []}
10+
test :erlang_module_completion do
11+
assert expand(':zl') == {:yes, 'ib.', []}
1212
end
1313

1414
test :erlang_module_no_completion do
15-
assert expand(':x') == {:no, '', []}
16-
assert expand('x.Foo') == {:no, '', []}
17-
end
18-
19-
test :erlang_module_common_prefix_completion do
20-
assert expand(':us') == {:yes, 'er', []}
15+
assert expand(':unknown') == {:no, '', []}
2116
end
2217

2318
test :erlang_module_multiple_values_completion do
2419
{:yes, '', list} = expand(':user')
25-
assert length(list) > 1
20+
assert 'user' in list
21+
assert 'user_drv' in list
22+
end
23+
24+
test :erlang_root_completion do
25+
{:yes, '', list} = expand(':')
26+
assert is_list(list)
27+
assert 'lists' in list
28+
end
29+
30+
test :elixir_proxy do
31+
{:yes, '', list} = expand('E')
32+
assert 'Elixir' in list
2633
end
2734

28-
test :elixir_simple_completion do
35+
test :elixir_completion do
2936
assert expand('En') == {:yes, 'um', []}
3037
assert expand('Enumera') == {:yes, 'ble.', []}
3138
end
3239

33-
test :elixir_auto_completion_with_self do
40+
test :elixir_completion_with_self do
3441
assert expand('Enumerable') == {:yes, '.', []}
3542
end
3643

37-
test :elixir_auto_completion_on_modules_from_load_path do
38-
assert expand('Str') == {:yes, [], ['String', 'StringIO', 'Stream']}
44+
test :elixir_completion_on_modules_from_load_path do
45+
assert expand('Str') == {:yes, [], ['Stream', 'String', 'StringIO']}
3946
assert expand('Ma') == {:yes, '', ['Macro', 'Map', 'MatchError']}
4047
assert expand('Dic') == {:yes, 't.', []}
41-
assert expand('Ex') == {:yes, [], ['Exception', 'ExUnit']}
48+
assert expand('Ex') == {:yes, [], ['ExUnit', 'Exception']}
4249
end
4350

4451
test :elixir_no_completion do
4552
assert expand('.') == {:no, '', []}
4653
assert expand('Xyz') == {:no, '', []}
54+
assert expand('x.Foo') == {:no, '', []}
4755
end
4856

4957
test :elixir_root_submodule_completion do
50-
_ = [foo: 1][:foo]
5158
assert expand('Elixir.Acce') == {:yes, 'ss.', []}
5259
end
5360

@@ -59,52 +66,43 @@ defmodule IEx.AutocompleteTest do
5966
assert expand('IEx.Xyz') == {:no, '', []}
6067
end
6168

62-
test :elixir_function_completion do
69+
test :function_completion do
6370
assert expand('System.ve') == {:yes, 'rsion', []}
6471
assert expand(':ets.fun2') == {:yes, 'ms', []}
6572
end
6673

67-
test :elixir_function_completion_with_arity do
74+
test :function_completion_with_arity do
6875
assert expand('String.printable?') == {:yes, '', ['printable?/1']}
6976
assert expand('String.printable?/') == {:yes, '', ['printable?/1']}
7077
end
7178

72-
test :elixir_macro_completion do
79+
test :macro_completion do
7380
{:yes, '', list} = expand('Kernel.is_')
7481
assert is_list(list)
7582
end
7683

77-
test :elixir_root_completion do
84+
test :imports_completion do
7885
{:yes, '', list} = expand('')
7986
assert is_list(list)
8087
assert 'h/1' in list
8188
assert 'unquote/1' in list
89+
assert 'pwd/0' in list
8290
end
8391

84-
test :elixir_kernel_completion do
92+
test :kernel_import_completion do
8593
assert expand('defstru') == {:yes, 'ct', []}
94+
assert expand('put_') == {:yes, '', ['put_elem/3', 'put_in/2', 'put_in/3']}
8695
end
8796

88-
test :elixir_special_form_completion do
97+
test :kernel_special_form_completion do
8998
assert expand('unquote_spl') == {:yes, 'icing', []}
9099
end
91100

92-
test :elixir_proxy do
93-
{:yes, '', list} = expand('E')
94-
assert 'Elixir' in list
95-
end
96-
97-
test :elixir_erlang_module_root_completion do
98-
{:yes, '', list} = expand(':')
99-
assert is_list(list)
100-
assert 'lists' in list
101-
end
102-
103101
test :completion_inside_expression do
104102
assert expand('1 En') == {:yes, 'um', []}
105103
assert expand('Test(En') == {:yes, 'um', []}
106-
assert expand('Test :z') == {:yes, 'lib.', []}
107-
assert expand('[:z') == {:yes, 'lib.', []}
108-
assert expand('{:z') == {:yes, 'lib.', []}
104+
assert expand('Test :zl') == {:yes, 'ib.', []}
105+
assert expand('[:zl') == {:yes, 'ib.', []}
106+
assert expand('{:zl') == {:yes, 'ib.', []}
109107
end
110108
end

0 commit comments

Comments
 (0)