mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
Merge #9701
9701: fix: correctly update diagnostics when files are opened and closed r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
e6bae220b7
@ -1,16 +0,0 @@
|
|||||||
//! In-memory document information.
|
|
||||||
|
|
||||||
/// Information about a document that the Language Client
|
|
||||||
/// knows about.
|
|
||||||
/// Its lifetime is driven by the textDocument/didOpen and textDocument/didClose
|
|
||||||
/// client notifications.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) struct DocumentData {
|
|
||||||
pub(crate) version: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DocumentData {
|
|
||||||
pub(crate) fn new(version: i32) -> Self {
|
|
||||||
DocumentData { version }
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,7 @@ use std::{sync::Arc, time::Instant};
|
|||||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use flycheck::FlycheckHandle;
|
use flycheck::FlycheckHandle;
|
||||||
use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId};
|
use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId};
|
||||||
use ide_db::base_db::{CrateId, VfsPath};
|
use ide_db::base_db::CrateId;
|
||||||
use lsp_types::{SemanticTokens, Url};
|
use lsp_types::{SemanticTokens, Url};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use project_model::{
|
use project_model::{
|
||||||
@ -20,11 +20,11 @@ use vfs::AnchoredPathBuf;
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
diagnostics::{CheckFixes, DiagnosticCollection},
|
diagnostics::{CheckFixes, DiagnosticCollection},
|
||||||
document::DocumentData,
|
|
||||||
from_proto,
|
from_proto,
|
||||||
line_index::{LineEndings, LineIndex},
|
line_index::{LineEndings, LineIndex},
|
||||||
lsp_ext,
|
lsp_ext,
|
||||||
main_loop::Task,
|
main_loop::Task,
|
||||||
|
mem_docs::MemDocs,
|
||||||
op_queue::OpQueue,
|
op_queue::OpQueue,
|
||||||
reload::SourceRootConfig,
|
reload::SourceRootConfig,
|
||||||
request_metrics::{LatestRequests, RequestMetrics},
|
request_metrics::{LatestRequests, RequestMetrics},
|
||||||
@ -57,7 +57,7 @@ pub(crate) struct GlobalState {
|
|||||||
pub(crate) config: Arc<Config>,
|
pub(crate) config: Arc<Config>,
|
||||||
pub(crate) analysis_host: AnalysisHost,
|
pub(crate) analysis_host: AnalysisHost,
|
||||||
pub(crate) diagnostics: DiagnosticCollection,
|
pub(crate) diagnostics: DiagnosticCollection,
|
||||||
pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
|
pub(crate) mem_docs: MemDocs,
|
||||||
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
|
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
|
||||||
pub(crate) shutdown_requested: bool,
|
pub(crate) shutdown_requested: bool,
|
||||||
pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
|
pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
|
||||||
@ -115,7 +115,7 @@ pub(crate) struct GlobalStateSnapshot {
|
|||||||
pub(crate) analysis: Analysis,
|
pub(crate) analysis: Analysis,
|
||||||
pub(crate) check_fixes: CheckFixes,
|
pub(crate) check_fixes: CheckFixes,
|
||||||
pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
|
pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
|
||||||
mem_docs: FxHashMap<VfsPath, DocumentData>,
|
mem_docs: MemDocs,
|
||||||
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
|
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
|
||||||
vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
|
vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
|
||||||
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
|
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
|
||||||
@ -147,7 +147,7 @@ impl GlobalState {
|
|||||||
config: Arc::new(config.clone()),
|
config: Arc::new(config.clone()),
|
||||||
analysis_host,
|
analysis_host,
|
||||||
diagnostics: Default::default(),
|
diagnostics: Default::default(),
|
||||||
mem_docs: FxHashMap::default(),
|
mem_docs: MemDocs::default(),
|
||||||
semantic_tokens_cache: Arc::new(Default::default()),
|
semantic_tokens_cache: Arc::new(Default::default()),
|
||||||
shutdown_requested: false,
|
shutdown_requested: false,
|
||||||
last_reported_status: None,
|
last_reported_status: None,
|
||||||
|
@ -33,7 +33,7 @@ mod line_index;
|
|||||||
mod request_metrics;
|
mod request_metrics;
|
||||||
mod lsp_utils;
|
mod lsp_utils;
|
||||||
mod thread_pool;
|
mod thread_pool;
|
||||||
mod document;
|
mod mem_docs;
|
||||||
mod diff;
|
mod diff;
|
||||||
mod op_queue;
|
mod op_queue;
|
||||||
pub mod lsp_ext;
|
pub mod lsp_ext;
|
||||||
|
@ -17,11 +17,11 @@ use vfs::ChangeKind;
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
dispatch::{NotificationDispatcher, RequestDispatcher},
|
dispatch::{NotificationDispatcher, RequestDispatcher},
|
||||||
document::DocumentData,
|
|
||||||
from_proto,
|
from_proto,
|
||||||
global_state::{file_id_to_url, url_to_file_id, GlobalState},
|
global_state::{file_id_to_url, url_to_file_id, GlobalState},
|
||||||
handlers, lsp_ext,
|
handlers, lsp_ext,
|
||||||
lsp_utils::{apply_document_changes, is_cancelled, notification_is, Progress},
|
lsp_utils::{apply_document_changes, is_cancelled, notification_is, Progress},
|
||||||
|
mem_docs::DocumentData,
|
||||||
reload::{BuildDataProgress, ProjectWorkspaceProgress},
|
reload::{BuildDataProgress, ProjectWorkspaceProgress},
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
@ -305,7 +305,7 @@ impl GlobalState {
|
|||||||
let vfs = &mut self.vfs.write().0;
|
let vfs = &mut self.vfs.write().0;
|
||||||
for (path, contents) in files {
|
for (path, contents) in files {
|
||||||
let path = VfsPath::from(path);
|
let path = VfsPath::from(path);
|
||||||
if !self.mem_docs.contains_key(&path) {
|
if !self.mem_docs.contains(&path) {
|
||||||
vfs.set_file_contents(path, contents);
|
vfs.set_file_contents(path, contents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -406,25 +406,49 @@ impl GlobalState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let state_changed = self.process_changes();
|
let state_changed = self.process_changes();
|
||||||
|
let memdocs_added_or_removed = self.mem_docs.take_changes();
|
||||||
|
|
||||||
if self.is_quiescent() && !was_quiescent {
|
if self.is_quiescent() {
|
||||||
for flycheck in &self.flycheck {
|
if !was_quiescent {
|
||||||
flycheck.update();
|
for flycheck in &self.flycheck {
|
||||||
}
|
flycheck.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_quiescent() && (!was_quiescent || state_changed) {
|
|
||||||
self.update_file_notifications_on_threadpool();
|
|
||||||
|
|
||||||
// Refresh semantic tokens if the client supports it.
|
|
||||||
if self.config.semantic_tokens_refresh() {
|
|
||||||
self.semantic_tokens_cache.lock().clear();
|
|
||||||
self.send_request::<lsp_types::request::SemanticTokensRefesh>((), |_, _| ());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh code lens if the client supports it.
|
if !was_quiescent || state_changed {
|
||||||
if self.config.code_lens_refresh() {
|
// Ensure that only one cache priming task can run at a time
|
||||||
self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ());
|
self.prime_caches_queue.request_op();
|
||||||
|
if self.prime_caches_queue.should_start_op() {
|
||||||
|
self.task_pool.handle.spawn_with_sender({
|
||||||
|
let snap = self.snapshot();
|
||||||
|
move |sender| {
|
||||||
|
let cb = |progress| {
|
||||||
|
sender.send(Task::PrimeCaches(progress)).unwrap();
|
||||||
|
};
|
||||||
|
match snap.analysis.prime_caches(cb) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(_canceled) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh semantic tokens if the client supports it.
|
||||||
|
if self.config.semantic_tokens_refresh() {
|
||||||
|
self.semantic_tokens_cache.lock().clear();
|
||||||
|
self.send_request::<lsp_types::request::SemanticTokensRefesh>((), |_, _| ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh code lens if the client supports it.
|
||||||
|
if self.config.code_lens_refresh() {
|
||||||
|
self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !was_quiescent || state_changed || memdocs_added_or_removed {
|
||||||
|
if self.config.publish_diagnostics() {
|
||||||
|
self.update_diagnostics()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,53 +606,43 @@ impl GlobalState {
|
|||||||
if this
|
if this
|
||||||
.mem_docs
|
.mem_docs
|
||||||
.insert(path.clone(), DocumentData::new(params.text_document.version))
|
.insert(path.clone(), DocumentData::new(params.text_document.version))
|
||||||
.is_some()
|
.is_err()
|
||||||
{
|
{
|
||||||
log::error!("duplicate DidOpenTextDocument: {}", path)
|
log::error!("duplicate DidOpenTextDocument: {}", path)
|
||||||
}
|
}
|
||||||
let changed = this
|
this.vfs
|
||||||
.vfs
|
|
||||||
.write()
|
.write()
|
||||||
.0
|
.0
|
||||||
.set_file_contents(path, Some(params.text_document.text.into_bytes()));
|
.set_file_contents(path, Some(params.text_document.text.into_bytes()));
|
||||||
|
|
||||||
// If the VFS contents are unchanged, update diagnostics, since `handle_event`
|
|
||||||
// won't see any changes. This avoids missing diagnostics when opening a file.
|
|
||||||
//
|
|
||||||
// If the file *was* changed, `handle_event` will already recompute and send
|
|
||||||
// diagnostics. We can't do it here, since the *current* file contents might be
|
|
||||||
// unset in salsa, since the VFS change hasn't been applied to the database yet.
|
|
||||||
if !changed {
|
|
||||||
this.maybe_update_diagnostics();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?
|
})?
|
||||||
.on::<lsp_types::notification::DidChangeTextDocument>(|this, params| {
|
.on::<lsp_types::notification::DidChangeTextDocument>(|this, params| {
|
||||||
if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
||||||
let doc = match this.mem_docs.get_mut(&path) {
|
match this.mem_docs.get_mut(&path) {
|
||||||
Some(doc) => doc,
|
Some(doc) => {
|
||||||
|
// The version passed in DidChangeTextDocument is the version after all edits are applied
|
||||||
|
// so we should apply it before the vfs is notified.
|
||||||
|
doc.version = params.text_document.version;
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!("expected DidChangeTextDocument: {}", path);
|
log::error!("expected DidChangeTextDocument: {}", path);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let vfs = &mut this.vfs.write().0;
|
let vfs = &mut this.vfs.write().0;
|
||||||
let file_id = vfs.file_id(&path).unwrap();
|
let file_id = vfs.file_id(&path).unwrap();
|
||||||
let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
|
let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
|
||||||
apply_document_changes(&mut text, params.content_changes);
|
apply_document_changes(&mut text, params.content_changes);
|
||||||
|
|
||||||
// The version passed in DidChangeTextDocument is the version after all edits are applied
|
|
||||||
// so we should apply it before the vfs is notified.
|
|
||||||
doc.version = params.text_document.version;
|
|
||||||
|
|
||||||
vfs.set_file_contents(path.clone(), Some(text.into_bytes()));
|
vfs.set_file_contents(path.clone(), Some(text.into_bytes()));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?
|
})?
|
||||||
.on::<lsp_types::notification::DidCloseTextDocument>(|this, params| {
|
.on::<lsp_types::notification::DidCloseTextDocument>(|this, params| {
|
||||||
if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
||||||
if this.mem_docs.remove(&path).is_none() {
|
if this.mem_docs.remove(&path).is_err() {
|
||||||
log::error!("orphan DidCloseTextDocument: {}", path);
|
log::error!("orphan DidCloseTextDocument: {}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,53 +710,33 @@ impl GlobalState {
|
|||||||
.finish();
|
.finish();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn update_file_notifications_on_threadpool(&mut self) {
|
|
||||||
self.maybe_update_diagnostics();
|
|
||||||
|
|
||||||
// Ensure that only one cache priming task can run at a time
|
fn update_diagnostics(&mut self) {
|
||||||
self.prime_caches_queue.request_op();
|
|
||||||
if self.prime_caches_queue.should_start_op() {
|
|
||||||
self.task_pool.handle.spawn_with_sender({
|
|
||||||
let snap = self.snapshot();
|
|
||||||
move |sender| {
|
|
||||||
let cb = |progress| {
|
|
||||||
sender.send(Task::PrimeCaches(progress)).unwrap();
|
|
||||||
};
|
|
||||||
match snap.analysis.prime_caches(cb) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(_canceled) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn maybe_update_diagnostics(&mut self) {
|
|
||||||
let subscriptions = self
|
let subscriptions = self
|
||||||
.mem_docs
|
.mem_docs
|
||||||
.keys()
|
.iter()
|
||||||
.map(|path| self.vfs.read().0.file_id(path).unwrap())
|
.map(|path| self.vfs.read().0.file_id(path).unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
log::trace!("updating notifications for {:?}", subscriptions);
|
log::trace!("updating notifications for {:?}", subscriptions);
|
||||||
if self.config.publish_diagnostics() {
|
|
||||||
let snapshot = self.snapshot();
|
let snapshot = self.snapshot();
|
||||||
self.task_pool.handle.spawn(move || {
|
self.task_pool.handle.spawn(move || {
|
||||||
let diagnostics = subscriptions
|
let diagnostics = subscriptions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|file_id| {
|
.filter_map(|file_id| {
|
||||||
handlers::publish_diagnostics(&snapshot, file_id)
|
handlers::publish_diagnostics(&snapshot, file_id)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
if !is_cancelled(&*err) {
|
if !is_cancelled(&*err) {
|
||||||
log::error!("failed to compute diagnostics: {:?}", err);
|
log::error!("failed to compute diagnostics: {:?}", err);
|
||||||
}
|
}
|
||||||
()
|
()
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
.map(|diags| (file_id, diags))
|
.map(|diags| (file_id, diags))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
Task::Diagnostics(diagnostics)
|
Task::Diagnostics(diagnostics)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
65
crates/rust-analyzer/src/mem_docs.rs
Normal file
65
crates/rust-analyzer/src/mem_docs.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//! In-memory document information.
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use vfs::VfsPath;
|
||||||
|
|
||||||
|
/// Holds the set of in-memory documents.
|
||||||
|
///
|
||||||
|
/// For these document, there true contents is maintained by the client. It
|
||||||
|
/// might be different from what's on disk.
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub(crate) struct MemDocs {
|
||||||
|
mem_docs: FxHashMap<VfsPath, DocumentData>,
|
||||||
|
added_or_removed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemDocs {
|
||||||
|
pub(crate) fn contains(&self, path: &VfsPath) -> bool {
|
||||||
|
self.mem_docs.contains_key(path)
|
||||||
|
}
|
||||||
|
pub(crate) fn insert(&mut self, path: VfsPath, data: DocumentData) -> Result<(), ()> {
|
||||||
|
self.added_or_removed = true;
|
||||||
|
match self.mem_docs.insert(path, data) {
|
||||||
|
Some(_) => Err(()),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn remove(&mut self, path: &VfsPath) -> Result<(), ()> {
|
||||||
|
self.added_or_removed = true;
|
||||||
|
match self.mem_docs.remove(path) {
|
||||||
|
Some(_) => Ok(()),
|
||||||
|
None => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn get(&self, path: &VfsPath) -> Option<&DocumentData> {
|
||||||
|
self.mem_docs.get(path)
|
||||||
|
}
|
||||||
|
pub(crate) fn get_mut(&mut self, path: &VfsPath) -> Option<&mut DocumentData> {
|
||||||
|
// NB: don't set `self.added_or_removed` here, as that purposefully only
|
||||||
|
// tracks changes to the key set.
|
||||||
|
self.mem_docs.get_mut(path)
|
||||||
|
}
|
||||||
|
pub(crate) fn iter(&self) -> impl Iterator<Item = &VfsPath> {
|
||||||
|
self.mem_docs.keys()
|
||||||
|
}
|
||||||
|
pub(crate) fn take_changes(&mut self) -> bool {
|
||||||
|
mem::replace(&mut self.added_or_removed, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a document that the Language Client
|
||||||
|
/// knows about.
|
||||||
|
/// Its lifetime is driven by the textDocument/didOpen and textDocument/didClose
|
||||||
|
/// client notifications.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct DocumentData {
|
||||||
|
pub(crate) version: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocumentData {
|
||||||
|
pub(crate) fn new(version: i32) -> Self {
|
||||||
|
DocumentData { version }
|
||||||
|
}
|
||||||
|
}
|
@ -403,7 +403,7 @@ impl GlobalState {
|
|||||||
let mut load = |path: &AbsPath| {
|
let mut load = |path: &AbsPath| {
|
||||||
let _p = profile::span("GlobalState::load");
|
let _p = profile::span("GlobalState::load");
|
||||||
let vfs_path = vfs::VfsPath::from(path.to_path_buf());
|
let vfs_path = vfs::VfsPath::from(path.to_path_buf());
|
||||||
if !mem_docs.contains_key(&vfs_path) {
|
if !mem_docs.contains(&vfs_path) {
|
||||||
let contents = loader.handle.load_sync(path);
|
let contents = loader.handle.load_sync(path);
|
||||||
vfs.set_file_contents(vfs_path.clone(), contents);
|
vfs.set_file_contents(vfs_path.clone(), contents);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user