Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: defmodule snippet infer module name #398

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ defmodule NextLS do
documentation: docs
}

case NextLS.Snippet.get(label, nil) do
case NextLS.Snippet.get(label, nil, uri: uri) do
nil -> [completion_item | results]
%{} = snippet -> [Map.merge(completion_item, snippet) | results]
end
Expand Down
2 changes: 2 additions & 0 deletions lib/next_ls/helpers/ast_helpers/env.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ defmodule NextLS.ASTHelpers.Env do
}
end

def ascend(nil, acc, _callback), do: acc

Comment on lines +116 to +117
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this change necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def ascend(%Zipper{path: nil} = zipper, acc, callback), do: callback.(Zipper.node(zipper), zipper, acc)

def ascend(zipper, acc, callback) do
Expand Down
81 changes: 66 additions & 15 deletions lib/next_ls/snippet.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
defmodule NextLS.Snippet do
@moduledoc false

def get("defmodule/2", nil) do
def get(label, trigger_character, opts \\ [])

def get("defmodule/2", nil, opts) do
uri = Keyword.get(opts, :uri)

modulename =
if uri do
infer_module_name(uri)
else
"ModuleName"
end

%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
insert_text: """
defmodule ${1:ModuleName} do
defmodule ${1:#{modulename}} do
$0
end
"""
}
end

def get("defstruct/1", nil) do
def get("defstruct/1", nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -23,7 +34,7 @@ defmodule NextLS.Snippet do
}
end

def get("defprotocol/2", nil) do
def get("defprotocol/2", nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -35,7 +46,7 @@ defmodule NextLS.Snippet do
}
end

def get("defimpl/2", nil) do
def get("defimpl/2", nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -49,7 +60,7 @@ defmodule NextLS.Snippet do
}
end

def get("defimpl/3", nil) do
def get("defimpl/3", nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -63,7 +74,7 @@ defmodule NextLS.Snippet do
}
end

def get("def/" <> _, nil) do
def get("def/" <> _, nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -75,7 +86,7 @@ defmodule NextLS.Snippet do
}
end

def get("defp/" <> _, nil) do
def get("defp/" <> _, nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -87,7 +98,7 @@ defmodule NextLS.Snippet do
}
end

def get("defmacro/" <> _, nil) do
def get("defmacro/" <> _, nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -101,7 +112,7 @@ defmodule NextLS.Snippet do
}
end

def get("defmacrop/" <> _, nil) do
def get("defmacrop/" <> _, nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -115,7 +126,7 @@ defmodule NextLS.Snippet do
}
end

def get("for/" <> _, nil) do
def get("for/" <> _, nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -127,7 +138,7 @@ defmodule NextLS.Snippet do
}
end

def get("with/" <> _, nil) do
def get("with/" <> _, nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -139,7 +150,7 @@ defmodule NextLS.Snippet do
}
end

def get("case/" <> _, nil) do
def get("case/" <> _, nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -155,7 +166,7 @@ defmodule NextLS.Snippet do
}
end

def get("cond/" <> _, nil) do
def get("cond/" <> _, nil, _opts) do
%{
kind: GenLSP.Enumerations.CompletionItemKind.snippet(),
insert_text_format: GenLSP.Enumerations.InsertTextFormat.snippet(),
Expand All @@ -171,7 +182,47 @@ defmodule NextLS.Snippet do
}
end

def get(_label, _trigger_character) do
def get(_label, _trigger_character, _opts) do
nil
end

