From baee6d03425ee8bfe3f78b1b8e0b2b65ad697b3d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Feb 2023 15:53:37 +0100 Subject: [PATCH] Simulate disk-based diagnostics finishing 1s after saving buffer Previously, we would simulate disk-based diagnostics finishing after saving a buffer. However, the language server may produce diagnostics right after emitting the event, causing the diagnostics status bar item to not reflect the latest state of the buffer. With this change, we will instead simulate disk-based diagnostics finishing after 1s after saving the buffer (only for language servers that don't have the concept of disk-based diagnostics, such as TypeScript). This ensures we always reflect the latest state and doesn't cause the UI to flicker as a result of the LSP sending us diagnostics after every input. --- crates/project/src/project.rs | 79 ++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8f6b867b1210d950b4675973783604a752ade650..ec6a99edecd9dd65c0bb9fcc8e1ecf71f76e2bb8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -59,7 +59,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::Instant, + time::{Duration, Instant}, }; use terminal::{Terminal, TerminalBuilder}; use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _}; @@ -185,6 +185,7 @@ pub enum LanguageServerState { language: Arc, adapter: Arc, server: Arc, + simulate_disk_based_diagnostics_completion: Option>, }, } @@ -1716,19 +1717,39 @@ impl Project { .log_err(); } - // After saving a buffer, simulate disk-based diagnostics being finished for languages - // that don't support a disk-based progress token. - let (lsp_adapter, language_server) = - self.language_server_for_buffer(buffer.read(cx), cx)?; - if lsp_adapter.disk_based_diagnostics_progress_token.is_none() { - let server_id = language_server.server_id(); - self.disk_based_diagnostics_finished(server_id, cx); - self.broadcast_language_server_update( - server_id, - proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( - proto::LspDiskBasedDiagnosticsUpdated {}, - ), - ); + let language_server_id = self.language_server_id_for_buffer(buffer.read(cx), cx)?; + if let Some(LanguageServerState::Running { + adapter, + simulate_disk_based_diagnostics_completion, + .. + }) = self.language_servers.get_mut(&language_server_id) + { + // After saving a buffer using a language server that doesn't provide + // a disk-based progress token, kick off a timer that will reset every + // time the buffer is saved. If the timer eventually fires, simulate + // disk-based diagnostics being finished so that other pieces of UI + // (e.g., project diagnostics view, diagnostic status bar) can update. + // We don't emit an event right away because the language server might take + // some time to publish diagnostics. + if adapter.disk_based_diagnostics_progress_token.is_none() { + const DISK_BASED_DIAGNOSTICS_DEBOUNCE: Duration = Duration::from_secs(1); + + let task = cx.spawn_weak(|this, mut cx| async move { + cx.background().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx | { + this.disk_based_diagnostics_finished(language_server_id, cx); + this.broadcast_language_server_update( + language_server_id, + proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( + proto::LspDiskBasedDiagnosticsUpdated {}, + ), + ); + }); + } + }); + *simulate_disk_based_diagnostics_completion = Some(task); + } } } _ => {} @@ -1749,6 +1770,7 @@ impl Project { adapter, language, server, + .. }) = self.language_servers.get(id) { return Some((adapter, language, server)); @@ -2035,6 +2057,7 @@ impl Project { adapter: adapter.clone(), language, server: language_server.clone(), + simulate_disk_based_diagnostics_completion: None, }, ); this.language_server_statuses.insert( @@ -3105,6 +3128,7 @@ impl Project { adapter, language, server, + .. }) = self.language_servers.get(server_id) { let adapter = adapter.clone(); @@ -6178,22 +6202,27 @@ impl Project { buffer: &Buffer, cx: &AppContext, ) -> Option<(&Arc, &Arc)> { + let server_id = self.language_server_id_for_buffer(buffer, cx)?; + let server = self.language_servers.get(&server_id)?; + if let LanguageServerState::Running { + adapter, server, .. + } = server + { + Some((adapter, server)) + } else { + None + } + } + + fn language_server_id_for_buffer(&self, buffer: &Buffer, cx: &AppContext) -> Option { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { let name = language.lsp_adapter()?.name.clone(); let worktree_id = file.worktree_id(cx); let key = (worktree_id, name); - - if let Some(server_id) = self.language_server_ids.get(&key) { - if let Some(LanguageServerState::Running { - adapter, server, .. - }) = self.language_servers.get(server_id) - { - return Some((adapter, server)); - } - } + self.language_server_ids.get(&key).copied() + } else { + None } - - None } }