From 4151895cc009fa4ab0344ed1fe455ed40e666830 Mon Sep 17 00:00:00 2001 From: Luca Cervello Date: Fri, 15 Mar 2024 17:02:22 +0100 Subject: [PATCH] feat: defmodule snippet infer module name (#398) --- lib/next_ls.ex | 2 +- lib/next_ls/helpers/ast_helpers/env.ex | 2 + lib/next_ls/snippet.ex | 81 +++++++++++++++++++++----- test/next_ls/completions_test.exs | 34 +++++++++++ test/next_ls/snippet_test.exs | 37 ++++++++++++ 5 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 test/next_ls/snippet_test.exs diff --git a/lib/next_ls.ex b/lib/next_ls.ex index b320f6d5..c51b8e11 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -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 diff --git a/lib/next_ls/helpers/ast_helpers/env.ex b/lib/next_ls/helpers/ast_helpers/env.ex index 345da2ef..e2808d85 100644 --- a/lib/next_ls/helpers/ast_helpers/env.ex +++ b/lib/next_ls/helpers/ast_helpers/env.ex @@ -113,6 +113,8 @@ defmodule NextLS.ASTHelpers.Env do } end + def ascend(nil, acc, _callback), do: acc + def ascend(%Zipper{path: nil} = zipper, acc, callback), do: callback.(Zipper.node(zipper), zipper, acc) def ascend(zipper, acc, callback) do diff --git a/lib/next_ls/snippet.ex b/lib/next_ls/snippet.ex index ec92d90c..447160cb 100644 --- a/lib/next_ls/snippet.ex +++ b/lib/next_ls/snippet.ex @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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 + "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 diff --git a/test/next_ls/completions_test.exs b/test/next_ls/completions_test.exs index 22535849..e90931d6 100644 --- a/test/next_ls/completions_test.exs +++ b/test/next_ls/completions_test.exs @@ -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", + "kind" => 15, + "label" => "defmodule/2", + "insertTextFormat" => 2 + } + ] + end end diff --git a/test/next_ls/snippet_test.exs b/test/next_ls/snippet_test.exs new file mode 100644 index 00000000..6898480b --- /dev/null +++ b/test/next_ls/snippet_test.exs @@ -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