From ededfff3a8fa24901f5396a25172a6862a24b160 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 21 Feb 2022 13:54:52 -0800 Subject: [PATCH] Download language servers on-demand Co-Authored-By: Nathan Sobo --- Cargo.lock | 1 + crates/client/src/client.rs | 4 + crates/language/Cargo.toml | 3 + crates/language/src/language.rs | 148 ++++++++++++++++---------------- crates/language/src/tests.rs | 40 ++++----- crates/project/src/project.rs | 33 +++---- crates/server/src/rpc.rs | 16 ++-- crates/zed/src/language.rs | 74 ++++++++++------ crates/zed/src/main.rs | 2 +- crates/zed/src/test.rs | 2 +- 10 files changed, 172 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76fb2e7cb0..7bf51789b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2620,6 +2620,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "client", "clock", "collections", "ctor", diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 93ab2c6ad6..2c31e8eef3 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -224,6 +224,10 @@ impl Client { self.id } + pub fn http_client(&self) -> Arc { + self.http.clone() + } + #[cfg(any(test, feature = "test-support"))] pub fn override_authenticate(&mut self, authenticate: F) -> &mut Self where diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index eef1a01054..5a3637c3bb 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -9,6 +9,7 @@ path = "src/language.rs" [features] test-support = [ "rand", + "client/test-support", "collections/test-support", "lsp/test-support", "text/test-support", @@ -17,6 +18,7 @@ test-support = [ ] [dependencies] +client = { path = "../client" } clock = { path = "../clock" } collections = { path = "../collections" } fuzzy = { path = "../fuzzy" } @@ -44,6 +46,7 @@ tree-sitter = "0.20" tree-sitter-rust = { version = "0.20.0", optional = true } [dev-dependencies] +client = { path = "../client", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 34276c051e..fd91d2e546 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -7,12 +7,13 @@ pub mod proto; mod tests; use anyhow::{anyhow, Result}; +use client::http::HttpClient; use collections::HashSet; use futures::{ future::{BoxFuture, Shared}, - FutureExt, + FutureExt, TryFutureExt, }; -use gpui::{executor, AppContext, Task}; +use gpui::{AppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::Mutex; @@ -20,7 +21,6 @@ use postage::watch; use serde::Deserialize; use std::{ cell::RefCell, - future::Future, ops::Range, path::{Path, PathBuf}, str, @@ -60,7 +60,10 @@ pub trait ToLspPosition { } pub trait LspExt: 'static + Send + Sync { - fn server_bin_path(&self) -> BoxFuture<'static, Option>; + fn fetch_latest_language_server( + &self, + http: Arc, + ) -> BoxFuture<'static, Result>; fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams); fn label_for_completion( &self, @@ -116,7 +119,7 @@ pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, pub(crate) lsp_ext: Option>, - lsp_binary_path: Mutex>>>>, + lsp_binary_path: Mutex>>>>>, } pub struct Grammar { @@ -144,17 +147,8 @@ impl LanguageRegistry { } } - pub fn add(&mut self, language: Arc, cx: &executor::Background) { + pub fn add(&mut self, language: Arc) { self.languages.push(language.clone()); - if let Some(lsp_binary_path) = language.lsp_binary_path() { - let pending_lsp_binaries_tx = self.pending_lsp_binaries_tx.clone(); - cx.spawn(async move { - *pending_lsp_binaries_tx.lock().borrow_mut() += 1; - lsp_binary_path.await; - *pending_lsp_binaries_tx.lock().borrow_mut() -= 1; - }) - .detach(); - } } pub fn set_theme(&self, theme: &SyntaxTheme) { @@ -183,6 +177,71 @@ impl LanguageRegistry { }) } + pub fn start_language_server( + &self, + language: &Arc, + root_path: Arc, + http_client: Arc, + cx: &AppContext, + ) -> Option>>> { + #[cfg(any(test, feature = "test-support"))] + if let Some(config) = &language.config.language_server { + if let Some(fake_config) = &config.fake_config { + use postage::prelude::Stream; + + let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities( + fake_config.capabilities.clone(), + cx.background().clone(), + ); + + if let Some(initalizer) = &fake_config.initializer { + initalizer(&mut fake_server); + } + + let servers_tx = fake_config.servers_tx.clone(); + let mut initialized = server.capabilities(); + cx.background() + .spawn(async move { + while initialized.recv().await.is_none() {} + servers_tx.unbounded_send(fake_server).ok(); + }) + .detach(); + + return Some(Task::ready(Ok(server.clone()))); + } + } + + let lsp_ext = language.lsp_ext.as_ref()?; + let background = cx.background().clone(); + let server_binary_path = { + Some( + language + .lsp_binary_path + .lock() + .get_or_insert_with(|| { + let pending_lsp_binaries_tx = self.pending_lsp_binaries_tx.clone(); + let language_server_path = + lsp_ext.fetch_latest_language_server(http_client); + async move { + *pending_lsp_binaries_tx.lock().borrow_mut() += 1; + let path = language_server_path.map_err(Arc::new).await; + *pending_lsp_binaries_tx.lock().borrow_mut() -= 1; + path + } + .boxed() + .shared() + }) + .clone() + .map_err(|e| anyhow!(e)), + ) + }?; + Some(cx.background().spawn(async move { + let server_binary_path = server_binary_path.await?; + let server = lsp::LanguageServer::new(&server_binary_path, &root_path, background)?; + Ok(server) + })) + } + pub fn pending_lsp_binaries(&self) -> watch::Receiver { self.pending_lsp_binaries_rx.clone() } @@ -260,52 +319,6 @@ impl Language { self.config.line_comment.as_deref() } - pub fn start_server( - &self, - root_path: Arc, - cx: &AppContext, - ) -> Task>>> { - #[cfg(any(test, feature = "test-support"))] - if let Some(config) = &self.config.language_server { - if let Some(fake_config) = &config.fake_config { - use postage::prelude::Stream; - - let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities( - fake_config.capabilities.clone(), - cx.background().clone(), - ); - - if let Some(initalizer) = &fake_config.initializer { - initalizer(&mut fake_server); - } - - let servers_tx = fake_config.servers_tx.clone(); - let mut initialized = server.capabilities(); - cx.background() - .spawn(async move { - while initialized.recv().await.is_none() {} - servers_tx.unbounded_send(fake_server).ok(); - }) - .detach(); - - return Task::ready(Ok(Some(server.clone()))); - } - } - - let background = cx.background().clone(); - let server_binary_path = self - .lsp_binary_path() - .ok_or_else(|| anyhow!("cannot locate or download language server")); - cx.background().spawn(async move { - if let Some(server_binary_path) = server_binary_path?.await { - let server = lsp::LanguageServer::new(&server_binary_path, &root_path, background)?; - Ok(Some(server)) - } else { - Ok(None) - } - }) - } - pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet> { self.config .language_server @@ -356,19 +369,6 @@ impl Language { result } - fn lsp_binary_path(&self) -> Option>> { - if let Some(lsp_ext) = self.lsp_ext.as_ref() { - Some( - self.lsp_binary_path - .lock() - .get_or_insert_with(|| lsp_ext.server_bin_path().shared()) - .clone(), - ) - } else { - None - } - } - pub fn brackets(&self) -> &[BracketPair] { &self.config.brackets } diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 98ce02e2f6..1adcca91a5 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -23,30 +23,24 @@ fn init_logger() { } #[gpui::test] -fn test_select_language(cx: &mut MutableAppContext) { +fn test_select_language() { let mut registry = LanguageRegistry::new(); - registry.add( - Arc::new(Language::new( - LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - )), - cx.background(), - ); - registry.add( - Arc::new(Language::new( - LanguageConfig { - name: "Make".to_string(), - path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - )), - cx.background(), - ); + registry.add(Arc::new(Language::new( + LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ))); + registry.add(Arc::new(Language::new( + LanguageConfig { + name: "Make".to_string(), + path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ))); // matching file extension assert_eq!( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7b53eba88b..c1ef8fdfcd 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -835,20 +835,21 @@ impl Project { self.started_language_servers .entry(key.clone()) .or_insert_with(|| { - let language_server = language.start_server(worktree_path, cx); + let language_server = self.languages.start_language_server( + &language, + worktree_path, + self.client.http_client(), + cx, + ); let rpc = self.client.clone(); cx.spawn_weak(|this, mut cx| async move { - let language_server = language_server.await.log_err().flatten(); + let language_server = language_server?.await.log_err()?; if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, _| { - if let Some(language_server) = language_server.clone() { - this.language_servers.insert(key, language_server); - } + this.language_servers.insert(key, language_server.clone()); }); } - let language_server = language_server?; - let disk_based_sources = language .disk_based_diagnostic_sources() .cloned() @@ -3069,10 +3070,8 @@ mod tests { .await; let project = Project::test(fs, &mut cx); - project.update(&mut cx, |project, cx| { - Arc::get_mut(&mut project.languages) - .unwrap() - .add(language, cx.background()); + project.update(&mut cx, |project, _| { + Arc::get_mut(&mut project.languages).unwrap().add(language); }); let (tree, _) = project @@ -3217,10 +3216,8 @@ mod tests { .await; let project = Project::test(fs, &mut cx); - project.update(&mut cx, |project, cx| { - Arc::get_mut(&mut project.languages) - .unwrap() - .add(language, cx.background()); + project.update(&mut cx, |project, _| { + Arc::get_mut(&mut project.languages).unwrap().add(language); }); let (tree, _) = project @@ -4112,10 +4109,8 @@ mod tests { .await; let project = Project::test(fs.clone(), &mut cx); - project.update(&mut cx, |project, cx| { - Arc::get_mut(&mut project.languages) - .unwrap() - .add(language, cx.background()); + project.update(&mut cx, |project, _| { + Arc::get_mut(&mut project.languages).unwrap().add(language); }); let (tree, _) = project diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index db9843fc95..0bfb918b45 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -2011,7 +2011,7 @@ mod tests { }, Some(tree_sitter_rust::language()), )), - &cx_a.background(), + ); // Connect to a server as 2 clients. @@ -2243,7 +2243,7 @@ mod tests { }, Some(tree_sitter_rust::language()), )), - &cx_a.background(), + ); // Connect to a server as 2 clients. @@ -2446,7 +2446,7 @@ mod tests { }, Some(tree_sitter_rust::language()), )), - &cx_a.background(), + ); // Connect to a server as 2 clients. @@ -2564,7 +2564,7 @@ mod tests { }, Some(tree_sitter_rust::language()), )), - &cx_a.background(), + ); // Connect to a server as 2 clients. @@ -2713,7 +2713,7 @@ mod tests { }, Some(tree_sitter_rust::language()), )), - &cx_a.background(), + ); // Connect to a server as 2 clients. @@ -2815,7 +2815,7 @@ mod tests { }, Some(tree_sitter_rust::language()), )), - &cx_a.background(), + ); // Connect to a server as 2 clients. @@ -3055,7 +3055,7 @@ mod tests { }, Some(tree_sitter_rust::language()), )), - &cx_a.background(), + ); // Connect to a server as 2 clients. @@ -3862,7 +3862,7 @@ mod tests { }, None, )), - &cx.background(), + ); let fs = FakeFs::new(cx.background()); diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index da5fa8105f..1203e87309 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -1,8 +1,7 @@ -use anyhow::anyhow; +use anyhow::{anyhow, Result}; use async_compression::futures::bufread::GzipDecoder; -use client::http; +use client::http::{self, HttpClient, Method}; use futures::{future::BoxFuture, FutureExt, StreamExt}; -use gpui::executor; pub use language::*; use lazy_static::lazy_static; use regex::Regex; @@ -10,7 +9,7 @@ use rust_embed::RustEmbed; use serde::Deserialize; use smol::fs::{self, File}; use std::{borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; -use util::{ResultExt, TryFutureExt}; +use util::ResultExt; #[derive(RustEmbed)] #[folder = "languages"] @@ -31,13 +30,27 @@ struct GithubReleaseAsset { } impl RustLsp { - async fn download(destination_dir_path: PathBuf) -> anyhow::Result { - let client = surf::client().with(surf::middleware::Redirect::default()); - let release = client - .get("https://api.github.com/repos/rust-analyzer/rust-analyzer/releases/latest") - .recv_json::() + async fn download( + destination_dir_path: PathBuf, + http: Arc, + ) -> anyhow::Result { + let release = http + .send( + surf::RequestBuilder::new( + Method::Get, + http::Url::parse( + "https://api.github.com/repos/rust-analyzer/rust-analyzer/releases/latest", + ) + .unwrap(), + ) + .middleware(surf::middleware::Redirect::default()) + .build(), + ) .await - .map_err(|err| anyhow!("error getting latest release: {}", err))?; + .map_err(|err| anyhow!("error fetching latest release: {}", err))? + .body_json::() + .await + .map_err(|err| anyhow!("error parsing latest release: {}", err))?; let release_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH); let asset = release .assets @@ -47,9 +60,12 @@ impl RustLsp { let destination_path = destination_dir_path.join(format!("rust-analyzer-{}", release.name)); if fs::metadata(&destination_path).await.is_err() { - let response = client - .get(&asset.browser_download_url) - .send() + let response = http + .send( + surf::RequestBuilder::new(Method::Get, asset.browser_download_url.clone()) + .middleware(surf::middleware::Redirect::default()) + .build(), + ) .await .map_err(|err| anyhow!("error downloading release: {}", err))?; let decompressed_bytes = GzipDecoder::new(response); @@ -67,32 +83,40 @@ impl RustLsp { } impl LspExt for RustLsp { - fn server_bin_path(&self) -> BoxFuture<'static, Option> { + fn fetch_latest_language_server( + &self, + http: Arc, + ) -> BoxFuture<'static, Result> { async move { let destination_dir_path = dirs::home_dir() .ok_or_else(|| anyhow!("can't determine home directory"))? .join(".zed/rust-analyzer"); fs::create_dir_all(&destination_dir_path).await?; - let mut server_bin_path = Self::download(destination_dir_path.clone()).await.log_err(); + let downloaded_bin_path = Self::download(destination_dir_path.clone(), http).await; + let mut last_cached_bin_path = None; if let Some(mut entries) = fs::read_dir(&destination_dir_path).await.log_err() { while let Some(entry) = entries.next().await { if let Some(entry) = entry.log_err() { let entry_path = entry.path(); - if let Some(downloaded_server_path) = server_bin_path.as_ref() { - if downloaded_server_path != entry_path.as_path() { - fs::remove_file(entry_path).await.log_err(); + if let Ok(downloaded_bin_path) = downloaded_bin_path.as_ref() { + if downloaded_bin_path != entry_path.as_path() { + fs::remove_file(&entry_path).await.log_err(); } - } else { - server_bin_path = Some(entry_path); } + last_cached_bin_path = Some(entry_path); } } } - server_bin_path.ok_or_else(|| anyhow!("could not locate or download server")) + if downloaded_bin_path.is_err() { + if let Some(last_cached_bin_path) = last_cached_bin_path { + return Ok(last_cached_bin_path); + } + } + + downloaded_bin_path } - .log_err() .boxed() } @@ -196,10 +220,10 @@ impl LspExt for RustLsp { } } -pub fn build_language_registry(executor: &Arc) -> LanguageRegistry { +pub fn build_language_registry() -> LanguageRegistry { let mut languages = LanguageRegistry::new(); - languages.add(Arc::new(rust()), executor); - languages.add(Arc::new(markdown()), executor); + languages.add(Arc::new(rust())); + languages.add(Arc::new(markdown())); languages } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 0724f14524..b154241343 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -39,7 +39,7 @@ fn main() { }, ); let (settings_tx, settings) = postage::watch::channel_with(settings); - let languages = Arc::new(language::build_language_registry(&app.background())); + let languages = Arc::new(language::build_language_registry()); languages.set_theme(&settings.borrow().theme.editor.syntax); app.run(move |cx| { diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index 608dea4fa2..8df47ee95e 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -35,7 +35,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { }, Some(tree_sitter_rust::language()), )), - cx.background(), + ); Arc::new(AppState { settings_tx: Arc::new(Mutex::new(settings_tx)),