From dc48af0ca1d5297fac94c7d02bb858d564a6542b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 25 Sep 2024 11:45:56 -0600 Subject: [PATCH] lsp: Remove reinstall, update config (#18318) Release Notes: - Fixed overriding the path of a language server binary for all language servers. `{"lsp":{"":{"binary":{"path": "_"}}}}` will now work for all language servers including those defined by extensions. - (breaking change) To disable finding lsp adapters in your path, you must now specify `{"lsp":{"":{"binary":{"ignore_system_version": true}}}}`. Previously this was `{"lsp":{"":{"binary":{"path_lookup": false}}}}`. Note that this setting still does not apply to extensions. - Removed automatic reinstallation of language servers. (It mostly didn't work) --------- Co-authored-by: Mikayla --- assets/settings/default.json | 2 +- .../src/activity_indicator.rs | 2 +- crates/extension/src/extension_lsp_adapter.rs | 15 +- crates/language/src/language.rs | 73 +- crates/language/src/language_registry.rs | 196 ++---- crates/languages/src/c.rs | 54 +- crates/languages/src/css.rs | 7 - crates/languages/src/go.rs | 54 +- crates/languages/src/json.rs | 19 - crates/languages/src/python.rs | 7 - crates/languages/src/rust.rs | 104 +-- crates/languages/src/tailwind.rs | 39 -- crates/languages/src/typescript.rs | 20 - crates/languages/src/vtsls.rs | 50 +- crates/languages/src/yaml.rs | 38 -- crates/lsp/src/lsp.rs | 9 + crates/project/src/lsp_store.rs | 638 ++++++++---------- crates/project/src/project.rs | 8 - crates/project/src/project_settings.rs | 4 +- crates/zed/src/main.rs | 2 +- docs/src/languages/rust.md | 4 +- 21 files changed, 405 insertions(+), 940 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 61239b002b..cf0de6a5e7 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -783,7 +783,7 @@ /// or to ensure Zed always downloads and installs an isolated version of node: /// { /// "node": { - /// "disable_path_lookup": true + /// "ignore_system_version": true, /// } /// NOTE: changing this setting currently requires restarting Zed. "node": {}, diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 52e6acc393..ace972bf87 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -299,7 +299,7 @@ impl ActivityIndicator { .into_any_element(), ), message: format!( - "Failed to download {}. Click to show error.", + "Failed to run {}. Click to show error.", failed .iter() .map(|name| name.0.as_ref()) diff --git a/crates/extension/src/extension_lsp_adapter.rs b/crates/extension/src/extension_lsp_adapter.rs index d6125241f1..25179acec6 100644 --- a/crates/extension/src/extension_lsp_adapter.rs +++ b/crates/extension/src/extension_lsp_adapter.rs @@ -10,16 +10,11 @@ use gpui::AsyncAppContext; use language::{ CodeLabel, HighlightId, Language, LanguageServerName, LspAdapter, LspAdapterDelegate, }; -use lsp::{CodeActionKind, LanguageServerBinary}; +use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions}; use serde::Serialize; use serde_json::Value; use std::ops::Range; -use std::{ - any::Any, - path::{Path, PathBuf}, - pin::Pin, - sync::Arc, -}; +use std::{any::Any, path::PathBuf, pin::Pin, sync::Arc}; use util::{maybe, ResultExt}; use wasmtime_wasi::WasiView as _; @@ -38,8 +33,8 @@ impl LspAdapter for ExtensionLspAdapter { fn get_language_server_command<'a>( self: Arc, - _: Option>, delegate: Arc, + _: LanguageServerBinaryOptions, _: futures::lock::MutexGuard<'a, Option>, _: &'a mut AsyncAppContext, ) -> Pin>>> { @@ -124,10 +119,6 @@ impl LspAdapter for ExtensionLspAdapter { unreachable!("get_language_server_command is overridden") } - async fn installation_test_binary(&self, _: PathBuf) -> Option { - None - } - fn code_action_kinds(&self) -> Option> { let code_action_kinds = self .extension diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index d70650cf44..4c75ef4eeb 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -29,7 +29,7 @@ use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task}; pub use highlight_map::HighlightMap; use http_client::HttpClient; pub use language_registry::LanguageName; -use lsp::{CodeActionKind, LanguageServerBinary}; +use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions}; use parking_lot::Mutex; use regex::Regex; use schemars::{ @@ -69,7 +69,7 @@ pub use buffer::*; pub use diagnostic_set::DiagnosticEntry; pub use language_registry::{ AvailableLanguage, LanguageNotFound, LanguageQueries, LanguageRegistry, - LanguageServerBinaryStatus, PendingLanguageServer, QUERY_FILENAME_PREFIXES, + LanguageServerBinaryStatus, QUERY_FILENAME_PREFIXES, }; pub use lsp::LanguageServerId; pub use outline::*; @@ -249,28 +249,17 @@ impl CachedLspAdapter { pub async fn get_language_server_command( self: Arc, - container_dir: Option>, delegate: Arc, + binary_options: LanguageServerBinaryOptions, cx: &mut AsyncAppContext, ) -> Result { let cached_binary = self.cached_binary.lock().await; self.adapter .clone() - .get_language_server_command(container_dir, delegate, cached_binary, cx) + .get_language_server_command(delegate, binary_options, cached_binary, cx) .await } - pub fn can_be_reinstalled(&self) -> bool { - self.adapter.can_be_reinstalled() - } - - pub async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - self.adapter.installation_test_binary(container_dir).await - } - pub fn code_action_kinds(&self) -> Option> { self.adapter.code_action_kinds() } @@ -322,6 +311,7 @@ pub trait LspAdapterDelegate: Send + Sync { fn worktree_id(&self) -> WorktreeId; fn worktree_root_path(&self) -> &Path; fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus); + async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option>; async fn which(&self, command: &OsStr) -> Option; async fn shell_env(&self) -> HashMap; @@ -335,8 +325,8 @@ pub trait LspAdapter: 'static + Send + Sync { fn get_language_server_command<'a>( self: Arc, - container_dir: Option>, delegate: Arc, + binary_options: LanguageServerBinaryOptions, mut cached_binary: futures::lock::MutexGuard<'a, Option>, cx: &'a mut AsyncAppContext, ) -> Pin>>> { @@ -352,30 +342,30 @@ pub trait LspAdapter: 'static + Send + Sync { // We only want to cache when we fall back to the global one, // because we don't want to download and overwrite our global one // for each worktree we might have open. - if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await { - log::info!( - "found user-installed language server for {}. path: {:?}, arguments: {:?}", - self.name().0, - binary.path, - binary.arguments - ); - return Ok(binary); + if binary_options.allow_path_lookup { + if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await { + log::info!( + "found user-installed language server for {}. path: {:?}, arguments: {:?}", + self.name().0, + binary.path, + binary.arguments + ); + return Ok(binary); + } + } + + if !binary_options.allow_binary_download { + return Err(anyhow!("downloading language servers disabled")); } if let Some(cached_binary) = cached_binary.as_ref() { return Ok(cached_binary.clone()); } - let Some(container_dir) = container_dir else { + let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await else { anyhow::bail!("cannot download language servers for remotes (yet)") }; - if !container_dir.exists() { - smol::fs::create_dir_all(&container_dir) - .await - .context("failed to create container directory")?; - } - let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await; if let Err(error) = binary.as_ref() { @@ -443,21 +433,6 @@ pub trait LspAdapter: 'static + Send + Sync { delegate: &dyn LspAdapterDelegate, ) -> Option; - /// Returns `true` if a language server can be reinstalled. - /// - /// If language server initialization fails, a reinstallation will be attempted unless the value returned from this method is `false`. - /// - /// Implementations that rely on software already installed on user's system - /// should have [`can_be_reinstalled`](Self::can_be_reinstalled) return `false`. - fn can_be_reinstalled(&self) -> bool { - true - } - - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option; - fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} /// Post-processes completions provided by the language server. @@ -1711,8 +1686,8 @@ impl LspAdapter for FakeLspAdapter { fn get_language_server_command<'a>( self: Arc, - _: Option>, _: Arc, + _: LanguageServerBinaryOptions, _: futures::lock::MutexGuard<'a, Option>, _: &'a mut AsyncAppContext, ) -> Pin>>> { @@ -1743,10 +1718,6 @@ impl LspAdapter for FakeLspAdapter { unreachable!(); } - async fn installation_test_binary(&self, _: PathBuf) -> Option { - unreachable!(); - } - fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} fn disk_based_diagnostic_sources(&self) -> Vec { diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index e264517d5b..880ae3b611 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -4,18 +4,17 @@ use crate::{ }, task_context::ContextProvider, with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher, - LanguageServerName, LspAdapter, LspAdapterDelegate, PLAIN_TEXT, + LanguageServerName, LspAdapter, PLAIN_TEXT, }; use anyhow::{anyhow, Context, Result}; use collections::{hash_map, HashMap, HashSet}; use futures::{ channel::{mpsc, oneshot}, - future::Shared, Future, }; use globset::GlobSet; -use gpui::{AppContext, BackgroundExecutor, Task}; +use gpui::{AppContext, BackgroundExecutor}; use lsp::LanguageServerId; use parking_lot::{Mutex, RwLock}; use postage::watch; @@ -118,12 +117,6 @@ pub enum LanguageServerBinaryStatus { Failed { error: String }, } -pub struct PendingLanguageServer { - pub server_id: LanguageServerId, - pub task: Task)>>, - pub container_dir: Option>, -} - #[derive(Clone)] pub struct AvailableLanguage { id: LanguageId, @@ -882,123 +875,53 @@ impl LanguageRegistry { self.lsp_binary_status_tx.send(server_name, status); } - #[allow(clippy::too_many_arguments)] - pub fn create_pending_language_server( - self: &Arc, - stderr_capture: Arc>>, - _language_name_for_tests: LanguageName, - adapter: Arc, - root_path: Arc, - delegate: Arc, - project_environment: Shared>>>, - cx: &mut AppContext, - ) -> Option { - let server_id = self.state.write().next_language_server_id(); - log::info!( - "attempting to start language server {:?}, path: {root_path:?}, id: {server_id}", - adapter.name.0 - ); + pub fn next_language_server_id(&self) -> LanguageServerId { + self.state.write().next_language_server_id() + } - let container_dir: Option> = self - .language_server_download_dir + pub fn language_server_download_dir(&self, name: &LanguageServerName) -> Option> { + self.language_server_download_dir .as_ref() - .map(|dir| Arc::from(dir.join(adapter.name.0.as_ref()))); - let root_path = root_path.clone(); - let this = Arc::downgrade(self); + .map(|dir| Arc::from(dir.join(name.0.as_ref()))) + } - let task = cx.spawn({ - let container_dir = container_dir.clone(); - move |mut cx| async move { - let project_environment = project_environment.await; - - let binary_result = adapter - .clone() - .get_language_server_command(container_dir, delegate.clone(), &mut cx) - .await; - - delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None); - - let mut binary = binary_result?; - - // If we do have a project environment (either by spawning a shell in in the project directory - // or by getting it from the CLI) and the language server command itself - // doesn't have an environment (which it would have, if it was found in $PATH), then - // we use the project environment. - if binary.env.is_none() && project_environment.is_some() { - log::info!( - "using project environment for language server {:?}, id: {server_id}", - adapter.name.0 - ); - binary.env = project_environment.clone(); - } - - let options = adapter - .adapter - .clone() - .initialization_options(&delegate) - .await?; - - #[cfg(any(test, feature = "test-support"))] - if true { - if let Some(this) = this.upgrade() { - if let Some(fake_entry) = this - .state - .write() - .fake_server_entries - .get_mut(&adapter.name) - { - let (server, mut fake_server) = lsp::FakeLanguageServer::new( - server_id, - binary, - adapter.name.0.to_string(), - fake_entry.capabilities.clone(), - cx.clone(), - ); - fake_entry._server = Some(fake_server.clone()); - - if let Some(initializer) = &fake_entry.initializer { - initializer(&mut fake_server); - } - - let tx = fake_entry.tx.clone(); - cx.background_executor() - .spawn(async move { - if fake_server - .try_receive_notification::( - ) - .await - .is_some() - { - tx.unbounded_send(fake_server.clone()).ok(); - } - }) - .detach(); - - return Ok((server, options)); - } - } - } - - drop(this); - Ok(( - lsp::LanguageServer::new( - stderr_capture, - server_id, - binary, - &root_path, - adapter.code_action_kinds(), - cx, - )?, - options, - )) - } - }); - - Some(PendingLanguageServer { + #[cfg(any(test, feature = "test-support"))] + pub fn create_fake_language_server( + &self, + server_id: LanguageServerId, + name: &LanguageServerName, + binary: lsp::LanguageServerBinary, + cx: gpui::AsyncAppContext, + ) -> Option { + let mut state = self.state.write(); + let fake_entry = state.fake_server_entries.get_mut(&name)?; + let (server, mut fake_server) = lsp::FakeLanguageServer::new( server_id, - task, - container_dir, - }) + binary, + name.0.to_string(), + fake_entry.capabilities.clone(), + cx.clone(), + ); + fake_entry._server = Some(fake_server.clone()); + + if let Some(initializer) = &fake_entry.initializer { + initializer(&mut fake_server); + } + + let tx = fake_entry.tx.clone(); + cx.background_executor() + .spawn(async move { + if fake_server + .try_receive_notification::() + .await + .is_some() + { + tx.unbounded_send(fake_server.clone()).ok(); + } + }) + .detach(); + + Some(server) } pub fn language_server_binary_statuses( @@ -1007,29 +930,16 @@ impl LanguageRegistry { self.lsp_binary_status_tx.subscribe() } - pub fn delete_server_container( - &self, - adapter: Arc, - cx: &mut AppContext, - ) -> Task<()> { + pub async fn delete_server_container(&self, name: LanguageServerName) { log::info!("deleting server container"); + let Some(dir) = self.language_server_download_dir(&name) else { + return; + }; - let download_dir = self - .language_server_download_dir - .clone() - .expect("language server download directory has not been assigned before deleting server container"); - - cx.spawn(|_| async move { - let container_dir = download_dir.join(adapter.name.0.as_ref()); - smol::fs::remove_dir_all(container_dir) - .await - .context("server container removal") - .log_err(); - }) - } - - pub fn next_language_server_id(&self) -> LanguageServerId { - self.state.write().next_language_server_id() + smol::fs::remove_dir_all(dir) + .await + .context("server container removal") + .log_err(); } } diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index 8a04e0aae6..28a12b5310 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -5,7 +5,6 @@ use gpui::AsyncAppContext; use http_client::github::{latest_github_release, GitHubLspBinaryVersion}; pub use language::*; use lsp::LanguageServerBinary; -use project::{lsp_store::language_server_settings, project_settings::BinarySettings}; use smol::fs::{self, File}; use std::{any::Any, env::consts, path::PathBuf, sync::Arc}; use util::{fs::remove_matching, maybe, ResultExt}; @@ -25,41 +24,14 @@ impl super::LspAdapter for CLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, - cx: &AsyncAppContext, + _: &AsyncAppContext, ) -> Option { - let configured_binary = cx.update(|cx| { - language_server_settings(delegate, &Self::SERVER_NAME, cx) - .and_then(|s| s.binary.clone()) - }); - - match configured_binary { - Ok(Some(BinarySettings { - path: Some(path), - arguments, - .. - })) => Some(LanguageServerBinary { - path: path.into(), - arguments: arguments - .unwrap_or_default() - .iter() - .map(|arg| arg.into()) - .collect(), - env: None, - }), - Ok(Some(BinarySettings { - path_lookup: Some(false), - .. - })) => None, - _ => { - let env = delegate.shell_env().await; - let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; - Some(LanguageServerBinary { - path, - arguments: vec![], - env: Some(env), - }) - } - } + let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; + Some(LanguageServerBinary { + path, + arguments: vec![], + env: None, + }) } async fn fetch_latest_server_version( @@ -141,18 +113,6 @@ impl super::LspAdapter for CLspAdapter { get_cached_server_binary(container_dir).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir) - .await - .map(|mut binary| { - binary.arguments = vec!["--help".into()]; - binary - }) - } - async fn label_for_completion( &self, completion: &lsp::CompletionItem, diff --git a/crates/languages/src/css.rs b/crates/languages/src/css.rs index 7b7e9ae77f..b4e5feaab7 100644 --- a/crates/languages/src/css.rs +++ b/crates/languages/src/css.rs @@ -84,13 +84,6 @@ impl LspAdapter for CssLspAdapter { get_cached_server_binary(container_dir, &self.node).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir, &self.node).await - } - async fn initialization_options( self: Arc, _: &Arc, diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index a1a996c066..135c080e00 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -6,7 +6,6 @@ use gpui::{AppContext, AsyncAppContext, Task}; use http_client::github::latest_github_release; pub use language::*; use lsp::LanguageServerBinary; -use project::{lsp_store::language_server_settings, project_settings::BinarySettings}; use regex::Regex; use serde_json::json; use smol::{fs, process}; @@ -68,41 +67,14 @@ impl super::LspAdapter for GoLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, - cx: &AsyncAppContext, + _: &AsyncAppContext, ) -> Option { - let configured_binary = cx.update(|cx| { - language_server_settings(delegate, &Self::SERVER_NAME, cx) - .and_then(|s| s.binary.clone()) - }); - - match configured_binary { - Ok(Some(BinarySettings { - path: Some(path), - arguments, - .. - })) => Some(LanguageServerBinary { - path: path.into(), - arguments: arguments - .unwrap_or_default() - .iter() - .map(|arg| arg.into()) - .collect(), - env: None, - }), - Ok(Some(BinarySettings { - path_lookup: Some(false), - .. - })) => None, - _ => { - let env = delegate.shell_env().await; - let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; - Some(LanguageServerBinary { - path, - arguments: server_binary_arguments(), - env: Some(env), - }) - } - } + let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; + Some(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + env: None, + }) } fn will_fetch_server( @@ -214,18 +186,6 @@ impl super::LspAdapter for GoLspAdapter { get_cached_server_binary(container_dir).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir) - .await - .map(|mut binary| { - binary.arguments = vec!["--help".into()]; - binary - }) - } - async fn initialization_options( self: Arc, _: &Arc, diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 44cc683876..95c4070b13 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -186,13 +186,6 @@ impl LspAdapter for JsonLspAdapter { get_cached_server_binary(container_dir, &self.node).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir, &self.node).await - } - async fn initialization_options( self: Arc, _: &Arc, @@ -374,18 +367,6 @@ impl LspAdapter for NodeVersionAdapter { ) -> Option { get_cached_version_server_binary(container_dir).await } - - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_version_server_binary(container_dir) - .await - .map(|mut binary| { - binary.arguments = vec!["--version".into()]; - binary - }) - } } async fn get_cached_version_server_binary(container_dir: PathBuf) -> Option { diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 75f124489c..964abf42b5 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -97,13 +97,6 @@ impl LspAdapter for PythonLspAdapter { get_cached_server_binary(container_dir, &self.node).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir, &self.node).await - } - async fn process_completions(&self, items: &mut [lsp::CompletionItem]) { // Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`. // Where `XX` is the sorting category, `YYYY` is based on most recent usage, diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index eebd573a7e..0d644e1bfe 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -8,7 +8,6 @@ use http_client::github::{latest_github_release, GitHubLspBinaryVersion}; pub use language::*; use language_settings::all_language_settings; use lsp::LanguageServerBinary; -use project::{lsp_store::language_server_settings, project_settings::BinarySettings}; use regex::Regex; use smol::fs::{self, File}; use std::{ @@ -37,77 +36,34 @@ impl LspAdapter for RustLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, - cx: &AsyncAppContext, + _: &AsyncAppContext, ) -> Option { - let configured_binary = cx - .update(|cx| { - language_server_settings(delegate, &Self::SERVER_NAME, cx) - .and_then(|s| s.binary.clone()) + let path = delegate.which("rust-analyzer".as_ref()).await?; + let env = delegate.shell_env().await; + + // It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to + // /usr/bin/rust-analyzer that fails when you run it; so we need to test it. + log::info!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`"); + let result = delegate + .try_exec(LanguageServerBinary { + path: path.clone(), + arguments: vec!["--help".into()], + env: Some(env.clone()), }) - .ok()?; + .await; + if let Err(err) = result { + log::error!( + "failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", + path, + err + ); + return None; + } - let (path, env, arguments) = match configured_binary { - // If nothing is configured, or path_lookup explicitly enabled, - // we lookup the binary in the path. - None - | Some(BinarySettings { - path: None, - path_lookup: Some(true), - .. - }) - | Some(BinarySettings { - path: None, - path_lookup: None, - .. - }) => { - let path = delegate.which("rust-analyzer".as_ref()).await; - let env = delegate.shell_env().await; - - if let Some(path) = path { - // It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to - // /usr/bin/rust-analyzer that fails when you run it; so we need to test it. - log::info!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`"); - match delegate - .try_exec(LanguageServerBinary { - path: path.clone(), - arguments: vec!["--help".into()], - env: Some(env.clone()), - }) - .await - { - Ok(()) => (Some(path), Some(env), None), - Err(err) => { - log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", path, err); - (None, None, None) - } - } - } else { - (None, None, None) - } - } - // Otherwise, we use the configured binary. - Some(BinarySettings { - path: Some(path), - arguments, - path_lookup, - }) => { - if path_lookup.is_some() { - log::warn!("Both `path` and `path_lookup` are set, ignoring `path_lookup`"); - } - (Some(path.into()), None, arguments) - } - - _ => (None, None, None), - }; - - path.map(|path| LanguageServerBinary { + Some(LanguageServerBinary { path, - env, - arguments: arguments - .unwrap_or_default() - .iter() - .map(|arg| arg.into()) - .collect(), + env: Some(env), + arguments: vec![], }) } @@ -186,18 +142,6 @@ impl LspAdapter for RustLspAdapter { get_cached_server_binary(container_dir).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir) - .await - .map(|mut binary| { - binary.arguments = vec!["--help".into()]; - binary - }) - } - fn disk_based_diagnostic_sources(&self) -> Vec { vec!["rustc".into()] } diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs index 62d967d6a4..4ed5c742a9 100644 --- a/crates/languages/src/tailwind.rs +++ b/crates/languages/src/tailwind.rs @@ -46,38 +46,6 @@ impl LspAdapter for TailwindLspAdapter { Self::SERVER_NAME.clone() } - async fn check_if_user_installed( - &self, - delegate: &dyn LspAdapterDelegate, - cx: &AsyncAppContext, - ) -> Option { - let configured_binary = cx - .update(|cx| { - language_server_settings(delegate, &Self::SERVER_NAME, cx) - .and_then(|s| s.binary.clone()) - }) - .ok()??; - - let path = if let Some(configured_path) = configured_binary.path.map(PathBuf::from) { - configured_path - } else { - self.node.binary_path().await.ok()? - }; - - let arguments = configured_binary - .arguments - .unwrap_or_default() - .iter() - .map(|arg| arg.into()) - .collect(); - - Some(LanguageServerBinary { - path, - arguments, - env: None, - }) - } - async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, @@ -125,13 +93,6 @@ impl LspAdapter for TailwindLspAdapter { get_cached_server_binary(container_dir, &self.node).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir, &self.node).await - } - async fn initialization_options( self: Arc, _: &Arc, diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index b7eb21132d..cfd7e04bc6 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -164,13 +164,6 @@ impl LspAdapter for TypeScriptLspAdapter { get_cached_ts_server_binary(container_dir, &self.node).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_ts_server_binary(container_dir, &self.node).await - } - fn code_action_kinds(&self) -> Option> { Some(vec![ CodeActionKind::QUICKFIX, @@ -509,19 +502,6 @@ impl LspAdapter for EsLintLspAdapter { arguments: eslint_server_binary_arguments(&server_path), }) } - - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - let server_path = - Self::build_destination_path(&container_dir).join(EsLintLspAdapter::SERVER_PATH); - Some(LanguageServerBinary { - path: self.node.binary_path().await.ok()?, - env: None, - arguments: eslint_server_binary_arguments(&server_path), - }) - } } #[cfg(target_os = "windows")] diff --git a/crates/languages/src/vtsls.rs b/crates/languages/src/vtsls.rs index de6d575a8e..ff8637dc28 100644 --- a/crates/languages/src/vtsls.rs +++ b/crates/languages/src/vtsls.rs @@ -5,7 +5,7 @@ use gpui::AsyncAppContext; use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; -use project::{lsp_store::language_server_settings, project_settings::BinarySettings}; +use project::lsp_store::language_server_settings; use serde_json::Value; use std::{ any::Any, @@ -71,40 +71,15 @@ impl LspAdapter for VtslsLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, - cx: &AsyncAppContext, + _: &AsyncAppContext, ) -> Option { - let configured_binary = cx.update(|cx| { - language_server_settings(delegate, &SERVER_NAME, cx).and_then(|s| s.binary.clone()) - }); - - match configured_binary { - Ok(Some(BinarySettings { - path: Some(path), - arguments, - .. - })) => Some(LanguageServerBinary { - path: path.into(), - arguments: arguments - .unwrap_or_default() - .iter() - .map(|arg| arg.into()) - .collect(), - env: None, - }), - Ok(Some(BinarySettings { - path_lookup: Some(false), - .. - })) => None, - _ => { - let env = delegate.shell_env().await; - let path = delegate.which(SERVER_NAME.as_ref()).await?; - Some(LanguageServerBinary { - path: path.clone(), - arguments: typescript_server_binary_arguments(&path), - env: Some(env), - }) - } - } + let env = delegate.shell_env().await; + let path = delegate.which(SERVER_NAME.as_ref()).await?; + Some(LanguageServerBinary { + path: path.clone(), + arguments: typescript_server_binary_arguments(&path), + env: Some(env), + }) } async fn fetch_server_binary( @@ -157,13 +132,6 @@ impl LspAdapter for VtslsLspAdapter { get_cached_ts_server_binary(container_dir, &self.node).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_ts_server_binary(container_dir, &self.node).await - } - fn code_action_kinds(&self) -> Option> { Some(vec![ CodeActionKind::QUICKFIX, diff --git a/crates/languages/src/yaml.rs b/crates/languages/src/yaml.rs index 32ca73168a..642d6c030a 100644 --- a/crates/languages/src/yaml.rs +++ b/crates/languages/src/yaml.rs @@ -42,37 +42,6 @@ impl LspAdapter for YamlLspAdapter { Self::SERVER_NAME.clone() } - async fn check_if_user_installed( - &self, - delegate: &dyn LspAdapterDelegate, - cx: &AsyncAppContext, - ) -> Option { - let configured_binary = cx - .update(|cx| { - language_server_settings(delegate, &Self::SERVER_NAME, cx) - .and_then(|s| s.binary.clone()) - }) - .ok()??; - - let path = if let Some(configured_path) = configured_binary.path.map(PathBuf::from) { - configured_path - } else { - self.node.binary_path().await.ok()? - }; - - let arguments = configured_binary - .arguments - .unwrap_or_default() - .iter() - .map(|arg| arg.into()) - .collect(); - Some(LanguageServerBinary { - path, - arguments, - env: None, - }) - } - async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, @@ -120,13 +89,6 @@ impl LspAdapter for YamlLspAdapter { get_cached_server_binary(container_dir, &self.node).await } - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir, &self.node).await - } - async fn workspace_configuration( self: Arc, delegate: &Arc, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index c2a5951de7..e380da052d 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -64,6 +64,15 @@ pub struct LanguageServerBinary { pub env: Option>, } +/// Configures the search (and installation) of language servers. +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageServerBinaryOptions { + /// Whether the adapter should look at the users system + pub allow_path_lookup: bool, + /// Whether the adapter should download its own version + pub allow_binary_download: bool, +} + /// A running language server process. pub struct LanguageServer { server_id: LanguageServerId, diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 8d859c091b..21d5de53e6 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -37,16 +37,16 @@ use language::{ proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageConfig, - LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerName, LocalFile, LspAdapter, - LspAdapterDelegate, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset, - ToPointUtf16, Transaction, Unclipped, + LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus, + LanguageServerName, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16, + TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use lsp::{ CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, Edit, FileSystemWatcher, InsertTextFormat, - LanguageServer, LanguageServerBinary, LanguageServerId, LspRequestFuture, MessageActionItem, - MessageType, OneOf, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, Url, - WorkDoneProgressCancelParams, WorkspaceFolder, + LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, + LspRequestFuture, MessageActionItem, MessageType, OneOf, ServerHealthStatus, ServerStatus, + SymbolKind, TextEdit, Url, WorkDoneProgressCancelParams, WorkspaceFolder, }; use parking_lot::{Mutex, RwLock}; use postage::watch; @@ -67,9 +67,8 @@ use std::{ iter, mem, ops::{ControlFlow, Range}, path::{self, Path, PathBuf}, - process::Stdio, str, - sync::{atomic::Ordering::SeqCst, Arc}, + sync::Arc, time::{Duration, Instant}, }; use text::{Anchor, BufferId, LineEnding}; @@ -87,8 +86,6 @@ pub use worktree::{ FS_WATCH_LATENCY, }; -const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4; -const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1); const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100); @@ -157,6 +154,7 @@ impl LocalLspStore { futures::future::join_all(shutdown_futures).await; } } + async fn format_locally( lsp_store: WeakModel, mut buffers_with_paths: Vec<(Model, Option)>, @@ -1471,7 +1469,7 @@ impl LspStore { } for (worktree_id, adapter_name) in language_servers_to_stop { - self.stop_language_server(worktree_id, adapter_name, cx) + self.stop_local_language_server(worktree_id, adapter_name, cx) .detach(); } @@ -1488,7 +1486,7 @@ impl LspStore { // Restart all language servers with changed initialization options. for (worktree, language) in language_servers_to_restart { - self.restart_language_servers(worktree, language, cx); + self.restart_local_language_servers(worktree, language, cx); } cx.notify(); @@ -3028,7 +3026,7 @@ impl LspStore { }) } - pub fn primary_language_server_for_buffer<'a>( + fn primary_language_server_for_buffer<'a>( &'a self, buffer: &'a Buffer, cx: &'a AppContext, @@ -3328,7 +3326,7 @@ impl LspStore { Ok(()) } - pub fn update_worktree_diagnostics( + fn update_worktree_diagnostics( &mut self, worktree_id: WorktreeId, server_id: LanguageServerId, @@ -5405,9 +5403,6 @@ impl LspStore { language_registry: self.languages.clone(), }) as Arc; - // TODO: We should use `adapter` here instead of reaching through the `CachedLspAdapter`. - let lsp_adapter = adapter.adapter.clone(); - let Some((upstream_client, project_id)) = self.upstream_client() else { return; }; @@ -5419,17 +5414,11 @@ impl LspStore { return; }; - let task = cx.spawn(|_, cx| async move { - let user_binary_task = lsp_adapter.check_if_user_installed(delegate.as_ref(), &cx); - let binary = match user_binary_task.await { - Some(binary) => binary, - None => { - return Err(anyhow!( - "Downloading language server for ssh host is not supported yet" - )) - } - }; + let user_binary_task = + self.get_language_server_binary(adapter.clone(), delegate.clone(), false, cx); + let task = cx.spawn(|_, _| async move { + let binary = user_binary_task.await?; let name = adapter.name(); let code_action_kinds = adapter .adapter @@ -5481,6 +5470,73 @@ impl LspStore { .detach(); } + fn get_language_server_binary( + &self, + adapter: Arc, + delegate: Arc, + allow_binary_download: bool, + cx: &mut ModelContext, + ) -> Task> { + let settings = ProjectSettings::get( + Some(SettingsLocation { + worktree_id: delegate.worktree_id(), + path: Path::new(""), + }), + cx, + ) + .lsp + .get(&adapter.name) + .and_then(|s| s.binary.clone()); + + if settings.as_ref().is_some_and(|b| b.path.is_some()) { + let settings = settings.unwrap(); + return cx.spawn(|_, _| async move { + Ok(LanguageServerBinary { + path: PathBuf::from(&settings.path.unwrap()), + env: Some(delegate.shell_env().await), + arguments: settings + .arguments + .unwrap_or_default() + .iter() + .map(Into::into) + .collect(), + }) + }); + } + let lsp_binary_options = LanguageServerBinaryOptions { + allow_path_lookup: !settings + .as_ref() + .and_then(|b| b.ignore_system_version) + .unwrap_or_default(), + allow_binary_download, + }; + cx.spawn(|_, mut cx| async move { + let binary_result = adapter + .clone() + .get_language_server_command(delegate.clone(), lsp_binary_options, &mut cx) + .await; + + delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None); + + let mut binary = binary_result?; + if let Some(arguments) = settings.and_then(|b| b.arguments) { + binary.arguments = arguments.into_iter().map(Into::into).collect(); + } + + // If we do have a project environment (either by spawning a shell in in the project directory + // or by getting it from the CLI) and the language server command itself + // doesn't have an environment, then we use the project environment. + if binary.env.is_none() { + log::info!( + "using project environment for language server {:?}", + adapter.name() + ); + binary.env = Some(delegate.shell_env().await); + } + Ok(binary) + }) + } + fn start_language_server( &mut self, worktree_handle: &Model, @@ -5496,6 +5552,7 @@ impl LspStore { let worktree_id = worktree.id(); let worktree_path = worktree.abs_path(); let key = (worktree_id, adapter.name.clone()); + if self.language_server_ids.contains_key(&key) { return; } @@ -5505,31 +5562,6 @@ impl LspStore { return; } - if adapter.reinstall_attempt_count.load(SeqCst) > MAX_SERVER_REINSTALL_ATTEMPT_COUNT { - return; - } - - let local = self.as_local().unwrap(); - - let stderr_capture = Arc::new(Mutex::new(Some(String::new()))); - let lsp_adapter_delegate = LocalLspAdapterDelegate::for_local(self, worktree_handle, cx); - let project_environment = local.environment.update(cx, |environment, cx| { - environment.get_environment(Some(worktree_id), Some(worktree_path.clone()), cx) - }); - - let pending_server = match self.languages.create_pending_language_server( - stderr_capture.clone(), - language.clone(), - adapter.clone(), - Arc::clone(&worktree_path), - lsp_adapter_delegate.clone(), - project_environment, - cx, - ) { - Some(pending_server) => pending_server, - None => return, - }; - let project_settings = ProjectSettings::get( Some(SettingsLocation { worktree_id, @@ -5537,76 +5569,146 @@ impl LspStore { }), cx, ); - - // We need some on the SSH client, and some on SSH host let lsp = project_settings.lsp.get(&adapter.name); let override_options = lsp.and_then(|s| s.initialization_options.clone()); - let server_id = pending_server.server_id; - let container_dir = pending_server.container_dir.clone(); - let state = LanguageServerState::Starting({ + let stderr_capture = Arc::new(Mutex::new(Some(String::new()))); + let delegate = LocalLspAdapterDelegate::for_local(self, worktree_handle, cx) + as Arc; + + let server_id = self.languages.next_language_server_id(); + let root_path = worktree_path.clone(); + log::info!( + "attempting to start language server {:?}, path: {root_path:?}, id: {server_id}", + adapter.name.0 + ); + + let binary = self.get_language_server_binary(adapter.clone(), delegate.clone(), true, cx); + + let pending_server = cx.spawn({ let adapter = adapter.clone(); + let stderr_capture = stderr_capture.clone(); + + move |_lsp_store, cx| async move { + let binary = binary.await?; + + #[cfg(any(test, feature = "test-support"))] + if let Some(server) = _lsp_store + .update(&mut cx.clone(), |this, cx| { + this.languages.create_fake_language_server( + server_id, + &adapter.name, + binary.clone(), + cx.to_async(), + ) + }) + .ok() + .flatten() + { + return Ok(server); + } + + lsp::LanguageServer::new( + stderr_capture, + server_id, + binary, + &root_path, + adapter.code_action_kinds(), + cx, + ) + } + }); + + let state = LanguageServerState::Starting({ let server_name = adapter.name.0.clone(); + let delegate = delegate as Arc; let language = language.clone(); let key = key.clone(); + let adapter = adapter.clone(); cx.spawn(move |this, mut cx| async move { - let result = Self::setup_and_insert_language_server( - this.clone(), - lsp_adapter_delegate, - override_options, - pending_server, - adapter.clone(), - language.clone(), - server_id, - key, - &mut cx, - ) + let result = { + let delegate = delegate.clone(); + let adapter = adapter.clone(); + let this = this.clone(); + let mut cx = cx.clone(); + async move { + let language_server = pending_server.await?; + + let workspace_config = adapter + .adapter + .clone() + .workspace_configuration(&delegate, &mut cx) + .await?; + + let mut initialization_options = adapter + .adapter + .clone() + .initialization_options(&(delegate)) + .await?; + + Self::setup_lsp_messages(this.clone(), &language_server, delegate, adapter); + + match (&mut initialization_options, override_options) { + (Some(initialization_options), Some(override_options)) => { + merge_json_value_into(override_options, initialization_options); + } + (None, override_options) => initialization_options = override_options, + _ => {} + } + + let language_server = cx + .update(|cx| language_server.initialize(initialization_options, cx))? + .await + .inspect_err(|_| { + if let Some(this) = this.upgrade() { + this.update(&mut cx, |_, cx| { + cx.emit(LspStoreEvent::LanguageServerRemoved(server_id)) + }) + .ok(); + } + })?; + + language_server + .notify::( + lsp::DidChangeConfigurationParams { + settings: workspace_config, + }, + ) + .ok(); + + anyhow::Ok(language_server) + } + } .await; match result { Ok(server) => { + this.update(&mut cx, |this, mut cx| { + this.insert_newly_running_language_server( + language, + adapter, + server.clone(), + server_id, + key, + &mut cx, + ); + }) + .ok(); stderr_capture.lock().take(); - server + Some(server) } Err(err) => { - log::error!("failed to start language server {server_name:?}: {err}"); - log::error!("server stderr: {:?}", stderr_capture.lock().take()); - - let this = this.upgrade()?; - let container_dir = container_dir?; - - let attempt_count = adapter.reinstall_attempt_count.fetch_add(1, SeqCst); - if attempt_count >= MAX_SERVER_REINSTALL_ATTEMPT_COUNT { - let max = MAX_SERVER_REINSTALL_ATTEMPT_COUNT; - log::error!("Hit {max} reinstallation attempts for {server_name:?}"); - return None; - } - - log::info!( - "retrying installation of language server {server_name:?} in {}s", - SERVER_REINSTALL_DEBOUNCE_TIMEOUT.as_secs() + let log = stderr_capture.lock().take().unwrap_or_default(); + delegate.update_status( + adapter.name(), + LanguageServerBinaryStatus::Failed { + error: format!("{err}\n-- stderr--\n{}", log), + }, ); - cx.background_executor() - .timer(SERVER_REINSTALL_DEBOUNCE_TIMEOUT) - .await; - - let installation_test_binary = adapter - .installation_test_binary(container_dir.to_path_buf()) - .await; - - this.update(&mut cx, |_, cx| { - Self::check_errored_server( - language, - adapter, - server_id, - installation_test_binary, - cx, - ) - }) - .ok(); - + log::error!("Failed to start language server {server_name:?}: {err}"); + log::error!("server stderr: {:?}", log); None } } @@ -5620,109 +5722,6 @@ impl LspStore { self.language_server_ids.insert(key, server_id); } - #[allow(clippy::too_many_arguments)] - async fn setup_and_insert_language_server( - this: WeakModel, - delegate: Arc, - override_initialization_options: Option, - pending_server: PendingLanguageServer, - adapter: Arc, - language: LanguageName, - server_id: LanguageServerId, - key: (WorktreeId, LanguageServerName), - cx: &mut AsyncAppContext, - ) -> Result>> { - let language_server = Self::setup_pending_language_server( - this.clone(), - override_initialization_options, - pending_server, - delegate, - adapter.clone(), - server_id, - cx, - ) - .await?; - - let this = match this.upgrade() { - Some(this) => this, - None => return Err(anyhow!("failed to upgrade project handle")), - }; - - this.update(cx, |this, cx| { - this.insert_newly_running_language_server( - language, - adapter, - language_server.clone(), - server_id, - key, - cx, - ) - })??; - - Ok(Some(language_server)) - } - - fn reinstall_language_server( - &mut self, - language: LanguageName, - adapter: Arc, - server_id: LanguageServerId, - cx: &mut ModelContext, - ) -> Option> { - log::info!("beginning to reinstall server"); - - if let Some(local) = self.as_local_mut() { - let existing_server = match local.language_servers.remove(&server_id) { - Some(LanguageServerState::Running { server, .. }) => Some(server), - _ => None, - }; - - self.worktree_store.update(cx, |store, cx| { - for worktree in store.worktrees() { - let key = (worktree.read(cx).id(), adapter.name.clone()); - self.language_server_ids.remove(&key); - } - }); - - Some(cx.spawn(move |this, mut cx| async move { - if let Some(task) = existing_server.and_then(|server| server.shutdown()) { - log::info!("shutting down existing server"); - task.await; - } - - // TODO: This is race-safe with regards to preventing new instances from - // starting while deleting, but existing instances in other projects are going - // to be very confused and messed up - let Some(task) = this - .update(&mut cx, |this, cx| { - this.languages.delete_server_container(adapter.clone(), cx) - }) - .log_err() - else { - return; - }; - task.await; - - this.update(&mut cx, |this, cx| { - for worktree in this.worktree_store.read(cx).worktrees().collect::>() { - this.start_language_server( - &worktree, - adapter.clone(), - language.clone(), - cx, - ); - } - }) - .ok(); - })) - } else if let Some(_ssh_store) = self.as_ssh() { - // TODO - None - } else { - None - } - } - async fn shutdown_language_server( server_state: Option, name: LanguageServerName, @@ -5761,7 +5760,7 @@ impl LspStore { // Returns a list of all of the worktrees which no longer have a language server and the root path // for the stopped server - pub fn stop_language_server( + fn stop_local_language_server( &mut self, worktree_id: WorktreeId, adapter_name: LanguageServerName, @@ -5877,7 +5876,6 @@ impl LspStore { .spawn(request) .detach_and_log_err(cx); } else { - #[allow(clippy::mutable_key_type)] let language_server_lookup_info: HashSet<(Model, LanguageName)> = buffers .into_iter() .filter_map(|buffer| { @@ -5893,12 +5891,12 @@ impl LspStore { .collect(); for (worktree, language) in language_server_lookup_info { - self.restart_language_servers(worktree, language, cx); + self.restart_local_language_servers(worktree, language, cx); } } } - pub fn restart_language_servers( + fn restart_local_language_servers( &mut self, worktree: Model, language: LanguageName, @@ -5912,7 +5910,8 @@ impl LspStore { .lsp_adapters(&language) .iter() .map(|adapter| { - let stop_task = self.stop_language_server(worktree_id, adapter.name.clone(), cx); + let stop_task = + self.stop_local_language_server(worktree_id, adapter.name.clone(), cx); (stop_task, adapter.name.clone()) }) .collect::>(); @@ -5951,93 +5950,14 @@ impl LspStore { .detach(); } - fn check_errored_server( - language: LanguageName, - adapter: Arc, - server_id: LanguageServerId, - installation_test_binary: Option, - cx: &mut ModelContext, - ) { - if !adapter.can_be_reinstalled() { - log::info!( - "Validation check requested for {:?} but it cannot be reinstalled", - adapter.name.0 - ); - return; - } - - cx.spawn(move |this, mut cx| async move { - log::info!("About to spawn test binary"); - - // A lack of test binary counts as a failure - let process = installation_test_binary.and_then(|binary| { - smol::process::Command::new(&binary.path) - .current_dir(&binary.path) - .args(binary.arguments) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .kill_on_drop(true) - .spawn() - .ok() - }); - - const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); - let mut timeout = cx.background_executor().timer(PROCESS_TIMEOUT).fuse(); - - let mut errored = false; - if let Some(mut process) = process { - futures::select! { - status = process.status().fuse() => match status { - Ok(status) => errored = !status.success(), - Err(_) => errored = true, - }, - - _ = timeout => { - log::info!("test binary time-ed out, this counts as a success"); - _ = process.kill(); - } - } - } else { - log::warn!("test binary failed to launch"); - errored = true; - } - - if errored { - log::warn!("test binary check failed"); - let task = this - .update(&mut cx, move |this, cx| { - this.reinstall_language_server(language, adapter, server_id, cx) - }) - .ok() - .flatten(); - - if let Some(task) = task { - task.await; - } - } - }) - .detach(); - } - - async fn setup_pending_language_server( + fn setup_lsp_messages( this: WeakModel, - override_options: Option, - pending_server: PendingLanguageServer, + language_server: &LanguageServer, delegate: Arc, adapter: Arc, - server_id: LanguageServerId, - cx: &mut AsyncAppContext, - ) -> Result> { - let workspace_config = adapter - .adapter - .clone() - .workspace_configuration(&delegate, cx) - .await?; - // This has to come from the server - let (language_server, mut initialization_options) = pending_server.task.await?; - + ) { let name = language_server.name(); + let server_id = language_server.server_id(); language_server .on_notification::({ let adapter = adapter.clone(); @@ -6091,7 +6011,6 @@ impl LspStore { }) .detach(); - let id = language_server.server_id(); language_server .on_request::({ let this = this.clone(); @@ -6099,7 +6018,7 @@ impl LspStore { let this = this.clone(); async move { let Some(server) = - this.update(&mut cx, |this, _| this.language_server_for_id(id))? + this.update(&mut cx, |this, _| this.language_server_for_id(server_id))? else { return Ok(None); }; @@ -6375,9 +6294,6 @@ impl LspStore { }) .detach(); - let disk_based_diagnostics_progress_token = - adapter.disk_based_diagnostics_progress_token.clone(); - language_server .on_notification::({ let this = this.clone(); @@ -6448,6 +6364,10 @@ impl LspStore { } }) .detach(); + + let disk_based_diagnostics_progress_token = + adapter.disk_based_diagnostics_progress_token.clone(); + language_server .on_notification::({ let this = this.clone(); @@ -6502,36 +6422,6 @@ impl LspStore { } }) .detach(); - - match (&mut initialization_options, override_options) { - (Some(initialization_options), Some(override_options)) => { - merge_json_value_into(override_options, initialization_options); - } - (None, override_options) => initialization_options = override_options, - _ => {} - } - - let language_server = cx - .update(|cx| language_server.initialize(initialization_options, cx))? - .await - .inspect_err(|_| { - if let Some(this) = this.upgrade() { - this.update(cx, |_, cx| { - cx.emit(LspStoreEvent::LanguageServerRemoved(server_id)) - }) - .ok(); - } - })?; - - language_server - .notify::( - lsp::DidChangeConfigurationParams { - settings: workspace_config, - }, - ) - .ok(); - - Ok(language_server) } pub fn update_diagnostics( @@ -6664,7 +6554,7 @@ impl LspStore { server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), cx: &mut ModelContext, - ) -> Result<()> { + ) { // If the language server for this key doesn't match the server id, don't store the // server. Which will cause it to be dropped, killing the process if self @@ -6673,7 +6563,7 @@ impl LspStore { .map(|id| id != &server_id) .unwrap_or(false) { - return Ok(()); + return; } // Update language_servers collection with Running variant of LanguageServerState @@ -6703,13 +6593,15 @@ impl LspStore { cx.emit(LspStoreEvent::LanguageServerAdded(server_id)); if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() { - downstream_client.send(proto::StartLanguageServer { - project_id: *project_id, - server: Some(proto::LanguageServer { - id: server_id.0 as u64, - name: language_server.name().to_string(), - }), - })?; + downstream_client + .send(proto::StartLanguageServer { + project_id: *project_id, + server: Some(proto::LanguageServer { + id: server_id.0 as u64, + name: language_server.name().to_string(), + }), + }) + .log_err(); } // Tell the language server about every open buffer in the worktree that matches the language. @@ -6756,16 +6648,18 @@ impl LspStore { let version = snapshot.version; let initial_snapshot = &snapshot.snapshot; let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); - language_server.notify::( - lsp::DidOpenTextDocumentParams { - text_document: lsp::TextDocumentItem::new( - uri, - adapter.language_id(&language.name()), - version, - initial_snapshot.text(), - ), - }, - )?; + language_server + .notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( + uri, + adapter.language_id(&language.name()), + version, + initial_snapshot.text(), + ), + }, + ) + .log_err(); buffer_handle.update(cx, |buffer, cx| { buffer.set_completion_triggers( @@ -6779,11 +6673,9 @@ impl LspStore { ) }); } - anyhow::Ok(()) - })?; + }); cx.notify(); - Ok(()) } fn buffer_snapshot_for_lsp_version( @@ -6878,7 +6770,7 @@ impl LspStore { }) } - pub fn register_supplementary_language_server( + fn register_supplementary_language_server( &mut self, id: LanguageServerId, name: LanguageServerName, @@ -6893,7 +6785,7 @@ impl LspStore { } } - pub fn unregister_supplementary_language_server( + fn unregister_supplementary_language_server( &mut self, id: LanguageServerId, cx: &mut ModelContext, @@ -7807,11 +7699,8 @@ impl LspAdapter for SshLspAdapter { ) -> Result { anyhow::bail!("SshLspAdapter does not support fetch_server_binary") } - - async fn installation_test_binary(&self, _: PathBuf) -> Option { - None - } } + pub fn language_server_settings<'a, 'b: 'a>( delegate: &'a dyn LspAdapterDelegate, language: &LanguageServerName, @@ -7855,22 +7744,6 @@ impl LocalLspAdapterDelegate { Self::new(lsp_store, worktree, http_client, local.fs.clone(), cx) } - // fn for_ssh( - // lsp_store: &LspStore, - // worktree: &Model, - // upstream_client: AnyProtoClient, - // cx: &mut ModelContext, - // ) -> Arc { - // Self::new( - // lsp_store, - // worktree, - // Arc::new(BlockedHttpClient), - // None, - // Some(upstream_client), - // cx, - // ) - // } - pub fn new( lsp_store: &LspStore, worktree: &Model, @@ -7972,6 +7845,19 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { .update_lsp_status(server_name, status); } + async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option> { + let dir = self.language_registry.language_server_download_dir(name)?; + + if !dir.exists() { + smol::fs::create_dir_all(&dir) + .await + .context("failed to create container directory") + .log_err()?; + } + + Some(dir) + } + async fn read_text_file(&self, path: PathBuf) -> Result { if self.worktree.entry_for_path(&path).is_none() { return Err(anyhow!("no such path {path:?}")); @@ -8056,6 +7942,10 @@ impl LspAdapterDelegate for SshLspAdapterDelegate { Ok(()) } + async fn language_server_download_dir(&self, _: &LanguageServerName) -> Option> { + None + } + fn update_status( &self, server_name: LanguageServerName, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ee7f93a4f9..8d95c8f2f1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3958,14 +3958,6 @@ impl Project { self.lsp_store.read(cx).supplementary_language_servers() } - pub fn language_server_adapter_for_id( - &self, - id: LanguageServerId, - cx: &AppContext, - ) -> Option> { - self.lsp_store.read(cx).language_server_adapter_for_id(id) - } - pub fn language_server_for_id( &self, id: LanguageServerId, diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 68593f8fab..706d3afdce 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -55,7 +55,7 @@ pub struct NodeBinarySettings { pub npm_path: Option, /// If disabled, zed will download its own copy of node. #[serde(default)] - pub disable_path_lookup: Option, + pub ignore_system_version: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -143,7 +143,7 @@ const fn true_value() -> bool { pub struct BinarySettings { pub path: Option, pub arguments: Option>, - pub path_lookup: Option, + pub ignore_system_version: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e3fe2baefa..0f37e06f43 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -481,7 +481,7 @@ fn main() { cx.observe_global::(move |cx| { let settings = &ProjectSettings::get_global(cx).node; let options = NodeBinaryOptions { - allow_path_lookup: !settings.disable_path_lookup.unwrap_or_default(), + allow_path_lookup: !settings.ignore_system_version.unwrap_or_default(), // TODO: Expose this setting allow_binary_download: true, use_paths: settings.path.as_ref().map(|node_path| { diff --git a/docs/src/languages/rust.md b/docs/src/languages/rust.md index 233c378dae..02e90d60a4 100644 --- a/docs/src/languages/rust.md +++ b/docs/src/languages/rust.md @@ -64,14 +64,14 @@ You can configure which `rust-analyzer` binary Zed should use. By default, Zed will try to find a `rust-analyzer` in your `$PATH` and try to use that. If that binary successfully executes `rust-analyzer --help`, it's used. Otherwise, Zed will fall back to installing its own `rust-analyzer` version and using that. -If you want to disable Zed looking for a `rust-analyzer` binary, you can set `path_lookup` to `false` in your `settings.json`: +If you want to disable Zed looking for a `rust-analyzer` binary, you can set `ignore_system_version` to `true` in your `settings.json`: ```json { "lsp": { "rust-analyzer": { "binary": { - "path_lookup": false + "ignore_system_version": true } } }