Skip to content

Commit e6bae22

Browse files
bors[bot]matklad
andauthored
Merge #9701
9701: fix: correctly update diagnostics when files are opened and closed r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents f0db648 + 891867b commit e6bae22

File tree

6 files changed

+143
-100
lines changed

6 files changed

+143
-100
lines changed

crates/rust-analyzer/src/document.rs

Lines changed: 0 additions & 16 deletions
This file was deleted.

crates/rust-analyzer/src/global_state.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{sync::Arc, time::Instant};
88
use crossbeam_channel::{unbounded, Receiver, Sender};
99
use flycheck::FlycheckHandle;
1010
use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId};
11-
use ide_db::base_db::{CrateId, VfsPath};
11+
use ide_db::base_db::CrateId;
1212
use lsp_types::{SemanticTokens, Url};
1313
use parking_lot::{Mutex, RwLock};
1414
use project_model::{
@@ -20,11 +20,11 @@ use vfs::AnchoredPathBuf;
2020
use crate::{
2121
config::Config,
2222
diagnostics::{CheckFixes, DiagnosticCollection},
23-
document::DocumentData,
2423
from_proto,
2524
line_index::{LineEndings, LineIndex},
2625
lsp_ext,
2726
main_loop::Task,
27+
mem_docs::MemDocs,
2828
op_queue::OpQueue,
2929
reload::SourceRootConfig,
3030
request_metrics::{LatestRequests, RequestMetrics},
@@ -57,7 +57,7 @@ pub(crate) struct GlobalState {
5757
pub(crate) config: Arc<Config>,
5858
pub(crate) analysis_host: AnalysisHost,
5959
pub(crate) diagnostics: DiagnosticCollection,
60-
pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
60+
pub(crate) mem_docs: MemDocs,
6161
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
6262
pub(crate) shutdown_requested: bool,
6363
pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
@@ -115,7 +115,7 @@ pub(crate) struct GlobalStateSnapshot {
115115
pub(crate) analysis: Analysis,
116116
pub(crate) check_fixes: CheckFixes,
117117
pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
118-
mem_docs: FxHashMap<VfsPath, DocumentData>,
118+
mem_docs: MemDocs,
119119
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
120120
vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
121121
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
@@ -147,7 +147,7 @@ impl GlobalState {
147147
config: Arc::new(config.clone()),
148148
analysis_host,
149149
diagnostics: Default::default(),
150-
mem_docs: FxHashMap::default(),
150+
mem_docs: MemDocs::default(),
151151
semantic_tokens_cache: Arc::new(Default::default()),
152152
shutdown_requested: false,
153153
last_reported_status: None,

crates/rust-analyzer/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ mod line_index;
3333
mod request_metrics;
3434
mod lsp_utils;
3535
mod thread_pool;
36-
mod document;
36+
mod mem_docs;
3737
mod diff;
3838
mod op_queue;
3939
pub mod lsp_ext;

crates/rust-analyzer/src/main_loop.rs

Lines changed: 71 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ use vfs::ChangeKind;
1717
use crate::{
1818
config::Config,
1919
dispatch::{NotificationDispatcher, RequestDispatcher},
20-
document::DocumentData,
2120
from_proto,
2221
global_state::{file_id_to_url, url_to_file_id, GlobalState},
2322
handlers, lsp_ext,
2423
lsp_utils::{apply_document_changes, is_cancelled, notification_is, Progress},
24+
mem_docs::DocumentData,
2525
reload::{BuildDataProgress, ProjectWorkspaceProgress},
2626
Result,
2727
};
@@ -305,7 +305,7 @@ impl GlobalState {
305305
let vfs = &mut self.vfs.write().0;
306306
for (path, contents) in files {
307307
let path = VfsPath::from(path);
308-
if !self.mem_docs.contains_key(&path) {
308+
if !self.mem_docs.contains(&path) {
309309
vfs.set_file_contents(path, contents);
310310
}
311311
}
@@ -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

@@ -582,53 +606,43 @@ impl GlobalState {
582606
if this
583607
.mem_docs
584608
.insert(path.clone(), DocumentData::new(params.text_document.version))
585-
.is_some()
609+
.is_err()
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(())
628642
})?
629643
.on::<lsp_types::notification::DidCloseTextDocument>(|this, params| {
630644
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
631-
if this.mem_docs.remove(&path).is_none() {
645+
if this.mem_docs.remove(&path).is_err() {
632646
log::error!("orphan DidCloseTextDocument: {}", path);
633647
}
634648

@@ -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
722-
.keys()
717+
.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: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! In-memory document information.
2+
3+
use std::mem;
4+
5+
use rustc_hash::FxHashMap;
6+
use vfs::VfsPath;
7+
8+
/// Holds the set of in-memory documents.
9+
///
10+
/// For these document, there true contents is maintained by the client. It
11+
/// might be different from what's on disk.
12+
#[derive(Default, Clone)]
13+
pub(crate) struct MemDocs {
14+
mem_docs: FxHashMap<VfsPath, DocumentData>,
15+
added_or_removed: bool,
16+
}
17+
18+
impl MemDocs {
19+
pub(crate) fn contains(&self, path: &VfsPath) -> bool {
20+
self.mem_docs.contains_key(path)
21+
}
22+
pub(crate) fn insert(&mut self, path: VfsPath, data: DocumentData) -> Result<(), ()> {
23+
self.added_or_removed = true;
24+
match self.mem_docs.insert(path, data) {
25+
Some(_) => Err(()),
26+
None => Ok(()),
27+
}
28+
}
29+
pub(crate) fn remove(&mut self, path: &VfsPath) -> Result<(), ()> {
30+
self.added_or_removed = true;
31+
match self.mem_docs.remove(path) {
32+
Some(_) => Ok(()),
33+
None => Err(()),
34+
}
35+
}
36+
pub(crate) fn get(&self, path: &VfsPath) -> Option<&DocumentData> {
37+
self.mem_docs.get(path)
38+
}
39+
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.
42+
self.mem_docs.get_mut(path)
43+
}
44+
pub(crate) fn iter(&self) -> impl Iterator<Item = &VfsPath> {
45+
self.mem_docs.keys()
46+
}
47+
pub(crate) fn take_changes(&mut self) -> bool {
48+
mem::replace(&mut self.added_or_removed, false)
49+
}
50+
}
51+
52+
/// Information about a document that the Language Client
53+
/// knows about.
54+
/// Its lifetime is driven by the textDocument/didOpen and textDocument/didClose
55+
/// client notifications.
56+
#[derive(Debug, Clone)]
57+
pub(crate) struct DocumentData {
58+
pub(crate) version: i32,
59+
}
60+
61+
impl DocumentData {
62+
pub(crate) fn new(version: i32) -> Self {
63+
DocumentData { version }
64+
}
65+
}

crates/rust-analyzer/src/reload.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ impl GlobalState {
403403
let mut load = |path: &AbsPath| {
404404
let _p = profile::span("GlobalState::load");
405405
let vfs_path = vfs::VfsPath::from(path.to_path_buf());
406-
if !mem_docs.contains_key(&vfs_path) {
406+
if !mem_docs.contains(&vfs_path) {
407407
let contents = loader.handle.load_sync(path);
408408
vfs.set_file_contents(vfs_path.clone(), contents);
409409
}

0 commit comments

Comments
 (0)