Skip to content

Commit

Permalink
feat(completions): imports, aliases, module attributes
Browse files Browse the repository at this point in the history
This patch adds support for completion candidates for functions/macros
imported via `import`, modules aliases via `alias`, module attributes,
and any of the above when injected via a macro such as `use`.

However, this is powered by new APIs and compiler changs that will be
available in Elixir 1.17, so when completions are enabled, we will use a
bundled 1.17 runtime of Elixir, instead of the ELixir in the user's
path.

This is a tradeoff, but I think one that is worthwhile in the name of
progress and improving the language and ecosystem.

Once completions exit experimental status, this means that Next LS will
always run with a bundled copy of Elixir of Elixir unless the user's
local copy is sufficiently new. This can be controlled via a setting.
  • Loading branch information
mhanberg committed Apr 11, 2024
1 parent 8d6bce1 commit 9f50457
Show file tree
Hide file tree
Showing 25 changed files with 1,448 additions and 353 deletions.
5 changes: 1 addition & 4 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
plugins: [Styler],
inputs: [
".formatter.exs",
"{config,lib,}/**/*.{ex,exs}",
"test/next_ls_test.exs",
"test/test_helper.exs",
"test/next_ls/**/*.{ex,exs}",
"{config,lib,test}/**/*.{ex,exs}",
"priv/**/*.ex"
]
]
7 changes: 0 additions & 7 deletions .mise.toml

This file was deleted.

2 changes: 0 additions & 2 deletions .tool-versions

This file was deleted.

