Detailed changes
@@ -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": {},
@@ -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())
@@ -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<Self>,
- _: Option<Arc<Path>>,
delegate: Arc<dyn LspAdapterDelegate>,
+ _: LanguageServerBinaryOptions,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: &'a mut AsyncAppContext,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
@@ -124,10 +119,6 @@ impl LspAdapter for ExtensionLspAdapter {
unreachable!("get_language_server_command is overridden")
}
- async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
- None
- }
-
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
let code_action_kinds = self
.extension
@@ -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<Self>,
- container_dir: Option<Arc<Path>>,
delegate: Arc<dyn LspAdapterDelegate>,
+ binary_options: LanguageServerBinaryOptions,
cx: &mut AsyncAppContext,
) -> Result<LanguageServerBinary> {
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<LanguageServerBinary> {
- self.adapter.installation_test_binary(container_dir).await
- }
-
pub fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
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<Arc<Path>>;
async fn which(&self, command: &OsStr) -> Option<PathBuf>;
async fn shell_env(&self) -> HashMap<String, String>;
@@ -335,8 +325,8 @@ pub trait LspAdapter: 'static + Send + Sync {
fn get_language_server_command<'a>(
self: Arc<Self>,
- container_dir: Option<Arc<Path>>,
delegate: Arc<dyn LspAdapterDelegate>,
+ binary_options: LanguageServerBinaryOptions,
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
cx: &'a mut AsyncAppContext,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
@@ -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<LanguageServerBinary>;
- /// 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<LanguageServerBinary>;
-
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<Self>,
- _: Option<Arc<Path>>,
_: Arc<dyn LspAdapterDelegate>,
+ _: LanguageServerBinaryOptions,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: &'a mut AsyncAppContext,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
@@ -1743,10 +1718,6 @@ impl LspAdapter for FakeLspAdapter {
unreachable!();
}
- async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
- unreachable!();
- }
-
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
fn disk_based_diagnostic_sources(&self) -> Vec<String> {
@@ -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<Result<(lsp::LanguageServer, Option<serde_json::Value>)>>,
- pub container_dir: Option<Arc<Path>>,
-}
-
#[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<Self>,
- stderr_capture: Arc<Mutex<Option<String>>>,
- _language_name_for_tests: LanguageName,
- adapter: Arc<CachedLspAdapter>,
- root_path: Arc<Path>,
- delegate: Arc<dyn LspAdapterDelegate>,
- project_environment: Shared<Task<Option<HashMap<String, String>>>>,
- cx: &mut AppContext,
- ) -> Option<PendingLanguageServer> {
- 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<Arc<Path>> = self
- .language_server_download_dir
+ pub fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>> {
+ 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);
-
- 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?;
+ .map(|dir| Arc::from(dir.join(name.0.as_ref())))
+ }
- #[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);
- }
+ #[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<lsp::LanguageServer> {
+ 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,
+ binary,
+ name.0.to_string(),
+ fake_entry.capabilities.clone(),
+ cx.clone(),
+ );
+ fake_entry._server = Some(fake_server.clone());
- let tx = fake_entry.tx.clone();
- cx.background_executor()
- .spawn(async move {
- if fake_server
- .try_receive_notification::<lsp::notification::Initialized>(
- )
- .await
- .is_some()
- {
- tx.unbounded_send(fake_server.clone()).ok();
- }
- })
- .detach();
+ if let Some(initializer) = &fake_entry.initializer {
+ initializer(&mut fake_server);
+ }
- return Ok((server, options));
- }
- }
+ let tx = fake_entry.tx.clone();
+ cx.background_executor()
+ .spawn(async move {
+ if fake_server
+ .try_receive_notification::<lsp::notification::Initialized>()
+ .await
+ .is_some()
+ {
+ tx.unbounded_send(fake_server.clone()).ok();
}
+ })
+ .detach();
- drop(this);
- Ok((
- lsp::LanguageServer::new(
- stderr_capture,
- server_id,
- binary,
- &root_path,
- adapter.code_action_kinds(),
- cx,
- )?,
- options,
- ))
- }
- });
-
- Some(PendingLanguageServer {
- server_id,
- task,
- container_dir,
- })
+ 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<CachedLspAdapter>,
- 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();
}
}
@@ -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<LanguageServerBinary> {
- 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<LanguageServerBinary> {
- 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,
@@ -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<LanguageServerBinary> {
- get_cached_server_binary(container_dir, &self.node).await
- }
-
async fn initialization_options(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
@@ -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<LanguageServerBinary> {
- 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<LanguageServerBinary> {
- get_cached_server_binary(container_dir)
- .await
- .map(|mut binary| {
- binary.arguments = vec!["--help".into()];
- binary
- })
- }
-
async fn initialization_options(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
@@ -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<LanguageServerBinary> {
- get_cached_server_binary(container_dir, &self.node).await
- }
-
async fn initialization_options(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
@@ -374,18 +367,6 @@ impl LspAdapter for NodeVersionAdapter {
) -> Option<LanguageServerBinary> {
get_cached_version_server_binary(container_dir).await
}
-
- async fn installation_test_binary(
- &self,
- container_dir: PathBuf,
- ) -> Option<LanguageServerBinary> {
- 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<LanguageServerBinary> {
@@ -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<LanguageServerBinary> {
- 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,
@@ -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<LanguageServerBinary> {
- 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()?;
-
- 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),
- };
+ .await;
+ if let Err(err) = result {
+ log::error!(
+ "failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}",
+ path,
+ err
+ );
+ return 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<LanguageServerBinary> {
- get_cached_server_binary(container_dir)
- .await
- .map(|mut binary| {
- binary.arguments = vec!["--help".into()];
- binary
- })
- }
-
fn disk_based_diagnostic_sources(&self) -> Vec<String> {
vec!["rustc".into()]
}
@@ -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<LanguageServerBinary> {
- 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<LanguageServerBinary> {
- get_cached_server_binary(container_dir, &self.node).await
- }
-
async fn initialization_options(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
@@ -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<LanguageServerBinary> {
- get_cached_ts_server_binary(container_dir, &self.node).await
- }
-
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
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<LanguageServerBinary> {
- 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")]
@@ -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<LanguageServerBinary> {
- 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<LanguageServerBinary> {
- get_cached_ts_server_binary(container_dir, &self.node).await
- }
-
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
Some(vec![
CodeActionKind::QUICKFIX,
@@ -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<LanguageServerBinary> {
- 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<LanguageServerBinary> {
- get_cached_server_binary(container_dir, &self.node).await
- }
-
async fn workspace_configuration(
self: Arc<Self>,
delegate: &Arc<dyn LspAdapterDelegate>,
@@ -64,6 +64,15 @@ pub struct LanguageServerBinary {
pub env: Option<HashMap<String, String>>,
}
+/// 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,
@@ -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<LspStore>,
mut buffers_with_paths: Vec<(Model<Buffer>, Option<PathBuf>)>,
@@ -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<dyn LspAdapterDelegate>;
- // 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<CachedLspAdapter>,
+ delegate: Arc<dyn LspAdapterDelegate>,
+ allow_binary_download: bool,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<LanguageServerBinary>> {
+ 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<Worktree>,
@@ -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<dyn LspAdapterDelegate>;
+
+ 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<dyn LspAdapterDelegate>;
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,
- )
- .await;
+ 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?;
- match result {
- Ok(server) => {
- stderr_capture.lock().take();
- server
- }
+ let workspace_config = adapter
+ .adapter
+ .clone()
+ .workspace_configuration(&delegate, &mut cx)
+ .await?;
- Err(err) => {
- log::error!("failed to start language server {server_name:?}: {err}");
- log::error!("server stderr: {:?}", stderr_capture.lock().take());
+ let mut initialization_options = adapter
+ .adapter
+ .clone()
+ .initialization_options(&(delegate))
+ .await?;
- let this = this.upgrade()?;
- let container_dir = container_dir?;
+ Self::setup_lsp_messages(this.clone(), &language_server, delegate, adapter);
- 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;
+ 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,
+ _ => {}
}
- log::info!(
- "retrying installation of language server {server_name:?} in {}s",
- SERVER_REINSTALL_DEBOUNCE_TIMEOUT.as_secs()
- );
- cx.background_executor()
- .timer(SERVER_REINSTALL_DEBOUNCE_TIMEOUT)
- .await;
+ 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();
+ }
+ })?;
- let installation_test_binary = adapter
- .installation_test_binary(container_dir.to_path_buf())
- .await;
+ language_server
+ .notify::<lsp::notification::DidChangeConfiguration>(
+ lsp::DidChangeConfigurationParams {
+ settings: workspace_config,
+ },
+ )
+ .ok();
- this.update(&mut cx, |_, cx| {
- Self::check_errored_server(
+ 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,
- installation_test_binary,
- cx,
- )
+ key,
+ &mut cx,
+ );
})
.ok();
+ stderr_capture.lock().take();
+ Some(server)
+ }
+ Err(err) => {
+ let log = stderr_capture.lock().take().unwrap_or_default();
+ delegate.update_status(
+ adapter.name(),
+ LanguageServerBinaryStatus::Failed {
+ error: format!("{err}\n-- stderr--\n{}", log),
+ },
+ );
+ 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<Self>,
- delegate: Arc<dyn LspAdapterDelegate>,
- override_initialization_options: Option<serde_json::Value>,
- pending_server: PendingLanguageServer,
- adapter: Arc<CachedLspAdapter>,
- language: LanguageName,
- server_id: LanguageServerId,
- key: (WorktreeId, LanguageServerName),
- cx: &mut AsyncAppContext,
- ) -> Result<Option<Arc<LanguageServer>>> {
- 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<CachedLspAdapter>,
- server_id: LanguageServerId,
- cx: &mut ModelContext<Self>,
- ) -> Option<Task<()>> {
- 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::<Vec<_>>() {
- 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<LanguageServerState>,
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<Worktree>, 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<Worktree>,
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::<Vec<_>>();
@@ -5951,93 +5950,14 @@ impl LspStore {
.detach();
}
- fn check_errored_server(
- language: LanguageName,
- adapter: Arc<CachedLspAdapter>,
- server_id: LanguageServerId,
- installation_test_binary: Option<LanguageServerBinary>,
- cx: &mut ModelContext<Self>,
- ) {
- 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<Self>,
- override_options: Option<serde_json::Value>,
- pending_server: PendingLanguageServer,
+ language_server: &LanguageServer,
delegate: Arc<dyn LspAdapterDelegate>,
adapter: Arc<CachedLspAdapter>,
- server_id: LanguageServerId,
- cx: &mut AsyncAppContext,
- ) -> Result<Arc<LanguageServer>> {
- 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::<lsp::notification::PublishDiagnostics, _>({
let adapter = adapter.clone();
@@ -6091,7 +6011,6 @@ impl LspStore {
})
.detach();
- let id = language_server.server_id();
language_server
.on_request::<lsp::request::WorkspaceFoldersRequest, _, _>({
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::<ServerStatus, _>({
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::<lsp::notification::Progress, _>({
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::notification::DidChangeConfiguration>(
- 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<Self>,
- ) -> 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::notification::DidOpenTextDocument>(
- lsp::DidOpenTextDocumentParams {
- text_document: lsp::TextDocumentItem::new(
- uri,
- adapter.language_id(&language.name()),
- version,
- initial_snapshot.text(),
- ),
- },
- )?;
+ language_server
+ .notify::<lsp::notification::DidOpenTextDocument>(
+ 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<Self>,
@@ -7807,11 +7699,8 @@ impl LspAdapter for SshLspAdapter {
) -> Result<LanguageServerBinary> {
anyhow::bail!("SshLspAdapter does not support fetch_server_binary")
}
-
- async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
- 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<Worktree>,
- // upstream_client: AnyProtoClient,
- // cx: &mut ModelContext<LspStore>,
- // ) -> Arc<Self> {
- // Self::new(
- // lsp_store,
- // worktree,
- // Arc::new(BlockedHttpClient),
- // None,
- // Some(upstream_client),
- // cx,
- // )
- // }
-
pub fn new(
lsp_store: &LspStore,
worktree: &Model<Worktree>,
@@ -7972,6 +7845,19 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
.update_lsp_status(server_name, status);
}
+ async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>> {
+ 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<String> {
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<Arc<Path>> {
+ None
+ }
+
fn update_status(
&self,
server_name: LanguageServerName,
@@ -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<Arc<CachedLspAdapter>> {
- self.lsp_store.read(cx).language_server_adapter_for_id(id)
- }
-
pub fn language_server_for_id(
&self,
id: LanguageServerId,
@@ -55,7 +55,7 @@ pub struct NodeBinarySettings {
pub npm_path: Option<String>,
/// If disabled, zed will download its own copy of node.
#[serde(default)]
- pub disable_path_lookup: Option<bool>,
+ pub ignore_system_version: Option<bool>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
@@ -143,7 +143,7 @@ const fn true_value() -> bool {
pub struct BinarySettings {
pub path: Option<String>,
pub arguments: Option<Vec<String>>,
- pub path_lookup: Option<bool>,
+ pub ignore_system_version: Option<bool>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -481,7 +481,7 @@ fn main() {
cx.observe_global::<SettingsStore>(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| {
@@ -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
}
}
}