Skip to content

Commit

Permalink
Fix the issue of completing the current module in NeoVim and VScode.(#…
Browse files Browse the repository at this point in the history
…224)

I believe that only in the context of functions and types will we use the structure of the current module. Therefore, I used these two methods to determine it. In addition, we should keep the behavior of structures consistent, so I adopted TextEdit.
  • Loading branch information
scottming committed Jul 25, 2023
1 parent 76b6ced commit 1bd442e
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 40 deletions.
37 changes: 35 additions & 2 deletions apps/server/lib/lexical/server/code_intelligence/completion/env.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,19 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Env do
{:ok, _line, {:struct, _}} ->
true

{:ok, line, {:local_or_var, [?_, ?_ | rest]}} ->
{:ok, _line, {:local_or_var, [?_ | _rest]}} ->
# a reference to `%__MODULE`, often in a function head, as in
# def foo(%__)
String.starts_with?("MODULE", List.to_string(rest)) and String.contains?(line, "%__")

starts_with_percent? =
env
|> prefix_tokens(2)
|> Enum.any?(fn
{:percent, :%, _} -> true
_ -> false
end)

starts_with_percent? and (ancestor_is_def?(env) or ancestor_is_type?(env))

_ ->
false
Expand Down Expand Up @@ -515,4 +524,28 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Env do
{:ok, tokens, ~c""}
end
end

defp ancestor_is_def?(env) do
env.document
|> Ast.cursor_path(env.position)
|> Enum.any?(fn
{:def, _, _} ->
true

{:defp, _, _} ->
true

_ ->
false
end)
end

defp ancestor_is_type?(env) do
env.document
|> Ast.cursor_path(env.position)
|> Enum.any?(fn
{:type, _, _} -> true
_ -> false
end)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Macro do
alias Lexical.Server.CodeIntelligence.Completion.Env
alias Lexical.Server.CodeIntelligence.Completion.Translatable
alias Lexical.Server.CodeIntelligence.Completion.Translations.Callable
alias Lexical.Server.CodeIntelligence.Completion.Translations.Struct

use Translatable.Impl, for: Candidate.Macro

Expand Down Expand Up @@ -512,12 +513,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Macro do

def translate(%Candidate.Macro{name: "__MODULE__"} = macro, builder, env) do
if Env.in_context?(env, :struct_reference) do
env
|> builder.snippet("%__MODULE__{$1}",
detail: "%__MODULE__{}",
label: "%__MODULE__{}",
kind: :struct
)
Struct.completion(env, builder, macro.name, macro.name)
else
env
|> builder.plain_text("__MODULE__",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,25 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Struct do
prefix_end

{:struct, typed_module_name} ->
edit_begin =
case left_offset_of(typed_module_name, ?.) do
{:ok, offset} ->
env.position.character - offset
beginning_of_edit(env, typed_module_name)

:error ->
env.position.character - length(typed_module_name)
end

edit_begin
{:local_or_var, [?_ | _rest] = typed} ->
beginning_of_edit(env, typed)
end

{edit_begin, env.position.character}
end

defp beginning_of_edit(env, typed_module_name) do
case left_offset_of(typed_module_name, ?.) do
{:ok, offset} ->
env.position.character - offset

:error ->
env.position.character - length(typed_module_name)
end
end

defp left_offset_of(string, character) do
string
|> Enum.reverse()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,20 @@ defmodule Lexical.Server.CodeIntelligence.Completion.EnvTest do
end

test "is true if a module reference starts in function arguments" do
env = new_env("def my_function(%__|)")
env = new_env("def my_function(%_|)")
assert in_context?(env, :struct_reference)
end

test "is ture if a module reference start in a t type spec" do
env = new_env("@type t :: %_|")
assert in_context?(env, :struct_reference)
end

test "is false if module reference not starts with %" do
env = new_env("def something(my_thing|, %Struct{})")
refute in_context?(env, :struct_reference)
end

test "is true if the reference is for %__MOD in a function definition " do
env = new_env("def my_fn(%__MOD")
assert in_context?(env, :struct_reference)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,18 +480,6 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.MacroTest do
assert completion.kind == :constant
end

test "__MODULE__ is suggested in a struct reference", %{project: project} do
assert {:ok, completion} =
project
|> complete("%__|")
|> fetch_completion("%__MODULE__")

assert completion.detail
assert completion.label == "%__MODULE__{}"
assert completion.insert_text_format == :snippet
assert completion.insert_text == "%__MODULE__{$1}"
end

test "__DIR__ is suggested", %{project: project} do
assert {:ok, completion} =
project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,37 +168,70 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.StructTest do
test "it should complete module structs", %{project: project} do
source = ~q{
defmodule NewStruct do
defstruct [:name, :value]
defstruct [:name, :value]
def my_function(%__|)
def my_function(%__|)
}

expected = ~q<
defmodule NewStruct do
defstruct [:name, :value]
def my_function(%__MODULE__{$1})
>

assert {:ok, completion} =
project
|> complete(source)
|> fetch_completion(kind: :struct)

assert completion.label == "%__MODULE__{}"
assert completion.detail == "%__MODULE__{}"
assert completion.kind == :struct
assert apply_completion(completion) == expected
end

test "it should complete module structs after characters are typed", %{project: project} do
source = ~q{
defmodule NewStruct do
defstruct [:name, :value]
defstruct [:name, :value]
def my_function(%__MO|)
def my_function(%__MO|)
}

expected = ~q<
defmodule NewStruct do
defstruct [:name, :value]
def my_function(%__MODULE__{$1})
>

assert {:ok, completion} =
project
|> complete(source)
|> fetch_completion(kind: :struct)

assert completion.label == "%__MODULE__{}"
assert completion.detail == "%__MODULE__{}"
assert completion.kind == :struct
assert apply_completion(completion) == expected
end

test "it should complete module structs when completing module type", %{project: project} do
source = ~q<
defmodule NewStruct do
defstruct [:name, :value]
@type t :: %_|
>

expected = ~q<
defmodule NewStruct do
defstruct [:name, :value]
@type t :: %__MODULE__{$1}
>

assert {:ok, completion} =
project
|> complete(source)
|> fetch_completion(kind: :struct)

assert apply_completion(completion) == expected
end

test "can be aliased", %{project: project} do
Expand Down

0 comments on commit 1bd442e

Please sign in to comment.