diff options
| author | Laurențiu Nicola <lnicola@dend.ro> | 2022-10-26 17:40:41 +0300 |
|---|---|---|
| committer | Laurențiu Nicola <lnicola@dend.ro> | 2022-10-26 17:40:41 +0300 |
| commit | 8807fc4cc358fa2152b303df0caa2f5fc9efaa9d (patch) | |
| tree | da701483c74d2d402617c2482626b70aa2fff536 /crates/rust-analyzer/src | |
| parent | 26a413e015b7fef4c0edb51bda0d39ab499f4950 (diff) | |
:arrow_up: rust-analyzer
Diffstat (limited to 'crates/rust-analyzer/src')
| -rw-r--r-- | crates/rust-analyzer/src/cargo_target_spec.rs | 2 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/cli/scip.rs | 107 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/config.rs | 79 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/dispatch.rs | 4 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/global_state.rs | 10 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 16 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/lsp_utils.rs | 10 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 184 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/reload.rs | 75 |
9 files changed, 299 insertions, 188 deletions
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index e1675a030c0..6ede194babc 100644 --- a/crates/rust-analyzer/src/cargo_target_spec.rs +++ b/crates/rust-analyzer/src/cargo_target_spec.rs @@ -118,7 +118,7 @@ impl CargoTargetSpec { global_state_snapshot: &GlobalStateSnapshot, file_id: FileId, ) -> Result<Option<CargoTargetSpec>> { - let crate_id = match &*global_state_snapshot.analysis.crate_for(file_id)? { + let crate_id = match &*global_state_snapshot.analysis.crates_for(file_id)? { &[crate_id, ..] => crate_id, _ => return Ok(None), }; diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index 2c29b3ee3a6..8b77ccde0ee 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -8,8 +8,8 @@ use std::{ use crate::line_index::{LineEndings, LineIndex, OffsetEncoding}; use hir::Name; use ide::{ - LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, TextRange, - TokenId, + LineCol, MonikerDescriptorKind, StaticIndex, StaticIndexedFile, TextRange, TokenId, + TokenStaticData, }; use ide_db::LineIndexDatabase; use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace}; @@ -75,7 +75,7 @@ impl flags::Scip { let mut symbols_emitted: HashSet<TokenId> = HashSet::default(); let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new(); - for file in si.files { + for StaticIndexedFile { file_id, tokens, .. } in si.files { let mut local_count = 0; let mut new_local_symbol = || { let new_symbol = scip::types::Symbol::new_local(local_count); @@ -84,7 +84,6 @@ impl flags::Scip { new_symbol }; - let StaticIndexedFile { file_id, tokens, .. } = file; let relative_path = match get_relative_filepath(&vfs, &rootpath, file_id) { Some(relative_path) => relative_path, None => continue, @@ -107,28 +106,20 @@ impl flags::Scip { let mut occurrence = scip_types::Occurrence::default(); occurrence.range = text_range_to_scip_range(&line_index, range); - occurrence.symbol = match tokens_to_symbol.get(&id) { - Some(symbol) => symbol.clone(), - None => { - let symbol = match &token.moniker { - Some(moniker) => moniker_to_symbol(&moniker), - None => new_local_symbol(), - }; - - let symbol = scip::symbol::format_symbol(symbol); - tokens_to_symbol.insert(id, symbol.clone()); - symbol - } - }; + occurrence.symbol = tokens_to_symbol + .entry(id) + .or_insert_with(|| { + let symbol = token_to_symbol(&token).unwrap_or_else(&mut new_local_symbol); + scip::symbol::format_symbol(symbol) + }) + .clone(); if let Some(def) = token.definition { if def.range == range { occurrence.symbol_roles |= scip_types::SymbolRole::Definition as i32; } - if !symbols_emitted.contains(&id) { - symbols_emitted.insert(id); - + if symbols_emitted.insert(id) { let mut symbol_info = scip_types::SymbolInformation::default(); symbol_info.symbol = occurrence.symbol.clone(); if let Some(hover) = &token.hover { @@ -207,9 +198,11 @@ fn new_descriptor(name: Name, suffix: scip_types::descriptor::Suffix) -> scip_ty /// /// Only returns a Symbol when it's a non-local symbol. /// So if the visibility isn't outside of a document, then it will return None -fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol { +fn token_to_symbol(token: &TokenStaticData) -> Option<scip_types::Symbol> { use scip_types::descriptor::Suffix::*; + let moniker = token.moniker.as_ref()?; + let package_name = moniker.package_information.name.clone(); let version = moniker.package_information.version.clone(); let descriptors = moniker @@ -233,7 +226,7 @@ fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol { }) .collect(); - scip_types::Symbol { + Some(scip_types::Symbol { scheme: "rust-analyzer".into(), package: Some(scip_types::Package { manager: "cargo".to_string(), @@ -244,19 +237,15 @@ fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol { .into(), descriptors, ..Default::default() - } + }) } #[cfg(test)] mod test { use super::*; - use hir::Semantics; - use ide::{AnalysisHost, FilePosition}; - use ide_db::defs::IdentClass; - use ide_db::{base_db::fixture::ChangeFixture, helpers::pick_best_token}; + use ide::{AnalysisHost, FilePosition, StaticIndex, TextSize}; + use ide_db::base_db::fixture::ChangeFixture; use scip::symbol::format_symbol; - use syntax::SyntaxKind::*; - use syntax::{AstNode, T}; fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) { let mut host = AnalysisHost::default(); @@ -273,53 +262,33 @@ mod test { fn check_symbol(ra_fixture: &str, expected: &str) { let (host, position) = position(ra_fixture); + let analysis = host.analysis(); + let si = StaticIndex::compute(&analysis); + let FilePosition { file_id, offset } = position; - let db = host.raw_database(); - let sema = &Semantics::new(db); - let file = sema.parse(file_id).syntax().clone(); - let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind { - IDENT - | INT_NUMBER - | LIFETIME_IDENT - | T![self] - | T![super] - | T![crate] - | T![Self] - | COMMENT => 2, - kind if kind.is_trivia() => 0, - _ => 1, - }) - .expect("OK OK"); - - let navs = sema - .descend_into_macros(original_token.clone()) - .into_iter() - .filter_map(|token| { - IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| { - it.into_iter().flat_map(|def| { - let module = def.module(db).unwrap(); - let current_crate = module.krate(); - - match MonikerResult::from_def(sema.db, def, current_crate) { - Some(moniker_result) => Some(moniker_to_symbol(&moniker_result)), - None => None, - } - }) - }) - }) - .flatten() - .collect::<Vec<_>>(); + let mut found_symbol = None; + for file in &si.files { + if file.file_id != file_id { + continue; + } + for &(range, id) in &file.tokens { + if range.contains(offset - TextSize::from(1)) { + let token = si.tokens.get(id).unwrap(); + found_symbol = token_to_symbol(token); + break; + } + } + } if expected == "" { - assert_eq!(0, navs.len(), "must have no symbols {:?}", navs); + assert!(found_symbol.is_none(), "must have no symbols {:?}", found_symbol); return; } - assert_eq!(1, navs.len(), "must have one symbol {:?}", navs); - - let res = navs.get(0).unwrap(); - let formatted = format_symbol(res.clone()); + assert!(found_symbol.is_some(), "must have one symbol {:?}", found_symbol); + let res = found_symbol.unwrap(); + let formatted = format_symbol(res); assert_eq!(formatted, expected); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 577a8640a4c..85322f12a83 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -69,6 +69,19 @@ config_data! { cargo_autoreload: bool = "true", /// Run build scripts (`build.rs`) for more precise code analysis. cargo_buildScripts_enable: bool = "true", + /// Specifies the working directory for running build scripts. + /// - "workspace": run build scripts for a workspace in the workspace's root directory. + /// This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`. + /// - "root": run build scripts in the project's root directory. + /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// is set. + cargo_buildScripts_invocationLocation: InvocationLocation = "\"workspace\"", + /// Specifies the invocation strategy to use when running the build scripts command. + /// If `per_workspace` is set, the command will be executed for each workspace. + /// If `once` is set, the command will be executed once. + /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// is set. + cargo_buildScripts_invocationStrategy: InvocationStrategy = "\"per_workspace\"", /// Override the command rust-analyzer uses to run build scripts and /// build procedural macros. The command is required to output json /// and should therefore include `--message-format=json` or a similar @@ -122,6 +135,20 @@ config_data! { /// /// Set to `"all"` to pass `--all-features` to Cargo. checkOnSave_features: Option<CargoFeaturesDef> = "null", + /// Specifies the working directory for running checks. + /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories. + // FIXME: Ideally we would support this in some way + /// This falls back to "root" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`. + /// - "root": run checks in the project's root directory. + /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// is set. + checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"", + /// Specifies the invocation strategy to use when running the checkOnSave command. + /// If `per_workspace` is set, the command will be executed for each workspace. + /// If `once` is set, the command will be executed once. + /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// is set. + checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"", /// Whether to pass `--no-default-features` to Cargo. Defaults to /// `#rust-analyzer.cargo.noDefaultFeatures#`. checkOnSave_noDefaultFeatures: Option<bool> = "null", @@ -1056,6 +1083,16 @@ impl Config { rustc_source, unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()), wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper, + invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy { + InvocationStrategy::Once => project_model::InvocationStrategy::Once, + InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace, + }, + invocation_location: match self.data.cargo_buildScripts_invocationLocation { + InvocationLocation::Root => { + project_model::InvocationLocation::Root(self.root_path.clone()) + } + InvocationLocation::Workspace => project_model::InvocationLocation::Workspace, + }, run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(), extra_env: self.data.cargo_extraEnv.clone(), } @@ -1087,6 +1124,18 @@ impl Config { command, args, extra_env: self.check_on_save_extra_env(), + invocation_strategy: match self.data.checkOnSave_invocationStrategy { + InvocationStrategy::Once => flycheck::InvocationStrategy::Once, + InvocationStrategy::PerWorkspace => { + flycheck::InvocationStrategy::PerWorkspace + } + }, + invocation_location: match self.data.checkOnSave_invocationLocation { + InvocationLocation::Root => { + flycheck::InvocationLocation::Root(self.root_path.clone()) + } + InvocationLocation::Workspace => flycheck::InvocationLocation::Workspace, + }, } } Some(_) | None => FlycheckConfig::CargoCommand { @@ -1588,6 +1637,20 @@ enum CargoFeaturesDef { } #[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +enum InvocationStrategy { + Once, + PerWorkspace, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +enum InvocationLocation { + Root, + Workspace, +} + +#[derive(Deserialize, Debug, Clone)] #[serde(untagged)] enum LifetimeElisionDef { #[serde(deserialize_with = "true_or_always")] @@ -2001,6 +2064,22 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "Render annotations above the whole item, including documentation comments and attributes." ], }, + "InvocationStrategy" => set! { + "type": "string", + "enum": ["per_workspace", "once"], + "enumDescriptions": [ + "The command will be executed for each workspace.", + "The command will be executed once." + ], + }, + "InvocationLocation" => set! { + "type": "string", + "enum": ["workspace", "root"], + "enumDescriptions": [ + "The command will be executed in the corresponding workspace root.", + "The command will be executed in the project root." + ], + }, _ => panic!("missing entry for {}: {}", ty, default), } diff --git a/crates/rust-analyzer/src/dispatch.rs b/crates/rust-analyzer/src/dispatch.rs index f16559148e6..57899b59914 100644 --- a/crates/rust-analyzer/src/dispatch.rs +++ b/crates/rust-analyzer/src/dispatch.rs @@ -52,7 +52,7 @@ impl<'a> RequestDispatcher<'a> { let _pctx = stdx::panic_context::enter(panic_context); f(self.global_state, params) }; - if let Ok(response) = result_to_response::<R>(req.id.clone(), result) { + if let Ok(response) = result_to_response::<R>(req.id, result) { self.global_state.respond(response); } @@ -80,7 +80,7 @@ impl<'a> RequestDispatcher<'a> { f(global_state_snapshot, params) }); - if let Ok(response) = thread_result_to_response::<R>(req.id.clone(), result) { + if let Ok(response) = thread_result_to_response::<R>(req.id, result) { self.global_state.respond(response); } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 000ff88e458..3fb06c31f7c 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -64,7 +64,7 @@ pub(crate) struct GlobalState { pub(crate) source_root_config: SourceRootConfig, pub(crate) proc_macro_clients: Vec<Result<ProcMacroServer, String>>, - pub(crate) flycheck: Vec<FlycheckHandle>, + pub(crate) flycheck: Arc<[FlycheckHandle]>, pub(crate) flycheck_sender: Sender<flycheck::Message>, pub(crate) flycheck_receiver: Receiver<flycheck::Message>, @@ -117,6 +117,7 @@ pub(crate) struct GlobalStateSnapshot { vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>, pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, pub(crate) proc_macros_loaded: bool, + pub(crate) flycheck: Arc<[FlycheckHandle]>, } impl std::panic::UnwindSafe for GlobalStateSnapshot {} @@ -155,7 +156,7 @@ impl GlobalState { source_root_config: SourceRootConfig::default(), proc_macro_clients: vec![], - flycheck: Vec::new(), + flycheck: Arc::new([]), flycheck_sender, flycheck_receiver, @@ -295,6 +296,7 @@ impl GlobalState { mem_docs: self.mem_docs.clone(), semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache), proc_macros_loaded: !self.fetch_build_data_queue.last_op_result().0.is_empty(), + flycheck: self.flycheck.clone(), } } @@ -398,6 +400,10 @@ impl GlobalStateSnapshot { url_from_abs_path(path) } + pub(crate) fn file_id_to_file_path(&self, file_id: FileId) -> vfs::VfsPath { + self.vfs.read().0.file_path(file_id) + } + pub(crate) fn cargo_target_for_crate_root( &self, crate_id: CrateId, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 8c3ea77d061..34795a8eb40 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -658,7 +658,7 @@ pub(crate) fn handle_parent_module( // check if invoked at the crate root let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; - let crate_id = match snap.analysis.crate_for(file_id)?.first() { + let crate_id = match snap.analysis.crates_for(file_id)?.first() { Some(&crate_id) => crate_id, None => return Ok(None), }; @@ -1782,7 +1782,15 @@ fn run_rustfmt( ) -> Result<Option<Vec<lsp_types::TextEdit>>> { let file_id = from_proto::file_id(snap, &text_document.uri)?; let file = snap.analysis.file_text(file_id)?; - let crate_ids = snap.analysis.crate_for(file_id)?; + + // find the edition of the package the file belongs to + // (if it belongs to multiple we'll just pick the first one and pray) + let edition = snap + .analysis + .relevant_crates_for(file_id)? + .into_iter() + .find_map(|crate_id| snap.cargo_target_for_crate_root(crate_id)) + .map(|(ws, target)| ws[ws[target].package].edition); let line_index = snap.file_line_index(file_id)?; @@ -1808,9 +1816,7 @@ fn run_rustfmt( ); } } - if let Some(&crate_id) = crate_ids.first() { - // Assume all crates are in the same edition - let edition = snap.analysis.crate_edition(crate_id)?; + if let Some(edition) = edition { cmd.arg("--edition"); cmd.arg(edition.to_string()); } diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs index 5a37cbe2e33..b3cea64d417 100644 --- a/crates/rust-analyzer/src/lsp_utils.rs +++ b/crates/rust-analyzer/src/lsp_utils.rs @@ -87,6 +87,7 @@ impl GlobalState { state: Progress, message: Option<String>, fraction: Option<f64>, + cancel_token: Option<String>, ) { if !self.config.work_done_progress() { return; @@ -95,7 +96,10 @@ impl GlobalState { assert!((0.0..=1.0).contains(&f)); (f * 100.0) as u32 }); - let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title)); + let cancellable = Some(cancel_token.is_some()); + let token = lsp_types::ProgressToken::String( + cancel_token.unwrap_or_else(|| format!("rustAnalyzer/{}", title)), + ); let work_done_progress = match state { Progress::Begin => { self.send_request::<lsp_types::request::WorkDoneProgressCreate>( @@ -105,14 +109,14 @@ impl GlobalState { lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { title: title.into(), - cancellable: None, + cancellable, message, percentage, }) } Progress::Report => { lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { - cancellable: None, + cancellable, message, percentage, }) diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 15922dac651..2c928a58040 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -10,7 +10,7 @@ use std::{ use always_assert::always; use crossbeam_channel::{select, Receiver}; use flycheck::FlycheckHandle; -use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath}; +use ide_db::base_db::{SourceDatabaseExt, VfsPath}; use itertools::Itertools; use lsp_server::{Connection, Notification, Request}; use lsp_types::notification::Notification as _; @@ -191,7 +191,7 @@ impl GlobalState { // NOTE: don't count blocking select! call as a loop-turn time let _p = profile::span("GlobalState::handle_event"); - tracing::debug!("handle_event({:?})", event); + tracing::debug!("{:?} handle_event({:?})", loop_start, event); let task_queue_len = self.task_pool.handle.len(); if task_queue_len > 0 { tracing::info!("task queue len: {}", task_queue_len); @@ -257,7 +257,7 @@ impl GlobalState { } }; - self.report_progress("Indexing", state, message, Some(fraction)); + self.report_progress("Indexing", state, message, Some(fraction), None); } } Event::Vfs(message) => { @@ -465,7 +465,7 @@ impl GlobalState { } }; - self.report_progress("Fetching", state, msg, None); + self.report_progress("Fetching", state, msg, None, None); } Task::FetchBuildData(progress) => { let (state, msg) = match progress { @@ -481,7 +481,7 @@ impl GlobalState { }; if let Some(state) = state { - self.report_progress("Loading", state, msg, None); + self.report_progress("Loading", state, msg, None, None); } } } @@ -518,6 +518,7 @@ impl GlobalState { state, Some(format!("{}/{}", n_done, n_total)), Some(Progress::fraction(n_done, n_total)), + None, ) } } @@ -542,7 +543,10 @@ impl GlobalState { diag.fix, ), Err(err) => { - tracing::error!("File with cargo diagnostic not found in VFS: {}", err); + tracing::error!( + "flycheck {id}: File with cargo diagnostic not found in VFS: {}", + err + ); } }; } @@ -584,7 +588,13 @@ impl GlobalState { } else { format!("cargo check (#{})", id + 1) }; - self.report_progress(&title, state, message, None); + self.report_progress( + &title, + state, + message, + None, + Some(format!("rust-analyzer/checkOnSave/{}", id)), + ); } } } @@ -698,7 +708,16 @@ impl GlobalState { this.cancel(id); Ok(()) })? - .on::<lsp_types::notification::WorkDoneProgressCancel>(|_this, _params| { + .on::<lsp_types::notification::WorkDoneProgressCancel>(|this, params| { + if let lsp_types::NumberOrString::String(s) = ¶ms.token { + if let Some(id) = s.strip_prefix("rust-analyzer/checkOnSave/") { + if let Ok(id) = u32::from_str_radix(id, 10) { + if let Some(flycheck) = this.flycheck.get(id as usize) { + flycheck.cancel(); + } + } + } + } // Just ignore this. It is OK to continue sending progress // notifications for this token, as the client can't know when // we accepted notification. @@ -711,7 +730,7 @@ impl GlobalState { .insert(path.clone(), DocumentData::new(params.text_document.version)) .is_err(); if already_exists { - tracing::error!("duplicate DidOpenTextDocument: {}", path) + tracing::error!("duplicate DidOpenTextDocument: {}", path); } this.vfs .write() @@ -758,69 +777,7 @@ impl GlobalState { Ok(()) })? .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| { - let mut updated = false; if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) { - let (vfs, _) = &*this.vfs.read(); - - // Trigger flychecks for all workspaces that depend on the saved file - if let Some(file_id) = vfs.file_id(&vfs_path) { - let analysis = this.analysis_host.analysis(); - // 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 crate_root_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::<ide::Cancellable<_>>()?; - 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| { - crate_root_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, - }); - - // Find and trigger corresponding flychecks - for flycheck in &this.flycheck { - for (id, _) in workspace_ids.clone() { - if id == flycheck.id() { - updated = true; - flycheck.restart(); - continue; - } - } - } - } - // Re-fetch workspaces if a workspace related file has changed if let Some(abs_path) = vfs_path.as_path() { if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) { @@ -828,13 +785,90 @@ impl GlobalState { .request_op(format!("DidSaveTextDocument {}", abs_path.display())); } } + + let file_id = this.vfs.read().0.file_id(&vfs_path); + if let Some(file_id) = file_id { + let world = this.snapshot(); + let mut updated = false; + let task = move || -> std::result::Result<(), ide::Cancelled> { + // Trigger flychecks for all workspaces that depend on the saved file + // Crates containing or depending on the saved file + let crate_ids: Vec<_> = world + .analysis + .crates_for(file_id)? + .into_iter() + .flat_map(|id| world.analysis.transitive_rev_deps(id)) + .flatten() + .sorted() + .unique() + .collect(); + + let crate_root_paths: Vec<_> = crate_ids + .iter() + .filter_map(|&crate_id| { + world + .analysis + .crate_root(crate_id) + .map(|file_id| { + world + .file_id_to_file_path(file_id) + .as_path() + .map(ToOwned::to_owned) + }) + .transpose() + }) + .collect::<ide::Cancellable<_>>()?; + 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 = + world.workspaces.iter().enumerate().filter(|(_, ws)| match ws { + project_model::ProjectWorkspace::Cargo { cargo, .. } => { + cargo.packages().any(|pkg| { + cargo[pkg].targets.iter().any(|&it| { + crate_root_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, + }); + + // Find and trigger corresponding flychecks + for flycheck in world.flycheck.iter() { + for (id, _) in workspace_ids.clone() { + if id == flycheck.id() { + updated = true; + flycheck.restart(); + continue; + } + } + } + // No specific flycheck was triggered, so let's trigger all of them. + if !updated { + for flycheck in world.flycheck.iter() { + flycheck.restart(); + } + } + Ok(()) + }; + this.task_pool.handle.spawn_with_sender(move |_| { + if let Err(e) = std::panic::catch_unwind(task) { + tracing::error!("DidSaveTextDocument flycheck task panicked: {e:?}") + } + }); + return Ok(()); + } } // No specific flycheck was triggered, so let's trigger all of them. - if !updated { - for flycheck in &this.flycheck { - flycheck.restart(); - } + for flycheck in this.flycheck.iter() { + flycheck.restart(); } Ok(()) })? diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index f8734893944..e1f651786de 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -175,10 +175,8 @@ impl GlobalState { sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap() } }; - let mut res = Vec::new(); - for ws in workspaces.iter() { - res.push(ws.run_build_scripts(&config, &progress)); - } + let res = ProjectWorkspace::run_all_build_scripts(&workspaces, &config, &progress); + sender.send(Task::FetchBuildData(BuildDataProgress::End((workspaces, res)))).unwrap(); }); } @@ -468,39 +466,54 @@ impl GlobalState { let config = match self.config.flycheck() { Some(it) => it, None => { - self.flycheck = Vec::new(); + self.flycheck = Arc::new([]); self.diagnostics.clear_check_all(); return; } }; let sender = self.flycheck_sender.clone(); - self.flycheck = self - .workspaces - .iter() - .enumerate() - .filter_map(|(id, w)| match w { - ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())), - ProjectWorkspace::Json { project, .. } => { - // Enable flychecks for json projects if a custom flycheck command was supplied - // in the workspace configuration. - match config { - FlycheckConfig::CustomCommand { .. } => Some((id, project.path())), - _ => None, - } - } - ProjectWorkspace::DetachedFiles { .. } => None, - }) - .map(|(id, root)| { - let sender = sender.clone(); - FlycheckHandle::spawn( - id, - Box::new(move |msg| sender.send(msg).unwrap()), - config.clone(), - root.to_path_buf(), - ) - }) - .collect(); + let invocation_strategy = match config { + FlycheckConfig::CargoCommand { .. } => flycheck::InvocationStrategy::PerWorkspace, + FlycheckConfig::CustomCommand { invocation_strategy, .. } => invocation_strategy, + }; + + self.flycheck = match invocation_strategy { + flycheck::InvocationStrategy::Once => vec![FlycheckHandle::spawn( + 0, + Box::new(move |msg| sender.send(msg).unwrap()), + config.clone(), + self.config.root_path().clone(), + )], + flycheck::InvocationStrategy::PerWorkspace => { + self.workspaces + .iter() + .enumerate() + .filter_map(|(id, w)| match w { + ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())), + ProjectWorkspace::Json { project, .. } => { + // Enable flychecks for json projects if a custom flycheck command was supplied + // in the workspace configuration. + match config { + FlycheckConfig::CustomCommand { .. } => Some((id, project.path())), + _ => None, + } + } + ProjectWorkspace::DetachedFiles { .. } => None, + }) + .map(|(id, root)| { + let sender = sender.clone(); + FlycheckHandle::spawn( + id, + Box::new(move |msg| sender.send(msg).unwrap()), + config.clone(), + root.to_path_buf(), + ) + }) + .collect() + } + } + .into(); } } |
