diff --git a/Cargo.lock b/Cargo.lock index c7cf4479b3..64c7762f41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -781,6 +781,7 @@ checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -1594,6 +1595,7 @@ dependencies = [ "ide", "ide-db", "ide-ssr", + "indexmap", "itertools", "load-cargo", "lsp-server 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1622,6 +1624,7 @@ dependencies = [ "test-fixture", "test-utils", "tikv-jemallocator", + "toml", "toolchain", "tracing", "tracing-subscriber", @@ -1775,6 +1778,15 @@ dependencies = [ "syn", ] +[[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.7" @@ -2025,6 +2037,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", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "toolchain" version = "0.0.0" @@ -2401,6 +2447,15 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[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.4" diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 1924ce578a..6a8ab71624 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -19,6 +19,10 @@ use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath}; // Map from crate id to the name of the crate and path of the proc-macro. If the value is `None`, // then the crate for the proc-macro hasn't been build yet as the build data is missing. pub type ProcMacroPaths = FxHashMap, AbsPathBuf), String>>; + +#[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 @@ -26,9 +30,6 @@ pub type ProcMacroPaths = FxHashMap, AbsPathBuf) /// 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. diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index e13060e4d7..e816fdcf52 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -64,7 +64,7 @@ use hir::ChangeWithProcMacros; use ide_db::{ base_db::{ salsa::{self, ParallelDatabase}, - CrateOrigin, Env, FileLoader, FileSet, SourceDatabase, VfsPath, + CrateOrigin, Env, FileLoader, FileSet, SourceDatabase, SourceDatabaseExt, VfsPath, }, prime_caches, symbol_index, FxHashMap, FxIndexSet, LineIndexDatabase, }; @@ -271,6 +271,10 @@ impl Analysis { self.with_db(|db| status::status(db, file_id)) } + pub fn source_root(&self, file_id: FileId) -> Cancellable { + self.with_db(|db| db.file_source_root(file_id)) + } + pub fn parallel_prime_caches(&self, num_worker_threads: u8, cb: F) -> Cancellable<()> where F: Fn(ParallelPrimeCachesProgress) + Sync + std::panic::UnwindSafe, @@ -280,7 +284,7 @@ impl Analysis { /// Gets the text of the source file. pub fn file_text(&self, file_id: FileId) -> Cancellable> { - self.with_db(|db| db.file_text(file_id)) + self.with_db(|db| SourceDatabaseExt::file_text(db, file_id)) } /// Gets the syntax tree of the file. @@ -290,7 +294,6 @@ impl Analysis { /// Returns true if this file belongs to an immutable library. pub fn is_library_file(&self, file_id: FileId) -> Cancellable { - use ide_db::base_db::SourceDatabaseExt; self.with_db(|db| db.source_root(db.file_source_root(file_id)).is_library) } diff --git a/crates/project-model/src/cfg_flag.rs b/crates/project-model/src/cfg_flag.rs index af682904b1..6192e18da0 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 1872d27c56..a8e30d2267 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -52,7 +52,7 @@ use base_db::{CrateDisplayName, CrateName}; use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::FxHashMap; -use serde::{de, Deserialize}; +use serde::{de, Deserialize, Serialize}; use span::Edition; use crate::cfg_flag::CfgFlag; @@ -161,14 +161,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: Utf8PathBuf, @@ -190,7 +190,7 @@ struct CrateData { repository: Option, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename = "edition")] enum EditionData { #[serde(rename = "2015")] @@ -218,20 +218,21 @@ impl From for Edition { /// /// This will differ from `CrateId` when multiple `ProjectJson` /// workspaces are loaded. -#[derive(Deserialize, Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq, Hash)] #[serde(transparent)] pub struct CrateArrayIdx(pub usize); -#[derive(Deserialize, Debug, Clone, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] pub(crate) struct Dep { /// Identifies a crate by position in the crates array. #[serde(rename = "crate")] pub(crate) krate: CrateArrayIdx, + #[serde(serialize_with = "serialize_crate_name")] #[serde(deserialize_with = "deserialize_crate_name")] pub(crate) name: CrateName, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct CrateSource { include_dirs: Vec, exclude_dirs: Vec, @@ -244,3 +245,10 @@ where let name = String::deserialize(de)?; CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {err:?}"))) } + +fn serialize_crate_name(name: &CrateName, se: S) -> Result +where + S: serde::Serializer, +{ + se.serialize_str(name) +} diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 6d70124188..fdb0313e22 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -39,11 +39,13 @@ tracing.workspace = true tracing-subscriber.workspace = true tracing-tree.workspace = true triomphe.workspace = true +toml = "0.8.8" nohash-hasher.workspace = true always-assert = "0.2.0" walkdir = "2.3.2" semver.workspace = true memchr = "2.7.1" +indexmap = { version = "2.0.0", features = ["serde"] } cfg.workspace = true flycheck.workspace = true diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 5890af17eb..b1e66c629e 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -6,7 +6,7 @@ //! Of particular interest is the `feature_flags` hash map: while other fields //! configure the server itself, feature flags are passed into analysis, and //! tweak things like automatic insertion of `()` in completions. - +#![allow(dead_code)] use std::{fmt, iter, ops::Not}; use cfg::{CfgAtom, CfgDiff}; @@ -15,12 +15,13 @@ use ide::{ AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, - Snippet, SnippetScope, + Snippet, SnippetScope, SourceRootId, }; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, SnippetCap, }; +use indexmap::IndexMap; use itertools::Itertools; use lsp_types::{ClientCapabilities, MarkupKind}; use paths::{Utf8Path, Utf8PathBuf}; @@ -29,7 +30,7 @@ use project_model::{ }; use rustc_hash::{FxHashMap, FxHashSet}; use semver::Version; -use serde::{de::DeserializeOwned, Deserialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use stdx::format_to_acc; use vfs::{AbsPath, AbsPathBuf}; @@ -59,38 +60,45 @@ 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 { + /// Configs that apply on a workspace-wide scope. There are 3 levels on which a global configuration can be configured + /// + /// 1. `rust-analyzer.toml` file under user's config directory (e.g ~/.config/rust-analyzer.toml) + /// 2. Client's own configurations (e.g `settings.json` on VS Code) + /// 3. `rust-analyzer.toml` file located at the workspace root + /// + /// A config is searched for by traversing a "config tree" in a bottom up fashion. It is chosen by the nearest first principle. + global: struct GlobalConfigData <- GlobalConfigInput -> { /// 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, /// Pass `--all-targets` to cargo invocation. - cargo_allTargets: bool = "true", + cargo_allTargets: bool = true, /// 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 @@ -109,81 +117,81 @@ config_data! { /// cargo check --quiet --workspace --message-format=json --all-targets /// ``` /// . - cargo_buildScripts_overrideCommand: Option> = "null", + cargo_buildScripts_overrideCommand: Option> = None, /// Rerun proc-macros building/build-scripts running when proc-macro /// or build-script sources change and are saved. - cargo_buildScripts_rebuildOnSave: bool = "true", + cargo_buildScripts_rebuildOnSave: bool = true, /// 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_owned()), /// Whether to run cargo metadata on the sysroot library allowing rust-analyzer to analyze /// third-party dependencies of the standard libraries. /// /// This will cause `cargo` to create a lockfile in your sysroot directory. rust-analyzer /// will attempt to clean up afterwards, but nevertheless requires the location to be /// writable to. - cargo_sysrootQueryMetadata: bool = "false", + cargo_sysrootQueryMetadata: bool = false, /// 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, /// Optional path to a rust-analyzer specific target directory. /// This prevents rust-analyzer's `cargo check` and initial build-script and proc-macro /// building 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. - cargo_targetDir | rust_analyzerTargetDir: Option = "null", + cargo_targetDir | rust_analyzerTargetDir: Option = None, /// Unsets the implicit `#[cfg(test)]` for the specified crates. - cargo_unsetTest: Vec = "[\"core\"]", + cargo_unsetTest: Vec = vec!["core".to_owned()], /// 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`). Defaults to /// `#rust-analyzer.cargo.allTargets#`. - check_allTargets | checkOnSave_allTargets: Option = "null", + check_allTargets | checkOnSave_allTargets: Option = None, /// Cargo command to use for `cargo check`. - check_command | checkOnSave_command: String = "\"check\"", + check_command | checkOnSave_command: String = "check".to_owned(), /// 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 @@ -191,16 +199,16 @@ config_data! { /// - "root": run checks in the project's root directory. /// This config only has an effect when `#rust-analyzer.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.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 @@ -228,37 +236,230 @@ 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, /// Whether `--workspace` should be passed to `cargo check`. /// If false, `-p ` will be passed instead. - check_workspace: bool = "true", + check_workspace: bool = true, + /// 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(), + /// Whether to run additional style lints. + diagnostics_styleLints_enable: bool = false, + /// 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, + + /// Whether to show `Debug` action. Only applies when + /// `#rust-analyzer.hover.actions.enable#` is set. + hover_actions_debug_enable: bool = true, + /// Whether to show HoverActions in Rust files. + 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, + /// Whether to show `Implementations` action. Only applies when + /// `#rust-analyzer.hover.actions.enable#` is set. + 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, + /// Whether to show `Run` action. Only applies when + /// `#rust-analyzer.hover.actions.enable#` is set. + hover_actions_run_enable: bool = true, + + /// Whether to show documentation on hover. + 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, + /// Use markdown syntax for links on hover. + hover_links_enable: bool = true, + /// How to render the align information in a memory layout hover. + hover_memoryLayout_alignment: Option = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), + /// Whether to show memory layout data on hover. + hover_memoryLayout_enable: bool = true, + /// How to render the niche information in a memory layout hover. + hover_memoryLayout_niches: Option = Some(false), + /// How to render the offset information in a memory layout hover. + hover_memoryLayout_offset: Option = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), + /// How to render the size information in a memory layout hover. + hover_memoryLayout_size: Option = Some(MemoryLayoutHoverRenderKindDef::Both), + + /// How many fields of a struct to display when hovering a struct. + hover_show_structFields: Option = None, + /// How many associated items of a trait to display when hovering a trait. + hover_show_traitAssocItems: Option = None, + + /// 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, + + /// Whether to send an UnindexedProject notification to the client. + notifications_unindexedProject: bool = false, + + /// 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, + + /// Exclude tests from find-all-references. + references_excludeTests: 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![], + + /// 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 configurations can be overridden for every crate by placing a `rust-analyzer.toml` on crate root. + /// A config is searched for by traversing a "config tree" in a bottom up fashion. It is chosen by the nearest first principle. + local: struct LocalConfigData <- LocalConfigInput -> { /// 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})", @@ -298,323 +499,139 @@ config_data! { "description": "Wrap the expression in an `Option::Some`", "scope": "expr" } - }"#, + }"#).unwrap(), /// Whether to enable term search based snippets like `Some(foo.bar().baz())`. - completion_termSearch_enable: bool = "false", - - /// 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 = "{}", - /// Whether to run additional style lints. - diagnostics_styleLints_enable: bool = "false", - /// 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\"", + completion_termSearch_enable: bool = false, /// 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", - - /// Whether to show `Debug` action. Only applies when - /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_debug_enable: bool = "true", - /// Whether to show HoverActions in Rust files. - 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", - /// Whether to show `Implementations` action. Only applies when - /// `#rust-analyzer.hover.actions.enable#` is set. - 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", - /// Whether to show `Run` action. Only applies when - /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_run_enable: bool = "true", - - /// Whether to show documentation on hover. - 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", - /// Use markdown syntax for links on hover. - hover_links_enable: bool = "true", - /// How to render the align information in a memory layout hover. - hover_memoryLayout_alignment: Option = "\"hexadecimal\"", - /// Whether to show memory layout data on hover. - hover_memoryLayout_enable: bool = "true", - /// How to render the niche information in a memory layout hover. - hover_memoryLayout_niches: Option = "false", - /// How to render the offset information in a memory layout hover. - hover_memoryLayout_offset: Option = "\"hexadecimal\"", - /// How to render the size information in a memory layout hover. - hover_memoryLayout_size: Option = "\"both\"", - - /// How many fields of a struct to display when hovering a struct. - hover_show_structFields: Option = "null", - /// How many associated items of a trait to display when hovering a trait. - hover_show_traitAssocItems: Option = "null", + highlightRelated_yieldPoints_enable: bool = true, /// 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_preferNoStd | imports_prefer_no_std: bool = "false", - /// Whether to prefer import paths containing a `prelude` module. - imports_preferPrelude: bool = "false", + imports_preferNoStd | imports_prefer_no_std: bool = false, + /// Whether to prefer import paths containing a `prelude` module. + imports_preferPrelude: 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 implicit drop hints. - inlayHints_implicitDrops_enable: bool = "false", + inlayHints_implicitDrops_enable: bool = false, /// 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 exclusive range inlay hints. - inlayHints_rangeExclusiveHints_enable: bool = "false", + inlayHints_rangeExclusiveHints_enable: bool = false, /// 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", - - /// Whether to send an UnindexedProject notification to the client. - notifications_unindexedProject: bool = "false", - - /// 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", - - /// Exclude tests from find-all-references. - references_excludeTests: 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 = "[]", - - /// 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", - - /// Show full signature of the callable. Only shows parameters if disabled. - signatureInfo_detail: 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 = "\"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\"", + semanticHighlighting_strings_enable: bool = true, } } -impl Default for ConfigData { - fn default() -> Self { - ConfigData::from_json(serde_json::Value::Null, &mut Vec::new()) - } +config_data! { + /// Configs that only make sense when they are set by a client. As such they can only be defined + /// by setting them using client's settings (e.g `settings.json` on VS Code). + client: struct ClientConfigData <- ClientConfigInput -> {} } #[derive(Debug, Clone)] @@ -624,10 +641,37 @@ pub struct Config { workspace_roots: Vec, caps: lsp_types::ClientCapabilities, root_path: AbsPathBuf, - data: ConfigData, detached_files: Vec, snippets: Vec, visual_studio_code_version: Option, + + default_config: ConfigData, + client_config: ConfigInput, + user_config: ConfigInput, + ratoml_files: FxHashMap, +} + +#[derive(Clone, Debug)] +struct RatomlNode { + node: ConfigInput, + parent: Option, +} + +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() + }; } type ParallelCachePrimingNumThreads = u8; @@ -675,7 +719,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, @@ -830,13 +874,16 @@ impl Config { ) -> Self { Config { caps, - data: ConfigData::default(), detached_files: Vec::new(), discovered_projects: Vec::new(), root_path, snippets: Default::default(), workspace_roots, visual_studio_code_version, + client_config: ConfigInput::default(), + user_config: ConfigInput::default(), + ratoml_files: FxHashMap::default(), + default_config: ConfigData::default(), } } @@ -866,15 +913,19 @@ 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); + self.client_config = ConfigInput::from_json(json, &mut errors); + tracing::debug!(?self.client_config, "deserialized config data"); self.snippets.clear(); - for (name, def) in self.data.completion_snippets_custom.iter() { + + let snips = self.completion_snippets_custom(None).to_owned(); + + for (name, def) in snips.iter() { if def.prefix.is_empty() && def.postfix.is_empty() { continue; } @@ -912,7 +963,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.check_command().is_empty() { error_sink.push(( "/check/command".to_owned(), serde_json::Error::custom("expected a non-empty string"), @@ -921,7 +972,7 @@ impl Config { } pub fn json_schema() -> serde_json::Value { - ConfigData::json_schema() + ConfigInput::json_schema() } pub fn root_path(&self) -> &AbsPathBuf { @@ -937,44 +988,302 @@ 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 assist(&self, source_root: Option) -> AssistConfig { + AssistConfig { + snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), + allowed: None, + insert_use: self.insert_use_config(source_root), + prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), + assist_emit_must_use: self.assist_emitMustUse().to_owned(), + prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), + } + } + + pub fn completion(&self, source_root: Option) -> CompletionConfig { + CompletionConfig { + enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(), + enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned() + && completion_item_edit_resolve(&self.caps), + enable_self_on_the_fly: self.completion_autoself_enable(source_root).to_owned(), + enable_private_editable: self.completion_privateEditable_enable(source_root).to_owned(), + full_function_signatures: self + .completion_fullFunctionSignatures_enable(source_root) + .to_owned(), + callable: match self.completion_callable_snippets(source_root) { + CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), + CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), + CallableCompletionDef::None => None, + }, + insert_use: self.insert_use_config(source_root), + prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), + 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.completion_limit(source_root).to_owned(), + enable_term_search: self.completion_termSearch_enable(source_root).to_owned(), + prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), + } + } + + pub fn diagnostics(&self, source_root: Option) -> DiagnosticsConfig { + DiagnosticsConfig { + enabled: *self.diagnostics_enable(), + proc_attr_macros_enabled: self.expand_proc_attr_macros(), + proc_macros_enabled: *self.procMacro_enable(), + disable_experimental: !self.diagnostics_experimental_enable(), + disabled: self.diagnostics_disabled().clone(), + expr_fill_default: match self.assist_expressionFillDefault() { + ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, + ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, + }, + insert_use: self.insert_use_config(source_root), + prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), + prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), + style_lints: self.diagnostics_styleLints_enable().to_owned(), + } + } + pub fn expand_proc_attr_macros(&self) -> bool { + self.procMacro_enable().to_owned() && self.procMacro_attributes_enable().to_owned() + } + + pub fn highlight_related(&self, source_root: Option) -> HighlightRelatedConfig { + HighlightRelatedConfig { + references: self.highlightRelated_references_enable(source_root).to_owned(), + break_points: self.highlightRelated_breakPoints_enable(source_root).to_owned(), + exit_points: self.highlightRelated_exitPoints_enable(source_root).to_owned(), + yield_points: self.highlightRelated_yieldPoints_enable(source_root).to_owned(), + closure_captures: self.highlightRelated_closureCaptures_enable(source_root).to_owned(), + } + } + + pub fn hover_actions(&self) -> HoverActionsConfig { + let enable = self.experimental("hoverActions") && self.hover_actions_enable().to_owned(); + HoverActionsConfig { + implementations: enable && self.hover_actions_implementations_enable().to_owned(), + references: enable && self.hover_actions_references_enable().to_owned(), + run: enable && self.hover_actions_run_enable().to_owned(), + debug: enable && self.hover_actions_debug_enable().to_owned(), + goto_type_def: enable && self.hover_actions_gotoTypeDef_enable().to_owned(), + } + } + + 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.hover_links_enable().to_owned(), + memory_layout: self.hover_memoryLayout_enable().then_some(MemoryLayoutHoverConfig { + size: self.hover_memoryLayout_size().map(mem_kind), + offset: self.hover_memoryLayout_offset().map(mem_kind), + alignment: self.hover_memoryLayout_alignment().map(mem_kind), + niches: self.hover_memoryLayout_niches().unwrap_or_default(), + }), + documentation: self.hover_documentation_enable().to_owned(), + 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.hover_documentation_keywords_enable().to_owned(), + max_trait_assoc_items_count: self.hover_show_traitAssocItems().to_owned(), + max_struct_field_count: self.hover_show_structFields().to_owned(), + } + } + + pub fn inlay_hints(&self, source_root: Option) -> 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.inlayHints_renderColons(source_root).to_owned(), + type_hints: self.inlayHints_typeHints_enable(source_root).to_owned(), + parameter_hints: self.inlayHints_parameterHints_enable(source_root).to_owned(), + chaining_hints: self.inlayHints_chainingHints_enable(source_root).to_owned(), + discriminant_hints: match self.inlayHints_discriminantHints_enable(source_root) { + DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, + DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, + DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless, + }, + closure_return_type_hints: match self + .inlayHints_closureReturnTypeHints_enable(source_root) + { + ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always, + ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never, + ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock, + }, + lifetime_elision_hints: match self.inlayHints_lifetimeElisionHints_enable(source_root) { + LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always, + LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never, + LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial, + }, + hide_named_constructor_hints: self + .inlayHints_typeHints_hideNamedConstructor(source_root) + .to_owned(), + hide_closure_initialization_hints: self + .inlayHints_typeHints_hideClosureInitialization(source_root) + .to_owned(), + closure_style: match self.inlayHints_closureStyle(source_root) { + ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn, + ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation, + ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId, + ClosureStyle::Hide => hir::ClosureStyle::Hide, + }, + closure_capture_hints: self + .inlayHints_closureCaptureHints_enable(source_root) + .to_owned(), + adjustment_hints: match self.inlayHints_expressionAdjustmentHints_enable(source_root) { + AdjustmentHintsDef::Always => ide::AdjustmentHints::Always, + AdjustmentHintsDef::Never => { + match self.inlayHints_reborrowHints_enable(source_root) { + ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { + ide::AdjustmentHints::ReborrowOnly + } + ReborrowHintsDef::Never => ide::AdjustmentHints::Never, + } + } + AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly, + }, + adjustment_hints_mode: match self.inlayHints_expressionAdjustmentHints_mode(source_root) + { + 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 + .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe(source_root) + .to_owned(), + binding_mode_hints: self.inlayHints_bindingModeHints_enable(source_root).to_owned(), + param_names_for_lifetime_elision_hints: self + .inlayHints_lifetimeElisionHints_useParameterNames(source_root) + .to_owned(), + max_length: self.inlayHints_maxLength(source_root).to_owned(), + closing_brace_hints_min_lines: if self + .inlayHints_closingBraceHints_enable(source_root) + .to_owned() + { + Some(self.inlayHints_closingBraceHints_minLines(source_root).to_owned()) + } 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"), + }, + implicit_drop_hints: self.inlayHints_implicitDrops_enable(source_root).to_owned(), + range_exclusive_hints: self + .inlayHints_rangeExclusiveHints_enable(source_root) + .to_owned(), + } + } + + fn insert_use_config(&self, source_root: Option) -> InsertUseConfig { + InsertUseConfig { + granularity: match self.imports_granularity_group(source_root) { + ImportGranularityDef::Preserve => ImportGranularity::Preserve, + ImportGranularityDef::Item => ImportGranularity::Item, + ImportGranularityDef::Crate => ImportGranularity::Crate, + ImportGranularityDef::Module => ImportGranularity::Module, + ImportGranularityDef::One => ImportGranularity::One, + }, + enforce_granularity: self.imports_granularity_enforce(source_root).to_owned(), + prefix_kind: match self.imports_prefix(source_root) { + ImportPrefixDef::Plain => PrefixKind::Plain, + ImportPrefixDef::ByCrate => PrefixKind::ByCrate, + ImportPrefixDef::BySelf => PrefixKind::BySelf, + }, + group: self.imports_group_enable(source_root).to_owned(), + skip_glob_imports: !self.imports_merge_glob(source_root), + } + } + + pub fn join_lines(&self, source_root: Option) -> JoinLinesConfig { + JoinLinesConfig { + join_else_if: self.joinLines_joinElseIf(source_root).to_owned(), + remove_trailing_comma: self.joinLines_removeTrailingComma(source_root).to_owned(), + unwrap_trivial_blocks: self.joinLines_unwrapTrivialBlock(source_root).to_owned(), + join_assignments: self.joinLines_joinAssignments(source_root).to_owned(), + } + } + + pub fn highlighting_non_standard_tokens(&self, source_root: Option) -> bool { + self.semanticHighlighting_nonStandardTokens(source_root).to_owned() + } + + pub fn highlighting_config(&self, source_root: Option) -> HighlightConfig { + HighlightConfig { + strings: self.semanticHighlighting_strings_enable(source_root).to_owned(), + punctuation: self.semanticHighlighting_punctuation_enable(source_root).to_owned(), + specialize_punctuation: self + .semanticHighlighting_punctuation_specialization_enable(source_root) + .to_owned(), + macro_bang: self + .semanticHighlighting_punctuation_separate_macro_bang(source_root) + .to_owned(), + operator: self.semanticHighlighting_operator_enable(source_root).to_owned(), + specialize_operator: self + .semanticHighlighting_operator_specialization_enable(source_root) + .to_owned(), + inject_doc_comment: self + .semanticHighlighting_doc_comment_inject_enable(source_root) + .to_owned(), + syntactic_name_ref_highlighting: false, + } + } + pub fn has_linked_projects(&self) -> bool { - !self.data.linkedProjects.is_empty() + !self.linkedProjects().is_empty() } pub fn linked_manifests(&self) -> impl Iterator + '_ { - self.data.linkedProjects.iter().filter_map(|it| match it { + self.linkedProjects().iter().filter_map(|it| match it { ManifestOrProjectJson::Manifest(p) => Some(&**p), ManifestOrProjectJson::ProjectJson(_) => None, }) } pub fn has_linked_project_jsons(&self) -> bool { - self.data - .linkedProjects - .iter() - .any(|it| matches!(it, ManifestOrProjectJson::ProjectJson(_))) + self.linkedProjects().iter().any(|it| matches!(it, ManifestOrProjectJson::ProjectJson(_))) } pub fn linked_or_discovered_projects(&self) -> Vec { - match self.data.linkedProjects.as_slice() { + match self.linkedProjects().as_slice() { [] => { let exclude_dirs: Vec<_> = - self.data.files_excludeDirs.iter().map(|p| self.root_path.join(p)).collect(); + self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect(); self.discovered_projects .iter() .filter( @@ -1028,7 +1337,7 @@ impl Config { } pub fn prefill_caches(&self) -> bool { - self.data.cachePriming_enable + self.cachePriming_enable().to_owned() } pub fn location_link(&self) -> bool { @@ -1165,117 +1474,95 @@ 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_preferNoStd, - prefer_prelude: self.data.imports_preferPrelude, - style_lints: self.data.diagnostics_styleLints_enable, - } + self.diagnostics_enable().to_owned() } 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.diagnostics_remapPrefix().clone(), + warnings_as_info: self.diagnostics_warningsAsInfo().clone(), + warnings_as_hint: self.diagnostics_warningsAsHint().clone(), + check_ignore: self.check_ignore().clone(), } } pub fn extra_args(&self) -> &Vec { - &self.data.cargo_extraArgs + self.cargo_extraArgs() } pub fn extra_env(&self) -> &FxHashMap { - &self.data.cargo_extraEnv + self.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.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.cargo_extraEnv().clone(); + extra_env.extend(self.check_extraEnv().clone()); extra_env } pub fn lru_parse_query_capacity(&self) -> Option { - self.data.lru_capacity + self.lru_capacity().to_owned() } - pub fn lru_query_capacities(&self) -> Option<&FxHashMap, usize>> { - self.data.lru_query_capacities.is_empty().not().then_some(&self.data.lru_query_capacities) + pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap, usize>> { + self.lru_query_capacities().is_empty().not().then(|| self.lru_query_capacities()) } pub fn proc_macro_srv(&self) -> Option { - let path = self.data.procMacro_server.clone()?; + let path = self.procMacro_server().clone()?; Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path))) } pub fn ignored_proc_macros(&self) -> &FxHashMap, Box<[Box]>> { - &self.data.procMacro_ignored + self.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.procMacro_enable().to_owned() } pub fn files(&self) -> FilesConfig { FilesConfig { - watcher: match self.data.files_watcher { + watcher: match self.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.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, - unindexed_project: self.data.notifications_unindexedProject, + cargo_toml_not_found: self.notifications_cargoTomlNotFound().to_owned(), + unindexed_project: self.notifications_unindexedProject().to_owned(), } } - pub fn cargo_autoreload(&self) -> bool { - self.data.cargo_autoreload + pub fn cargo_autoreload_config(&self) -> bool { + self.cargo_autoreload().to_owned() } pub fn run_build_scripts(&self) -> bool { - self.data.cargo_buildScripts_enable || self.data.procMacro_enable + self.cargo_buildScripts_enable().to_owned() || self.procMacro_enable().to_owned() } pub fn cargo(&self) -> CargoConfig { - let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| { + let rustc_source = self.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.cargo_sysroot().as_ref().map(|sysroot| { if sysroot == "discover" { RustLibSource::Discover } else { @@ -1283,27 +1570,26 @@ impl Config { } }); let sysroot_src = - self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot)); - let sysroot_query_metadata = self.data.cargo_sysrootQueryMetadata; + self.cargo_sysrootSrc().as_ref().map(|sysroot| self.root_path.join(sysroot)); + let sysroot_query_metadata = self.cargo_sysrootQueryMetadata(); CargoConfig { - all_targets: self.data.cargo_allTargets, - features: match &self.data.cargo_features { + all_targets: *self.cargo_allTargets(), + features: match &self.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.cargo_noDefaultFeatures().to_owned(), }, }, - target: self.data.cargo_target.clone(), + target: self.cargo_target().clone(), sysroot, - sysroot_query_metadata, + sysroot_query_metadata: *sysroot_query_metadata, sysroot_src, rustc_source, cfg_overrides: project_model::CfgOverrides { global: CfgDiff::new( - self.data - .cargo_cfgs + self.cargo_cfgs() .iter() .map(|(key, val)| { if val.is_empty() { @@ -1317,8 +1603,7 @@ impl Config { ) .unwrap(), selective: self - .data - .cargo_unsetTest + .cargo_unsetTest() .iter() .map(|it| { ( @@ -1328,49 +1613,49 @@ 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.cargo_buildScripts_useRustcWrapper(), + invocation_strategy: match self.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.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.cargo_buildScripts_overrideCommand().clone(), + extra_args: self.cargo_extraArgs().clone(), + extra_env: self.cargo_extraEnv().clone(), target_dir: self.target_dir_from_config(), } } pub fn rustfmt(&self) -> RustfmtConfig { - match &self.data.rustfmt_overrideCommand { + match &self.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, + extra_args: self.rustfmt_extraArgs().clone(), + enable_range_formatting: *self.rustfmt_rangeFormatting_enable(), }, } } pub fn flycheck_workspace(&self) -> bool { - self.data.check_workspace + *self.check_workspace() } pub fn cargo_test_options(&self) -> CargoOptions { CargoOptions { - target_triples: self.data.cargo_target.clone().into_iter().collect(), + target_triples: self.cargo_target().clone().into_iter().collect(), all_targets: false, - no_default_features: self.data.cargo_noDefaultFeatures, - all_features: matches!(self.data.cargo_features, CargoFeaturesDef::All), - features: match self.data.cargo_features.clone() { + no_default_features: *self.cargo_noDefaultFeatures(), + all_features: matches!(self.cargo_features(), CargoFeaturesDef::All), + features: match self.cargo_features().clone() { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, }, @@ -1381,7 +1666,7 @@ impl Config { } pub fn flycheck(&self) -> FlycheckConfig { - match &self.data.check_overrideCommand { + match &self.check_overrideCommand() { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); @@ -1389,13 +1674,13 @@ impl Config { command, args, extra_env: self.check_extra_env(), - invocation_strategy: match self.data.check_invocationStrategy { + invocation_strategy: match self.check_invocationStrategy() { InvocationStrategy::Once => flycheck::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => { flycheck::InvocationStrategy::PerWorkspace } }, - invocation_location: match self.data.check_invocationLocation { + invocation_location: match self.check_invocationLocation() { InvocationLocation::Root => { flycheck::InvocationLocation::Root(self.root_path.clone()) } @@ -1404,31 +1689,28 @@ impl Config { } } Some(_) | None => FlycheckConfig::CargoCommand { - command: self.data.check_command.clone(), + command: self.check_command().clone(), options: CargoOptions { target_triples: self - .data - .check_targets + .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.unwrap_or(self.data.cargo_allTargets), + .unwrap_or_else(|| self.cargo_target().clone().into_iter().collect()), + all_targets: self.check_allTargets().unwrap_or(*self.cargo_allTargets()), no_default_features: self - .data - .check_noDefaultFeatures - .unwrap_or(self.data.cargo_noDefaultFeatures), + .check_noDefaultFeatures() + .unwrap_or(*self.cargo_noDefaultFeatures()), all_features: matches!( - self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features), + self.check_features().as_ref().unwrap_or(self.cargo_features()), CargoFeaturesDef::All ), features: match self - .data - .check_features + .check_features() .clone() - .unwrap_or_else(|| self.data.cargo_features.clone()) + .unwrap_or_else(|| self.cargo_features().clone()) { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, @@ -1443,7 +1725,7 @@ impl Config { } fn target_dir_from_config(&self) -> Option { - self.data.cargo_targetDir.as_ref().and_then(|target_dir| match target_dir { + self.cargo_targetDir().as_ref().and_then(|target_dir| match target_dir { TargetDirectory::UseSubdirectory(true) => { Some(Utf8PathBuf::from("target/rust-analyzer")) } @@ -1454,294 +1736,66 @@ impl Config { } pub fn check_on_save(&self) -> bool { - self.data.checkOnSave + *self.checkOnSave() } pub fn script_rebuild_on_save(&self) -> bool { - self.data.cargo_buildScripts_rebuildOnSave + *self.cargo_buildScripts_rebuildOnSave() } 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, - implicit_drop_hints: self.data.inlayHints_implicitDrops_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 - }, - range_exclusive_hints: self.data.inlayHints_rangeExclusiveHints_enable, - 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, - ImportGranularityDef::One => ImportGranularity::One, - }, - 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, - enable_term_search: self.data.completion_termSearch_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, - }, - insert_use: self.insert_use_config(), - prefer_no_std: self.data.imports_preferNoStd, - prefer_prelude: self.data.imports_preferPrelude, - 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, + override_cargo: self.runnables_command().clone(), + cargo_extra_args: self.runnables_extraArgs().clone(), } } pub fn find_all_refs_exclude_imports(&self) -> bool { - self.data.references_excludeImports + *self.references_excludeImports() } pub fn find_all_refs_exclude_tests(&self) -> bool { - self.data.references_excludeTests + *self.references_excludeTests() } 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_preferNoStd, - prefer_prelude: self.data.imports_preferPrelude, - assist_emit_must_use: self.data.assist_emitMustUse, - } - } - - 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, - } - } - pub fn call_info(&self) -> CallInfoConfig { CallInfoConfig { - params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters), - docs: self.data.signatureInfo_documentation_enable, + params_only: matches!(self.signatureInfo_detail(), SignatureDetail::Parameters), + docs: *self.signatureInfo_documentation_enable(), } } 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 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 highlighting_non_standard_tokens(&self) -> bool { - self.data.semanticHighlighting_nonStandardTokens - } - - 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 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, - max_trait_assoc_items_count: self.data.hover_show_traitAssocItems, - max_struct_field_count: self.data.hover_show_structFields, + run: *self.lens_run_enable(), + debug: *self.lens_enable() && *self.lens_debug_enable(), + interpret: *self.lens_enable() && *self.lens_run_enable() && *self.interpret_tests(), + implementations: *self.lens_enable() && *self.lens_implementations_enable(), + method_refs: *self.lens_enable() && *self.lens_references_method_enable(), + refs_adt: *self.lens_enable() && *self.lens_references_adt_enable(), + refs_trait: *self.lens_enable() && *self.lens_references_trait_enable(), + enum_variant_refs: *self.lens_enable() && *self.lens_references_enumVariant_enable(), + location: *self.lens_location(), } } pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig { WorkspaceSymbolConfig { - search_scope: match self.data.workspace_symbol_search_scope { + search_scope: match self.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.workspace_symbol_search_kind() { WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes, WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols, }, - search_limit: self.data.workspace_symbol_search_limit, + search_limit: *self.workspace_symbol_search_limit(), } } @@ -1775,7 +1829,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.lens_forceCustomCommands(); let commands = commands.map(|it| it.commands).unwrap_or_default(); let get = |name: &str| commands.iter().any(|it| it == name) || force; @@ -1789,29 +1843,19 @@ 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.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()) + self.numThreads().unwrap_or(num_cpus::get_physical()) } pub fn typing_autoclose_angle(&self) -> bool { - self.data.typing_autoClosingAngleBrackets_enable + *self.typing_autoClosingAngleBrackets_enable() } // VSCode is our reference implementation, so we allow ourselves to work around issues by @@ -1822,100 +1866,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 = (); + 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 fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str(concat!( - stringify!($bool), - " or \"", - stringify!($string), - "\"" - )) - } + 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_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_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_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); @@ -1927,7 +1991,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")] #[derive(Default)] enum SnippetScopeDef { @@ -1937,67 +2001,92 @@ enum SnippetScopeDef { Type, } -#[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(super) 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 expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("string or array of strings") + } + + 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)) + } } - 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(super) fn serialize(vec: &[String], 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 { +pub(crate) enum ManifestOrProjectJson { Manifest(Utf8PathBuf), ProjectJson(ProjectJsonData), } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum ExprFillDefaultDef { +pub(crate) enum ExprFillDefaultDef { Todo, Default, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportGranularityDef { Preserve, @@ -2007,7 +2096,7 @@ enum ImportGranularityDef { One, } -#[derive(Deserialize, Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[serde(rename_all = "snake_case")] enum CallableCompletionDef { FillArguments, @@ -2015,54 +2104,54 @@ enum CallableCompletionDef { None, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] -enum CargoFeaturesDef { - #[serde(deserialize_with = "de_unit_v::all")] +pub(crate) enum CargoFeaturesDef { + #[serde(with = "unit_v::all")] All, Selected(Vec), } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum InvocationStrategy { +pub(crate) enum InvocationStrategy { Once, PerWorkspace, } -#[derive(Deserialize, Debug, Clone)] -struct CheckOnSaveTargets(#[serde(deserialize_with = "single_or_array")] Vec); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct CheckOnSaveTargets(#[serde(with = "single_or_array")] Vec); -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum InvocationLocation { +pub(crate) 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, @@ -2071,40 +2160,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)] #[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, @@ -2113,15 +2202,15 @@ enum AdjustmentHintsModeDef { PreferPostfix, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum FilesWatcherDef { +pub(crate) enum FilesWatcherDef { Client, Notify, Server, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportPrefixDef { Plain, @@ -2131,40 +2220,51 @@ enum ImportPrefixDef { ByCrate, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum WorkspaceSymbolSearchScopeDef { +pub(crate) enum WorkspaceSymbolSearchScopeDef { Workspace, WorkspaceAndDependencies, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum SignatureDetail { +pub(crate) enum SignatureDetail { Full, Parameters, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum WorkspaceSymbolSearchKindDef { +pub(crate) 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")] +pub(crate) 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 { @@ -2172,68 +2272,320 @@ pub enum TargetDirectory { Directory(Utf8PathBuf), } +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_owned() + }; + ($default:expr, $ty:ty) => {{ + let val = default_val!($default, $ty); + serde_json::to_string_pretty(&val).unwrap() + }}; +} + +macro_rules! _impl_for_config_data { + (local, $( + $(#[doc=$doc:literal])* + $field:ident : $ty:ty = $default:expr, + )* + ) => { + impl Config { + $( + $($doc)* + #[allow(non_snake_case)] + fn $field(&self, _source_root: Option) -> &$ty { + if let Some(v) = self.client_config.local.$field.as_ref() { + return &v; + } + + if let Some(v) = self.user_config.local.$field.as_ref() { + return &v; + } + + &self.default_config.local.$field + } + )* + } + }; + (global, $( + $(#[doc=$doc:literal])* + $field:ident : $ty:ty = $default:expr, + )* + ) => { + impl Config { + $( + $($doc)* + #[allow(non_snake_case)] + pub(crate) fn $field(&self) -> &$ty { + if let Some(v) = self.client_config.global.$field.as_ref() { + return &v; + } + + if let Some(v) = self.user_config.global.$field.as_ref() { + return &v; + } + + &self.default_config.global.$field + } + )* + } + }; + (client, $( + $(#[doc=$doc:literal])* + $field:ident : $ty:ty = $default:expr, + )* + ) => { + impl Config { + $( + $($doc)* + #[allow(non_snake_case)] + fn $field(&self) -> &$ty { + if let Some(v) = self.client_config.global.$field.as_ref() { + return &v; + } + + &self.default_config.client.$field + } + )* + } + }; +} + macro_rules! _config_data { - (struct $name:ident { + // modname is for the tests + ($(#[doc=$dox:literal])* $modname:ident: struct $name:ident <- $input: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 { + + impl_for_config_data!{ + $modname, + $( + $field : $ty = $default, + )* + } + + /// 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() + } + } + + 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, clippy::ptr_arg)] + 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, clippy::ptr_arg)] + 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; +use _impl_for_config_data as impl_for_config_data; + +#[derive(Default, Debug, Clone)] +struct ConfigData { + global: GlobalConfigData, + local: LocalConfigData, + 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 @@ -2254,12 +2606,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)| { @@ -2310,7 +2661,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! { @@ -2621,7 +2972,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().fold(String::new(), |mut acc, (field, _ty, doc, default)| { let name = format!("rust-analyzer.{}", field.replace('_', ".")); let doc = doc_comment_to_string(doc); @@ -2716,7 +3067,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); } @@ -2788,7 +3139,7 @@ mod tests { "rust": { "analyzerTargetDir": null } })) .unwrap(); - assert_eq!(config.data.cargo_targetDir, None); + assert_eq!(config.cargo_targetDir(), &None); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none()) ); @@ -2807,7 +3158,7 @@ mod tests { "rust": { "analyzerTargetDir": true } })) .unwrap(); - assert_eq!(config.data.cargo_targetDir, Some(TargetDirectory::UseSubdirectory(true))); + assert_eq!(config.cargo_targetDir(), &Some(TargetDirectory::UseSubdirectory(true))); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer"))) ); @@ -2827,8 +3178,8 @@ mod tests { })) .unwrap(); assert_eq!( - config.data.cargo_targetDir, - Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder"))) + config.cargo_targetDir(), + &Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder"))) ); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("other_folder"))) diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index a0a53f545c..65a9a49149 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -154,10 +154,12 @@ pub(crate) fn fetch_native_diagnostics( .copied() .filter_map(|file_id| { let line_index = snapshot.file_line_index(file_id).ok()?; + let source_root = snapshot.analysis.source_root(file_id).ok()?; + let diagnostics = snapshot .analysis .diagnostics( - &snapshot.config.diagnostics(), + &snapshot.config.diagnostics(Some(source_root)), ide::AssistResolveStrategy::None, file_id, ) diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 8516ffa0df..9cda0ed1a9 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -187,7 +187,7 @@ impl GlobalState { }; let mut analysis_host = AnalysisHost::new(config.lru_parse_query_capacity()); - if let Some(capacities) = config.lru_query_capacities() { + if let Some(capacities) = config.lru_query_capacities_config() { analysis_host.update_lru_capacities(capacities); } let (flycheck_sender, flycheck_receiver) = unbounded(); diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 77c4ad32cc..5e5861b14b 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -355,8 +355,9 @@ pub(crate) fn handle_join_lines( ) -> anyhow::Result> { let _p = tracing::span!(tracing::Level::INFO, "handle_join_lines").entered(); - let config = snap.config.join_lines(); let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let source_root = snap.analysis.source_root(file_id)?; + let config = snap.config.join_lines(Some(source_root)); let line_index = snap.file_line_index(file_id)?; let mut res = TextEdit::default(); @@ -923,7 +924,8 @@ 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 source_root = snap.analysis.source_root(position.file_id)?; + let completion_config = &snap.config.completion(Some(source_root)); let items = match snap.analysis.completions( completion_config, position, @@ -964,11 +966,12 @@ pub(crate) fn handle_completion_resolve( let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?; let line_index = snap.file_line_index(file_id)?; let offset = from_proto::offset(&line_index, resolve_data.position.position)?; + let source_root = snap.analysis.source_root(file_id)?; let additional_edits = snap .analysis .resolve_completion_edits( - &snap.config.completion(), + &snap.config.completion(Some(source_root)), FilePosition { file_id, offset }, resolve_data .imports @@ -1038,16 +1041,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 hover = snap.config.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( @@ -1191,11 +1195,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 source_root = snap.analysis.source_root(file_id)?; - let mut assists_config = snap.config.assist(); + let mut assists_config = snap.config.assist(Some(source_root)); assists_config.allowed = params .context .only @@ -1212,7 +1217,7 @@ pub(crate) fn handle_code_action( }; let assists = snap.analysis.assists_with_fixes( &assists_config, - &snap.config.diagnostics(), + &snap.config.diagnostics(Some(source_root)), resolve, frange, )?; @@ -1266,8 +1271,9 @@ pub(crate) fn handle_code_action_resolve( let line_index = snap.file_line_index(file_id)?; let range = from_proto::text_range(&line_index, params.code_action_params.range)?; let frange = FileRange { file_id, range }; + let source_root = snap.analysis.source_root(file_id)?; - let mut assists_config = snap.config.assist(); + let mut assists_config = snap.config.assist(Some(source_root)); assists_config.allowed = params .code_action_params .context @@ -1290,7 +1296,7 @@ pub(crate) fn handle_code_action_resolve( let assists = snap.analysis.assists_with_fixes( &assists_config, - &snap.config.diagnostics(), + &snap.config.diagnostics(Some(source_root)), AssistResolveStrategy::Single(assist_resolve), frange, )?; @@ -1419,8 +1425,12 @@ pub(crate) fn handle_document_highlight( let _p = tracing::span!(tracing::Level::INFO, "handle_document_highlight").entered(); let position = from_proto::file_position(&snap, params.text_document_position_params)?; let line_index = snap.file_line_index(position.file_id)?; + let source_root = snap.analysis.source_root(position.file_id)?; - let refs = match snap.analysis.highlight_related(snap.config.highlight_related(), position)? { + let refs = match snap + .analysis + .highlight_related(snap.config.highlight_related(Some(source_root)), position)? + { None => return Ok(None), Some(refs) => refs, }; @@ -1466,7 +1476,9 @@ 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 source_root = snap.analysis.source_root(file_id)?; + + let inlay_hints_config = snap.config.inlay_hints(Some(source_root)); Ok(Some( snap.analysis .inlay_hints(&inlay_hints_config, file_id, Some(range))? @@ -1501,7 +1513,9 @@ pub(crate) fn handle_inlay_hints_resolve( let line_index = snap.file_line_index(file_id)?; let hint_position = from_proto::offset(&line_index, original_hint.position)?; - let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(); + let source_root = snap.analysis.source_root(file_id)?; + + let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(Some(source_root)); forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty(); let resolve_hints = snap.analysis.inlay_hints_resolve( &forced_resolve_inlay_hints_config, @@ -1633,8 +1647,9 @@ pub(crate) fn handle_semantic_tokens_full( let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; + let source_root = snap.analysis.source_root(file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.highlighting_config(Some(source_root)); // 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; @@ -1645,7 +1660,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.highlighting_non_standard_tokens(Some(source_root)), ); // Unconditionally cache the tokens @@ -1663,8 +1678,9 @@ pub(crate) fn handle_semantic_tokens_full_delta( let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; + let source_root = snap.analysis.source_root(file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.highlighting_config(Some(source_root)); // 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; @@ -1675,7 +1691,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.highlighting_non_standard_tokens(Some(source_root)), ); let cached_tokens = snap.semantic_tokens_cache.lock().remove(¶ms.text_document.uri); @@ -1706,8 +1722,9 @@ pub(crate) fn handle_semantic_tokens_range( let frange = from_proto::file_range(&snap, ¶ms.text_document, params.range)?; let text = snap.analysis.file_text(frange.file_id)?; let line_index = snap.file_line_index(frange.file_id)?; + let source_root = snap.analysis.source_root(frange.file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.highlighting_config(Some(source_root)); // 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; @@ -1718,7 +1735,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.highlighting_non_standard_tokens(Some(source_root)), ); Ok(Some(semantic_tokens.into())) } @@ -1931,8 +1948,8 @@ 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.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 d8bb12528b..29185b717f 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -233,7 +233,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.completion(None).limit { res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text)); res.truncate(limit); } @@ -317,7 +317,7 @@ fn completion_item( set_score(&mut lsp_item, max_relevance, item.relevance); - if config.completion().enable_imports_on_the_fly && !item.import_to_add.is_empty() { + if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() { let imports = item .import_to_add .into_iter() diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 9459bd7c5d..37006cea49 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -434,7 +434,7 @@ impl GlobalState { } } - if self.config.cargo_autoreload() { + if self.config.cargo_autoreload_config() { if let Some((cause, force_crate_graph_reload)) = self.fetch_workspaces_queue.should_start_op() { diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index c4a42ce427..d2e495dbfc 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -76,9 +76,9 @@ impl GlobalState { if self.config.lru_parse_query_capacity() != old_config.lru_parse_query_capacity() { self.analysis_host.update_lru_capacity(self.config.lru_parse_query_capacity()); } - if self.config.lru_query_capacities() != old_config.lru_query_capacities() { + if self.config.lru_query_capacities_config() != old_config.lru_query_capacities_config() { self.analysis_host.update_lru_capacities( - &self.config.lru_query_capacities().cloned().unwrap_or_default(), + &self.config.lru_query_capacities_config().cloned().unwrap_or_default(), ); } if self.config.linked_or_discovered_projects() != old_config.linked_or_discovered_projects() diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 8f69895b2e..af4483a2cc 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -159,10 +159,17 @@ building from locking the `Cargo.lock` at the expense of duplicating build artif 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-analyzer.cargo.unsetTest]]rust-analyzer.cargo.unsetTest (default: `["core"]`):: +[[rust-analyzer.cargo.unsetTest]]rust-analyzer.cargo.unsetTest:: + -- +Default: +---- +[ + "core" +] +---- Unsets the implicit `#[cfg(test)]` for the specified crates. + -- [[rust-analyzer.checkOnSave]]rust-analyzer.checkOnSave (default: `true`):: + @@ -321,46 +328,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.