From ec0409a3d1aeaa04d6e2dc5715486d047f53e363 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 7 Jun 2023 14:01:50 -0400 Subject: [PATCH 01/22] Detect LSP startup failure --- crates/language/src/language.rs | 2 +- crates/lsp/src/lsp.rs | 1 + crates/project/src/project.rs | 603 ++++++++++++++++++-------------- 3 files changed, 333 insertions(+), 273 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e91d5770cfba80485f3cbe6272257a81c48c4fc3..fa85d0e21f64561d465511f27084f3899f6f6f47 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -887,8 +887,8 @@ impl LanguageRegistry { }) .clone(); drop(lock); - let binary = entry.clone().map_err(|e| anyhow!(e)).await?; + let binary = entry.clone().map_err(|e| anyhow!(e)).await?; let server = lsp::LanguageServer::new( server_id, &binary.path, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 96d43820758ee0ad17dd8bb11861fc57573f3f17..f05acbb1a2d57d011a82e7b058db8965d1f52045 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -167,6 +167,7 @@ impl LanguageServer { if let Some(name) = binary_path.file_name() { server.name = name.to_string_lossy().to_string(); } + Ok(server) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5069c805b729698b95a0988f56d15417cb2cc0c6..f78ff19eaef366b76f8ea5d1281560057f27bbd6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2383,16 +2383,9 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - if !language_settings( - Some(&language), - worktree - .update(cx, |tree, cx| tree.root_file(cx)) - .map(|f| f as _) - .as_ref(), - cx, - ) - .enable_language_server - { + let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx)); + let settings = language_settings(Some(&language), root_file.map(|f| f as _).as_ref(), cx); + if !settings.enable_language_server { return; } @@ -2414,9 +2407,8 @@ impl Project { None => continue, }; - let lsp = settings::get::(cx) - .lsp - .get(&adapter.name.0); + let project_settings = settings::get::(cx); + let lsp = project_settings.lsp.get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); let mut initialization_options = adapter.initialization_options.clone(); @@ -2429,302 +2421,368 @@ impl Project { } let server_id = pending_server.server_id; - let state = self.setup_pending_language_server( - initialization_options, - pending_server, - adapter.clone(), - language.clone(), - key.clone(), - cx, - ); + let state = LanguageServerState::Starting({ + let server_name = adapter.name.0.clone(); + let adapter = adapter.clone(); + let language = language.clone(); + let languages = self.languages.clone(); + let key = key.clone(); + + cx.spawn_weak(|this, cx| async move { + let result = Self::setup_and_insert_language_server( + this, + initialization_options, + pending_server, + adapter, + languages, + language, + server_id, + key, + cx, + ) + .await; + + match result { + Ok(server) => Some(server), + + Err(err) => { + log::warn!("Error starting language server {:?}: {}", server_name, err); + // TODO: Prompt installation validity check + None + } + } + }) + }); + self.language_servers.insert(server_id, state); - self.language_server_ids.insert(key.clone(), server_id); + self.language_server_ids.insert(key, server_id); } } - fn setup_pending_language_server( - &mut self, + async fn setup_and_insert_language_server( + this: WeakModelHandle, initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, + languages: Arc, language: Arc, + server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), - cx: &mut ModelContext, - ) -> LanguageServerState { - let server_id = pending_server.server_id; - let languages = self.languages.clone(); + mut cx: AsyncAppContext, + ) -> Result> { + let language_server = Self::setup_pending_language_server( + this, + initialization_options, + pending_server, + adapter.clone(), + languages, + server_id, + &mut cx, + ) + .await?; - LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { - let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; - let language_server = pending_server.task.await.log_err()?; + let this = match this.upgrade(&mut cx) { + Some(this) => this, + None => return Err(anyhow!("failed to upgrade project handle")), + }; - language_server - .on_notification::({ - move |params, mut cx| { + this.update(&mut cx, |this, cx| { + this.insert_newly_running_language_server( + language, + adapter, + language_server.clone(), + server_id, + key, + cx, + ) + })?; + + Ok(language_server) + } + + async fn setup_pending_language_server( + this: WeakModelHandle, + initialization_options: Option, + pending_server: PendingLanguageServer, + adapter: Arc, + languages: Arc, + server_id: LanguageServerId, + cx: &mut AsyncAppContext, + ) -> Result> { + let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; + + let language_server = pending_server.task.await?; + let language_server = language_server.initialize(initialization_options).await?; + + language_server + .on_notification::({ + move |params, mut cx| { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |_, cx| { + cx.emit(Event::LanguageServerLog(server_id, params.message)) + }); + } + } + }) + .detach(); + + language_server + .on_notification::({ + let adapter = adapter.clone(); + move |mut params, cx| { + let this = this; + let adapter = adapter.clone(); + cx.spawn(|mut cx| async move { + adapter.process_diagnostics(&mut params).await; if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |_, cx| { - cx.emit(Event::LanguageServerLog(server_id, params.message)) + this.update(&mut cx, |this, cx| { + this.update_diagnostics( + server_id, + params, + &adapter.disk_based_diagnostic_sources, + cx, + ) + .log_err(); }); } + }) + .detach(); + } + }) + .detach(); + + language_server + .on_request::({ + let languages = languages.clone(); + move |params, mut cx| { + let languages = languages.clone(); + async move { + let workspace_config = + cx.update(|cx| languages.workspace_configuration(cx)).await; + Ok(params + .items + .into_iter() + .map(|item| { + if let Some(section) = &item.section { + workspace_config + .get(section) + .cloned() + .unwrap_or(serde_json::Value::Null) + } else { + workspace_config.clone() + } + }) + .collect()) } - }) - .detach(); + } + }) + .detach(); - language_server - .on_notification::({ - let adapter = adapter.clone(); - move |mut params, cx| { - let adapter = adapter.clone(); - cx.spawn(|mut cx| async move { - adapter.process_diagnostics(&mut params).await; - if let Some(this) = this.upgrade(&cx) { + // Even though we don't have handling for these requests, respond to them to + // avoid stalling any language server like `gopls` which waits for a response + // to these requests when initializing. + language_server + .on_request::( + move |params, mut cx| async move { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, _| { + if let Some(status) = this.language_server_statuses.get_mut(&server_id) + { + if let lsp::NumberOrString::String(token) = params.token { + status.progress_tokens.insert(token); + } + } + }); + } + Ok(()) + }, + ) + .detach(); + language_server + .on_request::({ + move |params, mut cx| async move { + let this = this + .upgrade(&cx) + .ok_or_else(|| anyhow!("project dropped"))?; + for reg in params.registrations { + if reg.method == "workspace/didChangeWatchedFiles" { + if let Some(options) = reg.register_options { + let options = serde_json::from_value(options)?; this.update(&mut cx, |this, cx| { - this.update_diagnostics( - server_id, - params, - &adapter.disk_based_diagnostic_sources, - cx, - ) - .log_err(); + this.on_lsp_did_change_watched_files(server_id, options, cx); }); } - }) - .detach(); - } - }) - .detach(); - - language_server - .on_request::({ - let languages = languages.clone(); - move |params, mut cx| { - let languages = languages.clone(); - async move { - let workspace_config = - cx.update(|cx| languages.workspace_configuration(cx)).await; - Ok(params - .items - .into_iter() - .map(|item| { - if let Some(section) = &item.section { - workspace_config - .get(section) - .cloned() - .unwrap_or(serde_json::Value::Null) - } else { - workspace_config.clone() - } - }) - .collect()) } } - }) - .detach(); + Ok(()) + } + }) + .detach(); - // Even though we don't have handling for these requests, respond to them to - // avoid stalling any language server like `gopls` which waits for a response - // to these requests when initializing. - language_server - .on_request::( - move |params, mut cx| async move { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, _| { - if let Some(status) = - this.language_server_statuses.get_mut(&server_id) - { - if let lsp::NumberOrString::String(token) = params.token { - status.progress_tokens.insert(token); - } - } - }); - } - Ok(()) - }, - ) - .detach(); - language_server - .on_request::( - move |params, mut cx| async move { - let this = this - .upgrade(&cx) - .ok_or_else(|| anyhow!("project dropped"))?; - for reg in params.registrations { - if reg.method == "workspace/didChangeWatchedFiles" { - if let Some(options) = reg.register_options { - let options = serde_json::from_value(options)?; - this.update(&mut cx, |this, cx| { - this.on_lsp_did_change_watched_files( - server_id, options, cx, - ); - }); - } - } - } - Ok(()) - }, - ) - .detach(); + language_server + .on_request::({ + let adapter = adapter.clone(); + move |params, cx| { + Self::on_lsp_workspace_edit(this, params, server_id, adapter.clone(), cx) + } + }) + .detach(); - language_server - .on_request::({ - let adapter = adapter.clone(); - move |params, cx| { - Self::on_lsp_workspace_edit(this, params, server_id, adapter.clone(), cx) - } - }) - .detach(); + let disk_based_diagnostics_progress_token = + adapter.disk_based_diagnostics_progress_token.clone(); - let disk_based_diagnostics_progress_token = - adapter.disk_based_diagnostics_progress_token.clone(); + language_server + .on_notification::(move |params, mut cx| { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.on_lsp_progress( + params, + server_id, + disk_based_diagnostics_progress_token.clone(), + cx, + ); + }); + } + }) + .detach(); - language_server - .on_notification::({ - move |params, mut cx| { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.on_lsp_progress( - params, - server_id, - disk_based_diagnostics_progress_token.clone(), - cx, - ); - }); - } - } - }) - .detach(); + language_server + .notify::( + lsp::DidChangeConfigurationParams { + settings: workspace_config, + }, + ) + .ok(); - let language_server = language_server - .initialize(initialization_options) - .await - .log_err()?; - language_server - .notify::( - lsp::DidChangeConfigurationParams { - settings: workspace_config, - }, - ) - .ok(); + Ok(language_server) + } - let this = this.upgrade(&cx)?; - this.update(&mut cx, |this, cx| { - // 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 this - .language_server_ids - .get(&key) - .map(|id| id != &server_id) - .unwrap_or(false) - { - return None; - } + fn insert_newly_running_language_server( + &mut self, + language: Arc, + adapter: Arc, + language_server: Arc, + 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 + .language_server_ids + .get(&key) + .map(|id| id != &server_id) + .unwrap_or(false) + { + return Ok(()); + } - // Update language_servers collection with Running variant of LanguageServerState - // indicating that the server is up and running and ready - this.language_servers.insert( - server_id, - LanguageServerState::Running { - adapter: adapter.clone(), - language: language.clone(), - watched_paths: Default::default(), - server: language_server.clone(), - simulate_disk_based_diagnostics_completion: None, - }, - ); - this.language_server_statuses.insert( - server_id, - LanguageServerStatus { - name: language_server.name().to_string(), - pending_work: Default::default(), - has_pending_diagnostic_updates: false, - progress_tokens: Default::default(), - }, - ); + // Update language_servers collection with Running variant of LanguageServerState + // indicating that the server is up and running and ready + self.language_servers.insert( + server_id, + LanguageServerState::Running { + adapter: adapter.clone(), + language: language.clone(), + watched_paths: Default::default(), + server: language_server.clone(), + simulate_disk_based_diagnostics_completion: None, + }, + ); - cx.emit(Event::LanguageServerAdded(server_id)); + self.language_server_statuses.insert( + server_id, + LanguageServerStatus { + name: language_server.name().to_string(), + pending_work: Default::default(), + has_pending_diagnostic_updates: false, + progress_tokens: Default::default(), + }, + ); - if let Some(project_id) = this.remote_id() { - this.client - .send(proto::StartLanguageServer { - project_id, - server: Some(proto::LanguageServer { - id: server_id.0 as u64, - name: language_server.name().to_string(), - }), - }) - .log_err(); + cx.emit(Event::LanguageServerAdded(server_id)); + + if let Some(project_id) = self.remote_id() { + self.client.send(proto::StartLanguageServer { + project_id, + server: Some(proto::LanguageServer { + id: server_id.0 as u64, + name: language_server.name().to_string(), + }), + })?; + } + + // Tell the language server about every open buffer in the worktree that matches the language. + for buffer in self.opened_buffers.values() { + if let Some(buffer_handle) = buffer.upgrade(cx) { + let buffer = buffer_handle.read(cx); + let file = match File::from_dyn(buffer.file()) { + Some(file) => file, + None => continue, + }; + let language = match buffer.language() { + Some(language) => language, + None => continue, + }; + + if file.worktree.read(cx).id() != key.0 + || !language.lsp_adapters().iter().any(|a| a.name == key.1) + { + continue; } - // Tell the language server about every open buffer in the worktree that matches the language. - for buffer in this.opened_buffers.values() { - if let Some(buffer_handle) = buffer.upgrade(cx) { - let buffer = buffer_handle.read(cx); - let file = match File::from_dyn(buffer.file()) { - Some(file) => file, - None => continue, - }; - let language = match buffer.language() { - Some(language) => language, - None => continue, - }; + let file = match file.as_local() { + Some(file) => file, + None => continue, + }; - if file.worktree.read(cx).id() != key.0 - || !language.lsp_adapters().iter().any(|a| a.name == key.1) - { - continue; - } + let versions = self + .buffer_snapshots + .entry(buffer.remote_id()) + .or_default() + .entry(server_id) + .or_insert_with(|| { + vec![LspBufferSnapshot { + version: 0, + snapshot: buffer.text_snapshot(), + }] + }); - let file = file.as_local()?; - let versions = this - .buffer_snapshots - .entry(buffer.remote_id()) - .or_default() - .entry(server_id) - .or_insert_with(|| { - vec![LspBufferSnapshot { - version: 0, - snapshot: buffer.text_snapshot(), - }] - }); + let snapshot = versions.last().unwrap(); + 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_ids + .get(language.name().as_ref()) + .cloned() + .unwrap_or_default(), + version, + initial_snapshot.text(), + ), + }, + )?; - let snapshot = versions.last().unwrap(); - let version = snapshot.version; - let initial_snapshot = &snapshot.snapshot; - let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + buffer_handle.update(cx, |buffer, cx| { + buffer.set_completion_triggers( language_server - .notify::( - lsp::DidOpenTextDocumentParams { - text_document: lsp::TextDocumentItem::new( - uri, - adapter - .language_ids - .get(language.name().as_ref()) - .cloned() - .unwrap_or_default(), - version, - initial_snapshot.text(), - ), - }, - ) - .log_err()?; - buffer_handle.update(cx, |buffer, cx| { - buffer.set_completion_triggers( - language_server - .capabilities() - .completion_provider - .as_ref() - .and_then(|provider| provider.trigger_characters.clone()) - .unwrap_or_default(), - cx, - ) - }); - } - } + .capabilities() + .completion_provider + .as_ref() + .and_then(|provider| provider.trigger_characters.clone()) + .unwrap_or_default(), + cx, + ) + }); + } + } - cx.notify(); - Some(language_server) - }) - })) + cx.notify(); + Ok(()) } // Returns a list of all of the worktrees which no longer have a language server and the root path @@ -2776,6 +2834,7 @@ impl Project { Some(LanguageServerState::Starting(started_language_server)) => { started_language_server.await } + Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; From bca625a197b52597011d012c7846a65f6052cb51 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 12 Jun 2023 14:25:45 -0400 Subject: [PATCH 02/22] Many steps toward validating and reinstalling server after failure --- .../src/activity_indicator.rs | 17 +- crates/copilot/src/copilot.rs | 15 +- crates/language/src/language.rs | 99 ++++++- crates/lsp/src/lsp.rs | 51 +++- crates/project/src/project.rs | 275 +++++++++++------- crates/util/src/util.rs | 14 +- crates/zed/src/languages/c.rs | 1 + crates/zed/src/languages/elixir.rs | 2 +- crates/zed/src/languages/go.rs | 1 + crates/zed/src/languages/html.rs | 3 +- crates/zed/src/languages/json.rs | 3 +- crates/zed/src/languages/language_plugin.rs | 3 +- crates/zed/src/languages/lua.rs | 3 +- crates/zed/src/languages/python.rs | 3 +- crates/zed/src/languages/ruby.rs | 3 +- crates/zed/src/languages/rust.rs | 1 + crates/zed/src/languages/typescript.rs | 4 +- crates/zed/src/languages/yaml.rs | 5 +- styles/tsconfig.json | 1 + 19 files changed, 348 insertions(+), 156 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 801c8f7172ec40d5c111109482d55e345d037592..a8874e8d9e89f42f03f3f47d493a773dd4619f7b 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -203,20 +203,17 @@ impl ActivityIndicator { } // Show any language server installation info. + let mut validating = SmallVec::<[_; 3]>::new(); let mut downloading = SmallVec::<[_; 3]>::new(); let mut checking_for_update = SmallVec::<[_; 3]>::new(); let mut failed = SmallVec::<[_; 3]>::new(); for status in &self.statuses { + let name = status.name.clone(); match status.status { - LanguageServerBinaryStatus::CheckingForUpdate => { - checking_for_update.push(status.name.clone()); - } - LanguageServerBinaryStatus::Downloading => { - downloading.push(status.name.clone()); - } - LanguageServerBinaryStatus::Failed { .. } => { - failed.push(status.name.clone()); - } + LanguageServerBinaryStatus::Validating => validating.push(name), + LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name), + LanguageServerBinaryStatus::Downloading => downloading.push(name), + LanguageServerBinaryStatus::Failed { .. } => failed.push(name), LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {} } } @@ -245,7 +242,7 @@ impl ActivityIndicator { ), on_click: None, }; - } else if !failed.is_empty() { + } else if !failed.is_empty() || !validating.is_empty() { return Content { icon: Some(WARNING_ICON), message: format!( diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index e73424f0cd36945f1a24d3155521075c7967fa80..b72f00f36139484ef4e52d0a77cf88897ddfbc64 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -15,7 +15,7 @@ use language::{ ToPointUtf16, }; use log::{debug, error}; -use lsp::{LanguageServer, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerBinaries, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use request::{LogMessage, StatusNotification}; use settings::SettingsStore; @@ -361,11 +361,18 @@ impl Copilot { let start_language_server = async { let server_path = get_copilot_lsp(http).await?; let node_path = node_runtime.binary_path().await?; - let arguments: &[OsString] = &[server_path.into(), "--stdio".into()]; + let arguments: Vec = vec![server_path.into(), "--stdio".into()]; + let binary = LanguageServerBinary { + path: node_path, + arguments, + }; + let binaries = LanguageServerBinaries { + binary: binary.clone(), + installation_test_binary: binary, + }; let server = LanguageServer::new( LanguageServerId(0), - &node_path, - arguments, + binaries, Path::new("/"), None, cx.clone(), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index fa85d0e21f64561d465511f27084f3899f6f6f47..2ed896e03fbde49aae53adc8b9426d90765ce65a 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,7 +20,7 @@ use futures::{ use gpui::{executor::Background, AppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; -use lsp::CodeActionKind; +use lsp::{CodeActionKind, LanguageServer, LanguageServerBinaries, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -30,17 +30,18 @@ use std::{ any::Any, borrow::Cow, cell::RefCell, - ffi::OsString, fmt::Debug, hash::Hash, mem, ops::{Not, Range}, path::{Path, PathBuf}, + process::Stdio, str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, + time::Duration, }; use syntax_map::SyntaxSnapshot; use theme::{SyntaxTheme, Theme}; @@ -86,12 +87,6 @@ pub trait ToLspPosition { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LanguageServerName(pub Arc); -#[derive(Debug, Clone, Deserialize)] -pub struct LanguageServerBinary { - pub path: PathBuf, - pub arguments: Vec, -} - /// Represents a Language Server, with certain cached sync properties. /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. @@ -148,6 +143,10 @@ impl CachedLspAdapter { self.adapter.cached_server_binary(container_dir).await } + async fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary { + self.adapter.installation_test_binary(container_dir) + } + pub fn code_action_kinds(&self) -> Option> { self.adapter.code_action_kinds() } @@ -205,6 +204,10 @@ pub trait LspAdapter: 'static + Send + Sync { async fn cached_server_binary(&self, container_dir: PathBuf) -> Option; + fn installation_test_binary(&self, _container_dir: PathBuf) -> LanguageServerBinary { + unimplemented!(); + } + async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} async fn process_completion(&self, _: &mut lsp::CompletionItem) {} @@ -485,6 +488,7 @@ struct BracketConfig { #[derive(Clone)] pub enum LanguageServerBinaryStatus { + Validating, CheckingForUpdate, Downloading, Downloaded, @@ -515,7 +519,7 @@ pub struct LanguageRegistry { lsp_binary_paths: Mutex< HashMap< LanguageServerName, - Shared>>>, + Shared>>>, >, >, executor: Option>, @@ -891,8 +895,7 @@ impl LanguageRegistry { let binary = entry.clone().map_err(|e| anyhow!(e)).await?; let server = lsp::LanguageServer::new( server_id, - &binary.path, - &binary.arguments, + binary, &root_path, adapter.code_action_kinds(), cx, @@ -909,6 +912,56 @@ impl LanguageRegistry { ) -> async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)> { self.lsp_binary_statuses_rx.clone() } + + pub async fn check_errored_lsp_installation( + &self, + language_server: Arc, + cx: &mut AppContext, + ) { + // Check if child process is running + if !language_server.is_dead() { + return; + } + + // If not, get check binary + let test_binary = match language_server.test_installation_binary() { + Some(test_binary) => test_binary.clone(), + None => return, + }; + + // Run + const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); + let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); + + let mut errored = false; + let result = smol::process::Command::new(&test_binary.path) + .current_dir(&test_binary.path) + .args(test_binary.arguments) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn(); + + if let Ok(mut process) = result { + futures::select! { + _ = process.status().fuse() => {} + _ = timeout => errored = true, + } + } else { + errored = true; + } + + dbg!(errored); + + // If failure clear container dir + + // Prompt binary retrieval + + // Start language server + + // Update project server state + } } impl LanguageRegistryState { @@ -961,7 +1014,7 @@ async fn get_binary( http_client: Arc, download_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, -) -> Result { +) -> Result { let container_dir = download_dir.join(adapter.name.0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) @@ -979,11 +1032,15 @@ async fn get_binary( .await; if let Err(error) = binary.as_ref() { - if let Some(cached) = adapter.cached_server_binary(container_dir).await { + if let Some(binary) = adapter.cached_server_binary(container_dir.clone()).await { statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; - return Ok(cached); + let installation_test_binary = adapter.installation_test_binary(container_dir).await; + return Ok(LanguageServerBinaries { + binary, + installation_test_binary, + }); } else { statuses .broadcast(( @@ -995,6 +1052,7 @@ async fn get_binary( .await?; } } + binary } @@ -1004,7 +1062,7 @@ async fn fetch_latest_binary( http_client: Arc, container_dir: &Path, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, -) -> Result { +) -> Result { let container_dir: Arc = container_dir.into(); lsp_binary_statuses_tx .broadcast(( @@ -1012,19 +1070,28 @@ async fn fetch_latest_binary( LanguageServerBinaryStatus::CheckingForUpdate, )) .await?; + let version_info = adapter .fetch_latest_server_version(http_client.clone()) .await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading)) .await?; + let binary = adapter .fetch_server_binary(version_info, http_client, container_dir.to_path_buf()) .await?; + let installation_test_binary = adapter + .installation_test_binary(container_dir.to_path_buf()) + .await; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) .await?; - Ok(binary) + + Ok(LanguageServerBinaries { + binary, + installation_test_binary, + }) } impl Language { diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index f05acbb1a2d57d011a82e7b058db8965d1f52045..94a5096c5f08d7878c4480c17ecea698c34a696e 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -16,6 +16,7 @@ use smol::{ process::{self, Child}, }; use std::{ + ffi::OsString, fmt, future::Future, io::Write, @@ -36,6 +37,18 @@ type NotificationHandler = Box, &str, AsyncAppCon type ResponseHandler = Box)>; type IoHandler = Box; +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageServerBinary { + pub path: PathBuf, + pub arguments: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageServerBinaries { + pub binary: LanguageServerBinary, + pub installation_test_binary: LanguageServerBinary, +} + pub struct LanguageServer { server_id: LanguageServerId, next_id: AtomicUsize, @@ -51,7 +64,8 @@ pub struct LanguageServer { io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, root_path: PathBuf, - _server: Option, + server: Option>, + test_installation_binary: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -119,10 +133,9 @@ struct Error { } impl LanguageServer { - pub fn new>( + pub fn new( server_id: LanguageServerId, - binary_path: &Path, - arguments: &[T], + binaries: LanguageServerBinaries, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -133,9 +146,9 @@ impl LanguageServer { root_path.parent().unwrap_or_else(|| Path::new("/")) }; - let mut server = process::Command::new(binary_path) + let mut server = process::Command::new(&binaries.binary.path) .current_dir(working_dir) - .args(arguments) + .args(binaries.binary.arguments) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) @@ -149,6 +162,7 @@ impl LanguageServer { stdin, stout, Some(server), + Some(binaries.installation_test_binary), root_path, code_action_kinds, cx, @@ -164,7 +178,7 @@ impl LanguageServer { }, ); - if let Some(name) = binary_path.file_name() { + if let Some(name) = binaries.binary.path.file_name() { server.name = name.to_string_lossy().to_string(); } @@ -176,6 +190,7 @@ impl LanguageServer { stdin: Stdin, stdout: Stdout, server: Option, + test_installation_binary: Option, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -229,10 +244,28 @@ impl LanguageServer { io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), - _server: server, + server: server.map(|server| Mutex::new(server)), + test_installation_binary, } } + pub fn is_dead(&self) -> bool { + let server = match self.server.as_ref() { + Some(server) => server, + None => return false, // Fake server for tests + }; + + match server.lock().try_status() { + Ok(Some(_)) => true, + Ok(None) => false, + Err(_) => true, + } + } + + pub fn test_installation_binary(&self) -> &Option { + &self.test_installation_binary + } + pub fn code_action_kinds(&self) -> Option> { self.code_action_kinds.clone() } @@ -813,6 +846,7 @@ impl LanguageServer { stdin_writer, stdout_reader, None, + None, Path::new("/"), None, cx.clone(), @@ -824,6 +858,7 @@ impl LanguageServer { stdout_writer, stdin_reader, None, + None, Path::new("/"), None, cx, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f78ff19eaef366b76f8ea5d1281560057f27bbd6..5c9b79434aea4d1da0dd901a06ef44511460cdc1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -45,7 +45,7 @@ use language::{ use log::error; use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, - DocumentHighlightKind, LanguageServer, LanguageServerId, + DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, }; use lsp_command::*; use postage::watch; @@ -65,6 +65,7 @@ use std::{ num::NonZeroU32, ops::Range, path::{Component, Path, PathBuf}, + process::Stdio, rc::Rc, str, sync::{ @@ -277,6 +278,7 @@ pub enum Event { } pub enum LanguageServerState { + Validating(Task>>), Starting(Task>>), Running { language: Arc, @@ -2447,7 +2449,7 @@ impl Project { Err(err) => { log::warn!("Error starting language server {:?}: {}", server_name, err); - // TODO: Prompt installation validity check + // TODO: Prompt installation validity check LSP ERROR None } } @@ -2831,10 +2833,8 @@ impl Project { let mut root_path = None; let server = match server_state { - Some(LanguageServerState::Starting(started_language_server)) => { - started_language_server.await - } - + Some(LanguageServerState::Validating(task)) => task.await, + Some(LanguageServerState::Starting(task)) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; @@ -2945,6 +2945,15 @@ impl Project { .detach(); } + fn check_errored_lsp_installation( + &self, + language_server: Arc, + cx: &mut ModelContext, + ) { + self.languages + .check_errored_lsp_installation(language_server, cx); + } + fn on_lsp_progress( &mut self, progress: lsp::ProgressParams, @@ -3716,29 +3725,26 @@ impl Project { tab_size: NonZeroU32, cx: &mut AsyncAppContext, ) -> Result, String)>> { - let text_document = - lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(abs_path).unwrap()); + let uri = lsp::Url::from_file_path(abs_path) + .map_err(|_| anyhow!("failed to convert abs path to uri"))?; + let text_document = lsp::TextDocumentIdentifier::new(uri); let capabilities = &language_server.capabilities(); - let lsp_edits = if capabilities - .document_formatting_provider - .as_ref() - .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) - { + + let formatting_provider = capabilities.document_formatting_provider.as_ref(); + let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); + + let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) { language_server .request::(lsp::DocumentFormattingParams { text_document, options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) - .await? - } else if capabilities - .document_range_formatting_provider - .as_ref() - .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) - { + .await + } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { let buffer_start = lsp::Position::new(0, 0); - let buffer_end = - buffer.read_with(cx, |buffer, _| point_to_lsp(buffer.max_point_utf16())); + let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); + language_server .request::(lsp::DocumentRangeFormattingParams { text_document, @@ -3746,9 +3752,27 @@ impl Project { options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) - .await? + .await } else { - None + Ok(None) + }; + + let lsp_edits = match result { + Ok(lsp_edits) => lsp_edits, + + Err(err) => { + log::warn!( + "Error firing format request to {}: {}", + language_server.name(), + err + ); + + this.update(cx, |this, cx| { + this.check_errored_lsp_installation(language_server.clone(), cx); + }); + + None + } }; if let Some(lsp_edits) = lsp_edits { @@ -3757,7 +3781,7 @@ impl Project { }) .await } else { - Ok(Default::default()) + Ok(Vec::new()) } } @@ -3865,80 +3889,89 @@ impl Project { let mut requests = Vec::new(); for ((worktree_id, _), server_id) in self.language_server_ids.iter() { let worktree_id = *worktree_id; - if let Some(worktree) = self - .worktree_for_id(worktree_id, cx) - .and_then(|worktree| worktree.read(cx).as_local()) - { - if let Some(LanguageServerState::Running { + let worktree_handle = self.worktree_for_id(worktree_id, cx); + let worktree = match worktree_handle.and_then(|tree| tree.read(cx).as_local()) { + Some(worktree) => worktree, + None => continue, + }; + let worktree_abs_path = worktree.abs_path().clone(); + + let (adapter, language, server) = match self.language_servers.get(server_id) { + Some(LanguageServerState::Running { adapter, language, server, .. - }) = self.language_servers.get(server_id) - { - let adapter = adapter.clone(); - let language = language.clone(); - let worktree_abs_path = worktree.abs_path().clone(); - requests.push( - server - .request::( - lsp::WorkspaceSymbolParams { - query: query.to_string(), - ..Default::default() - }, - ) - .log_err() - .map(move |response| { - let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { - lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { - flat_responses.into_iter().map(|lsp_symbol| { - (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) - }).collect::>() - } - lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { - nested_responses.into_iter().filter_map(|lsp_symbol| { - let location = match lsp_symbol.location { - lsp::OneOf::Left(location) => location, - lsp::OneOf::Right(_) => { - error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport"); - return None - } - }; - Some((lsp_symbol.name, lsp_symbol.kind, location)) - }).collect::>() - } - }).unwrap_or_default(); - - ( - adapter, - language, - worktree_id, - worktree_abs_path, - lsp_symbols, - ) - }), - ); - } - } + }) => (adapter.clone(), language.clone(), server), + + _ => continue, + }; + + requests.push( + server + .request::( + lsp::WorkspaceSymbolParams { + query: query.to_string(), + ..Default::default() + }, + ) + .map_ok(move |response| { + let lsp_symbols = response.map(|symbol_response| match symbol_response { + lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { + flat_responses.into_iter().map(|lsp_symbol| { + (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) + }).collect::>() + } + lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { + nested_responses.into_iter().filter_map(|lsp_symbol| { + let location = match lsp_symbol.location { + OneOf::Left(location) => location, + OneOf::Right(_) => { + error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport"); + return None + } + }; + Some((lsp_symbol.name, lsp_symbol.kind, location)) + }).collect::>() + } + }).unwrap_or_default(); + + ( + adapter, + language, + worktree_id, + worktree_abs_path, + lsp_symbols, + ) + }), + ); } cx.spawn_weak(|this, cx| async move { let responses = futures::future::join_all(requests).await; - let this = if let Some(this) = this.upgrade(&cx) { - this - } else { - return Ok(Default::default()); + let this = match this.upgrade(&cx) { + Some(this) => this, + None => return Ok(Vec::new()), }; + let symbols = this.read_with(&cx, |this, cx| { let mut symbols = Vec::new(); - for ( - adapter, - adapter_language, - source_worktree_id, - worktree_abs_path, - lsp_symbols, - ) in responses - { + for response in responses { + let ( + adapter, + adapter_language, + source_worktree_id, + worktree_abs_path, + lsp_symbols, + ) = match response { + Ok(response) => response, + + Err(err) => { + // TODO: Prompt installation validity check LSP ERROR + return Vec::new(); + } + }; + symbols.extend(lsp_symbols.into_iter().filter_map( |(symbol_name, symbol_kind, symbol_location)| { let abs_path = symbol_location.uri.to_file_path().ok()?; @@ -3985,8 +4018,10 @@ impl Project { }, )); } + symbols }); + Ok(futures::future::join_all(symbols).await) }) } else if let Some(project_id) = self.remote_id() { @@ -4111,9 +4146,17 @@ impl Project { }; cx.spawn(|this, mut cx| async move { - let resolved_completion = lang_server + let resolved_completion = match lang_server .request::(completion.lsp_completion) - .await?; + .await + { + Ok(resolved_completion) => resolved_completion, + + Err(err) => { + // TODO: LSP ERROR + return Ok(None); + } + }; if let Some(edits) = resolved_completion.additional_text_edits { let edits = this @@ -4232,9 +4275,17 @@ impl Project { .and_then(|d| d.get_mut("range")) { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); - action.lsp_action = lang_server + action.lsp_action = match lang_server .request::(action.lsp_action) - .await?; + .await + { + Ok(lsp_action) => lsp_action, + + Err(err) => { + // LSP ERROR + return Err(err); + } + }; } else { let actions = this .update(&mut cx, |this, cx| { @@ -4267,13 +4318,20 @@ impl Project { this.last_workspace_edits_by_language_server .remove(&lang_server.server_id()); }); - lang_server + + let result = lang_server .request::(lsp::ExecuteCommandParams { command: command.command, arguments: command.arguments.unwrap_or_default(), ..Default::default() }) - .await?; + .await; + + if let Err(err) = result { + // TODO: LSP ERROR + return Err(err); + } + return Ok(this.update(&mut cx, |this, _| { this.last_workspace_edits_by_language_server .remove(&lang_server.server_id()) @@ -4433,7 +4491,7 @@ impl Project { uri, version: None, }, - edits: edits.into_iter().map(lsp::OneOf::Left).collect(), + edits: edits.into_iter().map(OneOf::Left).collect(), }) })); } @@ -4503,8 +4561,8 @@ impl Project { let edits = this .update(cx, |this, cx| { let edits = op.edits.into_iter().map(|edit| match edit { - lsp::OneOf::Left(edit) => edit, - lsp::OneOf::Right(edit) => edit.text_edit, + OneOf::Left(edit) => edit, + OneOf::Right(edit) => edit.text_edit, }); this.edits_from_lsp( &buffer_to_edit, @@ -4841,10 +4899,20 @@ impl Project { return Ok(Default::default()); } - let response = language_server - .request::(lsp_params) - .await - .context("lsp request failed")?; + let result = language_server.request::(lsp_params).await; + let response = match result { + Ok(response) => response, + + Err(err) => { + log::warn!( + "Generic lsp request to {} failed: {}", + language_server.name(), + err + ); + return Err(err); + } + }; + request .response_from_lsp( response, @@ -7211,11 +7279,10 @@ impl Entity for Project { .language_servers .drain() .map(|(_, server_state)| async { + use LanguageServerState::*; match server_state { - LanguageServerState::Running { server, .. } => server.shutdown()?.await, - LanguageServerState::Starting(starting_server) => { - starting_server.await?.shutdown()?.await - } + Running { server, .. } => server.shutdown()?.await, + Starting(task) | Validating(task) => task.await?.shutdown()?.await, } }) .collect::>(); diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 71ffacebf34e2474bcb1c394dada124827c53477..c8beb86aeff44a75482531d22a6e8b0c07155fa6 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -118,14 +118,15 @@ pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut se } } -pub trait ResultExt { +pub trait ResultExt { type Ok; fn log_err(self) -> Option; fn warn_on_err(self) -> Option; + fn inspect_error(self, func: impl FnOnce(&E)) -> Self; } -impl ResultExt for Result +impl ResultExt for Result where E: std::fmt::Debug, { @@ -152,6 +153,15 @@ where } } } + + /// https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err + fn inspect_error(self, func: impl FnOnce(&E)) -> Self { + if let Err(err) = &self { + func(err); + } + + self + } } pub trait TryFutureExt { diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 7e4ddcef19189cb0a5704d43ca78a6164186d1de..bfb25f55a091f90b41059d9fa0805444239f5ba3 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use futures::StreamExt; pub use language::*; +use lsp::LanguageServerBinary; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::fs::remove_matching; diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 2939a0fa5f942b9631b2ecd9e13d6c1e5c4de17e..8d6ece28c75c6cbe9dac34592c7002e917f8ebdc 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use futures::StreamExt; pub use language::*; -use lsp::{CompletionItemKind, SymbolKind}; +use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::fs::remove_matching; diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index ed24abb45c40b6b2cc2c2336a746557c03505745..9ffd88e53a996303e45e4c623bde3066cf3a1bfa 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use futures::StreamExt; pub use language::*; use lazy_static::lazy_static; +use lsp::LanguageServerBinary; use regex::Regex; use smol::{fs, process}; use std::ffi::{OsStr, OsString}; diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index 68f780c3af73b3febd1fdcbeaba1a2a95bb36b37..3d5adb88ba657080f88d70fde3bfddcfacbac23e 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use smol::fs; diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index e1f3da9e0238f0a91179447e7c15ce2df5239ec1..f6ca3f403fd8083fd3b30ad5d1547bef5296b625 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use collections::HashMap; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageRegistry, LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 9b82713d082d5d12a0243ebaf674b9a16550c3d4..4ff4816ae3eb007e6994ca88214939004f2a5972 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::http::HttpClient; diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index f204eb2555f5327d0e70df60963db771acd772ab..bb9070c1a2501260857d75b5e0845ea8283dac10 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -3,7 +3,8 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; -use language::{LanguageServerBinary, LanguageServerName}; +use language::LanguageServerName; +use lsp::LanguageServerBinary; use smol::fs; use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc}; use util::{async_iife, github::latest_github_release, http::HttpClient, ResultExt}; diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 7aaddf5fe8a72d5731322e911b5a5c87473dfdbe..6dd17660c4ec420d4cc31d17bc32073f23019ea7 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use smol::fs; use std::{ diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index d387f815f0cd345c4173f522370ab17495989592..189641bec5f1673f2575a7fb19c1f973b7a91656 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use std::{any::Any, path::PathBuf, sync::Arc}; use util::http::HttpClient; diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 15700ec80a1bc798d44210ed47d600cc319a1241..67a38d1355d0656e1b146c37c80b59eb59d8bce2 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; pub use language::*; use lazy_static::lazy_static; +use lsp::LanguageServerBinary; use regex::Regex; use smol::fs::{self, File}; use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 7d2d580857780a714496a5f8c2c850de36986d26..dd2ac2234881fbfe8e47bd52d6b52cf3142da959 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -4,8 +4,8 @@ use async_tar::Archive; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt}; use gpui::AppContext; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; -use lsp::CodeActionKind; +use language::{LanguageServerName, LspAdapter}; +use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; use serde_json::{json, Value}; use smol::{fs, io::BufReader, stream::StreamExt}; diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 7f87a7caedb764588a698b5e863fbd418c3859ed..48ce76721d7ff778acc47fd198746376b362eb17 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -2,9 +2,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{ - language_settings::all_language_settings, LanguageServerBinary, LanguageServerName, LspAdapter, -}; +use language::{language_settings::all_language_settings, LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::Value; use smol::fs; diff --git a/styles/tsconfig.json b/styles/tsconfig.json index 6d24728a0a9f0e38b3a038c7c7bf3d0642e6e88f..fe9682b9697ceaf53023527e668eb836a80a5c44 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -1,4 +1,5 @@ { + // "noErrorTruncation": true, "compilerOptions": { "target": "es2015", "module": "commonjs", From 4d24eae901fd2e94efb02778207c25bbbc4ce749 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 14 Jun 2023 12:27:08 -0400 Subject: [PATCH 03/22] Actually check and reinstall broken server --- crates/language/src/language.rs | 66 ++++++---------------- crates/project/src/project.rs | 98 ++++++++++++++++++++++++++++++++- crates/zed/src/languages/c.rs | 4 ++ 3 files changed, 118 insertions(+), 50 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 2ed896e03fbde49aae53adc8b9426d90765ce65a..28cd1cd5b1ccd5b007fffe18d197ce7191be0a20 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,7 +20,7 @@ use futures::{ use gpui::{executor::Background, AppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; -use lsp::{CodeActionKind, LanguageServer, LanguageServerBinaries, LanguageServerBinary}; +use lsp::{CodeActionKind, LanguageServerBinaries, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -35,13 +35,11 @@ use std::{ mem, ops::{Not, Range}, path::{Path, PathBuf}, - process::Stdio, str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::Duration, }; use syntax_map::SyntaxSnapshot; use theme::{SyntaxTheme, Theme}; @@ -860,7 +858,7 @@ impl LanguageRegistry { let download_dir = self .language_server_download_dir .clone() - .ok_or_else(|| anyhow!("language server download directory has not been assigned")) + .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server")) .log_err()?; let this = self.clone(); let language = language.clone(); @@ -913,54 +911,26 @@ impl LanguageRegistry { self.lsp_binary_statuses_rx.clone() } - pub async fn check_errored_lsp_installation( + pub fn delete_server_container( &self, - language_server: Arc, + adapter: Arc, cx: &mut AppContext, - ) { - // Check if child process is running - if !language_server.is_dead() { - return; - } - - // If not, get check binary - let test_binary = match language_server.test_installation_binary() { - Some(test_binary) => test_binary.clone(), - None => return, - }; - - // Run - const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); - let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); - - let mut errored = false; - let result = smol::process::Command::new(&test_binary.path) - .current_dir(&test_binary.path) - .args(test_binary.arguments) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .kill_on_drop(true) - .spawn(); - - if let Ok(mut process) = result { - futures::select! { - _ = process.status().fuse() => {} - _ = timeout => errored = true, - } - } else { - errored = true; - } - - dbg!(errored); + ) -> Task<()> { + let mut lock = self.lsp_binary_paths.lock(); + lock.remove(&adapter.name); - // If failure clear container dir - - // Prompt binary retrieval + let download_dir = self + .language_server_download_dir + .clone() + .expect("language server download directory has not been assigned before deleting server container"); - // Start language server - - // Update project server state + 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(); + }) } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5c9b79434aea4d1da0dd901a06ef44511460cdc1..e131bbb746da6d953669fe5daf75417d84df5b29 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2461,6 +2461,54 @@ impl Project { } } + fn reinstall_language_server( + &mut self, + server_id: LanguageServerId, + cx: &mut ModelContext, + ) -> Option> { + let (adapter, language, server) = match self.language_servers.remove(&server_id) { + Some(LanguageServerState::Running { + adapter, + language, + server, + .. + }) => (adapter.clone(), language.clone(), server), + + _ => return None, + }; + + Some(cx.spawn(move |this, mut cx| async move { + if let Some(task) = server.shutdown() { + 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 + this.update(&mut cx, |this, cx| { + this.languages.delete_server_container(adapter.clone(), cx) + }) + .await; + + this.update(&mut cx, |this, mut cx| { + for worktree in &this.worktrees { + let root_path = match worktree.upgrade(cx) { + Some(worktree) => worktree.read(cx).abs_path(), + None => continue, + }; + + this.languages.start_language_server( + language.clone(), + adapter.clone(), + root_path, + this.client.http_client(), + &mut cx, + ); + } + }) + })) + } + async fn setup_and_insert_language_server( this: WeakModelHandle, initialization_options: Option, @@ -2950,8 +2998,54 @@ impl Project { language_server: Arc, cx: &mut ModelContext, ) { - self.languages - .check_errored_lsp_installation(language_server, cx); + cx.spawn(|this, mut cx| async move { + if !language_server.is_dead() { + return; + } + + let server_id = language_server.server_id(); + let test_binary = match language_server.test_installation_binary() { + Some(test_binary) => test_binary.clone(), + None => return, + }; + + const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); + let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); + + let mut errored = false; + let result = smol::process::Command::new(&test_binary.path) + .current_dir(&test_binary.path) + .args(test_binary.arguments) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn(); + + if let Ok(mut process) = result { + futures::select! { + status = process.status().fuse() => match status { + Ok(status) => errored = !status.success(), + Err(_) => errored = true, + }, + + _ = timeout => {} + } + } else { + errored = true; + } + + if errored { + let task = this.update(&mut cx, move |this, mut cx| { + this.reinstall_language_server(server_id, &mut cx) + }); + + if let Some(task) = task { + task.await; + } + } + }) + .detach(); } fn on_lsp_progress( diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index bfb25f55a091f90b41059d9fa0805444239f5ba3..9d4e1802d5bd5648ca62fe03ff1c61ba8ba01a00 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -109,6 +109,10 @@ impl super::LspAdapter for CLspAdapter { .await .log_err() } + + fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary { + unimplemented!(); + } async fn label_for_completion( &self, From f81ccbd652642bf6852014b86bf138813a005d92 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 15 Jun 2023 12:03:11 -0400 Subject: [PATCH 04/22] Setup C adapter with test binary --- crates/copilot/src/copilot.rs | 2 +- crates/language/src/language.rs | 12 +++++++++--- crates/lsp/src/lsp.rs | 4 ++-- crates/project/src/project.rs | 32 +++++++++++++++++--------------- crates/zed/src/languages/c.rs | 14 +++++++++++--- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index b72f00f36139484ef4e52d0a77cf88897ddfbc64..866bb8d7f526358a7cacb833c84037c87a23b878 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -368,7 +368,7 @@ impl Copilot { }; let binaries = LanguageServerBinaries { binary: binary.clone(), - installation_test_binary: binary, + installation_test_binary: Some(binary), }; let server = LanguageServer::new( LanguageServerId(0), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 28cd1cd5b1ccd5b007fffe18d197ce7191be0a20..c25027f9bc0feef3ef75ffac8d5cbbcbe10e8306 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -141,8 +141,11 @@ impl CachedLspAdapter { self.adapter.cached_server_binary(container_dir).await } - async fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary { - self.adapter.installation_test_binary(container_dir) + 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> { @@ -202,7 +205,10 @@ pub trait LspAdapter: 'static + Send + Sync { async fn cached_server_binary(&self, container_dir: PathBuf) -> Option; - fn installation_test_binary(&self, _container_dir: PathBuf) -> LanguageServerBinary { + async fn installation_test_binary( + &self, + _container_dir: PathBuf, + ) -> Option { unimplemented!(); } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 94a5096c5f08d7878c4480c17ecea698c34a696e..f7d924604b69402a27210fb1df71a1da932d164a 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -46,7 +46,7 @@ pub struct LanguageServerBinary { #[derive(Debug, Clone, Deserialize)] pub struct LanguageServerBinaries { pub binary: LanguageServerBinary, - pub installation_test_binary: LanguageServerBinary, + pub installation_test_binary: Option, } pub struct LanguageServer { @@ -162,7 +162,7 @@ impl LanguageServer { stdin, stout, Some(server), - Some(binaries.installation_test_binary), + binaries.installation_test_binary, root_path, code_action_kinds, cx, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e131bbb746da6d953669fe5daf75417d84df5b29..9641ecc0f13f01727eb969808cd22ab3526e0aa0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3002,27 +3002,29 @@ impl Project { if !language_server.is_dead() { return; } - let server_id = language_server.server_id(); - let test_binary = match language_server.test_installation_binary() { - Some(test_binary) => test_binary.clone(), - None => return, - }; + + // A lack of test binary counts as a failure + let process = language_server + .test_installation_binary() + .as_ref() + .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().timer(PROCESS_TIMEOUT).fuse(); let mut errored = false; - let result = smol::process::Command::new(&test_binary.path) - .current_dir(&test_binary.path) - .args(test_binary.arguments) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .kill_on_drop(true) - .spawn(); - - if let Ok(mut process) = result { + if let Some(mut process) = process { futures::select! { status = process.status().fuse() => match status { Ok(status) => errored = !status.success(), diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 9d4e1802d5bd5648ca62fe03ff1c61ba8ba01a00..0f580f1d4fa02d967c887825fc23e24c92c520c7 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -109,9 +109,17 @@ impl super::LspAdapter for CLspAdapter { .await .log_err() } - - fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary { - unimplemented!(); + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + self.cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary + }) } async fn label_for_completion( From abe5ecc5ec01ac7216747632492c5cf39ef61299 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 15 Jun 2023 13:56:07 -0400 Subject: [PATCH 05/22] Actually fully start reinstalled language server --- crates/language/src/language.rs | 2 +- crates/project/src/project.rs | 145 ++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 64 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c25027f9bc0feef3ef75ffac8d5cbbcbe10e8306..1d595d0e2412cd060a3d83994bbde5aaea0ed703 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -815,7 +815,7 @@ impl LanguageRegistry { self.state.read().languages.iter().cloned().collect() } - pub fn start_language_server( + pub fn start_pending_language_server( self: &Arc, language: Arc, adapter: Arc, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9641ecc0f13f01727eb969808cd22ab3526e0aa0..41a9d65069c34f8c39934487cea286dbd55e940c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -224,6 +224,7 @@ enum OpenBuffer { Operations(Vec), } +#[derive(Clone)] enum WorktreeHandle { Strong(ModelHandle), Weak(WeakModelHandle), @@ -2393,72 +2394,87 @@ impl Project { let worktree_id = worktree.read(cx).id(); for adapter in language.lsp_adapters() { - let key = (worktree_id, adapter.name.clone()); - if self.language_server_ids.contains_key(&key) { - continue; - } - - let pending_server = match self.languages.start_language_server( - language.clone(), - adapter.clone(), + self.start_language_server( + worktree_id, worktree_path.clone(), - self.client.http_client(), + adapter.clone(), + language.clone(), cx, - ) { - Some(pending_server) => pending_server, - None => continue, - }; + ); + } + } + + fn start_language_server( + &mut self, + worktree_id: WorktreeId, + worktree_path: Arc, + adapter: Arc, + language: Arc, + cx: &mut ModelContext, + ) { + let key = (worktree_id, adapter.name.clone()); + if self.language_server_ids.contains_key(&key) { + return; + } - let project_settings = settings::get::(cx); - let lsp = project_settings.lsp.get(&adapter.name.0); - let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); + let pending_server = match self.languages.start_pending_language_server( + language.clone(), + adapter.clone(), + worktree_path, + self.client.http_client(), + cx, + ) { + Some(pending_server) => pending_server, + None => return, + }; - let mut initialization_options = adapter.initialization_options.clone(); - 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 project_settings = settings::get::(cx); + let lsp = project_settings.lsp.get(&adapter.name.0); + let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); + + let mut initialization_options = adapter.initialization_options.clone(); + 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 server_id = pending_server.server_id; - let state = LanguageServerState::Starting({ - let server_name = adapter.name.0.clone(); - let adapter = adapter.clone(); - let language = language.clone(); - let languages = self.languages.clone(); - let key = key.clone(); - - cx.spawn_weak(|this, cx| async move { - let result = Self::setup_and_insert_language_server( - this, - initialization_options, - pending_server, - adapter, - languages, - language, - server_id, - key, - cx, - ) - .await; + let server_id = pending_server.server_id; + let state = LanguageServerState::Starting({ + let server_name = adapter.name.0.clone(); + let languages = self.languages.clone(); + let key = key.clone(); - match result { - Ok(server) => Some(server), + cx.spawn_weak(|this, cx| async move { + let result = Self::setup_and_insert_language_server( + this, + initialization_options, + pending_server, + adapter, + languages, + language, + server_id, + key, + cx, + ) + .await; - Err(err) => { - log::warn!("Error starting language server {:?}: {}", server_name, err); - // TODO: Prompt installation validity check LSP ERROR - None - } + match result { + Ok(server) => Some(server), + + Err(err) => { + log::warn!("Error starting language server {:?}: {}", server_name, err); + // TODO: Prompt installation validity check LSP ERROR + None } - }) - }); + } + }) + }); - self.language_servers.insert(server_id, state); - self.language_server_ids.insert(key, server_id); - } + self.language_servers.insert(server_id, state); + self.language_server_ids.insert(key, server_id); } fn reinstall_language_server( @@ -2491,17 +2507,20 @@ impl Project { .await; this.update(&mut cx, |this, mut cx| { - for worktree in &this.worktrees { - let root_path = match worktree.upgrade(cx) { - Some(worktree) => worktree.read(cx).abs_path(), + let worktrees = this.worktrees.clone(); + for worktree in worktrees { + let worktree = match worktree.upgrade(cx) { + Some(worktree) => worktree.read(cx), None => continue, }; + let worktree_id = worktree.id(); + let root_path = worktree.abs_path(); - this.languages.start_language_server( - language.clone(), - adapter.clone(), + this.start_language_server( + worktree_id, root_path, - this.client.http_client(), + adapter.clone(), + language.clone(), &mut cx, ); } From da2ee550133374ea75545b08f34d93527d551fa6 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 19 Jun 2023 15:18:12 -0400 Subject: [PATCH 06/22] Route some more information for reinstall after startup failure Doesn't actually reinstall on that particular failure due to wrong variant in hashmap --- crates/language/src/language.rs | 101 +++++++++++++++++++------------- crates/lsp/src/lsp.rs | 10 ++-- crates/project/src/project.rs | 94 ++++++++++++++++++----------- 3 files changed, 126 insertions(+), 79 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1d595d0e2412cd060a3d83994bbde5aaea0ed703..e2c7b99763c2d783199bf1e0bc1f5824b2be7793 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -141,7 +141,7 @@ impl CachedLspAdapter { self.adapter.cached_server_binary(container_dir).await } - async fn installation_test_binary( + pub async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option { @@ -544,6 +544,7 @@ struct LanguageRegistryState { pub struct PendingLanguageServer { pub server_id: LanguageServerId, pub task: Task>, + pub container_dir: Option>, } impl LanguageRegistry { @@ -824,8 +825,8 @@ impl LanguageRegistry { cx: &mut AppContext, ) -> Option { let server_id = self.state.write().next_language_server_id(); - log::info!( - "starting language server name:{}, path:{root_path:?}, id:{server_id}", + println!( + "starting language server {:?}, path: {root_path:?}, id: {server_id}", adapter.name.0 ); @@ -858,7 +859,11 @@ impl LanguageRegistry { Ok(server) }); - return Some(PendingLanguageServer { server_id, task }); + return Some(PendingLanguageServer { + server_id, + task, + container_dir: None, + }); } let download_dir = self @@ -869,46 +874,54 @@ impl LanguageRegistry { let this = self.clone(); let language = language.clone(); let http_client = http_client.clone(); - let download_dir = download_dir.clone(); + let container_dir: Arc = Arc::from(download_dir.join(adapter.name.0.as_ref())); let root_path = root_path.clone(); let adapter = adapter.clone(); let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone(); - let task = cx.spawn(|cx| async move { - login_shell_env_loaded.await; - - let mut lock = this.lsp_binary_paths.lock(); - let entry = lock - .entry(adapter.name.clone()) - .or_insert_with(|| { - get_binary( - adapter.clone(), - language.clone(), - http_client, - download_dir, - lsp_binary_statuses, - ) - .map_err(Arc::new) - .boxed() - .shared() - }) - .clone(); - drop(lock); + let task = { + let container_dir = container_dir.clone(); + cx.spawn(|cx| async move { + login_shell_env_loaded.await; - let binary = entry.clone().map_err(|e| anyhow!(e)).await?; - let server = lsp::LanguageServer::new( - server_id, - binary, - &root_path, - adapter.code_action_kinds(), - cx, - )?; + let mut lock = this.lsp_binary_paths.lock(); + let entry = lock + .entry(adapter.name.clone()) + .or_insert_with(|| { + get_binaries( + adapter.clone(), + language.clone(), + http_client, + container_dir, + lsp_binary_statuses, + ) + .map_err(Arc::new) + .boxed() + .shared() + }) + .clone(); + drop(lock); + + let binaries = entry.clone().map_err(|e| anyhow!(e)).await?; + println!("starting server"); + let server = lsp::LanguageServer::new( + server_id, + binaries, + &root_path, + adapter.code_action_kinds(), + cx, + )?; - Ok(server) - }); + Ok(server) + }) + }; - Some(PendingLanguageServer { server_id, task }) + Some(PendingLanguageServer { + server_id, + task, + container_dir: Some(container_dir), + }) } pub fn language_server_binary_statuses( @@ -922,6 +935,7 @@ impl LanguageRegistry { adapter: Arc, cx: &mut AppContext, ) -> Task<()> { + println!("deleting server container"); let mut lock = self.lsp_binary_paths.lock(); lock.remove(&adapter.name); @@ -984,20 +998,20 @@ impl Default for LanguageRegistry { } } -async fn get_binary( +async fn get_binaries( adapter: Arc, language: Arc, http_client: Arc, - download_dir: Arc, + container_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { - let container_dir = download_dir.join(adapter.name.0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await .context("failed to create container directory")?; } + println!("fetching binary"); let binary = fetch_latest_binary( adapter.clone(), language.clone(), @@ -1008,11 +1022,16 @@ async fn get_binary( .await; if let Err(error) = binary.as_ref() { - if let Some(binary) = adapter.cached_server_binary(container_dir.clone()).await { + if let Some(binary) = adapter + .cached_server_binary(container_dir.to_path_buf()) + .await + { statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; - let installation_test_binary = adapter.installation_test_binary(container_dir).await; + let installation_test_binary = adapter + .installation_test_binary(container_dir.to_path_buf()) + .await; return Ok(LanguageServerBinaries { binary, installation_test_binary, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index f7d924604b69402a27210fb1df71a1da932d164a..cd7c1a0355cdc92de421fe66434fc7e32821908f 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -65,7 +65,7 @@ pub struct LanguageServer { output_done_rx: Mutex>, root_path: PathBuf, server: Option>, - test_installation_binary: Option, + installation_test_binary: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -190,7 +190,7 @@ impl LanguageServer { stdin: Stdin, stdout: Stdout, server: Option, - test_installation_binary: Option, + installation_test_binary: Option, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -245,7 +245,7 @@ impl LanguageServer { output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), server: server.map(|server| Mutex::new(server)), - test_installation_binary, + installation_test_binary, } } @@ -262,8 +262,8 @@ impl LanguageServer { } } - pub fn test_installation_binary(&self) -> &Option { - &self.test_installation_binary + pub fn installation_test_binary(&self) -> &Option { + &self.installation_test_binary } pub fn code_action_kinds(&self) -> Option> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 41a9d65069c34f8c39934487cea286dbd55e940c..44e1523bd17c7692eb4665e4a038114444a11236 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -45,7 +45,7 @@ use language::{ use log::error; use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, - DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, + DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf, }; use lsp_command::*; use postage::watch; @@ -2442,22 +2442,23 @@ impl Project { } let server_id = pending_server.server_id; + let container_dir = pending_server.container_dir.clone(); let state = LanguageServerState::Starting({ let server_name = adapter.name.0.clone(); let languages = self.languages.clone(); let key = key.clone(); - cx.spawn_weak(|this, cx| async move { + cx.spawn_weak(|this, mut cx| async move { let result = Self::setup_and_insert_language_server( this, initialization_options, pending_server, - adapter, + adapter.clone(), languages, language, server_id, key, - cx, + &mut cx, ) .await; @@ -2465,8 +2466,24 @@ impl Project { Ok(server) => Some(server), Err(err) => { - log::warn!("Error starting language server {:?}: {}", server_name, err); - // TODO: Prompt installation validity check LSP ERROR + println!("failed to start language server {:?}: {}", server_name, err); + + if let Some(this) = this.upgrade(&cx) { + if let Some(container_dir) = container_dir { + let installation_test_binary = adapter + .installation_test_binary(container_dir.to_path_buf()) + .await; + + this.update(&mut cx, |_, cx| { + Self::check_errored_server_id( + server_id, + installation_test_binary, + cx, + ) + }); + } + } + None } } @@ -2482,6 +2499,7 @@ impl Project { server_id: LanguageServerId, cx: &mut ModelContext, ) -> Option> { + println!("starting to reinstall server"); let (adapter, language, server) = match self.language_servers.remove(&server_id) { Some(LanguageServerState::Running { adapter, @@ -2495,6 +2513,7 @@ impl Project { Some(cx.spawn(move |this, mut cx| async move { if let Some(task) = server.shutdown() { + println!("shutting down existing server"); task.await; } @@ -2516,6 +2535,7 @@ impl Project { let worktree_id = worktree.id(); let root_path = worktree.abs_path(); + println!("prompting server start: {:?}", &adapter.name.0); this.start_language_server( worktree_id, root_path, @@ -2537,7 +2557,7 @@ impl Project { language: Arc, server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), - mut cx: AsyncAppContext, + cx: &mut AsyncAppContext, ) -> Result> { let language_server = Self::setup_pending_language_server( this, @@ -2546,16 +2566,16 @@ impl Project { adapter.clone(), languages, server_id, - &mut cx, + cx, ) .await?; - let this = match this.upgrade(&mut cx) { + let this = match this.upgrade(cx) { Some(this) => this, None => return Err(anyhow!("failed to upgrade project handle")), }; - this.update(&mut cx, |this, cx| { + this.update(cx, |this, cx| { this.insert_newly_running_language_server( language, adapter, @@ -3012,32 +3032,39 @@ impl Project { .detach(); } - fn check_errored_lsp_installation( + fn check_errored_language_server( &self, language_server: Arc, cx: &mut ModelContext, ) { - cx.spawn(|this, mut cx| async move { - if !language_server.is_dead() { - return; - } - let server_id = language_server.server_id(); + if !language_server.is_dead() { + return; + } + + let server_id = language_server.server_id(); + let installation_test_binary = language_server.installation_test_binary().clone(); + Self::check_errored_server_id(server_id, installation_test_binary, cx); + } + fn check_errored_server_id( + server_id: LanguageServerId, + installation_test_binary: Option, + cx: &mut ModelContext, + ) { + cx.spawn(|this, mut cx| async move { + println!("About to spawn test binary"); // A lack of test binary counts as a failure - let process = language_server - .test_installation_binary() - .as_ref() - .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() - }); + 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().timer(PROCESS_TIMEOUT).fuse(); @@ -3046,13 +3073,14 @@ impl Project { if let Some(mut process) = process { futures::select! { status = process.status().fuse() => match status { - Ok(status) => errored = !status.success(), + Ok(status) => errored = !dbg!(status.success()), Err(_) => errored = true, }, - _ = timeout => {} + _ = timeout => { println!("test binary time-ed out"); } } } else { + println!("test binary failed to launch"); errored = true; } @@ -3883,7 +3911,7 @@ impl Project { ); this.update(cx, |this, cx| { - this.check_errored_lsp_installation(language_server.clone(), cx); + this.check_errored_language_server(language_server.clone(), cx); }); None From afa1434aa94ae7073ebf4575c81198283a2e6c25 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 19 Jun 2023 17:45:27 -0400 Subject: [PATCH 07/22] Get further reinstalling a server which died on startup --- crates/project/src/project.rs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 44e1523bd17c7692eb4665e4a038114444a11236..5a42399040cec41a9e691f69dbf45a456fff265d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -280,7 +280,13 @@ pub enum Event { pub enum LanguageServerState { Validating(Task>>), - Starting(Task>>), + + Starting { + language: Arc, + adapter: Arc, + task: Task>>, + }, + Running { language: Arc, adapter: Arc, @@ -2443,9 +2449,11 @@ impl Project { let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); - let state = LanguageServerState::Starting({ + let task = { + let adapter = adapter.clone(); let server_name = adapter.name.0.clone(); let languages = self.languages.clone(); + let language = language.clone(); let key = key.clone(); cx.spawn_weak(|this, mut cx| async move { @@ -2488,7 +2496,12 @@ impl Project { } } }) - }); + }; + let state = LanguageServerState::Starting { + language, + adapter, + task, + }; self.language_servers.insert(server_id, state); self.language_server_ids.insert(key, server_id); @@ -2500,19 +2513,23 @@ impl Project { cx: &mut ModelContext, ) -> Option> { println!("starting to reinstall server"); - let (adapter, language, server) = match self.language_servers.remove(&server_id) { + let (language, adapter, server) = match self.language_servers.remove(&server_id) { Some(LanguageServerState::Running { - adapter, language, + adapter, server, .. - }) => (adapter.clone(), language.clone(), server), + }) => (language.clone(), adapter.clone(), Some(server)), + + Some(LanguageServerState::Starting { + language, adapter, .. + }) => (language.clone(), adapter.clone(), None), _ => return None, }; Some(cx.spawn(move |this, mut cx| async move { - if let Some(task) = server.shutdown() { + if let Some(task) = server.and_then(|server| server.shutdown()) { println!("shutting down existing server"); task.await; } @@ -2921,7 +2938,7 @@ impl Project { let server = match server_state { Some(LanguageServerState::Validating(task)) => task.await, - Some(LanguageServerState::Starting(task)) => task.await, + Some(LanguageServerState::Starting { task, .. }) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; @@ -7425,7 +7442,7 @@ impl Entity for Project { use LanguageServerState::*; match server_state { Running { server, .. } => server.shutdown()?.await, - Starting(task) | Validating(task) => task.await?.shutdown()?.await, + Starting { task, .. } | Validating(task) => task.await?.shutdown()?.await, } }) .collect::>(); From 7e70e24bfc92ac10cb64b464951e35ed56c8d690 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 19 Jun 2023 18:02:57 -0400 Subject: [PATCH 08/22] Remove server from both hashmaps --- crates/project/src/project.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5a42399040cec41a9e691f69dbf45a456fff265d..490d9fff800064bbcaf2e6f02d827875d27e7890 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2528,6 +2528,13 @@ impl Project { _ => return None, }; + for worktree in &self.worktrees { + if let Some(worktree) = worktree.upgrade(cx) { + 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) = server.and_then(|server| server.shutdown()) { println!("shutting down existing server"); From e15be61ded150c1f3f8433077111a40421593d53 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 21 Jun 2023 14:02:21 -0400 Subject: [PATCH 09/22] The log-ification --- crates/language/src/language.rs | 7 +++---- crates/project/src/project.rs | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index b9c2b52f9847010b6bd20cb6a18f9ace664b567b..e0ff6254445150ca93d88bb14f0a2bcfaaa5624d 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -869,7 +869,7 @@ impl LanguageRegistry { cx: &mut AppContext, ) -> Option { let server_id = self.state.write().next_language_server_id(); - println!( + log::info!( "starting language server {:?}, path: {root_path:?}, id: {server_id}", adapter.name.0 ); @@ -954,7 +954,6 @@ impl LanguageRegistry { task.await?; } - println!("starting server"); let server = lsp::LanguageServer::new( server_id, binaries, @@ -985,7 +984,8 @@ impl LanguageRegistry { adapter: Arc, cx: &mut AppContext, ) -> Task<()> { - println!("deleting server container"); + log::info!("deleting server container"); + let mut lock = self.lsp_binary_paths.lock(); lock.remove(&adapter.name); @@ -1066,7 +1066,6 @@ async fn get_binaries( task.await?; } - println!("fetching binary"); let binary = fetch_latest_binary( adapter.clone(), language.clone(), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e0ce91e772e7ed2d81bbb176bec6592fd49e1710..2536c94225f719b9153dc42ae7ec76323438d9f6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2480,7 +2480,7 @@ impl Project { Ok(server) => Some(server), Err(err) => { - println!("failed to start language server {:?}: {}", server_name, err); + log::error!("failed to start language server {:?}: {}", server_name, err); if let Some(this) = this.upgrade(&cx) { if let Some(container_dir) = container_dir { @@ -2518,7 +2518,7 @@ impl Project { server_id: LanguageServerId, cx: &mut ModelContext, ) -> Option> { - println!("starting to reinstall server"); + log::info!("beginning to reinstall server"); let (language, adapter, server) = match self.language_servers.remove(&server_id) { Some(LanguageServerState::Running { language, @@ -2543,7 +2543,7 @@ impl Project { Some(cx.spawn(move |this, mut cx| async move { if let Some(task) = server.and_then(|server| server.shutdown()) { - println!("shutting down existing server"); + log::info!("shutting down existing server"); task.await; } @@ -2565,7 +2565,6 @@ impl Project { let worktree_id = worktree.id(); let root_path = worktree.abs_path(); - println!("prompting server start: {:?}", &adapter.name.0); this.start_language_server( worktree_id, root_path, @@ -3082,7 +3081,8 @@ impl Project { cx: &mut ModelContext, ) { cx.spawn(|this, mut cx| async move { - println!("About to spawn test binary"); + 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) @@ -3103,18 +3103,21 @@ impl Project { if let Some(mut process) = process { futures::select! { status = process.status().fuse() => match status { - Ok(status) => errored = !dbg!(status.success()), + Ok(status) => errored = !status.success(), Err(_) => errored = true, }, - _ = timeout => { println!("test binary time-ed out"); } + _ = timeout => { + log::info!("test binary time-ed out, this counts as a success"); + } } } else { - println!("test binary failed to launch"); + 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, mut cx| { this.reinstall_language_server(server_id, &mut cx) }); From 9b63d6f832650c433cebd911c8b7863b062cdc1f Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 21 Jun 2023 23:05:37 -0400 Subject: [PATCH 10/22] Route language server requests through wrapper object --- crates/lsp/src/lsp.rs | 4 +- crates/project/src/lsp_command.rs | 28 +++--- crates/project/src/project.rs | 162 +++++++++++++++++++++++------- 3 files changed, 142 insertions(+), 52 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d52de667d42e668f43f959ff0fb42385ba3fcde3..e973a77f4fe211923a6aa9cb59d040bf4c606e96 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -665,11 +665,11 @@ impl LanguageServer { } } - pub fn name<'a>(self: &'a Arc) -> &'a str { + pub fn name<'a>(&self) -> &str { &self.name } - pub fn capabilities<'a>(self: &'a Arc) -> &'a ServerCapabilities { + pub fn capabilities<'a>(&self) -> &ServerCapabilities { &self.capabilities } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 8435de71e2f420e479035a5d21b36e850db29834..82e2c0c5a561dcfc4eda4b1dd22d1aa79937d6ea 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,6 +1,6 @@ use crate::{ DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project, - ProjectTransaction, + ProjectLanguageServer, ProjectTransaction, }; use anyhow::{anyhow, Result}; use async_trait::async_trait; @@ -14,7 +14,7 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; -use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities}; +use lsp::{DocumentHighlightKind, LanguageServerId, ServerCapabilities}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions { @@ -40,7 +40,7 @@ pub(crate) trait LspCommand: 'static + Sized { &self, path: &Path, buffer: &Buffer, - language_server: &Arc, + language_server: &Arc, cx: &AppContext, ) -> ::Params; @@ -156,7 +156,7 @@ impl LspCommand for PrepareRename { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::TextDocumentPositionParams { lsp::TextDocumentPositionParams { @@ -279,7 +279,7 @@ impl LspCommand for PerformRename { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::RenameParams { lsp::RenameParams { @@ -398,7 +398,7 @@ impl LspCommand for GetDefinition { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::GotoDefinitionParams { lsp::GotoDefinitionParams { @@ -499,7 +499,7 @@ impl LspCommand for GetTypeDefinition { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::GotoTypeDefinitionParams { lsp::GotoTypeDefinitionParams { @@ -587,7 +587,7 @@ fn language_server_for_buffer( buffer: &ModelHandle, server_id: LanguageServerId, cx: &mut AsyncAppContext, -) -> Result<(Arc, Arc)> { +) -> Result<(Arc, Arc)> { project .read_with(cx, |project, cx| { project @@ -784,7 +784,7 @@ impl LspCommand for GetReferences { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::ReferenceParams { lsp::ReferenceParams { @@ -949,7 +949,7 @@ impl LspCommand for GetDocumentHighlights { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::DocumentHighlightParams { lsp::DocumentHighlightParams { @@ -1096,7 +1096,7 @@ impl LspCommand for GetHover { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::HoverParams { lsp::HoverParams { @@ -1314,7 +1314,7 @@ impl LspCommand for GetCompletions { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::CompletionParams { lsp::CompletionParams { @@ -1530,7 +1530,7 @@ impl LspCommand for GetCodeActions { &self, path: &Path, buffer: &Buffer, - language_server: &Arc, + language_server: &Arc, _: &AppContext, ) -> lsp::CodeActionParams { let relevant_diagnostics = buffer @@ -1673,7 +1673,7 @@ impl LspCommand for OnTypeFormatting { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::DocumentOnTypeFormattingParams { lsp::DocumentOnTypeFormattingParams { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2536c94225f719b9153dc42ae7ec76323438d9f6..5c22a14d4d919bb412110c3570bd43dec11245aa 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -279,19 +279,79 @@ pub enum Event { CollaboratorLeft(proto::PeerId), } +pub struct ProjectLanguageServer { + server: Arc, + project: WeakModelHandle, +} + +impl std::ops::Deref for ProjectLanguageServer { + type Target = LanguageServer; + + fn deref(&self) -> &Self::Target { + &self.server + } +} + +impl ProjectLanguageServer { + pub fn new(server: Arc, project: WeakModelHandle) -> Self { + ProjectLanguageServer { server, project } + } + + pub fn request( + &self, + params: T::Params, + cx: &mut AsyncAppContext, + ) -> impl Future> + where + T::Result: 'static + Send, + { + let server = self.server.clone(); + let project = self.project.clone(); + + let future = server.request::(params); + + cx.spawn(|mut cx| async move { + let result = future.await; + if result.is_ok() { + return result; + } + + let project = match project.upgrade(&cx) { + Some(project) => project, + None => return result, + }; + + project.update(&mut cx, |_, cx| { + Project::check_errored_language_server(server, cx); + }); + + result + }) + } + + pub fn notify(&self, params: T::Params) -> Result<()> { + let result = self.server.notify::(params); + if result.is_ok() { + return Ok(()); + } + + result + } +} + pub enum LanguageServerState { - Validating(Task>>), + Validating(Task>>), Starting { language: Arc, adapter: Arc, - task: Task>>, + task: Task>>, }, Running { language: Arc, adapter: Arc, - server: Arc, + server: Arc, watched_paths: HashMap, simulate_disk_based_diagnostics_completion: Option>, }, @@ -2237,7 +2297,13 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator, &Arc, &Arc)> { + ) -> impl Iterator< + Item = ( + &Arc, + &Arc, + &Arc, + ), + > { self.language_server_ids .iter() .filter_map(move |((language_server_worktree_id, _), id)| { @@ -2477,7 +2543,7 @@ impl Project { .await; match result { - Ok(server) => Some(server), + Ok(server) => Some(Arc::new(ProjectLanguageServer::new(server, this))), Err(err) => { log::error!("failed to start language server {:?}: {}", server_name, err); @@ -2803,7 +2869,10 @@ impl Project { adapter: adapter.clone(), language: language.clone(), watched_paths: Default::default(), - server: language_server.clone(), + server: Arc::new(ProjectLanguageServer::new( + language_server.clone(), + cx.weak_handle(), + )), simulate_disk_based_diagnostics_completion: None, }, ); @@ -3062,7 +3131,6 @@ impl Project { } fn check_errored_language_server( - &self, language_server: Arc, cx: &mut ModelContext, ) { @@ -3897,7 +3965,7 @@ impl Project { this: &ModelHandle, buffer: &ModelHandle, abs_path: &Path, - language_server: &Arc, + language_server: &Arc, tab_size: NonZeroU32, cx: &mut AsyncAppContext, ) -> Result, String)>> { @@ -3911,23 +3979,29 @@ impl Project { let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) { language_server - .request::(lsp::DocumentFormattingParams { - text_document, - options: lsp_command::lsp_formatting_options(tab_size.get()), - work_done_progress_params: Default::default(), - }) + .request::( + lsp::DocumentFormattingParams { + text_document, + options: lsp_command::lsp_formatting_options(tab_size.get()), + work_done_progress_params: Default::default(), + }, + cx, + ) .await } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); language_server - .request::(lsp::DocumentRangeFormattingParams { - text_document, - range: lsp::Range::new(buffer_start, buffer_end), - options: lsp_command::lsp_formatting_options(tab_size.get()), - work_done_progress_params: Default::default(), - }) + .request::( + lsp::DocumentRangeFormattingParams { + text_document, + range: lsp::Range::new(buffer_start, buffer_end), + options: lsp_command::lsp_formatting_options(tab_size.get()), + work_done_progress_params: Default::default(), + }, + cx, + ) .await } else { Ok(None) @@ -3943,8 +4017,8 @@ impl Project { err ); - this.update(cx, |this, cx| { - this.check_errored_language_server(language_server.clone(), cx); + this.update(cx, |_, cx| { + Self::check_errored_language_server(language_server.server.clone(), cx); }); None @@ -4090,6 +4164,7 @@ impl Project { query: query.to_string(), ..Default::default() }, + &mut cx.to_async(), ) .map_ok(move |response| { let lsp_symbols = response.map(|symbol_response| match symbol_response { @@ -4323,7 +4398,10 @@ impl Project { cx.spawn(|this, mut cx| async move { let resolved_completion = match lang_server - .request::(completion.lsp_completion) + .request::( + completion.lsp_completion, + &mut cx, + ) .await { Ok(resolved_completion) => resolved_completion, @@ -4452,7 +4530,10 @@ impl Project { { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); action.lsp_action = match lang_server - .request::(action.lsp_action) + .request::( + action.lsp_action, + &mut cx, + ) .await { Ok(lsp_action) => lsp_action, @@ -4496,11 +4577,14 @@ impl Project { }); let result = lang_server - .request::(lsp::ExecuteCommandParams { - command: command.command, - arguments: command.arguments.unwrap_or_default(), - ..Default::default() - }) + .request::( + lsp::ExecuteCommandParams { + command: command.command, + arguments: command.arguments.unwrap_or_default(), + ..Default::default() + }, + &mut cx, + ) .await; if let Err(err) = result { @@ -4607,7 +4691,7 @@ impl Project { edits: Vec, push_to_history: bool, _: Arc, - language_server: Arc, + language_server: Arc, cx: &mut AsyncAppContext, ) -> Result> { let edits = this @@ -4648,7 +4732,7 @@ impl Project { edit: lsp::WorkspaceEdit, push_to_history: bool, lsp_adapter: Arc, - language_server: Arc, + language_server: Arc, cx: &mut AsyncAppContext, ) -> Result { let fs = this.read_with(cx, |this, _| this.fs.clone()); @@ -5070,12 +5154,15 @@ impl Project { .map(|(_, server)| server.clone()), ) { let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); - return cx.spawn(|this, cx| async move { + return cx.spawn(|this, mut cx| async move { if !request.check_capabilities(language_server.capabilities()) { return Ok(Default::default()); } - let result = language_server.request::(lsp_params).await; + let result = language_server + .request::(lsp_params, &mut cx) + .await; + let response = match result { Ok(response) => response, @@ -7277,7 +7364,10 @@ impl Project { }) } - pub fn language_server_for_id(&self, id: LanguageServerId) -> Option> { + pub fn language_server_for_id( + &self, + id: LanguageServerId, + ) -> Option> { if let LanguageServerState::Running { server, .. } = self.language_servers.get(&id)? { Some(server.clone()) } else { @@ -7289,7 +7379,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> impl Iterator, &Arc)> { + ) -> impl Iterator, &Arc)> { self.language_server_ids_for_buffer(buffer, cx) .into_iter() .filter_map(|server_id| { @@ -7309,7 +7399,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { self.language_servers_for_buffer(buffer, cx).next() } @@ -7318,7 +7408,7 @@ impl Project { buffer: &Buffer, server_id: LanguageServerId, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { self.language_servers_for_buffer(buffer, cx) .find(|(_, s)| s.server_id() == server_id) } From e1cd6cebb9542216e6101747c6da6e1f14722331 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 22 Jun 2023 10:45:08 -0400 Subject: [PATCH 11/22] Revert "Route language server requests through wrapper object" This reverts commit 9b63d6f832650c433cebd911c8b7863b062cdc1f. --- crates/lsp/src/lsp.rs | 4 +- crates/project/src/lsp_command.rs | 28 +++--- crates/project/src/project.rs | 162 +++++++----------------------- 3 files changed, 52 insertions(+), 142 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index e973a77f4fe211923a6aa9cb59d040bf4c606e96..d52de667d42e668f43f959ff0fb42385ba3fcde3 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -665,11 +665,11 @@ impl LanguageServer { } } - pub fn name<'a>(&self) -> &str { + pub fn name<'a>(self: &'a Arc) -> &'a str { &self.name } - pub fn capabilities<'a>(&self) -> &ServerCapabilities { + pub fn capabilities<'a>(self: &'a Arc) -> &'a ServerCapabilities { &self.capabilities } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 82e2c0c5a561dcfc4eda4b1dd22d1aa79937d6ea..8435de71e2f420e479035a5d21b36e850db29834 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,6 +1,6 @@ use crate::{ DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project, - ProjectLanguageServer, ProjectTransaction, + ProjectTransaction, }; use anyhow::{anyhow, Result}; use async_trait::async_trait; @@ -14,7 +14,7 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; -use lsp::{DocumentHighlightKind, LanguageServerId, ServerCapabilities}; +use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions { @@ -40,7 +40,7 @@ pub(crate) trait LspCommand: 'static + Sized { &self, path: &Path, buffer: &Buffer, - language_server: &Arc, + language_server: &Arc, cx: &AppContext, ) -> ::Params; @@ -156,7 +156,7 @@ impl LspCommand for PrepareRename { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::TextDocumentPositionParams { lsp::TextDocumentPositionParams { @@ -279,7 +279,7 @@ impl LspCommand for PerformRename { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::RenameParams { lsp::RenameParams { @@ -398,7 +398,7 @@ impl LspCommand for GetDefinition { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::GotoDefinitionParams { lsp::GotoDefinitionParams { @@ -499,7 +499,7 @@ impl LspCommand for GetTypeDefinition { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::GotoTypeDefinitionParams { lsp::GotoTypeDefinitionParams { @@ -587,7 +587,7 @@ fn language_server_for_buffer( buffer: &ModelHandle, server_id: LanguageServerId, cx: &mut AsyncAppContext, -) -> Result<(Arc, Arc)> { +) -> Result<(Arc, Arc)> { project .read_with(cx, |project, cx| { project @@ -784,7 +784,7 @@ impl LspCommand for GetReferences { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::ReferenceParams { lsp::ReferenceParams { @@ -949,7 +949,7 @@ impl LspCommand for GetDocumentHighlights { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::DocumentHighlightParams { lsp::DocumentHighlightParams { @@ -1096,7 +1096,7 @@ impl LspCommand for GetHover { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::HoverParams { lsp::HoverParams { @@ -1314,7 +1314,7 @@ impl LspCommand for GetCompletions { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::CompletionParams { lsp::CompletionParams { @@ -1530,7 +1530,7 @@ impl LspCommand for GetCodeActions { &self, path: &Path, buffer: &Buffer, - language_server: &Arc, + language_server: &Arc, _: &AppContext, ) -> lsp::CodeActionParams { let relevant_diagnostics = buffer @@ -1673,7 +1673,7 @@ impl LspCommand for OnTypeFormatting { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::DocumentOnTypeFormattingParams { lsp::DocumentOnTypeFormattingParams { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5c22a14d4d919bb412110c3570bd43dec11245aa..2536c94225f719b9153dc42ae7ec76323438d9f6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -279,79 +279,19 @@ pub enum Event { CollaboratorLeft(proto::PeerId), } -pub struct ProjectLanguageServer { - server: Arc, - project: WeakModelHandle, -} - -impl std::ops::Deref for ProjectLanguageServer { - type Target = LanguageServer; - - fn deref(&self) -> &Self::Target { - &self.server - } -} - -impl ProjectLanguageServer { - pub fn new(server: Arc, project: WeakModelHandle) -> Self { - ProjectLanguageServer { server, project } - } - - pub fn request( - &self, - params: T::Params, - cx: &mut AsyncAppContext, - ) -> impl Future> - where - T::Result: 'static + Send, - { - let server = self.server.clone(); - let project = self.project.clone(); - - let future = server.request::(params); - - cx.spawn(|mut cx| async move { - let result = future.await; - if result.is_ok() { - return result; - } - - let project = match project.upgrade(&cx) { - Some(project) => project, - None => return result, - }; - - project.update(&mut cx, |_, cx| { - Project::check_errored_language_server(server, cx); - }); - - result - }) - } - - pub fn notify(&self, params: T::Params) -> Result<()> { - let result = self.server.notify::(params); - if result.is_ok() { - return Ok(()); - } - - result - } -} - pub enum LanguageServerState { - Validating(Task>>), + Validating(Task>>), Starting { language: Arc, adapter: Arc, - task: Task>>, + task: Task>>, }, Running { language: Arc, adapter: Arc, - server: Arc, + server: Arc, watched_paths: HashMap, simulate_disk_based_diagnostics_completion: Option>, }, @@ -2297,13 +2237,7 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator< - Item = ( - &Arc, - &Arc, - &Arc, - ), - > { + ) -> impl Iterator, &Arc, &Arc)> { self.language_server_ids .iter() .filter_map(move |((language_server_worktree_id, _), id)| { @@ -2543,7 +2477,7 @@ impl Project { .await; match result { - Ok(server) => Some(Arc::new(ProjectLanguageServer::new(server, this))), + Ok(server) => Some(server), Err(err) => { log::error!("failed to start language server {:?}: {}", server_name, err); @@ -2869,10 +2803,7 @@ impl Project { adapter: adapter.clone(), language: language.clone(), watched_paths: Default::default(), - server: Arc::new(ProjectLanguageServer::new( - language_server.clone(), - cx.weak_handle(), - )), + server: language_server.clone(), simulate_disk_based_diagnostics_completion: None, }, ); @@ -3131,6 +3062,7 @@ impl Project { } fn check_errored_language_server( + &self, language_server: Arc, cx: &mut ModelContext, ) { @@ -3965,7 +3897,7 @@ impl Project { this: &ModelHandle, buffer: &ModelHandle, abs_path: &Path, - language_server: &Arc, + language_server: &Arc, tab_size: NonZeroU32, cx: &mut AsyncAppContext, ) -> Result, String)>> { @@ -3979,29 +3911,23 @@ impl Project { let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) { language_server - .request::( - lsp::DocumentFormattingParams { - text_document, - options: lsp_command::lsp_formatting_options(tab_size.get()), - work_done_progress_params: Default::default(), - }, - cx, - ) + .request::(lsp::DocumentFormattingParams { + text_document, + options: lsp_command::lsp_formatting_options(tab_size.get()), + work_done_progress_params: Default::default(), + }) .await } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); language_server - .request::( - lsp::DocumentRangeFormattingParams { - text_document, - range: lsp::Range::new(buffer_start, buffer_end), - options: lsp_command::lsp_formatting_options(tab_size.get()), - work_done_progress_params: Default::default(), - }, - cx, - ) + .request::(lsp::DocumentRangeFormattingParams { + text_document, + range: lsp::Range::new(buffer_start, buffer_end), + options: lsp_command::lsp_formatting_options(tab_size.get()), + work_done_progress_params: Default::default(), + }) .await } else { Ok(None) @@ -4017,8 +3943,8 @@ impl Project { err ); - this.update(cx, |_, cx| { - Self::check_errored_language_server(language_server.server.clone(), cx); + this.update(cx, |this, cx| { + this.check_errored_language_server(language_server.clone(), cx); }); None @@ -4164,7 +4090,6 @@ impl Project { query: query.to_string(), ..Default::default() }, - &mut cx.to_async(), ) .map_ok(move |response| { let lsp_symbols = response.map(|symbol_response| match symbol_response { @@ -4398,10 +4323,7 @@ impl Project { cx.spawn(|this, mut cx| async move { let resolved_completion = match lang_server - .request::( - completion.lsp_completion, - &mut cx, - ) + .request::(completion.lsp_completion) .await { Ok(resolved_completion) => resolved_completion, @@ -4530,10 +4452,7 @@ impl Project { { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); action.lsp_action = match lang_server - .request::( - action.lsp_action, - &mut cx, - ) + .request::(action.lsp_action) .await { Ok(lsp_action) => lsp_action, @@ -4577,14 +4496,11 @@ impl Project { }); let result = lang_server - .request::( - lsp::ExecuteCommandParams { - command: command.command, - arguments: command.arguments.unwrap_or_default(), - ..Default::default() - }, - &mut cx, - ) + .request::(lsp::ExecuteCommandParams { + command: command.command, + arguments: command.arguments.unwrap_or_default(), + ..Default::default() + }) .await; if let Err(err) = result { @@ -4691,7 +4607,7 @@ impl Project { edits: Vec, push_to_history: bool, _: Arc, - language_server: Arc, + language_server: Arc, cx: &mut AsyncAppContext, ) -> Result> { let edits = this @@ -4732,7 +4648,7 @@ impl Project { edit: lsp::WorkspaceEdit, push_to_history: bool, lsp_adapter: Arc, - language_server: Arc, + language_server: Arc, cx: &mut AsyncAppContext, ) -> Result { let fs = this.read_with(cx, |this, _| this.fs.clone()); @@ -5154,15 +5070,12 @@ impl Project { .map(|(_, server)| server.clone()), ) { let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); - return cx.spawn(|this, mut cx| async move { + return cx.spawn(|this, cx| async move { if !request.check_capabilities(language_server.capabilities()) { return Ok(Default::default()); } - let result = language_server - .request::(lsp_params, &mut cx) - .await; - + let result = language_server.request::(lsp_params).await; let response = match result { Ok(response) => response, @@ -7364,10 +7277,7 @@ impl Project { }) } - pub fn language_server_for_id( - &self, - id: LanguageServerId, - ) -> Option> { + pub fn language_server_for_id(&self, id: LanguageServerId) -> Option> { if let LanguageServerState::Running { server, .. } = self.language_servers.get(&id)? { Some(server.clone()) } else { @@ -7379,7 +7289,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> impl Iterator, &Arc)> { + ) -> impl Iterator, &Arc)> { self.language_server_ids_for_buffer(buffer, cx) .into_iter() .filter_map(|server_id| { @@ -7399,7 +7309,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { self.language_servers_for_buffer(buffer, cx).next() } @@ -7408,7 +7318,7 @@ impl Project { buffer: &Buffer, server_id: LanguageServerId, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { self.language_servers_for_buffer(buffer, cx) .find(|(_, s)| s.server_id() == server_id) } From 0abda54d3cb5d1576e66765ccca28fd048200fea Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 22 Jun 2023 11:18:17 -0400 Subject: [PATCH 12/22] Remove individual location's request error handling --- crates/lsp/src/lsp.rs | 17 +------ crates/project/src/project.rs | 93 +++++++---------------------------- 2 files changed, 21 insertions(+), 89 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d52de667d42e668f43f959ff0fb42385ba3fcde3..deab1d9cc06c4343e372f8930476cacd2f2f4b99 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -64,7 +64,7 @@ pub struct LanguageServer { io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, root_path: PathBuf, - server: Option>, + _server: Option>, installation_test_binary: Option, } @@ -247,24 +247,11 @@ impl LanguageServer { io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), - server: server.map(|server| Mutex::new(server)), + _server: server.map(|server| Mutex::new(server)), installation_test_binary, } } - pub fn is_dead(&self) -> bool { - let server = match self.server.as_ref() { - Some(server) => server, - None => return false, // Fake server for tests - }; - - match server.lock().try_status() { - Ok(Some(_)) => true, - Ok(None) => false, - Err(_) => true, - } - } - pub fn installation_test_binary(&self) -> &Option { &self.installation_test_binary } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2536c94225f719b9153dc42ae7ec76323438d9f6..46c8690555c7b33823b5b53983dcc14901bf9438 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3061,20 +3061,6 @@ impl Project { .detach(); } - fn check_errored_language_server( - &self, - language_server: Arc, - cx: &mut ModelContext, - ) { - if !language_server.is_dead() { - return; - } - - let server_id = language_server.server_id(); - let installation_test_binary = language_server.installation_test_binary().clone(); - Self::check_errored_server_id(server_id, installation_test_binary, cx); - } - fn check_errored_server_id( server_id: LanguageServerId, installation_test_binary: Option, @@ -3909,14 +3895,14 @@ impl Project { let formatting_provider = capabilities.document_formatting_provider.as_ref(); let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); - let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) { + let lsp_edits = if !matches!(formatting_provider, Some(OneOf::Left(false))) { language_server .request::(lsp::DocumentFormattingParams { text_document, options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) - .await + .await? } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); @@ -3928,27 +3914,9 @@ impl Project { options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) - .await + .await? } else { - Ok(None) - }; - - let lsp_edits = match result { - Ok(lsp_edits) => lsp_edits, - - Err(err) => { - log::warn!( - "Error firing format request to {}: {}", - language_server.name(), - err - ); - - this.update(cx, |this, cx| { - this.check_errored_language_server(language_server.clone(), cx); - }); - - None - } + None }; if let Some(lsp_edits) = lsp_edits { @@ -4091,8 +4059,9 @@ impl Project { ..Default::default() }, ) - .map_ok(move |response| { - let lsp_symbols = response.map(|symbol_response| match symbol_response { + .log_err() + .map(move |response| { + let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { flat_responses.into_iter().map(|lsp_symbol| { (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) @@ -4132,22 +4101,14 @@ impl Project { let symbols = this.read_with(&cx, |this, cx| { let mut symbols = Vec::new(); - for response in responses { - let ( - adapter, - adapter_language, - source_worktree_id, - worktree_abs_path, - lsp_symbols, - ) = match response { - Ok(response) => response, - - Err(err) => { - // TODO: Prompt installation validity check LSP ERROR - return Vec::new(); - } - }; - + for ( + adapter, + adapter_language, + source_worktree_id, + worktree_abs_path, + lsp_symbols, + ) in responses + { symbols.extend(lsp_symbols.into_iter().filter_map( |(symbol_name, symbol_kind, symbol_location)| { let abs_path = symbol_location.uri.to_file_path().ok()?; @@ -4322,17 +4283,9 @@ impl Project { }; cx.spawn(|this, mut cx| async move { - let resolved_completion = match lang_server + let resolved_completion = lang_server .request::(completion.lsp_completion) - .await - { - Ok(resolved_completion) => resolved_completion, - - Err(err) => { - // TODO: LSP ERROR - return Ok(None); - } - }; + .await?; if let Some(edits) = resolved_completion.additional_text_edits { let edits = this @@ -4451,17 +4404,9 @@ impl Project { .and_then(|d| d.get_mut("range")) { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); - action.lsp_action = match lang_server + action.lsp_action = lang_server .request::(action.lsp_action) - .await - { - Ok(lsp_action) => lsp_action, - - Err(err) => { - // LSP ERROR - return Err(err); - } - }; + .await?; } else { let actions = this .update(&mut cx, |this, cx| { From a8acf2898980cf6b46395d7f8bebf1b35fe4e2eb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 22 Jun 2023 20:19:07 -0400 Subject: [PATCH 13/22] Remove now-unnecessary complexity --- crates/project/src/project.rs | 46 +++++++++++++---------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 46c8690555c7b33823b5b53983dcc14901bf9438..60fd95e007d799fcdbb077b8f85ee77ae096abb6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -282,11 +282,7 @@ pub enum Event { pub enum LanguageServerState { Validating(Task>>), - Starting { - language: Arc, - adapter: Arc, - task: Task>>, - }, + Starting(Task>>), Running { language: Arc, @@ -2455,7 +2451,7 @@ impl Project { let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); - let task = { + let state = LanguageServerState::Starting({ let adapter = adapter.clone(); let server_name = adapter.name.0.clone(); let languages = self.languages.clone(); @@ -2469,7 +2465,7 @@ impl Project { pending_server, adapter.clone(), languages, - language, + language.clone(), server_id, key, &mut cx, @@ -2490,6 +2486,8 @@ impl Project { this.update(&mut cx, |_, cx| { Self::check_errored_server_id( + language, + adapter, server_id, installation_test_binary, cx, @@ -2502,12 +2500,7 @@ impl Project { } } }) - }; - let state = LanguageServerState::Starting { - language, - adapter, - task, - }; + }); self.language_servers.insert(server_id, state); self.language_server_ids.insert(key, server_id); @@ -2515,23 +2508,16 @@ impl Project { fn reinstall_language_server( &mut self, + language: Arc, + adapter: Arc, server_id: LanguageServerId, cx: &mut ModelContext, ) -> Option> { log::info!("beginning to reinstall server"); - let (language, adapter, server) = match self.language_servers.remove(&server_id) { - Some(LanguageServerState::Running { - language, - adapter, - server, - .. - }) => (language.clone(), adapter.clone(), Some(server)), - Some(LanguageServerState::Starting { - language, adapter, .. - }) => (language.clone(), adapter.clone(), None), - - _ => return None, + let existing_server = match self.language_servers.remove(&server_id) { + Some(LanguageServerState::Running { server, .. }) => Some(server), + _ => None, }; for worktree in &self.worktrees { @@ -2542,7 +2528,7 @@ impl Project { } Some(cx.spawn(move |this, mut cx| async move { - if let Some(task) = server.and_then(|server| server.shutdown()) { + if let Some(task) = existing_server.and_then(|server| server.shutdown()) { log::info!("shutting down existing server"); task.await; } @@ -2950,7 +2936,7 @@ impl Project { let server = match server_state { Some(LanguageServerState::Validating(task)) => task.await, - Some(LanguageServerState::Starting { task, .. }) => task.await, + Some(LanguageServerState::Starting(task)) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; @@ -3062,6 +3048,8 @@ impl Project { } fn check_errored_server_id( + language: Arc, + adapter: Arc, server_id: LanguageServerId, installation_test_binary: Option, cx: &mut ModelContext, @@ -3105,7 +3093,7 @@ impl Project { if errored { log::warn!("test binary check failed"); let task = this.update(&mut cx, move |this, mut cx| { - this.reinstall_language_server(server_id, &mut cx) + this.reinstall_language_server(language, adapter, server_id, &mut cx) }); if let Some(task) = task { @@ -7403,7 +7391,7 @@ impl Entity for Project { use LanguageServerState::*; match server_state { Running { server, .. } => server.shutdown()?.await, - Starting { task, .. } | Validating(task) => task.await?.shutdown()?.await, + Starting(task) | Validating(task) => task.await?.shutdown()?.await, } }) .collect::>(); From 3302e1133f4ffef9c5c022462b55aede2dc41dbb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 22 Jun 2023 20:22:05 -0400 Subject: [PATCH 14/22] Whoops --- styles/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/styles/tsconfig.json b/styles/tsconfig.json index fe9682b9697ceaf53023527e668eb836a80a5c44..6d24728a0a9f0e38b3a038c7c7bf3d0642e6e88f 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -1,5 +1,4 @@ { - // "noErrorTruncation": true, "compilerOptions": { "target": "es2015", "module": "commonjs", From 374c1a3a3e9f872c70c0051764835b8d7e99454d Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 23 Jun 2023 00:17:27 -0400 Subject: [PATCH 15/22] Remove some status stuff --- crates/activity_indicator/src/activity_indicator.rs | 4 +--- crates/language/src/language.rs | 1 - crates/project/src/project.rs | 9 +++------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index a8874e8d9e89f42f03f3f47d493a773dd4619f7b..9172b84f3c2cec4111777f8e1e974ea2a6d8f8a7 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -203,14 +203,12 @@ impl ActivityIndicator { } // Show any language server installation info. - let mut validating = SmallVec::<[_; 3]>::new(); let mut downloading = SmallVec::<[_; 3]>::new(); let mut checking_for_update = SmallVec::<[_; 3]>::new(); let mut failed = SmallVec::<[_; 3]>::new(); for status in &self.statuses { let name = status.name.clone(); match status.status { - LanguageServerBinaryStatus::Validating => validating.push(name), LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name), LanguageServerBinaryStatus::Downloading => downloading.push(name), LanguageServerBinaryStatus::Failed { .. } => failed.push(name), @@ -242,7 +240,7 @@ impl ActivityIndicator { ), on_click: None, }; - } else if !failed.is_empty() || !validating.is_empty() { + } else if !failed.is_empty() { return Content { icon: Some(WARNING_ICON), message: format!( diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e0ff6254445150ca93d88bb14f0a2bcfaaa5624d..9e3d3708ba7bc37ab81584cc68f3b19713bb0589 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -536,7 +536,6 @@ struct BracketConfig { #[derive(Clone)] pub enum LanguageServerBinaryStatus { - Validating, CheckingForUpdate, Downloading, Downloaded, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 60fd95e007d799fcdbb077b8f85ee77ae096abb6..a33a47779c24f27deff5e72645202e0df5831fa0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -280,8 +280,6 @@ pub enum Event { } pub enum LanguageServerState { - Validating(Task>>), - Starting(Task>>), Running { @@ -2485,7 +2483,7 @@ impl Project { .await; this.update(&mut cx, |_, cx| { - Self::check_errored_server_id( + Self::check_errored_server( language, adapter, server_id, @@ -2935,7 +2933,6 @@ impl Project { let mut root_path = None; let server = match server_state { - Some(LanguageServerState::Validating(task)) => task.await, Some(LanguageServerState::Starting(task)) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, @@ -3047,7 +3044,7 @@ impl Project { .detach(); } - fn check_errored_server_id( + fn check_errored_server( language: Arc, adapter: Arc, server_id: LanguageServerId, @@ -7391,7 +7388,7 @@ impl Entity for Project { use LanguageServerState::*; match server_state { Running { server, .. } => server.shutdown()?.await, - Starting(task) | Validating(task) => task.await?.shutdown()?.await, + Starting(task) => task.await?.shutdown()?.await, } }) .collect::>(); From 7caa096bd002f8e18d42f76b012ab7acfa5e25bc Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 23 Jun 2023 13:24:50 -0400 Subject: [PATCH 16/22] Remove installation test binary from language server instance --- crates/copilot/src/copilot.rs | 8 ++----- crates/language/src/language.rs | 39 ++++++++++----------------------- crates/lsp/src/lsp.rs | 24 ++++---------------- crates/project/src/project.rs | 2 +- 4 files changed, 18 insertions(+), 55 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 866bb8d7f526358a7cacb833c84037c87a23b878..4c45bf823bf8f77db9fbcea95e731e37475f4562 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -15,7 +15,7 @@ use language::{ ToPointUtf16, }; use log::{debug, error}; -use lsp::{LanguageServer, LanguageServerBinaries, LanguageServerBinary, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use request::{LogMessage, StatusNotification}; use settings::SettingsStore; @@ -366,13 +366,9 @@ impl Copilot { path: node_path, arguments, }; - let binaries = LanguageServerBinaries { - binary: binary.clone(), - installation_test_binary: Some(binary), - }; let server = LanguageServer::new( LanguageServerId(0), - binaries, + binary, Path::new("/"), None, cx.clone(), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 9e3d3708ba7bc37ab81584cc68f3b19713bb0589..beec63dfd2447da7be541f0475567e913cb8bd79 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,7 +20,7 @@ use futures::{ use gpui::{executor::Background, AppContext, AsyncAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; -use lsp::{CodeActionKind, LanguageServerBinaries, LanguageServerBinary}; +use lsp::{CodeActionKind, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -564,10 +564,7 @@ pub struct LanguageRegistry { login_shell_env_loaded: Shared>, #[allow(clippy::type_complexity)] lsp_binary_paths: Mutex< - HashMap< - LanguageServerName, - Shared>>>, - >, + HashMap>>>>, >, executor: Option>, } @@ -859,7 +856,7 @@ impl LanguageRegistry { self.state.read().languages.iter().cloned().collect() } - pub fn start_pending_language_server( + pub fn create_pending_language_server( self: &Arc, language: Arc, adapter: Arc, @@ -932,7 +929,7 @@ impl LanguageRegistry { .entry(adapter.name.clone()) .or_insert_with(|| { cx.spawn(|cx| { - get_binaries( + get_binary( adapter.clone(), language.clone(), delegate.clone(), @@ -953,15 +950,13 @@ impl LanguageRegistry { task.await?; } - let server = lsp::LanguageServer::new( + lsp::LanguageServer::new( server_id, binaries, &root_path, adapter.code_action_kinds(), cx, - )?; - - Ok(server) + ) }) }; @@ -1047,14 +1042,14 @@ impl Default for LanguageRegistry { } } -async fn get_binaries( +async fn get_binary( adapter: Arc, language: Arc, delegate: Arc, container_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, mut cx: AsyncAppContext, -) -> Result { +) -> Result { if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await @@ -1082,13 +1077,7 @@ async fn get_binaries( statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; - let installation_test_binary = adapter - .installation_test_binary(container_dir.to_path_buf()) - .await; - return Ok(LanguageServerBinaries { - binary, - installation_test_binary, - }); + return Ok(binary); } else { statuses .broadcast(( @@ -1110,7 +1099,7 @@ async fn fetch_latest_binary( delegate: &dyn LspAdapterDelegate, container_dir: &Path, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, -) -> Result { +) -> Result { let container_dir: Arc = container_dir.into(); lsp_binary_statuses_tx .broadcast(( @@ -1127,17 +1116,11 @@ async fn fetch_latest_binary( let binary = adapter .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate) .await?; - let installation_test_binary = adapter - .installation_test_binary(container_dir.to_path_buf()) - .await; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) .await?; - Ok(LanguageServerBinaries { - binary, - installation_test_binary, - }) + Ok(binary) } impl Language { diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index deab1d9cc06c4343e372f8930476cacd2f2f4b99..ffca2f24abf661056dc0c12bbcc69e31fdd5ca3d 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -43,12 +43,6 @@ pub struct LanguageServerBinary { pub arguments: Vec, } -#[derive(Debug, Clone, Deserialize)] -pub struct LanguageServerBinaries { - pub binary: LanguageServerBinary, - pub installation_test_binary: Option, -} - pub struct LanguageServer { server_id: LanguageServerId, next_id: AtomicUsize, @@ -65,7 +59,6 @@ pub struct LanguageServer { output_done_rx: Mutex>, root_path: PathBuf, _server: Option>, - installation_test_binary: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -135,7 +128,7 @@ struct Error { impl LanguageServer { pub fn new( server_id: LanguageServerId, - binaries: LanguageServerBinaries, + binary: LanguageServerBinary, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -146,9 +139,9 @@ impl LanguageServer { root_path.parent().unwrap_or_else(|| Path::new("/")) }; - let mut server = process::Command::new(&binaries.binary.path) + let mut server = process::Command::new(&binary.path) .current_dir(working_dir) - .args(binaries.binary.arguments) + .args(binary.arguments) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) @@ -162,7 +155,6 @@ impl LanguageServer { stdin, stout, Some(server), - binaries.installation_test_binary, root_path, code_action_kinds, cx, @@ -181,7 +173,7 @@ impl LanguageServer { }, ); - if let Some(name) = binaries.binary.path.file_name() { + if let Some(name) = binary.path.file_name() { server.name = name.to_string_lossy().to_string(); } @@ -193,7 +185,6 @@ impl LanguageServer { stdin: Stdin, stdout: Stdout, server: Option, - installation_test_binary: Option, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -248,14 +239,9 @@ impl LanguageServer { output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), _server: server.map(|server| Mutex::new(server)), - installation_test_binary, } } - pub fn installation_test_binary(&self) -> &Option { - &self.installation_test_binary - } - pub fn code_action_kinds(&self) -> Option> { self.code_action_kinds.clone() } @@ -840,7 +826,6 @@ impl LanguageServer { stdin_writer, stdout_reader, None, - None, Path::new("/"), None, cx.clone(), @@ -852,7 +837,6 @@ impl LanguageServer { stdout_writer, stdin_reader, None, - None, Path::new("/"), None, cx, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a33a47779c24f27deff5e72645202e0df5831fa0..4c6b25b0e90fa3a1367d5e60e4a8133e069c6f64 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2423,7 +2423,7 @@ impl Project { return; } - let pending_server = match self.languages.start_pending_language_server( + let pending_server = match self.languages.create_pending_language_server( language.clone(), adapter.clone(), worktree_path, From c4b83c86cc726417128a7dfa21ce1ec652573269 Mon Sep 17 00:00:00 2001 From: Julia Date: Sat, 24 Jun 2023 22:42:06 -0400 Subject: [PATCH 17/22] Avoid validating/reinstalling server which refuses will_fetch/start These adapters have indicated some broader reason to the user why they cannot be started, don't waste time/bandwidth attempting to validate and reinstall them --- crates/language/src/language.rs | 20 +++++++++++++------- crates/project/src/project.rs | 26 ++++++++++++++++---------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index beec63dfd2447da7be541f0475567e913cb8bd79..4f03da1afe7df3d81a53a1c6802eabf49c6bac05 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -583,7 +583,7 @@ struct LanguageRegistryState { pub struct PendingLanguageServer { pub server_id: LanguageServerId, - pub task: Task>, + pub task: Task>>, pub container_dir: Option>, } @@ -896,7 +896,8 @@ impl LanguageRegistry { } }) .detach(); - Ok(server) + + Ok(Some(server)) }); return Some(PendingLanguageServer { @@ -944,19 +945,24 @@ impl LanguageRegistry { .clone(); drop(lock); - let binaries = entry.clone().map_err(|e| anyhow!(e)).await?; + let binary = match entry.clone().await.log_err() { + Some(binary) => binary, + None => return Ok(None), + }; if let Some(task) = adapter.will_start_server(&delegate, &mut cx) { - task.await?; + if task.await.log_err().is_none() { + return Ok(None); + } } - lsp::LanguageServer::new( + Ok(Some(lsp::LanguageServer::new( server_id, - binaries, + binary, &root_path, adapter.code_action_kinds(), cx, - ) + )?)) }) }; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4c6b25b0e90fa3a1367d5e60e4a8133e069c6f64..7be57e921daf0378eebf7e661b3b4f48ee049620 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2471,7 +2471,7 @@ impl Project { .await; match result { - Ok(server) => Some(server), + Ok(server) => server, Err(err) => { log::error!("failed to start language server {:?}: {}", server_name, err); @@ -2571,8 +2571,8 @@ impl Project { server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), cx: &mut AsyncAppContext, - ) -> Result> { - let language_server = Self::setup_pending_language_server( + ) -> Result>> { + let setup = Self::setup_pending_language_server( this, initialization_options, pending_server, @@ -2580,8 +2580,12 @@ impl Project { languages, server_id, cx, - ) - .await?; + ); + + let language_server = match setup.await? { + Some(language_server) => language_server, + None => return Ok(None), + }; let this = match this.upgrade(cx) { Some(this) => this, @@ -2599,7 +2603,7 @@ impl Project { ) })?; - Ok(language_server) + Ok(Some(language_server)) } async fn setup_pending_language_server( @@ -2610,11 +2614,13 @@ impl Project { languages: Arc, server_id: LanguageServerId, cx: &mut AsyncAppContext, - ) -> Result> { + ) -> Result>> { let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; - let language_server = pending_server.task.await?; - let language_server = language_server.initialize(initialization_options).await?; + let language_server = match pending_server.task.await? { + Some(server) => server.initialize(initialization_options).await?, + None => return Ok(None), + }; language_server .on_notification::({ @@ -2756,7 +2762,7 @@ impl Project { ) .ok(); - Ok(language_server) + Ok(Some(language_server)) } fn insert_newly_running_language_server( From 5632f24d24f8a33177a32d67b22526d5cfac44b4 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 26 Jun 2023 10:18:30 -0400 Subject: [PATCH 18/22] Handle new elixir-ls release zip name --- crates/zed/src/languages/elixir.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 4d3f08ff53f8fd9dff393218d1496f9a402141b3..afac5abeb023d5c87e37bca141ffa0b4f334bf65 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -62,16 +62,23 @@ impl LspAdapter for ElixirLspAdapter { &self, delegate: &dyn LspAdapterDelegate, ) -> Result> { - let release = - latest_github_release("elixir-lsp/elixir-ls", false, delegate.http_client()).await?; - let asset_name = "elixir-ls.zip"; + let http = delegate.http_client(); + let release = latest_github_release("elixir-lsp/elixir-ls", false, http).await?; + let version_name = release + .name + .strip_prefix("Release ") + .context("Elixir-ls release name does not start with prefix")? + .to_owned(); + + let asset_name = format!("elixir-ls-{}.zip", &version_name); let asset = release .assets .iter() .find(|asset| asset.name == asset_name) .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + let version = GitHubLspBinaryVersion { - name: release.name, + name: version_name, url: asset.browser_download_url.clone(), }; Ok(Box::new(version) as Box<_>) @@ -116,7 +123,7 @@ impl LspAdapter for ElixirLspAdapter { .await? .status; if !unzip_status.success() { - Err(anyhow!("failed to unzip clangd archive"))?; + Err(anyhow!("failed to unzip elixir-ls archive"))?; } remove_matching(&container_dir, |entry| entry != version_dir).await; From 2a8d1343d66b9a10558402323c6ea33f01ed6b3f Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 26 Jun 2023 11:54:20 -0400 Subject: [PATCH 19/22] Add installation test binaries for all remaining adapters --- crates/language/src/language.rs | 18 ++++- crates/project/src/project.rs | 9 +++ crates/zed/src/languages/elixir.rs | 39 ++++++---- crates/zed/src/languages/go.rs | 66 ++++++++++------ crates/zed/src/languages/html.rs | 72 +++++++++++------- crates/zed/src/languages/json.rs | 64 ++++++++++------ crates/zed/src/languages/lua.rs | 66 ++++++++++------ crates/zed/src/languages/python.rs | 70 ++++++++++------- crates/zed/src/languages/ruby.rs | 8 ++ crates/zed/src/languages/rust.rs | 39 +++++++--- crates/zed/src/languages/typescript.rs | 101 ++++++++++++++++--------- crates/zed/src/languages/yaml.rs | 69 ++++++++++------- 12 files changed, 395 insertions(+), 226 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 4f03da1afe7df3d81a53a1c6802eabf49c6bac05..e8450344b8a627994cbe173a52c900339aa93cf3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -160,6 +160,10 @@ impl CachedLspAdapter { .await } + pub fn can_be_reinstalled(&self) -> bool { + self.adapter.can_be_reinstalled() + } + pub async fn installation_test_binary( &self, container_dir: PathBuf, @@ -249,12 +253,14 @@ pub trait LspAdapter: 'static + Send + Sync { delegate: &dyn LspAdapterDelegate, ) -> Option; + fn can_be_reinstalled(&self) -> bool { + true + } + async fn installation_test_binary( &self, - _container_dir: PathBuf, - ) -> Option { - unimplemented!(); - } + container_dir: PathBuf, + ) -> Option; async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} @@ -1667,6 +1673,10 @@ impl LspAdapter for Arc { unreachable!(); } + async fn installation_test_binary(&self, _: PathBuf) -> Option { + unreachable!(); + } + async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} async fn disk_based_diagnostic_sources(&self) -> Vec { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7be57e921daf0378eebf7e661b3b4f48ee049620..0820eaf26e00d9f580254edc9115ee14299ae118 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3057,6 +3057,14 @@ impl Project { 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(|this, mut cx| async move { log::info!("About to spawn test binary"); @@ -3086,6 +3094,7 @@ impl Project { _ = timeout => { log::info!("test binary time-ed out, this counts as a success"); + _ = process.kill(); } } } else { diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index afac5abeb023d5c87e37bca141ffa0b4f334bf65..c32927e15cfc177fe8c4611f8b5de686ebaffed6 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -140,20 +140,14 @@ impl LspAdapter for ElixirLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - last = Some(entry?.path()); - } - last.map(|path| LanguageServerBinary { - path, - arguments: vec![], - }) - .ok_or_else(|| anyhow!("no cached binary")) - })() - .await - .log_err() + get_cached_server_binary(container_dir).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir).await } async fn label_for_completion( @@ -239,3 +233,20 @@ impl LspAdapter for ElixirLspAdapter { }) } } + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + last.map(|path| LanguageServerBinary { + path, + arguments: vec![], + }) + .ok_or_else(|| anyhow!("no cached binary")) + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 9821dcd1eb60f96b35a1d63ccd88abe5c7e9d8d7..d7982f7bdb471d6b0a154d60094f6e206618f0b1 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -149,32 +149,19 @@ impl super::LspAdapter for GoLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_binary_path = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_file() - && entry - .file_name() - .to_str() - .map_or(false, |name| name.starts_with("gopls_")) - { - last_binary_path = Some(entry.path()); - } - } + get_cached_server_binary(container_dir).await + } - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - arguments: server_binary_arguments(), - }) - } else { - Err(anyhow!("no cached binary")) - } - })() - .await - .log_err() + 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( @@ -337,6 +324,35 @@ impl super::LspAdapter for GoLspAdapter { } } +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last_binary_path = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name.starts_with("gopls_")) + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + }) + } else { + Err(anyhow!("no cached binary")) + } + })() + .await + .log_err() +} + fn adjust_runs( delta: usize, mut runs: Vec<(Range, HighlightId)>, diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index 5e6f956b648101dae5afb3328b018ad7be183234..ecc839fca6875e54f0a0bf4d0671c675123418ef 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -14,6 +14,9 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = + "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -23,9 +26,6 @@ pub struct HtmlLspAdapter { } impl HtmlLspAdapter { - const SERVER_PATH: &'static str = - "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server"; - pub fn new(node: Arc) -> Self { HtmlLspAdapter { node } } @@ -55,7 +55,7 @@ impl LspAdapter for HtmlLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -77,31 +77,14 @@ impl LspAdapter for HtmlLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + 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) -> Option { @@ -110,3 +93,34 @@ impl LspAdapter for HtmlLspAdapter { })) } } + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index f40e35ebd7199dc8bda3157c73649dcf26588fb4..b7e4ab4ba7b32491bbbb8aa025cab543dde113af 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -83,32 +83,14 @@ impl LspAdapter for JsonLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } + get_cached_server_binary(container_dir, &self.node).await + } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await } async fn initialization_options(&self) -> Option { @@ -161,6 +143,38 @@ impl LspAdapter for JsonLspAdapter { } } +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} + fn schema_file_match(path: &Path) -> &Path { path.strip_prefix(path.parent().unwrap().parent().unwrap()) .unwrap() diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 3daabb64d093516aa83d2d92d26d107600d6b908..7c5c7179d019ee9c8c90e069fec55f297ceda2d5 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -92,31 +92,47 @@ impl super::LspAdapter for LuaLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - async_iife!({ - let mut last_binary_path = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_file() - && entry - .file_name() - .to_str() - .map_or(false, |name| name == "lua-language-server") - { - last_binary_path = Some(entry.path()); - } - } + get_cached_server_binary(container_dir).await + } - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - arguments: server_binary_arguments(), - }) - } else { - Err(anyhow!("no cached binary")) - } - }) - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--version".into()]; + binary + }) } } + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + async_iife!({ + let mut last_binary_path = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name == "lua-language-server") + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + }) + } else { + Err(anyhow!("no cached binary")) + } + }) + .await + .log_err() +} diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 5be05bea2e2b8948442df83099578fc7810166a6..41ad28ba862e38e04b52ec5e4e1b77e87b183200 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -13,6 +13,8 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -22,8 +24,6 @@ pub struct PythonLspAdapter { } impl PythonLspAdapter { - const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js"; - pub fn new(node: Arc) -> Self { PythonLspAdapter { node } } @@ -49,7 +49,7 @@ impl LspAdapter for PythonLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -68,31 +68,14 @@ impl LspAdapter for PythonLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + 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_completion(&self, item: &mut lsp::CompletionItem) { @@ -171,6 +154,37 @@ impl LspAdapter for PythonLspAdapter { } } +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} + #[cfg(test)] mod tests { use gpui::{ModelContext, TestAppContext}; diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index f88f4af5fd323539b9280e0b4f982fefab71dfc6..358441352a7090084c46e2f6fd1d06b2aed8c68c 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -39,6 +39,14 @@ impl LspAdapter for RubyLanguageServer { }) } + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } + async fn label_for_completion( &self, item: &lsp::CompletionItem, diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 0a2c93f42244cdfab0dcf092d22b2fdf25957838..97549b00583d45f3b03a730d2a10f43dc9b2f978 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -79,20 +79,19 @@ impl LspAdapter for RustLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - last = Some(entry?.path()); - } + get_cached_server_binary(container_dir).await + } - anyhow::Ok(LanguageServerBinary { - path: last.ok_or_else(|| anyhow!("no cached binary"))?, - arguments: Default::default(), + 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 }) - })() - .await - .log_err() } async fn disk_based_diagnostic_sources(&self) -> Vec { @@ -259,6 +258,22 @@ impl LspAdapter for RustLspAdapter { }) } } +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + + anyhow::Ok(LanguageServerBinary { + path: last.ok_or_else(|| anyhow!("no cached binary"))?, + arguments: Default::default(), + }) + })() + .await + .log_err() +} #[cfg(test)] mod tests { diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index c1df52c161869ee1c6c12e60c6a3b5dd83a5ae58..e6d1466731491376ac9f6f1278814a0e90b5f625 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -104,28 +104,14 @@ impl LspAdapter for TypeScriptLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let old_server_path = container_dir.join(Self::OLD_SERVER_PATH); - let new_server_path = container_dir.join(Self::NEW_SERVER_PATH); - if new_server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: typescript_server_binary_arguments(&new_server_path), - }) - } else if old_server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: typescript_server_binary_arguments(&old_server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - container_dir - )) - } - })() - .await - .log_err() + 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> { @@ -173,6 +159,34 @@ impl LspAdapter for TypeScriptLspAdapter { } } +async fn get_cached_ts_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let old_server_path = container_dir.join(TypeScriptLspAdapter::OLD_SERVER_PATH); + let new_server_path = container_dir.join(TypeScriptLspAdapter::NEW_SERVER_PATH); + if new_server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: typescript_server_binary_arguments(&new_server_path), + }) + } else if old_server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: typescript_server_binary_arguments(&old_server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + container_dir + )) + } + })() + .await + .log_err() +} + pub struct EsLintLspAdapter { node: Arc, } @@ -268,21 +282,14 @@ impl LspAdapter for EsLintLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - // This is unfortunate but we don't know what the version is to build a path directly - let mut dir = fs::read_dir(&container_dir).await?; - let first = dir.next().await.ok_or(anyhow!("missing first file"))??; - if !first.file_type().await?.is_dir() { - return Err(anyhow!("First entry is not a directory")); - } + get_cached_eslint_server_binary(container_dir, &self.node).await + } - Ok(LanguageServerBinary { - path: first.path().join(Self::SERVER_PATH), - arguments: Default::default(), - }) - })() - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_eslint_server_binary(container_dir, &self.node).await } async fn label_for_completion( @@ -298,6 +305,28 @@ impl LspAdapter for EsLintLspAdapter { } } +async fn get_cached_eslint_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + // This is unfortunate but we don't know what the version is to build a path directly + let mut dir = fs::read_dir(&container_dir).await?; + let first = dir.next().await.ok_or(anyhow!("missing first file"))??; + if !first.file_type().await?.is_dir() { + return Err(anyhow!("First entry is not a directory")); + } + let server_path = first.path().join(EsLintLspAdapter::SERVER_PATH); + + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: eslint_server_binary_arguments(&server_path), + }) + })() + .await + .log_err() +} + #[cfg(test)] mod tests { use gpui::TestAppContext; diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index c03bcf405e13051361aebe4fdaa72558a2a8fb6d..b57c6f5699498706de0b1c91655fb15848b1a2c6 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -18,6 +18,8 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -27,8 +29,6 @@ pub struct YamlLspAdapter { } impl YamlLspAdapter { - const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; - pub fn new(node: Arc) -> Self { YamlLspAdapter { node } } @@ -58,7 +58,7 @@ impl LspAdapter for YamlLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -77,33 +77,15 @@ impl LspAdapter for YamlLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + 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 + } fn workspace_configuration(&self, cx: &mut AppContext) -> Option> { let tab_size = all_language_settings(None, cx) .language(Some("YAML")) @@ -121,3 +103,34 @@ impl LspAdapter for YamlLspAdapter { ) } } + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} From b2de28ccfc9b3c1428693e5e7a30e7e7df64d157 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 27 Jun 2023 14:16:01 -0400 Subject: [PATCH 20/22] Match original logic when determining server to request formatting --- crates/project/src/project.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0820eaf26e00d9f580254edc9115ee14299ae118..561235d35fca0572a36c945173f0425715c4a80a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3895,7 +3895,7 @@ impl Project { let formatting_provider = capabilities.document_formatting_provider.as_ref(); let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); - let lsp_edits = if !matches!(formatting_provider, Some(OneOf::Left(false))) { + let lsp_edits = if matches!(formatting_provider, Some(p) if *p != OneOf::Left(false)) { language_server .request::(lsp::DocumentFormattingParams { text_document, @@ -3903,7 +3903,7 @@ impl Project { work_done_progress_params: Default::default(), }) .await? - } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { + } else if matches!(range_formatting_provider, Some(p) if *p != OneOf::Left(false)) { let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); From db2b3e47bc91dfba556ed2c9af24702f7dc2bcbe Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 28 Jun 2023 16:43:45 -0400 Subject: [PATCH 21/22] Reinstall Node whenever a NodeRuntime operation has serious error --- Cargo.lock | 1 + crates/copilot/src/copilot.rs | 2 +- crates/node_runtime/Cargo.toml | 1 + crates/node_runtime/src/node_runtime.rs | 203 ++++++++++++++---------- crates/zed/src/languages/typescript.rs | 4 +- crates/zed/src/main.rs | 2 +- crates/zed/src/zed.rs | 2 +- 7 files changed, 127 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4b12223e5a8fe6770464280bf652e08346a26ba..55e10ed32620816839622831db11168f2a11c004 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4138,6 +4138,7 @@ dependencies = [ "async-tar", "futures 0.3.28", "gpui", + "log", "parking_lot 0.11.2", "serde", "serde_derive", diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 4c45bf823bf8f77db9fbcea95e731e37475f4562..ce4938ed0d9e31c8130625b526095aeb0283e12c 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -340,7 +340,7 @@ impl Copilot { let http = util::http::FakeHttpClient::create(|_| async { unreachable!() }); let this = cx.add_model(|cx| Self { http: http.clone(), - node_runtime: NodeRuntime::new(http, cx.background().clone()), + node_runtime: NodeRuntime::instance(http, cx.background().clone()), server: CopilotServer::Running(RunningCopilotServer { lsp: Arc::new(server), sign_in_status: SignInStatus::Authorized, diff --git a/crates/node_runtime/Cargo.toml b/crates/node_runtime/Cargo.toml index fce0fdfe506201e6d126c141c0b692bd7ff39ada..53635f2725ba7a982d773abfd642104bca57d10f 100644 --- a/crates/node_runtime/Cargo.toml +++ b/crates/node_runtime/Cargo.toml @@ -20,3 +20,4 @@ serde.workspace = true serde_derive.workspace = true serde_json.workspace = true smol.workspace = true +log.workspace = true diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index e2a8d0d0032e23ea6751855acce33d90bded46d8..27a763e7f8851722907749c1a28a958827c3f9f5 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -1,21 +1,24 @@ use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; +use futures::lock::Mutex; use futures::{future::Shared, FutureExt}; use gpui::{executor::Background, Task}; -use parking_lot::Mutex; use serde::Deserialize; use smol::{fs, io::BufReader, process::Command}; +use std::process::Output; use std::{ env::consts, path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, OnceLock}, }; -use util::http::HttpClient; +use util::{http::HttpClient, ResultExt}; const VERSION: &str = "v18.15.0"; -#[derive(Deserialize)] +static RUNTIME_INSTANCE: OnceLock> = OnceLock::new(); + +#[derive(Debug, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct NpmInfo { #[serde(default)] @@ -23,7 +26,7 @@ pub struct NpmInfo { versions: Vec, } -#[derive(Deserialize, Default)] +#[derive(Debug, Deserialize, Default)] pub struct NpmInfoDistTags { latest: Option, } @@ -35,12 +38,16 @@ pub struct NodeRuntime { } impl NodeRuntime { - pub fn new(http: Arc, background: Arc) -> Arc { - Arc::new(NodeRuntime { - http, - background, - installation_path: Mutex::new(None), - }) + pub fn instance(http: Arc, background: Arc) -> Arc { + RUNTIME_INSTANCE + .get_or_init(|| { + Arc::new(NodeRuntime { + http, + background, + installation_path: Mutex::new(None), + }) + }) + .clone() } pub async fn binary_path(&self) -> Result { @@ -50,55 +57,74 @@ impl NodeRuntime { pub async fn run_npm_subcommand( &self, - directory: &Path, + directory: Option<&Path>, subcommand: &str, args: &[&str], - ) -> Result<()> { + ) -> Result { + let attempt = |installation_path: PathBuf| async move { + let node_binary = installation_path.join("bin/node"); + let npm_file = installation_path.join("bin/npm"); + + if smol::fs::metadata(&node_binary).await.is_err() { + return Err(anyhow!("missing node binary file")); + } + + if smol::fs::metadata(&npm_file).await.is_err() { + return Err(anyhow!("missing npm file")); + } + + let mut command = Command::new(node_binary); + command.arg(npm_file).arg(subcommand).args(args); + + if let Some(directory) = directory { + command.current_dir(directory); + } + + command.output().await.map_err(|e| anyhow!("{e}")) + }; + let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); - - let output = Command::new(node_binary) - .arg(npm_file) - .arg(subcommand) - .args(args) - .current_dir(directory) - .output() - .await?; + let mut output = attempt(installation_path).await; + if output.is_err() { + let installation_path = self.reinstall().await?; + output = attempt(installation_path).await; + if output.is_err() { + return Err(anyhow!( + "failed to launch npm subcommand {subcommand} subcommand" + )); + } + } - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); + if let Ok(output) = &output { + if !output.status.success() { + return Err(anyhow!( + "failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } } - Ok(()) + output.map_err(|e| anyhow!("{e}")) } pub async fn npm_package_latest_version(&self, name: &str) -> Result { - let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); - - let output = Command::new(node_binary) - .arg(npm_file) - .args(["-fetch-retry-mintimeout", "2000"]) - .args(["-fetch-retry-maxtimeout", "5000"]) - .args(["-fetch-timeout", "5000"]) - .args(["info", name, "--json"]) - .output() - .await - .context("failed to run npm info")?; - - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } + let output = self + .run_npm_subcommand( + None, + "info", + &[ + name, + "--json", + "-fetch-retry-mintimeout", + "2000", + "-fetch-retry-maxtimeout", + "5000", + "-fetch-timeout", + "5000", + ], + ) + .await?; let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; info.dist_tags @@ -112,41 +138,54 @@ impl NodeRuntime { directory: &Path, packages: impl IntoIterator, ) -> Result<()> { - let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); - - let output = Command::new(node_binary) - .arg(npm_file) - .args(["-fetch-retry-mintimeout", "2000"]) - .args(["-fetch-retry-maxtimeout", "5000"]) - .args(["-fetch-timeout", "5000"]) - .arg("install") - .arg("--prefix") - .arg(directory) - .args( - packages - .into_iter() - .map(|(name, version)| format!("{name}@{version}")), - ) - .output() - .await - .context("failed to run npm install")?; - - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm install:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } + let packages: Vec<_> = packages + .into_iter() + .map(|(name, version)| format!("{name}@{version}")) + .collect(); + + let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect(); + arguments.extend_from_slice(&[ + "-fetch-retry-mintimeout", + "2000", + "-fetch-retry-maxtimeout", + "5000", + "-fetch-timeout", + "5000", + ]); + + self.run_npm_subcommand(Some(directory), "install", &arguments) + .await?; Ok(()) } + async fn reinstall(&self) -> Result { + log::info!("beginnning to reinstall Node runtime"); + let mut installation_path = self.installation_path.lock().await; + + if let Some(task) = installation_path.as_ref().cloned() { + if let Ok(installation_path) = task.await { + smol::fs::remove_dir_all(&installation_path) + .await + .context("node dir removal") + .log_err(); + } + } + + let http = self.http.clone(); + let task = self + .background + .spawn(async move { Self::install(http).await.map_err(Arc::new) }) + .shared(); + + *installation_path = Some(task.clone()); + task.await.map_err(|e| anyhow!("{}", e)) + } + async fn install_if_needed(&self) -> Result { let task = self .installation_path .lock() + .await .get_or_insert_with(|| { let http = self.http.clone(); self.background @@ -155,13 +194,11 @@ impl NodeRuntime { }) .clone(); - match task.await { - Ok(path) => Ok(path), - Err(error) => Err(anyhow!("{}", error)), - } + task.await.map_err(|e| anyhow!("{}", e)) } async fn install(http: Arc) -> Result { + log::info!("installing Node runtime"); let arch = match consts::ARCH { "x86_64" => "x64", "aarch64" => "arm64", diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index e6d1466731491376ac9f6f1278814a0e90b5f625..0a47d365b598aa41df1c1fa50aedd7d718aceb87 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -263,11 +263,11 @@ impl LspAdapter for EsLintLspAdapter { fs::rename(first.path(), &repo_root).await?; self.node - .run_npm_subcommand(&repo_root, "install", &[]) + .run_npm_subcommand(Some(&repo_root), "install", &[]) .await?; self.node - .run_npm_subcommand(&repo_root, "run-script", &["compile"]) + .run_npm_subcommand(Some(&repo_root), "run-script", &["compile"]) .await?; } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index dcdf5c1ea5f542339cfac3ef7edeac61c5ab34c4..6421014a4d1eb742030f7434177deed65164a122 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -131,7 +131,7 @@ fn main() { languages.set_executor(cx.background().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); - let node_runtime = NodeRuntime::new(http.clone(), cx.background().to_owned()); + let node_runtime = NodeRuntime::instance(http.clone(), cx.background().to_owned()); languages::init(languages.clone(), node_runtime.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index bcdfe57a469013251994b4112500daa4a7489027..260c7edf94a55647a30636eb872daa3128de19ce 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2177,7 +2177,7 @@ mod tests { languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); let http = FakeHttpClient::with_404_response(); - let node_runtime = NodeRuntime::new(http, cx.background().to_owned()); + let node_runtime = NodeRuntime::instance(http, cx.background().to_owned()); languages::init(languages.clone(), node_runtime); for name in languages.language_names() { languages.language_for_name(&name); From 2ed0284d4979e42eeadd535bb1462de53d3d3401 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 28 Jun 2023 17:06:50 -0400 Subject: [PATCH 22/22] Stub out for language plugin --- crates/zed/src/languages/language_plugin.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 11720d899308227a20b252f3371991322b0fc17d..b0719363929232aa47721624a63a9b8bb4aacb5d 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -130,6 +130,14 @@ impl LspAdapter for PluginLspAdapter { .await } + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } + async fn initialization_options(&self) -> Option { let string: String = self .runtime