From d8b888c9cb366870748b318b157209c84a714812 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 4 Jan 2022 14:29:22 -0800 Subject: [PATCH] Replicate diagnostic summaries Co-Authored-By: Antonio Scandurra --- crates/gpui/src/app.rs | 4 ++ crates/gpui/src/executor.rs | 20 +++++++-- crates/project/src/project.rs | 44 ++++++++++++++++++- crates/project/src/worktree.rs | 77 ++++++++++++++++++++++++++++++---- crates/rpc/proto/zed.proto | 52 +++++++++++++---------- crates/rpc/src/proto.rs | 10 +++-- crates/server/src/rpc.rs | 74 ++++++++++++++++++++++++++------ 7 files changed, 231 insertions(+), 50 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a14664a9f8..e1b919e5fe 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2672,6 +2672,10 @@ impl ModelHandle { } } + if cx.borrow_mut().foreground().would_park() { + panic!("parked while waiting on condition"); + } + rx.recv() .await .expect("model dropped with pending condition"); diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 23b870c11f..3283ab7553 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -206,10 +206,7 @@ impl Deterministic { } let state = self.state.lock(); - if state.scheduled_from_foreground.is_empty() - && state.scheduled_from_background.is_empty() - && state.spawned_from_foreground.is_empty() - { + if state.would_park() { return None; } } @@ -261,6 +258,14 @@ impl Deterministic { } } +impl DeterministicState { + fn would_park(&self) -> bool { + self.scheduled_from_foreground.is_empty() + && self.scheduled_from_background.is_empty() + && self.spawned_from_foreground.is_empty() + } +} + #[derive(Default)] struct Trace { executed: Vec, @@ -433,6 +438,13 @@ impl Foreground { *any_value.downcast().unwrap() } + pub fn would_park(&self) -> bool { + match self { + Self::Deterministic(executor) => executor.state.lock().would_park(), + _ => panic!("this method can only be called on a deterministic executor"), + } + } + pub fn forbid_parking(&self) { match self { Self::Deterministic(executor) => { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c05e0c8f4a..8ce3504e5d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -70,7 +70,7 @@ pub struct ProjectPath { pub path: Arc, } -#[derive(Clone)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct DiagnosticSummary { pub error_count: usize, pub warning_count: usize, @@ -243,6 +243,12 @@ impl Project { client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree), client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree), client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree), + client.subscribe_to_entity(remote_id, cx, Self::handle_update_diagnostic_summary), + client.subscribe_to_entity( + remote_id, + cx, + Self::handle_disk_based_diagnostics_updated, + ), client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer), client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), ], @@ -661,6 +667,42 @@ impl Project { Ok(()) } + fn handle_update_diagnostic_summary( + &mut self, + envelope: TypedEnvelope, + _: Arc, + cx: &mut ModelContext, + ) -> Result<()> { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { + worktree.update(cx, |worktree, cx| { + worktree + .as_remote_mut() + .unwrap() + .update_diagnostic_summary(envelope, cx); + }); + } + Ok(()) + } + + fn handle_disk_based_diagnostics_updated( + &mut self, + envelope: TypedEnvelope, + _: Arc, + cx: &mut ModelContext, + ) -> Result<()> { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { + worktree.update(cx, |worktree, cx| { + worktree + .as_remote() + .unwrap() + .disk_based_diagnostics_updated(cx); + }); + } + Ok(()) + } + pub fn handle_update_buffer( &mut self, envelope: TypedEnvelope, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 030e55deae..f7538f6294 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -777,10 +777,38 @@ impl Worktree { } let this = self.as_local_mut().unwrap(); + let summary = DiagnosticSummary::new(&diagnostics); this.diagnostic_summaries - .insert(worktree_path.clone(), DiagnosticSummary::new(&diagnostics)); + .insert(worktree_path.clone(), summary.clone()); this.diagnostics.insert(worktree_path.clone(), diagnostics); + cx.emit(Event::DiagnosticsUpdated(worktree_path.clone())); + + if let Some(share) = this.share.as_ref() { + cx.foreground() + .spawn({ + let client = this.client.clone(); + let project_id = share.project_id; + let worktree_id = this.id().to_proto(); + let path = worktree_path.to_string_lossy().to_string(); + async move { + client + .send(proto::UpdateDiagnosticSummary { + project_id, + worktree_id, + path, + error_count: summary.error_count as u32, + warning_count: summary.warning_count as u32, + info_count: summary.info_count as u32, + hint_count: summary.hint_count as u32, + }) + .await + .log_err() + } + }) + .detach(); + } + Ok(()) } @@ -1063,6 +1091,8 @@ impl LocalWorktree { let disk_based_diagnostics_progress_token = language.disk_based_diagnostics_progress_token().cloned(); let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); + let (disk_based_diagnostics_done_tx, disk_based_diagnostics_done_rx) = + smol::channel::unbounded(); language_server .on_notification::(move |params| { smol::block_on(diagnostics_tx.send(params)).ok(); @@ -1071,6 +1101,7 @@ impl LocalWorktree { cx.spawn_weak(|this, mut cx| { let has_disk_based_diagnostic_progress_token = disk_based_diagnostics_progress_token.is_some(); + let disk_based_diagnostics_done_tx = disk_based_diagnostics_done_tx.clone(); async move { while let Ok(diagnostics) = diagnostics_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { @@ -1078,9 +1109,9 @@ impl LocalWorktree { this.update_diagnostics(diagnostics, &disk_based_sources, cx) .log_err(); if !has_disk_based_diagnostic_progress_token { - cx.emit(Event::DiskBasedDiagnosticsUpdated); + smol::block_on(disk_based_diagnostics_done_tx.send(())).ok(); } - }); + }) } else { break; } @@ -1089,8 +1120,6 @@ impl LocalWorktree { }) .detach(); - let (mut disk_based_diagnostics_done_tx, mut disk_based_diagnostics_done_rx) = - watch::channel_with(()); language_server .on_notification::(move |params| { let token = match params.token { @@ -1110,12 +1139,24 @@ impl LocalWorktree { } }) .detach(); + let rpc = self.client.clone(); cx.spawn_weak(|this, mut cx| async move { - while let Some(()) = disk_based_diagnostics_done_rx.recv().await { + while let Ok(()) = disk_based_diagnostics_done_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |_, cx| { + let message = handle.update(&mut cx, |this, cx| { cx.emit(Event::DiskBasedDiagnosticsUpdated); + let this = this.as_local().unwrap(); + this.share + .as_ref() + .map(|share| proto::DiskBasedDiagnosticsUpdated { + project_id: share.project_id, + worktree_id: this.id().to_proto(), + }) }); + + if let Some(message) = message { + rpc.send(message).await.log_err(); + } } else { break; } @@ -1572,6 +1613,28 @@ impl RemoteWorktree { Ok(()) } + pub fn update_diagnostic_summary( + &mut self, + envelope: TypedEnvelope, + cx: &mut ModelContext, + ) { + let path: Arc = Path::new(&envelope.payload.path).into(); + self.diagnostic_summaries.insert( + path.clone(), + DiagnosticSummary { + error_count: envelope.payload.error_count as usize, + warning_count: envelope.payload.warning_count as usize, + info_count: envelope.payload.info_count as usize, + hint_count: envelope.payload.hint_count as usize, + }, + ); + cx.emit(Event::DiagnosticsUpdated(path)); + } + + pub fn disk_based_diagnostics_updated(&self, cx: &mut ModelContext) { + cx.emit(Event::DiskBasedDiagnosticsUpdated); + } + pub fn remove_collaborator(&mut self, replica_id: ReplicaId, cx: &mut ModelContext) { for (_, buffer) in &self.open_buffers { if let Some(buffer) = buffer.upgrade(cx) { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 5ef34960e7..9ec5f90719 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -23,32 +23,33 @@ message Envelope { RegisterWorktree register_worktree = 17; UnregisterWorktree unregister_worktree = 18; - ShareWorktree share_worktree = 100; - UpdateWorktree update_worktree = 19; - UpdateDiagnosticSummary update_diagnostic_summary = 20; + ShareWorktree share_worktree = 19; + UpdateWorktree update_worktree = 20; + UpdateDiagnosticSummary update_diagnostic_summary = 21; + DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 22; - OpenBuffer open_buffer = 22; - OpenBufferResponse open_buffer_response = 23; - CloseBuffer close_buffer = 24; - UpdateBuffer update_buffer = 25; - SaveBuffer save_buffer = 26; - BufferSaved buffer_saved = 27; + OpenBuffer open_buffer = 23; + OpenBufferResponse open_buffer_response = 24; + CloseBuffer close_buffer = 25; + UpdateBuffer update_buffer = 26; + SaveBuffer save_buffer = 27; + BufferSaved buffer_saved = 28; - GetChannels get_channels = 28; - GetChannelsResponse get_channels_response = 29; - JoinChannel join_channel = 30; - JoinChannelResponse join_channel_response = 31; - LeaveChannel leave_channel = 32; - SendChannelMessage send_channel_message = 33; - SendChannelMessageResponse send_channel_message_response = 34; - ChannelMessageSent channel_message_sent = 35; - GetChannelMessages get_channel_messages = 36; - GetChannelMessagesResponse get_channel_messages_response = 37; + GetChannels get_channels = 29; + GetChannelsResponse get_channels_response = 30; + JoinChannel join_channel = 31; + JoinChannelResponse join_channel_response = 32; + LeaveChannel leave_channel = 33; + SendChannelMessage send_channel_message = 34; + SendChannelMessageResponse send_channel_message_response = 35; + ChannelMessageSent channel_message_sent = 36; + GetChannelMessages get_channel_messages = 37; + GetChannelMessagesResponse get_channel_messages_response = 38; - UpdateContacts update_contacts = 38; + UpdateContacts update_contacts = 39; - GetUsers get_users = 39; - GetUsersResponse get_users_response = 40; + GetUsers get_users = 40; + GetUsersResponse get_users_response = 41; } } @@ -172,6 +173,13 @@ message UpdateDiagnosticSummary { string path = 3; uint32 error_count = 4; uint32 warning_count = 5; + uint32 info_count = 6; + uint32 hint_count = 7; +} + +message DiskBasedDiagnosticsUpdated { + uint64 project_id = 1; + uint64 worktree_id = 2; } message GetChannels {} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 9274049a7b..2cabdc9218 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -125,6 +125,7 @@ messages!( BufferSaved, ChannelMessageSent, CloseBuffer, + DiskBasedDiagnosticsUpdated, Error, GetChannelMessages, GetChannelMessagesResponse, @@ -155,6 +156,7 @@ messages!( UnshareProject, UpdateBuffer, UpdateContacts, + UpdateDiagnosticSummary, UpdateWorktree, ); @@ -178,17 +180,19 @@ request_messages!( entity_messages!( project_id, AddProjectCollaborator, - RemoveProjectCollaborator, + BufferSaved, + CloseBuffer, + DiskBasedDiagnosticsUpdated, JoinProject, LeaveProject, - BufferSaved, OpenBuffer, - CloseBuffer, + RemoveProjectCollaborator, SaveBuffer, ShareWorktree, UnregisterWorktree, UnshareProject, UpdateBuffer, + UpdateDiagnosticSummary, UpdateWorktree, ); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 0b1b7e7aab..7e955ca335 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -71,6 +71,8 @@ impl Server { .add_handler(Server::unregister_worktree) .add_handler(Server::share_worktree) .add_handler(Server::update_worktree) + .add_handler(Server::update_diagnostic_summary) + .add_handler(Server::disk_based_diagnostics_updated) .add_handler(Server::open_buffer) .add_handler(Server::close_buffer) .add_handler(Server::update_buffer) @@ -517,6 +519,38 @@ impl Server { Ok(()) } + async fn update_diagnostic_summary( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result<()> { + let receiver_ids = self + .state() + .project_connection_ids(request.payload.project_id, request.sender_id) + .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?; + broadcast(request.sender_id, receiver_ids, |connection_id| { + self.peer + .forward_send(request.sender_id, connection_id, request.payload.clone()) + }) + .await?; + Ok(()) + } + + async fn disk_based_diagnostics_updated( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result<()> { + let receiver_ids = self + .state() + .project_connection_ids(request.payload.project_id, request.sender_id) + .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?; + broadcast(request.sender_id, receiver_ids, |connection_id| { + self.peer + .forward_send(request.sender_id, connection_id, request.payload.clone()) + }) + .await?; + Ok(()) + } + async fn open_buffer( self: Arc, request: TypedEnvelope, @@ -1026,7 +1060,7 @@ mod tests { LanguageRegistry, LanguageServerConfig, Point, }, lsp, - project::Project, + project::{DiagnosticSummary, Project}, }; #[gpui::test] @@ -1781,6 +1815,19 @@ mod tests { .await .unwrap(); + // Join the worktree as client B. + let project_b = Project::remote( + project_id, + client_b.clone(), + client_b.user_store.clone(), + lang_registry.clone(), + fs.clone(), + &mut cx_b.to_async(), + ) + .await + .unwrap(); + let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + // Simulate a language server reporting errors for a file. fake_language_server .notify::(lsp::PublishDiagnosticsParams { @@ -1806,18 +1853,19 @@ mod tests { }) .await; - // Join the worktree as client B. - let project_b = Project::remote( - project_id, - client_b.clone(), - client_b.user_store.clone(), - lang_registry.clone(), - fs.clone(), - &mut cx_b.to_async(), - ) - .await - .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + worktree_b + .condition(&cx_b, |worktree, _| { + worktree.diagnostic_summaries().collect::>() + == &[( + Arc::from(Path::new("a.rs")), + DiagnosticSummary { + error_count: 1, + warning_count: 1, + ..Default::default() + }, + )] + }) + .await; // Open the file with the errors. let buffer_b = cx_b