diff --git a/apps/language_server/lib/language_server/providers/completion.ex b/apps/language_server/lib/language_server/providers/completion.ex index 03bd1d861..54bab289c 100644 --- a/apps/language_server/lib/language_server/providers/completion.ex +++ b/apps/language_server/lib/language_server/providers/completion.ex @@ -56,6 +56,13 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do {"ExUnit.Case", "describe"} => "describe \"$1\" do\n\t$0\nend" } + # Alternative snippets versions to be preferred when preceded by a pipe + @pipe_func_snippets %{ + {"Kernel.SpecialForms", "case"} => "case do\n\t$1 ->\n\t\t$0\nend", + {"Kernel", "if"} => "if do\n\t$0\nend", + {"Kernel", "unless"} => "unless do\n\t$0\nend" + } + @use_name_only MapSet.new([ {"Kernel", "not"}, {"Kernel", "use"}, @@ -493,7 +500,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do completion end - if snippet = Map.get(@func_snippets, {origin, name}) do + if snippet = snippet_for({origin, name}, context) do %{completion | insert_text: snippet, kind: :snippet, label: name} else completion @@ -504,6 +511,15 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do nil end + defp snippet_for(key, %{pipe_before?: true}) do + # Get pipe-friendly version of snippet if available, otherwise fallback to standard + Map.get(@pipe_func_snippets, key) || Map.get(@func_snippets, key) + end + + defp snippet_for(key, _context) do + Map.get(@func_snippets, key) + end + defp def_snippet(def_str, name, args, arity, opts) do if Keyword.get(opts, :snippets_supported, false) do "#{def_str}#{function_snippet(name, args, arity, opts)} do\n\t$0\nend" diff --git a/apps/language_server/test/providers/completion_test.exs b/apps/language_server/test/providers/completion_test.exs index f1d9c4aba..514576a2f 100644 --- a/apps/language_server/test/providers/completion_test.exs +++ b/apps/language_server/test/providers/completion_test.exs @@ -940,4 +940,68 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do refute Enum.any?(items, fn i -> i["label"] == "make_ref/0" end) end end + + describe "use the (arity - 1) version of snippets after pipe" do + test "case/2 snippet skips the condition argument" do + text = """ + defmodule MyModule do + def hello do + [1, 2] + |> Enum.random() + |> ca + # ^ + end + end + """ + + {line, char} = {4, 9} + TestUtils.assert_has_cursor_char(text, line, char) + assert {:ok, %{"items" => items}} = Completion.completion(text, line, char, @supports) + assert %{"insertText" => insert_text} = Enum.find(items, &match?(%{"label" => "case"}, &1)) + assert insert_text =~ "case do\n\t" + end + + test "unless/2 snippet skips the condition argument" do + text = """ + defmodule MyModule do + def hello do + [1, 2] + |> Enum.random() + |> unl + # ^ + end + end + """ + + {line, char} = {4, 10} + TestUtils.assert_has_cursor_char(text, line, char) + assert {:ok, %{"items" => items}} = Completion.completion(text, line, char, @supports) + + assert %{"insertText" => insert_text} = + Enum.find(items, &match?(%{"label" => "unless"}, &1)) + + assert insert_text =~ "unless do\n\t" + end + + test "if/2 snippet skips the condition argument" do + text = """ + defmodule MyModule do + def hello do + [1, 2] + |> Enum.random() + |> if + # ^ + end + end + """ + + {line, char} = {4, 9} + TestUtils.assert_has_cursor_char(text, line, char) + assert {:ok, %{"items" => items}} = Completion.completion(text, line, char, @supports) + + assert %{"insertText" => insert_text} = Enum.find(items, &match?(%{"label" => "if"}, &1)) + + assert insert_text =~ "if do\n\t" + end + end end