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.
This commit is contained in:
Antonio Scandurra 2023-02-16 15:53:37 +01:00
parent fbd23986e3
commit baee6d0342

View File

@ -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<Language>,
adapter: Arc<CachedLspAdapter>,
server: Arc<LanguageServer>,
simulate_disk_based_diagnostics_completion: Option<Task<()>>,
},
}
@ -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<CachedLspAdapter>, &Arc<LanguageServer>)> {
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<usize> {
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
}
}