Skip to content

Commit 4fad185

Browse files
committed
insert needed requires as additional text edits
skip completions that would need to alter imports
1 parent 903cef5 commit 4fad185

File tree

2 files changed

+103
-24
lines changed

2 files changed

+103
-24
lines changed

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

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -349,15 +349,15 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
349349
&("alias " <> &1)
350350
)
351351

352-
struct(completion_without_additional_text_edit,
352+
%__MODULE__{completion_without_additional_text_edit |
353353
additional_text_edit: %TextEdit{
354354
range: range(line_to_insert_alias, 0, line_to_insert_alias, 0),
355355
newText: alias_edit
356356
},
357357
documentation: name <> "\n" <> summary,
358358
label_details: label_details,
359359
priority: 24
360-
)
360+
}
361361
end
362362

363363
defp from_completion_item(
@@ -673,6 +673,14 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
673673
),
674674
do: nil
675675

676+
# import with only or except was used and the completion would need to change it
677+
# this is not trivial to implement and most likely not wanted so let's skip that
678+
defp from_completion_item(
679+
%{needed_import: needed_import},
680+
_context,
681+
_options
682+
) when needed_import != nil, do: nil
683+
676684
defp from_completion_item(
677685
%{name: name, origin: origin} = item,
678686
%{def_before: nil} = context,
@@ -682,15 +690,39 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
682690

683691
completion =
684692
if origin == "Kernel" || origin == "Kernel.SpecialForms" do
685-
%{completion | kind: :keyword, priority: 18}
693+
%__MODULE__{completion | kind: :keyword, priority: 18}
686694
else
687695
completion
688696
end
689697

698+
completion = if item.needed_require do
699+
{line_to_insert_require, column_to_insert_require} = context.position_to_insert_alias
700+
indentation =
701+
if column_to_insert_require >= 1,
702+
do: 1..column_to_insert_require |> Enum.map_join(fn _ -> " " end),
703+
else: ""
704+
require_edit = indentation <> "require " <> item.needed_require <> "\n"
705+
label_details =
706+
Map.update!(
707+
completion.label_details,
708+
"description",
709+
&("require " <> &1)
710+
)
711+
%__MODULE__{completion |
712+
additional_text_edit: %TextEdit{
713+
range: range(line_to_insert_require, 0, line_to_insert_require, 0),
714+
newText: require_edit
715+
},
716+
label_details: label_details
717+
}
718+
else
719+
completion
720+
end
721+
690722
file_path = Keyword.get(options, :file_path)
691723

692724
if snippet = snippet_for({origin, name}, Map.put(context, :file_path, file_path)) do
693-
%{completion | insert_text: snippet, kind: :snippet, label: name}
725+
%__MODULE__{completion | insert_text: snippet, kind: :snippet, label: name}
694726
else
695727
completion
696728
end
@@ -977,7 +1009,6 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
9771009
defp function_completion(info, context, options) do
9781010
%{
9791011
type: type,
980-
args: args,
9811012
args_list: args_list,
9821013
name: name,
9831014
summary: summary,
@@ -986,15 +1017,10 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
9861017
origin: origin,
9871018
metadata: metadata
9881019
} = info
989-
990-
# ElixirSense now returns types as an atom
991-
type = to_string(type)
992-
9931020
%{
9941021
pipe_before?: pipe_before?,
9951022
capture_before?: capture_before?,
9961023
text_after_cursor: text_after_cursor,
997-
module: module
9981024
} = context
9991025

10001026
locals_without_parens = Keyword.get(options, :locals_without_parens)
@@ -1036,15 +1062,6 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
10361062
{label, insert_text}
10371063
end
10381064

1039-
detail_prefix =
1040-
if inspect(module) == origin do
1041-
"(#{type}) "
1042-
else
1043-
"(#{type}) #{origin}."
1044-
end
1045-
1046-
detail = Enum.join([detail_prefix, name, "(", args, ")"])
1047-
10481065
footer = SourceFile.format_spec(spec, line_length: 30)
10491066

10501067
command =
@@ -1058,7 +1075,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
10581075
%__MODULE__{
10591076
label: label,
10601077
kind: :function,
1061-
detail: detail,
1078+
detail: to_string(type),
10621079
label_details: %{
10631080
"detail" => "(#{Enum.join(args_list, ", ")})",
10641081
"description" => "#{origin}.#{name}/#{arity}"

apps/language_server/test/providers/completion_test.exs

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do
7373

7474
completions =
7575
items
76-
|> Enum.filter(&(&1["detail"] =~ "engineering_department"))
76+
|> Enum.filter(&(&1["label"] =~ "engineering_department"))
7777
|> Enum.map(& &1["insertText"])
7878

7979
assert completions == ["engineering_department()"]
@@ -483,6 +483,68 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do
483483
end
484484
end
485485

486+
describe "auto require" do
487+
test "suggests require as additionalTextEdits" do
488+
text = """
489+
defmodule MyModule do
490+
def dummy_function() do
491+
Logger.err
492+
# ^
493+
end
494+
end
495+
"""
496+
497+
{line, char} = {2, 14}
498+
TestUtils.assert_has_cursor_char(text, line, char)
499+
500+
{:ok, %{"items" => items}} = Completion.completion(text, line, char, @supports)
501+
502+
assert [item] = items
503+
504+
# 3 is function
505+
assert item["kind"] == 3
506+
assert item["label"] == "error"
507+
assert item["detail"] == "macro"
508+
assert item["labelDetails"]["detail"] == "(message_or_fun, metadata \\\\ [])"
509+
510+
assert item["labelDetails"]["description"] ==
511+
"require Logger.error/2"
512+
513+
assert [%{newText: " require Logger\n"}] =
514+
item["additionalTextEdits"]
515+
516+
assert [
517+
%{
518+
range: %{
519+
"end" => %{"character" => 0, "line" => 1},
520+
"start" => %{"character" => 0, "line" => 1}
521+
}
522+
}
523+
] = item["additionalTextEdits"]
524+
end
525+
end
526+
527+
describe "auto import" do
528+
test "no suggestion if import excluded" do
529+
text = """
530+
defmodule MyModule do
531+
import Enum, only: [all?: 1]
532+
def dummy_function() do
533+
cou
534+
# ^
535+
end
536+
end
537+
"""
538+
539+
{line, char} = {3, 7}
540+
TestUtils.assert_has_cursor_char(text, line, char)
541+
542+
{:ok, %{"items" => items}} = Completion.completion(text, line, char, @supports)
543+
544+
assert [] == items
545+
end
546+
end
547+
486548
describe "structs and maps" do
487549
test "completions of structs are rendered as a struct" do
488550
text = """
@@ -974,11 +1036,11 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do
9741036
{:ok, %{"items" => [pub, priv]}} = Completion.completion(text, line, char, @supports)
9751037

9761038
assert pub["label"] == "my_func"
977-
assert pub["detail"] == "(function) my_func(text)"
1039+
assert pub["detail"] == "function"
9781040
assert pub["labelDetails"]["detail"] == "(text)"
9791041
assert pub["labelDetails"]["description"] == "MyModule.my_func/1"
9801042
assert priv["label"] == "my_func_priv"
981-
assert priv["detail"] == "(function) my_func_priv(text)"
1043+
assert priv["detail"] == "function"
9821044
assert priv["labelDetails"]["detail"] == "(text)"
9831045
assert priv["labelDetails"]["description"] == "MyModule.my_func_priv/1"
9841046
end
@@ -1004,7 +1066,7 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do
10041066
{:ok, %{"items" => [item | _]}} = Completion.completion(text, line, char, @supports)
10051067

10061068
assert item["label"] == "func"
1007-
assert item["detail"] == "(function) RemoteMod.func()"
1069+
assert item["detail"] == "function"
10081070
assert item["labelDetails"]["detail"] == "()"
10091071
assert item["labelDetails"]["description"] == "RemoteMod.func/0"
10101072
end

0 commit comments

Comments
 (0)