From 87da77308c82092ae843b0645e9bab9ddec6c191 Mon Sep 17 00:00:00 2001 From: Jon Carstens Date: Sat, 20 Jan 2024 12:59:38 -0700 Subject: [PATCH] Support workspace/project shims with `elixir_exec` option Relates to #334 IDEs like to work with shims rather than tools that alter path (such as `mise`/`rtx`). As a result, one must launch their IDE manually from a terminal session with all the `PATH` loaded in order to properly find the `elixir` executable. This works fine, but switching between projects/workspaces that have varying Elixir/OTP versions will most likely be using the version loaded when launching the program from terminal. One resolve might be to include shims in the `PATH` globally. However, that negates some of the reasoning to move to alternate tools like `mise. Instead, this allows setting the `elixir_exec` option to point directly at a shim. When used in workspace directories, that should also be able to load the correct versions defined in the tool version files. This also allows keeping the need for a shim isolated to the LS tool rather than global use --- lib/next_ls.ex | 3 +++ lib/next_ls/runtime.ex | 14 +++++++++++++- test/next_ls/runtime_test.exs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/next_ls.ex b/lib/next_ls.ex index 7ba9440d..11590ac9 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -661,6 +661,7 @@ defmodule NextLS do uri: uri, mix_env: lsp.assigns.init_opts.mix_env, mix_target: lsp.assigns.init_opts.mix_target, + elixir_exec: lsp.assigns.init_opts.elixir_exec, on_initialized: fn status -> if status == :ready do Progress.stop(lsp, token, "NextLS runtime for folder #{name} has initialized!") @@ -1117,6 +1118,7 @@ defmodule NextLS do defstruct mix_target: "host", mix_env: "dev", + elixir_exec: nil, experimental: %NextLS.InitOpts.Experimental{}, extensions: %NextLS.InitOpts.Extensions{} @@ -1126,6 +1128,7 @@ defmodule NextLS do schema(__MODULE__, %{ optional(:mix_target) => str(), optional(:mix_env) => str(), + optional(:elixir_exec) => str(), optional(:experimental) => schema(NextLS.InitOpts.Experimental, %{ optional(:completions) => diff --git a/lib/next_ls/runtime.ex b/lib/next_ls/runtime.ex index 303e50bb..8264611d 100644 --- a/lib/next_ls/runtime.ex +++ b/lib/next_ls/runtime.ex @@ -111,7 +111,7 @@ defmodule NextLS.Runtime do new_path = String.replace(path, bindir <> ":", "") with dir when is_list(dir) <- :code.priv_dir(:next_ls), - elixir_exe when is_binary(elixir_exe) <- System.find_executable("elixir") do + elixir_exe when is_binary(elixir_exe) <- use_or_find_elixir_exec(opts) do exe = dir |> Path.join("cmd") @@ -348,4 +348,16 @@ defmodule NextLS.Runtime do true end end + + defp use_or_find_elixir_exec(opts) do + with path when is_binary(path) <- opts[:elixir_exec], + exec = Path.expand(path), + true <- File.exists?(exec), + {_, 0} <- System.cmd(exec, ["--version"]) do + exec + else + _ -> + System.find_executable("elixir") + end + end end diff --git a/test/next_ls/runtime_test.exs b/test/next_ls/runtime_test.exs index 310e6817..e83c18d7 100644 --- a/test/next_ls/runtime_test.exs +++ b/test/next_ls/runtime_test.exs @@ -253,6 +253,37 @@ defmodule NextLs.RuntimeTest do end end + test "supports using custom elixir_exec path", %{logger: logger, cwd: cwd, on_init: on_init} do + start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry}) + tvisor = start_supervised!(Task.Supervisor) + + # Create local Elixir shim + elixir_exec = Path.join(cwd, "elixir") + File.ln_s!(System.find_executable("elixir"), elixir_exec) + assert {_, 0} = System.cmd(elixir_exec, ["--version"]) + + pid = + start_supervised!( + {Runtime, + name: "my_proj", + on_initialized: on_init, + task_supervisor: tvisor, + working_dir: cwd, + uri: "file://#{cwd}", + parent: self(), + logger: logger, + db: :some_db, + mix_env: "dev", + mix_target: "host", + elixir_exec: elixir_exec, + registry: RuntimeTest.Registry} + ) + + Process.link(pid) + + assert_receive :ready + end + defp flush_messages do receive do _ -> flush_messages()