Show a notification when unable to start elixir-ls

Max Brunsfeld created

Change summary

crates/language/src/language.rs    | 32 ++++++++++++++++++++----
crates/zed/src/languages/elixir.rs | 41 +++++++++++++++++++++++++++++++
crates/zed/src/languages/go.rs     | 25 ++++++++++++++----
3 files changed, 85 insertions(+), 13 deletions(-)

Detailed changes

crates/language/src/language.rs 🔗

@@ -130,12 +130,20 @@ impl CachedLspAdapter {
         self.adapter.fetch_latest_server_version(delegate).await
     }
 
-    pub fn will_fetch_server_binary(
+    pub fn will_fetch_server(
         &self,
         delegate: &Arc<dyn LspAdapterDelegate>,
         cx: &mut AsyncAppContext,
     ) -> Option<Task<Result<()>>> {
-        self.adapter.will_fetch_server_binary(delegate, cx)
+        self.adapter.will_fetch_server(delegate, cx)
+    }
+
+    pub fn will_start_server(
+        &self,
+        delegate: &Arc<dyn LspAdapterDelegate>,
+        cx: &mut AsyncAppContext,
+    ) -> Option<Task<Result<()>>> {
+        self.adapter.will_start_server(delegate, cx)
     }
 
     pub async fn fetch_server_binary(
@@ -212,7 +220,15 @@ pub trait LspAdapter: 'static + Send + Sync {
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>>;
 
-    fn will_fetch_server_binary(
+    fn will_fetch_server(
+        &self,
+        _: &Arc<dyn LspAdapterDelegate>,
+        _: &mut AsyncAppContext,
+    ) -> Option<Task<Result<()>>> {
+        None
+    }
+
+    fn will_start_server(
         &self,
         _: &Arc<dyn LspAdapterDelegate>,
         _: &mut AsyncAppContext,
@@ -891,7 +907,7 @@ impl LanguageRegistry {
         let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
         let login_shell_env_loaded = self.login_shell_env_loaded.clone();
 
-        let task = cx.spawn(|cx| async move {
+        let task = cx.spawn(|mut cx| async move {
             login_shell_env_loaded.await;
 
             let entry = this
@@ -903,7 +919,7 @@ impl LanguageRegistry {
                         get_binary(
                             adapter.clone(),
                             language.clone(),
-                            delegate,
+                            delegate.clone(),
                             download_dir,
                             lsp_binary_statuses,
                             cx,
@@ -915,6 +931,10 @@ impl LanguageRegistry {
                 .clone();
             let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
 
+            if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
+                task.await?;
+            }
+
             let server = lsp::LanguageServer::new(
                 server_id,
                 &binary.path,
@@ -996,7 +1016,7 @@ async fn get_binary(
             .context("failed to create container directory")?;
     }
 
-    if let Some(task) = adapter.will_fetch_server_binary(&delegate, &mut cx) {
+    if let Some(task) = adapter.will_fetch_server(&delegate, &mut cx) {
         task.await?;
     }
 

crates/zed/src/languages/elixir.rs 🔗

@@ -1,10 +1,18 @@
 use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use futures::StreamExt;
+use gpui::{AsyncAppContext, Task};
 pub use language::*;
 use lsp::{CompletionItemKind, SymbolKind};
 use smol::fs::{self, File};
-use std::{any::Any, path::PathBuf, sync::Arc};
+use std::{
+    any::Any,
+    path::PathBuf,
+    sync::{
+        atomic::{AtomicBool, Ordering::SeqCst},
+        Arc,
+    },
+};
 use util::{
     fs::remove_matching,
     github::{latest_github_release, GitHubLspBinaryVersion},
@@ -19,6 +27,37 @@ impl LspAdapter for ElixirLspAdapter {
         LanguageServerName("elixir-ls".into())
     }
 
+    fn will_start_server(
+        &self,
+        delegate: &Arc<dyn LspAdapterDelegate>,
+        cx: &mut AsyncAppContext,
+    ) -> Option<Task<Result<()>>> {
+        static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
+
+        const NOTIFICATION_MESSAGE: &str = "Could not run the elixir language server, `elixir-ls`, because `elixir` was not found.";
+
+        let delegate = delegate.clone();
+        Some(cx.spawn(|mut cx| async move {
+            let elixir_output = smol::process::Command::new("elixir")
+                .args(["--version"])
+                .output()
+                .await;
+            if elixir_output.is_err() {
+                if DID_SHOW_NOTIFICATION
+                    .compare_exchange(false, true, SeqCst, SeqCst)
+                    .is_ok()
+                {
+                    cx.update(|cx| {
+                        delegate.show_notification(NOTIFICATION_MESSAGE, cx);
+                    })
+                }
+                return Err(anyhow!("cannot run elixir-ls"));
+            }
+
+            Ok(())
+        }))
+    }
+
     async fn fetch_latest_server_version(
         &self,
         delegate: &dyn LspAdapterDelegate,

crates/zed/src/languages/go.rs 🔗

@@ -12,7 +12,10 @@ use std::{
     ops::Range,
     path::PathBuf,
     str,
-    sync::Arc,
+    sync::{
+        atomic::{AtomicBool, Ordering::SeqCst},
+        Arc,
+    },
 };
 use util::{fs::remove_matching, github::latest_github_release, ResultExt};
 
@@ -48,19 +51,29 @@ impl super::LspAdapter for GoLspAdapter {
         Ok(Box::new(version) as Box<_>)
     }
 
-    fn will_fetch_server_binary(
+    fn will_fetch_server(
         &self,
         delegate: &Arc<dyn LspAdapterDelegate>,
         cx: &mut AsyncAppContext,
     ) -> Option<Task<Result<()>>> {
+        static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
+
+        const NOTIFICATION_MESSAGE: &str =
+            "Could not install the Go language server `gopls`, because `go` was not found.";
+
         let delegate = delegate.clone();
         Some(cx.spawn(|mut cx| async move {
             let install_output = process::Command::new("go").args(["version"]).output().await;
             if install_output.is_err() {
-                cx.update(|cx| {
-                    delegate
-                        .show_notification("go is not installed. gopls will not be available.", cx);
-                })
+                if DID_SHOW_NOTIFICATION
+                    .compare_exchange(false, true, SeqCst, SeqCst)
+                    .is_ok()
+                {
+                    cx.update(|cx| {
+                        delegate.show_notification(NOTIFICATION_MESSAGE, cx);
+                    })
+                }
+                return Err(anyhow!("cannot install gopls"));
             }
             Ok(())
         }))