From e996a387620bd3b861e23e5fadfd49ffcb2471fa Mon Sep 17 00:00:00 2001 From: Ajay <59688988+ajayvigneshk@users.noreply.github.com> Date: Tue, 1 Nov 2022 13:43:09 +0530 Subject: [PATCH] Add alias as additionalTextEdits (#722) --- .../language_server/providers/completion.ex | 65 ++++++++++++++++++- .../test/providers/completion_test.exs | 42 ++++++++++++ 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/apps/language_server/lib/language_server/providers/completion.ex b/apps/language_server/lib/language_server/providers/completion.ex index 295177070..3fdb672a0 100644 --- a/apps/language_server/lib/language_server/providers/completion.ex +++ b/apps/language_server/lib/language_server/providers/completion.ex @@ -7,7 +7,9 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do with the Language Server Protocol. We also attempt to determine the context based on the line text before the cursor so we can filter out suggestions that are not relevant. """ + alias ElixirLS.LanguageServer.Protocol.TextEdit alias ElixirLS.LanguageServer.SourceFile + import ElixirLS.LanguageServer.Protocol, only: [range: 4] @enforce_keys [:label, :kind, :insert_text, :priority, :tags] defstruct [ @@ -21,7 +23,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do :priority, :tags, :command, - {:preselect, false} + {:preselect, false}, + :additional_text_edit ] @func_snippets %{ @@ -102,8 +105,10 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do # TODO: Don't call into here directly # Can we use ElixirSense.Providers.Suggestion? ElixirSense.suggestions/3 + metadata = ElixirSense.Core.Parser.parse_string(text, true, true, line) + env = - ElixirSense.Core.Parser.parse_string(text, true, true, line) + metadata |> ElixirSense.Core.Metadata.get_env(line) scope = @@ -139,8 +144,18 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do module: env.module } + position_to_insert_alias = + ElixirSense.Core.Metadata.get_position_to_insert_alias(metadata, line) || {line, 0} + + context = + Map.put( + context, + :position_to_insert_alias, + SourceFile.elixir_position_to_lsp(text, position_to_insert_alias) + ) + items = - ElixirSense.suggestions(text, line, character) + ElixirSense.suggestions(text, line, character, required_alias: true) |> maybe_reject_derived_functions(context, options) |> Enum.map(&from_completion_item(&1, context, options)) |> maybe_add_do(context) @@ -285,6 +300,44 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do } end + defp from_completion_item( + %{ + type: :module, + name: name, + summary: summary, + subtype: subtype, + metadata: metadata, + required_alias: required_alias + }, + %{ + def_before: nil, + position_to_insert_alias: {line_to_insert_alias, column_to_insert_alias} + }, + options + ) do + completion_without_additional_text_edit = + from_completion_item( + %{type: :module, name: name, summary: summary, subtype: subtype, metadata: metadata}, + %{def_before: nil}, + options + ) + + alias_value = + Atom.to_string(required_alias) + |> String.replace_prefix("Elixir.", "") + + indentation = 1..column_to_insert_alias//1 |> Enum.map(fn _ -> " " end) |> Enum.join() + alias_edit = indentation <> "alias " <> alias_value <> "\n" + + struct(completion_without_additional_text_edit, + additional_text_edit: %TextEdit{ + range: range(line_to_insert_alias, 0, line_to_insert_alias, 0), + newText: alias_edit + }, + documentation: alias_value <> "\n" <> summary + ) + end + defp from_completion_item( %{type: :module, name: name, summary: summary, subtype: subtype, metadata: metadata}, %{def_before: nil}, @@ -989,6 +1042,12 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do "filterText" => item.filter_text, "sortText" => String.pad_leading(to_string(idx), 8, "0"), "insertText" => item.insert_text, + "additionalTextEdits" => + if item.additional_text_edit do + [item.additional_text_edit] + else + nil + end, "command" => item.command, "insertTextFormat" => if Keyword.get(options, :snippets_supported, false) do diff --git a/apps/language_server/test/providers/completion_test.exs b/apps/language_server/test/providers/completion_test.exs index 941b32242..41d2a7c6b 100644 --- a/apps/language_server/test/providers/completion_test.exs +++ b/apps/language_server/test/providers/completion_test.exs @@ -368,6 +368,48 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do end describe "structs and maps" do + test "suggests full module path as additionalTextEdits" do + text = """ + defmodule MyModule do + @moduledoc \"\"\" + This + is a + long + moduledoc + + \"\"\" + + def dummy_function() do + ExampleS + # ^ + end + end + """ + + {line, char} = {10, 12} + TestUtils.assert_has_cursor_char(text, line, char) + + {:ok, %{"items" => items}} = Completion.completion(text, line, char, @supports) + + assert [item] = items + + # 22 is struct + assert item["kind"] == 22 + assert item["label"] == "ExampleStruct (struct)" + + assert [%{newText: "alias ElixirLS.LanguageServer.Fixtures.ExampleStruct\n"}] = + item["additionalTextEdits"] + + assert [ + %{ + range: %{ + "end" => %{"character" => 0, "line" => 8}, + "start" => %{"character" => 0, "line" => 8} + } + } + ] = item["additionalTextEdits"] + end + test "completions of structs are rendered as a struct" do text = """ defmodule MyModule do