Skip to content

Commit 891867b

Browse files
committed
fix: correctly update diagnostics when files are opened and closed
Basically, this tracks the changes to `subscriptions` we use when issuing a publish_diagnostics.
1 parent 4106792 commit 891867b

File tree

2 files changed

+76
-73
lines changed

2 files changed

+76
-73
lines changed

crates/rust-analyzer/src/main_loop.rs

Lines changed: 66 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -406,25 +406,49 @@ impl GlobalState {
406406
}
407407

408408
let state_changed = self.process_changes();
409+
let memdocs_added_or_removed = self.mem_docs.take_changes();
409410

410-
if self.is_quiescent() && !was_quiescent {
411-
for flycheck in &self.flycheck {
412-
flycheck.update();
411+
if self.is_quiescent() {
412+
if !was_quiescent {
413+
for flycheck in &self.flycheck {
414+
flycheck.update();
415+
}
413416
}
414-
}
415417

416-
if self.is_quiescent() && (!was_quiescent || state_changed) {
417-
self.update_file_notifications_on_threadpool();
418+
if !was_quiescent || state_changed {
419+
// Ensure that only one cache priming task can run at a time
420+
self.prime_caches_queue.request_op();
421+
if self.prime_caches_queue.should_start_op() {
422+
self.task_pool.handle.spawn_with_sender({
423+
let snap = self.snapshot();
424+
move |sender| {
425+
let cb = |progress| {
426+
sender.send(Task::PrimeCaches(progress)).unwrap();
427+
};
428+
match snap.analysis.prime_caches(cb) {
429+
Ok(()) => (),
430+
Err(_canceled) => (),
431+
}
432+
}
433+
});
434+
}
418435

419-
// Refresh semantic tokens if the client supports it.
420-
if self.config.semantic_tokens_refresh() {
421-
self.semantic_tokens_cache.lock().clear();
422-
self.send_request::<lsp_types::request::SemanticTokensRefesh>((), |_, _| ());
436+
// Refresh semantic tokens if the client supports it.
437+
if self.config.semantic_tokens_refresh() {
438+
self.semantic_tokens_cache.lock().clear();
439+
self.send_request::<lsp_types::request::SemanticTokensRefesh>((), |_, _| ());
440+
}
441+
442+
// Refresh code lens if the client supports it.
443+
if self.config.code_lens_refresh() {
444+
self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ());
445+
}
423446
}
424447

425-
// Refresh code lens if the client supports it.
426-
if self.config.code_lens_refresh() {
427-
self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ());
448+
if !was_quiescent || state_changed || memdocs_added_or_removed {
449+
if self.config.publish_diagnostics() {
450+
self.update_diagnostics()
451+
}
428452
}
429453
}
430454

@@ -586,42 +610,32 @@ impl GlobalState {
586610
{
587611
log::error!("duplicate DidOpenTextDocument: {}", path)
588612
}
589-
let changed = this
590-
.vfs
613+
this.vfs
591614
.write()
592615
.0
593616
.set_file_contents(path, Some(params.text_document.text.into_bytes()));
594-
595-
// If the VFS contents are unchanged, update diagnostics, since `handle_event`
596-
// won't see any changes. This avoids missing diagnostics when opening a file.
597-
//
598-
// If the file *was* changed, `handle_event` will already recompute and send
599-
// diagnostics. We can't do it here, since the *current* file contents might be
600-
// unset in salsa, since the VFS change hasn't been applied to the database yet.
601-
if !changed {
602-
this.maybe_update_diagnostics();
603-
}
604617
}
605618
Ok(())
606619
})?
607620
.on::<lsp_types::notification::DidChangeTextDocument>(|this, params| {
608621
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
609-
let doc = match this.mem_docs.get_mut(&path) {
610-
Some(doc) => doc,
622+
match this.mem_docs.get_mut(&path) {
623+
Some(doc) => {
624+
// The version passed in DidChangeTextDocument is the version after all edits are applied
625+
// so we should apply it before the vfs is notified.
626+
doc.version = params.text_document.version;
627+
}
611628
None => {
612629
log::error!("expected DidChangeTextDocument: {}", path);
613630
return Ok(());
614631
}
615632
};
633+
616634
let vfs = &mut this.vfs.write().0;
617635
let file_id = vfs.file_id(&path).unwrap();
618636
let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
619637
apply_document_changes(&mut text, params.content_changes);
620638

621-
// The version passed in DidChangeTextDocument is the version after all edits are applied
622-
// so we should apply it before the vfs is notified.
623-
doc.version = params.text_document.version;
624-
625639
vfs.set_file_contents(path.clone(), Some(text.into_bytes()));
626640
}
627641
Ok(())
@@ -696,53 +710,33 @@ impl GlobalState {
696710
.finish();
697711
Ok(())
698712
}
699-
fn update_file_notifications_on_threadpool(&mut self) {
700-
self.maybe_update_diagnostics();
701-
702-
// Ensure that only one cache priming task can run at a time
703-
self.prime_caches_queue.request_op();
704-
if self.prime_caches_queue.should_start_op() {
705-
self.task_pool.handle.spawn_with_sender({
706-
let snap = self.snapshot();
707-
move |sender| {
708-
let cb = |progress| {
709-
sender.send(Task::PrimeCaches(progress)).unwrap();
710-
};
711-
match snap.analysis.prime_caches(cb) {
712-
Ok(()) => (),
713-
Err(_canceled) => (),
714-
}
715-
}
716-
});
717-
}
718-
}
719-
fn maybe_update_diagnostics(&mut self) {
713+
714+
fn update_diagnostics(&mut self) {
720715
let subscriptions = self
721716
.mem_docs
722717
.iter()
723718
.map(|path| self.vfs.read().0.file_id(path).unwrap())
724719
.collect::<Vec<_>>();
725720

726721
log::trace!("updating notifications for {:?}", subscriptions);
727-
if self.config.publish_diagnostics() {
728-
let snapshot = self.snapshot();
729-
self.task_pool.handle.spawn(move || {
730-
let diagnostics = subscriptions
731-
.into_iter()
732-
.filter_map(|file_id| {
733-
handlers::publish_diagnostics(&snapshot, file_id)
734-
.map_err(|err| {
735-
if !is_cancelled(&*err) {
736-
log::error!("failed to compute diagnostics: {:?}", err);
737-
}
738-
()
739-
})
740-
.ok()
741-
.map(|diags| (file_id, diags))
742-
})
743-
.collect::<Vec<_>>();
744-
Task::Diagnostics(diagnostics)
745-
})
746-
}
722+
723+
let snapshot = self.snapshot();
724+
self.task_pool.handle.spawn(move || {
725+
let diagnostics = subscriptions
726+
.into_iter()
727+
.filter_map(|file_id| {
728+
handlers::publish_diagnostics(&snapshot, file_id)
729+
.map_err(|err| {
730+
if !is_cancelled(&*err) {
731+
log::error!("failed to compute diagnostics: {:?}", err);
732+
}
733+
()
734+
})
735+
.ok()
736+
.map(|diags| (file_id, diags))
737+
})
738+
.collect::<Vec<_>>();
739+
Task::Diagnostics(diagnostics)
740+
})
747741
}
748742
}

crates/rust-analyzer/src/mem_docs.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! In-memory document information.
22
3+
use std::mem;
4+
35
use rustc_hash::FxHashMap;
46
use vfs::VfsPath;
57

@@ -10,19 +12,22 @@ use vfs::VfsPath;
1012
#[derive(Default, Clone)]
1113
pub(crate) struct MemDocs {
1214
mem_docs: FxHashMap<VfsPath, DocumentData>,
15+
added_or_removed: bool,
1316
}
1417

1518
impl MemDocs {
1619
pub(crate) fn contains(&self, path: &VfsPath) -> bool {
1720
self.mem_docs.contains_key(path)
1821
}
1922
pub(crate) fn insert(&mut self, path: VfsPath, data: DocumentData) -> Result<(), ()> {
23+
self.added_or_removed = true;
2024
match self.mem_docs.insert(path, data) {
2125
Some(_) => Err(()),
2226
None => Ok(()),
2327
}
2428
}
2529
pub(crate) fn remove(&mut self, path: &VfsPath) -> Result<(), ()> {
30+
self.added_or_removed = true;
2631
match self.mem_docs.remove(path) {
2732
Some(_) => Ok(()),
2833
None => Err(()),
@@ -32,12 +37,16 @@ impl MemDocs {
3237
self.mem_docs.get(path)
3338
}
3439
pub(crate) fn get_mut(&mut self, path: &VfsPath) -> Option<&mut DocumentData> {
40+
// NB: don't set `self.added_or_removed` here, as that purposefully only
41+
// tracks changes to the key set.
3542
self.mem_docs.get_mut(path)
3643
}
37-
3844
pub(crate) fn iter(&self) -> impl Iterator<Item = &VfsPath> {
3945
self.mem_docs.keys()
4046
}
47+
pub(crate) fn take_changes(&mut self) -> bool {
48+
mem::replace(&mut self.added_or_removed, false)
49+
}
4150
}
4251

4352
/// Information about a document that the Language Client

0 commit comments

Comments
 (0)