diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50d817637e..4050d4e120 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,11 +34,6 @@ jobs: with: clean: false - - name: Download rust-analyzer - run: | - script/download-rust-analyzer - echo "$PWD/vendor/bin" >> $GITHUB_PATH - - name: Run tests run: cargo test --workspace --no-fail-fast @@ -69,14 +64,11 @@ jobs: uses: actions/checkout@v2 with: clean: false - + - name: Validate version if: ${{ startsWith(github.ref, 'refs/tags/v') }} run: script/validate-version - - name: Download rust-analyzer - run: script/download-rust-analyzer - - name: Create app bundle run: script/bundle diff --git a/Cargo.lock b/Cargo.lock index 9bc19c7ad9..c3e6f6f884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,6 +178,17 @@ dependencies = [ "syn", ] +[[package]] +name = "async-broadcast" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90622698a1218e0b2fb846c97b5f19a0831f6baddee73d9454156365ccfa473b" +dependencies = [ + "easy-parallel", + "event-listener", + "futures-core", +] + [[package]] name = "async-channel" version = "1.6.1" @@ -1926,9 +1937,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" @@ -2619,7 +2630,9 @@ name = "language" version = "0.1.0" dependencies = [ "anyhow", + "async-broadcast", "async-trait", + "client", "clock", "collections", "ctor", @@ -5701,6 +5714,7 @@ dependencies = [ "client", "clock", "collections", + "futures", "gpui", "language", "log", @@ -5741,6 +5755,7 @@ name = "zed" version = "0.15.2" dependencies = [ "anyhow", + "async-compression", "async-recursion", "async-trait", "chat_panel", 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/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/Cargo.toml b/crates/language/Cargo.toml index eef1a01054..ec90b9c76a 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" } @@ -28,6 +30,7 @@ text = { path = "../text" } theme = { path = "../theme" } util = { path = "../util" } anyhow = "1.0.38" +async-broadcast = "0.3.4" async-trait = "0.1" futures = "0.3" lazy_static = "1.4" @@ -44,6 +47,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 73de5af12c..9f685befff 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -7,15 +7,27 @@ pub mod proto; mod tests; use anyhow::{anyhow, Result}; +use client::http::{self, HttpClient}; use collections::HashSet; -use gpui::AppContext; +use futures::{ + future::{BoxFuture, Shared}, + FutureExt, TryFutureExt, +}; +use gpui::{AppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Deserialize; -use std::{cell::RefCell, ops::Range, path::Path, str, sync::Arc}; +use std::{ + cell::RefCell, + ops::Range, + path::{Path, PathBuf}, + str, + sync::Arc, +}; use theme::SyntaxTheme; use tree_sitter::{self, Query}; +use util::ResultExt; #[cfg(any(test, feature = "test-support"))] use futures::channel::mpsc; @@ -47,7 +59,23 @@ pub trait ToLspPosition { fn to_lsp_position(self) -> lsp::Position; } -pub trait LspPostProcessor: 'static + Send + Sync { +pub struct LspBinaryVersion { + pub name: String, + pub url: http::Url, +} + +pub trait LspExt: 'static + Send + Sync { + fn fetch_latest_server_version( + &self, + http: Arc, + ) -> BoxFuture<'static, Result>; + fn fetch_server_binary( + &self, + version: LspBinaryVersion, + http: Arc, + download_dir: Arc, + ) -> BoxFuture<'static, Result>; + fn cached_server_binary(&self, download_dir: Arc) -> BoxFuture<'static, Option>; fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams); fn label_for_completion( &self, @@ -77,7 +105,6 @@ pub struct LanguageConfig { #[derive(Default, Deserialize)] pub struct LanguageServerConfig { - pub binary: String, pub disk_based_diagnostic_sources: HashSet, pub disk_based_diagnostics_progress_token: Option, #[cfg(any(test, feature = "test-support"))] @@ -103,7 +130,8 @@ pub struct BracketPair { pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, - pub(crate) lsp_post_processor: Option>, + pub(crate) lsp_ext: Option>, + lsp_binary_path: Mutex>>>>>, } pub struct Grammar { @@ -115,18 +143,35 @@ pub struct Grammar { pub(crate) highlight_map: Mutex, } -#[derive(Default)] +#[derive(Clone)] +pub enum LanguageServerBinaryStatus { + CheckingForUpdate, + Downloading, + Downloaded, + Cached, + Failed, +} + pub struct LanguageRegistry { languages: Vec>, + language_server_download_dir: Option>, + lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, + lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)>, } impl LanguageRegistry { pub fn new() -> Self { - Self::default() + let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16); + Self { + language_server_download_dir: None, + languages: Default::default(), + lsp_binary_statuses_tx, + lsp_binary_statuses_rx, + } } pub fn add(&mut self, language: Arc) { - self.languages.push(language); + self.languages.push(language.clone()); } pub fn set_theme(&self, theme: &SyntaxTheme) { @@ -135,6 +180,10 @@ impl LanguageRegistry { } } + pub fn set_language_server_download_dir(&mut self, path: impl Into>) { + self.language_server_download_dir = Some(path.into()); + } + pub fn get_language(&self, name: &str) -> Option<&Arc> { self.languages .iter() @@ -154,6 +203,140 @@ impl LanguageRegistry { .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) }) } + + 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 download_dir = self + .language_server_download_dir + .clone() + .ok_or_else(|| anyhow!("language server download directory has not been assigned")) + .log_err()?; + + let lsp_ext = language.lsp_ext.clone()?; + let background = cx.background().clone(); + let server_binary_path = { + Some( + language + .lsp_binary_path + .lock() + .get_or_insert_with(|| { + get_server_binary_path( + lsp_ext, + language.clone(), + http_client, + download_dir, + self.lsp_binary_statuses_tx.clone(), + ) + .map_err(Arc::new) + .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 language_server_binary_statuses( + &self, + ) -> async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)> { + self.lsp_binary_statuses_rx.clone() + } +} + +async fn get_server_binary_path( + lsp_ext: Arc, + language: Arc, + http_client: Arc, + download_dir: Arc, + statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, +) -> Result { + let path = fetch_latest_server_binary_path( + lsp_ext.clone(), + language.clone(), + http_client, + download_dir.clone(), + statuses.clone(), + ) + .await; + if path.is_err() { + if let Some(cached_path) = lsp_ext.cached_server_binary(download_dir).await { + statuses + .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) + .await?; + return Ok(cached_path); + } else { + statuses + .broadcast((language.clone(), LanguageServerBinaryStatus::Failed)) + .await?; + } + } + path +} + +async fn fetch_latest_server_binary_path( + lsp_ext: Arc, + language: Arc, + http_client: Arc, + download_dir: Arc, + lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, +) -> Result { + lsp_binary_statuses_tx + .broadcast(( + language.clone(), + LanguageServerBinaryStatus::CheckingForUpdate, + )) + .await?; + let version_info = lsp_ext + .fetch_latest_server_version(http_client.clone()) + .await?; + lsp_binary_statuses_tx + .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading)) + .await?; + let path = lsp_ext + .fetch_server_binary(version_info, http_client, download_dir) + .await?; + lsp_binary_statuses_tx + .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) + .await?; + Ok(path) } impl Language { @@ -170,7 +353,8 @@ impl Language { highlight_map: Default::default(), }) }), - lsp_post_processor: None, + lsp_ext: None, + lsp_binary_path: Default::default(), } } @@ -214,8 +398,8 @@ impl Language { Ok(self) } - pub fn with_lsp_post_processor(mut self, processor: impl LspPostProcessor) -> Self { - self.lsp_post_processor = Some(Box::new(processor)); + pub fn with_lsp_ext(mut self, lsp_ext: impl LspExt) -> Self { + self.lsp_ext = Some(Arc::new(lsp_ext)); self } @@ -227,50 +411,6 @@ impl Language { self.config.line_comment.as_deref() } - pub fn start_server( - &self, - root_path: &Path, - cx: &AppContext, - ) -> Result>> { - if let Some(config) = &self.config.language_server { - #[cfg(any(test, feature = "test-support"))] - 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 Ok(Some(server.clone())); - } - - const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE"); - let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? { - cx.platform() - .path_for_resource(Some(&config.binary), None)? - } else { - Path::new(&config.binary).to_path_buf() - }; - lsp::LanguageServer::new(&binary_path, root_path, cx.background().clone()).map(Some) - } else { - Ok(None) - } - } - pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet> { self.config .language_server @@ -286,7 +426,7 @@ impl Language { } pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) { - if let Some(processor) = self.lsp_post_processor.as_ref() { + if let Some(processor) = self.lsp_ext.as_ref() { processor.process_diagnostics(diagnostics); } } @@ -295,7 +435,7 @@ impl Language { &self, completion: &lsp::CompletionItem, ) -> Option { - self.lsp_post_processor + self.lsp_ext .as_ref()? .label_for_completion(completion, self) } diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 0ae1fbe707..1adcca91a5 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -22,28 +22,25 @@ fn init_logger() { } } -#[test] +#[gpui::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()), - )), - ], - }; + 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()), + ))); + 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/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 0281e8cd8b..93df14f89d 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -700,8 +700,6 @@ impl FakeLanguageServer { mod tests { use super::*; use gpui::TestAppContext; - use unindent::Unindent; - use util::test::temp_tree; #[ctor::ctor] fn init_logger() { @@ -710,64 +708,6 @@ mod tests { } } - #[gpui::test] - async fn test_rust_analyzer(cx: TestAppContext) { - let lib_source = r#" - fn fun() { - let hello = "world"; - } - "# - .unindent(); - let root_dir = temp_tree(json!({ - "Cargo.toml": r#" - [package] - name = "temp" - version = "0.1.0" - edition = "2018" - "#.unindent(), - "src": { - "lib.rs": &lib_source - } - })); - let lib_file_uri = Url::from_file_path(root_dir.path().join("src/lib.rs")).unwrap(); - - let server = - LanguageServer::new(Path::new("rust-analyzer"), root_dir.path(), cx.background()) - .unwrap(); - server.next_idle_notification().await; - - server - .notify::(DidOpenTextDocumentParams { - text_document: TextDocumentItem::new( - lib_file_uri.clone(), - "rust".to_string(), - 0, - lib_source, - ), - }) - .await - .unwrap(); - - let hover = server - .request::(HoverParams { - text_document_position_params: TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(lib_file_uri), - position: Position::new(1, 21), - }, - work_done_progress_params: Default::default(), - }) - .await - .unwrap() - .unwrap(); - assert_eq!( - hover.contents, - HoverContents::Markup(MarkupContent { - kind: MarkupKind::PlainText, - value: "&str".to_string() - }) - ); - } - #[gpui::test] async fn test_fake(cx: TestAppContext) { let (server, mut fake) = LanguageServer::fake(cx.background()); @@ -828,19 +768,6 @@ mod tests { fake.receive_notification::().await; } - impl LanguageServer { - async fn next_idle_notification(self: &Arc) { - let (tx, rx) = channel::unbounded(); - let _subscription = - self.on_notification::(move |params| { - if params.quiescent { - tx.try_send(()).unwrap(); - } - }); - let _ = rx.recv().await; - } - } - pub enum ServerStatusNotification {} impl notification::Notification for ServerStatusNotification { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9a231b707b..c1ef8fdfcd 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; -use futures::Future; +use futures::{future::Shared, Future, FutureExt}; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, @@ -39,6 +39,8 @@ pub struct Project { active_entry: Option, languages: Arc, language_servers: HashMap<(WorktreeId, String), Arc>, + started_language_servers: + HashMap<(WorktreeId, String), Shared>>>>, client: Arc, user_store: ModelHandle, fs: Arc, @@ -258,6 +260,7 @@ impl Project { fs, language_servers_with_diagnostics_running: 0, language_servers: Default::default(), + started_language_servers: Default::default(), } }) } @@ -309,6 +312,7 @@ impl Project { }, language_servers_with_diagnostics_running: 0, language_servers: Default::default(), + started_language_servers: Default::default(), }; for worktree in worktrees { this.add_worktree(&worktree, cx); @@ -776,7 +780,7 @@ impl Project { }; // If the buffer has a language, set it and start/assign the language server - if let Some(language) = self.languages.select_language(&full_path) { + if let Some(language) = self.languages.select_language(&full_path).cloned() { buffer.update(cx, |buffer, cx| { buffer.set_language(Some(language.clone()), cx); }); @@ -786,24 +790,20 @@ impl Project { if let Some(local_worktree) = worktree.and_then(|w| w.read(cx).as_local()) { let worktree_id = local_worktree.id(); let worktree_abs_path = local_worktree.abs_path().clone(); + let buffer = buffer.downgrade(); + let language_server = + self.start_language_server(worktree_id, worktree_abs_path, language, cx); - let language_server = match self - .language_servers - .entry((worktree_id, language.name().to_string())) - { - hash_map::Entry::Occupied(e) => Some(e.get().clone()), - hash_map::Entry::Vacant(e) => Self::start_language_server( - self.client.clone(), - language.clone(), - &worktree_abs_path, - cx, - ) - .map(|server| e.insert(server).clone()), - }; - - buffer.update(cx, |buffer, cx| { - buffer.set_language_server(language_server, cx); - }); + cx.spawn_weak(|_, mut cx| async move { + if let Some(language_server) = language_server.await { + if let Some(buffer) = buffer.upgrade(&cx) { + buffer.update(&mut cx, |buffer, cx| { + buffer.set_language_server(Some(language_server), cx); + }); + } + } + }) + .detach(); } } @@ -819,116 +819,151 @@ impl Project { } fn start_language_server( - rpc: Arc, + &mut self, + worktree_id: WorktreeId, + worktree_path: Arc, language: Arc, - worktree_path: &Path, cx: &mut ModelContext, - ) -> Option> { + ) -> Shared>>> { enum LspEvent { DiagnosticsStart, DiagnosticsUpdate(lsp::PublishDiagnosticsParams), DiagnosticsFinish, } - let language_server = language - .start_server(worktree_path, cx) - .log_err() - .flatten()?; - let disk_based_sources = language - .disk_based_diagnostic_sources() - .cloned() - .unwrap_or_default(); - let disk_based_diagnostics_progress_token = - language.disk_based_diagnostics_progress_token().cloned(); - let has_disk_based_diagnostic_progress_token = - disk_based_diagnostics_progress_token.is_some(); - let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); - - // Listen for `PublishDiagnostics` notifications. - language_server - .on_notification::({ - let diagnostics_tx = diagnostics_tx.clone(); - move |params| { - if !has_disk_based_diagnostic_progress_token { - block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok(); + let key = (worktree_id, language.name().to_string()); + self.started_language_servers + .entry(key.clone()) + .or_insert_with(|| { + 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()?; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, _| { + this.language_servers.insert(key, language_server.clone()); + }); } - block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))).ok(); - if !has_disk_based_diagnostic_progress_token { - block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok(); - } - } - }) - .detach(); - // Listen for `Progress` notifications. Send an event when the language server - // transitions between running jobs and not running any jobs. - let mut running_jobs_for_this_server: i32 = 0; - language_server - .on_notification::(move |params| { - let token = match params.token { - lsp::NumberOrString::Number(_) => None, - lsp::NumberOrString::String(token) => Some(token), - }; + let disk_based_sources = language + .disk_based_diagnostic_sources() + .cloned() + .unwrap_or_default(); + let disk_based_diagnostics_progress_token = + language.disk_based_diagnostics_progress_token().cloned(); + let has_disk_based_diagnostic_progress_token = + disk_based_diagnostics_progress_token.is_some(); + let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); - if token == disk_based_diagnostics_progress_token { - match params.value { - lsp::ProgressParamsValue::WorkDone(progress) => match progress { - lsp::WorkDoneProgress::Begin(_) => { - running_jobs_for_this_server += 1; - if running_jobs_for_this_server == 1 { + // Listen for `PublishDiagnostics` notifications. + language_server + .on_notification::({ + let diagnostics_tx = diagnostics_tx.clone(); + move |params| { + if !has_disk_based_diagnostic_progress_token { block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok(); } - } - lsp::WorkDoneProgress::End(_) => { - running_jobs_for_this_server -= 1; - if running_jobs_for_this_server == 0 { + block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))) + .ok(); + if !has_disk_based_diagnostic_progress_token { block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok(); } } - _ => {} - }, - } - } + }) + .detach(); + + // Listen for `Progress` notifications. Send an event when the language server + // transitions between running jobs and not running any jobs. + let mut running_jobs_for_this_server: i32 = 0; + language_server + .on_notification::(move |params| { + let token = match params.token { + lsp::NumberOrString::Number(_) => None, + lsp::NumberOrString::String(token) => Some(token), + }; + + if token == disk_based_diagnostics_progress_token { + match params.value { + lsp::ProgressParamsValue::WorkDone(progress) => { + match progress { + lsp::WorkDoneProgress::Begin(_) => { + running_jobs_for_this_server += 1; + if running_jobs_for_this_server == 1 { + block_on( + diagnostics_tx + .send(LspEvent::DiagnosticsStart), + ) + .ok(); + } + } + lsp::WorkDoneProgress::End(_) => { + running_jobs_for_this_server -= 1; + if running_jobs_for_this_server == 0 { + block_on( + diagnostics_tx + .send(LspEvent::DiagnosticsFinish), + ) + .ok(); + } + } + _ => {} + } + } + } + } + }) + .detach(); + + // Process all the LSP events. + cx.spawn(|mut cx| async move { + while let Ok(message) = diagnostics_rx.recv().await { + let this = this.upgrade(&cx)?; + match message { + LspEvent::DiagnosticsStart => { + this.update(&mut cx, |this, cx| { + this.disk_based_diagnostics_started(cx); + if let Some(project_id) = this.remote_id() { + rpc.send(proto::DiskBasedDiagnosticsUpdating { + project_id, + }) + .log_err(); + } + }); + } + LspEvent::DiagnosticsUpdate(mut params) => { + language.process_diagnostics(&mut params); + this.update(&mut cx, |this, cx| { + this.update_diagnostics(params, &disk_based_sources, cx) + .log_err(); + }); + } + LspEvent::DiagnosticsFinish => { + this.update(&mut cx, |this, cx| { + this.disk_based_diagnostics_finished(cx); + if let Some(project_id) = this.remote_id() { + rpc.send(proto::DiskBasedDiagnosticsUpdated { + project_id, + }) + .log_err(); + } + }); + } + } + } + Some(()) + }) + .detach(); + + Some(language_server) + }) + .shared() }) - .detach(); - - // Process all the LSP events. - cx.spawn_weak(|this, mut cx| async move { - while let Ok(message) = diagnostics_rx.recv().await { - let this = this.upgrade(&cx)?; - match message { - LspEvent::DiagnosticsStart => { - this.update(&mut cx, |this, cx| { - this.disk_based_diagnostics_started(cx); - if let Some(project_id) = this.remote_id() { - rpc.send(proto::DiskBasedDiagnosticsUpdating { project_id }) - .log_err(); - } - }); - } - LspEvent::DiagnosticsUpdate(mut params) => { - language.process_diagnostics(&mut params); - this.update(&mut cx, |this, cx| { - this.update_diagnostics(params, &disk_based_sources, cx) - .log_err(); - }); - } - LspEvent::DiagnosticsFinish => { - this.update(&mut cx, |this, cx| { - this.disk_based_diagnostics_finished(cx); - if let Some(project_id) = this.remote_id() { - rpc.send(proto::DiskBasedDiagnosticsUpdated { project_id }) - .log_err(); - } - }); - } - } - } - Some(()) - }) - .detach(); - - Some(language_server) + .clone() } pub fn update_diagnostics( @@ -2857,8 +2892,6 @@ impl Entity for Project { &mut self, _: &mut MutableAppContext, ) -> Option>>> { - use futures::FutureExt; - let shutdown_futures = self .language_servers .drain() diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index c6a0ef2be6..0bfb918b45 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()), - ))); + )), + + ); // 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()), - ))); + )), + + ); // 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()), - ))); + )), + + ); // 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()), - ))); + )), + + ); // 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()), - ))); + )), + + ); // 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()), - ))); + )), + + ); // 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()), - ))); + )), + + ); // 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, - ))); + )), + + ); 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..093f10b143 --- /dev/null +++ b/crates/workspace/src/lsp_status.rs @@ -0,0 +1,137 @@ +use crate::{ItemViewHandle, Settings, StatusItemView}; +use futures::StreamExt; +use gpui::{ + action, elements::*, platform::CursorStyle, Entity, MutableAppContext, RenderContext, View, + ViewContext, +}; +use language::{LanguageRegistry, LanguageServerBinaryStatus}; +use postage::watch; +use std::sync::Arc; + +action!(DismissErrorMessage); + +pub struct LspStatus { + settings_rx: watch::Receiver, + checking_for_update: Vec, + downloading: Vec, + failed: Vec, +} + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(LspStatus::dismiss_error_message); +} + +impl LspStatus { + pub fn new( + languages: Arc, + settings_rx: watch::Receiver, + cx: &mut ViewContext, + ) -> Self { + let mut status_events = languages.language_server_binary_statuses(); + cx.spawn_weak(|this, mut cx| async move { + while let Some((language, event)) = status_events.next().await { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + for vector in [ + &mut this.checking_for_update, + &mut this.downloading, + &mut this.failed, + ] { + vector.retain(|name| name != language.name()); + } + + match event { + LanguageServerBinaryStatus::CheckingForUpdate => { + this.checking_for_update.push(language.name().to_string()); + } + LanguageServerBinaryStatus::Downloading => { + this.downloading.push(language.name().to_string()); + } + LanguageServerBinaryStatus::Failed => { + this.failed.push(language.name().to_string()); + } + LanguageServerBinaryStatus::Downloaded + | LanguageServerBinaryStatus::Cached => {} + } + + cx.notify(); + }); + } else { + break; + } + } + }) + .detach(); + Self { + settings_rx, + checking_for_update: Default::default(), + downloading: Default::default(), + failed: Default::default(), + } + } + + fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext) { + self.failed.clear(); + cx.notify(); + } +} + +impl Entity for LspStatus { + type Event = (); +} + +impl View for LspStatus { + fn ui_name() -> &'static str { + "LspStatus" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + let theme = &self.settings_rx.borrow().theme; + if !self.downloading.is_empty() { + Label::new( + format!( + "Downloading {} language server{}...", + self.downloading.join(", "), + if self.downloading.len() > 1 { "s" } else { "" } + ), + theme.workspace.status_bar.lsp_message.clone(), + ) + .boxed() + } else if !self.checking_for_update.is_empty() { + Label::new( + format!( + "Checking for updates to {} language server{}...", + self.checking_for_update.join(", "), + if self.checking_for_update.len() > 1 { + "s" + } else { + "" + } + ), + theme.workspace.status_bar.lsp_message.clone(), + ) + .boxed() + } else if !self.failed.is_empty() { + MouseEventHandler::new::(0, cx, |_, _| { + Label::new( + format!( + "Failed to download {} language server{}. Click to dismiss.", + self.failed.join(", "), + if self.failed.len() > 1 { "s" } else { "" } + ), + theme.workspace.status_bar.lsp_message.clone(), + ) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(|cx| cx.dispatch_action(DismissErrorMessage)) + .boxed() + } else { + Empty::new().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/Cargo.toml b/crates/zed/Cargo.toml index 3f4cf8a96f..cd581cb90d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -55,6 +55,7 @@ theme_selector = { path = "../theme_selector" } util = { path = "../util" } workspace = { path = "../workspace" } anyhow = "1.0.38" +async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } async-recursion = "0.3" async-trait = "0.1" crossbeam-channel = "0.5.0" 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/languages/rust/config.toml b/crates/zed/languages/rust/config.toml index 426dcc2b48..e4bf50a929 100644 --- a/crates/zed/languages/rust/config.toml +++ b/crates/zed/languages/rust/config.toml @@ -11,6 +11,5 @@ brackets = [ ] [language_server] -binary = "rust-analyzer" disk_based_diagnostic_sources = ["rustc"] disk_based_diagnostics_progress_token = "rustAnalyzer/cargo check" diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index a1ad424c11..748e82ffe8 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -1,17 +1,140 @@ +use anyhow::{anyhow, Result}; +use async_compression::futures::bufread::GzipDecoder; +use client::http::{self, HttpClient, Method}; +use futures::{future::BoxFuture, FutureExt, StreamExt}; pub use language::*; use lazy_static::lazy_static; use regex::Regex; use rust_embed::RustEmbed; -use std::borrow::Cow; -use std::{str, sync::Arc}; +use serde::Deserialize; +use smol::fs::{self, File}; +use std::{ + borrow::Cow, + env::consts, + path::{Path, PathBuf}, + str, + sync::Arc, +}; +use util::{ResultExt, TryFutureExt}; #[derive(RustEmbed)] #[folder = "languages"] struct LanguageDir; -struct RustPostProcessor; +struct RustLsp; + +#[derive(Deserialize)] +struct GithubRelease { + name: String, + assets: Vec, +} + +#[derive(Deserialize)] +struct GithubReleaseAsset { + name: String, + browser_download_url: http::Url, +} + +impl LspExt for RustLsp { + fn fetch_latest_server_version( + &self, + http: Arc, + ) -> BoxFuture<'static, Result> { + async move { + 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 fetching latest release: {}", err))? + .body_json::() + .await + .map_err(|err| anyhow!("error parsing latest release: {}", err))?; + let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no release found matching {:?}", asset_name))?; + Ok(LspBinaryVersion { + name: release.name, + url: asset.browser_download_url.clone(), + }) + } + .boxed() + } + + fn fetch_server_binary( + &self, + version: LspBinaryVersion, + http: Arc, + download_dir: Arc, + ) -> BoxFuture<'static, Result> { + async move { + let destination_dir_path = download_dir.join("rust-analyzer"); + fs::create_dir_all(&destination_dir_path).await?; + let destination_path = + destination_dir_path.join(format!("rust-analyzer-{}", version.name)); + + if fs::metadata(&destination_path).await.is_err() { + let response = http + .send( + surf::RequestBuilder::new(Method::Get, version.url) + .middleware(surf::middleware::Redirect::default()) + .build(), + ) + .await + .map_err(|err| anyhow!("error downloading release: {}", err))?; + let decompressed_bytes = GzipDecoder::new(response); + let mut file = File::create(&destination_path).await?; + futures::io::copy(decompressed_bytes, &mut file).await?; + fs::set_permissions( + &destination_path, + ::from_mode(0o755), + ) + .await?; + + 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 entry_path.as_path() != destination_path { + fs::remove_file(&entry_path).await.log_err(); + } + } + } + } + } + + Ok(destination_path) + } + .boxed() + } + + fn cached_server_binary(&self, download_dir: Arc) -> BoxFuture<'static, Option> { + async move { + let destination_dir_path = download_dir.join("rust-analyzer"); + fs::create_dir_all(&destination_dir_path).await?; + + let mut last = None; + let mut entries = fs::read_dir(&destination_dir_path).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + last.ok_or_else(|| anyhow!("no cached binary")) + } + .log_err() + .boxed() + } -impl LspPostProcessor for RustPostProcessor { fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { lazy_static! { static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap(); @@ -113,7 +236,12 @@ impl LspPostProcessor for RustPostProcessor { } pub fn build_language_registry() -> LanguageRegistry { - let mut languages = LanguageRegistry::default(); + let mut languages = LanguageRegistry::new(); + languages.set_language_server_download_dir( + dirs::home_dir() + .expect("failed to determine home directory") + .join(".zed"), + ); languages.add(Arc::new(rust())); languages.add(Arc::new(markdown())); languages @@ -131,7 +259,7 @@ fn rust() -> Language { .unwrap() .with_outline_query(load_query("rust/outline.scm").as_ref()) .unwrap() - .with_lsp_post_processor(RustPostProcessor) + .with_lsp_ext(RustLsp) } fn markdown() -> Language { @@ -153,7 +281,7 @@ fn load_query(path: &str) -> Cow<'static, str> { mod tests { use super::*; use gpui::color::Color; - use language::LspPostProcessor; + use language::LspExt; use theme::SyntaxTheme; #[test] @@ -180,7 +308,7 @@ mod tests { }, ], }; - RustPostProcessor.process_diagnostics(&mut params); + RustLsp.process_diagnostics(&mut params); assert_eq!(params.diagnostics[0].message, "use of moved value `a`"); diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index 18819b25a6..8df47ee95e 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()), + )), + + ); 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..cdc24e5a52 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -43,6 +43,8 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { } }); + workspace::lsp_status::init(cx); + cx.add_bindings(vec![ Binding::new("cmd-=", AdjustBufferFontSize(1.), None), Binding::new("cmd--", AdjustBufferFontSize(-1.), None), @@ -97,11 +99,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); }); diff --git a/script/bundle b/script/bundle index ecc295de9a..bcaa68c1e9 100755 --- a/script/bundle +++ b/script/bundle @@ -21,9 +21,6 @@ cargo build --release --target aarch64-apple-darwin # Replace the bundle's binary with a "fat binary" that combines the two architecture-specific binaries lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed -# Bundle rust-analyzer -cp vendor/bin/rust-analyzer target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/Resources/ - # Sign the app bundle with an ad-hoc signature so it runs on the M1. We need a real certificate but this works for now. if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then echo "Signing bundle with Apple-issued certificate" @@ -34,7 +31,6 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR security import /tmp/zed-certificate.p12 -k zed.keychain -P $MACOS_CERTIFICATE_PASSWORD -T /usr/bin/codesign rm /tmp/zed-certificate.p12 security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $MACOS_CERTIFICATE_PASSWORD zed.keychain - /usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/Resources/rust-analyzer -v /usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." target/x86_64-apple-darwin/release/bundle/osx/Zed.app -v security default-keychain -s login.keychain else diff --git a/script/download-rust-analyzer b/script/download-rust-analyzer deleted file mode 100755 index 8c366c5609..0000000000 --- a/script/download-rust-analyzer +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -set -e - -export RUST_ANALYZER_URL="https://github.com/rust-analyzer/rust-analyzer/releases/download/2022-01-24/" - -function download { - local filename="rust-analyzer-$1" - curl -L $RUST_ANALYZER_URL/$filename.gz | gunzip > vendor/bin/$filename - chmod +x vendor/bin/$filename -} - -mkdir -p vendor/bin -download "x86_64-apple-darwin" -download "aarch64-apple-darwin" - -cd vendor/bin -lipo -create rust-analyzer-* -output rust-analyzer -rm rust-analyzer-*