From a63b5d3c846b74b917b8029aa00cb448faba409f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 18 Jul 2022 20:30:07 +0200 Subject: [PATCH 1/5] feat: Only flycheck workspace that belongs to saved file --- crates/flycheck/src/lib.rs | 7 ++- crates/paths/src/lib.rs | 8 ++++ crates/rust-analyzer/src/main_loop.rs | 62 ++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index f683fe61fe38..973f2a53c1ad 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -55,6 +55,7 @@ pub struct FlycheckHandle { // XXX: drop order is significant sender: Sender, _thread: jod_thread::JoinHandle, + id: usize, } impl FlycheckHandle { @@ -70,13 +71,17 @@ impl FlycheckHandle { .name("Flycheck".to_owned()) .spawn(move || actor.run(receiver)) .expect("failed to spawn thread"); - FlycheckHandle { sender, _thread: thread } + FlycheckHandle { id, sender, _thread: thread } } /// Schedule a re-start of the cargo check worker. pub fn update(&self) { self.sender.send(Restart).unwrap(); } + + pub fn id(&self) -> usize { + self.id + } } pub enum Message { diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs index b4beb40e7469..cf06bf0c27b8 100644 --- a/crates/paths/src/lib.rs +++ b/crates/paths/src/lib.rs @@ -103,6 +103,14 @@ impl AsRef for AbsPath { } } +impl ToOwned for AbsPath { + type Owned = AbsPathBuf; + + fn to_owned(&self) -> Self::Owned { + AbsPathBuf(self.0.to_owned()) + } +} + impl<'a> TryFrom<&'a Path> for &'a AbsPath { type Error = &'a Path; fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 49b83941119f..262c30f132c1 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -2,6 +2,7 @@ //! requests/replies and notifications back to the client. use std::{ fmt, + ops::Deref, sync::Arc, time::{Duration, Instant}, }; @@ -720,13 +721,62 @@ impl GlobalState { Ok(()) })? .on::(|this, params| { - for flycheck in &this.flycheck { - flycheck.update(); + let mut updated = false; + if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) { + let (vfs, _) = &*this.vfs.read(); + if let Some(file_id) = vfs.file_id(&vfs_path) { + let analysis = this.analysis_host.analysis(); + let crate_ids = analysis.crate_for(file_id)?; + + let paths: Vec<_> = crate_ids + .iter() + .filter_map(|&crate_id| { + analysis + .crate_root(crate_id) + .map(|file_id| { + vfs.file_path(file_id).as_path().map(ToOwned::to_owned) + }) + .transpose() + }) + .collect::>()?; + let paths: Vec<_> = paths.iter().map(Deref::deref).collect(); + + let workspace_ids = + this.workspaces.iter().enumerate().filter(|(_, ws)| match ws { + project_model::ProjectWorkspace::Cargo { cargo, .. } => { + cargo.packages().filter(|&pkg| cargo[pkg].is_member).any( + |pkg| { + cargo[pkg].targets.iter().any(|&it| { + paths.contains(&cargo[it].root.as_path()) + }) + }, + ) + } + project_model::ProjectWorkspace::Json { project, .. } => project + .crates() + .any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)), + project_model::ProjectWorkspace::DetachedFiles { .. } => false, + }); + 'workspace: for (id, _) in workspace_ids { + for flycheck in &this.flycheck { + if id == flycheck.id() { + updated = true; + flycheck.update(); + continue 'workspace; + } + } + } + } + if let Some(abs_path) = vfs_path.as_path() { + if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) { + this.fetch_workspaces_queue + .request_op(format!("DidSaveTextDocument {}", abs_path.display())); + } + } } - if let Ok(abs_path) = from_proto::abs_path(¶ms.text_document.uri) { - if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) { - this.fetch_workspaces_queue - .request_op(format!("DidSaveTextDocument {}", abs_path.display())); + if !updated { + for flycheck in &this.flycheck { + flycheck.update(); } } Ok(()) From 25391e6d44e3dc740349198c309e08782691da7e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 20 Jul 2022 11:49:36 +0200 Subject: [PATCH 2/5] Only clear diagnostics of workspaces who have been flychecked --- crates/flycheck/src/lib.rs | 8 ++++--- crates/rust-analyzer/src/diagnostics.rs | 27 ++++++++++++++++++------ crates/rust-analyzer/src/global_state.rs | 1 + crates/rust-analyzer/src/handlers.rs | 4 +++- crates/rust-analyzer/src/main_loop.rs | 5 +++-- crates/rust-analyzer/src/reload.rs | 2 +- 6 files changed, 33 insertions(+), 14 deletions(-) diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 973f2a53c1ad..7f14fe5798a0 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -86,7 +86,7 @@ impl FlycheckHandle { pub enum Message { /// Request adding a diagnostic with fixes included to a file - AddDiagnostic { workspace_root: AbsPathBuf, diagnostic: Diagnostic }, + AddDiagnostic { id: usize, workspace_root: AbsPathBuf, diagnostic: Diagnostic }, /// Request check progress notification to client Progress { @@ -99,8 +99,9 @@ pub enum Message { impl fmt::Debug for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Message::AddDiagnostic { workspace_root, diagnostic } => f + Message::AddDiagnostic { id, workspace_root, diagnostic } => f .debug_struct("AddDiagnostic") + .field("id", id) .field("workspace_root", workspace_root) .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code)) .finish(), @@ -186,7 +187,7 @@ impl FlycheckActor { } } Event::CheckEvent(None) => { - tracing::debug!("flycheck finished"); + tracing::debug!(flycheck_id = self.id, "flycheck finished"); // Watcher finished let cargo_handle = self.cargo_handle.take().unwrap(); @@ -206,6 +207,7 @@ impl FlycheckActor { CargoMessage::Diagnostic(msg) => { self.send(Message::AddDiagnostic { + id: self.id, workspace_root: self.workspace_root.clone(), diagnostic: msg, }); diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index 202a01adf710..7917ced666a8 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -8,7 +8,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use crate::lsp_ext; -pub(crate) type CheckFixes = Arc>>; +pub(crate) type CheckFixes = Arc>>>; #[derive(Debug, Default, Clone)] pub struct DiagnosticsMapConfig { @@ -22,7 +22,7 @@ pub(crate) struct DiagnosticCollection { // FIXME: should be FxHashMap> pub(crate) native: FxHashMap>, // FIXME: should be Vec - pub(crate) check: FxHashMap>, + pub(crate) check: FxHashMap>>, pub(crate) check_fixes: CheckFixes, changes: FxHashSet, } @@ -35,9 +35,19 @@ pub(crate) struct Fix { } impl DiagnosticCollection { - pub(crate) fn clear_check(&mut self) { + pub(crate) fn clear_check(&mut self, flycheck_id: usize) { + if let Some(it) = Arc::make_mut(&mut self.check_fixes).get_mut(&flycheck_id) { + it.clear(); + } + if let Some(it) = self.check.get_mut(&flycheck_id) { + self.changes.extend(it.drain().map(|(key, _value)| key)); + } + } + + pub(crate) fn clear_check_all(&mut self) { Arc::make_mut(&mut self.check_fixes).clear(); - self.changes.extend(self.check.drain().map(|(key, _value)| key)) + self.changes + .extend(self.check.values_mut().flat_map(|it| it.drain().map(|(key, _value)| key))) } pub(crate) fn clear_native_for(&mut self, file_id: FileId) { @@ -47,11 +57,12 @@ impl DiagnosticCollection { pub(crate) fn add_check_diagnostic( &mut self, + flycheck_id: usize, file_id: FileId, diagnostic: lsp_types::Diagnostic, fix: Option, ) { - let diagnostics = self.check.entry(file_id).or_default(); + let diagnostics = self.check.entry(flycheck_id).or_default().entry(file_id).or_default(); for existing_diagnostic in diagnostics.iter() { if are_diagnostics_equal(existing_diagnostic, &diagnostic) { return; @@ -59,8 +70,9 @@ impl DiagnosticCollection { } let check_fixes = Arc::make_mut(&mut self.check_fixes); - check_fixes.entry(file_id).or_default().extend(fix); + check_fixes.entry(flycheck_id).or_default().entry(file_id).or_default().extend(fix); diagnostics.push(diagnostic); + tracing::warn!(?flycheck_id, ?file_id, "add_check_diagnostic changes pushed"); self.changes.insert(file_id); } @@ -89,7 +101,8 @@ impl DiagnosticCollection { file_id: FileId, ) -> impl Iterator { let native = self.native.get(&file_id).into_iter().flatten(); - let check = self.check.get(&file_id).into_iter().flatten(); + let check = + self.check.values().filter_map(move |it| it.get(&file_id)).into_iter().flatten(); native.chain(check) } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 4af60035a20e..2cd2044aeff0 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -201,6 +201,7 @@ impl GlobalState { } } + // Clear native diagnostics when their file gets deleted if !file.exists() { self.diagnostics.clear_native_for(file.file_id); } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index e3875228a187..a64dc57ec644 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -1094,7 +1094,9 @@ pub(crate) fn handle_code_action( } // Fixes from `cargo check`. - for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() { + for fix in + snap.check_fixes.values().filter_map(|it| it.get(&frange.file_id)).into_iter().flatten() + { // FIXME: this mapping is awkward and shouldn't exist. Refactor // `snap.check_fixes` to not convert to LSP prematurely. let intersect_fix_range = fix diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 262c30f132c1..572466cdfa7f 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -372,7 +372,7 @@ impl GlobalState { let _p = profile::span("GlobalState::handle_event/flycheck"); loop { match task { - flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { + flycheck::Message::AddDiagnostic { id, workspace_root, diagnostic } => { let snap = self.snapshot(); let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( @@ -384,6 +384,7 @@ impl GlobalState { for diag in diagnostics { match url_to_file_id(&self.vfs.read().0, &diag.url) { Ok(file_id) => self.diagnostics.add_check_diagnostic( + id, file_id, diag.diagnostic, diag.fix, @@ -401,7 +402,7 @@ impl GlobalState { flycheck::Message::Progress { id, progress } => { let (state, message) = match progress { flycheck::Progress::DidStart => { - self.diagnostics.clear_check(); + self.diagnostics.clear_check(id); (Progress::Begin, None) } flycheck::Progress::DidCheckCrate(target) => { diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 13bcb7dfa23b..579ba380273a 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -417,7 +417,7 @@ impl GlobalState { Some(it) => it, None => { self.flycheck = Vec::new(); - self.diagnostics.clear_check(); + self.diagnostics.clear_check_all(); return; } }; From d73b0d5fc6c6be434bd0e31ddc6572a6efaf6400 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 20 Jul 2022 11:52:44 +0200 Subject: [PATCH 3/5] Don't filter flychecks by package member status --- crates/rust-analyzer/src/main_loop.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 572466cdfa7f..561c2d7aef77 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -745,25 +745,24 @@ impl GlobalState { let workspace_ids = this.workspaces.iter().enumerate().filter(|(_, ws)| match ws { project_model::ProjectWorkspace::Cargo { cargo, .. } => { - cargo.packages().filter(|&pkg| cargo[pkg].is_member).any( - |pkg| { - cargo[pkg].targets.iter().any(|&it| { - paths.contains(&cargo[it].root.as_path()) - }) - }, - ) + cargo.packages().any(|pkg| { + cargo[pkg] + .targets + .iter() + .any(|&it| paths.contains(&cargo[it].root.as_path())) + }) } project_model::ProjectWorkspace::Json { project, .. } => project .crates() .any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)), project_model::ProjectWorkspace::DetachedFiles { .. } => false, }); - 'workspace: for (id, _) in workspace_ids { - for flycheck in &this.flycheck { + for flycheck in &this.flycheck { + for (id, _) in workspace_ids.clone() { if id == flycheck.id() { updated = true; flycheck.update(); - continue 'workspace; + continue; } } } From df9d3db82f1d0dce5f74faf8ae0501a534953f84 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 4 Aug 2022 13:22:15 +0200 Subject: [PATCH 4/5] Trigger flycheck on all transitive dependencies as well --- crates/rust-analyzer/src/main_loop.rs | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 561c2d7aef77..5c93874ca2af 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -9,7 +9,8 @@ use std::{ use always_assert::always; use crossbeam_channel::{select, Receiver}; -use ide_db::base_db::{SourceDatabaseExt, VfsPath}; +use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath}; +use itertools::Itertools; use lsp_server::{Connection, Notification, Request}; use lsp_types::notification::Notification as _; use vfs::{ChangeKind, FileId}; @@ -727,9 +728,21 @@ impl GlobalState { let (vfs, _) = &*this.vfs.read(); if let Some(file_id) = vfs.file_id(&vfs_path) { let analysis = this.analysis_host.analysis(); - let crate_ids = analysis.crate_for(file_id)?; + // Crates containing or depending on the saved file + let crate_ids: Vec<_> = analysis + .crate_for(file_id)? + .into_iter() + .flat_map(|id| { + this.analysis_host + .raw_database() + .crate_graph() + .transitive_rev_deps(id) + }) + .sorted() + .unique() + .collect(); - let paths: Vec<_> = crate_ids + let crate_root_paths: Vec<_> = crate_ids .iter() .filter_map(|&crate_id| { analysis @@ -740,16 +753,17 @@ impl GlobalState { .transpose() }) .collect::>()?; - let paths: Vec<_> = paths.iter().map(Deref::deref).collect(); + let crate_root_paths: Vec<_> = + crate_root_paths.iter().map(Deref::deref).collect(); + // Find all workspaces that have at least one target containing the saved file let workspace_ids = this.workspaces.iter().enumerate().filter(|(_, ws)| match ws { project_model::ProjectWorkspace::Cargo { cargo, .. } => { cargo.packages().any(|pkg| { - cargo[pkg] - .targets - .iter() - .any(|&it| paths.contains(&cargo[it].root.as_path())) + cargo[pkg].targets.iter().any(|&it| { + crate_root_paths.contains(&cargo[it].root.as_path()) + }) }) } project_model::ProjectWorkspace::Json { project, .. } => project @@ -757,6 +771,8 @@ impl GlobalState { .any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)), project_model::ProjectWorkspace::DetachedFiles { .. } => false, }); + + // Find and trigger corresponding flychecks for flycheck in &this.flycheck { for (id, _) in workspace_ids.clone() { if id == flycheck.id() { From df7f755e3b08e58a30bdbb39685bea76be7762ba Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 4 Aug 2022 14:01:33 +0200 Subject: [PATCH 5/5] Don't flycheck while the workspace is being loaded --- crates/rust-analyzer/src/diagnostics.rs | 1 - crates/rust-analyzer/src/global_state.rs | 1 + crates/rust-analyzer/src/main_loop.rs | 5 ++++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index 7917ced666a8..09150c77d7dd 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -72,7 +72,6 @@ impl DiagnosticCollection { let check_fixes = Arc::make_mut(&mut self.check_fixes); check_fixes.entry(flycheck_id).or_default().entry(file_id).or_default().extend(fix); diagnostics.push(diagnostic); - tracing::warn!(?flycheck_id, ?file_id, "add_check_diagnostic changes pushed"); self.changes.insert(file_id); } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 2cd2044aeff0..55c4cfcf8608 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -192,6 +192,7 @@ impl GlobalState { if let Some(path) = vfs.file_path(file.file_id).as_path() { let path = path.to_path_buf(); if reload::should_refresh_for_change(&path, file.change_kind) { + tracing::warn!("fetch-fiel_change"); self.fetch_workspaces_queue .request_op(format!("vfs file change: {}", path.display())); } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 5c93874ca2af..4ed34df01ca9 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -447,7 +447,10 @@ impl GlobalState { let memdocs_added_or_removed = self.mem_docs.take_changes(); if self.is_quiescent() { - if !was_quiescent { + if !was_quiescent + && !self.fetch_workspaces_queue.op_requested() + && !self.fetch_build_data_queue.op_requested() + { for flycheck in &self.flycheck { flycheck.update(); }