diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9741f167841407aae26da7daac22cdd49bc0e087..95609bf43f36fe5d448a1cbf45e3803442c41f4a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -51,6 +51,8 @@ pub struct Project { languages: Arc, language_servers: HashMap<(WorktreeId, Arc), Arc>, started_language_servers: HashMap<(WorktreeId, Arc), Task>>>, + pending_language_server_work: HashMap<(usize, String), LspWorkProgress>, + next_language_server_id: usize, client: Arc, user_store: ModelHandle, fs: Arc, @@ -120,8 +122,7 @@ enum LspEvent { }, WorkProgress { token: String, - message: Option, - percentage: Option, + progress: LspWorkProgress, }, WorkEnd { token: String, @@ -129,6 +130,12 @@ enum LspEvent { DiagnosticsUpdate(lsp::PublishDiagnosticsParams), } +#[derive(Clone, Default)] +pub struct LspWorkProgress { + pub message: Option, + pub percentage: Option, +} + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct ProjectPath { pub worktree_id: WorktreeId, @@ -317,6 +324,8 @@ impl Project { language_servers_with_diagnostics_running: 0, language_servers: Default::default(), started_language_servers: Default::default(), + pending_language_server_work: Default::default(), + next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), } }) @@ -386,6 +395,8 @@ impl Project { language_servers_with_diagnostics_running: 0, language_servers: Default::default(), started_language_servers: Default::default(), + pending_language_server_work: Default::default(), + next_language_server_id: 0, opened_buffers: Default::default(), buffer_snapshots: Default::default(), nonce: StdRng::from_entropy().gen(), @@ -1172,6 +1183,7 @@ impl Project { self.started_language_servers .entry(key.clone()) .or_insert_with(|| { + let server_id = post_inc(&mut self.next_language_server_id); let language_server = self.languages.start_language_server( language.clone(), worktree_path, @@ -1213,8 +1225,12 @@ impl Project { lsp_events_tx .try_send(LspEvent::WorkProgress { token, - message: report.message, - percentage: report.percentage.map(|p| p as usize), + progress: LspWorkProgress { + message: report.message, + percentage: report + .percentage + .map(|p| p as usize), + }, }) .ok(); } @@ -1233,7 +1249,7 @@ impl Project { while let Ok(event) = lsp_events_rx.recv().await { let this = this.upgrade(&cx)?; this.update(&mut cx, |this, cx| { - this.on_local_lsp_event(event, &language, cx) + this.on_local_lsp_event(server_id, event, &language, cx) }); } Some(()) @@ -1309,6 +1325,7 @@ impl Project { fn on_local_lsp_event( &mut self, + language_server_id: usize, event: LspEvent, language: &Arc, cx: &mut ModelContext, @@ -1318,43 +1335,53 @@ impl Project { LspEvent::WorkStart { token } => { if Some(&token) == disk_diagnostics_token { self.disk_based_diagnostics_started(cx); - self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( - proto::LspDiskBasedDiagnosticsUpdating {}, - )); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + ), + ); } else { - self.on_lsp_work_start(token.clone(), cx); - self.send_lsp_event(proto::lsp_event::Variant::WorkStart( - proto::LspWorkStart { token }, - )); + self.on_lsp_work_start(language_server_id, token.clone(), cx); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::WorkStart(proto::LspWorkStart { token }), + ); } } - LspEvent::WorkProgress { - token, - message, - percentage, - } => { + LspEvent::WorkProgress { token, progress } => { if Some(&token) != disk_diagnostics_token { - self.on_lsp_work_progress(token.clone(), message.clone(), percentage, cx); - self.send_lsp_event(proto::lsp_event::Variant::WorkProgress( - proto::LspWorkProgress { + self.on_lsp_work_progress( + language_server_id, + token.clone(), + progress.clone(), + cx, + ); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::WorkProgress(proto::LspWorkProgress { token, - message, - percentage: percentage.map(|p| p as u32), - }, - )); + message: progress.message, + percentage: progress.percentage.map(|p| p as u32), + }), + ); } } LspEvent::WorkEnd { token } => { if Some(&token) == disk_diagnostics_token { self.disk_based_diagnostics_finished(cx); - self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( - proto::LspDiskBasedDiagnosticsUpdated {}, - )); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + ), + ); } else { - self.on_lsp_work_end(token.clone(), cx); - self.send_lsp_event(proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { - token, - })); + self.on_lsp_work_end(language_server_id, token.clone(), cx); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { token }), + ); } } LspEvent::DiagnosticsUpdate(mut params) => { @@ -1362,9 +1389,12 @@ impl Project { if disk_diagnostics_token.is_none() { self.disk_based_diagnostics_started(cx); - self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( - proto::LspDiskBasedDiagnosticsUpdating {}, - )); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + ), + ); } self.update_diagnostics( params, @@ -1376,38 +1406,74 @@ impl Project { .log_err(); if disk_diagnostics_token.is_none() { self.disk_based_diagnostics_finished(cx); - self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( - proto::LspDiskBasedDiagnosticsUpdated {}, - )); + self.broadcast_lsp_event( + language_server_id, + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + ), + ); } } } } - fn on_lsp_work_start(&mut self, token: String, cx: &mut ModelContext) {} + fn on_lsp_work_start( + &mut self, + language_server_id: usize, + token: String, + cx: &mut ModelContext, + ) { + self.pending_language_server_work.insert( + (language_server_id, token), + LspWorkProgress { + message: None, + percentage: None, + }, + ); + cx.notify(); + } fn on_lsp_work_progress( &mut self, + language_server_id: usize, token: String, - message: Option, - percentage: Option, + progress: LspWorkProgress, cx: &mut ModelContext, ) { + self.pending_language_server_work + .insert((language_server_id, token), progress); + cx.notify(); } - fn on_lsp_work_end(&mut self, token: String, cx: &mut ModelContext) {} + fn on_lsp_work_end( + &mut self, + language_server_id: usize, + token: String, + cx: &mut ModelContext, + ) { + self.pending_language_server_work + .remove(&(language_server_id, token)); + cx.notify(); + } - fn send_lsp_event(&self, event: proto::lsp_event::Variant) { + fn broadcast_lsp_event(&self, language_server_id: usize, event: proto::lsp_event::Variant) { if let Some(project_id) = self.remote_id() { self.client .send(proto::LspEvent { project_id, + language_server_id: language_server_id as u64, variant: Some(event), }) .log_err(); } } + pub fn pending_language_server_work(&self) -> impl Iterator { + self.pending_language_server_work + .iter() + .map(|((_, token), progress)| (token.as_str(), progress)) + } + pub fn update_diagnostics( &mut self, params: lsp::PublishDiagnosticsParams, @@ -3152,24 +3218,28 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { + let language_server_id = envelope.payload.language_server_id as usize; match envelope .payload .variant .ok_or_else(|| anyhow!("invalid variant"))? { proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, cx| { - this.on_lsp_work_start(payload.token, cx); + this.on_lsp_work_start(language_server_id, payload.token, cx); }), proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| { this.on_lsp_work_progress( + language_server_id, payload.token, - payload.message, - payload.percentage.map(|p| p as usize), + LspWorkProgress { + message: payload.message, + percentage: payload.percentage.map(|p| p as usize), + }, cx, ); }), proto::lsp_event::Variant::WorkEnd(payload) => this.update(&mut cx, |this, cx| { - this.on_lsp_work_end(payload.token, cx); + this.on_lsp_work_end(language_server_id, payload.token, cx); }), proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => { this.update(&mut cx, |this, cx| { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 18df77e5c329160bdeb63f1b71e571704451e3bf..c9895739d98af1026afe6b1864ae3f563da5480e 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -425,12 +425,13 @@ message DiagnosticSummary { message LspEvent { uint64 project_id = 1; + uint64 language_server_id = 2; oneof variant { - LspWorkStart work_start = 2; - LspWorkProgress work_progress = 3; - LspWorkEnd work_end = 4; - LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 5; - LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 6; + LspWorkStart work_start = 3; + LspWorkProgress work_progress = 4; + LspWorkEnd work_end = 5; + LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6; + LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7; } } diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index ee61ecf24d9fc1e509377f460689dc68d9885563..98bd0112a9c9b8fd644ebe4891cbef39c2948dde 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -1,11 +1,13 @@ use crate::{ItemViewHandle, Settings, StatusItemView}; use futures::StreamExt; use gpui::{ - action, elements::*, platform::CursorStyle, Entity, MutableAppContext, RenderContext, View, - ViewContext, + action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext, + RenderContext, View, ViewContext, }; use language::{LanguageRegistry, LanguageServerBinaryStatus}; use postage::watch; +use project::Project; +use std::fmt::Write; use std::sync::Arc; action!(DismissErrorMessage); @@ -15,6 +17,7 @@ pub struct LspStatus { checking_for_update: Vec, downloading: Vec, failed: Vec, + project: ModelHandle, } pub fn init(cx: &mut MutableAppContext) { @@ -23,6 +26,7 @@ pub fn init(cx: &mut MutableAppContext) { impl LspStatus { pub fn new( + project: &ModelHandle, languages: Arc, settings_rx: watch::Receiver, cx: &mut ViewContext, @@ -62,11 +66,14 @@ impl LspStatus { } }) .detach(); + cx.observe(project, |_, _, cx| cx.notify()).detach(); + Self { settings_rx, checking_for_update: Default::default(), downloading: Default::default(), failed: Default::default(), + project: project.clone(), } } @@ -87,7 +94,24 @@ impl View for LspStatus { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let theme = &self.settings_rx.borrow().theme; - if !self.downloading.is_empty() { + + let mut pending_work = self.project.read(cx).pending_language_server_work(); + if let Some((progress_token, progress)) = pending_work.next() { + let mut message = progress + .message + .clone() + .unwrap_or_else(|| progress_token.to_string()); + if let Some(percentage) = progress.percentage { + write!(&mut message, " ({}%)", percentage).unwrap(); + } + + let additional_work_count = pending_work.count(); + if additional_work_count > 0 { + write!(&mut message, " + {} more", additional_work_count).unwrap(); + } + + Label::new(message, theme.workspace.status_bar.lsp_message.clone()).boxed() + } else if !self.downloading.is_empty() { Label::new( format!( "Downloading {} language server{}...", @@ -112,6 +136,7 @@ impl View for LspStatus { ) .boxed() } else if !self.failed.is_empty() { + drop(pending_work); MouseEventHandler::new::(0, cx, |_, _| { Label::new( format!( diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c9a14bf2364441e002e5208af7b53aa6e2420830..c513155e6f0a72b13405d3993296368633a8334e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -101,6 +101,7 @@ pub fn build_workspace( }); let lsp_status = cx.add_view(|cx| { workspace::lsp_status::LspStatus::new( + workspace.project(), app_state.languages.clone(), app_state.settings.clone(), cx,