diff --git a/Cargo.lock b/Cargo.lock index fcb188c0dfab..aaedd6281a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,7 @@ dependencies = [ "profile", "rustc-hash", "salsa", + "serde", "stdx", "syntax", "test-utils", @@ -788,6 +789,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", + "serde", ] [[package]] @@ -888,9 +890,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -1020,9 +1022,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -1583,7 +1585,9 @@ dependencies = [ "ide", "ide-db", "ide-ssr", + "indexmap 2.0.0", "itertools", + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "load-cargo", "lsp-server 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "lsp-types", @@ -1602,6 +1606,7 @@ dependencies = [ "rayon", "rustc-dependencies", "rustc-hash", + "salsa", "scip", "serde", "serde_json", @@ -1610,6 +1615,7 @@ dependencies = [ "syntax", "test-utils", "tikv-jemallocator", + "toml", "toolchain", "tracing", "tracing-log", @@ -1762,6 +1768,15 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -1996,6 +2011,40 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "toolchain" version = "0.0.0" @@ -2365,6 +2414,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6" +dependencies = [ + "memchr", +] + [[package]] name = "write-json" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index c382a5a37d23..e24ab0867236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ nohash-hasher = "0.2.0" text-size = "1.1.0" serde = { version = "1.0.156", features = ["derive"] } serde_json = "1.0.96" +salsa = "0.17.0-pre.2" triomphe = { version = "0.1.8", default-features = false, features = ["std"] } # can't upgrade due to dashmap depending on 0.12.3 currently hashbrown = { version = "0.12.3", features = [ diff --git a/crates/base-db/Cargo.toml b/crates/base-db/Cargo.toml index 171c113a950d..d6fd0047a22c 100644 --- a/crates/base-db/Cargo.toml +++ b/crates/base-db/Cargo.toml @@ -12,7 +12,7 @@ rust-version.workspace = true doctest = false [dependencies] -salsa = "0.17.0-pre.2" +salsa.workspace = true rustc-hash = "1.1.0" triomphe.workspace = true @@ -27,3 +27,4 @@ syntax.workspace = true test-utils.workspace = true tt.workspace = true vfs.workspace = true +serde.workspace = true diff --git a/crates/base-db/src/change.rs b/crates/base-db/src/change.rs index 6a3b36b23128..8300326650ab 100644 --- a/crates/base-db/src/change.rs +++ b/crates/base-db/src/change.rs @@ -25,7 +25,8 @@ impl fmt::Debug for Change { d.field("roots", roots); } if !self.files_changed.is_empty() { - d.field("files_changed", &self.files_changed.len()); + d.field("files_changed", &self.files_changed); + // d.field("files_changed", &self.files_changed.len()); } if self.crate_graph.is_some() { d.field("crate_graph", &self.crate_graph); diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 65db5c0fc7d3..a43f542ecdc3 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -11,6 +11,7 @@ use std::{fmt, mem, ops, panic::RefUnwindSafe, str::FromStr, sync}; use cfg::CfgOptions; use la_arena::{Arena, Idx}; use rustc_hash::{FxHashMap, FxHashSet}; +use serde::Serialize; use syntax::SmolStr; use triomphe::Arc; use tt::token_id::Subtree; @@ -21,6 +22,9 @@ use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath}; pub type ProcMacroPaths = FxHashMap, AbsPathBuf), String>>; pub type ProcMacros = FxHashMap; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct SourceRootId(pub u32); + /// Files are grouped into source roots. A source root is a directory on the /// file systems which is watched for changes. Typically it corresponds to a /// Rust crate. Source roots *might* be nested: in this case, a file belongs to @@ -28,9 +32,6 @@ pub type ProcMacros = FxHashMap; /// source root, and the analyzer does not know the root path of the source root at /// all. So, a file from one source root can't refer to a file in another source /// root by path. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct SourceRootId(pub u32); - #[derive(Clone, Debug, PartialEq, Eq)] pub struct SourceRoot { /// Sysroot or crates.io library. @@ -38,16 +39,23 @@ pub struct SourceRoot { /// Libraries are considered mostly immutable, this assumption is used to /// optimize salsa's query structure pub is_library: bool, + cargo_file_id: Option, + /// FIXME : @alibektas We know that this is wrong. + /// base-db must stay as a level of abstraction + /// that has no knowledge of such specific files + /// so this should be moved somewhere else. + ratoml_file_id: Option, file_set: FileSet, } impl SourceRoot { pub fn new_local(file_set: FileSet) -> SourceRoot { - SourceRoot { is_library: false, file_set } + eprintln!("{:#?}", file_set); + SourceRoot { is_library: false, file_set, cargo_file_id: None, ratoml_file_id: None } } pub fn new_library(file_set: FileSet) -> SourceRoot { - SourceRoot { is_library: true, file_set } + SourceRoot { is_library: true, file_set, cargo_file_id: None, ratoml_file_id: None } } pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> { @@ -65,6 +73,16 @@ impl SourceRoot { pub fn iter(&self) -> impl Iterator + '_ { self.file_set.iter() } + + /// Get `FileId` of the `Cargo.toml` if one present in `SourceRoot` + pub fn cargo_toml(&self) -> Option { + self.cargo_file_id + } + + /// Get `FileId` of the `rust-analyzer.toml` if one present in `SourceRoot` + pub fn ratoml(&self) -> Option { + self.ratoml_file_id + } } /// `CrateGraph` is a bit of information which turns a set of text files into a @@ -103,6 +121,15 @@ pub type CrateId = Idx; #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct CrateName(SmolStr); +impl Serialize for CrateName { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self) + } +} + impl CrateName { /// Creates a crate name, checking for dashes in the string provided. /// Dashes are not allowed in the crate names, diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index 7a795dd62ab7..68b592ffaa4d 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -208,6 +208,7 @@ impl ProjectFolders { let entry = { let mut dirs = vfs::loader::Directories::default(); dirs.extensions.push("rs".into()); + dirs.extensions.push("toml".into()); dirs.include.extend(root.include); dirs.exclude.extend(root.exclude); for excl in global_excludes { diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs index 88b8d0aee3a4..55bd5376dfa8 100644 --- a/crates/paths/src/lib.rs +++ b/crates/paths/src/lib.rs @@ -136,7 +136,8 @@ impl AbsPath { /// # Panics /// /// Panics if `path` is not absolute. - pub fn assert(path: &Path) -> &AbsPath { + pub fn assert + ?Sized>(path: &P) -> &AbsPath { + let path = path.as_ref(); assert!(path.is_absolute()); unsafe { &*(path as *const Path as *const AbsPath) } } @@ -194,6 +195,10 @@ impl AbsPath { self.0.ends_with(&suffix.0) } + pub fn ancestors(&self) -> impl Iterator { + self.0.ancestors().map(AbsPath::assert) + } + pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { Some(( self.file_stem()?.to_str()?, diff --git a/crates/project-model/src/cfg_flag.rs b/crates/project-model/src/cfg_flag.rs index e366d441c1bd..05d69726e5ef 100644 --- a/crates/project-model/src/cfg_flag.rs +++ b/crates/project-model/src/cfg_flag.rs @@ -4,8 +4,9 @@ use std::{fmt, str::FromStr}; use cfg::CfgOptions; +use serde::Serialize; -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Debug, Serialize)] pub enum CfgFlag { Atom(String), KeyValue { key: String, value: String }, diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index 80897f7478cf..ae9434743ba1 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -53,7 +53,7 @@ use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition}; use la_arena::RawIdx; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; -use serde::{de, Deserialize}; +use serde::{de, Deserialize, Serialize}; use std::path::PathBuf; use crate::cfg_flag::CfgFlag; @@ -174,14 +174,14 @@ impl ProjectJson { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProjectJsonData { sysroot: Option, sysroot_src: Option, crates: Vec, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct CrateData { display_name: Option, root_module: PathBuf, @@ -203,7 +203,7 @@ struct CrateData { repository: Option, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename = "edition")] enum EditionData { #[serde(rename = "2015")] @@ -224,7 +224,7 @@ impl From for Edition { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct DepData { /// Identifies a crate by position in the crates array. #[serde(rename = "crate")] @@ -233,7 +233,7 @@ struct DepData { name: CrateName, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct CrateSource { include_dirs: Vec, exclude_dirs: Vec, diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index ee5df984b68e..009c03b645f7 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -44,10 +44,12 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features = tracing-log = "0.1.3" tracing-tree = "0.2.1" triomphe.workspace = true +toml = "0.8.8" nohash-hasher.workspace = true always-assert = "0.1.2" +indexmap = { version = "2.0.0", features = ["serde"] } -# These 3 deps are not used by r-a directly, but we list them here to lock in their versions +# These 2 deps are not used by r-a directly, but we list them here to lock in their versions # in our transitive deps to prevent them from pulling in windows-sys 0.45.0 mio = "=0.8.5" parking_lot_core = "=0.9.6" @@ -72,6 +74,8 @@ parser.workspace = true toolchain.workspace = true vfs-notify.workspace = true vfs.workspace = true +la-arena.workspace = true +salsa.workspace = true [target.'cfg(windows)'.dependencies] winapi = "0.3.9" diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index c8df4255d96b..5fce21ddce77 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] //! Config used by the language server. //! //! We currently get this config from `initialize` LSP request, which is not the @@ -21,14 +22,17 @@ use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, SnippetCap, }; +use indexmap::IndexMap; use itertools::Itertools; +use la_arena::Arena; use lsp_types::{ClientCapabilities, MarkupKind}; use project_model::{ CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource, }; use rustc_hash::{FxHashMap, FxHashSet}; -use serde::{de::DeserializeOwned, Deserialize}; -use vfs::{AbsPath, AbsPathBuf}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use toml; +use vfs::{AbsPath, AbsPathBuf, FileId}; use crate::{ caps::completion_item_edit_resolve, @@ -38,6 +42,7 @@ use crate::{ }; mod patch_old_style; +mod tree; // Conventions for configuration keys to preserve maximal extendability without breakage: // - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable` @@ -56,36 +61,36 @@ mod patch_old_style; // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep // parsing the old name. config_data! { - struct ConfigData { + global: struct GlobalConfigData <- GlobalConfigInput -> RootGlobalConfigData { /// Whether to insert #[must_use] when generating `as_` methods /// for enum variants. - assist_emitMustUse: bool = "false", + assist_emitMustUse: bool = false, /// Placeholder expression to use for missing expressions in assists. - assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"", + assist_expressionFillDefault: ExprFillDefaultDef = ExprFillDefaultDef::Todo, /// Warm up caches on project load. - cachePriming_enable: bool = "true", + cachePriming_enable: bool = true, /// How many worker threads to handle priming caches. The default `0` means to pick automatically. - cachePriming_numThreads: ParallelCachePrimingNumThreads = "0", + cachePriming_numThreads: ParallelCachePrimingNumThreads = 0u8, /// Automatically refresh project info via `cargo metadata` on /// `Cargo.toml` or `.cargo/config.toml` changes. - cargo_autoreload: bool = "true", + cargo_autoreload: bool = true, /// Run build scripts (`build.rs`) for more precise code analysis. - cargo_buildScripts_enable: bool = "true", + 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\"", + cargo_buildScripts_invocationLocation: 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\"", + cargo_buildScripts_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace, /// 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 @@ -104,63 +109,63 @@ config_data! { /// cargo check --quiet --workspace --message-format=json --all-targets /// ``` /// . - cargo_buildScripts_overrideCommand: Option> = "null", + cargo_buildScripts_overrideCommand: Option> = None, /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to /// avoid checking unnecessary things. - cargo_buildScripts_useRustcWrapper: bool = "true", + cargo_buildScripts_useRustcWrapper: bool = true, /// List of cfg options to enable with the given values. - cargo_cfgs: FxHashMap = "{}", + cargo_cfgs: FxHashMap = FxHashMap::default(), /// Extra arguments that are passed to every cargo invocation. - cargo_extraArgs: Vec = "[]", + cargo_extraArgs: Vec = vec![], /// Extra environment variables that will be set when running cargo, rustc /// or other commands within the workspace. Useful for setting RUSTFLAGS. - cargo_extraEnv: FxHashMap = "{}", + cargo_extraEnv: FxHashMap = FxHashMap::default(), /// List of features to activate. /// /// Set this to `"all"` to pass `--all-features` to cargo. - cargo_features: CargoFeaturesDef = "[]", + cargo_features: CargoFeaturesDef = CargoFeaturesDef::Selected(vec![]), /// Whether to pass `--no-default-features` to cargo. - cargo_noDefaultFeatures: bool = "false", + cargo_noDefaultFeatures: bool = false, /// Relative path to the sysroot, or "discover" to try to automatically find it via /// "rustc --print sysroot". /// /// Unsetting this disables sysroot loading. /// /// This option does not take effect until rust-analyzer is restarted. - cargo_sysroot: Option = "\"discover\"", + cargo_sysroot: Option = Some("discover".to_string()), /// Relative path to the sysroot library sources. If left unset, this will default to /// `{cargo.sysroot}/lib/rustlib/src/rust/library`. /// /// This option does not take effect until rust-analyzer is restarted. - cargo_sysrootSrc: Option = "null", + cargo_sysrootSrc: Option = None, /// Compilation target override (target triple). // FIXME(@poliorcetics): move to multiple targets here too, but this will need more work // than `checkOnSave_target` - cargo_target: Option = "null", + cargo_target: Option = None, /// Unsets the implicit `#[cfg(test)]` for the specified crates. - cargo_unsetTest: Vec = "[\"core\"]", + cargo_unsetTest: Vec = @verbatim: r#"["core"]"#, /// Run the check command for diagnostics on save. - checkOnSave | checkOnSave_enable: bool = "true", + checkOnSave | checkOnSave_enable: bool = true, /// Check all targets and tests (`--all-targets`). - check_allTargets | checkOnSave_allTargets: bool = "true", + check_allTargets | checkOnSave_allTargets: bool = true, /// Cargo command to use for `cargo check`. - check_command | checkOnSave_command: String = "\"check\"", + check_command | checkOnSave_command: String = "check".to_string(), /// Extra arguments for `cargo check`. - check_extraArgs | checkOnSave_extraArgs: Vec = "[]", + check_extraArgs | checkOnSave_extraArgs: Vec = vec![], /// Extra environment variables that will be set when running `cargo check`. /// Extends `#rust-analyzer.cargo.extraEnv#`. - check_extraEnv | checkOnSave_extraEnv: FxHashMap = "{}", + check_extraEnv | checkOnSave_extraEnv: FxHashMap = FxHashMap::default(), /// List of features to activate. Defaults to /// `#rust-analyzer.cargo.features#`. /// /// Set to `"all"` to pass `--all-features` to Cargo. - check_features | checkOnSave_features: Option = "null", + check_features | checkOnSave_features: Option = None, /// List of `cargo check` (or other command specified in `check.command`) diagnostics to ignore. /// /// For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,... - check_ignore: FxHashSet = "[]", + check_ignore: FxHashSet = FxHashSet::default(), /// 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 @@ -168,16 +173,16 @@ config_data! { /// - "root": run checks in the project's root directory. /// This config only has an effect when `#rust-analyzer.cargo.check.overrideCommand#` /// is set. - check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"", + check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = InvocationLocation::Workspace, /// Specifies the invocation strategy to use when running the check 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.check.overrideCommand#` /// is set. - check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"", + check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace, /// Whether to pass `--no-default-features` to Cargo. Defaults to /// `#rust-analyzer.cargo.noDefaultFeatures#`. - check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option = "null", + check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option = None, /// Override the command rust-analyzer uses instead of `cargo check` for /// diagnostics on save. The command is required to output json and /// should therefore include `--message-format=json` or a similar option @@ -200,34 +205,188 @@ config_data! { /// cargo check --workspace --message-format=json --all-targets /// ``` /// . - check_overrideCommand | checkOnSave_overrideCommand: Option> = "null", + check_overrideCommand | checkOnSave_overrideCommand: Option> = None, /// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty. /// /// Can be a single target, e.g. `"x86_64-unknown-linux-gnu"` or a list of targets, e.g. /// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`. /// /// Aliased as `"checkOnSave.targets"`. - check_targets | checkOnSave_targets | checkOnSave_target: Option = "null", + check_targets | checkOnSave_targets | checkOnSave_target: Option = None, + + /// List of rust-analyzer diagnostics to disable. + diagnostics_disabled: FxHashSet = FxHashSet::default(), + /// Whether to show native rust-analyzer diagnostics. + diagnostics_enable: bool = true, + /// Whether to show experimental rust-analyzer diagnostics that might + /// have more false positives than usual. + diagnostics_experimental_enable: bool = false, + /// Map of prefixes to be substituted when parsing diagnostic file paths. + /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`. + diagnostics_remapPrefix: FxHashMap = FxHashMap::default(), + /// List of warnings that should be displayed with hint severity. + /// + /// The warnings will be indicated by faded text or three dots in code + /// and will not show up in the `Problems Panel`. + diagnostics_warningsAsHint: Vec = vec![], + /// List of warnings that should be displayed with info severity. + /// + /// The warnings will be indicated by a blue squiggly underline in code + /// and a blue icon in the `Problems Panel`. + diagnostics_warningsAsInfo: Vec = vec![], + /// These directories will be ignored by rust-analyzer. They are + /// relative to the workspace root, and globs are not supported. You may + /// also need to add the folders to Code's `files.watcherExclude`. + files_excludeDirs: Vec = vec![], + /// Controls file watching implementation. + files_watcher: FilesWatcherDef = FilesWatcherDef::Client, + + + /// Enables the experimental support for interpreting tests. + interpret_tests: bool = false, + + + + /// Whether to show `Debug` lens. Only applies when + /// `#rust-analyzer.lens.enable#` is set. + lens_debug_enable: bool = true, + /// Whether to show CodeLens in Rust files. + lens_enable: bool = true, + /// Internal config: use custom client-side commands even when the + /// client doesn't set the corresponding capability. + lens_forceCustomCommands: bool = true, + /// Whether to show `Implementations` lens. Only applies when + /// `#rust-analyzer.lens.enable#` is set. + lens_implementations_enable: bool = true, + /// Where to render annotations. + lens_location: AnnotationLocation = AnnotationLocation::AboveName, + /// Whether to show `References` lens for Struct, Enum, and Union. + /// Only applies when `#rust-analyzer.lens.enable#` is set. + lens_references_adt_enable: bool = false, + /// Whether to show `References` lens for Enum Variants. + /// Only applies when `#rust-analyzer.lens.enable#` is set. + lens_references_enumVariant_enable: bool = false, + /// Whether to show `Method References` lens. Only applies when + /// `#rust-analyzer.lens.enable#` is set. + lens_references_method_enable: bool = false, + /// Whether to show `References` lens for Trait. + /// Only applies when `#rust-analyzer.lens.enable#` is set. + lens_references_trait_enable: bool = false, + /// Whether to show `Run` lens. Only applies when + /// `#rust-analyzer.lens.enable#` is set. + lens_run_enable: bool = true, + + /// Disable project auto-discovery in favor of explicitly specified set + /// of projects. + /// + /// Elements must be paths pointing to `Cargo.toml`, + /// `rust-project.json`, or JSON objects in `rust-project.json` format. + linkedProjects: Vec = vec![], + + /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128. + lru_capacity: Option = None, + /// Sets the LRU capacity of the specified queries. + lru_query_capacities: FxHashMap, usize> = FxHashMap::default(), + + /// Whether to show `can't find Cargo.toml` error message. + notifications_cargoTomlNotFound: bool = true, + + /// How many worker threads in the main loop. The default `null` means to pick automatically. + numThreads: Option = None, + + /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set. + procMacro_attributes_enable: bool = true, + /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`. + procMacro_enable: bool = true, + /// These proc-macros will be ignored when trying to expand them. + /// + /// This config takes a map of crate names with the exported proc-macro names to ignore as values. + procMacro_ignored: FxHashMap, Box<[Box]>> = FxHashMap::default(), + /// Internal config, path to proc-macro server executable. + procMacro_server: Option = None, + + /// Exclude imports from find-all-references. + references_excludeImports: bool = false, + + /// Command to be executed instead of 'cargo' for runnables. + runnables_command: Option = None, + /// Additional arguments to be passed to cargo for runnables such as + /// tests or binaries. For example, it may be `--release`. + runnables_extraArgs: Vec = vec![], + + /// Optional path to a rust-analyzer specific target directory. + /// This prevents rust-analyzer's `cargo check` from locking the `Cargo.lock` + /// at the expense of duplicating build artifacts. + /// + /// Set to `true` to use a subdirectory of the existing target directory or + /// set to a path relative to the workspace to use that path. + rust_analyzerTargetDir: Option = None, + + /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private + /// projects, or "discover" to try to automatically find it if the `rustc-dev` component + /// is installed. + /// + /// Any project which uses rust-analyzer with the rustcPrivate + /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it. + /// + /// This option does not take effect until rust-analyzer is restarted. + rustc_source: Option = None, + + /// Additional arguments to `rustfmt`. + rustfmt_extraArgs: Vec = vec![], + /// Advanced option, fully override the command rust-analyzer uses for + /// formatting. This should be the equivalent of `rustfmt` here, and + /// not that of `cargo fmt`. The file contents will be passed on the + /// standard input and the formatted result will be read from the + /// standard output. + rustfmt_overrideCommand: Option> = None, + /// Enables the use of rustfmt's unstable range formatting command for the + /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only + /// available on a nightly build. + rustfmt_rangeFormatting_enable: bool = false, + + + /// Show full signature of the callable. Only shows parameters if disabled. + signatureInfo_detail: SignatureDetail = SignatureDetail::Full, + /// Show documentation. + signatureInfo_documentation_enable: bool = true, + + /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. + typing_autoClosingAngleBrackets_enable: bool = false, + + /// Workspace symbol search kind. + workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = WorkspaceSymbolSearchKindDef::OnlyTypes, + /// Limits the number of items returned from a workspace symbol search (Defaults to 128). + /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search. + /// Other clients requires all results upfront and might require a higher limit. + workspace_symbol_search_limit: usize = 128, + /// Workspace symbol search scope. + workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = WorkspaceSymbolSearchScopeDef::Workspace, + } +} + +config_data! { + local: struct LocalConfigData <- LocalConfigInput -> RootLocalConfigData { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. - completion_autoimport_enable: bool = "true", + completion_autoimport_enable: bool = true, /// Toggles the additional completions that automatically show method calls and field accesses /// with `self` prefixed to them when inside a method. - completion_autoself_enable: bool = "true", + completion_autoself_enable: bool = true, /// Whether to add parenthesis and argument snippets when completing function. - completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"", + completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments, /// Whether to show full function/method signatures in completion docs. - completion_fullFunctionSignatures_enable: bool = "false", + completion_fullFunctionSignatures_enable: bool = false, /// Maximum number of completions to return. If `None`, the limit is infinite. - completion_limit: Option = "null", + completion_limit: Option = None, /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. - completion_postfix_enable: bool = "true", + completion_postfix_enable: bool = true, /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. - completion_privateEditable_enable: bool = "false", + completion_privateEditable_enable: bool = false, /// Custom completion snippets. - // NOTE: Keep this list in sync with the feature docs of user snippets. - completion_snippets_custom: FxHashMap = r#"{ + // NOTE: we use IndexMap for deterministic serialization ordering + completion_snippets_custom: IndexMap = serde_json::from_str(r#"{ "Arc::new": { "postfix": "arc", "body": "Arc::new(${receiver})", @@ -267,323 +426,479 @@ config_data! { "description": "Wrap the expression in an `Option::Some`", "scope": "expr" } - }"#, - - /// List of rust-analyzer diagnostics to disable. - diagnostics_disabled: FxHashSet = "[]", - /// Whether to show native rust-analyzer diagnostics. - diagnostics_enable: bool = "true", - /// Whether to show experimental rust-analyzer diagnostics that might - /// have more false positives than usual. - diagnostics_experimental_enable: bool = "false", - /// Map of prefixes to be substituted when parsing diagnostic file paths. - /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`. - diagnostics_remapPrefix: FxHashMap = "{}", - /// List of warnings that should be displayed with hint severity. - /// - /// The warnings will be indicated by faded text or three dots in code - /// and will not show up in the `Problems Panel`. - diagnostics_warningsAsHint: Vec = "[]", - /// List of warnings that should be displayed with info severity. - /// - /// The warnings will be indicated by a blue squiggly underline in code - /// and a blue icon in the `Problems Panel`. - diagnostics_warningsAsInfo: Vec = "[]", - /// These directories will be ignored by rust-analyzer. They are - /// relative to the workspace root, and globs are not supported. You may - /// also need to add the folders to Code's `files.watcherExclude`. - files_excludeDirs: Vec = "[]", - /// Controls file watching implementation. - files_watcher: FilesWatcherDef = "\"client\"", + }"#).unwrap(), /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. - highlightRelated_breakPoints_enable: bool = "true", + highlightRelated_breakPoints_enable: bool = true, /// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure. - highlightRelated_closureCaptures_enable: bool = "true", + highlightRelated_closureCaptures_enable: bool = true, /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`). - highlightRelated_exitPoints_enable: bool = "true", + highlightRelated_exitPoints_enable: bool = true, /// Enables highlighting of related references while the cursor is on any identifier. - highlightRelated_references_enable: bool = "true", + highlightRelated_references_enable: bool = true, /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords. - highlightRelated_yieldPoints_enable: bool = "true", + highlightRelated_yieldPoints_enable: bool = true, /// Whether to show `Debug` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_debug_enable: bool = "true", + hover_actions_debug_enable: bool = true, /// Whether to show HoverActions in Rust files. - hover_actions_enable: bool = "true", + hover_actions_enable: bool = true, /// Whether to show `Go to Type Definition` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_gotoTypeDef_enable: bool = "true", + hover_actions_gotoTypeDef_enable: bool = true, /// Whether to show `Implementations` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_implementations_enable: bool = "true", + hover_actions_implementations_enable: bool = true, /// Whether to show `References` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_references_enable: bool = "false", + hover_actions_references_enable: bool = false, /// Whether to show `Run` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_run_enable: bool = "true", + hover_actions_run_enable: bool = true, /// Whether to show documentation on hover. - hover_documentation_enable: bool = "true", + hover_documentation_enable: bool = true, /// Whether to show keyword hover popups. Only applies when /// `#rust-analyzer.hover.documentation.enable#` is set. - hover_documentation_keywords_enable: bool = "true", + hover_documentation_keywords_enable: bool = true, /// Use markdown syntax for links on hover. - hover_links_enable: bool = "true", + hover_links_enable: bool = true, /// How to render the align information in a memory layout hover. - hover_memoryLayout_alignment: Option = "\"hexadecimal\"", + hover_memoryLayout_alignment: Option = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), /// Whether to show memory layout data on hover. - hover_memoryLayout_enable: bool = "true", + hover_memoryLayout_enable: bool = true, /// How to render the niche information in a memory layout hover. - hover_memoryLayout_niches: Option = "false", + hover_memoryLayout_niches: Option = Some(false), /// How to render the offset information in a memory layout hover. - hover_memoryLayout_offset: Option = "\"hexadecimal\"", + hover_memoryLayout_offset: Option = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), /// How to render the size information in a memory layout hover. - hover_memoryLayout_size: Option = "\"both\"", + hover_memoryLayout_size: Option = Some(MemoryLayoutHoverRenderKindDef::Both), /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file. - imports_granularity_enforce: bool = "false", + imports_granularity_enforce: bool = false, /// How imports should be grouped into use statements. - imports_granularity_group: ImportGranularityDef = "\"crate\"", + imports_granularity_group: ImportGranularityDef = ImportGranularityDef::Crate, /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines. - imports_group_enable: bool = "true", + imports_group_enable: bool = true, /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`. - imports_merge_glob: bool = "true", + imports_merge_glob: bool = true, /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate. - imports_prefer_no_std: bool = "false", + imports_prefer_no_std: bool = false, /// The path structure for newly inserted paths to use. - imports_prefix: ImportPrefixDef = "\"plain\"", + imports_prefix: ImportPrefixDef = ImportPrefixDef::Plain, + /// Whether to show inlay type hints for binding modes. - inlayHints_bindingModeHints_enable: bool = "false", + inlayHints_bindingModeHints_enable: bool = false, /// Whether to show inlay type hints for method chains. - inlayHints_chainingHints_enable: bool = "true", + inlayHints_chainingHints_enable: bool = true, /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to. - inlayHints_closingBraceHints_enable: bool = "true", + inlayHints_closingBraceHints_enable: bool = true, /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1 /// to always show them). - inlayHints_closingBraceHints_minLines: usize = "25", + inlayHints_closingBraceHints_minLines: usize = 25, /// Whether to show inlay hints for closure captures. - inlayHints_closureCaptureHints_enable: bool = "false", + inlayHints_closureCaptureHints_enable: bool = false, /// Whether to show inlay type hints for return types of closures. - inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"", + inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = ClosureReturnTypeHintsDef::Never, /// Closure notation in type and chaining inlay hints. - inlayHints_closureStyle: ClosureStyle = "\"impl_fn\"", + inlayHints_closureStyle: ClosureStyle = ClosureStyle::ImplFn, /// Whether to show enum variant discriminant hints. - inlayHints_discriminantHints_enable: DiscriminantHintsDef = "\"never\"", + inlayHints_discriminantHints_enable: DiscriminantHintsDef = DiscriminantHintsDef::Never, /// Whether to show inlay hints for type adjustments. - inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"", + inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = AdjustmentHintsDef::Never, /// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks. - inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false", + inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = false, /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc). - inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"", + inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = AdjustmentHintsModeDef::Prefix, /// Whether to show inlay type hints for elided lifetimes in function signatures. - inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"", + inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = LifetimeElisionDef::Never, /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. - inlayHints_lifetimeElisionHints_useParameterNames: bool = "false", + inlayHints_lifetimeElisionHints_useParameterNames: bool = false, /// Maximum length for inlay hints. Set to null to have an unlimited length. - inlayHints_maxLength: Option = "25", + inlayHints_maxLength: Option = Some(25), /// Whether to show function parameter name inlay hints at the call /// site. - inlayHints_parameterHints_enable: bool = "true", + inlayHints_parameterHints_enable: bool = true, /// Whether to show inlay hints for compiler inserted reborrows. /// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#. - inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"", + inlayHints_reborrowHints_enable: ReborrowHintsDef = ReborrowHintsDef::Never, /// Whether to render leading colons for type hints, and trailing colons for parameter hints. - inlayHints_renderColons: bool = "true", + inlayHints_renderColons: bool = true, /// Whether to show inlay type hints for variables. - inlayHints_typeHints_enable: bool = "true", + inlayHints_typeHints_enable: bool = true, /// Whether to hide inlay type hints for `let` statements that initialize to a closure. /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`. - inlayHints_typeHints_hideClosureInitialization: bool = "false", + inlayHints_typeHints_hideClosureInitialization: bool = false, /// Whether to hide inlay type hints for constructors. - inlayHints_typeHints_hideNamedConstructor: bool = "false", - /// Enables the experimental support for interpreting tests. - interpret_tests: bool = "false", + inlayHints_typeHints_hideNamedConstructor: bool = false, + /// Join lines merges consecutive declaration and initialization of an assignment. - joinLines_joinAssignments: bool = "true", + joinLines_joinAssignments: bool = true, /// Join lines inserts else between consecutive ifs. - joinLines_joinElseIf: bool = "true", + joinLines_joinElseIf: bool = true, /// Join lines removes trailing commas. - joinLines_removeTrailingComma: bool = "true", + joinLines_removeTrailingComma: bool = true, /// Join lines unwraps trivial blocks. - joinLines_unwrapTrivialBlock: bool = "true", - - - /// Whether to show `Debug` lens. Only applies when - /// `#rust-analyzer.lens.enable#` is set. - lens_debug_enable: bool = "true", - /// Whether to show CodeLens in Rust files. - lens_enable: bool = "true", - /// Internal config: use custom client-side commands even when the - /// client doesn't set the corresponding capability. - lens_forceCustomCommands: bool = "true", - /// Whether to show `Implementations` lens. Only applies when - /// `#rust-analyzer.lens.enable#` is set. - lens_implementations_enable: bool = "true", - /// Where to render annotations. - lens_location: AnnotationLocation = "\"above_name\"", - /// Whether to show `References` lens for Struct, Enum, and Union. - /// Only applies when `#rust-analyzer.lens.enable#` is set. - lens_references_adt_enable: bool = "false", - /// Whether to show `References` lens for Enum Variants. - /// Only applies when `#rust-analyzer.lens.enable#` is set. - lens_references_enumVariant_enable: bool = "false", - /// Whether to show `Method References` lens. Only applies when - /// `#rust-analyzer.lens.enable#` is set. - lens_references_method_enable: bool = "false", - /// Whether to show `References` lens for Trait. - /// Only applies when `#rust-analyzer.lens.enable#` is set. - lens_references_trait_enable: bool = "false", - /// Whether to show `Run` lens. Only applies when - /// `#rust-analyzer.lens.enable#` is set. - lens_run_enable: bool = "true", - - /// Disable project auto-discovery in favor of explicitly specified set - /// of projects. - /// - /// Elements must be paths pointing to `Cargo.toml`, - /// `rust-project.json`, or JSON objects in `rust-project.json` format. - linkedProjects: Vec = "[]", - - /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128. - lru_capacity: Option = "null", - /// Sets the LRU capacity of the specified queries. - lru_query_capacities: FxHashMap, usize> = "{}", - - /// Whether to show `can't find Cargo.toml` error message. - notifications_cargoTomlNotFound: bool = "true", - - /// How many worker threads in the main loop. The default `null` means to pick automatically. - numThreads: Option = "null", - - /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set. - procMacro_attributes_enable: bool = "true", - /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`. - procMacro_enable: bool = "true", - /// These proc-macros will be ignored when trying to expand them. - /// - /// This config takes a map of crate names with the exported proc-macro names to ignore as values. - procMacro_ignored: FxHashMap, Box<[Box]>> = "{}", - /// Internal config, path to proc-macro server executable. - procMacro_server: Option = "null", - - /// Exclude imports from find-all-references. - references_excludeImports: bool = "false", - - /// Command to be executed instead of 'cargo' for runnables. - runnables_command: Option = "null", - /// Additional arguments to be passed to cargo for runnables such as - /// tests or binaries. For example, it may be `--release`. - runnables_extraArgs: Vec = "[]", - - /// Optional path to a rust-analyzer specific target directory. - /// This prevents rust-analyzer's `cargo check` from locking the `Cargo.lock` - /// at the expense of duplicating build artifacts. - /// - /// Set to `true` to use a subdirectory of the existing target directory or - /// set to a path relative to the workspace to use that path. - rust_analyzerTargetDir: Option = "null", - - /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private - /// projects, or "discover" to try to automatically find it if the `rustc-dev` component - /// is installed. - /// - /// Any project which uses rust-analyzer with the rustcPrivate - /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it. - /// - /// This option does not take effect until rust-analyzer is restarted. - rustc_source: Option = "null", - - /// Additional arguments to `rustfmt`. - rustfmt_extraArgs: Vec = "[]", - /// Advanced option, fully override the command rust-analyzer uses for - /// formatting. This should be the equivalent of `rustfmt` here, and - /// not that of `cargo fmt`. The file contents will be passed on the - /// standard input and the formatted result will be read from the - /// standard output. - rustfmt_overrideCommand: Option> = "null", - /// Enables the use of rustfmt's unstable range formatting command for the - /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only - /// available on a nightly build. - rustfmt_rangeFormatting_enable: bool = "false", + joinLines_unwrapTrivialBlock: bool = true, /// Inject additional highlighting into doc comments. /// /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra /// doc links. - semanticHighlighting_doc_comment_inject_enable: bool = "true", + semanticHighlighting_doc_comment_inject_enable: bool = true, /// Whether the server is allowed to emit non-standard tokens and modifiers. - semanticHighlighting_nonStandardTokens: bool = "true", + semanticHighlighting_nonStandardTokens: bool = true, /// Use semantic tokens for operators. /// /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when /// they are tagged with modifiers. - semanticHighlighting_operator_enable: bool = "true", + semanticHighlighting_operator_enable: bool = true, /// Use specialized semantic tokens for operators. /// /// When enabled, rust-analyzer will emit special token types for operator tokens instead /// of the generic `operator` token type. - semanticHighlighting_operator_specialization_enable: bool = "false", + semanticHighlighting_operator_specialization_enable: bool = false, /// Use semantic tokens for punctuation. /// /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when /// they are tagged with modifiers or have a special role. - semanticHighlighting_punctuation_enable: bool = "false", + semanticHighlighting_punctuation_enable: bool = false, /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro /// calls. - semanticHighlighting_punctuation_separate_macro_bang: bool = "false", + semanticHighlighting_punctuation_separate_macro_bang: bool = false, /// Use specialized semantic tokens for punctuation. /// /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead /// of the generic `punctuation` token type. - semanticHighlighting_punctuation_specialization_enable: bool = "false", + semanticHighlighting_punctuation_specialization_enable: bool = false, /// Use semantic tokens for strings. /// /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars. /// By disabling semantic tokens for strings, other grammars can be used to highlight /// their contents. - semanticHighlighting_strings_enable: bool = "true", + semanticHighlighting_strings_enable: bool = true, + } +} - /// Show full signature of the callable. Only shows parameters if disabled. - signatureInfo_detail: SignatureDetail = "\"full\"", - /// Show documentation. - signatureInfo_documentation_enable: bool = "true", +config_data! { + client: struct ClientConfigData <- ClientConfigInput -> RootClientConfigData {} +} + +#[derive(Debug, Clone, Default)] +struct RootConfigData { + local: RootLocalConfigData, + global: RootGlobalConfigData, + client: RootClientConfigData, +} + +impl RootConfigData { + /// Reads a single root config blob. All fields are either set by the config blob, or to the + /// default value. + fn from_root_input(input: ConfigInput) -> Self { + let ConfigInput { global, local, client } = input; + Self { + global: RootGlobalConfigData::from_root_input(global), + local: RootLocalConfigData::from_root_input(local), + client: RootClientConfigData::from_root_input(client), + } + } +} + +#[derive(Debug, Clone)] +pub struct Config { + discovered_projects: Vec, + /// The workspace roots as registered by the LSP client + workspace_roots: Vec, + caps: lsp_types::ClientCapabilities, + root_path: AbsPathBuf, + root_config: RootConfigData, + config_arena: Arena, + detached_files: Vec, + snippets: Vec, + is_visual_studio_code: bool, +} + +macro_rules! try_ { + ($expr:expr) => { + || -> _ { Some($expr) }() + }; +} +macro_rules! try_or { + ($expr:expr, $or:expr) => { + try_!($expr).unwrap_or($or) + }; +} + +macro_rules! try_or_def { + ($expr:expr) => { + try_!($expr).unwrap_or_default() + }; +} + +#[derive(Debug, Clone)] +pub struct LocalConfigView<'a> { + local: &'a LocalConfigData, + global: &'a RootGlobalConfigData, + client: &'a RootClientConfigData, + caps: &'a lsp_types::ClientCapabilities, + snippets: &'a Vec, +} + +impl<'a> LocalConfigView<'a> { + pub fn assist(&self) -> AssistConfig { + AssistConfig { + snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), + allowed: None, + insert_use: self.insert_use_config(), + prefer_no_std: self.local.imports_prefer_no_std, + assist_emit_must_use: self.global.0.assist_emitMustUse, + } + } + + pub fn completion(&self) -> CompletionConfig { + CompletionConfig { + enable_postfix_completions: self.local.completion_postfix_enable, + enable_imports_on_the_fly: self.local.completion_autoimport_enable + && completion_item_edit_resolve(&self.caps), + enable_self_on_the_fly: self.local.completion_autoself_enable, + enable_private_editable: self.local.completion_privateEditable_enable, + full_function_signatures: self.local.completion_fullFunctionSignatures_enable, + callable: match self.local.completion_callable_snippets { + CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), + CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), + CallableCompletionDef::None => None, + }, + insert_use: self.insert_use_config(), + prefer_no_std: self.local.imports_prefer_no_std, + snippet_cap: SnippetCap::new(try_or_def!( + self.caps + .text_document + .as_ref()? + .completion + .as_ref()? + .completion_item + .as_ref()? + .snippet_support? + )), + snippets: self.snippets.clone().to_vec(), + limit: self.local.completion_limit, + } + } + + pub fn diagnostics(&self) -> DiagnosticsConfig { + DiagnosticsConfig { + enabled: self.global.0.diagnostics_enable, + proc_attr_macros_enabled: self.expand_proc_attr_macros(), + proc_macros_enabled: self.global.0.procMacro_enable, + disable_experimental: !self.global.0.diagnostics_experimental_enable, + disabled: self.global.0.diagnostics_disabled.clone(), + expr_fill_default: match self.global.0.assist_expressionFillDefault { + ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, + ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, + }, + insert_use: self.insert_use_config(), + prefer_no_std: self.local.imports_prefer_no_std, + } + } + pub fn expand_proc_attr_macros(&self) -> bool { + self.global.0.procMacro_enable && self.global.0.procMacro_attributes_enable + } + + fn experimental(&self, index: &'static str) -> bool { + try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?) + } + + pub fn highlight_related(&self) -> HighlightRelatedConfig { + HighlightRelatedConfig { + references: self.local.highlightRelated_references_enable, + break_points: self.local.highlightRelated_breakPoints_enable, + exit_points: self.local.highlightRelated_exitPoints_enable, + yield_points: self.local.highlightRelated_yieldPoints_enable, + closure_captures: self.local.highlightRelated_closureCaptures_enable, + } + } + + pub fn hover_actions(&self) -> HoverActionsConfig { + let enable = self.experimental("hoverActions") && self.local.hover_actions_enable; + HoverActionsConfig { + implementations: enable && self.local.hover_actions_implementations_enable, + references: enable && self.local.hover_actions_references_enable, + run: enable && self.local.hover_actions_run_enable, + debug: enable && self.local.hover_actions_debug_enable, + goto_type_def: enable && self.local.hover_actions_gotoTypeDef_enable, + } + } + + pub fn hover(&self) -> HoverConfig { + let mem_kind = |kind| match kind { + MemoryLayoutHoverRenderKindDef::Both => MemoryLayoutHoverRenderKind::Both, + MemoryLayoutHoverRenderKindDef::Decimal => MemoryLayoutHoverRenderKind::Decimal, + MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal, + }; + HoverConfig { + links_in_hover: self.local.hover_links_enable, + memory_layout: self.local.hover_memoryLayout_enable.then_some( + MemoryLayoutHoverConfig { + size: self.local.hover_memoryLayout_size.map(mem_kind), + offset: self.local.hover_memoryLayout_offset.map(mem_kind), + alignment: self.local.hover_memoryLayout_alignment.map(mem_kind), + niches: self.local.hover_memoryLayout_niches.unwrap_or_default(), + }, + ), + documentation: self.local.hover_documentation_enable, + format: { + let is_markdown = try_or_def!(self + .caps + .text_document + .as_ref()? + .hover + .as_ref()? + .content_format + .as_ref()? + .as_slice()) + .contains(&MarkupKind::Markdown); + if is_markdown { + HoverDocFormat::Markdown + } else { + HoverDocFormat::PlainText + } + }, + keywords: self.local.hover_documentation_keywords_enable, + } + } + + pub fn inlay_hints(&self) -> InlayHintsConfig { + let client_capability_fields = self + .caps + .text_document + .as_ref() + .and_then(|text| text.inlay_hint.as_ref()) + .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref()) + .map(|inlay_resolve| inlay_resolve.properties.iter()) + .into_iter() + .flatten() + .cloned() + .collect::>(); + + InlayHintsConfig { + render_colons: self.local.inlayHints_renderColons, + type_hints: self.local.inlayHints_typeHints_enable, + parameter_hints: self.local.inlayHints_parameterHints_enable, + chaining_hints: self.local.inlayHints_chainingHints_enable, + discriminant_hints: match self.local.inlayHints_discriminantHints_enable { + DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, + DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, + DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless, + }, + closure_return_type_hints: match self.local.inlayHints_closureReturnTypeHints_enable { + ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always, + ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never, + ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock, + }, + lifetime_elision_hints: match self.local.inlayHints_lifetimeElisionHints_enable { + LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always, + LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never, + LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial, + }, + hide_named_constructor_hints: self.local.inlayHints_typeHints_hideNamedConstructor, + hide_closure_initialization_hints: self + .local + .inlayHints_typeHints_hideClosureInitialization, + closure_style: match self.local.inlayHints_closureStyle { + ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn, + ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation, + ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId, + ClosureStyle::Hide => hir::ClosureStyle::Hide, + }, + closure_capture_hints: self.local.inlayHints_closureCaptureHints_enable, + adjustment_hints: match self.local.inlayHints_expressionAdjustmentHints_enable { + AdjustmentHintsDef::Always => ide::AdjustmentHints::Always, + AdjustmentHintsDef::Never => match self.local.inlayHints_reborrowHints_enable { + ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { + ide::AdjustmentHints::ReborrowOnly + } + ReborrowHintsDef::Never => ide::AdjustmentHints::Never, + }, + AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly, + }, + adjustment_hints_mode: match self.local.inlayHints_expressionAdjustmentHints_mode { + AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix, + AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix, + AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix, + AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix, + }, + adjustment_hints_hide_outside_unsafe: self + .local + .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe, + binding_mode_hints: self.local.inlayHints_bindingModeHints_enable, + param_names_for_lifetime_elision_hints: self + .local + .inlayHints_lifetimeElisionHints_useParameterNames, + max_length: self.local.inlayHints_maxLength, + closing_brace_hints_min_lines: if self.local.inlayHints_closingBraceHints_enable { + Some(self.local.inlayHints_closingBraceHints_minLines) + } else { + None + }, + fields_to_resolve: InlayFieldsToResolve { + resolve_text_edits: client_capability_fields.contains("textEdits"), + resolve_hint_tooltip: client_capability_fields.contains("tooltip"), + resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), + resolve_label_location: client_capability_fields.contains("label.location"), + resolve_label_command: client_capability_fields.contains("label.command"), + }, + } + } - /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. - typing_autoClosingAngleBrackets_enable: bool = "false", + fn insert_use_config(&self) -> InsertUseConfig { + InsertUseConfig { + granularity: match self.local.imports_granularity_group { + ImportGranularityDef::Preserve => ImportGranularity::Preserve, + ImportGranularityDef::Item => ImportGranularity::Item, + ImportGranularityDef::Crate => ImportGranularity::Crate, + ImportGranularityDef::Module => ImportGranularity::Module, + }, + enforce_granularity: self.local.imports_granularity_enforce, + prefix_kind: match self.local.imports_prefix { + ImportPrefixDef::Plain => PrefixKind::Plain, + ImportPrefixDef::ByCrate => PrefixKind::ByCrate, + ImportPrefixDef::BySelf => PrefixKind::BySelf, + }, + group: self.local.imports_group_enable, + skip_glob_imports: !self.local.imports_merge_glob, + } + } - /// Workspace symbol search kind. - workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"", - /// Limits the number of items returned from a workspace symbol search (Defaults to 128). - /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search. - /// Other clients requires all results upfront and might require a higher limit. - workspace_symbol_search_limit: usize = "128", - /// Workspace symbol search scope. - workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"", + pub fn join_lines(&self) -> JoinLinesConfig { + JoinLinesConfig { + join_else_if: self.local.joinLines_joinElseIf, + remove_trailing_comma: self.local.joinLines_removeTrailingComma, + unwrap_trivial_blocks: self.local.joinLines_unwrapTrivialBlock, + join_assignments: self.local.joinLines_joinAssignments, + } } -} -impl Default for ConfigData { - fn default() -> Self { - ConfigData::from_json(serde_json::Value::Null, &mut Vec::new()) + pub fn highlighting_non_standard_tokens(&self) -> bool { + self.local.semanticHighlighting_nonStandardTokens } -} -#[derive(Debug, Clone)] -pub struct Config { - discovered_projects: Vec, - /// The workspace roots as registered by the LSP client - workspace_roots: Vec, - caps: lsp_types::ClientCapabilities, - root_path: AbsPathBuf, - data: ConfigData, - detached_files: Vec, - snippets: Vec, - is_visual_studio_code: bool, + pub fn highlighting_config(&self) -> HighlightConfig { + HighlightConfig { + strings: self.local.semanticHighlighting_strings_enable, + punctuation: self.local.semanticHighlighting_punctuation_enable, + specialize_punctuation: self + .local + .semanticHighlighting_punctuation_specialization_enable, + macro_bang: self.local.semanticHighlighting_punctuation_separate_macro_bang, + operator: self.local.semanticHighlighting_operator_enable, + specialize_operator: self.local.semanticHighlighting_operator_specialization_enable, + inject_doc_comment: self.local.semanticHighlighting_doc_comment_inject_enable, + syntactic_name_ref_highlighting: false, + } + } } type ParallelCachePrimingNumThreads = u8; @@ -631,7 +946,7 @@ pub struct LensConfig { pub location: AnnotationLocation, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AnnotationLocation { AboveName, @@ -783,18 +1098,52 @@ impl Config { workspace_roots: Vec, is_visual_studio_code: bool, ) -> Self { + let root_config = RootConfigData::default(); + let config_arena = Arena::new(); + Config { caps, - data: ConfigData::default(), + root_config, detached_files: Vec::new(), discovered_projects: Vec::new(), root_path, snippets: Default::default(), workspace_roots, is_visual_studio_code, + config_arena, + } + } + + /// Returns a `LocalConfigView` that points to the root config. + /// This is a compromise for cases where we do not have enough data + /// to point to a specific local view and we still need to have one + /// because we need to query fields that are essentially global. + pub fn localize_to_root_view(&self) -> LocalConfigView<'_> { + LocalConfigView { + local: &self.root_config.local.0, + global: &self.root_config.global, + client: &self.root_config.client, + caps: &self.caps, + snippets: &self.snippets, + } + } + + pub fn localize_by_file_id(&self, file_id: FileId) -> LocalConfigView<'_> { + // FIXME : Plain wrong + LocalConfigView { + local: &self.root_config.local.0, + global: &self.root_config.global, + client: &self.root_config.client, + caps: &self.caps, + snippets: &self.snippets, } } + pub fn expand_proc_attr_macros(&self) -> bool { + self.root_config.global.0.procMacro_enable + && self.root_config.global.0.procMacro_attributes_enable + } + pub fn rediscover_workspaces(&mut self) { let discovered = ProjectManifest::discover_all(&self.workspace_roots); tracing::info!("discovered projects: {:?}", discovered); @@ -821,15 +1170,17 @@ impl Config { } let mut errors = Vec::new(); self.detached_files = - get_field::>(&mut json, &mut errors, "detachedFiles", None, "[]") + get_field::>(&mut json, &mut errors, "detachedFiles", None) + .unwrap_or_default() .into_iter() .map(AbsPathBuf::assert) .collect(); patch_old_style::patch_json_for_outdated_configs(&mut json); - self.data = ConfigData::from_json(json, &mut errors); - tracing::debug!("deserialized config data: {:#?}", self.data); + let input = ConfigInput::from_json(json, &mut errors); + self.root_config = RootConfigData::from_root_input(input); + tracing::debug!("deserialized config data: {:#?}", self.root_config.global); self.snippets.clear(); - for (name, def) in self.data.completion_snippets_custom.iter() { + for (name, def) in self.root_config.local.0.completion_snippets_custom.iter() { if def.prefix.is_empty() && def.postfix.is_empty() { continue; } @@ -867,7 +1218,7 @@ impl Config { fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) { use serde::de::Error; - if self.data.check_command.is_empty() { + if self.root_config.global.0.check_command.is_empty() { error_sink.push(( "/check/command".to_string(), serde_json::Error::custom("expected a non-empty string"), @@ -876,7 +1227,7 @@ impl Config { } pub fn json_schema() -> serde_json::Value { - ConfigData::json_schema() + ConfigInput::json_schema() } pub fn root_path(&self) -> &AbsPathBuf { @@ -892,32 +1243,21 @@ impl Config { } } -macro_rules! try_ { - ($expr:expr) => { - || -> _ { Some($expr) }() - }; -} -macro_rules! try_or { - ($expr:expr, $or:expr) => { - try_!($expr).unwrap_or($or) - }; -} - -macro_rules! try_or_def { - ($expr:expr) => { - try_!($expr).unwrap_or_default() - }; -} - impl Config { pub fn has_linked_projects(&self) -> bool { - !self.data.linkedProjects.is_empty() + !self.root_config.global.0.linkedProjects.is_empty() } pub fn linked_projects(&self) -> Vec { - match self.data.linkedProjects.as_slice() { + match self.root_config.global.0.linkedProjects.as_slice() { [] => { - let exclude_dirs: Vec<_> = - self.data.files_excludeDirs.iter().map(|p| self.root_path.join(p)).collect(); + let exclude_dirs: Vec<_> = self + .root_config + .global + .0 + .files_excludeDirs + .iter() + .map(|p| self.root_path.join(p)) + .collect(); self.discovered_projects .iter() .filter( @@ -954,7 +1294,7 @@ impl Config { .map(ManifestOrProjectJson::ProjectJson) .collect::>(); - self.data.linkedProjects.append(&mut linked_projects); + self.root_config.global.0.linkedProjects.append(&mut linked_projects); } pub fn did_save_text_document_dynamic_registration(&self) -> bool { @@ -969,7 +1309,7 @@ impl Config { } pub fn prefill_caches(&self) -> bool { - self.data.cachePriming_enable + self.root_config.global.0.cachePriming_enable } pub fn location_link(&self) -> bool { @@ -1102,136 +1442,139 @@ impl Config { } pub fn publish_diagnostics(&self) -> bool { - self.data.diagnostics_enable - } - - pub fn diagnostics(&self) -> DiagnosticsConfig { - DiagnosticsConfig { - enabled: self.data.diagnostics_enable, - proc_attr_macros_enabled: self.expand_proc_attr_macros(), - proc_macros_enabled: self.data.procMacro_enable, - disable_experimental: !self.data.diagnostics_experimental_enable, - disabled: self.data.diagnostics_disabled.clone(), - expr_fill_default: match self.data.assist_expressionFillDefault { - ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, - ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, - }, - insert_use: self.insert_use_config(), - prefer_no_std: self.data.imports_prefer_no_std, - } + self.root_config.global.0.diagnostics_enable } pub fn diagnostics_map(&self) -> DiagnosticsMapConfig { DiagnosticsMapConfig { - remap_prefix: self.data.diagnostics_remapPrefix.clone(), - warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(), - warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(), - check_ignore: self.data.check_ignore.clone(), + remap_prefix: self.root_config.global.0.diagnostics_remapPrefix.clone(), + warnings_as_info: self.root_config.global.0.diagnostics_warningsAsInfo.clone(), + warnings_as_hint: self.root_config.global.0.diagnostics_warningsAsHint.clone(), + check_ignore: self.root_config.global.0.check_ignore.clone(), } } pub fn extra_args(&self) -> &Vec { - &self.data.cargo_extraArgs + &self.root_config.global.0.cargo_extraArgs } pub fn extra_env(&self) -> &FxHashMap { - &self.data.cargo_extraEnv + &self.root_config.global.0.cargo_extraEnv } pub fn check_extra_args(&self) -> Vec { let mut extra_args = self.extra_args().clone(); - extra_args.extend_from_slice(&self.data.check_extraArgs); + extra_args.extend_from_slice(&self.root_config.global.0.check_extraArgs); extra_args } pub fn check_extra_env(&self) -> FxHashMap { - let mut extra_env = self.data.cargo_extraEnv.clone(); - extra_env.extend(self.data.check_extraEnv.clone()); + let mut extra_env = self.root_config.global.0.cargo_extraEnv.clone(); + extra_env.extend(self.root_config.global.0.check_extraEnv.clone()); extra_env } pub fn lru_parse_query_capacity(&self) -> Option { - self.data.lru_capacity + self.root_config.global.0.lru_capacity } pub fn lru_query_capacities(&self) -> Option<&FxHashMap, usize>> { - self.data.lru_query_capacities.is_empty().not().then(|| &self.data.lru_query_capacities) + self.root_config + .global + .0 + .lru_query_capacities + .is_empty() + .not() + .then(|| &self.root_config.global.0.lru_query_capacities) } pub fn proc_macro_srv(&self) -> Option { - let path = self.data.procMacro_server.clone()?; + let path = self.root_config.global.0.procMacro_server.clone()?; Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(&path))) } pub fn dummy_replacements(&self) -> &FxHashMap, Box<[Box]>> { - &self.data.procMacro_ignored + &self.root_config.global.0.procMacro_ignored } pub fn expand_proc_macros(&self) -> bool { - self.data.procMacro_enable - } - - pub fn expand_proc_attr_macros(&self) -> bool { - self.data.procMacro_enable && self.data.procMacro_attributes_enable + self.root_config.global.0.procMacro_enable } pub fn files(&self) -> FilesConfig { FilesConfig { - watcher: match self.data.files_watcher { + watcher: match self.root_config.global.0.files_watcher { FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => { FilesWatcher::Client } _ => FilesWatcher::Server, }, - exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(), + exclude: self + .root_config + .global + .0 + .files_excludeDirs + .iter() + .map(|it| self.root_path.join(it)) + .collect(), } } pub fn notifications(&self) -> NotificationsConfig { - NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound } + NotificationsConfig { + cargo_toml_not_found: self.root_config.global.0.notifications_cargoTomlNotFound, + } } pub fn cargo_autoreload(&self) -> bool { - self.data.cargo_autoreload + self.root_config.global.0.cargo_autoreload } pub fn run_build_scripts(&self) -> bool { - self.data.cargo_buildScripts_enable || self.data.procMacro_enable + self.root_config.global.0.cargo_buildScripts_enable + || self.root_config.global.0.procMacro_enable } pub fn cargo(&self) -> CargoConfig { - let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| { + let rustc_source = self.root_config.global.0.rustc_source.as_ref().map(|rustc_src| { if rustc_src == "discover" { RustLibSource::Discover } else { RustLibSource::Path(self.root_path.join(rustc_src)) } }); - let sysroot = self.data.cargo_sysroot.as_ref().map(|sysroot| { + let sysroot = self.root_config.global.0.cargo_sysroot.as_ref().map(|sysroot| { if sysroot == "discover" { RustLibSource::Discover } else { RustLibSource::Path(self.root_path.join(sysroot)) } }); - let sysroot_src = - self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot)); + let sysroot_src = self + .root_config + .global + .0 + .cargo_sysrootSrc + .as_ref() + .map(|sysroot| self.root_path.join(sysroot)); CargoConfig { - features: match &self.data.cargo_features { + features: match &self.root_config.global.0.cargo_features { CargoFeaturesDef::All => CargoFeatures::All, CargoFeaturesDef::Selected(features) => CargoFeatures::Selected { features: features.clone(), - no_default_features: self.data.cargo_noDefaultFeatures, + no_default_features: self.root_config.global.0.cargo_noDefaultFeatures, }, }, - target: self.data.cargo_target.clone(), + target: self.root_config.global.0.cargo_target.clone(), sysroot, sysroot_src, rustc_source, cfg_overrides: project_model::CfgOverrides { global: CfgDiff::new( - self.data + self.root_config + .global + .0 .cargo_cfgs .iter() .map(|(key, val)| { @@ -1246,7 +1589,9 @@ impl Config { ) .unwrap(), selective: self - .data + .root_config + .global + .0 .cargo_unsetTest .iter() .map(|it| { @@ -1257,381 +1602,206 @@ impl Config { }) .collect(), }, - wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper, - invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy { + wrap_rustc_in_build_scripts: self + .root_config + .global + .0 + .cargo_buildScripts_useRustcWrapper, + invocation_strategy: match self + .root_config + .global + .0 + .cargo_buildScripts_invocationStrategy + { InvocationStrategy::Once => project_model::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace, }, - invocation_location: match self.data.cargo_buildScripts_invocationLocation { + invocation_location: match self + .root_config + .global + .0 + .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_args: self.data.cargo_extraArgs.clone(), - extra_env: self.data.cargo_extraEnv.clone(), + run_build_script_command: self + .root_config + .global + .0 + .cargo_buildScripts_overrideCommand + .clone(), + extra_args: self.root_config.global.0.cargo_extraArgs.clone(), + extra_env: self.root_config.global.0.cargo_extraEnv.clone(), target_dir: self.target_dir_from_config(), } } - pub fn rustfmt(&self) -> RustfmtConfig { - match &self.data.rustfmt_overrideCommand { - Some(args) if !args.is_empty() => { - let mut args = args.clone(); - let command = args.remove(0); - RustfmtConfig::CustomCommand { command, args } - } - Some(_) | None => RustfmtConfig::Rustfmt { - extra_args: self.data.rustfmt_extraArgs.clone(), - enable_range_formatting: self.data.rustfmt_rangeFormatting_enable, - }, - } - } - - pub fn flycheck(&self) -> FlycheckConfig { - match &self.data.check_overrideCommand { - Some(args) if !args.is_empty() => { - let mut args = args.clone(); - let command = args.remove(0); - FlycheckConfig::CustomCommand { - command, - args, - extra_env: self.check_extra_env(), - invocation_strategy: match self.data.check_invocationStrategy { - InvocationStrategy::Once => flycheck::InvocationStrategy::Once, - InvocationStrategy::PerWorkspace => { - flycheck::InvocationStrategy::PerWorkspace - } - }, - invocation_location: match self.data.check_invocationLocation { - InvocationLocation::Root => { - flycheck::InvocationLocation::Root(self.root_path.clone()) - } - InvocationLocation::Workspace => flycheck::InvocationLocation::Workspace, - }, - } - } - Some(_) | None => FlycheckConfig::CargoCommand { - command: self.data.check_command.clone(), - target_triples: self - .data - .check_targets - .clone() - .and_then(|targets| match &targets.0[..] { - [] => None, - targets => Some(targets.into()), - }) - .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()), - all_targets: self.data.check_allTargets, - no_default_features: self - .data - .check_noDefaultFeatures - .unwrap_or(self.data.cargo_noDefaultFeatures), - all_features: matches!( - self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features), - CargoFeaturesDef::All - ), - features: match self - .data - .check_features - .clone() - .unwrap_or_else(|| self.data.cargo_features.clone()) - { - CargoFeaturesDef::All => vec![], - CargoFeaturesDef::Selected(it) => it, - }, - extra_args: self.check_extra_args(), - extra_env: self.check_extra_env(), - ansi_color_output: self.color_diagnostic_output(), - target_dir: self.target_dir_from_config(), - }, - } - } - - fn target_dir_from_config(&self) -> Option { - self.data.rust_analyzerTargetDir.as_ref().and_then(|target_dir| match target_dir { - TargetDirectory::UseSubdirectory(yes) if *yes => { - Some(PathBuf::from("target/rust-analyzer")) - } - TargetDirectory::UseSubdirectory(_) => None, - TargetDirectory::Directory(dir) => Some(dir.clone()), - }) - } - - pub fn check_on_save(&self) -> bool { - self.data.checkOnSave - } - - pub fn runnables(&self) -> RunnablesConfig { - RunnablesConfig { - override_cargo: self.data.runnables_command.clone(), - cargo_extra_args: self.data.runnables_extraArgs.clone(), - } - } - - pub fn inlay_hints(&self) -> InlayHintsConfig { - let client_capability_fields = self - .caps - .text_document - .as_ref() - .and_then(|text| text.inlay_hint.as_ref()) - .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref()) - .map(|inlay_resolve| inlay_resolve.properties.iter()) - .into_iter() - .flatten() - .cloned() - .collect::>(); - - InlayHintsConfig { - render_colons: self.data.inlayHints_renderColons, - type_hints: self.data.inlayHints_typeHints_enable, - parameter_hints: self.data.inlayHints_parameterHints_enable, - chaining_hints: self.data.inlayHints_chainingHints_enable, - discriminant_hints: match self.data.inlayHints_discriminantHints_enable { - DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, - DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, - DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless, - }, - closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable { - ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always, - ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never, - ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock, - }, - lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable { - LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always, - LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never, - LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial, - }, - hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor, - hide_closure_initialization_hints: self - .data - .inlayHints_typeHints_hideClosureInitialization, - closure_style: match self.data.inlayHints_closureStyle { - ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn, - ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation, - ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId, - ClosureStyle::Hide => hir::ClosureStyle::Hide, - }, - closure_capture_hints: self.data.inlayHints_closureCaptureHints_enable, - adjustment_hints: match self.data.inlayHints_expressionAdjustmentHints_enable { - AdjustmentHintsDef::Always => ide::AdjustmentHints::Always, - AdjustmentHintsDef::Never => match self.data.inlayHints_reborrowHints_enable { - ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { - ide::AdjustmentHints::ReborrowOnly - } - ReborrowHintsDef::Never => ide::AdjustmentHints::Never, - }, - AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly, - }, - adjustment_hints_mode: match self.data.inlayHints_expressionAdjustmentHints_mode { - AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix, - AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix, - AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix, - AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix, - }, - adjustment_hints_hide_outside_unsafe: self - .data - .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe, - binding_mode_hints: self.data.inlayHints_bindingModeHints_enable, - param_names_for_lifetime_elision_hints: self - .data - .inlayHints_lifetimeElisionHints_useParameterNames, - max_length: self.data.inlayHints_maxLength, - closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable { - Some(self.data.inlayHints_closingBraceHints_minLines) - } else { - None - }, - fields_to_resolve: InlayFieldsToResolve { - resolve_text_edits: client_capability_fields.contains("textEdits"), - resolve_hint_tooltip: client_capability_fields.contains("tooltip"), - resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), - resolve_label_location: client_capability_fields.contains("label.location"), - resolve_label_command: client_capability_fields.contains("label.command"), - }, - } - } - - fn insert_use_config(&self) -> InsertUseConfig { - InsertUseConfig { - granularity: match self.data.imports_granularity_group { - ImportGranularityDef::Preserve => ImportGranularity::Preserve, - ImportGranularityDef::Item => ImportGranularity::Item, - ImportGranularityDef::Crate => ImportGranularity::Crate, - ImportGranularityDef::Module => ImportGranularity::Module, - }, - enforce_granularity: self.data.imports_granularity_enforce, - prefix_kind: match self.data.imports_prefix { - ImportPrefixDef::Plain => PrefixKind::Plain, - ImportPrefixDef::ByCrate => PrefixKind::ByCrate, - ImportPrefixDef::BySelf => PrefixKind::BySelf, - }, - group: self.data.imports_group_enable, - skip_glob_imports: !self.data.imports_merge_glob, - } - } - - pub fn completion(&self) -> CompletionConfig { - CompletionConfig { - enable_postfix_completions: self.data.completion_postfix_enable, - enable_imports_on_the_fly: self.data.completion_autoimport_enable - && completion_item_edit_resolve(&self.caps), - enable_self_on_the_fly: self.data.completion_autoself_enable, - enable_private_editable: self.data.completion_privateEditable_enable, - full_function_signatures: self.data.completion_fullFunctionSignatures_enable, - callable: match self.data.completion_callable_snippets { - CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), - CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), - CallableCompletionDef::None => None, + pub fn rustfmt(&self) -> RustfmtConfig { + match &self.root_config.global.0.rustfmt_overrideCommand { + Some(args) if !args.is_empty() => { + let mut args = args.clone(); + let command = args.remove(0); + RustfmtConfig::CustomCommand { command, args } + } + Some(_) | None => RustfmtConfig::Rustfmt { + extra_args: self.root_config.global.0.rustfmt_extraArgs.clone(), + enable_range_formatting: self.root_config.global.0.rustfmt_rangeFormatting_enable, }, - insert_use: self.insert_use_config(), - prefer_no_std: self.data.imports_prefer_no_std, - snippet_cap: SnippetCap::new(try_or_def!( - self.caps - .text_document - .as_ref()? - .completion - .as_ref()? - .completion_item - .as_ref()? - .snippet_support? - )), - snippets: self.snippets.clone(), - limit: self.data.completion_limit, } } - pub fn find_all_refs_exclude_imports(&self) -> bool { - self.data.references_excludeImports - } - - pub fn snippet_cap(&self) -> bool { - self.experimental("snippetTextEdit") - } - - pub fn assist(&self) -> AssistConfig { - AssistConfig { - snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), - allowed: None, - insert_use: self.insert_use_config(), - prefer_no_std: self.data.imports_prefer_no_std, - assist_emit_must_use: self.data.assist_emitMustUse, + pub fn flycheck(&self) -> FlycheckConfig { + match &self.root_config.global.0.check_overrideCommand { + Some(args) if !args.is_empty() => { + let mut args = args.clone(); + let command = args.remove(0); + FlycheckConfig::CustomCommand { + command, + args, + extra_env: self.check_extra_env(), + invocation_strategy: match self.root_config.global.0.check_invocationStrategy { + InvocationStrategy::Once => flycheck::InvocationStrategy::Once, + InvocationStrategy::PerWorkspace => { + flycheck::InvocationStrategy::PerWorkspace + } + }, + invocation_location: match self.root_config.global.0.check_invocationLocation { + InvocationLocation::Root => { + flycheck::InvocationLocation::Root(self.root_path.clone()) + } + InvocationLocation::Workspace => flycheck::InvocationLocation::Workspace, + }, + } + } + Some(_) | None => FlycheckConfig::CargoCommand { + command: self.root_config.global.0.check_command.clone(), + target_triples: self + .root_config + .global + .0 + .check_targets + .clone() + .and_then(|targets| match &targets.0[..] { + [] => None, + targets => Some(targets.into()), + }) + .unwrap_or_else(|| { + self.root_config.global.0.cargo_target.clone().into_iter().collect() + }), + all_targets: self.root_config.global.0.check_allTargets, + no_default_features: self + .root_config + .global + .0 + .check_noDefaultFeatures + .unwrap_or(self.root_config.global.0.cargo_noDefaultFeatures), + all_features: matches!( + self.root_config + .global + .0 + .check_features + .as_ref() + .unwrap_or(&self.root_config.global.0.cargo_features), + CargoFeaturesDef::All + ), + features: match self + .root_config + .global + .0 + .check_features + .clone() + .unwrap_or_else(|| self.root_config.global.0.cargo_features.clone()) + { + CargoFeaturesDef::All => vec![], + CargoFeaturesDef::Selected(it) => it, + }, + extra_args: self.check_extra_args(), + extra_env: self.check_extra_env(), + ansi_color_output: self.color_diagnostic_output(), + target_dir: self.target_dir_from_config(), + }, } } - pub fn join_lines(&self) -> JoinLinesConfig { - JoinLinesConfig { - join_else_if: self.data.joinLines_joinElseIf, - remove_trailing_comma: self.data.joinLines_removeTrailingComma, - unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock, - join_assignments: self.data.joinLines_joinAssignments, - } + fn target_dir_from_config(&self) -> Option { + self.root_config.global.0.rust_analyzerTargetDir.as_ref().and_then(|target_dir| { + match target_dir { + TargetDirectory::UseSubdirectory(yes) if *yes => { + Some(PathBuf::from("target/rust-analyzer")) + } + TargetDirectory::UseSubdirectory(_) => None, + TargetDirectory::Directory(dir) => Some(dir.clone()), + } + }) } - pub fn call_info(&self) -> CallInfoConfig { - CallInfoConfig { - params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters), - docs: self.data.signatureInfo_documentation_enable, - } + pub fn check_on_save(&self) -> bool { + self.root_config.global.0.checkOnSave } - pub fn lens(&self) -> LensConfig { - LensConfig { - run: self.data.lens_enable && self.data.lens_run_enable, - debug: self.data.lens_enable && self.data.lens_debug_enable, - interpret: self.data.lens_enable - && self.data.lens_run_enable - && self.data.interpret_tests, - implementations: self.data.lens_enable && self.data.lens_implementations_enable, - method_refs: self.data.lens_enable && self.data.lens_references_method_enable, - refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable, - refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable, - enum_variant_refs: self.data.lens_enable - && self.data.lens_references_enumVariant_enable, - location: self.data.lens_location, + pub fn runnables(&self) -> RunnablesConfig { + RunnablesConfig { + override_cargo: self.root_config.global.0.runnables_command.clone(), + cargo_extra_args: self.root_config.global.0.runnables_extraArgs.clone(), } } - pub fn hover_actions(&self) -> HoverActionsConfig { - let enable = self.experimental("hoverActions") && self.data.hover_actions_enable; - HoverActionsConfig { - implementations: enable && self.data.hover_actions_implementations_enable, - references: enable && self.data.hover_actions_references_enable, - run: enable && self.data.hover_actions_run_enable, - debug: enable && self.data.hover_actions_debug_enable, - goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable, - } + pub fn find_all_refs_exclude_imports(&self) -> bool { + self.root_config.global.0.references_excludeImports } - pub fn highlighting_non_standard_tokens(&self) -> bool { - self.data.semanticHighlighting_nonStandardTokens + pub fn snippet_cap(&self) -> bool { + self.experimental("snippetTextEdit") } - pub fn highlighting_config(&self) -> HighlightConfig { - HighlightConfig { - strings: self.data.semanticHighlighting_strings_enable, - punctuation: self.data.semanticHighlighting_punctuation_enable, - specialize_punctuation: self - .data - .semanticHighlighting_punctuation_specialization_enable, - macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang, - operator: self.data.semanticHighlighting_operator_enable, - specialize_operator: self.data.semanticHighlighting_operator_specialization_enable, - inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable, - syntactic_name_ref_highlighting: false, + pub fn call_info(&self) -> CallInfoConfig { + CallInfoConfig { + params_only: matches!( + self.root_config.global.0.signatureInfo_detail, + SignatureDetail::Parameters + ), + docs: self.root_config.global.0.signatureInfo_documentation_enable, } } - pub fn hover(&self) -> HoverConfig { - let mem_kind = |kind| match kind { - MemoryLayoutHoverRenderKindDef::Both => MemoryLayoutHoverRenderKind::Both, - MemoryLayoutHoverRenderKindDef::Decimal => MemoryLayoutHoverRenderKind::Decimal, - MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal, - }; - HoverConfig { - links_in_hover: self.data.hover_links_enable, - memory_layout: self.data.hover_memoryLayout_enable.then_some(MemoryLayoutHoverConfig { - size: self.data.hover_memoryLayout_size.map(mem_kind), - offset: self.data.hover_memoryLayout_offset.map(mem_kind), - alignment: self.data.hover_memoryLayout_alignment.map(mem_kind), - niches: self.data.hover_memoryLayout_niches.unwrap_or_default(), - }), - documentation: self.data.hover_documentation_enable, - format: { - let is_markdown = try_or_def!(self - .caps - .text_document - .as_ref()? - .hover - .as_ref()? - .content_format - .as_ref()? - .as_slice()) - .contains(&MarkupKind::Markdown); - if is_markdown { - HoverDocFormat::Markdown - } else { - HoverDocFormat::PlainText - } - }, - keywords: self.data.hover_documentation_keywords_enable, + pub fn lens(&self) -> LensConfig { + LensConfig { + run: self.root_config.global.0.lens_enable && self.root_config.global.0.lens_run_enable, + debug: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_debug_enable, + interpret: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_run_enable + && self.root_config.global.0.interpret_tests, + implementations: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_implementations_enable, + method_refs: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_references_method_enable, + refs_adt: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_references_adt_enable, + refs_trait: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_references_trait_enable, + enum_variant_refs: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_references_enumVariant_enable, + location: self.root_config.global.0.lens_location, } } pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig { WorkspaceSymbolConfig { - search_scope: match self.data.workspace_symbol_search_scope { + search_scope: match self.root_config.global.0.workspace_symbol_search_scope { WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace, WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => { WorkspaceSymbolSearchScope::WorkspaceAndDependencies } }, - search_kind: match self.data.workspace_symbol_search_kind { + search_kind: match self.root_config.global.0.workspace_symbol_search_kind { WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes, WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols, }, - search_limit: self.data.workspace_symbol_search_limit, + search_limit: self.root_config.global.0.workspace_symbol_search_limit, } } @@ -1665,7 +1835,7 @@ impl Config { try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null); let commands: Option = serde_json::from_value(commands.clone()).ok(); - let force = commands.is_none() && self.data.lens_forceCustomCommands; + let force = commands.is_none() && self.root_config.global.0.lens_forceCustomCommands; let commands = commands.map(|it| it.commands).unwrap_or_default(); let get = |name: &str| commands.iter().any(|it| it == name) || force; @@ -1679,29 +1849,23 @@ impl Config { } } - pub fn highlight_related(&self) -> HighlightRelatedConfig { - HighlightRelatedConfig { - references: self.data.highlightRelated_references_enable, - break_points: self.data.highlightRelated_breakPoints_enable, - exit_points: self.data.highlightRelated_exitPoints_enable, - yield_points: self.data.highlightRelated_yieldPoints_enable, - closure_captures: self.data.highlightRelated_closureCaptures_enable, - } - } - pub fn prime_caches_num_threads(&self) -> u8 { - match self.data.cachePriming_numThreads { + match self.root_config.global.0.cachePriming_numThreads { 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX), n => n, } } pub fn main_loop_num_threads(&self) -> usize { - self.data.numThreads.unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1)) + self.root_config + .global + .0 + .numThreads + .unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1)) } pub fn typing_autoclose_angle(&self) -> bool { - self.data.typing_autoClosingAngleBrackets_enable + self.root_config.global.0.typing_autoClosingAngleBrackets_enable } // FIXME: VSCode seems to work wrong sometimes, see https://github.com/microsoft/vscode/issues/193124 @@ -1712,100 +1876,120 @@ impl Config { } // Deserialization definitions -macro_rules! create_bool_or_string_de { +macro_rules! create_bool_or_string_serde { ($ident:ident<$bool:literal, $string:literal>) => { - fn $ident<'de, D>(d: D) -> Result<(), D::Error> - where - D: serde::Deserializer<'de>, - { - struct V; - impl<'de> serde::de::Visitor<'de> for V { - type Value = (); - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str(concat!( - stringify!($bool), - " or \"", - stringify!($string), - "\"" - )) - } + mod $ident { + pub(super) fn deserialize<'de, D>(d: D) -> Result<(), D::Error> + where + D: serde::Deserializer<'de>, + { + struct V; + impl<'de> serde::de::Visitor<'de> for V { + type Value = (); + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + formatter.write_str(concat!( + stringify!($bool), + " or \"", + stringify!($string), + "\"" + )) + } - fn visit_bool(self, v: bool) -> Result - where - E: serde::de::Error, - { - match v { - $bool => Ok(()), - _ => Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Bool(v), - &self, - )), + fn visit_bool(self, v: bool) -> Result + where + E: serde::de::Error, + { + match v { + $bool => Ok(()), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Bool(v), + &self, + )), + } } - } - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - match v { - $string => Ok(()), - _ => Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Str(v), - &self, - )), + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v { + $string => Ok(()), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(v), + &self, + )), + } } - } - fn visit_enum(self, a: A) -> Result - where - A: serde::de::EnumAccess<'de>, - { - use serde::de::VariantAccess; - let (variant, va) = a.variant::<&'de str>()?; - va.unit_variant()?; - match variant { - $string => Ok(()), - _ => Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Str(variant), - &self, - )), + fn visit_enum(self, a: A) -> Result + where + A: serde::de::EnumAccess<'de>, + { + use serde::de::VariantAccess; + let (variant, va) = a.variant::<&'de str>()?; + va.unit_variant()?; + match variant { + $string => Ok(()), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(variant), + &self, + )), + } } } + d.deserialize_any(V) + } + + pub(super) fn serialize(serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str($string) } - d.deserialize_any(V) } }; } -create_bool_or_string_de!(true_or_always); -create_bool_or_string_de!(false_or_never); +create_bool_or_string_serde!(true_or_always); +create_bool_or_string_serde!(false_or_never); macro_rules! named_unit_variant { ($variant:ident) => { - pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error> - where - D: serde::Deserializer<'de>, - { - struct V; - impl<'de> serde::de::Visitor<'de> for V { - type Value = (); - fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(concat!("\"", stringify!($variant), "\"")) - } - fn visit_str(self, value: &str) -> Result { - if value == stringify!($variant) { - Ok(()) - } else { - Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) + pub(super) mod $variant { + pub(in super::super) fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> + where + D: serde::Deserializer<'de>, + { + struct V; + impl<'de> serde::de::Visitor<'de> for V { + type Value = (); + fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(concat!("\"", stringify!($variant), "\"")) + } + fn visit_str(self, value: &str) -> Result { + if value == stringify!($variant) { + Ok(()) + } else { + Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) + } } } + deserializer.deserialize_str(V) + } + pub(in super::super) fn serialize(serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(stringify!($variant)) } - deserializer.deserialize_str(V) } }; } -mod de_unit_v { +mod unit_v { named_unit_variant!(all); named_unit_variant!(skip_trivial); named_unit_variant!(mutable); @@ -1817,7 +2001,7 @@ mod de_unit_v { named_unit_variant!(both); } -#[derive(Deserialize, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[serde(rename_all = "snake_case")] enum SnippetScopeDef { Expr, @@ -1831,67 +2015,92 @@ impl Default for SnippetScopeDef { } } -#[derive(Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[serde(default)] struct SnippetDef { - #[serde(deserialize_with = "single_or_array")] + #[serde(with = "single_or_array")] + #[serde(skip_serializing_if = "Vec::is_empty")] prefix: Vec, - #[serde(deserialize_with = "single_or_array")] + + #[serde(with = "single_or_array")] + #[serde(skip_serializing_if = "Vec::is_empty")] postfix: Vec, - description: Option, - #[serde(deserialize_with = "single_or_array")] + + #[serde(with = "single_or_array")] + #[serde(skip_serializing_if = "Vec::is_empty")] body: Vec, - #[serde(deserialize_with = "single_or_array")] + + #[serde(with = "single_or_array")] + #[serde(skip_serializing_if = "Vec::is_empty")] requires: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, + scope: SnippetScopeDef, } -fn single_or_array<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - struct SingleOrVec; +mod single_or_array { + use serde::{Deserialize, Serialize}; - impl<'de> serde::de::Visitor<'de> for SingleOrVec { - type Value = Vec; + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + struct SingleOrVec; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("string or array of strings") - } + impl<'de> serde::de::Visitor<'de> for SingleOrVec { + type Value = Vec; - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - Ok(vec![value.to_owned()]) - } + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("string or array of strings") + } - fn visit_seq(self, seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)) + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + Ok(vec![value.to_owned()]) + } + + fn visit_seq(self, seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)) + } } + + deserializer.deserialize_any(SingleOrVec) } - deserializer.deserialize_any(SingleOrVec) + pub fn serialize(vec: &Vec, serializer: S) -> Result + where + S: serde::Serializer, + { + match &vec[..] { + // [] case is handled by skip_serializing_if + [single] => serializer.serialize_str(&single), + slice => slice.serialize(serializer), + } + } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ManifestOrProjectJson { Manifest(PathBuf), ProjectJson(ProjectJsonData), } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ExprFillDefaultDef { Todo, Default, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportGranularityDef { Preserve, @@ -1900,7 +2109,7 @@ enum ImportGranularityDef { Module, } -#[derive(Deserialize, Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[serde(rename_all = "snake_case")] enum CallableCompletionDef { FillArguments, @@ -1908,54 +2117,54 @@ enum CallableCompletionDef { None, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum CargoFeaturesDef { - #[serde(deserialize_with = "de_unit_v::all")] + #[serde(with = "unit_v::all")] All, Selected(Vec), } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum InvocationStrategy { Once, PerWorkspace, } -#[derive(Deserialize, Debug, Clone)] -struct CheckOnSaveTargets(#[serde(deserialize_with = "single_or_array")] Vec); +#[derive(Serialize, Deserialize, Debug, Clone)] +struct CheckOnSaveTargets(#[serde(with = "single_or_array")] Vec); -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum InvocationLocation { Root, Workspace, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum LifetimeElisionDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::skip_trivial")] + #[serde(with = "unit_v::skip_trivial")] SkipTrivial, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ClosureReturnTypeHintsDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::with_block")] + #[serde(with = "unit_v::with_block")] WithBlock, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ClosureStyle { ImplFn, @@ -1964,40 +2173,40 @@ enum ClosureStyle { Hide, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ReborrowHintsDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::mutable")] + #[serde(with = "unit_v::mutable")] Mutable, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum AdjustmentHintsDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::reborrow")] + #[serde(with = "unit_v::reborrow")] Reborrow, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(untagged)] enum DiscriminantHintsDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::fieldless")] + #[serde(with = "unit_v::fieldless")] Fieldless, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum AdjustmentHintsModeDef { Prefix, @@ -2006,7 +2215,7 @@ enum AdjustmentHintsModeDef { PreferPostfix, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum FilesWatcherDef { Client, @@ -2014,7 +2223,7 @@ enum FilesWatcherDef { Server, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportPrefixDef { Plain, @@ -2024,40 +2233,51 @@ enum ImportPrefixDef { ByCrate, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum WorkspaceSymbolSearchScopeDef { Workspace, WorkspaceAndDependencies, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum SignatureDetail { Full, Parameters, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum WorkspaceSymbolSearchKindDef { OnlyTypes, AllSymbols, } -#[derive(Deserialize, Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] #[serde(rename_all = "snake_case")] #[serde(untagged)] -pub enum MemoryLayoutHoverRenderKindDef { - #[serde(deserialize_with = "de_unit_v::decimal")] +enum MemoryLayoutHoverRenderKindDef { + #[serde(with = "unit_v::decimal")] Decimal, - #[serde(deserialize_with = "de_unit_v::hexadecimal")] + #[serde(with = "unit_v::hexadecimal")] Hexadecimal, - #[serde(deserialize_with = "de_unit_v::both")] + #[serde(with = "unit_v::both")] Both, } -#[derive(Deserialize, Debug, Clone, PartialEq)] +#[test] +fn untagged_option_hover_render_kind() { + let hex = MemoryLayoutHoverRenderKindDef::Hexadecimal; + + let ser = serde_json::to_string(&Some(hex)).unwrap(); + assert_eq!(&ser, "\"hexadecimal\""); + + let opt: Option<_> = serde_json::from_str("\"hexadecimal\"").unwrap(); + assert_eq!(opt, Some(hex)); +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "snake_case")] #[serde(untagged)] pub enum TargetDirectory { @@ -2065,68 +2285,263 @@ pub enum TargetDirectory { Directory(PathBuf), } +macro_rules! _default_val { + (@verbatim: $s:literal, $ty:ty) => {{ + let default_: $ty = serde_json::from_str(&$s).unwrap(); + default_ + }}; + ($default:expr, $ty:ty) => {{ + let default_: $ty = $default; + default_ + }}; +} + +macro_rules! _default_str { + (@verbatim: $s:literal, $_ty:ty) => { + $s.to_string() + }; + ($default:expr, $ty:ty) => {{ + let val = default_val!($default, $ty); + serde_json::to_string_pretty(&val).unwrap() + }}; +} + macro_rules! _config_data { - (struct $name:ident { + // modname is for the tests + ($modname:ident: struct $name:ident <- $input:ident -> $root:ident { $( $(#[doc=$doc:literal])* - $field:ident $(| $alias:ident)*: $ty:ty = $default:expr, + $field:ident $(| $alias:ident)*: $ty:ty = $(@$marker:ident: )? $default:expr, )* }) => { + /// All fields raw `T`, representing either a root config, or a root config + overrides from + /// some distal configuration blob(s). #[allow(non_snake_case)] - #[derive(Debug, Clone)] + #[derive(Debug, Clone, Serialize)] struct $name { $($field: $ty,)* } - impl $name { - fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name { + + /// All fields `Option`, `None` representing fields not set in a particular JSON/TOML blob. + #[allow(non_snake_case)] + #[derive(Clone, Serialize, Default)] + struct $input { $( + #[serde(skip_serializing_if = "Option::is_none")] + $field: Option<$ty>, + )* } + + impl std::fmt::Debug for $input { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut s = f.debug_struct(stringify!($input)); + $( + if let Some(val) = self.$field.as_ref() { + s.field(stringify!($field), val); + } + )* + s.finish() + } + } + + /// Newtype of + #[doc = stringify!($name)] + /// expressing that this was read directly from a single, root config blob. + #[derive(Debug, Clone, Default)] + struct $root($name); + + impl $root { + /// Reads a single root config blob. All fields are either set by the config blob, or to the + /// default value. + fn from_root_input(input: $input) -> Self { + let mut data = $name::default(); + data.apply_input(input); + Self(data) + } + } + + impl Default for $name { + fn default() -> Self { $name {$( + $field: default_val!($(@$marker:)? $default, $ty), + )*} + } + } + + impl $name { + /// Applies overrides from some more local config blob, to self. + #[allow(unused)] + fn apply_input(&mut self, input: $input) { + $( + if let Some(value) = input.$field { + self.$field = value; + } + )* + } + + #[allow(unused)] + fn clone_with_overrides(&self, input: $input) -> Self { + Self {$( + $field: input.$field.unwrap_or_else(|| self.$field.clone()), + )*} + } + } + + impl $input { + #[allow(unused)] + fn from_json(json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> Self { + Self {$( $field: get_field( - &mut json, + json, error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - $default, ), )*} } - fn json_schema() -> serde_json::Value { - schema(&[ - $({ - let field = stringify!($field); - let ty = stringify!($ty); - - (field, ty, &[$($doc),*], $default) - },)* - ]) + #[allow(unused)] + fn from_toml(toml: &mut toml::Table , error_sink: &mut Vec<(String, toml::de::Error)>) -> Self { + Self {$( + $field: get_field_toml::<$ty>( + toml, + error_sink, + stringify!($field), + None$(.or(Some(stringify!($alias))))*, + ), + )*} } - #[cfg(test)] - fn manual() -> String { - manual(&[ + fn schema_fields(sink: &mut Vec) { + sink.extend_from_slice(&[ $({ let field = stringify!($field); let ty = stringify!($ty); + let default = default_str!($(@$marker:)? $default, $ty); - (field, ty, &[$($doc),*], $default) + (field, ty, &[$($doc),*], default) },)* ]) } } - #[test] - fn fields_are_sorted() { - [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); + mod $modname { + #[test] + fn fields_are_sorted() { + let field_names: &'static [&'static str] = &[$(stringify!($field)),*]; + field_names.windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); + } } }; } use _config_data as config_data; +use _default_str as default_str; +use _default_val as default_val; + +/// All of the config levels, all fields raw `T`. Represents a root configuration, or a config set +#[derive(Debug, Clone, Serialize, Default)] +struct ConfigData { + #[serde(flatten)] + global: GlobalConfigData, + #[serde(flatten)] + local: LocalConfigData, + #[serde(flatten)] + client: ClientConfigData, +} + +/// All of the config levels, all fields `Option`, to describe fields that are actually set by +/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to +/// all fields being None. +#[derive(Debug, Clone, Serialize, Default)] +struct ConfigInput { + #[serde(flatten)] + global: GlobalConfigInput, + #[serde(flatten)] + local: LocalConfigInput, + #[serde(flatten)] + client: ClientConfigInput, +} + +impl ConfigInput { + fn from_json( + mut json: serde_json::Value, + error_sink: &mut Vec<(String, serde_json::Error)>, + ) -> ConfigInput { + ConfigInput { + global: GlobalConfigInput::from_json(&mut json, error_sink), + local: LocalConfigInput::from_json(&mut json, error_sink), + client: ClientConfigInput::from_json(&mut json, error_sink), + } + } + + fn from_toml( + mut toml: toml::Table, + error_sink: &mut Vec<(String, toml::de::Error)>, + ) -> ConfigInput { + ConfigInput { + global: GlobalConfigInput::from_toml(&mut toml, error_sink), + local: LocalConfigInput::from_toml(&mut toml, error_sink), + client: ClientConfigInput::from_toml(&mut toml, error_sink), + } + } + + fn schema_fields() -> Vec { + let mut fields = Vec::new(); + GlobalConfigInput::schema_fields(&mut fields); + LocalConfigInput::schema_fields(&mut fields); + ClientConfigInput::schema_fields(&mut fields); + // HACK: sort the fields, so the diffs on the generated docs/schema are smaller + fields.sort_by_key(|&(x, ..)| x); + fields + } + + fn json_schema() -> serde_json::Value { + schema(&Self::schema_fields()) + } + + #[cfg(test)] + fn manual() -> String { + manual(&Self::schema_fields()) + } +} + +fn get_field_toml( + val: &toml::Table, + error_sink: &mut Vec<(String, toml::de::Error)>, + field: &'static str, + alias: Option<&'static str>, +) -> Option { + alias + .into_iter() + .chain(iter::once(field)) + .filter_map(move |field| { + let subkeys = field.split('_'); + let mut v = val; + for subkey in subkeys { + if let Some(val) = v.get(subkey) { + if let Some(map) = val.as_table() { + v = map; + } else { + return Some(toml::Value::try_into(val.clone()).map_err(|e| (e, v))); + } + } else { + return None; + } + } + None + }) + .find(Result::is_ok) + .and_then(|res| match res { + Ok(it) => Some(it), + Err((e, pointer)) => { + error_sink.push((pointer.to_string(), e)); + None + } + }) +} fn get_field( json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>, field: &'static str, alias: Option<&'static str>, - default: &str, -) -> T { +) -> Option { // XXX: check alias first, to work around the VS Code where it pre-fills the // defaults instead of sending an empty object. alias @@ -2147,12 +2562,11 @@ fn get_field( None } }) - .unwrap_or_else(|| { - serde_json::from_str(default).unwrap_or_else(|e| panic!("{e} on: `{default}`")) - }) } -fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value { +type SchemaField = (&'static str, &'static str, &'static [&'static str], String); + +fn schema(fields: &[SchemaField]) -> serde_json::Value { let map = fields .iter() .map(|(field, ty, doc, default)| { @@ -2203,7 +2617,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "FxHashMap, Box<[Box]>>" => set! { "type": "object", }, - "FxHashMap" => set! { + "IndexMap" => set! { "type": "object", }, "FxHashMap" => set! { @@ -2513,7 +2927,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json } #[cfg(test)] -fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String { +fn manual(fields: &[SchemaField]) -> String { fields .iter() .map(|(field, _ty, doc, default)| { @@ -2608,7 +3022,7 @@ mod tests { #[test] fn generate_config_documentation() { let docs_path = project_root().join("docs/user/generated_config.adoc"); - let expected = ConfigData::manual(); + let expected = ConfigInput::manual(); ensure_file_contents(&docs_path, &expected); } @@ -2680,7 +3094,7 @@ mod tests { "rust": { "analyzerTargetDir": null } })) .unwrap(); - assert_eq!(config.data.rust_analyzerTargetDir, None); + assert_eq!(config.root_config.global.0.rust_analyzerTargetDir, None); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == None) ); @@ -2700,7 +3114,7 @@ mod tests { })) .unwrap(); assert_eq!( - config.data.rust_analyzerTargetDir, + config.root_config.global.0.rust_analyzerTargetDir, Some(TargetDirectory::UseSubdirectory(true)) ); assert!( @@ -2722,7 +3136,7 @@ mod tests { })) .unwrap(); assert_eq!( - config.data.rust_analyzerTargetDir, + config.root_config.global.0.rust_analyzerTargetDir, Some(TargetDirectory::Directory(PathBuf::from("other_folder"))) ); assert!( diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs new file mode 100644 index 000000000000..ef2803f3d1d7 --- /dev/null +++ b/crates/rust-analyzer/src/config/tree.rs @@ -0,0 +1,685 @@ +use itertools::Itertools; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::sync::Arc; +use vfs::{AbsPathBuf, FileId, Vfs}; + +use super::{ConfigInput, LocalConfigData, RootLocalConfigData}; + +#[derive(Debug, PartialEq)] +pub enum ConfigTreeError { + Removed, + NonExistent, + Utf8(FileId, std::str::Utf8Error), + TomlParse(FileId, toml::de::Error), + TomlDeserialize { file_id: FileId, field: String, error: toml::de::Error }, +} + +/// Some rust-analyzer.toml files have changed, and/or the LSP client sent a new configuration. +pub struct ConfigChanges { + /// - `None` => no change + /// - `Some(None)` => the client config was removed / reset or something + /// - `Some(Some(...))` => the client config was updated + client_change: Option>>, + set_project_root: Option, + set_source_roots: Option>, + ra_toml_changes: Vec, +} + +/// Internal version +struct ConfigChangesInner { + /// - `None` => no change + /// - `Some(None)` => the client config was removed / reset or something + /// - `Some(Some(...))` => the client config was updated + client_change: Option>>, + parent_changes: FxHashMap, + ra_toml_changes: Vec, +} + +#[derive(Debug)] +pub enum ConfigParent { + /// The node is now a root in its own right, but still inherits from the config in XDG_CONFIG_HOME + /// etc + UserDefault, + /// The node is now a child of another rust-analyzer.toml. Even if that one is a non-existent + /// file, it's fine. + /// + /// + /// ```ignore,text + /// /project_root/ + /// rust-analyzer.toml + /// crate_a/ + /// crate_b/ + /// rust-analyzer.toml + /// + /// ``` + /// + /// ```ignore + /// // imagine set_file_contents = vfs.set_file_contents() and then get the vfs.file_id() + /// + /// let root = vfs.set_file_contents("/project_root/rust-analyzer.toml", Some("...")); + /// let crate_a = vfs.set_file_contents("/project_root/crate_a/rust-analyzer.toml", None); + /// let crate_b = vfs.set_file_contents("/project_root/crate_a/crate_b/rust-analyzer.toml", Some("...")); + /// let parent_changes = FxHashMap::from_iter([ + /// (root, ConfigParent::UserDefault), + /// (crate_a, ConfigParent::Parent(root)), + /// (crate_b, ConfigParent::Parent(crate_a)), + /// ]); + /// ``` + Parent(FileId), +} + +/// Easier and probably more performant than making ConfigInput implement Eq +#[derive(Debug)] +struct PointerCmp(Arc); +impl PointerCmp { + fn new(t: T) -> Self { + Self(Arc::new(t)) + } +} +impl Clone for PointerCmp { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} +impl PartialEq for PointerCmp { + fn eq(&self, other: &Self) -> bool { + (Arc::as_ptr(&self.0) as *const ()).eq(&Arc::as_ptr(&other.0).cast()) + } +} +impl Eq for PointerCmp {} +impl std::ops::Deref for PointerCmp { + type Target = T; + fn deref(&self) -> &T { + self.0.deref() + } +} + +#[salsa::query_group(ConfigTreeStorage)] +trait ConfigTreeQueries { + #[salsa::input] + fn client_config(&self) -> Option>; + + #[salsa::input] + fn parent(&self, file_id: FileId) -> Option; + + #[salsa::input] + fn config_input(&self, file_id: FileId) -> Option>; + + fn recursive_local(&self, file_id: FileId) -> PointerCmp; + + /// The output + fn computed_local_config(&self, file_id: FileId) -> PointerCmp; +} + +fn recursive_local(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp { + let self_input = db.config_input(file_id); + tracing::trace!(?self_input, ?file_id); + match db.parent(file_id) { + Some(parent) if parent != file_id => { + let parent_computed = db.recursive_local(parent); + if let Some(input) = self_input.as_deref() { + PointerCmp::new(parent_computed.clone_with_overrides(input.local.clone())) + } else { + parent_computed + } + } + _ => { + // this is a root node, or we just broke a cycle + if let Some(input) = self_input.as_deref() { + let root_local = RootLocalConfigData::from_root_input(input.local.clone()); + PointerCmp::new(root_local.0) + } else { + PointerCmp::new(LocalConfigData::default()) + } + } + } +} + +fn computed_local_config( + db: &dyn ConfigTreeQueries, + file_id: FileId, +) -> PointerCmp { + let computed = db.recursive_local(file_id); + if let Some(client) = db.client_config() { + PointerCmp::new(computed.clone_with_overrides(client.local.clone())) + } else { + computed + } +} + +#[salsa::database(ConfigTreeStorage)] +pub struct ConfigDb { + storage: salsa::Storage, + known_file_ids: FxHashSet, + xdg_config_file_id: FileId, + source_roots: FxHashSet, + project_root: AbsPathBuf, +} + +impl salsa::Database for ConfigDb {} + +impl ConfigDb { + pub fn new(xdg_config_file_id: FileId, project_root: AbsPathBuf) -> Self { + let mut this = Self { + storage: Default::default(), + known_file_ids: FxHashSet::default(), + xdg_config_file_id, + source_roots: FxHashSet::default(), + project_root, + }; + this.set_client_config(None); + this.ensure_node(xdg_config_file_id); + this + } + + /// Gets the value of LocalConfigData for a given `rust-analyzer.toml` FileId. + /// + /// The rust-analyzer.toml does not need to exist on disk. All values are the expression of + /// overriding the parent `rust-analyzer.toml`, set by adding an entry in + /// `ConfigChanges.parent_changes`. + /// + /// If the db is not aware of the given `rust-analyzer.toml` FileId, then the config is read + /// from the user's system-wide default config. + /// + /// Note that the client config overrides all configs. + pub fn local_config(&self, ra_toml_file_id: FileId) -> Arc { + if self.known_file_ids.contains(&ra_toml_file_id) { + self.computed_local_config(ra_toml_file_id).0 + } else { + tracing::warn!(?ra_toml_file_id, "called local_config with unknown file id"); + self.computed_local_config(self.xdg_config_file_id).0 + } + } + + /// Applies a bunch of [`ConfigChanges`]. The FileIds referred to in `ConfigChanges` do not + /// need to exist. + pub fn apply_changes(&mut self, changes: ConfigChanges, vfs: &mut Vfs) -> Vec { + if let Some(new_project_root) = &changes.set_project_root { + self.project_root = new_project_root.clone(); + } + let source_root_change = changes.set_source_roots.as_ref().or_else(|| { + if changes.set_project_root.is_some() { + Some(&self.source_roots) + } else { + None + } + }); + let parent_changes = if let Some(source_roots) = source_root_change { + let parent_changes = source_roots + .iter() + .flat_map(|path: &AbsPathBuf| { + path.ancestors() + // Note that Path::new("/root2/abc").starts_with(Path::new("/root")) is false + .take_while(|x| x.starts_with(&self.project_root)) + .map(|dir| dir.join("rust-analyzer.toml")) + .map(|path| vfs.alloc_file_id(path.into())) + .collect_vec() + // immediately get tuple_windows before returning from flat_map + .into_iter() + .tuple_windows() + .map(|(a, b)| (a, ConfigParent::Parent(b))) + }) + .collect::>(); + + // Remove source roots (& their parent config files) that are no longer part of the project root + self.known_file_ids + .iter() + .cloned() + .filter(|&x| x != self.xdg_config_file_id && !parent_changes.contains_key(&x)) + .collect_vec() + .into_iter() + .for_each(|deleted| { + self.known_file_ids.remove(&deleted); + self.reset_node(deleted); + }); + + parent_changes + } else { + Default::default() + }; + + if tracing::enabled!(tracing::Level::TRACE) { + for (&a, parent) in &parent_changes { + tracing::trace!( + "{a:?} ({:?}): parent = {parent:?} ({:?})", + vfs.file_path(a), + match parent { + ConfigParent::Parent(p) => vfs.file_path(*p).to_string(), + ConfigParent::UserDefault => "xdg".to_string(), + } + ); + } + } + + let inner = ConfigChangesInner { + ra_toml_changes: changes.ra_toml_changes, + client_change: changes.client_change, + parent_changes, + }; + self.apply_changes_inner(inner, vfs) + } + + fn apply_changes_inner( + &mut self, + changes: ConfigChangesInner, + vfs: &Vfs, + ) -> Vec { + let mut scratch_errors = Vec::new(); + let mut errors = Vec::new(); + let ConfigChangesInner { client_change, ra_toml_changes, parent_changes } = changes; + + if let Some(change) = client_change { + let current = self.client_config(); + let change = change.map(PointerCmp); + match (current.as_ref(), change.as_ref()) { + (None, None) => {} + (Some(a), Some(b)) if a == b => {} + _ => { + self.set_client_config(change); + } + } + } + + for (file_id, parent) in parent_changes { + self.ensure_node(file_id); + let parent_node_id = match parent { + ConfigParent::Parent(parent_file_id) => { + self.ensure_node(parent_file_id); + parent_file_id + } + ConfigParent::UserDefault if file_id == self.xdg_config_file_id => continue, + ConfigParent::UserDefault => self.xdg_config_file_id, + }; + self.set_parent(file_id, Some(parent_node_id)) + } + + for change in ra_toml_changes { + if !self.known_file_ids.contains(&change.file_id) { + // Irrelevant Vfs change. Ideally you would not pass these in at all, but it's not + // a problem to filter them out here. + continue; + } + // turn and face the strain + match change.change_kind { + vfs::ChangeKind::Create | vfs::ChangeKind::Modify => { + let input = parse_toml(change.file_id, vfs, &mut scratch_errors, &mut errors) + .map(PointerCmp); + tracing::trace!("updating toml for {:?} to {:?}", change.file_id, input); + + self.set_config_input(change.file_id, input); + } + vfs::ChangeKind::Delete => { + self.set_config_input(change.file_id, None); + } + } + } + + errors + } + + /// Inserts default values into the salsa inputs for the given file_id + /// if it's never been seen before + fn ensure_node(&mut self, file_id: FileId) { + if self.known_file_ids.insert(file_id) { + self.reset_node(file_id); + } + } + + fn reset_node(&mut self, file_id: FileId) { + self.set_config_input(file_id, None); + self.set_parent( + file_id, + if file_id == self.xdg_config_file_id { None } else { Some(self.xdg_config_file_id) }, + ); + } +} + +fn parse_toml( + file_id: FileId, + vfs: &Vfs, + scratch: &mut Vec<(String, toml::de::Error)>, + errors: &mut Vec, +) -> Option> { + let content = vfs.file_contents(file_id); + let content_str = match std::str::from_utf8(content) { + Err(e) => { + tracing::error!("non-UTF8 TOML content for {file_id:?}: {e}"); + errors.push(ConfigTreeError::Utf8(file_id, e)); + return None; + } + Ok(str) => str, + }; + if content_str.is_empty() { + return None; + } + let table = match toml::from_str(content_str) { + Ok(table) => table, + Err(e) => { + errors.push(ConfigTreeError::TomlParse(file_id, e)); + return None; + } + }; + let input = Arc::new(ConfigInput::from_toml(table, scratch)); + scratch.drain(..).for_each(|(field, error)| { + errors.push(ConfigTreeError::TomlDeserialize { file_id, field, error }); + }); + Some(input) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use vfs::{AbsPath, AbsPathBuf, VfsPath}; + + fn alloc_file_id(vfs: &mut Vfs, s: &str) -> FileId { + let abs_path = AbsPath::assert(s).to_path_buf(); + let vfs_path = VfsPath::from(abs_path); + let file_id = vfs.alloc_file_id(vfs_path); + vfs.set_file_id_contents(file_id, None); + file_id + } + + fn alloc_config(vfs: &mut Vfs, s: &str, config: &str) -> FileId { + let abs_path = AbsPath::assert(s).to_path_buf(); + let vfs_path = VfsPath::from(abs_path); + let file_id = vfs.alloc_file_id(vfs_path); + vfs.set_file_id_contents(file_id, Some(config.to_string().into_bytes())); + file_id + } + + const XDG_CONFIG_HOME_RATOML: &'static str = + "/home/username/.config/rust-analyzer/rust-analyzer.toml"; + + use super::*; + #[test] + fn basic() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + let project_root = AbsPath::assert("/root"); + let xdg_config_file_id = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); + let mut config_tree = ConfigDb::new(xdg_config_file_id, project_root.to_path_buf()); + + let source_roots = ["/root/crate_a"].map(AbsPath::assert); + + let _root = alloc_config( + &mut vfs, + "/root/rust-analyzer.toml", + r#" + [completion.autoself] + enable = false + "#, + ); + + let crate_a = alloc_config( + &mut vfs, + "/root/crate_a/rust-analyzer.toml", + r#" + [completion.autoimport] + enable = false + # will be overridden by client + [semanticHighlighting.strings] + enable = true + "#, + ); + + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); + let changes = ConfigChanges { + // Normally you will filter these! + ra_toml_changes: vfs.take_changes(), + set_project_root: None, + set_source_roots: Some(new_source_roots), + client_change: Some(Some(Arc::new(ConfigInput { + local: crate::config::LocalConfigInput { + semanticHighlighting_strings_enable: Some(false), + ..Default::default() + }, + ..Default::default() + }))), + }; + + dbg!(config_tree.apply_changes(changes, &mut vfs)); + + let local = config_tree.local_config(crate_a); + // from root + assert_eq!(local.completion_autoself_enable, false); + // from crate_a + assert_eq!(local.completion_autoimport_enable, false); + // from client + assert_eq!(local.semanticHighlighting_strings_enable, false); + + // -------------------------------------------------------- + + // Now let's modify the xdg_config_file_id, which should invalidate everything else + vfs.set_file_id_contents( + xdg_config_file_id, + Some( + r#" + # default is "never" + [inlayHints.discriminantHints] + enable = "always" + [completion.autoself] + enable = true + [completion.autoimport] + enable = true + [semanticHighlighting.strings] + enable = true + "# + .to_string() + .into_bytes(), + ), + ); + + let changes = ConfigChanges { + client_change: None, + set_project_root: None, + set_source_roots: None, + ra_toml_changes: dbg!(vfs.take_changes()), + }; + dbg!(config_tree.apply_changes(changes, &mut vfs)); + + let prev = local; + let local = config_tree.local_config(crate_a); + // Should have been recomputed + assert!(!Arc::ptr_eq(&prev, &local)); + // But without changes in between, should give the same Arc back + assert!(Arc::ptr_eq(&local, &config_tree.local_config(crate_a))); + + // The newly added xdg_config_file_id should affect the output if nothing else touches + // this key + assert_eq!( + local.inlayHints_discriminantHints_enable, + crate::config::DiscriminantHintsDef::Always + ); + // But it should not win + assert_eq!(local.completion_autoself_enable, false); + assert_eq!(local.completion_autoimport_enable, false); + assert_eq!(local.semanticHighlighting_strings_enable, false); + } + + #[test] + fn set_source_roots() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + + let project_root = AbsPath::assert("/root"); + let xdg = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); + let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); + + let source_roots = ["/root/crate_a", "/root/crate_a/crate_b"].map(AbsPath::assert); + let [crate_a, crate_b] = source_roots + .map(|dir| dir.join("rust-analyzer.toml")) + .map(|path| vfs.alloc_file_id(path.into())); + + vfs.set_file_id_contents( + xdg, + Some(b"[inlayHints.discriminantHints]\nenable = \"always\"".to_vec()), + ); + vfs.set_file_id_contents(crate_a, Some(b"[completion.autoself]\nenable = false".to_vec())); + // note that crate_b's rust-analyzer.toml doesn't exist + + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); + let changes = ConfigChanges { + client_change: None, + set_project_root: None, // already set in ConfigDb::new(...) + set_source_roots: Some(new_source_roots), + ra_toml_changes: dbg!(vfs.take_changes()), + }; + + dbg!(config_tree.apply_changes(changes, &mut vfs)); + let local = config_tree.local_config(crate_b); + + assert_eq!( + local.inlayHints_discriminantHints_enable, + crate::config::DiscriminantHintsDef::Always + ); + assert_eq!(local.completion_autoself_enable, false); + + // ---- + + // Now move crate b to the root. This gives a new FileId for crate_b/ra.toml. + let source_roots = ["/root/crate_a", "/root/crate_b"].map(AbsPath::assert); + let [_crate_a, crate_b] = source_roots + .map(|dir| dir.join("rust-analyzer.toml")) + .map(|path| vfs.alloc_file_id(path.into())); + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); + let changes = ConfigChanges { + client_change: None, + set_project_root: None, // already set in ConfigDb::new(...) + set_source_roots: Some(new_source_roots), + ra_toml_changes: dbg!(vfs.take_changes()), + }; + + dbg!(config_tree.apply_changes(changes, &mut vfs)); + let local = config_tree.local_config(crate_b); + + // Still inherits from xdg + assert_eq!( + local.inlayHints_discriminantHints_enable, + crate::config::DiscriminantHintsDef::Always + ); + // new crate_b does not inherit from crate_a + assert_eq!(local.completion_autoself_enable, true); + } + + #[test] + fn change_project_root() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + + let project_root = AbsPath::assert("/root"); + let xdg = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); + let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); + + let source_roots = ["/root/crate_a"].map(AbsPath::assert); + let crate_a = vfs.alloc_file_id(source_roots[0].join("rust-analyzer.toml").into()); + + let _root = alloc_config( + &mut vfs, + "/root/rust-analyzer.toml", + r#" + [completion.autoself] + enable = false + "#, + ); + + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); + let changes = ConfigChanges { + client_change: None, + set_project_root: None, // already set in ConfigDb::new(...) + set_source_roots: Some(new_source_roots), + ra_toml_changes: dbg!(vfs.take_changes()), + }; + config_tree.apply_changes(changes, &mut vfs); + let local = config_tree.local_config(crate_a); + // initially crate_a is part of the project root, so it does inherit + // from /root/rust-analyzer.toml + assert_eq!(local.completion_autoself_enable, false); + + // change project root + let changes = ConfigChanges { + client_change: None, + set_project_root: Some(AbsPath::assert("/ro").to_path_buf()), + set_source_roots: None, + ra_toml_changes: dbg!(vfs.take_changes()), + }; + config_tree.apply_changes(changes, &mut vfs); + // crate_a is now outside the project root and hence inherit (1) xdg (2) + // crate_a/ra.toml, but not /root/rust-analyzer.toml any more + let local = config_tree.local_config(crate_a); + assert_eq!(local.completion_autoself_enable, true); + } + + #[test] + fn no_change_to_source_roots() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + + let project_root = AbsPath::assert("/root"); + let xdg = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); + let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); + + let source_roots = ["/root/crate_a"].map(AbsPath::assert); + let crate_a = vfs.alloc_file_id(source_roots[0].join("rust-analyzer.toml").into()); + + let _root = alloc_config( + &mut vfs, + "/root/rust-analyzer.toml", + r#" + [completion.autoself] + enable = false + "#, + ); + + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); + let changes = ConfigChanges { + client_change: None, + set_project_root: None, // already set in ConfigDb::new(...) + set_source_roots: Some(new_source_roots), + ra_toml_changes: dbg!(vfs.take_changes()), + }; + config_tree.apply_changes(changes, &mut vfs); + let local = config_tree.local_config(crate_a); + // initially crate_a is part of the project root, so it does inherit + // from /root/rust-analyzer.toml + assert_eq!(local.completion_autoself_enable, false); + + // Send in an empty change, should have no effect + let changes = ConfigChanges { + client_change: None, + set_project_root: None, + set_source_roots: None, + ra_toml_changes: dbg!(vfs.take_changes()), + }; + config_tree.apply_changes(changes, &mut vfs); + let local = config_tree.local_config(crate_a); + assert_eq!(local.completion_autoself_enable, false); + } + + #[test] + fn ignore_irrelevant_vfs_changes() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + + let project_root = AbsPath::assert("/root"); + let xdg = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); + let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); + + // The main way an irrelevant vfs file change is going to show up is in TOML parse errors. + let invalid_utf8 = b"\xc3\x28"; + vfs.set_file_contents( + AbsPath::assert("/irrelevant/file.bin").to_path_buf().into(), + Some(invalid_utf8.to_vec()), + ); + let errors = config_tree.apply_changes( + ConfigChanges { + client_change: None, + set_project_root: None, + set_source_roots: None, + ra_toml_changes: vfs.take_changes(), + }, + &mut vfs, + ); + assert_eq!(errors, vec![]); + } +} diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index 71701ef16179..b841762d21b7 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -136,7 +136,7 @@ pub(crate) fn fetch_native_diagnostics( let diagnostics = snapshot .analysis .diagnostics( - &snapshot.config.diagnostics(), + &snapshot.config.localize_by_file_id(file_id).diagnostics(), ide::AssistResolveStrategy::None, file_id, ) diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index c09f57252ce9..90743251c4a8 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -330,7 +330,6 @@ impl GlobalState { } (change, changed_files, workspace_structure_change) }; - self.analysis_host.apply_change(change); { diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 06c27332d440..9491c9ed50f8 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -288,8 +288,8 @@ pub(crate) fn handle_join_lines( ) -> anyhow::Result> { let _p = profile::span("handle_join_lines"); - let config = snap.config.join_lines(); let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let config = snap.config.localize_by_file_id(file_id).join_lines(); let line_index = snap.file_line_index(file_id)?; let mut res = TextEdit::default(); @@ -859,7 +859,7 @@ pub(crate) fn handle_completion( let completion_trigger_character = params.context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next()); - let completion_config = &snap.config.completion(); + let completion_config = &snap.config.localize_by_file_id(position.file_id).completion(); let items = match snap.analysis.completions( completion_config, position, @@ -904,7 +904,7 @@ pub(crate) fn handle_completion_resolve( let additional_edits = snap .analysis .resolve_completion_edits( - &snap.config.completion(), + &snap.config.localize_by_file_id(file_id).completion(), FilePosition { file_id, offset }, resolve_data .imports @@ -974,16 +974,17 @@ pub(crate) fn handle_hover( PositionOrRange::Position(position) => Range::new(position, position), PositionOrRange::Range(range) => range, }; - let file_range = from_proto::file_range(&snap, ¶ms.text_document, range)?; - let info = match snap.analysis.hover(&snap.config.hover(), file_range)? { + let local_conf = snap.config.localize_by_file_id(file_range.file_id); + let hover = local_conf.hover(); + let info = match snap.analysis.hover(&hover, file_range)? { None => return Ok(None), Some(info) => info, }; let line_index = snap.file_line_index(file_range.file_id)?; let range = to_proto::range(&line_index, info.range); - let markup_kind = snap.config.hover().format; + let markup_kind = hover.format; let hover = lsp_ext::Hover { hover: lsp_types::Hover { contents: HoverContents::Markup(to_proto::markup_content( @@ -992,7 +993,7 @@ pub(crate) fn handle_hover( )), range: Some(range), }, - actions: if snap.config.hover_actions().none() { + actions: if local_conf.hover_actions().none() { Vec::new() } else { prepare_hover_actions(&snap, &info.info.actions) @@ -1129,11 +1130,12 @@ pub(crate) fn handle_code_action( return Ok(None); } - let line_index = - snap.file_line_index(from_proto::file_id(&snap, ¶ms.text_document.uri)?)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.file_line_index(file_id)?; let frange = from_proto::file_range(&snap, ¶ms.text_document, params.range)?; - let mut assists_config = snap.config.assist(); + let local_config = snap.config.localize_by_file_id(file_id); + let mut assists_config = local_config.assist(); assists_config.allowed = params .context .only @@ -1150,7 +1152,7 @@ pub(crate) fn handle_code_action( }; let assists = snap.analysis.assists_with_fixes( &assists_config, - &snap.config.diagnostics(), + &local_config.diagnostics(), resolve, frange, )?; @@ -1209,7 +1211,9 @@ pub(crate) fn handle_code_action_resolve( let range = from_proto::text_range(&line_index, params.code_action_params.range)?; let frange = FileRange { file_id, range }; - let mut assists_config = snap.config.assist(); + let local_config = snap.config.localize_by_file_id(file_id); + + let mut assists_config = local_config.assist(); assists_config.allowed = params .code_action_params .context @@ -1232,7 +1236,7 @@ pub(crate) fn handle_code_action_resolve( let assists = snap.analysis.assists_with_fixes( &assists_config, - &snap.config.diagnostics(), + &local_config.diagnostics(), AssistResolveStrategy::Single(assist_resolve), frange, )?; @@ -1362,7 +1366,10 @@ pub(crate) fn handle_document_highlight( let position = from_proto::file_position(&snap, params.text_document_position_params)?; let line_index = snap.file_line_index(position.file_id)?; - let refs = match snap.analysis.highlight_related(snap.config.highlight_related(), position)? { + let refs = match snap.analysis.highlight_related( + snap.config.localize_by_file_id(position.file_id).highlight_related(), + position, + )? { None => return Ok(None), Some(refs) => refs, }; @@ -1408,7 +1415,7 @@ pub(crate) fn handle_inlay_hints( params.range, )?; let line_index = snap.file_line_index(file_id)?; - let inlay_hints_config = snap.config.inlay_hints(); + let inlay_hints_config = snap.config.localize_by_file_id(file_id).inlay_hints(); Ok(Some( snap.analysis .inlay_hints(&inlay_hints_config, file_id, Some(range))? @@ -1452,7 +1459,8 @@ pub(crate) fn handle_inlay_hints_resolve( range_start.checked_sub(1.into()).unwrap_or(range_start), range_end.checked_add(1.into()).unwrap_or(range_end), ); - let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(); + let mut forced_resolve_inlay_hints_config = + snap.config.localize_by_file_id(file_id).inlay_hints(); forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty(); let resolve_hints = snap.analysis.inlay_hints( &forced_resolve_inlay_hints_config, @@ -1584,7 +1592,7 @@ pub(crate) fn handle_semantic_tokens_full( let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.localize_by_file_id(file_id).highlighting_config(); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -1595,7 +1603,7 @@ pub(crate) fn handle_semantic_tokens_full( &line_index, highlights, snap.config.semantics_tokens_augments_syntax_tokens(), - snap.config.highlighting_non_standard_tokens(), + snap.config.localize_by_file_id(file_id).highlighting_non_standard_tokens(), ); // Unconditionally cache the tokens @@ -1614,7 +1622,7 @@ pub(crate) fn handle_semantic_tokens_full_delta( let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.localize_by_file_id(file_id).highlighting_config(); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -1625,7 +1633,7 @@ pub(crate) fn handle_semantic_tokens_full_delta( &line_index, highlights, snap.config.semantics_tokens_augments_syntax_tokens(), - snap.config.highlighting_non_standard_tokens(), + snap.config.localize_by_file_id(file_id).highlighting_non_standard_tokens(), ); let cached_tokens = snap.semantic_tokens_cache.lock().remove(¶ms.text_document.uri); @@ -1657,7 +1665,8 @@ pub(crate) fn handle_semantic_tokens_range( let text = snap.analysis.file_text(frange.file_id)?; let line_index = snap.file_line_index(frange.file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = + snap.config.localize_by_file_id(frange.file_id).highlighting_config(); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -1668,7 +1677,7 @@ pub(crate) fn handle_semantic_tokens_range( &line_index, highlights, snap.config.semantics_tokens_augments_syntax_tokens(), - snap.config.highlighting_non_standard_tokens(), + snap.config.localize_by_file_id(frange.file_id).highlighting_non_standard_tokens(), ); Ok(Some(semantic_tokens.into())) } @@ -1788,7 +1797,9 @@ fn show_impl_command_link( snap: &GlobalStateSnapshot, position: &FilePosition, ) -> Option { - if snap.config.hover_actions().implementations && snap.config.client_commands().show_reference { + if snap.config.localize_by_file_id(position.file_id).hover_actions().implementations + && snap.config.client_commands().show_reference + { if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; @@ -1814,7 +1825,9 @@ fn show_ref_command_link( snap: &GlobalStateSnapshot, position: &FilePosition, ) -> Option { - if snap.config.hover_actions().references && snap.config.client_commands().show_reference { + if snap.config.localize_by_file_id(position.file_id).hover_actions().references + && snap.config.client_commands().show_reference + { if let Some(ref_search_res) = snap.analysis.find_all_refs(*position, None).unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; @@ -1844,12 +1857,14 @@ fn runnable_action_links( snap: &GlobalStateSnapshot, runnable: Runnable, ) -> Option { - let hover_actions_config = snap.config.hover_actions(); + let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?; + + let hover_actions_config = + snap.config.localize_by_file_id(runnable.nav.file_id).hover_actions(); if !hover_actions_config.runnable() { return None; } - let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?; if should_skip_target(&runnable, cargo_spec.as_ref()) { return None; } @@ -1881,8 +1896,12 @@ fn goto_type_action_links( snap: &GlobalStateSnapshot, nav_targets: &[HoverGotoTypeData], ) -> Option { - if !snap.config.hover_actions().goto_type_def - || nav_targets.is_empty() + if nav_targets.is_empty() + || !snap + .config + .localize_by_file_id(nav_targets[0].nav.file_id) + .hover_actions() + .goto_type_def || !snap.config.client_commands().goto_location { return None; diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index aca91570f7c2..5da70526b662 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -226,7 +226,7 @@ pub(crate) fn completion_items( completion_item(&mut res, config, line_index, &tdpp, max_relevance, item); } - if let Some(limit) = config.completion().limit { + if let Some(limit) = config.localize_to_root_view().completion().limit { res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text)); res.truncate(limit); } @@ -308,7 +308,7 @@ fn completion_item( set_score(&mut lsp_item, max_relevance, item.relevance); - if config.completion().enable_imports_on_the_fly { + if config.localize_to_root_view().completion().enable_imports_on_the_fly { if !item.import_to_add.is_empty() { let imports: Vec<_> = item .import_to_add diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index cdf41c955d26..2ab7fcd5a673 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -159,6 +159,11 @@ impl GlobalState { scheme: None, pattern: Some("**/Cargo.lock".into()), }, + lsp_types::DocumentFilter { + language: None, + scheme: None, + pattern: Some("**/.rust-analyzer.toml".into()), + }, ]), }, }; diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 3fae08b82e27..5f0edd56566a 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -172,7 +172,7 @@ impl GlobalState { } } - if let Err(_) = self.fetch_workspace_error() { + if let Err(k) = self.fetch_workspace_error() { status.health = lsp_ext::Health::Error; message.push_str("Failed to load workspaces.\n\n"); } @@ -414,6 +414,8 @@ impl GlobalState { format!("{it}/**/*.rs"), format!("{it}/**/Cargo.toml"), format!("{it}/**/Cargo.lock"), + // FIXME @alibektas : WS may not have to be local for RATOML to be included. + format!("{it}/**/.rust-analyzer.toml"), ] }) }) @@ -526,6 +528,7 @@ impl GlobalState { change.set_crate_graph(crate_graph); self.analysis_host.apply_change(change); self.crate_graph_file_dependencies = crate_graph_file_dependencies; + eprintln!("Recreate crate graph"); self.process_changes(); self.reload_flycheck(); diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs index d59914298991..2053b8342c31 100644 --- a/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/crates/rust-analyzer/tests/slow-tests/main.rs @@ -1131,3 +1131,42 @@ version = "0.0.0" server.request::(Default::default(), json!([])); } + +#[test] +fn test_ratoml_exists() { + if skip_slow_tests() { + return; + } + + let server = Project::with_fixture( + r#" +//- /foo/Cargo.toml +[package] +name = "foo" +version = "0.0.0" + +[dependencies] +bar.path = "./bar" + +//- /foo/src/lib.rs +pub fn foo() {} + +//- /foo/src/abc/mod.rs +pub fn bar() {} + +//- /foo/.rust-analyzer.toml +ABC + +//- /foo/bar/Cargo.toml +[package] +name = "bar" +version = "0.0.0" + +//- /foo/bar/src/lib.rs +pub fn bar() {} +"#, + ) + .root("foo") + .server() + .wait_until_workspace_is_loaded(); +} diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs index 0392ef3cebe9..e8b32a15eeca 100644 --- a/crates/vfs/src/file_set.rs +++ b/crates/vfs/src/file_set.rs @@ -11,7 +11,7 @@ use rustc_hash::FxHashMap; use crate::{AnchoredPath, FileId, Vfs, VfsPath}; /// A set of [`VfsPath`]s identified by [`FileId`]s. -#[derive(Default, Clone, Eq, PartialEq)] +#[derive(Default, Debug, Clone, Eq, PartialEq)] pub struct FileSet { files: FxHashMap, paths: IntMap, @@ -59,11 +59,11 @@ impl FileSet { } } -impl fmt::Debug for FileSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FileSet").field("n_files", &self.files.len()).finish() - } -} +// impl fmt::Debug for FileSet { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// f.debug_struct("FileSet").field("n_files", &self.files.len()).finish() +// } +// } /// This contains path prefixes to partition a [`Vfs`] into [`FileSet`]s. /// diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 06004adad34a..0c8474f60753 100644 --- a/crates/vfs/src/lib.rs +++ b/crates/vfs/src/lib.rs @@ -157,8 +157,18 @@ impl Vfs { /// /// If the path does not currently exists in the `Vfs`, allocates a new /// [`FileId`] for it. - pub fn set_file_contents(&mut self, path: VfsPath, mut contents: Option>) -> bool { + pub fn set_file_contents(&mut self, path: VfsPath, contents: Option>) -> bool { let file_id = self.alloc_file_id(path); + self.set_file_id_contents(file_id, contents) + } + + /// Update the given `file_id` with the given `contents`. `None` means the file was deleted. + /// + /// Returns `true` if the file was modified, and saves the [change](ChangedFile). + /// + /// If the path does not currently exists in the `Vfs`, allocates a new + /// [`FileId`] for it. + pub fn set_file_id_contents(&mut self, file_id: FileId, mut contents: Option>) -> bool { let change_kind = match (self.get(file_id), &contents) { (None, None) => return false, (Some(old), Some(new)) if old == new => return false, @@ -196,7 +206,7 @@ impl Vfs { /// - Else, returns `path`'s id. /// /// Does not record a change. - fn alloc_file_id(&mut self, path: VfsPath) -> FileId { + pub fn alloc_file_id(&mut self, path: VfsPath) -> FileId { let file_id = self.interner.intern(path); let idx = file_id.0 as usize; let len = self.data.len().max(idx + 1); diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 7c76ae81bea0..0e05735ac21b 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -278,46 +278,46 @@ Enables completions of private items and fields that are defined in the current Default: ---- { - "Arc::new": { - "postfix": "arc", - "body": "Arc::new(${receiver})", - "requires": "std::sync::Arc", - "description": "Put the expression into an `Arc`", - "scope": "expr" - }, - "Rc::new": { - "postfix": "rc", - "body": "Rc::new(${receiver})", - "requires": "std::rc::Rc", - "description": "Put the expression into an `Rc`", - "scope": "expr" - }, - "Box::pin": { - "postfix": "pinbox", - "body": "Box::pin(${receiver})", - "requires": "std::boxed::Box", - "description": "Put the expression into a pinned `Box`", - "scope": "expr" - }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, - "Err": { - "postfix": "err", - "body": "Err(${receiver})", - "description": "Wrap the expression in a `Result::Err`", - "scope": "expr" - }, - "Some": { - "postfix": "some", - "body": "Some(${receiver})", - "description": "Wrap the expression in an `Option::Some`", - "scope": "expr" - } - } + "Arc::new": { + "postfix": "arc", + "body": "Arc::new(${receiver})", + "requires": "std::sync::Arc", + "description": "Put the expression into an `Arc`", + "scope": "expr" + }, + "Rc::new": { + "postfix": "rc", + "body": "Rc::new(${receiver})", + "requires": "std::rc::Rc", + "description": "Put the expression into an `Rc`", + "scope": "expr" + }, + "Box::pin": { + "postfix": "pinbox", + "body": "Box::pin(${receiver})", + "requires": "std::boxed::Box", + "description": "Put the expression into a pinned `Box`", + "scope": "expr" + }, + "Ok": { + "postfix": "ok", + "body": "Ok(${receiver})", + "description": "Wrap the expression in a `Result::Ok`", + "scope": "expr" + }, + "Err": { + "postfix": "err", + "body": "Err(${receiver})", + "description": "Wrap the expression in a `Result::Err`", + "scope": "expr" + }, + "Some": { + "postfix": "some", + "body": "Some(${receiver})", + "description": "Wrap the expression in an `Option::Some`", + "scope": "expr" + } +} ---- Custom completion snippets.