4 changes: 2 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
musl = lib.optionals nixpkgs.legacyPackages.${system}.stdenv.isLinux (builtins.fetchurl (nixpkgs.lib.attrsets.getAttrs ["url" "sha256"] musls.${system}));
otp = (pkgs.beam.packagesWith beamPackages.erlang).extend (final: prev: {
elixir_1_17 = prev.elixir_1_16.override {
rev = "514615d0347cb9bb513faa44ae1e36406979e516";
rev = "e3b6a91b173f7e836401a6a75c3906c26bd7fd39";
# You can discover this using Trust On First Use by filling in `lib.fakeHash`
sha256 = "sha256-lEnDgHi1sRg+3/JTnQJVo1qqSi0X2cNN4i9i9M95B2A=";
sha256 = "sha256-RK0aMW7pz7kQtK9XXN1wVCBxKOJKdQD7I/53V8rWD04=";
version = "1.17.0-dev";
};

Expand Down
65 changes: 50 additions & 15 deletions lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ defmodule NextLS do
alias NextLS.Progress
alias NextLS.Runtime

require NextLS.Runtime

def start_link(args) do
{args, opts} =
Keyword.split(args, [
Expand Down Expand Up @@ -588,13 +590,16 @@ defmodule NextLS do
end)
|> Enum.join("\n")

env =
ast =
spliced
|> Spitfire.parse(literal_encoder: &{:ok, {:__literal__, &2, [&1]}})
|> Spitfire.parse(literal_encoder: &{:ok, {:__block__, &2, [&1]}})
|> then(fn
{:ok, ast} -> ast
{:error, ast, _} -> ast
end)

env =
ast
|> NextLS.ASTHelpers.find_cursor()
|> then(fn
{:ok, cursor} ->
Expand Down Expand Up @@ -627,6 +632,23 @@ defmodule NextLS do
dispatch(lsp.assigns.registry, :runtimes, fn entries ->
[{wuri, result}] =
for {runtime, %{uri: wuri}} <- entries, String.starts_with?(uri, wuri) do
ast =
spliced
|> Spitfire.parse()
|> then(fn
{:ok, ast} -> ast
{:error, ast, _} -> ast
end)

{:ok, {_, _, _, macro_env}} = Runtime.expand(runtime, ast, Path.basename(uri))

env =
env
|> Map.put(:functions, macro_env.functions)
|> Map.put(:macros, macro_env.macros)
|> Map.put(:aliases, macro_env.aliases)
|> Map.put(:attrs, macro_env.attrs)

{wuri,
document_slice
|> String.to_charlist()
Expand All @@ -652,7 +674,7 @@ defmodule NextLS do
{"#{name}/#{symbol.arity}", GenLSP.Enumerations.CompletionItemKind.function(), symbol.docs}

:module ->
{name, GenLSP.Enumerations.CompletionItemKind.module(), ""}
{name, GenLSP.Enumerations.CompletionItemKind.module(), symbol.docs}

:variable ->
{name, GenLSP.Enumerations.CompletionItemKind.variable(), ""}
Expand All @@ -666,6 +688,12 @@ defmodule NextLS do
:keyword ->
{name, GenLSP.Enumerations.CompletionItemKind.field(), ""}

:attribute ->
{name, GenLSP.Enumerations.CompletionItemKind.property(), ""}

:sigil ->
{name, GenLSP.Enumerations.CompletionItemKind.function(), ""}

_ ->
{name, GenLSP.Enumerations.CompletionItemKind.text(), ""}
end
Expand Down Expand Up @@ -870,7 +898,7 @@ defmodule NextLS do
for {pid, _} <- entries, do: send(pid, msg)
end)

send(parent, msg)
Process.send(parent, msg, [])
else
Progress.stop(lsp, token)

Expand Down Expand Up @@ -1136,25 +1164,28 @@ defmodule NextLS do
end

def handle_info({:runtime_ready, name, runtime_pid}, lsp) do
token = Progress.token()
Progress.start(lsp, token, "Compiling #{name}...")
case NextLS.Registry.dispatch(lsp.assigns.registry, :databases, fn entries ->
Enum.find(entries, fn {_, %{runtime: runtime}} -> runtime == name end)
end) do
{_, %{mode: mode}} ->
token = Progress.token()
Progress.start(lsp, token, "Compiling #{name}...")

{_, %{mode: mode}} =
dispatch(lsp.assigns.registry, :databases, fn entries ->
Enum.find(entries, fn {_, %{runtime: runtime}} -> runtime == name end)
end)
ref = make_ref()
Runtime.compile(runtime_pid, caller_ref: ref, force: mode == :reindex)

ref = make_ref()
Runtime.compile(runtime_pid, caller_ref: ref, force: mode == :reindex)
refresh_refs = Map.put(lsp.assigns.refresh_refs, ref, {token, "Compiled #{name}!"})

refresh_refs = Map.put(lsp.assigns.refresh_refs, ref, {token, "Compiled #{name}!"})
{:noreply, assign(lsp, ready: true, refresh_refs: refresh_refs)}

{:noreply, assign(lsp, ready: true, refresh_refs: refresh_refs)}
nil ->
{:noreply, assign(lsp, ready: true)}
end
end

def handle_info({:runtime_failed, name, status}, lsp) do
{pid, %{init_arg: init_arg}} =
dispatch(lsp.assigns.registry, :runtime_supervisors, fn entries ->
NextLS.Registry.dispatch(lsp.assigns.registry, :runtime_supervisors, fn entries ->
Enum.find(entries, fn {_pid, %{name: n}} -> n == name end)
end)

Expand Down Expand Up @@ -1186,6 +1217,7 @@ defmodule NextLS do
)

File.rm_rf!(Path.join(init_arg[:runtime][:working_dir], ".elixir-tools/_build"))
File.rm_rf!(Path.join(init_arg[:runtime][:working_dir], ".elixir-tools/_build2"))

case System.cmd("mix", ["deps.get"],
env: [{"MIX_ENV", "dev"}, {"MIX_BUILD_ROOT", ".elixir-tools/_build"}],
Expand Down Expand Up @@ -1267,6 +1299,9 @@ defmodule NextLS do

receive do
{^ref, result} -> result
after
1000 ->
:timeout
end
end

Expand Down
Loading

0 comments on commit 9f50457

Please sign in to comment.