Render pending language server work in status bar

This commit is contained in:
Antonio Scandurra 2022-03-10 16:09:47 +01:00
parent 4bbf5ed0b9
commit 4243f0c339
4 changed files with 151 additions and 54 deletions

View file

@ -51,6 +51,8 @@ pub struct Project {
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
language_servers: HashMap<(WorktreeId, Arc<str>), Arc<LanguageServer>>, language_servers: HashMap<(WorktreeId, Arc<str>), Arc<LanguageServer>>,
started_language_servers: HashMap<(WorktreeId, Arc<str>), Task<Option<Arc<LanguageServer>>>>, started_language_servers: HashMap<(WorktreeId, Arc<str>), Task<Option<Arc<LanguageServer>>>>,
pending_language_server_work: HashMap<(usize, String), LspWorkProgress>,
next_language_server_id: usize,
client: Arc<client::Client>, client: Arc<client::Client>,
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
@ -120,8 +122,7 @@ enum LspEvent {
}, },
WorkProgress { WorkProgress {
token: String, token: String,
message: Option<String>, progress: LspWorkProgress,
percentage: Option<usize>,
}, },
WorkEnd { WorkEnd {
token: String, token: String,
@ -129,6 +130,12 @@ enum LspEvent {
DiagnosticsUpdate(lsp::PublishDiagnosticsParams), DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
} }
#[derive(Clone, Default)]
pub struct LspWorkProgress {
pub message: Option<String>,
pub percentage: Option<usize>,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ProjectPath { pub struct ProjectPath {
pub worktree_id: WorktreeId, pub worktree_id: WorktreeId,
@ -317,6 +324,8 @@ impl Project {
language_servers_with_diagnostics_running: 0, language_servers_with_diagnostics_running: 0,
language_servers: Default::default(), language_servers: Default::default(),
started_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(), nonce: StdRng::from_entropy().gen(),
} }
}) })
@ -386,6 +395,8 @@ impl Project {
language_servers_with_diagnostics_running: 0, language_servers_with_diagnostics_running: 0,
language_servers: Default::default(), language_servers: Default::default(),
started_language_servers: Default::default(), started_language_servers: Default::default(),
pending_language_server_work: Default::default(),
next_language_server_id: 0,
opened_buffers: Default::default(), opened_buffers: Default::default(),
buffer_snapshots: Default::default(), buffer_snapshots: Default::default(),
nonce: StdRng::from_entropy().gen(), nonce: StdRng::from_entropy().gen(),
@ -1172,6 +1183,7 @@ impl Project {
self.started_language_servers self.started_language_servers
.entry(key.clone()) .entry(key.clone())
.or_insert_with(|| { .or_insert_with(|| {
let server_id = post_inc(&mut self.next_language_server_id);
let language_server = self.languages.start_language_server( let language_server = self.languages.start_language_server(
language.clone(), language.clone(),
worktree_path, worktree_path,
@ -1213,8 +1225,12 @@ impl Project {
lsp_events_tx lsp_events_tx
.try_send(LspEvent::WorkProgress { .try_send(LspEvent::WorkProgress {
token, token,
progress: LspWorkProgress {
message: report.message, message: report.message,
percentage: report.percentage.map(|p| p as usize), percentage: report
.percentage
.map(|p| p as usize),
},
}) })
.ok(); .ok();
} }
@ -1233,7 +1249,7 @@ impl Project {
while let Ok(event) = lsp_events_rx.recv().await { while let Ok(event) = lsp_events_rx.recv().await {
let this = this.upgrade(&cx)?; let this = this.upgrade(&cx)?;
this.update(&mut cx, |this, 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(()) Some(())
@ -1309,6 +1325,7 @@ impl Project {
fn on_local_lsp_event( fn on_local_lsp_event(
&mut self, &mut self,
language_server_id: usize,
event: LspEvent, event: LspEvent,
language: &Arc<Language>, language: &Arc<Language>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
@ -1318,43 +1335,53 @@ impl Project {
LspEvent::WorkStart { token } => { LspEvent::WorkStart { token } => {
if Some(&token) == disk_diagnostics_token { if Some(&token) == disk_diagnostics_token {
self.disk_based_diagnostics_started(cx); self.disk_based_diagnostics_started(cx);
self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( self.broadcast_lsp_event(
language_server_id,
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(
proto::LspDiskBasedDiagnosticsUpdating {}, proto::LspDiskBasedDiagnosticsUpdating {},
)); ),
);
} else { } else {
self.on_lsp_work_start(token.clone(), cx); self.on_lsp_work_start(language_server_id, token.clone(), cx);
self.send_lsp_event(proto::lsp_event::Variant::WorkStart( self.broadcast_lsp_event(
proto::LspWorkStart { token }, language_server_id,
)); proto::lsp_event::Variant::WorkStart(proto::LspWorkStart { token }),
);
} }
} }
LspEvent::WorkProgress { LspEvent::WorkProgress { token, progress } => {
token,
message,
percentage,
} => {
if Some(&token) != disk_diagnostics_token { if Some(&token) != disk_diagnostics_token {
self.on_lsp_work_progress(token.clone(), message.clone(), percentage, cx); self.on_lsp_work_progress(
self.send_lsp_event(proto::lsp_event::Variant::WorkProgress( language_server_id,
proto::LspWorkProgress { token.clone(),
progress.clone(),
cx,
);
self.broadcast_lsp_event(
language_server_id,
proto::lsp_event::Variant::WorkProgress(proto::LspWorkProgress {
token, token,
message, message: progress.message,
percentage: percentage.map(|p| p as u32), percentage: progress.percentage.map(|p| p as u32),
}, }),
)); );
} }
} }
LspEvent::WorkEnd { token } => { LspEvent::WorkEnd { token } => {
if Some(&token) == disk_diagnostics_token { if Some(&token) == disk_diagnostics_token {
self.disk_based_diagnostics_finished(cx); self.disk_based_diagnostics_finished(cx);
self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( self.broadcast_lsp_event(
language_server_id,
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(
proto::LspDiskBasedDiagnosticsUpdated {}, proto::LspDiskBasedDiagnosticsUpdated {},
)); ),
);
} else { } else {
self.on_lsp_work_end(token.clone(), cx); self.on_lsp_work_end(language_server_id, token.clone(), cx);
self.send_lsp_event(proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { self.broadcast_lsp_event(
token, language_server_id,
})); proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { token }),
);
} }
} }
LspEvent::DiagnosticsUpdate(mut params) => { LspEvent::DiagnosticsUpdate(mut params) => {
@ -1362,9 +1389,12 @@ impl Project {
if disk_diagnostics_token.is_none() { if disk_diagnostics_token.is_none() {
self.disk_based_diagnostics_started(cx); self.disk_based_diagnostics_started(cx);
self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( self.broadcast_lsp_event(
language_server_id,
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(
proto::LspDiskBasedDiagnosticsUpdating {}, proto::LspDiskBasedDiagnosticsUpdating {},
)); ),
);
} }
self.update_diagnostics( self.update_diagnostics(
params, params,
@ -1376,38 +1406,74 @@ impl Project {
.log_err(); .log_err();
if disk_diagnostics_token.is_none() { if disk_diagnostics_token.is_none() {
self.disk_based_diagnostics_finished(cx); self.disk_based_diagnostics_finished(cx);
self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( self.broadcast_lsp_event(
language_server_id,
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(
proto::LspDiskBasedDiagnosticsUpdated {}, proto::LspDiskBasedDiagnosticsUpdated {},
)); ),
);
} }
} }
} }
} }
fn on_lsp_work_start(&mut self, token: String, cx: &mut ModelContext<Self>) {} fn on_lsp_work_start(
&mut self,
language_server_id: usize,
token: String,
cx: &mut ModelContext<Self>,
) {
self.pending_language_server_work.insert(
(language_server_id, token),
LspWorkProgress {
message: None,
percentage: None,
},
);
cx.notify();
}
fn on_lsp_work_progress( fn on_lsp_work_progress(
&mut self, &mut self,
language_server_id: usize,
token: String, token: String,
message: Option<String>, progress: LspWorkProgress,
percentage: Option<usize>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
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<Self>) {} fn on_lsp_work_end(
&mut self,
language_server_id: usize,
token: String,
cx: &mut ModelContext<Self>,
) {
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() { if let Some(project_id) = self.remote_id() {
self.client self.client
.send(proto::LspEvent { .send(proto::LspEvent {
project_id, project_id,
language_server_id: language_server_id as u64,
variant: Some(event), variant: Some(event),
}) })
.log_err(); .log_err();
} }
} }
pub fn pending_language_server_work(&self) -> impl Iterator<Item = (&str, &LspWorkProgress)> {
self.pending_language_server_work
.iter()
.map(|((_, token), progress)| (token.as_str(), progress))
}
pub fn update_diagnostics( pub fn update_diagnostics(
&mut self, &mut self,
params: lsp::PublishDiagnosticsParams, params: lsp::PublishDiagnosticsParams,
@ -3152,24 +3218,28 @@ impl Project {
_: Arc<Client>, _: Arc<Client>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
let language_server_id = envelope.payload.language_server_id as usize;
match envelope match envelope
.payload .payload
.variant .variant
.ok_or_else(|| anyhow!("invalid variant"))? .ok_or_else(|| anyhow!("invalid variant"))?
{ {
proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, cx| { 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| { proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| {
this.on_lsp_work_progress( this.on_lsp_work_progress(
language_server_id,
payload.token, payload.token,
payload.message, LspWorkProgress {
payload.percentage.map(|p| p as usize), message: payload.message,
percentage: payload.percentage.map(|p| p as usize),
},
cx, cx,
); );
}), }),
proto::lsp_event::Variant::WorkEnd(payload) => this.update(&mut cx, |this, 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(_) => { proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {

View file

@ -425,12 +425,13 @@ message DiagnosticSummary {
message LspEvent { message LspEvent {
uint64 project_id = 1; uint64 project_id = 1;
uint64 language_server_id = 2;
oneof variant { oneof variant {
LspWorkStart work_start = 2; LspWorkStart work_start = 3;
LspWorkProgress work_progress = 3; LspWorkProgress work_progress = 4;
LspWorkEnd work_end = 4; LspWorkEnd work_end = 5;
LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 5; LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6;
LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 6; LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7;
} }
} }

View file

@ -1,11 +1,13 @@
use crate::{ItemViewHandle, Settings, StatusItemView}; use crate::{ItemViewHandle, Settings, StatusItemView};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
action, elements::*, platform::CursorStyle, Entity, MutableAppContext, RenderContext, View, action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext,
ViewContext, RenderContext, View, ViewContext,
}; };
use language::{LanguageRegistry, LanguageServerBinaryStatus}; use language::{LanguageRegistry, LanguageServerBinaryStatus};
use postage::watch; use postage::watch;
use project::Project;
use std::fmt::Write;
use std::sync::Arc; use std::sync::Arc;
action!(DismissErrorMessage); action!(DismissErrorMessage);
@ -15,6 +17,7 @@ pub struct LspStatus {
checking_for_update: Vec<String>, checking_for_update: Vec<String>,
downloading: Vec<String>, downloading: Vec<String>,
failed: Vec<String>, failed: Vec<String>,
project: ModelHandle<Project>,
} }
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
@ -23,6 +26,7 @@ pub fn init(cx: &mut MutableAppContext) {
impl LspStatus { impl LspStatus {
pub fn new( pub fn new(
project: &ModelHandle<Project>,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
settings_rx: watch::Receiver<Settings>, settings_rx: watch::Receiver<Settings>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
@ -62,11 +66,14 @@ impl LspStatus {
} }
}) })
.detach(); .detach();
cx.observe(project, |_, _, cx| cx.notify()).detach();
Self { Self {
settings_rx, settings_rx,
checking_for_update: Default::default(), checking_for_update: Default::default(),
downloading: Default::default(), downloading: Default::default(),
failed: Default::default(), failed: Default::default(),
project: project.clone(),
} }
} }
@ -87,7 +94,24 @@ impl View for LspStatus {
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
let theme = &self.settings_rx.borrow().theme; 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( Label::new(
format!( format!(
"Downloading {} language server{}...", "Downloading {} language server{}...",
@ -112,6 +136,7 @@ impl View for LspStatus {
) )
.boxed() .boxed()
} else if !self.failed.is_empty() { } else if !self.failed.is_empty() {
drop(pending_work);
MouseEventHandler::new::<Self, _, _>(0, cx, |_, _| { MouseEventHandler::new::<Self, _, _>(0, cx, |_, _| {
Label::new( Label::new(
format!( format!(

View file

@ -101,6 +101,7 @@ pub fn build_workspace(
}); });
let lsp_status = cx.add_view(|cx| { let lsp_status = cx.add_view(|cx| {
workspace::lsp_status::LspStatus::new( workspace::lsp_status::LspStatus::new(
workspace.project(),
app_state.languages.clone(), app_state.languages.clone(),
app_state.settings.clone(), app_state.settings.clone(),
cx, cx,