From 1c24e788c136e8ba80703256554b3efda3f0f63d Mon Sep 17 00:00:00 2001 From: doorgan Date: Mon, 7 Jul 2025 20:04:03 -0300 Subject: [PATCH 1/4] fix: handle shutdown request conversion --- apps/expert/lib/expert/protocol/convert.ex | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/expert/lib/expert/protocol/convert.ex b/apps/expert/lib/expert/protocol/convert.ex index b58b93b6..ce473e9c 100644 --- a/apps/expert/lib/expert/protocol/convert.ex +++ b/apps/expert/lib/expert/protocol/convert.ex @@ -29,4 +29,9 @@ defmodule Expert.Protocol.Convert do {:ok, %{original_request | params: updated_request}} end end + + def to_native(%GenLSP.Requests.Shutdown{} = request) do + # Special case for shutdown requests, which don't have a params field + {:ok, request} + end end From e322c0b4b054f6f7dc5a835e592b0cc500fda69c Mon Sep 17 00:00:00 2001 From: doorgan Date: Mon, 7 Jul 2025 20:07:43 -0300 Subject: [PATCH 2/4] fix: convert completion item tags --- .../code_intelligence/completion/translations/callable.ex | 2 +- .../completion/translations/function_test.exs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/expert/lib/expert/code_intelligence/completion/translations/callable.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/callable.ex index b64b9190..88f86368 100644 --- a/apps/expert/lib/expert/code_intelligence/completion/translations/callable.ex +++ b/apps/expert/lib/expert/code_intelligence/completion/translations/callable.ex @@ -44,7 +44,7 @@ defmodule Expert.CodeIntelligence.Completion.Translations.Callable do tags = if Map.get(callable.metadata, :deprecated) do - [:deprecated] + [GenLSP.Enumerations.CompletionItemTag.deprecated()] end kind = diff --git a/apps/expert/test/expert/code_intelligence/completion/translations/function_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/function_test.exs index ed6f6d45..4f66d470 100644 --- a/apps/expert/test/expert/code_intelligence/completion/translations/function_test.exs +++ b/apps/expert/test/expert/code_intelligence/completion/translations/function_test.exs @@ -12,7 +12,8 @@ defmodule Expert.CodeIntelligence.Completion.Translations.FunctionTest do |> fetch_completion("filter_map") assert completion.label - assert [:deprecated] = completion.tags + expected_tag = GenLSP.Enumerations.CompletionItemTag.deprecated() + assert [^expected_tag] = completion.tags end test "bang functions are sorted after non-bang functions", %{project: project} do From bb880feed21d1a4ec575ec2127f66ebbcbf4f427 Mon Sep 17 00:00:00 2001 From: doorgan Date: Wed, 9 Jul 2025 22:26:29 -0300 Subject: [PATCH 3/4] fix: supress errors caused by code actions triggered before we try to start the engine --- apps/expert/lib/expert.ex | 65 ++++++++++++++++++--------- apps/expert/lib/expert/application.ex | 1 + apps/expert/lib/expert/state.ex | 6 ++- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/apps/expert/lib/expert.ex b/apps/expert/lib/expert.ex index 24bf504b..8da8c1d6 100644 --- a/apps/expert/lib/expert.ex +++ b/apps/expert/lib/expert.ex @@ -84,30 +84,39 @@ defmodule Expert do def handle_request(request, lsp) do state = assigns(lsp).state - with {:ok, handler} <- fetch_handler(request), - {:ok, request} <- Convert.to_native(request), - {:ok, response} <- handler.handle(request, state.configuration), - {:ok, response} <- Expert.Protocol.Convert.to_lsp(response) do - {:reply, response, lsp} + if state.engine_initialized? do + with {:ok, handler} <- fetch_handler(request), + {:ok, request} <- Convert.to_native(request), + {:ok, response} <- handler.handle(request, state.configuration), + {:ok, response} <- Expert.Protocol.Convert.to_lsp(response) do + {:reply, response, lsp} + else + {:error, {:unhandled, _}} -> + Logger.info("Unhandled request: #{request.method}") + + {:reply, + %GenLSP.ErrorResponse{ + code: GenLSP.Enumerations.ErrorCodes.method_not_found(), + message: "Method not found" + }, lsp} + + error -> + message = "Failed to handle #{request.method}, #{inspect(error)}" + Logger.error(message) + + {:reply, + %GenLSP.ErrorResponse{ + code: GenLSP.Enumerations.ErrorCodes.internal_error(), + message: message + }, lsp} + end else - {:error, {:unhandled, _}} -> - Logger.info("Unhandled request: #{request.method}") - - {:reply, - %GenLSP.ErrorResponse{ - code: GenLSP.Enumerations.ErrorCodes.method_not_found(), - message: "Method not found" - }, lsp} - - error -> - message = "Failed to handle #{request.method}, #{inspect(error)}" - Logger.error(message) + GenLSP.warning( + lsp, + "Received request #{request.method} before engine was initialized. Ignoring." + ) - {:reply, - %GenLSP.ErrorResponse{ - code: GenLSP.Enumerations.ErrorCodes.internal_error(), - message: message - }, lsp} + {:noreply, lsp} end end @@ -155,6 +164,18 @@ defmodule Expert do end end + def handle_info(:engine_initialized, lsp) do + state = assigns(lsp).state + + new_state = %State{state | engine_initialized?: true} + + lsp = assign(lsp, state: new_state) + + Logger.info("Engine initialized") + + {:noreply, lsp} + end + def handle_info(:default_config, lsp) do state = assigns(lsp).state diff --git a/apps/expert/lib/expert/application.ex b/apps/expert/lib/expert/application.ex index de275596..1cb44732 100644 --- a/apps/expert/lib/expert/application.ex +++ b/apps/expert/lib/expert/application.ex @@ -19,6 +19,7 @@ defmodule Expert.Application do {Task.Supervisor, name: :expert_task_queue}, {GenLSP.Buffer, name: Expert.Buffer}, {Expert, + name: Expert, buffer: Expert.Buffer, task_supervisor: :expert_task_queue, dynamic_supervisor: Expert.DynamicSupervisor, diff --git a/apps/expert/lib/expert/state.ex b/apps/expert/lib/expert/state.ex index b9bd5f23..927345b3 100644 --- a/apps/expert/lib/expert/state.ex +++ b/apps/expert/lib/expert/state.ex @@ -16,6 +16,7 @@ defmodule Expert.State do defstruct configuration: nil, initialized?: false, + engine_initialized?: false, shutdown_received?: false, in_flight_requests: %{} @@ -54,7 +55,10 @@ defmodule Expert.State do response = initialize_result() - Task.start_link(fn -> Project.Supervisor.start(config.project) end) + Task.Supervisor.start_child(:expert_task_queue, fn -> + Project.Supervisor.start(config.project) + send(Expert, :engine_initialized) + end) {:ok, response, new_state} end From 11a3b6071d9d7c6f3d8b8128d9087017069c53e1 Mon Sep 17 00:00:00 2001 From: doorgan Date: Wed, 9 Jul 2025 22:26:54 -0300 Subject: [PATCH 4/4] chore: update elixir_sense --- apps/engine/mix.exs | 2 +- apps/engine/mix.lock | 2 +- apps/expert/mix.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/engine/mix.exs b/apps/engine/mix.exs index f0450dcd..11ada865 100644 --- a/apps/engine/mix.exs +++ b/apps/engine/mix.exs @@ -44,7 +44,7 @@ defmodule Engine.MixProject do Mix.Credo.dependency(), Mix.Dialyzer.dependency(), {:elixir_sense, - github: "elixir-lsp/elixir_sense", ref: "73ce7e0d239342fb9527d7ba567203e77dbb9b25"}, + github: "elixir-lsp/elixir_sense", ref: "e3ddc403554050221a2fd19a10a896fa7525bc02"}, {:forge, path: "../forge", env: Mix.env()}, {:gen_lsp, "~> 0.11"}, {:patch, "~> 0.15", only: [:dev, :test], optional: true, runtime: false}, diff --git a/apps/engine/mix.lock b/apps/engine/mix.lock index 35ca2232..6bb509a3 100644 --- a/apps/engine/mix.lock +++ b/apps/engine/mix.lock @@ -5,7 +5,7 @@ "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, - "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "73ce7e0d239342fb9527d7ba567203e77dbb9b25", [ref: "73ce7e0d239342fb9527d7ba567203e77dbb9b25"]}, + "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "e3ddc403554050221a2fd19a10a896fa7525bc02", [ref: "e3ddc403554050221a2fd19a10a896fa7525bc02"]}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "gen_lsp": {:hex, :gen_lsp, "0.11.0", "9eda4d2fcaff94d9b3062e322fcf524c176db1502f584a3cff6135088b46084b", [: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", "d67c20650a5290a02f7bac53083ac4487d3c6b461f35a8b14c5d2d7638c20d26"}, diff --git a/apps/expert/mix.lock b/apps/expert/mix.lock index ea4f538f..649a8437 100644 --- a/apps/expert/mix.lock +++ b/apps/expert/mix.lock @@ -4,7 +4,7 @@ "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, - "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "73ce7e0d239342fb9527d7ba567203e77dbb9b25", [ref: "73ce7e0d239342fb9527d7ba567203e77dbb9b25"]}, + "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "e3ddc403554050221a2fd19a10a896fa7525bc02", [ref: "e3ddc403554050221a2fd19a10a896fa7525bc02"]}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "gen_lsp": {:hex, :gen_lsp, "0.11.0", "9eda4d2fcaff94d9b3062e322fcf524c176db1502f584a3cff6135088b46084b", [: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", "d67c20650a5290a02f7bac53083ac4487d3c6b461f35a8b14c5d2d7638c20d26"},