Skip to content

Commit

Permalink
Suggest a module name for defmodule completion (#338)
Browse files Browse the repository at this point in the history
* Suggest module name for defmodule

The defmodule completion snippet now does a best-effort attempt to
guess the module name.

The guessing takes into account lib, test and test/support
directories, and if it doesn't detect any of them, just uses the file name.
  • Loading branch information
scohen authored Aug 25, 2023
1 parent f86c0fa commit 28501cb
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Macro do
alias Lexical.Document
alias Lexical.RemoteControl.Completion.Candidate
alias Lexical.Server.CodeIntelligence.Completion.Env
alias Lexical.Server.CodeIntelligence.Completion.Translatable
Expand Down Expand Up @@ -52,9 +53,10 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Macro do

def translate(%Candidate.Macro{name: "defmodule"} = macro, builder, env) do
label = "defmodule (Define a module)"
suggestion = suggest_module_name(env.document)

snippet = """
defmodule ${1:module name} do
defmodule ${1:#{suggestion}} do
$0
end
"""
Expand Down Expand Up @@ -561,4 +563,44 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Macro do
def translate(%Candidate.Macro{}, _builder, _env) do
:skip
end

def suggest_module_name(%Document{} = document) do
result =
document.path
|> Path.split()
|> Enum.reduce(false, fn
"lib", _ ->
{:lib, []}

"test", _ ->
{:test, []}

"support", {:test, _} ->
{:lib, []}

_, false ->
false

element, {type, elements} ->
camelized =
element
|> Path.rootname()
|> Macro.camelize()

{type, [camelized | elements]}
end)

case result do
{_, parts} ->
parts
|> Enum.reverse()
|> Enum.join(".")

false ->
document.path
|> Path.basename()
|> Path.rootname()
|> Macro.camelize()
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.MacroTest do
assert %Completion.Item{} = completion
end

test "defmodule", %{project: project} do
test "defmodule for lib paths", %{project: project} do
assert {:ok, completion} =
project
|> complete("defmodule|")
Expand All @@ -118,7 +118,58 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.MacroTest do
assert completion.insert_text_format == :snippet

assert completion.insert_text == """
defmodule ${1:module name} do
defmodule ${1:File} do
$0
end
"""
end

test "defmodule for test paths", %{project: project} do
assert {:ok, completion} =
project
|> complete("defmodule|", path: "test/foo/project/my_test.exs")
|> fetch_completion("defmodule ")

assert completion.detail
assert completion.label == "defmodule (Define a module)"
assert completion.insert_text_format == :snippet

assert completion.insert_text == """
defmodule ${1:Foo.Project.MyTest} do
$0
end
"""
end

test "defmodule for test/support paths", %{project: project} do
assert {:ok, completion} =
project
|> complete("defmodule|", path: "test/support/path/to/file.ex")
|> fetch_completion("defmodule ")

assert completion.detail
assert completion.label == "defmodule (Define a module)"
assert completion.insert_text_format == :snippet

assert completion.insert_text == """
defmodule ${1:Path.To.File} do
$0
end
"""
end

test "defmodule for other paths", %{project: project} do
assert {:ok, completion} =
project
|> complete("defmodule|", path: "/this/is/another/path.ex")
|> fetch_completion("defmodule ")

assert completion.detail
assert completion.label == "defmodule (Define a module)"
assert completion.insert_text_format == :snippet

assert completion.insert_text == """
defmodule ${1:Path} do
$0
end
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.StructTest do
test "when using %, child structs are returned", %{project: project} do
assert [account, order, order_line, user] =
project
|> complete("%Project.Structs.|", "%")
|> complete("%Project.Structs.|", trigger_character: "%")
|> Enum.sort_by(& &1.label)

assert account.label == "Account"
Expand All @@ -150,7 +150,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.StructTest do
test "when using % and child is not a struct, just `more` snippet is returned", %{
project: project
} do
assert [more] = complete(project, "%Project.|", "%")
assert [more] = complete(project, "%Project.|", trigger_character: "%")
assert more.label == "Structs...(4 more structs)"
assert more.detail == "Project.Structs."
end
Expand Down
20 changes: 17 additions & 3 deletions apps/server/test/support/lexical/test/completion_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,27 @@ defmodule Lexical.Test.Server.CompletionCase do
Document.to_string(edited_document)
end

def complete(project, text, trigger_character \\ nil) do
def complete(project, text, opts \\ []) do
trigger_character = Keyword.get(opts, :trigger_character)
{line, column} = cursor_position(text)

text = strip_cursor(text)

root_path = Project.root_path(project)
file_path = Path.join([root_path, "lib", "file.ex"])

file_path =
case Keyword.fetch(opts, :path) do
{:ok, path} ->
if Path.expand(path) == path do
# it's absolute
path
else
Path.join(root_path, path)
end

:error ->
Path.join([root_path, "lib", "file.ex"])
end

document =
file_path
Expand All @@ -59,7 +73,7 @@ defmodule Lexical.Test.Server.CompletionCase do
if is_binary(trigger_character) do
CompletionContext.new(
trigger_kind: :trigger_character,
trigger_character: trigger_character
trigger_character: nil
)
else
CompletionContext.new(trigger_kind: :trigger_character)
Expand Down

0 comments on commit 28501cb

Please sign in to comment.