summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorƁukasz Samson <lukaszsamson@gmail.com>2023-05-04 13:01:34 +0200
committerGitHub <noreply@github.com>2023-05-04 13:01:34 +0200
commitccbdd63070bd80299a3231767880bc9fe10d4903 (patch)
treeacae3f44bb663d3b3fd0424389b8d29a5db62263
parentac15a185fc0be4f69112eb570a3043ae90ada942 (diff)
downloadelixir-ccbdd63070bd80299a3231767880bc9fe10d4903.tar.gz
Make map/struct field completion work in map update (#12455)
-rw-r--r--lib/iex/lib/iex/autocomplete.ex81
-rw-r--r--lib/iex/test/iex/autocomplete_test.exs32
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", []}