diff --git a/crates/ide-lsp/src/handler.rs b/crates/ide-lsp/src/handler.rs index 2bb81f386..4ba4e2202 100644 --- a/crates/ide-lsp/src/handler.rs +++ b/crates/ide-lsp/src/handler.rs @@ -250,7 +250,14 @@ impl ClientHandler { { return; } - let shown = self.server.set_diagnostics(path.clone(), params.diagnostics); + // A late answer for an older version of the text is shown, but isn't fresh (`check_files`). + let obsolete = + params.version.is_some_and(|v| mgr.docs.lock().values().any(|d| d.path == path && d.version > v)); + let shown = if obsolete { + self.server.set_obsolete_diagnostics(path.clone(), params.diagnostics) + } else { + self.server.set_diagnostics(path.clone(), params.diagnostics) + }; mgr.emit_diagnostics(&self.server, path, shown, self.encoding()); } diff --git a/crates/ide-lsp/src/server.rs b/crates/ide-lsp/src/server.rs index 90500e6c8..df91c91a5 100644 --- a/crates/ide-lsp/src/server.rs +++ b/crates/ide-lsp/src/server.rs @@ -770,6 +770,20 @@ impl Server { self.combine(path) } + /// [`Server::set_diagnostics`] for a publication tagged with a document version older than + /// the text the server has now: shown, but not an answer for the current text, so + /// [`Server::last_published`] keeps its time. + pub fn set_obsolete_diagnostics(&self, path: PathBuf, items: Vec) -> Vec { + let before = self.last_published(&path); + let shown = self.set_diagnostics(path.clone(), items); + let mut published = self.published.lock(); + match before { + Some(at) => published.insert(path, at), + None => published.remove(&path), + }; + shown + } + /// Records a pulled report for `path` (`None` items: unchanged since `result_id`'s /// predecessor); returns what the file now shows. pub fn set_pulled( diff --git a/crates/ide-lsp/tests/mock_server.rs b/crates/ide-lsp/tests/mock_server.rs index e3837cebd..f906fb217 100644 --- a/crates/ide-lsp/tests/mock_server.rs +++ b/crates/ide-lsp/tests/mock_server.rs @@ -2210,6 +2210,19 @@ async fn checking_a_document_saves_it_and_waits_for_fresh_diagnostics() { h.mgr.did_change(doc, 2, &[change(rng(0, 0, 0, 3), "good")], &|| "good\n".to_owned()); let reloaded = h.mgr.check_document(doc, None, Duration::from_millis(800), &cancel).await.unwrap(); assert!(reloaded.servers[0].incomplete, "{:?}", reloaded.servers); + + // A late publication tagged with the old version is no answer for the current text. + s.peer() + .notify("textDocument/publishDiagnostics", json!({ "uri": uri_of(&path), "version": 1, "diagnostics": [] })) + .unwrap(); + let late = h.mgr.check_document(doc, None, Duration::from_millis(800), &cancel).await.unwrap(); + assert!(late.servers[0].incomplete, "{:?}", late.servers); + // One for the current version is. + s.peer() + .notify("textDocument/publishDiagnostics", json!({ "uri": uri_of(&path), "version": 2, "diagnostics": [] })) + .unwrap(); + let fresh = h.mgr.check_document(doc, None, Duration::from_secs(5), &cancel).await.unwrap(); + assert!(fresh.servers[0].checked && !fresh.servers[0].incomplete, "{:?}", fresh.servers); } #[tokio::test(flavor = "multi_thread")]