Skip to content

Commit e15220e

Browse files
committed
Implement it on IEx autocomplete
1 parent 2e5c871 commit e15220e

File tree

4 files changed

+74
-33
lines changed

4 files changed

+74
-33
lines changed

Diff for: lib/elixir/lib/code/fragment.ex

+6-7
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,9 @@ defmodule Code.Fragment do
4646
or `{:local_or_var, charlist}` and `charlist` is a static part
4747
Examples are `__MODULE__.Submodule` or `@hello.Submodule`
4848
49-
* `:block_keyword_or_binary_operator` - may be a block keyword (do, end, after,
49+
* `{:block_keyword_or_binary_operator, charlist}` - may be a block keyword (do, end, after,
5050
catch, else, rescue) or a binary operator
5151
52-
* `{:block_keyword, charlist}` - the context is a block keyword
53-
5452
* `{:dot, inside_dot, charlist}` - the context is a dot
5553
where `inside_dot` is either a `{:var, charlist}`, `{:alias, charlist}`,
5654
`{:module_attribute, charlist}`, `{:unquoted_atom, charlist}` or a `dot`
@@ -144,8 +142,7 @@ defmodule Code.Fragment do
144142
@spec cursor_context(List.Chars.t(), keyword()) ::
145143
{:alias, charlist}
146144
| {:alias, inside_alias, charlist}
147-
| :block_keyword_or_binary_operator
148-
| {:block_keyword, charlist}
145+
| {:block_keyword_or_binary_operator, charlist}
149146
| {:dot, inside_dot, charlist}
150147
| {:dot_arity, inside_dot, charlist}
151148
| {:dot_call, inside_dot, charlist}
@@ -278,7 +275,7 @@ defmodule Code.Fragment do
278275

279276
defp closing_or_call_to_cursor_context({reverse, spaces}) do
280277
if closing?(reverse) do
281-
{:block_keyword_or_binary_operator, 0}
278+
{{:block_keyword_or_binary_operator, ~c""}, 0}
282279
else
283280
call_to_cursor_context({reverse, spaces})
284281
end
@@ -337,7 +334,9 @@ defmodule Code.Fragment do
337334

338335
{rest, rest_count} ->
339336
response =
340-
if rest_count > count and closing?(rest), do: :block_keyword, else: :local_or_var
337+
if rest_count > count and closing?(rest),
338+
do: :block_keyword_or_binary_operator,
339+
else: :local_or_var
341340

342341
{{response, acc}, count}
343342
end

Diff for: lib/elixir/test/elixir/code_fragment_test.exs

+27-26
Original file line numberDiff line numberDiff line change
@@ -373,21 +373,21 @@ defmodule CodeFragmentTest do
373373

374374
test "keyword or binary operator" do
375375
# Literals
376-
assert CF.cursor_context("Foo.Bar ") == :block_keyword_or_binary_operator
377-
assert CF.cursor_context("Foo ") == :block_keyword_or_binary_operator
378-
assert CF.cursor_context(":foo ") == :block_keyword_or_binary_operator
379-
assert CF.cursor_context("123 ") == :block_keyword_or_binary_operator
380-
assert CF.cursor_context("nil ") == :block_keyword_or_binary_operator
381-
assert CF.cursor_context("true ") == :block_keyword_or_binary_operator
382-
assert CF.cursor_context("false ") == :block_keyword_or_binary_operator
383-
assert CF.cursor_context("\"foo\" ") == :block_keyword_or_binary_operator
384-
assert CF.cursor_context("'foo' ") == :block_keyword_or_binary_operator
376+
assert CF.cursor_context("Foo.Bar ") == {:block_keyword_or_binary_operator, ~c""}
377+
assert CF.cursor_context("Foo ") == {:block_keyword_or_binary_operator, ~c""}
378+
assert CF.cursor_context(":foo ") == {:block_keyword_or_binary_operator, ~c""}
379+
assert CF.cursor_context("123 ") == {:block_keyword_or_binary_operator, ~c""}
380+
assert CF.cursor_context("nil ") == {:block_keyword_or_binary_operator, ~c""}
381+
assert CF.cursor_context("true ") == {:block_keyword_or_binary_operator, ~c""}
382+
assert CF.cursor_context("false ") == {:block_keyword_or_binary_operator, ~c""}
383+
assert CF.cursor_context("\"foo\" ") == {:block_keyword_or_binary_operator, ~c""}
384+
assert CF.cursor_context("'foo' ") == {:block_keyword_or_binary_operator, ~c""}
385385

386386
# Containers
387-
assert CF.cursor_context("(foo) ") == :block_keyword_or_binary_operator
388-
assert CF.cursor_context("[foo] ") == :block_keyword_or_binary_operator
389-
assert CF.cursor_context("{foo} ") == :block_keyword_or_binary_operator
390-
assert CF.cursor_context("<<foo>> ") == :block_keyword_or_binary_operator
387+
assert CF.cursor_context("(foo) ") == {:block_keyword_or_binary_operator, ~c""}
388+
assert CF.cursor_context("[foo] ") == {:block_keyword_or_binary_operator, ~c""}
389+
assert CF.cursor_context("{foo} ") == {:block_keyword_or_binary_operator, ~c""}
390+
assert CF.cursor_context("<<foo>> ") == {:block_keyword_or_binary_operator, ~c""}
391391

392392
# False positives
393393
assert CF.cursor_context("foo ~>> ") == {:operator_call, ~c"~>>"}
@@ -396,21 +396,22 @@ defmodule CodeFragmentTest do
396396

397397
test "keyword from keyword or binary operator" do
398398
# Literals
399-
assert CF.cursor_context("Foo.Bar d") == {:block_keyword, ~c"d"}
400-
assert CF.cursor_context("Foo d") == {:block_keyword, ~c"d"}
401-
assert CF.cursor_context(":foo d") == {:block_keyword, ~c"d"}
402-
assert CF.cursor_context("123 d") == {:block_keyword, ~c"d"}
403-
assert CF.cursor_context("nil d") == {:block_keyword, ~c"d"}
404-
assert CF.cursor_context("true d") == {:block_keyword, ~c"d"}
405-
assert CF.cursor_context("false d") == {:block_keyword, ~c"d"}
406-
assert CF.cursor_context("\"foo\" d") == {:block_keyword, ~c"d"}
407-
assert CF.cursor_context("'foo' d") == {:block_keyword, ~c"d"}
399+
assert CF.cursor_context("Foo.Bar do") == {:block_keyword_or_binary_operator, ~c"do"}
400+
assert CF.cursor_context("Foo.Bar d") == {:block_keyword_or_binary_operator, ~c"d"}
401+
assert CF.cursor_context("Foo d") == {:block_keyword_or_binary_operator, ~c"d"}
402+
assert CF.cursor_context(":foo d") == {:block_keyword_or_binary_operator, ~c"d"}
403+
assert CF.cursor_context("123 d") == {:block_keyword_or_binary_operator, ~c"d"}
404+
assert CF.cursor_context("nil d") == {:block_keyword_or_binary_operator, ~c"d"}
405+
assert CF.cursor_context("true d") == {:block_keyword_or_binary_operator, ~c"d"}
406+
assert CF.cursor_context("false d") == {:block_keyword_or_binary_operator, ~c"d"}
407+
assert CF.cursor_context("\"foo\" d") == {:block_keyword_or_binary_operator, ~c"d"}
408+
assert CF.cursor_context("'foo' d") == {:block_keyword_or_binary_operator, ~c"d"}
408409

409410
# Containers
410-
assert CF.cursor_context("(foo) d") == {:block_keyword, ~c"d"}
411-
assert CF.cursor_context("[foo] d") == {:block_keyword, ~c"d"}
412-
assert CF.cursor_context("{foo} d") == {:block_keyword, ~c"d"}
413-
assert CF.cursor_context("<<foo>> d") == {:block_keyword, ~c"d"}
411+
assert CF.cursor_context("(foo) d") == {:block_keyword_or_binary_operator, ~c"d"}
412+
assert CF.cursor_context("[foo] d") == {:block_keyword_or_binary_operator, ~c"d"}
413+
assert CF.cursor_context("{foo} d") == {:block_keyword_or_binary_operator, ~c"d"}
414+
assert CF.cursor_context("<<foo>> d") == {:block_keyword_or_binary_operator, ~c"d"}
414415

415416
# False positives
416417
assert CF.cursor_context("foo ~>> d") == {:local_or_var, ~c"d"}

Diff for: lib/iex/lib/iex/autocomplete.ex

+26
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@ defmodule IEx.Autocomplete do
2222
%{kind: :variable, name: "utf32"}
2323
]
2424

