Skip to content

Commit e996a38

Browse files
authored
Add alias as additionalTextEdits (#722)
1 parent 685c2fa commit e996a38

File tree

2 files changed

+104
-3
lines changed

2 files changed

+104
-3
lines changed

apps/language_server/lib/language_server/providers/completion.ex

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
77
with the Language Server Protocol. We also attempt to determine the context based on the line
88
text before the cursor so we can filter out suggestions that are not relevant.
99
"""
10+
alias ElixirLS.LanguageServer.Protocol.TextEdit
1011
alias ElixirLS.LanguageServer.SourceFile
12+
import ElixirLS.LanguageServer.Protocol, only: [range: 4]
1113

1214
@enforce_keys [:label, :kind, :insert_text, :priority, :tags]
1315
defstruct [
@@ -21,7 +23,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
2123
:priority,
2224
:tags,
2325
:command,
24-
{:preselect, false}
26+
{:preselect, false},
27+
:additional_text_edit
2528
]
2629

2730
@func_snippets %{
@@ -102,8 +105,10 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
102105

103106
# TODO: Don't call into here directly
104107
# Can we use ElixirSense.Providers.Suggestion? ElixirSense.suggestions/3
108+
metadata = ElixirSense.Core.Parser.parse_string(text, true, true, line)
109+
105110
env =
106-
ElixirSense.Core.Parser.parse_string(text, true, true, line)
111+
metadata
107112
|> ElixirSense.Core.Metadata.get_env(line)
108113

109114
scope =
@@ -139,8 +144,18 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
139144
module: env.module
140145
}
141146

147+
position_to_insert_alias =
148+
ElixirSense.Core.Metadata.get_position_to_insert_alias(metadata, line) || {line, 0}
149+
150+
context =
151+
Map.put(
152+
context,
153+
:position_to_insert_alias,
154+
SourceFile.elixir_position_to_lsp(text, position_to_insert_alias)
155+
)
156+
142157
items =
143-
ElixirSense.suggestions(text, line, character)
158+
ElixirSense.suggestions(text, line, character, required_alias: true)
144159
|> maybe_reject_derived_functions(context, options)
145160
|> Enum.map(&from_completion_item(&1, context, options))
146161
|> maybe_add_do(context)
@@ -285,6 +300,44 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
285300
}
286301
end
287302

303+
defp from_completion_item(
304+
%{
305+
type: :module,
306+
name: name,
307+
summary: summary,
308+
subtype: subtype,
309+
metadata: metadata,
310+
required_alias: required_alias
311+
},
312+
%{
313+
def_before: nil,
314+
position_to_insert_alias: {line_to_insert_alias, column_to_insert_alias}
315+
},
316+
options
317+
) do
318+
completion_without_additional_text_edit =
319+
from_completion_item(
320+
%{type: :module, name: name, summary: summary, subtype: subtype, metadata: metadata},
321+
%{def_before: nil},
322+
options
323+
)
324+
325+
alias_value =
326+
Atom.to_string(required_alias)
327+
|> String.replace_prefix("Elixir.", "")
328+
329+
indentation = 1..column_to_insert_alias//1 |> Enum.map(fn _ -> " " end) |> Enum.join()
330+
alias_edit = indentation <> "alias " <> alias_value <> "\n"
331+
332+
struct(completion_without_additional_text_edit,
333+
additional_text_edit: %TextEdit{
334+
range: range(line_to_insert_alias, 0, line_to_insert_alias, 0),
335+
newText: alias_edit
336+
},
337+
documentation: alias_value <> "\n" <> summary
338+
)
339+
end
340+
288341
defp from_completion_item(
289342
%{type: :module, name: name, summary: summary, subtype: subtype, metadata: metadata},
290343
%{def_before: nil},
@@ -989,6 +1042,12 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
9891042
"filterText" => item.filter_text,
9901043
"sortText" => String.pad_leading(to_string(idx), 8, "0"),
9911044
"insertText" => item.insert_text,
1045+
"additionalTextEdits" =>
1046+
if item.additional_text_edit do
1047+
[item.additional_text_edit]
1048+
else
1049+
nil
1050+
end,
9921051
"command" => item.command,
9931052
"insertTextFormat" =>
9941053
if Keyword.get(options, :snippets_supported, false) do

apps/language_server/test/providers/completion_test.exs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,48 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do
368368
end
369369

370370
describe "structs and maps" do
371+
test "suggests full module path as additionalTextEdits" do
372+
text = """
373+
defmodule MyModule do
374+
@moduledoc \"\"\"
375+
This
376+
is a
377+
long
378+
moduledoc
379+
380+
\"\"\"
381+
382+
def dummy_function() do
383+
ExampleS
384+
# ^
385+
end
386+
end
387+
"""
388+
389+
{line, char} = {10, 12}
390+
TestUtils.assert_has_cursor_char(text, line, char)
391+
392+
{:ok, %{"items" => items}} = Completion.completion(text, line, char, @supports)
393+
394+
assert [item] = items
395+
396+
# 22 is struct
397+
assert item["kind"] == 22
398+
assert item["label"] == "ExampleStruct (struct)"
399+
400+
assert [%{newText: "alias ElixirLS.LanguageServer.Fixtures.ExampleStruct\n"}] =
401+
item["additionalTextEdits"]
402+
403+
assert [
404+
%{
405+
range: %{
406+
"end" => %{"character" => 0, "line" => 8},
407+
"start" => %{"character" => 0, "line" => 8}
408+
}
409+
}
410+
] = item["additionalTextEdits"]
411+
end
412+
371413
test "completions of structs are rendered as a struct" do
372414
text = """
373415
defmodule MyModule do

0 commit comments

Comments
 (0)