From 997550161e818941fbddd56a587d1d5d93fbfd92 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Thu, 22 Jun 2023 21:30:18 -0400 Subject: [PATCH] fix: gracefully handle uninitialized runtime (#19) --- lib/next_ls.ex | 44 +++++++++++++++++++++-------------- lib/next_ls/runtime.ex | 6 ++++- test/next_ls/runtime_test.exs | 20 ++++++++++++++++ test/next_ls_test.exs | 30 +++++++++++++++++------- 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/lib/next_ls.ex b/lib/next_ls.ex index 52115e5f..b326e066 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -96,26 +96,34 @@ defmodule NextLS do def handle_request(%TextDocumentFormatting{params: %{text_document: %{uri: uri}}}, lsp) do document = lsp.assigns.documents[uri] - - {formatter, _} = Runtime.call(lsp.assigns.runtime, {Mix.Tasks.Format, :formatter_for_file, [".formatter.exs"]}) - - new_document = - Runtime.call(lsp.assigns.runtime, {Kernel, :apply, [formatter, [Enum.join(document, "\n")]]}) - |> IO.iodata_to_binary() - - {:reply, - [ - %TextEdit{ - new_text: new_document, - range: %Range{ - start: %Position{line: 0, character: 0}, - end: %Position{ - line: length(document), - character: document |> List.last() |> String.length() |> Kernel.-(1) |> max(0) + runtime = lsp.assigns.runtime + + with {:ok, {formatter, _}} <- Runtime.call(runtime, {Mix.Tasks.Format, :formatter_for_file, [".formatter.exs"]}), + {:ok, response} <- Runtime.call(runtime, {Kernel, :apply, [formatter, [Enum.join(document, "\n")]]}) do + {:reply, + [ + %TextEdit{ + new_text: IO.iodata_to_binary(response), + range: %Range{ + start: %Position{line: 0, character: 0}, + end: %Position{ + line: length(document), + character: document |> List.last() |> String.length() |> Kernel.-(1) |> max(0) + } } } - } - ], lsp} + ], lsp} + else + {:error, :not_ready} -> + GenLSP.notify(lsp, %GenLSP.Notifications.WindowShowMessage{ + params: %GenLSP.Structures.ShowMessageParams{ + type: GenLSP.Enumerations.MessageType.info(), + message: "The NextLS runtime is still initializing!" + } + }) + + {:reply, nil, lsp} + end end def handle_request(%Shutdown{}, lsp) do diff --git a/lib/next_ls/runtime.ex b/lib/next_ls/runtime.ex index a613879b..e08499dd 100644 --- a/lib/next_ls/runtime.ex +++ b/lib/next_ls/runtime.ex @@ -107,7 +107,11 @@ defmodule NextLS.Runtime do def handle_call({:call, {m, f, a}}, _from, %{node: node} = state) do reply = :rpc.call(node, m, f, a) - {:reply, reply, state} + {:reply, {:ok, reply}, state} + end + + def handle_call({:call, _}, _from, state) do + {:reply, {:error, :not_ready}, state} end def handle_call(:compile, _, %{node: node} = state) do diff --git a/test/next_ls/runtime_test.exs b/test/next_ls/runtime_test.exs index 7dc54d4c..962744ba 100644 --- a/test/next_ls/runtime_test.exs +++ b/test/next_ls/runtime_test.exs @@ -28,6 +28,26 @@ defmodule NextLs.RuntimeTest do [logger: logger, cwd: Path.absname(tmp_dir)] end + test "returns the response in an ok tuple", %{logger: logger, cwd: cwd} do + start_supervised!({Registry, keys: :unique, name: RuntimeTestRegistry}) + pid = start_supervised!({Runtime, working_dir: cwd, parent: logger, extension_registry: RuntimeTestRegistry}) + + Process.link(pid) + + assert wait_for_ready(pid) + + assert {:ok, "\"hi\""} = Runtime.call(pid, {Kernel, :inspect, ["hi"]}) + end + + test "call returns an error when the runtime is node ready", %{logger: logger, cwd: cwd} do + start_supervised!({Registry, keys: :unique, name: RuntimeTestRegistry}) + pid = start_supervised!({Runtime, working_dir: cwd, parent: logger, extension_registry: RuntimeTestRegistry}) + + Process.link(pid) + + assert {:error, :not_ready} = Runtime.call(pid, {IO, :puts, ["hi"]}) + end + test "compiles the code and returns diagnostics", %{logger: logger, cwd: cwd} do start_supervised!({Registry, keys: :unique, name: RuntimeTestRegistry}) diff --git a/test/next_ls_test.exs b/test/next_ls_test.exs index db6f9c43..bda2c1d9 100644 --- a/test/next_ls_test.exs +++ b/test/next_ls_test.exs @@ -190,9 +190,6 @@ defmodule NextLSTest do params: %{} }) - assert_notification "window/logMessage", - %{"message" => "[NextLS] Runtime ready..."} - notify client, %{ method: "textDocument/didOpen", jsonrpc: "2.0", @@ -229,6 +226,26 @@ defmodule NextLSTest do } } + assert_result 2, nil + + assert_notification "window/logMessage", + %{"message" => "[NextLS] Runtime ready..."} + + request client, %{ + method: "textDocument/formatting", + id: 3, + jsonrpc: "2.0", + params: %{ + textDocument: %{ + uri: "file://lib/foo/bar.ex" + }, + options: %{ + insertSpaces: true, + tabSize: 2 + } + } + } + new_text = """ defmodule Foo.Bar do def run() do @@ -238,14 +255,11 @@ defmodule NextLSTest do """ assert_result( - 2, + 3, [ %{ "newText" => ^new_text, - "range" => %{ - "start" => %{"character" => 0, "line" => 0}, - "end" => %{"character" => 0, "line" => 8} - } + "range" => %{"start" => %{"character" => 0, "line" => 0}, "end" => %{"character" => 0, "line" => 8}} } ] )