25+
block_keywords =
26+
for block_keyword <- ~w(do end after catch else rescue) do
27+
%{kind: :block_keyword, name: block_keyword}
28+
end
29+
30+
binary_operators =
31+
for operator <-
32+
["**", "*", "/", "+", "-", "++", "--", "+++", "---", "..", "<>"] ++
33+
["in", "not in", "|>", "<<<", ">>>", "<<~", "~>>", "<~", "~>", "<~>"] ++
34+
["<", ">", "<=", ">=", "==", "!=", "=~", "===", "!=="] ++
35+
["&&", "&&&", "and", "||", "|||", "or"] ++
36+
["=", "=>", "|", "::", "when", "<-", "\\\\"] do
37+
%{kind: :export, name: operator, arity: 2}
38+
end
39+
40+
@block_keyword_or_binary_operator block_keywords ++ Enum.sort(binary_operators)
41+
2542
@alias_only_atoms ~w(alias import require)a
2643
@alias_only_charlists ~w(alias import require)c
2744

@@ -63,6 +80,9 @@ defmodule IEx.Autocomplete do
6380
{:unquoted_atom, unquoted_atom} ->
6481
expand_erlang_modules(List.to_string(unquoted_atom))
6582

83+
{:block_keyword_or_binary_operator, hint} ->
84+
filter_and_format_expansion(@block_keyword_or_binary_operator, List.to_string(hint))
85+
6686
expansion when helper == ?b ->
6787
expand_typespecs(expansion, shell, &get_module_callbacks/1)
6888

