diff --git a/Cargo.lock b/Cargo.lock index 3fec56a450..76fb2e7cb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5701,6 +5701,7 @@ dependencies = [ "client", "clock", "collections", + "futures", "gpui", "language", "log", diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index c669bc744e..af12cae947 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -410,8 +410,6 @@ impl View for DiagnosticMessage { diagnostic.message.split('\n').next().unwrap().to_string(), theme.diagnostic_message.clone(), ) - .contained() - .with_margin_left(theme.item_spacing) .boxed() } else { Empty::new().boxed() diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 624ae29b86..34276c051e 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -12,10 +12,11 @@ use futures::{ future::{BoxFuture, Shared}, FutureExt, }; -use gpui::{AppContext, Task}; +use gpui::{executor, AppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::Mutex; +use postage::watch; use serde::Deserialize; use std::{ cell::RefCell, @@ -127,18 +128,33 @@ pub struct Grammar { pub(crate) highlight_map: Mutex, } -#[derive(Default)] pub struct LanguageRegistry { languages: Vec>, + pending_lsp_binaries_tx: Arc>>, + pending_lsp_binaries_rx: watch::Receiver, } impl LanguageRegistry { pub fn new() -> Self { - Self::default() + let (pending_lsp_binaries_tx, pending_lsp_binaries_rx) = watch::channel(); + Self { + languages: Default::default(), + pending_lsp_binaries_tx: Arc::new(Mutex::new(pending_lsp_binaries_tx)), + pending_lsp_binaries_rx, + } } - pub fn add(&mut self, language: Arc) { - self.languages.push(language); + pub fn add(&mut self, language: Arc, cx: &executor::Background) { + 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) { @@ -166,6 +182,10 @@ impl LanguageRegistry { .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) }) } + + pub fn pending_lsp_binaries(&self) -> watch::Receiver { + self.pending_lsp_binaries_rx.clone() + } } impl Language { diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 0ae1fbe707..98ce02e2f6 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -22,28 +22,31 @@ fn init_logger() { } } -#[test] -fn test_select_language() { - let registry = LanguageRegistry { - languages: vec![ - Arc::new(Language::new( - LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - )), - 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()), - )), - ], - }; +#[gpui::test] +fn test_select_language(cx: &mut MutableAppContext) { + 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(), + ); // matching file extension assert_eq!( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d1303900cf..7b53eba88b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3069,8 +3069,10 @@ mod tests { .await; let project = Project::test(fs, &mut cx); - project.update(&mut cx, |project, _| { - Arc::get_mut(&mut project.languages).unwrap().add(language); + project.update(&mut cx, |project, cx| { + Arc::get_mut(&mut project.languages) + .unwrap() + .add(language, cx.background()); }); let (tree, _) = project @@ -3215,8 +3217,10 @@ mod tests { .await; let project = Project::test(fs, &mut cx); - project.update(&mut cx, |project, _| { - Arc::get_mut(&mut project.languages).unwrap().add(language); + project.update(&mut cx, |project, cx| { + Arc::get_mut(&mut project.languages) + .unwrap() + .add(language, cx.background()); }); let (tree, _) = project @@ -4108,8 +4112,10 @@ mod tests { .await; let project = Project::test(fs.clone(), &mut cx); - project.update(&mut cx, |project, _| { - Arc::get_mut(&mut project.languages).unwrap().add(language); + project.update(&mut cx, |project, cx| { + Arc::get_mut(&mut project.languages) + .unwrap() + .add(language, cx.background()); }); let (tree, _) = project diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index c6a0ef2be6..db9843fc95 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -2001,9 +2001,8 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake(); - Arc::get_mut(&mut lang_registry) - .unwrap() - .add(Arc::new(Language::new( + Arc::get_mut(&mut lang_registry).unwrap().add( + Arc::new(Language::new( LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], @@ -2011,7 +2010,9 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )), + &cx_a.background(), + ); // Connect to a server as 2 clients. let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; @@ -2232,9 +2233,8 @@ mod tests { }), ..Default::default() }); - Arc::get_mut(&mut lang_registry) - .unwrap() - .add(Arc::new(Language::new( + Arc::get_mut(&mut lang_registry).unwrap().add( + Arc::new(Language::new( LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], @@ -2242,7 +2242,9 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )), + &cx_a.background(), + ); // Connect to a server as 2 clients. let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; @@ -2434,9 +2436,8 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake(); - Arc::get_mut(&mut lang_registry) - .unwrap() - .add(Arc::new(Language::new( + Arc::get_mut(&mut lang_registry).unwrap().add( + Arc::new(Language::new( LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], @@ -2444,7 +2445,9 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )), + &cx_a.background(), + ); // Connect to a server as 2 clients. let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; @@ -2551,9 +2554,8 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake(); - Arc::get_mut(&mut lang_registry) - .unwrap() - .add(Arc::new(Language::new( + Arc::get_mut(&mut lang_registry).unwrap().add( + Arc::new(Language::new( LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], @@ -2561,7 +2563,9 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )), + &cx_a.background(), + ); // Connect to a server as 2 clients. let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; @@ -2699,9 +2703,8 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake(); - Arc::get_mut(&mut lang_registry) - .unwrap() - .add(Arc::new(Language::new( + Arc::get_mut(&mut lang_registry).unwrap().add( + Arc::new(Language::new( LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], @@ -2709,7 +2712,9 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )), + &cx_a.background(), + ); // Connect to a server as 2 clients. let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; @@ -2800,9 +2805,8 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake(); - Arc::get_mut(&mut lang_registry) - .unwrap() - .add(Arc::new(Language::new( + Arc::get_mut(&mut lang_registry).unwrap().add( + Arc::new(Language::new( LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], @@ -2810,7 +2814,9 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )), + &cx_a.background(), + ); // Connect to a server as 2 clients. let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; @@ -3039,9 +3045,8 @@ mod tests { // Set up a fake language server. let (language_server_config, mut fake_language_servers) = LanguageServerConfig::fake(); - Arc::get_mut(&mut lang_registry) - .unwrap() - .add(Arc::new(Language::new( + Arc::get_mut(&mut lang_registry).unwrap().add( + Arc::new(Language::new( LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], @@ -3049,7 +3054,9 @@ mod tests { ..Default::default() }, Some(tree_sitter_rust::language()), - ))); + )), + &cx_a.background(), + ); // Connect to a server as 2 clients. let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; @@ -3845,9 +3852,8 @@ mod tests { }); }); - Arc::get_mut(&mut host_lang_registry) - .unwrap() - .add(Arc::new(Language::new( + Arc::get_mut(&mut host_lang_registry).unwrap().add( + Arc::new(Language::new( LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], @@ -3855,7 +3861,9 @@ mod tests { ..Default::default() }, None, - ))); + )), + &cx.background(), + ); let fs = FakeFs::new(cx.background()); fs.insert_tree( diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 1e63830792..a30dccb1a3 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -141,6 +141,7 @@ pub struct StatusBar { pub item_spacing: f32, pub cursor_position: TextStyle, pub diagnostic_message: TextStyle, + pub lsp_message: TextStyle, } #[derive(Deserialize, Default)] diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index f3ea330ca3..a7a8bfa744 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -19,6 +19,7 @@ project = { path = "../project" } theme = { path = "../theme" } util = { path = "../util" } anyhow = "1.0.38" +futures = "0.3" log = "0.4" parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs new file mode 100644 index 0000000000..e254a05a93 --- /dev/null +++ b/crates/workspace/src/lsp_status.rs @@ -0,0 +1,65 @@ +use crate::{ItemViewHandle, Settings, StatusItemView}; +use futures::StreamExt; +use gpui::{elements::*, Entity, RenderContext, View, ViewContext}; +use language::LanguageRegistry; +use postage::watch; +use std::sync::Arc; + +pub struct LspStatus { + pending_lsp_binaries: usize, + settings_rx: watch::Receiver, +} + +impl LspStatus { + pub fn new( + languages: Arc, + settings_rx: watch::Receiver, + cx: &mut ViewContext, + ) -> Self { + let mut pending_lsp_binaries = languages.pending_lsp_binaries(); + cx.spawn_weak(|this, mut cx| async move { + while let Some(pending_lsp_binaries) = pending_lsp_binaries.next().await { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.pending_lsp_binaries = pending_lsp_binaries; + cx.notify(); + }); + } else { + break; + } + } + }) + .detach(); + Self { + pending_lsp_binaries: 0, + settings_rx, + } + } +} + +impl Entity for LspStatus { + type Event = (); +} + +impl View for LspStatus { + fn ui_name() -> &'static str { + "LspStatus" + } + + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + if self.pending_lsp_binaries == 0 { + Empty::new().boxed() + } else { + let theme = &self.settings_rx.borrow().theme; + Label::new( + "Downloading language servers...".to_string(), + theme.workspace.status_bar.lsp_message.clone(), + ) + .boxed() + } + } +} + +impl StatusItemView for LspStatus { + fn set_active_pane_item(&mut self, _: Option<&dyn ItemViewHandle>, _: &mut ViewContext) {} +} diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index 2d26c33a8a..d4cd939cb8 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -42,17 +42,21 @@ impl View for StatusBar { fn render(&mut self, _: &mut RenderContext) -> ElementBox { let theme = &self.settings.borrow().theme.workspace.status_bar; Flex::row() - .with_children( - self.left_items - .iter() - .map(|i| ChildView::new(i.as_ref()).aligned().boxed()), - ) + .with_children(self.left_items.iter().map(|i| { + ChildView::new(i.as_ref()) + .aligned() + .contained() + .with_margin_right(theme.item_spacing) + .boxed() + })) .with_child(Empty::new().flexible(1., true).boxed()) - .with_children( - self.right_items - .iter() - .map(|i| ChildView::new(i.as_ref()).aligned().boxed()), - ) + .with_children(self.right_items.iter().map(|i| { + ChildView::new(i.as_ref()) + .aligned() + .contained() + .with_margin_left(theme.item_spacing) + .boxed() + })) .contained() .with_style(theme.container) .constrained() diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ddff6f9bf7..c7368ddbe9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,3 +1,4 @@ +pub mod lsp_status; pub mod menu; pub mod pane; pub mod pane_group; diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index a9c500b640..5b9f822bf9 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -77,9 +77,10 @@ border = { width = 1, color = "$border.0", left = true } [workspace.status_bar] padding = { left = 6, right = 6 } height = 24 -item_spacing = 24 +item_spacing = 8 cursor_position = "$text.2" diagnostic_message = "$text.2" +lsp_message = "$text.2" [workspace.toolbar] height = 44 @@ -188,7 +189,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 93cc777bb7..da5fa8105f 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -2,6 +2,7 @@ use anyhow::anyhow; use async_compression::futures::bufread::GzipDecoder; use client::http; use futures::{future::BoxFuture, FutureExt, StreamExt}; +use gpui::executor; pub use language::*; use lazy_static::lazy_static; use regex::Regex; @@ -45,7 +46,7 @@ impl RustLsp { .ok_or_else(|| anyhow!("no release found matching {:?}", release_name))?; let destination_path = destination_dir_path.join(format!("rust-analyzer-{}", release.name)); - if fs::metadata(&destination_path).await.is_ok() { + if fs::metadata(&destination_path).await.is_err() { let response = client .get(&asset.browser_download_url) .send() @@ -195,10 +196,10 @@ impl LspExt for RustLsp { } } -pub fn build_language_registry() -> LanguageRegistry { - let mut languages = LanguageRegistry::default(); - languages.add(Arc::new(rust())); - languages.add(Arc::new(markdown())); +pub fn build_language_registry(executor: &Arc) -> LanguageRegistry { + let mut languages = LanguageRegistry::new(); + languages.add(Arc::new(rust()), executor); + languages.add(Arc::new(markdown()), executor); languages } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b154241343..0724f14524 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()); + let languages = Arc::new(language::build_language_registry(&app.background())); 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 18819b25a6..608dea4fa2 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -26,14 +26,17 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { let client = Client::new(http.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); let mut languages = LanguageRegistry::new(); - languages.add(Arc::new(language::Language::new( - language::LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ))); + languages.add( + Arc::new(language::Language::new( + language::LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + )), + cx.background(), + ); Arc::new(AppState { settings_tx: Arc::new(Mutex::new(settings_tx)), settings, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4c98e0fa98..7178ff44eb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -97,11 +97,19 @@ pub fn build_workspace( cx, ) }); + let lsp_status = cx.add_view(|cx| { + workspace::lsp_status::LspStatus::new( + app_state.languages.clone(), + app_state.settings.clone(), + cx, + ) + }); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new(app_state.settings.clone())); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(diagnostic_message, cx); + status_bar.add_left_item(lsp_status, cx); status_bar.add_right_item(cursor_position, cx); });