diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 4a09ef21ad..6724b6a7ad 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -779,11 +779,8 @@ pub struct Config { /// Config node whose values apply to **every** Rust project. user_config: Option<(GlobalLocalConfigInput, ConfigErrors)>, - /// A special file for this session whose path is set to `self.root_path.join("rust-analyzer.toml")` - root_ratoml_path: VfsPath, - - /// This file can be used to make global changes while having only a workspace-wide scope. - root_ratoml: Option<(GlobalLocalConfigInput, ConfigErrors)>, + /// TODO : This file can be used to make global changes while having only a workspace-wide scope. + workspace_ratoml_change: FxHashMap, /// For every `SourceRoot` there can be at most one RATOML file. ratoml_files: FxHashMap, @@ -917,38 +914,44 @@ impl Config { should_update = true; } - if let Some(change) = change.root_ratoml_change { - tracing::info!("updating root ra-toml config: {:#}", change); - #[allow(clippy::single_match)] - match toml::from_str(&change) { - Ok(table) => { + if let Some(change) = change.workspace_ratoml_change { + tracing::info!("updating root ra-toml config"); + for (source_root_id, (_, text)) in change { + if let Some(text) = text { let mut toml_errors = vec![]; - validate_toml_table( - GlobalLocalConfigInput::FIELDS, - &table, - &mut String::new(), - &mut toml_errors, - ); - config.root_ratoml = Some(( - GlobalLocalConfigInput::from_toml(table, &mut toml_errors), - ConfigErrors( - toml_errors - .into_iter() - .map(|(a, b)| ConfigErrorInner::Toml { config_key: a, error: b }) - .map(Arc::new) - .collect(), - ), - )); - should_update = true; - } - Err(e) => { - config.root_ratoml = Some(( - GlobalLocalConfigInput::from_toml(toml::map::Map::default(), &mut vec![]), - ConfigErrors(vec![ConfigErrorInner::ParseError { - reason: e.message().to_owned(), + match toml::from_str(&text) { + Ok(table) => { + validate_toml_table( + GlobalLocalConfigInput::FIELDS, + &table, + &mut String::new(), + &mut toml_errors, + ); + config.workspace_ratoml_change.insert( + source_root_id, + ( + GlobalLocalConfigInput::from_toml(table, &mut toml_errors), + ConfigErrors( + toml_errors + .into_iter() + .map(|(a, b)| ConfigErrorInner::Toml { + config_key: a, + error: b, + }) + .map(Arc::new) + .collect(), + ), + ), + ); + should_update = true; } - .into()]), - )); + Err(e) => { + config.validation_errors.0.push( + ConfigErrorInner::ParseError { reason: e.message().to_owned() } + .into(), + ); + } + } } } } @@ -958,7 +961,6 @@ impl Config { if let Some(text) = text { let mut toml_errors = vec![]; tracing::info!("updating ra-toml config: {:#}", text); - #[allow(clippy::single_match)] match toml::from_str(&text) { Ok(table) => { validate_toml_table( @@ -985,16 +987,10 @@ impl Config { ); } Err(e) => { - config.root_ratoml = Some(( - GlobalLocalConfigInput::from_toml( - toml::map::Map::default(), - &mut vec![], - ), - ConfigErrors(vec![ConfigErrorInner::ParseError { - reason: e.message().to_owned(), - } - .into()]), - )); + config.validation_errors.0.push( + ConfigErrorInner::ParseError { reason: e.message().to_owned() } + .into(), + ); } } } @@ -1026,7 +1022,13 @@ impl Config { .1 .0 .iter() - .chain(config.root_ratoml.as_ref().into_iter().flat_map(|it| it.1 .0.iter())) + .chain( + config + .workspace_ratoml_change + .values() + .into_iter() + .flat_map(|it| it.1 .0.iter()), + ) .chain(config.user_config.as_ref().into_iter().flat_map(|it| it.1 .0.iter())) .chain(config.ratoml_files.values().flat_map(|it| it.1 .0.iter())) .chain(config.validation_errors.0.iter()) @@ -1055,8 +1057,8 @@ impl Config { #[derive(Default, Debug)] pub struct ConfigChange { user_config_change: Option>, - root_ratoml_change: Option>, client_config_change: Option, + workspace_ratoml_change: Option>)>>, ratoml_file_change: Option>)>>, source_map_change: Option>>, } @@ -1078,9 +1080,15 @@ impl ConfigChange { self.user_config_change = content; } - pub fn change_root_ratoml(&mut self, content: Option>) { - assert!(self.root_ratoml_change.is_none()); // Otherwise it is a double write. - self.root_ratoml_change = content; + pub fn change_workspace_ratoml( + &mut self, + source_root: SourceRootId, + vfs_path: VfsPath, + content: Option>, + ) -> Option<(VfsPath, Option>)> { + self.workspace_ratoml_change + .get_or_insert_with(Default::default) + .insert(source_root, (vfs_path, content)) } pub fn change_client_config(&mut self, change: serde_json::Value) { @@ -1333,11 +1341,6 @@ impl Config { // FIXME @alibektas : Temporary solution. I don't think this is right as at some point we may allow users to specify // custom USER_CONFIG_PATHs which may also be relative. let user_config_path = VfsPath::from(AbsPathBuf::assert(user_config_path)); - let root_ratoml_path = { - let mut p = root_path.clone(); - p.push("rust-analyzer.toml"); - VfsPath::new_real_path(p.to_string()) - }; Config { caps: ClientCapabilities::new(caps), @@ -1352,10 +1355,9 @@ impl Config { source_root_parent_map: Arc::new(FxHashMap::default()), user_config: None, user_config_path, - root_ratoml: None, - root_ratoml_path, detached_files: Default::default(), validation_errors: Default::default(), + workspace_ratoml_change: Default::default(), } } @@ -1398,10 +1400,6 @@ impl Config { &self.root_path } - pub fn root_ratoml_path(&self) -> &VfsPath { - &self.root_ratoml_path - } - pub fn caps(&self) -> &ClientCapabilities { &self.caps } @@ -3591,7 +3589,7 @@ mod tests { let mut change = ConfigChange::default(); - change.change_root_ratoml(Some( + change.change_user_config(Some( toml::toml! { [cargo.cfgs] these = "these" diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 9fd9bee537..d78e760358 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -10,6 +10,7 @@ use flycheck::{project_json, FlycheckHandle}; use hir::ChangeWithProcMacros; use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId}; use ide_db::base_db::{CrateId, ProcMacroPaths, SourceDatabaseExt}; +use itertools::Itertools; use load_cargo::SourceRootConfig; use lsp_types::{SemanticTokens, Url}; use nohash_hasher::IntMap; @@ -22,7 +23,7 @@ use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, Worksp use rustc_hash::{FxHashMap, FxHashSet}; use tracing::{span, trace, Level}; use triomphe::Arc; -use vfs::{AbsPathBuf, AnchoredPathBuf, ChangeKind, Vfs}; +use vfs::{AbsPathBuf, AnchoredPathBuf, ChangeKind, Vfs, VfsPath}; use crate::{ config::{Config, ConfigChange, ConfigErrors}, @@ -382,26 +383,37 @@ impl GlobalState { { let config_change = { let user_config_path = self.config.user_config_path(); - let root_ratoml_path = self.config.root_ratoml_path(); let mut change = ConfigChange::default(); let db = self.analysis_host.raw_database(); + // FIXME @alibektas : This is silly. There is abs no reason to use VfsPaths when there is SourceRoots. But how + // do I resolve a "workspace_root" to its corresponding id without having to rely on a cargo.toml's ( or project json etc.) file id? + let workspace_roots = self + .workspaces + .iter() + .map(|ws| VfsPath::from(ws.workspace_root().to_owned())) + .collect_vec(); + for (file_id, (_change_kind, vfs_path)) in modified_ratoml_files { if vfs_path == *user_config_path { change.change_user_config(Some(db.file_text(file_id))); continue; } - if vfs_path == *root_ratoml_path { - change.change_root_ratoml(Some(db.file_text(file_id))); + // If change has been made to a ratoml file that + // belongs to a non-local source root, we will ignore it. + let sr_id = db.file_source_root(file_id); + let sr = db.source_root(sr_id); + + if workspace_roots.contains(&vfs_path) { + change.change_workspace_ratoml( + sr_id, + vfs_path, + Some(db.file_text(file_id)), + ); continue; } - // If change has been made to a ratoml file that - // belongs to a non-local source root, we will ignore it. - // As it doesn't make sense a users to use external config files. - let sr_id = db.file_source_root(file_id); - let sr = db.source_root(sr_id); if !sr.is_library { if let Some((old_path, old_text)) = change.change_ratoml( sr_id, @@ -430,7 +442,7 @@ impl GlobalState { if should_update { self.update_configuration(config); } else { - // No global or client level config was changed. So we can just naively replace config. + // No global or client level config was changed. So we can naively replace config. self.config = Arc::new(config); } } diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index fb16f28a14..b2d360c62d 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -541,7 +541,6 @@ impl GlobalState { watchers.extend( iter::once(self.config.user_config_path().as_path()) - .chain(iter::once(self.config.root_ratoml_path().as_path())) .chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref))) .flatten() .map(|glob_pattern| lsp_types::FileSystemWatcher { diff --git a/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/crates/rust-analyzer/tests/slow-tests/ratoml.rs index 3b05138e18..298e10fb9e 100644 --- a/crates/rust-analyzer/tests/slow-tests/ratoml.rs +++ b/crates/rust-analyzer/tests/slow-tests/ratoml.rs @@ -808,7 +808,7 @@ enum Value { /// Having a ratoml file at the root of a project enables /// configuring global level configurations as well. #[test] -#[ignore = "Root ratomls are not being looked for on startup. Fix this."] +// #[ignore = "Root ratomls are not being looked for on startup. Fix this."] fn ratoml_in_root_is_global() { let server = RatomlTest::new( vec![