@@ -519,6 +539,12 @@ defmodule IEx.Autocomplete do
519539

520540
## Formatting
521541

542+
defp filter_and_format_expansion(results, hint) do
543+
results
544+
|> Enum.filter(&String.starts_with?(&1.name, hint))
545+
|> format_expansion(hint)
546+
end
547+
522548
defp format_expansion([], _) do
523549
no()
524550
end

Diff for: lib/iex/test/iex/autocomplete_test.exs

+15
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,21 @@ defmodule IEx.AutocompleteTest do
184184
assert expand(~c"IEx.Xyz") == {:no, ~c"", []}
185185
end
186186

187+
test "block keywords" do
188+
assert expand(~c"if true do") == {:yes, ~c"", [~c"do"]}
189+
assert expand(~c"if true a") == {:yes, ~c"", [~c"after", ~c"and/2"]}
190+
assert expand(~c"if true d") == {:yes, ~c"o", []}
191+
assert expand(~c"if true e") == {:yes, ~c"", [~c"end", ~c"else"]}
192+
end
193+
194+
test "block keywords or operators" do
195+
{:yes, ~c"", hints} = expand(~c"if true ")
196+
assert ~c"do" in hints
197+
assert ~c"else" in hints
198+
assert ~c"+/2" in hints
199+
assert ~c"and/2" in hints
200+
end
201+
187202
test "function completion" do
188203
assert expand(~c"System.ve") == {:yes, ~c"rsion", []}
189204
assert expand(~c":ets.fun2") == {:yes, ~c"ms", []}

0 commit comments

Comments
 (0)