diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e4552864dd..5a4d604ce3 100644 --- a/crates/language/src/language.rs +++ b/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, cx: &mut AsyncAppContext, ) -> Option>> { - self.adapter.will_fetch_server_binary(delegate, cx) + self.adapter.will_fetch_server(delegate, cx) + } + + pub fn will_start_server( + &self, + delegate: &Arc, + cx: &mut AsyncAppContext, + ) -> Option>> { + 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>; - fn will_fetch_server_binary( + fn will_fetch_server( + &self, + _: &Arc, + _: &mut AsyncAppContext, + ) -> Option>> { + None + } + + fn will_start_server( &self, _: &Arc, _: &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?; } diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index b63dba3778..22aaedc069 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/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, + cx: &mut AsyncAppContext, + ) -> Option>> { + 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, diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index fe518ed4ee..34364d0b7f 100644 --- a/crates/zed/src/languages/go.rs +++ b/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, cx: &mut AsyncAppContext, ) -> Option>> { + 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(()) }))