From 4bbf5ed0b979b633d52c736281b55ae7e8f7660f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 12:00:33 +0100 Subject: [PATCH 1/6] Listen to all LSP progress notifications and broadcast them to peers --- crates/project/src/project.rs | 286 +++++++++++++++++++++------------- crates/rpc/proto/zed.proto | 30 +++- crates/rpc/src/proto.rs | 6 +- crates/server/src/rpc.rs | 21 +-- 4 files changed, 207 insertions(+), 136 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9a45e08164..9741f16784 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -28,7 +28,6 @@ use rand::prelude::*; use search::SearchQuery; use sha2::{Digest, Sha256}; use similar::{ChangeTag, TextDiff}; -use smol::block_on; use std::{ cell::RefCell, cmp::{self, Ordering}, @@ -115,6 +114,21 @@ pub enum Event { DiagnosticsUpdated(ProjectPath), } +enum LspEvent { + WorkStart { + token: String, + }, + WorkProgress { + token: String, + message: Option, + percentage: Option, + }, + WorkEnd { + token: String, + }, + DiagnosticsUpdate(lsp::PublishDiagnosticsParams), +} + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct ProjectPath { pub worktree_id: WorktreeId, @@ -203,8 +217,7 @@ impl Project { client.add_entity_message_handler(Self::handle_add_collaborator); client.add_entity_message_handler(Self::handle_buffer_reloaded); client.add_entity_message_handler(Self::handle_buffer_saved); - client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updated); - client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updating); + client.add_entity_message_handler(Self::handle_lsp_event); client.add_entity_message_handler(Self::handle_remove_collaborator); client.add_entity_message_handler(Self::handle_register_worktree); client.add_entity_message_handler(Self::handle_unregister_worktree); @@ -1155,12 +1168,6 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - enum LspEvent { - DiagnosticsStart, - DiagnosticsUpdate(lsp::PublishDiagnosticsParams), - DiagnosticsFinish, - } - let key = (worktree_id, language.name()); self.started_language_servers .entry(key.clone()) @@ -1171,76 +1178,50 @@ impl Project { self.client.http_client(), cx, ); - let rpc = self.client.clone(); cx.spawn_weak(|this, mut cx| async move { let mut language_server = language_server?.await.log_err()?; let this = this.upgrade(&cx)?; + let (lsp_events_tx, lsp_events_rx) = smol::channel::unbounded(); - 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(); + let lsp_events_tx = lsp_events_tx.clone(); move |params| { - if !has_disk_based_diagnostic_progress_token { - block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok(); - } - block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))) + lsp_events_tx + .try_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), + lsp::NumberOrString::String(token) => token, + lsp::NumberOrString::Number(token) => { + log::info!("skipping numeric progress token {}", token); + return; + } }; - 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(); - } - } - _ => {} - } + match params.value { + lsp::ProgressParamsValue::WorkDone(progress) => match progress { + lsp::WorkDoneProgress::Begin(_) => { + lsp_events_tx.try_send(LspEvent::WorkStart { token }).ok(); } - } + lsp::WorkDoneProgress::Report(report) => { + lsp_events_tx + .try_send(LspEvent::WorkProgress { + token, + message: report.message, + percentage: report.percentage.map(|p| p as usize), + }) + .ok(); + } + lsp::WorkDoneProgress::End(_) => { + lsp_events_tx.try_send(LspEvent::WorkEnd { token }).ok(); + } + }, } }) .detach(); @@ -1249,43 +1230,11 @@ impl Project { cx.spawn(|mut cx| { let this = this.downgrade(); async move { - while let Ok(message) = diagnostics_rx.recv().await { + while let Ok(event) = lsp_events_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(); - } - }); - } - } + this.update(&mut cx, |this, cx| { + this.on_local_lsp_event(event, &language, cx) + }); } Some(()) } @@ -1358,6 +1307,107 @@ impl Project { }); } + fn on_local_lsp_event( + &mut self, + event: LspEvent, + language: &Arc, + cx: &mut ModelContext, + ) { + let disk_diagnostics_token = language.disk_based_diagnostics_progress_token(); + match event { + 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 {}, + )); + } else { + self.on_lsp_work_start(token.clone(), cx); + self.send_lsp_event(proto::lsp_event::Variant::WorkStart( + proto::LspWorkStart { token }, + )); + } + } + LspEvent::WorkProgress { + token, + message, + percentage, + } => { + 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 { + token, + message, + percentage: 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 {}, + )); + } else { + self.on_lsp_work_end(token.clone(), cx); + self.send_lsp_event(proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { + token, + })); + } + } + LspEvent::DiagnosticsUpdate(mut params) => { + language.process_diagnostics(&mut params); + + if disk_diagnostics_token.is_none() { + self.disk_based_diagnostics_started(cx); + self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + )); + } + self.update_diagnostics( + params, + language + .disk_based_diagnostic_sources() + .unwrap_or(&Default::default()), + cx, + ) + .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 {}, + )); + } + } + } + } + + fn on_lsp_work_start(&mut self, token: String, cx: &mut ModelContext) {} + + fn on_lsp_work_progress( + &mut self, + token: String, + message: Option, + percentage: Option, + cx: &mut ModelContext, + ) { + } + + fn on_lsp_work_end(&mut self, token: String, cx: &mut ModelContext) {} + + fn send_lsp_event(&self, event: proto::lsp_event::Variant) { + if let Some(project_id) = self.remote_id() { + self.client + .send(proto::LspEvent { + project_id, + variant: Some(event), + }) + .log_err(); + } + } + pub fn update_diagnostics( &mut self, params: lsp::PublishDiagnosticsParams, @@ -3096,23 +3146,41 @@ impl Project { }) } - async fn handle_disk_based_diagnostics_updating( + async fn handle_lsp_event( this: ModelHandle, - _: TypedEnvelope, + envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { - this.update(&mut cx, |this, cx| this.disk_based_diagnostics_started(cx)); - Ok(()) - } + 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); + }), + proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| { + this.on_lsp_work_progress( + payload.token, + payload.message, + 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); + }), + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => { + this.update(&mut cx, |this, cx| { + this.disk_based_diagnostics_started(cx); + }) + } + proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(_) => { + this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx)); + } + } - async fn handle_disk_based_diagnostics_updated( - this: ModelHandle, - _: TypedEnvelope, - _: Arc, - mut cx: AsyncAppContext, - ) -> Result<()> { - this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx)); Ok(()) } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 7f43aaff1a..18df77e5c3 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -37,8 +37,7 @@ message Envelope { UnregisterWorktree unregister_worktree = 29; UpdateWorktree update_worktree = 31; UpdateDiagnosticSummary update_diagnostic_summary = 32; - DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 33; - DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 34; + LspEvent lsp_event = 33; OpenBuffer open_buffer = 35; OpenBufferResponse open_buffer_response = 36; @@ -424,14 +423,35 @@ message DiagnosticSummary { uint32 hint_count = 5; } -message DiskBasedDiagnosticsUpdating { +message LspEvent { uint64 project_id = 1; + 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; + } } -message DiskBasedDiagnosticsUpdated { - uint64 project_id = 1; +message LspWorkStart { + string token = 1; } +message LspWorkProgress { + string token = 1; + optional string message = 2; + optional uint32 percentage = 3; +} + +message LspWorkEnd { + string token = 1; +} + +message LspDiskBasedDiagnosticsUpdating {} + +message LspDiskBasedDiagnosticsUpdated {} + message GetChannels {} message GetChannelsResponse { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index d252decb3a..15a5839524 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -146,8 +146,6 @@ messages!( (BufferReloaded, Foreground), (BufferSaved, Foreground), (ChannelMessageSent, Foreground), - (DiskBasedDiagnosticsUpdated, Background), - (DiskBasedDiagnosticsUpdating, Background), (Error, Foreground), (FormatBuffers, Foreground), (FormatBuffersResponse, Foreground), @@ -175,6 +173,7 @@ messages!( (JoinProjectResponse, Foreground), (LeaveChannel, Foreground), (LeaveProject, Foreground), + (LspEvent, Background), (OpenBuffer, Background), (OpenBufferForSymbol, Background), (OpenBufferForSymbolResponse, Background), @@ -246,8 +245,6 @@ entity_messages!( ApplyCompletionAdditionalEdits, BufferReloaded, BufferSaved, - DiskBasedDiagnosticsUpdated, - DiskBasedDiagnosticsUpdating, FormatBuffers, GetCodeActions, GetCompletions, @@ -257,6 +254,7 @@ entity_messages!( GetProjectSymbols, JoinProject, LeaveProject, + LspEvent, OpenBuffer, OpenBufferForSymbol, PerformRename, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 2b954b5774..74406146c1 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -84,8 +84,7 @@ impl Server { .add_message_handler(Server::unregister_worktree) .add_request_handler(Server::update_worktree) .add_message_handler(Server::update_diagnostic_summary) - .add_message_handler(Server::disk_based_diagnostics_updating) - .add_message_handler(Server::disk_based_diagnostics_updated) + .add_message_handler(Server::lsp_event) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) @@ -535,23 +534,9 @@ impl Server { Ok(()) } - async fn disk_based_diagnostics_updating( + async fn lsp_event( self: Arc, - request: TypedEnvelope, - ) -> tide::Result<()> { - let receiver_ids = self - .state() - .project_connection_ids(request.payload.project_id, request.sender_id)?; - broadcast(request.sender_id, receiver_ids, |connection_id| { - self.peer - .forward_send(request.sender_id, connection_id, request.payload.clone()) - })?; - Ok(()) - } - - async fn disk_based_diagnostics_updated( - self: Arc, - request: TypedEnvelope, + request: TypedEnvelope, ) -> tide::Result<()> { let receiver_ids = self .state() From 4243f0c339166cad2ba7ccc7c8731134b9d85b7f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 16:09:47 +0100 Subject: [PATCH 2/6] Render pending language server work in status bar --- crates/project/src/project.rs | 162 +++++++++++++++++++++-------- crates/rpc/proto/zed.proto | 11 +- crates/workspace/src/lsp_status.rs | 31 +++++- crates/zed/src/zed.rs | 1 + 4 files changed, 151 insertions(+), 54 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9741f16784..95609bf43f 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 18df77e5c3..c9895739d9 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 ee61ecf24d..98bd0112a9 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 c9a14bf236..c513155e6f 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, From 45fb470f4d8b16801f530a0cf4ef05d6fb0242b7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 16:45:13 +0100 Subject: [PATCH 3/6] Display language server name in status bar --- crates/lsp/src/lsp.rs | 22 ++- crates/project/src/project.rs | 207 +++++++++++++++++++---------- crates/rpc/proto/zed.proto | 16 ++- crates/rpc/src/proto.rs | 6 +- crates/server/src/rpc.rs | 28 +++- crates/server/src/rpc/store.rs | 20 +++ crates/workspace/src/lsp_status.rs | 15 ++- 7 files changed, 228 insertions(+), 86 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d9024975e4..43b26efc37 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -35,6 +35,7 @@ type ResponseHandler = Box)>; pub struct LanguageServer { next_id: AtomicUsize, outbound_tx: channel::Sender>, + name: String, capabilities: ServerCapabilities, notification_handlers: Arc>>, response_handlers: Arc>>, @@ -118,9 +119,11 @@ impl LanguageServer { .spawn()?; let stdin = server.stdin.take().unwrap(); let stdout = server.stdout.take().unwrap(); - Ok(Self::new_internal( - stdin, stdout, root_path, options, background, - )) + let mut server = Self::new_internal(stdin, stdout, root_path, options, background); + if let Some(name) = binary_path.file_name() { + server.name = name.to_string_lossy().to_string(); + } + Ok(server) } fn new_internal( @@ -222,6 +225,7 @@ impl LanguageServer { Self { notification_handlers, response_handlers, + name: Default::default(), capabilities: Default::default(), next_id: Default::default(), outbound_tx, @@ -292,7 +296,13 @@ impl LanguageServer { }; let response = this.request::(params).await?; - Arc::get_mut(&mut this).unwrap().capabilities = response.capabilities; + { + let this = Arc::get_mut(&mut this).unwrap(); + if let Some(info) = response.server_info { + this.name = info.name; + } + this.capabilities = response.capabilities; + } this.notify::(InitializedParams {})?; Ok(this) } @@ -355,6 +365,10 @@ impl LanguageServer { } } + pub fn name<'a>(self: &'a Arc) -> &'a str { + &self.name + } + pub fn capabilities<'a>(self: &'a Arc) -> &'a ServerCapabilities { &self.capabilities } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 95609bf43f..a35b8dc79a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -7,7 +7,7 @@ pub mod worktree; use anyhow::{anyhow, Context, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; -use collections::{hash_map, HashMap, HashSet}; +use collections::{hash_map, BTreeMap, HashMap, HashSet}; use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt}; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ @@ -51,7 +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>, + pending_language_server_work: BTreeMap<(usize, String), LanguageServerProgress>, + language_server_names: HashMap, next_language_server_id: usize, client: Arc, user_store: ModelHandle, @@ -116,13 +117,13 @@ pub enum Event { DiagnosticsUpdated(ProjectPath), } -enum LspEvent { +enum LanguageServerEvent { WorkStart { token: String, }, WorkProgress { token: String, - progress: LspWorkProgress, + progress: LanguageServerProgress, }, WorkEnd { token: String, @@ -131,7 +132,7 @@ enum LspEvent { } #[derive(Clone, Default)] -pub struct LspWorkProgress { +pub struct LanguageServerProgress { pub message: Option, pub percentage: Option, } @@ -224,7 +225,8 @@ impl Project { client.add_entity_message_handler(Self::handle_add_collaborator); client.add_entity_message_handler(Self::handle_buffer_reloaded); client.add_entity_message_handler(Self::handle_buffer_saved); - client.add_entity_message_handler(Self::handle_lsp_event); + client.add_entity_message_handler(Self::handle_start_language_server); + client.add_entity_message_handler(Self::handle_update_language_server); client.add_entity_message_handler(Self::handle_remove_collaborator); client.add_entity_message_handler(Self::handle_register_worktree); client.add_entity_message_handler(Self::handle_unregister_worktree); @@ -325,6 +327,7 @@ impl Project { language_servers: Default::default(), started_language_servers: Default::default(), pending_language_server_work: Default::default(), + language_server_names: Default::default(), next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), } @@ -396,6 +399,11 @@ impl Project { language_servers: Default::default(), started_language_servers: Default::default(), pending_language_server_work: Default::default(), + language_server_names: response + .language_servers + .into_iter() + .map(|s| (s.id as usize, s.name)) + .collect(), next_language_server_id: 0, opened_buffers: Default::default(), buffer_snapshots: Default::default(), @@ -1193,14 +1201,15 @@ impl Project { cx.spawn_weak(|this, mut cx| async move { let mut language_server = language_server?.await.log_err()?; let this = this.upgrade(&cx)?; - let (lsp_events_tx, lsp_events_rx) = smol::channel::unbounded(); + let (language_server_events_tx, language_server_events_rx) = + smol::channel::unbounded(); language_server .on_notification::({ - let lsp_events_tx = lsp_events_tx.clone(); + let language_server_events_tx = language_server_events_tx.clone(); move |params| { - lsp_events_tx - .try_send(LspEvent::DiagnosticsUpdate(params)) + language_server_events_tx + .try_send(LanguageServerEvent::DiagnosticsUpdate(params)) .ok(); } }) @@ -1219,13 +1228,15 @@ impl Project { match params.value { lsp::ProgressParamsValue::WorkDone(progress) => match progress { lsp::WorkDoneProgress::Begin(_) => { - lsp_events_tx.try_send(LspEvent::WorkStart { token }).ok(); + language_server_events_tx + .try_send(LanguageServerEvent::WorkStart { token }) + .ok(); } lsp::WorkDoneProgress::Report(report) => { - lsp_events_tx - .try_send(LspEvent::WorkProgress { + language_server_events_tx + .try_send(LanguageServerEvent::WorkProgress { token, - progress: LspWorkProgress { + progress: LanguageServerProgress { message: report.message, percentage: report .percentage @@ -1235,7 +1246,9 @@ impl Project { .ok(); } lsp::WorkDoneProgress::End(_) => { - lsp_events_tx.try_send(LspEvent::WorkEnd { token }).ok(); + language_server_events_tx + .try_send(LanguageServerEvent::WorkEnd { token }) + .ok(); } }, } @@ -1246,10 +1259,10 @@ impl Project { cx.spawn(|mut cx| { let this = this.downgrade(); async move { - while let Ok(event) = lsp_events_rx.recv().await { + while let Ok(event) = language_server_events_rx.recv().await { let this = this.upgrade(&cx)?; this.update(&mut cx, |this, cx| { - this.on_local_lsp_event(server_id, event, &language, cx) + this.on_lsp_event(server_id, event, &language, cx) }); } Some(()) @@ -1261,6 +1274,20 @@ impl Project { this.update(&mut cx, |this, cx| { this.language_servers .insert(key.clone(), language_server.clone()); + this.language_server_names + .insert(server_id, language_server.name().to_string()); + + if let Some(project_id) = this.remote_id() { + this.client + .send(proto::StartLanguageServer { + project_id, + server: Some(proto::LanguageServer { + id: server_id as u64, + name: language_server.name().to_string(), + }), + }) + .log_err(); + } // Tell the language server about every open buffer in the worktree that matches the language. for buffer in this.opened_buffers.values() { @@ -1315,6 +1342,7 @@ impl Project { } } + cx.notify(); Some(()) }); @@ -1323,33 +1351,35 @@ impl Project { }); } - fn on_local_lsp_event( + fn on_lsp_event( &mut self, language_server_id: usize, - event: LspEvent, + event: LanguageServerEvent, language: &Arc, cx: &mut ModelContext, ) { let disk_diagnostics_token = language.disk_based_diagnostics_progress_token(); match event { - LspEvent::WorkStart { token } => { + LanguageServerEvent::WorkStart { token } => { if Some(&token) == disk_diagnostics_token { self.disk_based_diagnostics_started(cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating( proto::LspDiskBasedDiagnosticsUpdating {}, ), ); } else { self.on_lsp_work_start(language_server_id, token.clone(), cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::WorkStart(proto::LspWorkStart { token }), + proto::update_language_server::Variant::WorkStart(proto::LspWorkStart { + token, + }), ); } } - LspEvent::WorkProgress { token, progress } => { + LanguageServerEvent::WorkProgress { token, progress } => { if Some(&token) != disk_diagnostics_token { self.on_lsp_work_progress( language_server_id, @@ -1357,41 +1387,45 @@ impl Project { progress.clone(), cx, ); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::WorkProgress(proto::LspWorkProgress { - token, - message: progress.message, - percentage: progress.percentage.map(|p| p as u32), - }), + proto::update_language_server::Variant::WorkProgress( + proto::LspWorkProgress { + token, + message: progress.message, + percentage: progress.percentage.map(|p| p as u32), + }, + ), ); } } - LspEvent::WorkEnd { token } => { + LanguageServerEvent::WorkEnd { token } => { if Some(&token) == disk_diagnostics_token { self.disk_based_diagnostics_finished(cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( proto::LspDiskBasedDiagnosticsUpdated {}, ), ); } else { self.on_lsp_work_end(language_server_id, token.clone(), cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { token }), + proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd { + token, + }), ); } } - LspEvent::DiagnosticsUpdate(mut params) => { + LanguageServerEvent::DiagnosticsUpdate(mut params) => { language.process_diagnostics(&mut params); if disk_diagnostics_token.is_none() { self.disk_based_diagnostics_started(cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating( + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating( proto::LspDiskBasedDiagnosticsUpdating {}, ), ); @@ -1406,9 +1440,9 @@ impl Project { .log_err(); if disk_diagnostics_token.is_none() { self.disk_based_diagnostics_finished(cx); - self.broadcast_lsp_event( + self.broadcast_language_server_update( language_server_id, - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated( + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( proto::LspDiskBasedDiagnosticsUpdated {}, ), ); @@ -1425,7 +1459,7 @@ impl Project { ) { self.pending_language_server_work.insert( (language_server_id, token), - LspWorkProgress { + LanguageServerProgress { message: None, percentage: None, }, @@ -1437,7 +1471,7 @@ impl Project { &mut self, language_server_id: usize, token: String, - progress: LspWorkProgress, + progress: LanguageServerProgress, cx: &mut ModelContext, ) { self.pending_language_server_work @@ -1456,10 +1490,14 @@ impl Project { cx.notify(); } - fn broadcast_lsp_event(&self, language_server_id: usize, event: proto::lsp_event::Variant) { + fn broadcast_language_server_update( + &self, + language_server_id: usize, + event: proto::update_language_server::Variant, + ) { if let Some(project_id) = self.remote_id() { self.client - .send(proto::LspEvent { + .send(proto::UpdateLanguageServer { project_id, language_server_id: language_server_id as u64, variant: Some(event), @@ -1468,10 +1506,15 @@ impl Project { } } - pub fn pending_language_server_work(&self) -> impl Iterator { - self.pending_language_server_work - .iter() - .map(|((_, token), progress)| (token.as_str(), progress)) + pub fn pending_language_server_work( + &self, + ) -> impl Iterator { + self.pending_language_server_work.iter().filter_map( + |((language_server_id, token), progress)| { + let name = self.language_server_names.get(language_server_id)?; + Some((name.as_str(), token.as_str(), progress)) + }, + ) } pub fn update_diagnostics( @@ -3212,9 +3255,27 @@ impl Project { }) } - async fn handle_lsp_event( + async fn handle_start_language_server( this: ModelHandle, - envelope: TypedEnvelope, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result<()> { + let server = envelope + .payload + .server + .ok_or_else(|| anyhow!("invalid server"))?; + this.update(&mut cx, |this, cx| { + this.language_server_names + .insert(server.id as usize, server.name); + cx.notify(); + }); + Ok(()) + } + + async fn handle_update_language_server( + this: ModelHandle, + envelope: TypedEnvelope, _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { @@ -3224,29 +3285,35 @@ impl Project { .variant .ok_or_else(|| anyhow!("invalid variant"))? { - proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, 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, - 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(language_server_id, payload.token, cx); - }), - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => { + proto::update_language_server::Variant::WorkStart(payload) => { + this.update(&mut cx, |this, cx| { + this.on_lsp_work_start(language_server_id, payload.token, cx); + }) + } + proto::update_language_server::Variant::WorkProgress(payload) => { + this.update(&mut cx, |this, cx| { + this.on_lsp_work_progress( + language_server_id, + payload.token, + LanguageServerProgress { + message: payload.message, + percentage: payload.percentage.map(|p| p as usize), + }, + cx, + ); + }) + } + proto::update_language_server::Variant::WorkEnd(payload) => { + this.update(&mut cx, |this, cx| { + this.on_lsp_work_end(language_server_id, payload.token, cx); + }) + } + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(_) => { this.update(&mut cx, |this, cx| { this.disk_based_diagnostics_started(cx); }) } - proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(_) => { + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(_) => { this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx)); } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index c9895739d9..87303c3c26 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -37,7 +37,8 @@ message Envelope { UnregisterWorktree unregister_worktree = 29; UpdateWorktree update_worktree = 31; UpdateDiagnosticSummary update_diagnostic_summary = 32; - LspEvent lsp_event = 33; + StartLanguageServer start_language_server = 33; + UpdateLanguageServer update_language_server = 34; OpenBuffer open_buffer = 35; OpenBufferResponse open_buffer_response = 36; @@ -121,6 +122,7 @@ message JoinProjectResponse { uint32 replica_id = 1; repeated Worktree worktrees = 2; repeated Collaborator collaborators = 3; + repeated LanguageServer language_servers = 4; } message LeaveProject { @@ -409,6 +411,16 @@ message LocalTimestamp { uint32 value = 2; } +message LanguageServer { + uint64 id = 1; + string name = 2; +} + +message StartLanguageServer { + uint64 project_id = 1; + LanguageServer server = 2; +} + message UpdateDiagnosticSummary { uint64 project_id = 1; uint64 worktree_id = 2; @@ -423,7 +435,7 @@ message DiagnosticSummary { uint32 hint_count = 5; } -message LspEvent { +message UpdateLanguageServer { uint64 project_id = 1; uint64 language_server_id = 2; oneof variant { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 15a5839524..54b26b830c 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -171,9 +171,10 @@ messages!( (JoinChannelResponse, Foreground), (JoinProject, Foreground), (JoinProjectResponse, Foreground), + (StartLanguageServer, Foreground), + (UpdateLanguageServer, Foreground), (LeaveChannel, Foreground), (LeaveProject, Foreground), - (LspEvent, Background), (OpenBuffer, Background), (OpenBufferForSymbol, Background), (OpenBufferForSymbolResponse, Background), @@ -254,7 +255,6 @@ entity_messages!( GetProjectSymbols, JoinProject, LeaveProject, - LspEvent, OpenBuffer, OpenBufferForSymbol, PerformRename, @@ -262,11 +262,13 @@ entity_messages!( RemoveProjectCollaborator, SaveBuffer, SearchProject, + StartLanguageServer, UnregisterWorktree, UnshareProject, UpdateBuffer, UpdateBufferFile, UpdateDiagnosticSummary, + UpdateLanguageServer, RegisterWorktree, UpdateWorktree, ); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 74406146c1..393e54165f 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -83,8 +83,9 @@ impl Server { .add_request_handler(Server::register_worktree) .add_message_handler(Server::unregister_worktree) .add_request_handler(Server::update_worktree) + .add_message_handler(Server::start_language_server) + .add_message_handler(Server::update_language_server) .add_message_handler(Server::update_diagnostic_summary) - .add_message_handler(Server::lsp_event) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) .add_request_handler(Server::forward_project_request::) @@ -385,6 +386,7 @@ impl Server { worktrees, replica_id: joined.replica_id as u32, collaborators, + language_servers: joined.project.language_servers.clone(), }; let connection_ids = joined.project.connection_ids(); let contact_user_ids = joined.project.authorized_user_ids(); @@ -534,9 +536,29 @@ impl Server { Ok(()) } - async fn lsp_event( + async fn start_language_server( + mut self: Arc, + request: TypedEnvelope, + ) -> tide::Result<()> { + let receiver_ids = self.state_mut().start_language_server( + request.payload.project_id, + request.sender_id, + request + .payload + .server + .clone() + .ok_or_else(|| anyhow!("invalid language server"))?, + )?; + broadcast(request.sender_id, receiver_ids, |connection_id| { + self.peer + .forward_send(request.sender_id, connection_id, request.payload.clone()) + })?; + Ok(()) + } + + async fn update_language_server( self: Arc, - request: TypedEnvelope, + request: TypedEnvelope, ) -> tide::Result<()> { let receiver_ids = self .state() diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index c18db3b684..6f5252fecf 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -25,6 +25,7 @@ pub struct Project { pub host_user_id: UserId, pub share: Option, pub worktrees: HashMap, + pub language_servers: Vec, } pub struct Worktree { @@ -240,6 +241,7 @@ impl Store { host_user_id, share: None, worktrees: Default::default(), + language_servers: Default::default(), }, ); self.next_project_id += 1; @@ -438,6 +440,24 @@ impl Store { Err(anyhow!("no such worktree"))? } + pub fn start_language_server( + &mut self, + project_id: u64, + connection_id: ConnectionId, + language_server: proto::LanguageServer, + ) -> tide::Result> { + let project = self + .projects + .get_mut(&project_id) + .ok_or_else(|| anyhow!("no such project"))?; + if project.host_connection_id == connection_id { + project.language_servers.push(language_server); + return Ok(project.connection_ids()); + } + + Err(anyhow!("no such project"))? + } + pub fn join_project( &mut self, connection_id: ConnectionId, diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index 98bd0112a9..e2976824b5 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -96,11 +96,16 @@ impl View for LspStatus { let theme = &self.settings_rx.borrow().theme; 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((lang_server_name, progress_token, progress)) = pending_work.next() { + let mut message = lang_server_name.to_string(); + + message.push_str(": "); + if let Some(progress_message) = progress.message.as_ref() { + message.push_str(progress_message); + } else { + message.push_str(progress_token); + } + if let Some(percentage) = progress.percentage { write!(&mut message, " ({}%)", percentage).unwrap(); } From 5157b428961929446930efbf9c70df24b014e7e7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Mar 2022 17:04:36 +0100 Subject: [PATCH 4/6] Extract a `LanguageServerStatus` struct --- crates/project/src/project.rs | 130 ++++++++++++++++++----------- crates/workspace/src/lsp_status.rs | 26 +++++- 2 files changed, 106 insertions(+), 50 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a35b8dc79a..dd4a17c13a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -51,8 +51,7 @@ pub struct Project { languages: Arc, language_servers: HashMap<(WorktreeId, Arc), Arc>, started_language_servers: HashMap<(WorktreeId, Arc), Task>>>, - pending_language_server_work: BTreeMap<(usize, String), LanguageServerProgress>, - language_server_names: HashMap, + language_server_statuses: BTreeMap, next_language_server_id: usize, client: Arc, user_store: ModelHandle, @@ -131,6 +130,12 @@ enum LanguageServerEvent { DiagnosticsUpdate(lsp::PublishDiagnosticsParams), } +pub struct LanguageServerStatus { + pub name: String, + pub pending_work: BTreeMap, + pending_diagnostic_updates: isize, +} + #[derive(Clone, Default)] pub struct LanguageServerProgress { pub message: Option, @@ -326,8 +331,7 @@ impl Project { language_servers_with_diagnostics_running: 0, language_servers: Default::default(), started_language_servers: Default::default(), - pending_language_server_work: Default::default(), - language_server_names: Default::default(), + language_server_statuses: Default::default(), next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), } @@ -398,11 +402,19 @@ impl Project { language_servers_with_diagnostics_running: 0, language_servers: Default::default(), started_language_servers: Default::default(), - pending_language_server_work: Default::default(), - language_server_names: response + language_server_statuses: response .language_servers .into_iter() - .map(|s| (s.id as usize, s.name)) + .map(|server| { + ( + server.id as usize, + LanguageServerStatus { + name: server.name, + pending_work: Default::default(), + pending_diagnostic_updates: 0, + }, + ) + }) .collect(), next_language_server_id: 0, opened_buffers: Default::default(), @@ -1274,8 +1286,14 @@ impl Project { this.update(&mut cx, |this, cx| { this.language_servers .insert(key.clone(), language_server.clone()); - this.language_server_names - .insert(server_id, language_server.name().to_string()); + this.language_server_statuses.insert( + server_id, + LanguageServerStatus { + name: language_server.name().to_string(), + pending_work: Default::default(), + pending_diagnostic_updates: 0, + }, + ); if let Some(project_id) = this.remote_id() { this.client @@ -1359,16 +1377,26 @@ impl Project { cx: &mut ModelContext, ) { let disk_diagnostics_token = language.disk_based_diagnostics_progress_token(); + let language_server_status = + if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { + status + } else { + return; + }; + match event { LanguageServerEvent::WorkStart { token } => { if Some(&token) == disk_diagnostics_token { - self.disk_based_diagnostics_started(cx); - self.broadcast_language_server_update( - language_server_id, - proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating( - proto::LspDiskBasedDiagnosticsUpdating {}, - ), - ); + language_server_status.pending_diagnostic_updates += 1; + if language_server_status.pending_diagnostic_updates == 1 { + self.disk_based_diagnostics_started(cx); + self.broadcast_language_server_update( + language_server_id, + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating( + proto::LspDiskBasedDiagnosticsUpdating {}, + ), + ); + } } else { self.on_lsp_work_start(language_server_id, token.clone(), cx); self.broadcast_language_server_update( @@ -1401,13 +1429,16 @@ impl Project { } LanguageServerEvent::WorkEnd { token } => { if Some(&token) == disk_diagnostics_token { - self.disk_based_diagnostics_finished(cx); - self.broadcast_language_server_update( - language_server_id, - proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( - proto::LspDiskBasedDiagnosticsUpdated {}, - ), - ); + language_server_status.pending_diagnostic_updates -= 1; + if language_server_status.pending_diagnostic_updates == 0 { + self.disk_based_diagnostics_finished(cx); + self.broadcast_language_server_update( + language_server_id, + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + ), + ); + } } else { self.on_lsp_work_end(language_server_id, token.clone(), cx); self.broadcast_language_server_update( @@ -1457,14 +1488,16 @@ impl Project { token: String, cx: &mut ModelContext, ) { - self.pending_language_server_work.insert( - (language_server_id, token), - LanguageServerProgress { - message: None, - percentage: None, - }, - ); - cx.notify(); + if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { + status.pending_work.insert( + token, + LanguageServerProgress { + message: None, + percentage: None, + }, + ); + cx.notify(); + } } fn on_lsp_work_progress( @@ -1474,9 +1507,10 @@ impl Project { progress: LanguageServerProgress, cx: &mut ModelContext, ) { - self.pending_language_server_work - .insert((language_server_id, token), progress); - cx.notify(); + if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { + status.pending_work.insert(token, progress); + cx.notify(); + } } fn on_lsp_work_end( @@ -1485,9 +1519,10 @@ impl Project { token: String, cx: &mut ModelContext, ) { - self.pending_language_server_work - .remove(&(language_server_id, token)); - cx.notify(); + if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { + status.pending_work.remove(&token); + cx.notify(); + } } fn broadcast_language_server_update( @@ -1506,15 +1541,8 @@ impl Project { } } - pub fn pending_language_server_work( - &self, - ) -> impl Iterator { - self.pending_language_server_work.iter().filter_map( - |((language_server_id, token), progress)| { - let name = self.language_server_names.get(language_server_id)?; - Some((name.as_str(), token.as_str(), progress)) - }, - ) + pub fn language_server_statuses(&self) -> impl Iterator { + self.language_server_statuses.values() } pub fn update_diagnostics( @@ -3266,8 +3294,14 @@ impl Project { .server .ok_or_else(|| anyhow!("invalid server"))?; this.update(&mut cx, |this, cx| { - this.language_server_names - .insert(server.id as usize, server.name); + this.language_server_statuses.insert( + server.id as usize, + LanguageServerStatus { + name: server.name, + pending_work: Default::default(), + pending_diagnostic_updates: 0, + }, + ); cx.notify(); }); Ok(()) diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index e2976824b5..6907b02948 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -1,12 +1,13 @@ use crate::{ItemViewHandle, Settings, StatusItemView}; use futures::StreamExt; +use gpui::AppContext; use gpui::{ action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext, RenderContext, View, ViewContext, }; use language::{LanguageRegistry, LanguageServerBinaryStatus}; use postage::watch; -use project::Project; +use project::{LanguageServerProgress, Project}; use std::fmt::Write; use std::sync::Arc; @@ -81,6 +82,27 @@ impl LspStatus { self.failed.clear(); cx.notify(); } + + fn pending_language_server_work<'a>( + &self, + cx: &'a AppContext, + ) -> impl Iterator { + self.project + .read(cx) + .language_server_statuses() + .filter_map(|status| { + if status.pending_work.is_empty() { + None + } else { + Some( + status.pending_work.iter().map(|(token, progress)| { + (status.name.as_str(), token.as_str(), progress) + }), + ) + } + }) + .flatten() + } } impl Entity for LspStatus { @@ -95,7 +117,7 @@ impl View for LspStatus { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let theme = &self.settings_rx.borrow().theme; - let mut pending_work = self.project.read(cx).pending_language_server_work(); + let mut pending_work = self.pending_language_server_work(cx); if let Some((lang_server_name, progress_token, progress)) = pending_work.next() { let mut message = lang_server_name.to_string(); From 7a454003fe18722f545402334d8632ab66ade6b7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Mar 2022 09:59:13 +0100 Subject: [PATCH 5/6] Show the last in-progress task from language servers --- Cargo.lock | 1 + crates/project/src/project.rs | 10 ++++++++-- crates/workspace/Cargo.toml | 1 + crates/workspace/src/lsp_status.rs | 15 ++++++++++----- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7aafce3bbb..499acae085 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5852,6 +5852,7 @@ dependencies = [ "postage", "project", "serde_json", + "smallvec", "theme", "util", ] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index dd4a17c13a..430c6875d0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -136,10 +136,11 @@ pub struct LanguageServerStatus { pending_diagnostic_updates: isize, } -#[derive(Clone, Default)] +#[derive(Clone, Debug)] pub struct LanguageServerProgress { pub message: Option, pub percentage: Option, + pub last_update_at: Instant, } #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] @@ -1253,6 +1254,7 @@ impl Project { percentage: report .percentage .map(|p| p as usize), + last_update_at: Instant::now(), }, }) .ok(); @@ -1494,6 +1496,7 @@ impl Project { LanguageServerProgress { message: None, percentage: None, + last_update_at: Instant::now(), }, ); cx.notify(); @@ -1541,7 +1544,9 @@ impl Project { } } - pub fn language_server_statuses(&self) -> impl Iterator { + pub fn language_server_statuses( + &self, + ) -> impl DoubleEndedIterator { self.language_server_statuses.values() } @@ -3332,6 +3337,7 @@ impl Project { LanguageServerProgress { message: payload.message, percentage: payload.percentage.map(|p| p as usize), + last_update_at: Instant::now(), }, cx, ); diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index d83cbf29d4..e92c4bf186 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -24,6 +24,7 @@ futures = "0.3" log = "0.4" parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } +smallvec = { version = "1.6", features = ["union"] } [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index 6907b02948..43b58bd02b 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -8,6 +8,8 @@ use gpui::{ use language::{LanguageRegistry, LanguageServerBinaryStatus}; use postage::watch; use project::{LanguageServerProgress, Project}; +use smallvec::SmallVec; +use std::cmp::Reverse; use std::fmt::Write; use std::sync::Arc; @@ -90,15 +92,18 @@ impl LspStatus { self.project .read(cx) .language_server_statuses() + .rev() .filter_map(|status| { if status.pending_work.is_empty() { None } else { - Some( - status.pending_work.iter().map(|(token, progress)| { - (status.name.as_str(), token.as_str(), progress) - }), - ) + let mut pending_work = status + .pending_work + .iter() + .map(|(token, progress)| (status.name.as_str(), token.as_str(), progress)) + .collect::>(); + pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at)); + Some(pending_work) } }) .flatten() From 18b1e9d35f789a26f5094ea5df855755c593ccde Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Mar 2022 10:02:37 +0100 Subject: [PATCH 6/6] Don't starve main thread when lots of messages/events arrive at once --- crates/client/src/client.rs | 3 +++ crates/project/src/project.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 62d2c6fb31..59110f73c6 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -631,6 +631,9 @@ impl Client { } else { log::info!("unhandled message {}", type_name); } + + // Don't starve the main thread when receiving lots of messages at once. + smol::future::yield_now().await; } } }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 430c6875d0..2f84f702be 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1278,6 +1278,9 @@ impl Project { this.update(&mut cx, |this, cx| { this.on_lsp_event(server_id, event, &language, cx) }); + + // Don't starve the main thread when lots of events arrive all at once. + smol::future::yield_now().await; } Some(()) }