defp infer_module_name(uri) do
result =
uri
|> Path.split()
|> Enum.reduce(false, fn
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what the first element of the accumulator signifies here?

The clause with support is confusing me a tad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually the module in test/support don't use Test in the module name like https://github.com/elixir-tools/next-ls/blob/main/test/support/utils.ex

Can you explain what the first element of the accumulator signifies here?

do you mean false?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the tuple {:test, []}. I'm not sure what the atom is doing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used here to check if there is support under test

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the line that I am asking about.

I'm not sure where you use that later, you said something about tests ending in "Test", but i'm not seeing that either in this code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my bad, I'll try to explain.

I'm looking for three folders

  • lib where usually the code is, lib/foo.ex it's translated to Foo (without lib)
  • test where usually the tests are, test/foo_test.exs it's translated to FooTest (without test)
  • support where usually are utils for tests, test/support/foo.ex it's translated to Foo (without test and support)

here are some tests that can help https://github.com/elixir-tools/next-ls/blob/9d65b759ac0cf86c943190342cf829097fc62346/test/next_ls/snippet_test.exs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i understand now, thanks!

basically you are just striping lib/, test/ and test/support/ from the beginning for the path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes 👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucacervello When you use someone else's code, please mention the source. If possible, contacting the author would be best; otherwise, it appears quite unethical.

https://github.com/lexical-lsp/lexical/blob/2178b4beab5d1a04cb83990e09b3dc232ed941cb/apps/server/lib/lexical/server/code_intelligence/completion/translations/macro.ex#L567C5-L603C8

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the code on main.

Sorry about that!

"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 ->
uri
|> Path.basename()
|> Path.rootname()
|> Macro.camelize()
end
end
end
34 changes: 34 additions & 0 deletions test/next_ls/completions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,38 @@ defmodule NextLS.CompletionsTest do
"label" => "next_ls/"
} in results
end

test "defmodule infer name", %{client: client, foo: foo} do
uri = uri(foo)

did_open(client, foo, """
defmod
""")

request client, %{
method: "textDocument/completion",
id: 2,
jsonrpc: "2.0",
params: %{
textDocument: %{
uri: uri
},
position: %{
line: 0,
character: 6
}
}
}

assert_result 2, [
%{
"data" => nil,
"documentation" => _,
"insertText" => "defmodule ${1:Foo} do\n $0\nend\n",
lucacervello marked this conversation as resolved.
Show resolved Hide resolved
"kind" => 15,
"label" => "defmodule/2",
"insertTextFormat" => 2
}
]
end
end
37 changes: 37 additions & 0 deletions test/next_ls/snippet_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule NextLS.SnippetTest do
use ExUnit.Case, async: true

alias NextLS.Snippet

describe "defmodule snippet" do
test "simple module" do
assert %{insert_text: "defmodule ${1:Foo} do\n $0\nend\n", insert_text_format: 2, kind: 15} ==
Snippet.get("defmodule/2", nil, uri: "file:///my_proj/lib/foo.ex")
end

test "nested module" do
assert %{insert_text: "defmodule ${1:Foo.Bar.Baz} do\n $0\nend\n", insert_text_format: 2, kind: 15} ==
Snippet.get("defmodule/2", nil, uri: "file:///my_proj/lib/foo/bar/baz.ex")
end

test "test module" do
assert %{insert_text: "defmodule ${1:FooTest} do\n $0\nend\n", insert_text_format: 2, kind: 15} ==
Snippet.get("defmodule/2", nil, uri: "file:///my_proj/test/foo_test.exs")
end

test "support test module" do
assert %{insert_text: "defmodule ${1:Foo} do\n $0\nend\n", insert_text_format: 2, kind: 15} ==
Snippet.get("defmodule/2", nil, uri: "file:///my_proj/test/support/foo.ex")
end

test "module outside canonical folders" do
assert %{insert_text: "defmodule ${1:Foo} do\n $0\nend\n", insert_text_format: 2, kind: 15} ==
Snippet.get("defmodule/2", nil, uri: "file:///my_proj/foo.ex")
end

test "without uri" do
assert %{insert_text: "defmodule ${1:ModuleName} do\n $0\nend\n", insert_text_format: 2, kind: 15} ==
Snippet.get("defmodule/2", nil)
end
end
end
Loading