Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alias as additionalTextEdits #722

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions apps/language_server/lib/language_server/providers/completion.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand All @@ -21,7 +23,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
:priority,
:tags,
:command,
{:preselect, false}
{:preselect, false},
:additional_text_edit
]

@func_snippets %{
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions apps/language_server/test/providers/completion_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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"}] =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion before using the SourceFile module to convert positions was (notice the leading space characters)

assert [%{newText: "  alias ElixirLS.LanguageServer.Fixtures.ExampleStruct\n"}] =
               item["additionalTextEdits"]

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
Expand Down