Skip to content

Commit

Permalink
fixup! refactor(completions): improve cursor position detection
Browse files Browse the repository at this point in the history
  • Loading branch information
mhanberg committed May 1, 2024
1 parent e985a32 commit e09f262
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 79 deletions.
76 changes: 58 additions & 18 deletions lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -588,21 +588,43 @@ defmodule NextLS do

source = Enum.join(document, "\n")

ast =
source
|> Spitfire.parse(literal_encoder: &{:ok, {:__block__, [{:literal, true} | &2], [&1]}})
|> then(fn
{:ok, ast} -> ast
{:error, ast, _} -> ast
{:error, :no_fuel_remaining} -> nil
end)
{ms, ast} =
:timer.tc(
fn ->
source
|> Spitfire.parse()
|> then(fn
{:ok, ast} -> ast
{:error, ast, _} -> ast
{:error, :no_fuel_remaining} -> nil
end)
end,
:millisecond
)

Logger.debug("parsing: #{ms}ms")

with_cursor_zipper =
NextLS.ASTHelpers.find_cursor(ast, line: position.line + 1, column: position.character + 1)
{ms, with_cursor_zipper} =
:timer.tc(
fn ->
NextLS.ASTHelpers.find_cursor(ast, line: position.line + 1, column: position.character + 1)
end,
:millisecond
)

Logger.debug("find_cursor: #{ms}ms")

# dbg(Sourceror.Zipper.node(with_cursor_zipper))

env = NextLS.ASTHelpers.Env.build(with_cursor_zipper, %{line: position.line + 1, column: position.character + 1})
{ms, env} =
:timer.tc(
fn ->
NextLS.ASTHelpers.Env.build(with_cursor_zipper, %{line: position.line + 1, column: position.character + 1})
end,
:millisecond
)

Logger.debug("build env: #{ms}ms")

document_slice =
document
Expand All @@ -621,8 +643,15 @@ defmodule NextLS do
for {runtime, %{uri: wuri}} <- entries, String.starts_with?(uri, wuri) do
ast = Sourceror.Zipper.node(with_cursor_zipper)

{:ok, {_, _, _, macro_env}} =
Runtime.expand(runtime, ast, Path.basename(uri))
{ms, {:ok, {_, _, _, macro_env}}} =
:timer.tc(
fn ->
Runtime.expand(runtime, ast, Path.basename(uri))
end,
:millisecond
)

Logger.debug("expand: #{ms}ms")

env =
env
Expand All @@ -631,11 +660,22 @@ defmodule NextLS do
|> Map.put(:aliases, macro_env.aliases)
|> Map.put(:attrs, macro_env.attrs)

{wuri,
document_slice
|> String.to_charlist()
|> Enum.reverse()
|> NextLS.Autocomplete.expand(runtime, env)}
doc =
document_slice
|> String.to_charlist()
|> Enum.reverse()

{ms, result} =
:timer.tc(
fn ->
NextLS.Autocomplete.expand(doc, runtime, env)
end,
:millisecond
)

Logger.debug("complete: #{ms}ms")

{wuri, result}
end

case result do
Expand Down
98 changes: 39 additions & 59 deletions lib/next_ls/helpers/ast_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -207,86 +207,66 @@ defmodule NextLS.ASTHelpers do
def find_cursor(ast, position) do
ast = {:__block__, [], [ast]}

