From a7abe78c11dd7213978bc9edc20ff76feb57da0d Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 11 Jul 2026 12:12:23 +0200 Subject: [PATCH] completion: restore bitstring modifier suggestions --- lib/elixir_sense/core/source.ex | 13 ++++++++++++- .../providers/completion/completion_engine.ex | 13 ++++++++++++- test/elixir_sense/core/source_test.exs | 4 ++++ .../providers/completion/suggestions_test.exs | 4 ++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/elixir_sense/core/source.ex b/lib/elixir_sense/core/source.ex index 8cb039d9..ca9102f2 100644 --- a/lib/elixir_sense/core/source.ex +++ b/lib/elixir_sense/core/source.ex @@ -735,7 +735,7 @@ defmodule ElixirSense.Core.Source do def concat_module_parts([], _, _), do: :error def bitstring_options(prefix) do - case Code.Fragment.container_cursor_to_quoted(prefix, columns: true) do + case bitstring_quoted(prefix) do {:ok, quoted} -> path = Macro.path(quoted, &match?({:__cursor__, _, []}, &1)) @@ -764,6 +764,17 @@ defmodule ElixirSense.Core.Source do end end + # A modifier prefix can be an Elixir reserved word (for example, `in`). In + # that case Code.Fragment fails before it has a chance to insert its cursor. + # Retrying with an identifier suffix makes the fragment parseable without + # changing the original text returned to the completion engine. + defp bitstring_quoted(prefix) do + case Code.Fragment.container_cursor_to_quoted(prefix, columns: true) do + {:error, _} -> Code.Fragment.container_cursor_to_quoted(prefix <> "_", columns: true) + result -> result + end + end + # Strip binary `-` operator wrappers introduced when typing `big-uns` etc. # Mirrors remove_operators/2 from elixir-ls completion_engine.ex (commit 98e983d). defp bitstring_remove_operators([{op, _, [_, previous]} = head | tail], previous) diff --git a/lib/elixir_sense/providers/completion/completion_engine.ex b/lib/elixir_sense/providers/completion/completion_engine.ex index 47c11892..ccde4c72 100644 --- a/lib/elixir_sense/providers/completion/completion_engine.ex +++ b/lib/elixir_sense/providers/completion/completion_engine.ex @@ -998,7 +998,7 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do end defp container_context(code, env, metadata, cursor_position) do - case Code.Fragment.container_cursor_to_quoted(code) do + case container_cursor_to_quoted(code) do {:ok, quoted} -> case Macro.path(quoted, &match?({:__cursor__, _, []}, &1)) do [cursor, {:%{}, _, pairs}, {:%, _, [struct_module_ast, _map]} | _] -> @@ -1056,6 +1056,17 @@ defmodule ElixirSense.Providers.Completion.CompletionEngine do end end + # A modifier prefix can be an Elixir reserved word (for example, `in`). In + # that case Code.Fragment fails before it has a chance to insert its cursor. + # Retrying with an identifier suffix makes the fragment parseable without + # affecting the original completion hint. + defp container_cursor_to_quoted(code) do + case Code.Fragment.container_cursor_to_quoted(code) do + {:error, _} -> Code.Fragment.container_cursor_to_quoted(code ++ ~c"_") + result -> result + end + end + defp remove_operators([{op, _, [_, previous]} = head | tail], previous) when op in [:-], do: remove_operators(tail, head) diff --git a/test/elixir_sense/core/source_test.exs b/test/elixir_sense/core/source_test.exs index d286e55b..433e28ed 100644 --- a/test/elixir_sense/core/source_test.exs +++ b/test/elixir_sense/core/source_test.exs @@ -1229,6 +1229,10 @@ defmodule ElixirSense.Core.SourceTest do assert "int" == bitstring_options(text) end + test "single line reserved word prefix" do + assert "in" == bitstring_options("< Enum.filter(&(&1.type == :bitstring_option)) + buffer = """ defmodule ElixirSenseExample.OtherModule do alias ElixirSenseExample.SameModule