Extract a `LanguageServerStatus` struct

Antonio Scandurra created

Change summary

crates/project/src/project.rs      | 130 ++++++++++++++++++++-----------
crates/workspace/src/lsp_status.rs |  26 +++++
2 files changed, 106 insertions(+), 50 deletions(-)

Detailed changes

crates/project/src/project.rs 🔗

@@ -51,8 +51,7 @@ pub struct Project {
     languages: Arc<LanguageRegistry>,
     language_servers: HashMap<(WorktreeId, Arc<str>), Arc<LanguageServer>>,
     started_language_servers: HashMap<(WorktreeId, Arc<str>), Task<Option<Arc<LanguageServer>>>>,
-    pending_language_server_work: BTreeMap<(usize, String), LanguageServerProgress>,
-    language_server_names: HashMap<usize, String>,
+    language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
     next_language_server_id: usize,
     client: Arc<client::Client>,
     user_store: ModelHandle<UserStore>,
@@ -131,6 +130,12 @@ enum LanguageServerEvent {
     DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
 }
 
+pub struct LanguageServerStatus {
+    pub name: String,
+    pub pending_work: BTreeMap<String, LanguageServerProgress>,
+    pending_diagnostic_updates: isize,
+}
+
 #[derive(Clone, Default)]
 pub struct LanguageServerProgress {
     pub message: Option<String>,
@@ -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<Self>,
     ) {
         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>,
     ) {
-        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>,
     ) {
-        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>,
     ) {
-        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<Item = (&str, &str, &LanguageServerProgress)> {
-        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<Item = &LanguageServerStatus> {
+        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(())

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<Item = (&'a str, &'a str, &'a LanguageServerProgress)> {
+        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<Self>) -> 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();