{tree, id} =
{tree, cursor_tree} =
ast
|> Zipper.zip()
|> Zipper.traverse(0, fn tree, id ->
|> Zipper.traverse(nil, fn tree, acc ->
node = Zipper.node(tree)
node_range = Sourceror.Range.get_range(node)
is_literal = Macro.quoted_literal?(node)

is_inside =
with nil <- node_range do
with nil <- node_range, is_literal do
false
else
_ -> sourceror_inside?(node_range, position)
end

tree =
case node do
{_, [{:literal, true} | _], [literal]} -> Zipper.replace(tree, literal)
_ -> tree
end

if is_inside do
id = id + 1
cond do
is_literal ->
{tree, acc}

tree =
Zipper.update(tree, fn node ->
Macro.update_meta(node, fn meta ->
Keyword.put(meta, :id, id)
end)
end)
is_inside ->
{tree, tree}

{tree, id}
else
{tree, id}
true ->
{tree, acc}
end
end)

# PERF: doing a second pass, can we improve?
Zipper.traverse(tree, fn tree ->
node = Zipper.node(tree)

case node do
{_, [{:id, cid} | _], _} ->
tree =
Zipper.update(tree, fn node ->
Macro.update_meta(node, fn meta ->
Keyword.delete(meta, :id)
end)
end)

tree =
if cid == id do
# insert our "cursor" node
left = Zipper.left(tree)
up = Zipper.up(tree)

tree =
cond do
up && match?({:<-, _, _}, Zipper.node(up)) ->
up |> Zipper.insert_left({:__cursor__, [], []}) |> Zipper.down() |> Zipper.rightmost()
if cursor_tree do
left = Zipper.left(cursor_tree)
up = Zipper.up(cursor_tree)

left && Zipper.node(left) == :do ->
Zipper.update(tree, fn n ->
{:__block__, [], [n, {:__cursor__, [], []}]}
end)
cond_result =
cond do
up && match?({:"::", _, _}, Zipper.node(up)) ->
up
|> Zipper.up()
|> Zipper.update(fn n ->
{:__block__, [],
[
{:__cursor__, [], []},
n
]}
end)

true ->
Zipper.insert_right(tree, {:__cursor__, [], []})
end
up && match?({:<-, _, _}, Zipper.node(up)) ->
up |> Zipper.insert_left({:__cursor__, [], []}) |> Zipper.down() |> Zipper.rightmost()

tree
else
tree
end
left && Zipper.node(left) == :do ->
Zipper.update(cursor_tree, fn n ->
{:__block__, [], [n, {:__cursor__, [], []}]}
end)

tree
true ->
Zipper.insert_right(cursor_tree, {:__cursor__, [], []})
end

_ ->
tree
end
end)
Zipper.top(cond_result)
else
tree
end
end

def top(nil, acc, _callback), do: acc
Expand Down
4 changes: 2 additions & 2 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"req": {:hex, :req, "0.4.0", "1c759054dd64ef1b1a0e475c2d2543250d18f08395d3174c371b7746984579ce", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.9", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "f53eadc32ebefd3e5d50390356ec3a59ed2b8513f7da8c6c3f2e14040e9fe989"},
"schematic": {:hex, :schematic, "0.2.1", "0b091df94146fd15a0a343d1bd179a6c5a58562527746dadd09477311698dbb1", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0b255d65921e38006138201cd4263fd8bb807d9dfc511074615cd264a571b3b1"},
"sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"},
"spitfire": {:git, "https://github.com/elixir-tools/spitfire.git", "f913c6025875c9d69b4d35f94cae3e70c7f6320e", []},
"sourceror": {:hex, :sourceror, "1.0.3", "111711c147f4f1414c07a67b45ad0064a7a41569037355407eda635649507f1d", [:mix], [], "hexpm", "56c21ef146c00b51bc3bb78d1f047cb732d193256a7c4ba91eaf828d3ae826af"},
"spitfire": {:git, "https://github.com/elixir-tools/spitfire.git", "8f991b87b0543b7bd6801855a1f1be4e3b4bebb4", []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"styler": {:hex, :styler, "0.8.1", "f3c0f65023e4bfbf7e7aa752d128b8475fdabfd30f96ee7314b84480cc56e788", [:mix], [], "hexpm", "1aa48d3aa689a639289af3d8254d40e068e98c083d6e5e3d1a695e71a147b344"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
Expand Down
40 changes: 40 additions & 0 deletions test/next_ls/completions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule NextLS.CompletionsTest do
import GenLSP.Test
import NextLS.Support.Utils

@moduletag init_options: %{"experimental" => %{"completions" => %{"enable" => true}}}

defmacrop assert_match({:in, _, [left, right]}) do
quote do
assert Enum.any?(unquote(right), fn x ->
Expand Down Expand Up @@ -347,6 +349,44 @@ defmodule NextLS.CompletionsTest do
} in results
end

test "inside interpolation in strings", %{client: client, foo: foo} do
uri = uri(foo)

did_open(client, foo, ~S"""
defmodule Foo do
def run(thing) do
"./lib/#{t}"
:ok
end
end
""")

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

assert_result 2, results

assert %{
"data" => nil,
"documentation" => "",
"insertText" => "thing",
"kind" => 6,
"label" => "thing"
} in results
end

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

Expand Down

0 comments on commit e09f262

Please sign in to comment.