From b5b17f1dddfb317938baa50bd5a6e9ff4e0231a6 Mon Sep 17 00:00:00 2001
From: Mitchell Hanberg <mitch@mitchellhanberg.com>
Date: Tue, 27 Feb 2024 23:59:30 -0500
Subject: [PATCH] fix: prompt the user to run mix deps.get when dependency
 problems happen at runtime

Closes #53
---
 lib/next_ls.ex                   | 11 ++++++
 lib/next_ls/runtime.ex           |  9 ++++-
 test/next_ls/dependency_test.exs | 67 +++++++++++++++++++++++++++++++-
 3 files changed, 84 insertions(+), 3 deletions(-)

diff --git a/lib/next_ls.ex b/lib/next_ls.ex
index 1ca3d1cf..997cdd74 100644
--- a/lib/next_ls.ex
+++ b/lib/next_ls.ex
@@ -1065,6 +1065,17 @@ defmodule NextLS do
     {:noreply, lsp}
   end
 
+  def handle_info({ref, {:runtime_failed, _, _} = error}, %{assigns: %{refresh_refs: refs}} = lsp)
+      when is_map_key(refs, ref) do
+    Process.demonitor(ref, [:flush])
+    {{token, msg}, refs} = Map.pop(refs, ref)
+
+    Progress.stop(lsp, token, msg)
+    send(self(), error)
+
+    {:noreply, assign(lsp, refresh_refs: refs)}
+  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 dc10482d..27ee3165 100644
--- a/lib/next_ls/runtime.ex
+++ b/lib/next_ls/runtime.ex
@@ -303,7 +303,7 @@ defmodule NextLS.Runtime do
             diagnostics
 
           {:error, %Mix.Error{message: "Can't continue due to errors on dependencies"}} ->
-            nil
+            {:runtime_failed, state.name, {:error, :deps}}
 
           unknown ->
             NextLS.Logger.warning(state.logger, "Unexpected compiler response: #{inspect(unknown)}")
@@ -362,6 +362,13 @@ defmodule NextLS.Runtime do
     {:noreply, state}
   end
 
+  def handle_info({port, {:data, "Unchecked 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/test/next_ls/dependency_test.exs b/test/next_ls/dependency_test.exs
index c3509848..32354ac8 100644
--- a/test/next_ls/dependency_test.exs
+++ b/test/next_ls/dependency_test.exs
@@ -6,7 +6,7 @@ defmodule NextLS.DependencyTest do
 
   @moduletag :tmp_dir
 
-  describe "" do
+  describe "refetching deps" do
     @describetag root_paths: ["my_proj"]
 
     setup %{tmp_dir: cwd} do
@@ -40,7 +40,8 @@ defmodule NextLS.DependencyTest do
 
     setup :with_lsp
 
-    test "successfully asks to refetch deps", %{client: client, mixexs: mixexs, lockfile: lockfile} = context do
+    test "successfully asks to refetch deps on start",
+         %{client: client, mixexs: mixexs, lockfile: lockfile} = context do
       assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}})
 
       assert_is_ready(context, "my_proj")
@@ -100,6 +101,68 @@ defmodule NextLS.DependencyTest do
       assert_is_ready(context, "my_proj")
       assert_compiled(context, "my_proj")
     end
+
+    test "successfully asks to refetch deps on compile",
+         %{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")
+
+      did_open(client, mixexs, File.read!(mixexs))
+
+      # write new mix.exs and lockfile to simulate having them out of sync with the `deps` folder
+      new_text =
+        proj_mix_exs("""
+        [{:temple, "~> 0.12.0"}]
+        """)
+
+      File.write!(mixexs, new_text)
+
+      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"},
+      }
+      """)
+
+      notify client, %{
+        method: "textDocument/didSave",
+        jsonrpc: "2.0",
+        params: %{
+          text: new_text,
+          textDocument: %{uri: uri(mixexs)}
+        }
+      }
+
+      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
 
   describe "local deps" do
