diff --git a/apps/language_server/lib/language_server/providers/completion.ex b/apps/language_server/lib/language_server/providers/completion.ex index bb8c1d3b7..42e17fddb 100644 --- a/apps/language_server/lib/language_server/providers/completion.ex +++ b/apps/language_server/lib/language_server/providers/completion.ex @@ -246,9 +246,25 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do "module" end + kind = + case subtype do + :behaviour -> :interface + :protocol -> :interface + :exception -> :struct + :struct -> :struct + _ -> :module + end + + label = + if subtype do + "#{name} (#{subtype})" + else + name + end + %__MODULE__{ - label: name, - kind: :module, + label: label, + kind: kind, detail: detail, documentation: summary, insert_text: name, @@ -582,8 +598,9 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do text != "" end - defp completion_kind(type) do - case type do + # LSP CompletionItemKind enumeration + defp completion_kind(kind) do + case kind do :text -> 1 :method -> 2 :function -> 3 diff --git a/apps/language_server/test/providers/completion_test.exs b/apps/language_server/test/providers/completion_test.exs index 10b6e689f..09c0a6bf0 100644 --- a/apps/language_server/test/providers/completion_test.exs +++ b/apps/language_server/test/providers/completion_test.exs @@ -110,6 +110,26 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do assert length(items) == 0 end + test "completions of protocols are rendered as an interface" do + text = """ + defmodule MyModule do + def dummy_function() do + ElixirLS.LanguageServer.Fixtures.ExampleP + # ^ + end + end + """ + + {line, char} = {2, 45} + TestUtils.assert_has_cursor_char(text, line, char) + + {:ok, %{"items" => [item]}} = Completion.completion(text, line, char, @supports) + + # 8 is interface + assert item["kind"] == 8 + assert item["label"] == "ExampleProtocol (protocol)" + end + test "provides completions for protocol functions" do text = """ defimpl ElixirLS.LanguageServer.Fixtures.ExampleProtocol, for: MyModule do @@ -132,6 +152,50 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do ] end + test "completions of behaviours are rendered as an interface" do + text = """ + defmodule MyModule do + def dummy_function() do + ElixirLS.LanguageServer.Fixtures.ExampleB + # ^ + end + end + """ + + {line, char} = {2, 45} + TestUtils.assert_has_cursor_char(text, line, char) + + {:ok, %{"items" => items}} = Completion.completion(text, line, char, @supports) + + assert [item, _] = items + + # 8 is interface + assert item["kind"] == 8 + assert item["label"] == "ExampleBehaviour (behaviour)" + end + + test "completions of exceptions are rendered as a struct" do + text = """ + defmodule MyModule do + def dummy_function() do + ElixirLS.LanguageServer.Fixtures.ExampleE + # ^ + end + end + """ + + {line, char} = {2, 45} + 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"] == "ExampleException (exception)" + end + test "provides completions for callbacks without `def` before" do text = """ defmodule MyModule do @@ -199,7 +263,7 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do |> Enum.filter(&(&1["detail"] =~ "struct")) |> Enum.map(& &1["label"]) - assert "NaiveDateTime" in completions + assert "NaiveDateTime (struct)" in completions {line, char} = {4, 17} TestUtils.assert_has_cursor_char(text, line, char) @@ -221,7 +285,7 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do |> Enum.filter(&(&1["detail"] =~ "struct")) |> Enum.map(& &1["label"]) - assert "NaiveDateTime" in completions + assert "NaiveDateTime (struct)" in completions end describe "deprecated" do @@ -257,6 +321,28 @@ defmodule ElixirLS.LanguageServer.Providers.CompletionTest do end describe "structs and maps" do + test "completions of structs are rendered as a struct" do + text = """ + defmodule MyModule do + def dummy_function() do + ElixirLS.LanguageServer.Fixtures.ExampleS + # ^ + end + end + """ + + {line, char} = {2, 45} + 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)" + end + test "returns struct fields in call syntax" do text = """ defmodule MyModule do diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 2bc432d03..5f9eda8e0 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -675,8 +675,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do %{ "detail" => "behaviour", "documentation" => _, - "kind" => 9, - "label" => "GenServer" + "kind" => 8, + "label" => "GenServer (behaviour)" } | _ ] diff --git a/apps/language_server/test/support/fixtures/example_exception.ex b/apps/language_server/test/support/fixtures/example_exception.ex new file mode 100644 index 000000000..cf41ee879 --- /dev/null +++ b/apps/language_server/test/support/fixtures/example_exception.ex @@ -0,0 +1,3 @@ +defmodule ElixirLS.LanguageServer.Fixtures.ExampleException do + defexception [:message] +end diff --git a/apps/language_server/test/support/fixtures/example_struct.ex b/apps/language_server/test/support/fixtures/example_struct.ex new file mode 100644 index 000000000..86a1b730a --- /dev/null +++ b/apps/language_server/test/support/fixtures/example_struct.ex @@ -0,0 +1,3 @@ +defmodule ElixirLS.LanguageServer.Fixtures.ExampleStruct do + defstruct [:name] +end