Display a "Checking..." message when running disk-based diagnostics

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/diagnostics/src/items.rs |  40 +++++++++----
crates/project/src/project.rs   |  46 +++++++++++++++
crates/project/src/worktree.rs  | 101 +++++++++++++++++++++++++++-------
crates/rpc/proto/zed.proto      |  54 ++++++++++--------
crates/rpc/src/proto.rs         |   2 
crates/server/src/rpc.rs        |  17 +++++
6 files changed, 200 insertions(+), 60 deletions(-)

Detailed changes

crates/diagnostics/src/items.rs 🔗

@@ -3,11 +3,13 @@ use gpui::{
 };
 use postage::watch;
 use project::Project;
+use std::fmt::Write;
 use workspace::{Settings, StatusItemView};
 
 pub struct DiagnosticSummary {
     settings: watch::Receiver<Settings>,
     summary: project::DiagnosticSummary,
+    in_progress: bool,
 }
 
 impl DiagnosticSummary {
@@ -16,16 +18,26 @@ impl DiagnosticSummary {
         settings: watch::Receiver<Settings>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        cx.subscribe(project, |this, project, event, cx| {
-            if let project::Event::DiskBasedDiagnosticsUpdated { .. } = event {
+        cx.subscribe(project, |this, project, event, cx| match event {
+            project::Event::DiskBasedDiagnosticsUpdated { .. } => {
                 this.summary = project.read(cx).diagnostic_summary(cx);
                 cx.notify();
             }
+            project::Event::DiskBasedDiagnosticsStarted => {
+                this.in_progress = true;
+                cx.notify();
+            }
+            project::Event::DiskBasedDiagnosticsFinished => {
+                this.in_progress = false;
+                cx.notify();
+            }
+            _ => {}
         })
         .detach();
         Self {
             settings,
             summary: project.read(cx).diagnostic_summary(cx),
+            in_progress: project.read(cx).is_running_disk_based_diagnostics(),
         }
     }
 }
@@ -43,17 +55,21 @@ impl View for DiagnosticSummary {
         enum Tag {}
 
         let theme = &self.settings.borrow().theme.project_diagnostics;
+        let mut message = String::new();
+        if self.in_progress {
+            message.push_str("Checking... ");
+        }
+        write!(
+            message,
+            "Errors: {}, Warnings: {}",
+            self.summary.error_count, self.summary.warning_count
+        )
+        .unwrap();
         MouseEventHandler::new::<Tag, _, _, _>(0, cx, |_, _| {
-            Label::new(
-                format!(
-                    "Errors: {}, Warnings: {}",
-                    self.summary.error_count, self.summary.warning_count
-                ),
-                theme.status_bar_item.text.clone(),
-            )
-            .contained()
-            .with_style(theme.status_bar_item.container)
-            .boxed()
+            Label::new(message, theme.status_bar_item.text.clone())
+                .contained()
+                .with_style(theme.status_bar_item.container)
+                .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
         .on_click(|cx| cx.dispatch_action(crate::Deploy))

crates/project/src/project.rs 🔗

@@ -33,6 +33,7 @@ pub struct Project {
     client_state: ProjectClientState,
     collaborators: HashMap<PeerId, Collaborator>,
     subscriptions: Vec<client::Subscription>,
+    pending_disk_based_diagnostics: isize,
 }
 
 enum ProjectClientState {
@@ -60,7 +61,9 @@ pub struct Collaborator {
 pub enum Event {
     ActiveEntryChanged(Option<ProjectEntry>),
     WorktreeRemoved(WorktreeId),
+    DiskBasedDiagnosticsStarted,
     DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId },
+    DiskBasedDiagnosticsFinished,
     DiagnosticsUpdated(ProjectPath),
 }
 
@@ -187,6 +190,7 @@ impl Project {
                 client,
                 user_store,
                 fs,
+                pending_disk_based_diagnostics: 0,
             }
         })
     }
@@ -259,6 +263,11 @@ impl Project {
                         cx,
                         Self::handle_update_diagnostic_summary,
                     ),
+                    client.subscribe_to_entity(
+                        remote_id,
+                        cx,
+                        Self::handle_disk_based_diagnostics_updating,
+                    ),
                     client.subscribe_to_entity(
                         remote_id,
                         cx,
@@ -273,6 +282,7 @@ impl Project {
                     remote_id,
                     replica_id,
                 },
+                pending_disk_based_diagnostics: 0,
             };
             for worktree in worktrees {
                 this.add_worktree(worktree, cx);
@@ -506,17 +516,29 @@ impl Project {
 
     fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
         cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
-        cx.subscribe(&worktree, |_, worktree, event, cx| match event {
+        cx.subscribe(&worktree, move |this, worktree, event, cx| match event {
             worktree::Event::DiagnosticsUpdated(path) => {
                 cx.emit(Event::DiagnosticsUpdated(ProjectPath {
                     worktree_id: worktree.read(cx).id(),
                     path: path.clone(),
                 }));
             }
+            worktree::Event::DiskBasedDiagnosticsUpdating => {
+                if this.pending_disk_based_diagnostics == 0 {
+                    cx.emit(Event::DiskBasedDiagnosticsStarted);
+                }
+                this.pending_disk_based_diagnostics += 1;
+            }
             worktree::Event::DiskBasedDiagnosticsUpdated => {
+                this.pending_disk_based_diagnostics -= 1;
                 cx.emit(Event::DiskBasedDiagnosticsUpdated {
                     worktree_id: worktree.read(cx).id(),
                 });
+                if this.pending_disk_based_diagnostics == 0 {
+                    if this.pending_disk_based_diagnostics == 0 {
+                        cx.emit(Event::DiskBasedDiagnosticsFinished);
+                    }
+                }
             }
         })
         .detach();
@@ -539,6 +561,10 @@ impl Project {
         }
     }
 
+    pub fn is_running_disk_based_diagnostics(&self) -> bool {
+        self.pending_disk_based_diagnostics > 0
+    }
+
     pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
         let mut summary = DiagnosticSummary::default();
         for (_, path_summary) in self.diagnostic_summaries(cx) {
@@ -716,6 +742,24 @@ impl Project {
         Ok(())
     }
 
+    fn handle_disk_based_diagnostics_updating(
+        &mut self,
+        envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
+        _: Arc<Client>,
+        cx: &mut ModelContext<Self>,
+    ) -> 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_updating(cx);
+            });
+        }
+        Ok(())
+    }
+
     fn handle_disk_based_diagnostics_updated(
         &mut self,
         envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,

crates/project/src/worktree.rs 🔗

@@ -67,6 +67,7 @@ pub enum Worktree {
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub enum Event {
+    DiskBasedDiagnosticsUpdating,
     DiskBasedDiagnosticsUpdated,
     DiagnosticsUpdated(Arc<Path>),
 }
@@ -1133,6 +1134,11 @@ impl LocalWorktree {
             .log_err()
             .flatten()
         {
+            enum DiagnosticProgress {
+                Updating,
+                Updated,
+            }
+
             let disk_based_sources = language
                 .disk_based_diagnostic_sources()
                 .cloned()
@@ -1155,10 +1161,21 @@ impl LocalWorktree {
                     while let Ok(diagnostics) = diagnostics_rx.recv().await {
                         if let Some(handle) = cx.read(|cx| this.upgrade(cx)) {
                             handle.update(&mut cx, |this, cx| {
+                                if !has_disk_based_diagnostic_progress_token {
+                                    smol::block_on(
+                                        disk_based_diagnostics_done_tx
+                                            .send(DiagnosticProgress::Updating),
+                                    )
+                                    .ok();
+                                }
                                 this.update_diagnostics(diagnostics, &disk_based_sources, cx)
                                     .log_err();
                                 if !has_disk_based_diagnostic_progress_token {
-                                    smol::block_on(disk_based_diagnostics_done_tx.send(())).ok();
+                                    smol::block_on(
+                                        disk_based_diagnostics_done_tx
+                                            .send(DiagnosticProgress::Updated),
+                                    )
+                                    .ok();
                                 }
                             })
                         } else {
@@ -1181,13 +1198,23 @@ impl LocalWorktree {
                         match params.value {
                             lsp::ProgressParamsValue::WorkDone(progress) => match progress {
                                 lsp::WorkDoneProgress::Begin(_) => {
+                                    if pending_disk_based_diagnostics == 0 {
+                                        smol::block_on(
+                                            disk_based_diagnostics_done_tx
+                                                .send(DiagnosticProgress::Updating),
+                                        )
+                                        .ok();
+                                    }
                                     pending_disk_based_diagnostics += 1;
                                 }
                                 lsp::WorkDoneProgress::End(_) => {
                                     pending_disk_based_diagnostics -= 1;
                                     if pending_disk_based_diagnostics == 0 {
-                                        smol::block_on(disk_based_diagnostics_done_tx.send(()))
-                                            .ok();
+                                        smol::block_on(
+                                            disk_based_diagnostics_done_tx
+                                                .send(DiagnosticProgress::Updated),
+                                        )
+                                        .ok();
                                     }
                                 }
                                 _ => {}
@@ -1198,21 +1225,41 @@ impl LocalWorktree {
                 .detach();
             let rpc = self.client.clone();
             cx.spawn_weak(|this, mut cx| async move {
-                while let Ok(()) = disk_based_diagnostics_done_rx.recv().await {
+                while let Ok(progress) = disk_based_diagnostics_done_rx.recv().await {
                     if let Some(handle) = cx.read(|cx| this.upgrade(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();
+                        match progress {
+                            DiagnosticProgress::Updating => {
+                                let message = handle.update(&mut cx, |this, cx| {
+                                    cx.emit(Event::DiskBasedDiagnosticsUpdating);
+                                    let this = this.as_local().unwrap();
+                                    this.share.as_ref().map(|share| {
+                                        proto::DiskBasedDiagnosticsUpdating {
+                                            project_id: share.project_id,
+                                            worktree_id: this.id().to_proto(),
+                                        }
+                                    })
+                                });
+
+                                if let Some(message) = message {
+                                    rpc.send(message).await.log_err();
+                                }
+                            }
+                            DiagnosticProgress::Updated => {
+                                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;
@@ -1691,6 +1738,10 @@ impl RemoteWorktree {
         }
     }
 
+    pub fn disk_based_diagnostics_updating(&self, cx: &mut ModelContext<Worktree>) {
+        cx.emit(Event::DiskBasedDiagnosticsUpdating);
+    }
+
     pub fn disk_based_diagnostics_updated(&self, cx: &mut ModelContext<Worktree>) {
         cx.emit(Event::DiskBasedDiagnosticsUpdated);
     }
@@ -3848,6 +3899,11 @@ mod tests {
         let mut events = subscribe(&tree, &mut cx);
 
         fake_server.start_progress(&progress_token).await;
+        assert_eq!(
+            events.next().await.unwrap(),
+            Event::DiskBasedDiagnosticsUpdating
+        );
+
         fake_server.start_progress(&progress_token).await;
         fake_server.end_progress(&progress_token).await;
         fake_server.start_progress(&progress_token).await;
@@ -3864,18 +3920,17 @@ mod tests {
                 }],
             })
             .await;
-
-        let event = events.next().await.unwrap();
         assert_eq!(
-            event,
+            events.next().await.unwrap(),
             Event::DiagnosticsUpdated(Arc::from(Path::new("a.rs")))
         );
 
         fake_server.end_progress(&progress_token).await;
         fake_server.end_progress(&progress_token).await;
-
-        let event = events.next().await.unwrap();
-        assert_eq!(event, Event::DiskBasedDiagnosticsUpdated);
+        assert_eq!(
+            events.next().await.unwrap(),
+            Event::DiskBasedDiagnosticsUpdated
+        );
 
         let buffer = tree
             .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx))

crates/rpc/proto/zed.proto 🔗

@@ -26,30 +26,31 @@ message Envelope {
         ShareWorktree share_worktree = 19;
         UpdateWorktree update_worktree = 20;
         UpdateDiagnosticSummary update_diagnostic_summary = 21;
-        DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 22;
-
-        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 = 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 = 39;
-
-        GetUsers get_users = 40;
-        GetUsersResponse get_users_response = 41;
+        DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 22;
+        DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 23;
+
+        OpenBuffer open_buffer = 24;
+        OpenBufferResponse open_buffer_response = 25;
+        CloseBuffer close_buffer = 26;
+        UpdateBuffer update_buffer = 27;
+        SaveBuffer save_buffer = 28;
+        BufferSaved buffer_saved = 29;
+
+        GetChannels get_channels = 30;
+        GetChannelsResponse get_channels_response = 31;
+        JoinChannel join_channel = 32;
+        JoinChannelResponse join_channel_response = 33;
+        LeaveChannel leave_channel = 34;
+        SendChannelMessage send_channel_message = 35;
+        SendChannelMessageResponse send_channel_message_response = 36;
+        ChannelMessageSent channel_message_sent = 37;
+        GetChannelMessages get_channel_messages = 38;
+        GetChannelMessagesResponse get_channel_messages_response = 39;
+
+        UpdateContacts update_contacts = 40;
+
+        GetUsers get_users = 41;
+        GetUsersResponse get_users_response = 42;
     }
 }
 
@@ -181,6 +182,11 @@ message DiagnosticSummary {
     uint32 hint_count = 7;
 }
 
+message DiskBasedDiagnosticsUpdating {
+    uint64 project_id = 1;
+    uint64 worktree_id = 2;
+}
+
 message DiskBasedDiagnosticsUpdated {
     uint64 project_id = 1;
     uint64 worktree_id = 2;

crates/rpc/src/proto.rs 🔗

@@ -126,6 +126,7 @@ messages!(
     ChannelMessageSent,
     CloseBuffer,
     DiskBasedDiagnosticsUpdated,
+    DiskBasedDiagnosticsUpdating,
     Error,
     GetChannelMessages,
     GetChannelMessagesResponse,
@@ -183,6 +184,7 @@ entity_messages!(
     BufferSaved,
     CloseBuffer,
     DiskBasedDiagnosticsUpdated,
+    DiskBasedDiagnosticsUpdating,
     JoinProject,
     LeaveProject,
     OpenBuffer,

crates/server/src/rpc.rs 🔗

@@ -72,6 +72,7 @@ impl Server {
             .add_handler(Server::share_worktree)
             .add_handler(Server::update_worktree)
             .add_handler(Server::update_diagnostic_summary)
+            .add_handler(Server::disk_based_diagnostics_updating)
             .add_handler(Server::disk_based_diagnostics_updated)
             .add_handler(Server::open_buffer)
             .add_handler(Server::close_buffer)
@@ -556,6 +557,22 @@ impl Server {
         Ok(())
     }
 
+    async fn disk_based_diagnostics_updating(
+        self: Arc<Server>,
+        request: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
+    ) -> 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<Server>,
         request: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,