diff options
author | Ćukasz Samson <lukaszsamson@gmail.com> | 2023-05-04 13:01:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-04 13:01:34 +0200 |
commit | ccbdd63070bd80299a3231767880bc9fe10d4903 (patch) | |
tree | acae3f44bb663d3b3fd0424389b8d29a5db62263 | |
parent | ac15a185fc0be4f69112eb570a3043ae90ada942 (diff) | |
download | elixir-ccbdd63070bd80299a3231767880bc9fe10d4903.tar.gz |
Make map/struct field completion work in map update (#12455)
-rw-r--r-- | lib/iex/lib/iex/autocomplete.ex | 81 | ||||
-rw-r--r-- | lib/iex/test/iex/autocomplete_test.exs | 32 |
2 files changed, 91 insertions, 22 deletions
diff --git a/lib/iex/lib/iex/autocomplete.ex b/lib/iex/lib/iex/autocomplete.ex index 3b7550fbe..e4a7d1a78 100644 --- a/lib/iex/lib/iex/autocomplete.ex +++ b/lib/iex/lib/iex/autocomplete.ex @@ -93,6 +93,9 @@ defmodule IEx.Autocomplete do {:operator_arity, operator} -> expand_local(List.to_string(operator), true, shell) + {:operator_call, operator} when operator in ~w(|)c -> + expand_container_context(code, :expr, "", shell) || expand_local_or_var("", shell) + {:operator_call, _operator} -> expand_local_or_var("", shell) @@ -318,22 +321,12 @@ defmodule IEx.Autocomplete do defp expand_container_context(code, context, hint, shell) do case container_context(code, shell) do + {:map, map, pairs} when context == :expr -> + container_context_map_fields(pairs, map, hint) + {:struct, alias, pairs} when context == :expr -> - pairs = - Enum.reduce(pairs, Map.from_struct(alias.__struct__), fn {key, _}, map -> - Map.delete(map, key) - end) - - entries = - for {key, _value} <- pairs, - name = Atom.to_string(key), - if(hint == "", - do: not String.starts_with?(name, "_"), - else: String.starts_with?(name, hint) - ), - do: %{kind: :keyword, name: name} - - format_expansion(entries, hint) + map = Map.from_struct(alias.__struct__) + container_context_map_fields(pairs, map, hint) :bitstring_modifier -> existing = @@ -352,18 +345,42 @@ defmodule IEx.Autocomplete do end end + defp container_context_map_fields(pairs, map, hint) do + pairs = + Enum.reduce(pairs, map, fn {key, _}, map -> + Map.delete(map, key) + end) + + entries = + for {key, _value} <- pairs, + name = Atom.to_string(key), + if(hint == "", + do: not String.starts_with?(name, "_"), + else: String.starts_with?(name, hint) + ), + do: %{kind: :keyword, name: name} + + format_expansion(entries, hint) + end + defp container_context(code, shell) do case Code.Fragment.container_cursor_to_quoted(code) do {:ok, quoted} -> case Macro.path(quoted, &match?({:__cursor__, _, []}, &1)) do [cursor, {:%{}, _, pairs}, {:%, _, [{:__aliases__, _, aliases}, _map]} | _] -> - with {pairs, [^cursor]} <- Enum.split(pairs, -1), - alias = value_from_alias(aliases, shell), - true <- Keyword.keyword?(pairs) and struct?(alias) do - {:struct, alias, pairs} - else - _ -> nil - end + container_context_struct(cursor, pairs, aliases, shell) + + [ + cursor, + pairs, + {:|, _, _}, + {:%{}, _, _}, + {:%, _, [{:__aliases__, _, aliases}, _map]} | _ + ] -> + container_context_struct(cursor, pairs, aliases, shell) + + [cursor, pairs, {:|, _, [{variable, _, nil} | _]}, {:%{}, _, _} | _] -> + container_context_map(cursor, pairs, variable, shell) [cursor, {:"::", _, [_, cursor]}, {:<<>>, _, [_ | _]} | _] -> :bitstring_modifier @@ -377,6 +394,26 @@ defmodule IEx.Autocomplete do end end + defp container_context_struct(cursor, pairs, aliases, shell) do + with {pairs, [^cursor]} <- Enum.split(pairs, -1), + alias = value_from_alias(aliases, shell), + true <- Keyword.keyword?(pairs) and struct?(alias) do + {:struct, alias, pairs} + else + _ -> nil + end + end + + defp container_context_map(cursor, pairs, variable, shell) do + with {pairs, [^cursor]} <- Enum.split(pairs, -1), + {:ok, map} when is_map(map) <- value_from_binding([variable], shell), + true <- Keyword.keyword?(pairs) do + {:map, map, pairs} + else + _ -> nil + end + end + ## Aliases and modules defp expand_aliases(all, shell) do diff --git a/lib/iex/test/iex/autocomplete_test.exs b/lib/iex/test/iex/autocomplete_test.exs index 36190d576..dd4a5be42 100644 --- a/lib/iex/test/iex/autocomplete_test.exs +++ b/lib/iex/test/iex/autocomplete_test.exs @@ -418,6 +418,38 @@ defmodule IEx.AutocompleteTest do assert {:no, [], []} = expand(~c"%Unkown{path: \"foo\", unkno") end + test "completion for struct keys in update syntax" do + assert {:yes, ~c"", entries} = expand(~c"%URI{var | ") + assert ~c"path:" in entries + assert ~c"query:" in entries + + assert {:yes, ~c"", entries} = expand(~c"%URI{var | path: \"foo\",") + assert ~c"path:" not in entries + assert ~c"query:" in entries + + assert {:yes, ~c"ry: ", []} = expand(~c"%URI{var | path: \"foo\", que") + assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno") + assert {:no, [], []} = expand(~c"%Unkown{var | path: \"foo\", unkno") + end + + test "completion for map keys in update syntax" do + eval("map = %{some: 1, other: :ok, another: \"qwe\"}") + # Code.Fragment.container_cursor_to_quoted returns + # {:%{}, [line: 1], [{:__cursor__, [line: 1], []}]} + # and `map` variable and map update AST is lost + # assert {:yes, ~c"", entries} = expand(~c"%{map | ") + # assert ~c"some:" in entries + # assert ~c"other:" in entries + + assert {:yes, ~c"", entries} = expand(~c"%{map | some: \"foo\",") + assert ~c"some:" not in entries + assert ~c"other:" in entries + + assert {:yes, ~c"er: ", []} = expand(~c"%{map | some: \"foo\", oth") + assert {:no, [], []} = expand(~c"%{map | some: \"foo\", unkno") + assert {:no, [], []} = expand(~c"%{unknown | some: \"foo\", unkno") + end + test "completion for struct var keys" do eval("struct = %IEx.AutocompleteTest.MyStruct{}") assert expand(~c"struct.my") == {:yes, ~c"_val", []} |