From 94c20a7c5cddde1370f565ad3002033e58c7d350 Mon Sep 17 00:00:00 2001 From: Jon Carstens Date: Wed, 15 Nov 2023 06:20:23 -0700 Subject: [PATCH] fix: prompt to run mix deps.get if deps are out of sync on start Partially addresses #53 #115 #285 Co-authored-by: Mitchell Hanberg --- flake.nix | 2 +- lib/next_ls.ex | 134 ++++++--- lib/next_ls/runtime.ex | 21 ++ lib/next_ls/runtime/supervisor.ex | 2 +- mix.exs | 3 +- mix.lock | 2 +- test/next_ls/dependency_test.exs | 467 ++++++++++++++++++------------ test/support/utils.ex | 40 ++- 8 files changed, 433 insertions(+), 238 deletions(-) diff --git a/flake.nix b/flake.nix index be8b6f0b..8a40ede3 100644 --- a/flake.nix +++ b/flake.nix @@ -115,7 +115,7 @@ src = self.outPath; inherit version elixir; pname = "next-ls-deps"; - hash = "sha256-BteNxUWcubVZ/SrFeBxKKV7KHmR39H50kUVaUz53dJs="; + hash = "sha256-ni9gJxzFKY3M/1h1uhDta36YccmlakTejOXZn0lgrI4="; mixEnv = "prod"; }; diff --git a/lib/next_ls.ex b/lib/next_ls.ex index 2468a2db..fc5a02f8 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -743,10 +743,11 @@ defmodule NextLS do GenLSP.log(lsp, "[NextLS] Booting runtimes...") + parent = self() + for %{uri: uri, name: name} <- lsp.assigns.workspace_folders do token = Progress.token() Progress.start(lsp, token, "Initializing NextLS runtime for folder #{name}...") - parent = self() working_dir = URI.parse(uri).path {:ok, _} = @@ -778,6 +779,9 @@ defmodule NextLS do send(parent, msg) else Progress.stop(lsp, token) + + send(parent, {:runtime_failed, name, status}) + GenLSP.error(lsp, "[NextLS] Runtime for folder #{name} failed to initialize") end end, @@ -865,38 +869,39 @@ defmodule NextLS do parent = self() working_dir = URI.parse(uri).path - # TODO: probably extract this to the Runtime module {:ok, _} = - DynamicSupervisor.start_child( - lsp.assigns.dynamic_supervisor, - {NextLS.Runtime.Supervisor, - path: Path.join(working_dir, ".elixir-tools"), - name: name, - registry: lsp.assigns.registry, - runtime: [ - task_supervisor: lsp.assigns.runtime_task_supervisor, - working_dir: working_dir, - uri: uri, - mix_env: lsp.assigns.init_opts.mix_env, - mix_target: lsp.assigns.init_opts.mix_target, - on_initialized: fn status -> - if status == :ready do - Progress.stop(lsp, token, "NextLS runtime for folder #{name} has initialized!") - GenLSP.log(lsp, "[NextLS] Runtime for folder #{name} is ready...") - msg = {:runtime_ready, name, self()} - - dispatch(lsp.assigns.registry, :extensions, fn entries -> - for {pid, _} <- entries, do: send(pid, msg) - end) - - send(parent, msg) - else - Progress.stop(lsp, token) - GenLSP.error(lsp, "[NextLS] Runtime for folder #{name} failed to initialize") - end - end, - logger: lsp.assigns.logger - ]} + NextLS.Runtime.boot(lsp.assigns.dynamic_supervisor, + path: Path.join(working_dir, ".elixir-tools"), + name: name, + registry: lsp.assigns.registry, + runtime: [ + task_supervisor: lsp.assigns.runtime_task_supervisor, + working_dir: working_dir, + uri: uri, + mix_env: lsp.assigns.init_opts.mix_env, + mix_target: lsp.assigns.init_opts.mix_target, + on_initialized: fn status -> + if status == :ready do + Progress.stop(lsp, token, "NextLS runtime for folder #{name} has initialized!") + GenLSP.log(lsp, "[NextLS] Runtime for folder #{name} is ready...") + + msg = {:runtime_ready, name, self()} + + dispatch(lsp.assigns.registry, :extensions, fn entries -> + for {pid, _} <- entries, do: send(pid, msg) + end) + + send(parent, msg) + else + Progress.stop(lsp, token) + + send(parent, {:runtime_failed, name, status}) + + GenLSP.error(lsp, "[NextLS] Runtime for folder #{name} failed to initialize") + end + end, + logger: lsp.assigns.logger + ] ) end @@ -904,8 +909,7 @@ defmodule NextLS do for {pid, %{name: name}} <- entries, name in names do GenLSP.log(lsp, "[NextLS] Removing workspace folder #{name}") - # TODO: probably extract this to the Runtime module - DynamicSupervisor.terminate_child(lsp.assigns.dynamic_supervisor, pid) + NextLS.Runtime.stop(lsp.assigns.dynamic_supervisor, pid) end end) @@ -995,6 +999,68 @@ defmodule NextLS do {:noreply, assign(lsp, ready: true, refresh_refs: refresh_refs)} end + def handle_info({:runtime_failed, name, status}, lsp) do + {pid, %{init_arg: init_arg}} = + dispatch(lsp.assigns.registry, :runtime_supervisors, fn entries -> + Enum.find(entries, fn {_pid, %{name: n}} -> n == name end) + end) + + :ok = DynamicSupervisor.terminate_child(lsp.assigns.dynamic_supervisor, pid) + + if status == {:error, :deps} do + resp = + GenLSP.request(lsp, %GenLSP.Requests.WindowShowMessageRequest{ + id: System.unique_integer([:positive]), + params: %GenLSP.Structures.ShowMessageRequestParams{ + type: GenLSP.Enumerations.MessageType.error(), + message: "The NextLS runtime failed with errors on dependencies. Would you like to re-fetch them?", + actions: [ + %GenLSP.Structures.MessageActionItem{title: "yes"}, + %GenLSP.Structures.MessageActionItem{title: "no"} + ] + } + }) + + case resp do + %{"title" => "yes"} -> + NextLS.Logger.info( + lsp.assigns.logger, + "Running `mix deps.get` in directory #{init_arg[:runtime][:working_dir]}" + ) + + File.rm_rf!(Path.join(init_arg[:runtime][:working_dir], ".elixir-tools/_build")) + + case System.cmd("mix", ["deps.get"], + env: [{"MIX_ENV", "dev"}, {"MIX_BUILD_ROOT", ".elixir-tools/_build"}], + cd: init_arg[:runtime][:working_dir], + stderr_to_stdout: true + ) do + {msg, 0} -> + NextLS.Logger.info( + lsp.assigns.logger, + "Restarting runtime #{name} for directory #{init_arg[:runtime][:working_dir]}" + ) + + NextLS.Logger.info(lsp.assigns.logger, msg) + + {:ok, _} = + DynamicSupervisor.start_child(lsp.assigns.dynamic_supervisor, {NextLS.Runtime.Supervisor, init_arg}) + + {msg, _} -> + NextLS.Logger.warning( + lsp.assigns.logger, + "Failed to run `mix deps.get` in directory #{init_arg[:runtime][:working_dir]} with message: #{msg}" + ) + end + + _ -> + NextLS.Logger.info(lsp.assigns.logger, "Not running `mix deps.get`") + end + end + + {:noreply, lsp} + end + def handle_info({ref, _resp}, %{assigns: %{refresh_refs: refs}} = lsp) when is_map_key(refs, ref) do Process.demonitor(ref, [:flush]) {{token, msg}, refs} = Map.pop(refs, ref) diff --git a/lib/next_ls/runtime.ex b/lib/next_ls/runtime.ex index 6c8712b9..dc10482d 100644 --- a/lib/next_ls/runtime.ex +++ b/lib/next_ls/runtime.ex @@ -45,6 +45,14 @@ defmodule NextLS.Runtime do GenServer.call(server, {:compile, opts}, :infinity) end + def boot(supervisor, opts) do + DynamicSupervisor.start_child(supervisor, {NextLS.Runtime.Supervisor, opts}) + end + + def stop(supervisor, pid) do + DynamicSupervisor.terminate_child(supervisor, pid) + end + defmacro execute!(runtime, block) do quote do {:ok, result} = NextLS.Runtime.execute(unquote_splicing([runtime, block])) @@ -294,6 +302,9 @@ defmodule NextLS.Runtime do diagnostics + {:error, %Mix.Error{message: "Can't continue due to errors on dependencies"}} -> + nil + unknown -> NextLS.Logger.warning(state.logger, "Unexpected compiler response: #{inspect(unknown)}") [] @@ -341,6 +352,16 @@ defmodule NextLS.Runtime do {:stop, {:shutdown, :nodedown}, state} end + def handle_info( + {port, {:data, "** (Mix) Can't continue due to errors on dependencies" <> _ = data}}, + %{port: port} = state + ) do + NextLS.Logger.log(state.logger, data) + + state.on_initialized.({:error, :deps}) + {:noreply, state} + end + def handle_info({port, {:data, data}}, %{port: port} = state) do NextLS.Logger.info(state.logger, data) {:noreply, state} diff --git a/lib/next_ls/runtime/supervisor.ex b/lib/next_ls/runtime/supervisor.ex index 32751e03..7ccb4d88 100644 --- a/lib/next_ls/runtime/supervisor.ex +++ b/lib/next_ls/runtime/supervisor.ex @@ -21,7 +21,7 @@ defmodule NextLS.Runtime.Supervisor do sidecar_name = :"sidecar-#{name}" db_activity = :"db-activity-#{name}" - Registry.register(registry, :runtime_supervisors, %{name: name}) + Registry.register(registry, :runtime_supervisors, %{name: name, init_arg: init_arg}) children = [ {NextLS.Runtime.Sidecar, name: sidecar_name, db: db_name}, diff --git a/mix.exs b/mix.exs index 990aa63e..af8ef355 100644 --- a/mix.exs +++ b/mix.exs @@ -59,7 +59,8 @@ defmodule NextLS.MixProject do defp deps do [ {:exqlite, "~> 0.13.14"}, - {:gen_lsp, "~> 0.7"}, + {:gen_lsp, "~> 0.8"}, + # {:gen_lsp, path: "../gen_lsp"}, {:req, "~> 0.3"}, {:schematic, "~> 0.2"}, {:spitfire, github: "elixir-tools/spitfire"}, diff --git a/mix.lock b/mix.lock index b0ad8d19..b00ea12f 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "exqlite": {:hex, :exqlite, "0.13.15", "a32c0763915e2b0d7ced9dd8638802d38e9569053f3b28b815bd0faef1cbe6d9", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "4afcc870a33b57781a1e57cd4294eef68815059d26b774c7cd075536b21434b7"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, - "gen_lsp": {:hex, :gen_lsp, "0.7.3", "08de9b88a8e8e79777bbab78d07ea0d5bbd05d54fdcba2c1d3328a9d091172eb", [:mix], [{:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:schematic, "~> 0.2.1", [hex: :schematic, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "ef7288e55be8889aba263658aa74584af0ea5d04848000cb4e16d3c2e21dde37"}, + "gen_lsp": {:hex, :gen_lsp, "0.8.0", "f771bc993492e8b0bcbca4ab016d82f9e3a1dfeb04187dcc09948cd87f1a1ad5", [:mix], [{:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:schematic, "~> 0.2.1", [hex: :schematic, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "c5d9d15623def3b02878be845fd36dd1a280b8377cf0080757f7c718cd58a0af"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, diff --git a/test/next_ls/dependency_test.exs b/test/next_ls/dependency_test.exs index 75460df4..097fdaa4 100644 --- a/test/next_ls/dependency_test.exs +++ b/test/next_ls/dependency_test.exs @@ -5,242 +5,343 @@ defmodule NextLS.DependencyTest do import NextLS.Support.Utils @moduletag :tmp_dir - @moduletag root_paths: ["my_proj"] - setup %{tmp_dir: tmp_dir} do - File.mkdir_p!(Path.join(tmp_dir, "my_proj/lib")) - File.write!(Path.join(tmp_dir, "my_proj/mix.exs"), proj_mix_exs()) + describe "" do + @describetag root_paths: ["my_proj"] - File.mkdir_p!(Path.join(tmp_dir, "bar/lib")) - File.write!(Path.join(tmp_dir, "bar/mix.exs"), bar_mix_exs()) + setup %{tmp_dir: cwd} do + mixexs = Path.join(cwd, "my_proj/mix.exs") + File.mkdir_p!(Path.join(cwd, "my_proj/lib")) - File.mkdir_p!(Path.join(tmp_dir, "baz/lib")) - File.write!(Path.join(tmp_dir, "baz/mix.exs"), baz_mix_exs()) + File.write!( + mixexs, + proj_mix_exs(""" + [{:temple, "~> 0.11.0"}] + """) + ) - [cwd: tmp_dir] - end - - setup %{cwd: cwd} do - foo = Path.join(cwd, "my_proj/lib/foo.ex") + foo = Path.join(cwd, "my_proj/lib/foo.ex") - File.write!(foo, """ - defmodule Foo do - def foo() do - Bar.bar() - Baz + File.write!(foo, """ + defmodule Foo do + def foo, do: :ok end + """) - def call_baz() do - Baz.baz() - end + lockfile = Path.join(cwd, "my_proj/mix.lock") + + [cwd: cwd, foo: foo, mixexs: mixexs, lockfile: lockfile] end - """) - cache = Path.join(cwd, "my_proj/lib/cache.ex") + setup %{cwd: cwd} do + assert {_, 0} = System.cmd("mix", ["deps.get"], cd: Path.join(cwd, "my_proj")) + :ok + end - File.write!(cache, """ - defmodule Cache do - use GenServer + setup :with_lsp - def init(_) do - {:ok, nil} - end + test "successfully asks to refetch deps", %{client: client, mixexs: mixexs, lockfile: lockfile} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + + assert_is_ready(context, "my_proj") + assert_compiled(context, "my_proj") - def get() do - GenServer.call(__MODULE__, :get) + for pid <- context.pids do + stop_supervised!(pid) end + Process.unlink(context.server.lsp) + shutdown_client!(context.client) + shutdown_server!(context.server) + + + # write new mix.exs and lockfile to simulate having them out of sync with the `deps` folder + File.write!( + mixexs, + proj_mix_exs(""" + [{:temple, "~> 0.12.0"}] + """) + ) + + File.write!(lockfile, """ + %{ + "floki": {:hex, :floki, "0.35.4", "cc947b446024732c07274ac656600c5c4dc014caa1f8fb2dfff93d275b83890d", [:mix], [], "hexpm", "27fa185d3469bd8fc5947ef0f8d5c4e47f0af02eb6b070b63c868f69e3af0204"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, + "temple": {:hex, :temple, "0.12.0", "b50b806e1f1805219f0cbffc9c747c14f138543977fa6c01e74756c3e0daaa25", [:mix], [{:floki, ">= 0.0.0", [hex: :floki, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "0d006e850bf21f6684fa0ee52ceeb2f8516bb0213bd003f6d38c66880262f8a8"}, + "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, + } + """) + + %{client: client} = context = Map.merge(context, Map.new(with_lsp(context))) + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + + assert_request(client, "window/showMessageRequest", fn params -> + assert %{ + "type" => 1, + "actions" => [ + %{"title" => "yes"}, + %{"title" => "no"} + ] + } = params + + # respond with yes + %{"title" => "yes"} + end) + + assert_notification "window/logMessage", %{ + "message" => "[NextLS] Running `mix deps.get` in directory" <> _, + "type" => 3 + } + + assert_notification "window/logMessage", %{ + "message" => "[NextLS] Restarting runtime" <> _, + "type" => 3 + } + + assert_is_ready(context, "my_proj") + assert_compiled(context, "my_proj") end - """) + end - bar = Path.join(cwd, "bar/lib/bar.ex") + describe "local deps" do + @describetag root_paths: ["my_proj"] + setup %{tmp_dir: tmp_dir} do + File.mkdir_p!(Path.join(tmp_dir, "my_proj/lib")) + + File.write!( + Path.join(tmp_dir, "my_proj/mix.exs"), + proj_mix_exs(""" + [{:bar, path: "../bar"}] + """) + ) + + File.mkdir_p!(Path.join(tmp_dir, "bar/lib")) + File.write!(Path.join(tmp_dir, "bar/mix.exs"), bar_mix_exs()) + + File.mkdir_p!(Path.join(tmp_dir, "baz/lib")) + File.write!(Path.join(tmp_dir, "baz/mix.exs"), baz_mix_exs()) + + cwd = tmp_dir + foo = Path.join(cwd, "my_proj/lib/foo.ex") + + File.write!(foo, """ + defmodule Foo do + def foo() do + Bar.bar() + Baz + end + + def call_baz() do + Baz.baz() + end + end + """) - File.write!(bar, """ - defmodule Bar do - def bar() do - 42 + cache = Path.join(cwd, "my_proj/lib/cache.ex") + + File.write!(cache, """ + defmodule Cache do + use GenServer + + def init(_) do + {:ok, nil} + end + + def get() do + GenServer.call(__MODULE__, :get) + end end - end - """) + """) - baz = Path.join(cwd, "baz/lib/baz.ex") + bar = Path.join(cwd, "bar/lib/bar.ex") - File.write!(baz, """ - defmodule Baz do - def baz() do - 42 + File.write!(bar, """ + defmodule Bar do + def bar() do + 42 + end end - end - """) + """) - [foo: foo, bar: bar, baz: baz, cache: cache] - end + baz = Path.join(cwd, "baz/lib/baz.ex") - setup :with_lsp + File.write!(baz, """ + defmodule Baz do + def baz() do + 42 + end + end + """) - test "go to dependency function definition", context do - %{client: client, foo: foo, bar: bar} = context + [foo: foo, bar: bar, baz: baz, cache: cache] + end - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + setup :with_lsp - assert_is_ready(context, "my_proj") - assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + test "go to dependency function definition", context do + %{client: client, foo: foo, bar: bar} = context - uri = uri(foo) + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - request(client, %{ - method: "textDocument/definition", - id: 4, - jsonrpc: "2.0", - params: %{ - position: %{line: 2, character: 9}, - textDocument: %{uri: uri} - } - }) + assert_is_ready(context, "my_proj") + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} - uri = uri(bar) + uri = uri(foo) - assert_result 4, %{ - "range" => %{ - "start" => %{ - "line" => 1, - "character" => 6 - }, - "end" => %{ - "line" => 1, - "character" => 6 + request(client, %{ + method: "textDocument/definition", + id: 4, + jsonrpc: "2.0", + params: %{ + position: %{line: 2, character: 9}, + textDocument: %{uri: uri} } - }, - "uri" => ^uri - } - end + }) + + uri = uri(bar) + + assert_result 4, %{ + "range" => %{ + "start" => %{ + "line" => 1, + "character" => 6 + }, + "end" => %{ + "line" => 1, + "character" => 6 + } + }, + "uri" => ^uri + } + end - test "does not show in workspace symbols", context do - %{client: client, foo: foo, bar: bar} = context - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + test "does not show in workspace symbols", context do + %{client: client, foo: foo, bar: bar} = context + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_is_ready(context, "my_proj") - assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + assert_is_ready(context, "my_proj") + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} - request client, %{ - method: "workspace/symbol", - id: 2, - jsonrpc: "2.0", - params: %{ - query: "" + request client, %{ + method: "workspace/symbol", + id: 2, + jsonrpc: "2.0", + params: %{ + query: "" + } } - } - assert_result 2, symbols + assert_result 2, symbols - uris = Enum.map(symbols, fn result -> result["location"]["uri"] end) - assert uri(foo) in uris - refute uri(bar) in uris - end + uris = Enum.map(symbols, fn result -> result["location"]["uri"] end) + assert uri(foo) in uris + refute uri(bar) in uris + end - test "does not show up in function references", %{client: client, foo: foo} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + test "does not show up in function references", %{client: client, foo: foo} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_is_ready(context, "my_proj") - assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + assert_is_ready(context, "my_proj") + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} - uri = uri(foo) + uri = uri(foo) - request(client, %{ - method: "textDocument/references", - id: 4, - jsonrpc: "2.0", - params: %{ - position: %{line: 7, character: 8}, - textDocument: %{uri: uri}, - context: %{includeDeclaration: true} - } - }) - - assert_result2( - 4, - [ - %{ - "range" => %{"start" => %{"character" => 8, "line" => 7}, "end" => %{"character" => 10, "line" => 7}}, - "uri" => uri + request(client, %{ + method: "textDocument/references", + id: 4, + jsonrpc: "2.0", + params: %{ + position: %{line: 7, character: 8}, + textDocument: %{uri: uri}, + context: %{includeDeclaration: true} } - ] - ) - end + }) - test "does not show up in module references", %{client: client, foo: foo} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_result2( + 4, + [ + %{ + "range" => %{"start" => %{"character" => 8, "line" => 7}, "end" => %{"character" => 10, "line" => 7}}, + "uri" => uri + } + ] + ) + end - assert_is_ready(context, "my_proj") - assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + test "does not show up in module references", %{client: client, foo: foo} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - uri = uri(foo) + assert_is_ready(context, "my_proj") + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} - request(client, %{ - method: "textDocument/references", - id: 4, - jsonrpc: "2.0", - params: %{ - position: %{line: 3, character: 4}, - textDocument: %{uri: uri}, - context: %{includeDeclaration: true} - } - }) - - assert_result2( - 4, - [ - %{ - "range" => %{"start" => %{"character" => 4, "line" => 3}, "end" => %{"character" => 6, "line" => 3}}, - "uri" => uri - }, - %{ - "range" => %{"start" => %{"character" => 4, "line" => 7}, "end" => %{"character" => 6, "line" => 7}}, - "uri" => uri + uri = uri(foo) + + request(client, %{ + method: "textDocument/references", + id: 4, + jsonrpc: "2.0", + params: %{ + position: %{line: 3, character: 4}, + textDocument: %{uri: uri}, + context: %{includeDeclaration: true} } - ] - ) - end + }) - test "elixir source files do not show up in references", %{client: client, cache: cache} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_result2( + 4, + [ + %{ + "range" => %{"start" => %{"character" => 4, "line" => 3}, "end" => %{"character" => 6, "line" => 3}}, + "uri" => uri + }, + %{ + "range" => %{"start" => %{"character" => 4, "line" => 7}, "end" => %{"character" => 6, "line" => 7}}, + "uri" => uri + } + ] + ) + end - assert_is_ready(context, "my_proj") + test "elixir source files do not show up in references", %{client: client, cache: cache} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_notification "$/progress", %{ - "value" => %{"kind" => "end", "message" => "Compiled Elixir.NextLS.DependencyTest-my_proj!"} - } + assert_is_ready(context, "my_proj") - assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + assert_notification "$/progress", %{ + "value" => %{"kind" => "end", "message" => "Compiled Elixir.NextLS.DependencyTest-my_proj!"} + } - uri = uri(cache) + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} - request(client, %{ - method: "textDocument/references", - id: 4, - jsonrpc: "2.0", - params: %{ - position: %{line: 8, character: 6}, - textDocument: %{uri: uri}, - context: %{includeDeclaration: true} - } - }) - - assert_result2( - 4, - [ - %{ - "range" => %{"end" => %{"character" => 14, "line" => 1}, "start" => %{"character" => 6, "line" => 1}}, - "uri" => uri - }, - %{ - "range" => %{"end" => %{"character" => 12, "line" => 8}, "start" => %{"character" => 4, "line" => 8}}, - "uri" => uri + uri = uri(cache) + + request(client, %{ + method: "textDocument/references", + id: 4, + jsonrpc: "2.0", + params: %{ + position: %{line: 8, character: 6}, + textDocument: %{uri: uri}, + context: %{includeDeclaration: true} } - ] - ) + }) + + assert_result2( + 4, + [ + %{ + "range" => %{"end" => %{"character" => 14, "line" => 1}, "start" => %{"character" => 6, "line" => 1}}, + "uri" => uri + }, + %{ + "range" => %{"end" => %{"character" => 12, "line" => 8}, "start" => %{"character" => 4, "line" => 8}}, + "uri" => uri + } + ] + ) + end end - defp proj_mix_exs do + defp proj_mix_exs(deps) do """ defmodule MyProj.MixProject do use Mix.Project @@ -250,9 +351,7 @@ defmodule NextLS.DependencyTest do app: :my_proj, version: "0.1.0", elixir: "~> 1.10", - deps: [ - {:bar, path: "../bar"}, - ] + deps: #{deps} ] end end diff --git a/test/support/utils.ex b/test/support/utils.ex index 7733442d..d628c5b7 100644 --- a/test/support/utils.ex +++ b/test/support/utils.ex @@ -42,12 +42,20 @@ defmodule NextLS.Support.Utils do tvisor = start_supervised!(Supervisor.child_spec(Task.Supervisor, id: :one)) r_tvisor = start_supervised!(Supervisor.child_spec(Task.Supervisor, id: :two)) - rvisor = start_supervised!({DynamicSupervisor, [strategy: :one_for_one]}) - start_supervised!({Registry, [keys: :duplicate, name: context.module]}) + rvisor = start_supervised!({DynamicSupervisor, [strategy: :one_for_one]}, id: :three) + start_supervised!({Registry, [keys: :duplicate, name: context.module]}, id: :four) extensions = [elixir: NextLS.ElixirExtension, credo: NextLS.CredoExtension] - cache = start_supervised!(NextLS.DiagnosticCache) + cache = start_supervised!(NextLS.DiagnosticCache, id: :five) init_options = context[:init_options] || %{} + pids = [ + :one, + :two, + :three, + :four, + :five + ] + server = server(NextLS, task_supervisor: tvisor, @@ -82,7 +90,7 @@ defmodule NextLS.Support.Utils do } }) - [server: server, client: client] + [server: server, client: client, pids: pids] end defmacro assert_is_ready( @@ -144,18 +152,18 @@ defmodule NextLS.Support.Utils do defmacro did_open(client, file_path, text) do quote do assert :ok == - notify(unquote(client), %{ - method: "textDocument/didOpen", - jsonrpc: "2.0", - params: %{ - textDocument: %{ - uri: uri(unquote(file_path)), - text: unquote(text), - languageId: "elixir", - version: 1 - } - } - }) + notify(unquote(client), %{ + method: "textDocument/didOpen", + jsonrpc: "2.0", + params: %{ + textDocument: %{ + uri: uri(unquote(file_path)), + text: unquote(text), + languageId: "elixir", + version: 1 + } + } + }) end end end