diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index ceb8534fdf..b92713129d 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -51,7 +51,7 @@ impl flags::Scip { // FIXME @alibektas : What happens to errors without logging? error!(?error_sink, "Config Error(s)"); } - let cargo_config = config.cargo(); + let cargo_config = config.cargo(None); let (db, vfs, _) = load_workspace_at( root.as_path().as_ref(), &cargo_config, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 2889af844b..c884216b3e 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -62,12 +62,10 @@ 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! { - /// Configs that apply on a workspace-wide scope. There are 3 levels on which a global configuration can be configured - // FIXME: 1. and 3. should be split, some configs do not make sense per project + /// Configs that apply on a workspace-wide scope. There are 2 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) + /// 1. `rust-analyzer.toml` file under user's config directory (e.g ~/.config/rust-analyzer/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 GlobalDefaultConfigData <- GlobalConfigInput -> { @@ -76,178 +74,7 @@ config_data! { /// How many worker threads to handle priming caches. The default `0` means to pick automatically. cachePriming_numThreads: NumThreads = NumThreads::Physical, - /// Pass `--all-targets` to cargo invocation. - cargo_allTargets: bool = true, - /// Automatically refresh project info via `cargo metadata` on - /// `Cargo.toml` or `.cargo/config.toml` changes. - pub(crate) cargo_autoreload: bool = true, - /// Run build scripts (`build.rs`) for more precise code analysis. - cargo_buildScripts_enable: bool = true, - /// Specifies the invocation strategy to use when running the build scripts command. - /// If `per_workspace` is set, the command will be executed for each Rust workspace with the - /// workspace as the working directory. - /// If `once` is set, the command will be executed once with the opened project as the - /// working directory. - /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` - /// is set. - 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 - /// option. - /// - /// If there are multiple linked projects/workspaces, this command is invoked for - /// each of them, with the working directory being the workspace root - /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten - /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`. - /// - /// By default, a cargo invocation will be constructed for the configured - /// targets and features, with the following base command line: - /// - /// ```bash - /// cargo check --quiet --workspace --message-format=json --all-targets --keep-going - /// ``` - /// . - 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, - /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to - /// avoid checking unnecessary things. - cargo_buildScripts_useRustcWrapper: bool = true, - /// List of cfg options to enable with the given values. - cargo_cfgs: FxHashMap> = { - let mut m = FxHashMap::default(); - m.insert("debug_assertions".to_owned(), None); - m.insert("miri".to_owned(), None); - m - }, - /// Extra arguments that are passed to every cargo invocation. - 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 = FxHashMap::default(), - /// List of features to activate. - /// - /// Set this to `"all"` to pass `--all-features` to cargo. - cargo_features: CargoFeaturesDef = CargoFeaturesDef::Selected(vec![]), - /// Whether to pass `--no-default-features` to cargo. - 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 = Some("discover".to_owned()), - /// 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 = 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 = 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 = None, - /// Run the check command for diagnostics on save. - checkOnSave | checkOnSave_enable: bool = true, - - /// Check all targets and tests (`--all-targets`). Defaults to - /// `#rust-analyzer.cargo.allTargets#`. - check_allTargets | checkOnSave_allTargets: Option = None, - /// Cargo command to use for `cargo check`. - check_command | checkOnSave_command: String = "check".to_owned(), - /// Extra arguments for `cargo check`. - 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 = 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 = 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 = FxHashSet::default(), - /// 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 = InvocationStrategy::PerWorkspace, - /// Whether to pass `--no-default-features` to Cargo. Defaults to - /// `#rust-analyzer.cargo.noDefaultFeatures#`. - 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 - /// (if your client supports the `colorDiagnosticOutput` experimental - /// capability, you can use `--message-format=json-diagnostic-rendered-ansi`). - /// - /// If you're changing this because you're using some tool wrapping - /// Cargo, you might also want to change - /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`. - /// - /// If there are multiple linked projects/workspaces, this command is invoked for - /// each of them, with the working directory being the workspace root - /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten - /// by changing `#rust-analyzer.check.invocationStrategy#`. - /// - /// If `$saved_file` is part of the command, rust-analyzer will pass - /// the absolute path of the saved file to the provided command. This is - /// intended to be used with non-Cargo build systems. - /// Note that `$saved_file` is experimental and may be removed in the future. - /// - /// An example command would be: - /// - /// ```bash - /// cargo check --workspace --message-format=json --all-targets - /// ``` - /// . - 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 = None, - /// Whether `--workspace` should be passed to `cargo check`. - /// If false, `-p ` will be passed instead. - 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 @@ -255,266 +82,6 @@ config_data! { files_excludeDirs: Vec = vec![], - /// Disable project auto-discovery in favor of explicitly specified set - /// of projects. - /// - /// Elements must be paths pointing to `Cargo.toml`, - /// `rust-project.json`, `.rs` files (which will be treated as standalone files) 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, u16> = FxHashMap::default(), - - /// 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(), - - /// 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![], - /// Additional arguments to be passed through Cargo to launched tests, benchmarks, or - /// doc-tests. - /// - /// Unless the launched target uses a - /// [custom test harness](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-harness-field), - /// they will end up being interpreted as options to - /// [`rustc`’s built-in test harness (“libtest”)](https://doc.rust-lang.org/rustc/tests/index.html#cli-arguments). - runnables_extraTestBinaryArgs: Vec = vec!["--show-output".to_owned()], - - /// 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, - - - /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. - /// - /// [`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`. - /// `progress_label` is used for the title in progress indicators, whereas `files_to_watch` - /// is used to determine which build system-specific files should be watched in order to - /// reload rust-analyzer. - /// - /// Below is an example of a valid configuration: - /// ```json - /// "rust-analyzer.workspace.discoverConfig": { - /// "command": [ - /// "rust-project", - /// "develop-json" - /// ], - /// "progressLabel": "rust-analyzer", - /// "filesToWatch": [ - /// "BUCK" - /// ] - /// } - /// ``` - /// - /// ## On `DiscoverWorkspaceConfig::command` - /// - /// **Warning**: This format is provisional and subject to change. - /// - /// [`DiscoverWorkspaceConfig::command`] *must* return a JSON object - /// corresponding to `DiscoverProjectData::Finished`: - /// - /// ```norun - /// #[derive(Debug, Clone, Deserialize, Serialize)] - /// #[serde(tag = "kind")] - /// #[serde(rename_all = "snake_case")] - /// enum DiscoverProjectData { - /// Finished { buildfile: Utf8PathBuf, project: ProjectJsonData }, - /// Error { error: String, source: Option }, - /// Progress { message: String }, - /// } - /// ``` - /// - /// As JSON, `DiscoverProjectData::Finished` is: - /// - /// ```json - /// { - /// // the internally-tagged representation of the enum. - /// "kind": "finished", - /// // the file used by a non-Cargo build system to define - /// // a package or target. - /// "buildfile": "rust-analyzer/BUILD", - /// // the contents of a rust-project.json, elided for brevity - /// "project": { - /// "sysroot": "foo", - /// "crates": [] - /// } - /// } - /// ``` - /// - /// It is encouraged, but not required, to use the other variants on - /// `DiscoverProjectData` to provide a more polished end-user experience. - /// - /// `DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`, - /// which will be substituted with the JSON-serialized form of the following - /// enum: - /// - /// ```norun - /// #[derive(PartialEq, Clone, Debug, Serialize)] - /// #[serde(rename_all = "camelCase")] - /// pub enum DiscoverArgument { - /// Path(AbsPathBuf), - /// Buildfile(AbsPathBuf), - /// } - /// ``` - /// - /// The JSON representation of `DiscoverArgument::Path` is: - /// - /// ```json - /// { - /// "path": "src/main.rs" - /// } - /// ``` - /// - /// Similarly, the JSON representation of `DiscoverArgument::Buildfile` is: - /// - /// ``` - /// { - /// "buildfile": "BUILD" - /// } - /// ``` - /// - /// `DiscoverArgument::Path` is used to find and generate a `rust-project.json`, - /// and therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to - /// to update an existing workspace. As a reference for implementors, - /// buck2's `rust-project` will likely be useful: - /// https://github.com/facebook/buck2/tree/main/integrations/rust-project. - workspace_discoverConfig: Option = None, - } -} - -config_data! { - /// Local configurations can be defined per `SourceRoot`. This almost always corresponds to a `Crate`. - local: struct LocalDefaultConfigData <- LocalConfigInput -> { - /// Whether to insert #[must_use] when generating `as_` methods - /// for enum variants. - assist_emitMustUse: bool = false, - /// Placeholder expression to use for missing expressions in assists. - assist_expressionFillDefault: ExprFillDefaultDef = ExprFillDefaultDef::Todo, - /// Enable borrow checking for term search code assists. If set to false, also there will be more suggestions, but some of them may not borrow-check. - assist_termSearch_borrowcheck: bool = true, - /// Term search fuel in "units of work" for assists (Defaults to 1800). - assist_termSearch_fuel: usize = 1800, - - /// 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, - /// How imports should be grouped into use statements. - 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, - /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`. - 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, - /// The path structure for newly inserted paths to use. - imports_prefix: ImportPrefixDef = ImportPrefixDef::Plain, - /// Whether to prefix external (including std, core) crate imports with `::`. e.g. "use ::std::io::Read;". - imports_prefixExternPrelude: bool = false, - } -} - -config_data! { - workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> { - - /// 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, - - } -} - -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 ClientDefaultConfigData <- ClientConfigInput -> { - /// 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, - /// 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, - /// Whether to add parenthesis and argument snippets when completing function. - completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments, - /// Whether to show full function/method signatures in completion docs. - completion_fullFunctionSignatures_enable: bool = false, - /// Maximum number of completions to return. If `None`, the limit is infinite. - completion_limit: Option = None, - /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. - 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, - /// Custom completion snippets. - completion_snippets_custom: FxHashMap = serde_json::from_str(r#"{ - "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" - } - }"#).unwrap(), - /// Whether to enable term search based snippets like `Some(foo.bar().baz())`. - completion_termSearch_enable: bool = false, - /// Term search fuel in "units of work" for autocompletion (Defaults to 1000). - completion_termSearch_fuel: usize = 1000, - - /// Controls file watching implementation. - files_watcher: FilesWatcherDef = FilesWatcherDef::Client, /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. highlightRelated_breakPoints_enable: bool = true, @@ -663,6 +230,19 @@ config_data! { /// `#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`, `.rs` files (which will be treated as standalone files) 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, u16> = FxHashMap::default(), + /// Whether to show `can't find Cargo.toml` error message. notifications_cargoTomlNotFound: bool = true, @@ -727,6 +307,419 @@ config_data! { /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. typing_autoClosingAngleBrackets_enable: bool = false, + + /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. + /// + /// [`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`. + /// `progress_label` is used for the title in progress indicators, whereas `files_to_watch` + /// is used to determine which build system-specific files should be watched in order to + /// reload rust-analyzer. + /// + /// Below is an example of a valid configuration: + /// ```json + /// "rust-analyzer.workspace.discoverConfig": { + /// "command": [ + /// "rust-project", + /// "develop-json" + /// ], + /// "progressLabel": "rust-analyzer", + /// "filesToWatch": [ + /// "BUCK" + /// ] + /// } + /// ``` + /// + /// ## On `DiscoverWorkspaceConfig::command` + /// + /// **Warning**: This format is provisional and subject to change. + /// + /// [`DiscoverWorkspaceConfig::command`] *must* return a JSON object + /// corresponding to `DiscoverProjectData::Finished`: + /// + /// ```norun + /// #[derive(Debug, Clone, Deserialize, Serialize)] + /// #[serde(tag = "kind")] + /// #[serde(rename_all = "snake_case")] + /// enum DiscoverProjectData { + /// Finished { buildfile: Utf8PathBuf, project: ProjectJsonData }, + /// Error { error: String, source: Option }, + /// Progress { message: String }, + /// } + /// ``` + /// + /// As JSON, `DiscoverProjectData::Finished` is: + /// + /// ```json + /// { + /// // the internally-tagged representation of the enum. + /// "kind": "finished", + /// // the file used by a non-Cargo build system to define + /// // a package or target. + /// "buildfile": "rust-analyzer/BUILD", + /// // the contents of a rust-project.json, elided for brevity + /// "project": { + /// "sysroot": "foo", + /// "crates": [] + /// } + /// } + /// ``` + /// + /// It is encouraged, but not required, to use the other variants on + /// `DiscoverProjectData` to provide a more polished end-user experience. + /// + /// `DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`, + /// which will be substituted with the JSON-serialized form of the following + /// enum: + /// + /// ```norun + /// #[derive(PartialEq, Clone, Debug, Serialize)] + /// #[serde(rename_all = "camelCase")] + /// pub enum DiscoverArgument { + /// Path(AbsPathBuf), + /// Buildfile(AbsPathBuf), + /// } + /// ``` + /// + /// The JSON representation of `DiscoverArgument::Path` is: + /// + /// ```json + /// { + /// "path": "src/main.rs" + /// } + /// ``` + /// + /// Similarly, the JSON representation of `DiscoverArgument::Buildfile` is: + /// + /// ``` + /// { + /// "buildfile": "BUILD" + /// } + /// ``` + /// + /// `DiscoverArgument::Path` is used to find and generate a `rust-project.json`, + /// and therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to + /// to update an existing workspace. As a reference for implementors, + /// buck2's `rust-project` will likely be useful: + /// https://github.com/facebook/buck2/tree/main/integrations/rust-project. + workspace_discoverConfig: Option = None, + } +} + +config_data! { + /// Local configurations can be defined per `SourceRoot`. This almost always corresponds to a `Crate`. + local: struct LocalDefaultConfigData <- LocalConfigInput -> { + /// Whether to insert #[must_use] when generating `as_` methods + /// for enum variants. + assist_emitMustUse: bool = false, + /// Placeholder expression to use for missing expressions in assists. + assist_expressionFillDefault: ExprFillDefaultDef = ExprFillDefaultDef::Todo, + /// Enable borrow checking for term search code assists. If set to false, also there will be more suggestions, but some of them may not borrow-check. + assist_termSearch_borrowcheck: bool = true, + /// Term search fuel in "units of work" for assists (Defaults to 1800). + assist_termSearch_fuel: usize = 1800, + + + /// 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, + /// 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, + /// Whether to add parenthesis and argument snippets when completing function. + completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments, + /// Whether to show full function/method signatures in completion docs. + completion_fullFunctionSignatures_enable: bool = false, + /// Maximum number of completions to return. If `None`, the limit is infinite. + completion_limit: Option = None, + /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. + 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, + /// Custom completion snippets. + completion_snippets_custom: FxHashMap = serde_json::from_str(r#"{ + "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" + } + }"#).unwrap(), + /// Whether to enable term search based snippets like `Some(foo.bar().baz())`. + completion_termSearch_enable: bool = false, + /// Term search fuel in "units of work" for autocompletion (Defaults to 1000). + completion_termSearch_fuel: usize = 1000, + + /// 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![], + + /// 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, + /// How imports should be grouped into use statements. + 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, + /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`. + 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, + /// The path structure for newly inserted paths to use. + imports_prefix: ImportPrefixDef = ImportPrefixDef::Plain, + /// Whether to prefix external (including std, core) crate imports with `::`. e.g. "use ::std::io::Read;". + imports_prefixExternPrelude: bool = false, + } +} + +config_data! { + workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> { + /// Pass `--all-targets` to cargo invocation. + cargo_allTargets: bool = true, + /// Automatically refresh project info via `cargo metadata` on + /// `Cargo.toml` or `.cargo/config.toml` changes. + cargo_autoreload: bool = true, + /// Run build scripts (`build.rs`) for more precise code analysis. + cargo_buildScripts_enable: bool = true, + /// Specifies the invocation strategy to use when running the build scripts command. + /// If `per_workspace` is set, the command will be executed for each Rust workspace with the + /// workspace as the working directory. + /// If `once` is set, the command will be executed once with the opened project as the + /// working directory. + /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// is set. + 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 + /// option. + /// + /// If there are multiple linked projects/workspaces, this command is invoked for + /// each of them, with the working directory being the workspace root + /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten + /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`. + /// + /// By default, a cargo invocation will be constructed for the configured + /// targets and features, with the following base command line: + /// + /// ```bash + /// cargo check --quiet --workspace --message-format=json --all-targets --keep-going + /// ``` + /// . + 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, + /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to + /// avoid checking unnecessary things. + cargo_buildScripts_useRustcWrapper: bool = true, + /// List of cfg options to enable with the given values. + cargo_cfgs: FxHashMap> = { + let mut m = FxHashMap::default(); + m.insert("debug_assertions".to_owned(), None); + m.insert("miri".to_owned(), None); + m + }, + /// Extra arguments that are passed to every cargo invocation. + 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 = FxHashMap::default(), + /// List of features to activate. + /// + /// Set this to `"all"` to pass `--all-features` to cargo. + cargo_features: CargoFeaturesDef = CargoFeaturesDef::Selected(vec![]), + /// Whether to pass `--no-default-features` to cargo. + 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 = Some("discover".to_owned()), + /// 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 = 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 = 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 = None, + + /// Run the check command for diagnostics on save. + checkOnSave | checkOnSave_enable: bool = true, + + + /// Check all targets and tests (`--all-targets`). Defaults to + /// `#rust-analyzer.cargo.allTargets#`. + check_allTargets | checkOnSave_allTargets: Option = None, + /// Cargo command to use for `cargo check`. + check_command | checkOnSave_command: String = "check".to_owned(), + /// Extra arguments for `cargo check`. + 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 = 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 = 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 = FxHashSet::default(), + /// 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 = InvocationStrategy::PerWorkspace, + /// Whether to pass `--no-default-features` to Cargo. Defaults to + /// `#rust-analyzer.cargo.noDefaultFeatures#`. + 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 + /// (if your client supports the `colorDiagnosticOutput` experimental + /// capability, you can use `--message-format=json-diagnostic-rendered-ansi`). + /// + /// If you're changing this because you're using some tool wrapping + /// Cargo, you might also want to change + /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`. + /// + /// If there are multiple linked projects/workspaces, this command is invoked for + /// each of them, with the working directory being the workspace root + /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten + /// by changing `#rust-analyzer.check.invocationStrategy#`. + /// + /// If `$saved_file` is part of the command, rust-analyzer will pass + /// the absolute path of the saved file to the provided command. This is + /// intended to be used with non-Cargo build systems. + /// Note that `$saved_file` is experimental and may be removed in the future. + /// + /// An example command would be: + /// + /// ```bash + /// cargo check --workspace --message-format=json --all-targets + /// ``` + /// . + 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 = None, + /// Whether `--workspace` should be passed to `cargo check`. + /// If false, `-p ` will be passed instead. + check_workspace: 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(), + + /// 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![], + /// Additional arguments to be passed through Cargo to launched tests, benchmarks, or + /// doc-tests. + /// + /// Unless the launched target uses a + /// [custom test harness](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-harness-field), + /// they will end up being interpreted as options to + /// [`rustc`’s built-in test harness (“libtest”)](https://doc.rust-lang.org/rustc/tests/index.html#cli-arguments). + runnables_extraTestBinaryArgs: Vec = vec!["--show-output".to_owned()], + + /// 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, + + /// 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). @@ -735,6 +728,19 @@ config_data! { workspace_symbol_search_limit: usize = 128, /// Workspace symbol search scope. workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = WorkspaceSymbolSearchScopeDef::Workspace, + + } +} + +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 ClientDefaultConfigData <- ClientConfigInput -> { + + /// Controls file watching implementation. + files_watcher: FilesWatcherDef = FilesWatcherDef::Client, + + } } @@ -874,7 +880,7 @@ impl Config { // IMPORTANT : This holds as long as ` completion_snippets_custom` is declared `client`. config.snippets.clear(); - let snips = self.completion_snippets_custom().to_owned(); + let snips = self.completion_snippets_custom(None).to_owned(); for (name, def) in snips.iter() { if def.prefix.is_empty() && def.postfix.is_empty() { @@ -1016,7 +1022,7 @@ impl Config { config.source_root_parent_map = source_root_map; } - if config.check_command().is_empty() { + if config.check_command(None).is_empty() { config.validation_errors.0.push(Arc::new(ConfigErrorInner::Json { config_key: "/check/command".to_owned(), error: serde_json::Error::custom("expected a non-empty string"), @@ -1412,13 +1418,15 @@ impl Config { pub fn completion(&self, source_root: Option) -> CompletionConfig { CompletionConfig { - enable_postfix_completions: self.completion_postfix_enable().to_owned(), - enable_imports_on_the_fly: self.completion_autoimport_enable().to_owned() + enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(), + enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned() && self.caps.completion_item_edit_resolve(), - enable_self_on_the_fly: self.completion_autoself_enable().to_owned(), - enable_private_editable: self.completion_privateEditable_enable().to_owned(), - full_function_signatures: self.completion_fullFunctionSignatures_enable().to_owned(), - callable: match self.completion_callable_snippets() { + 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, @@ -1429,9 +1437,9 @@ impl Config { prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(), snippets: self.snippets.clone().to_vec(), - limit: self.completion_limit().to_owned(), - enable_term_search: self.completion_termSearch_enable().to_owned(), - term_search_fuel: self.completion_termSearch_fuel().to_owned() as u64, + limit: self.completion_limit(source_root).to_owned(), + enable_term_search: self.completion_termSearch_enable(source_root).to_owned(), + term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64, } } @@ -1443,11 +1451,11 @@ impl Config { pub fn diagnostics(&self, source_root: Option) -> DiagnosticsConfig { DiagnosticsConfig { - enabled: *self.diagnostics_enable(), + enabled: *self.diagnostics_enable(source_root), 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(), + disable_experimental: !self.diagnostics_experimental_enable(source_root), + disabled: self.diagnostics_disabled(source_root).clone(), expr_fill_default: match self.assist_expressionFillDefault(source_root) { ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, @@ -1457,7 +1465,7 @@ impl Config { prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(), - style_lints: self.diagnostics_styleLints_enable().to_owned(), + style_lints: self.diagnostics_styleLints_enable(source_root).to_owned(), term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64, term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(), } @@ -1715,36 +1723,36 @@ impl Config { self.cachePriming_enable().to_owned() } - pub fn publish_diagnostics(&self) -> bool { - self.diagnostics_enable().to_owned() + pub fn publish_diagnostics(&self, source_root: Option) -> bool { + self.diagnostics_enable(source_root).to_owned() } - pub fn diagnostics_map(&self) -> DiagnosticsMapConfig { + pub fn diagnostics_map(&self, source_root: Option) -> DiagnosticsMapConfig { DiagnosticsMapConfig { - 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(), + remap_prefix: self.diagnostics_remapPrefix(source_root).clone(), + warnings_as_info: self.diagnostics_warningsAsInfo(source_root).clone(), + warnings_as_hint: self.diagnostics_warningsAsHint(source_root).clone(), + check_ignore: self.check_ignore(source_root).clone(), } } - pub fn extra_args(&self) -> &Vec { - self.cargo_extraArgs() + pub fn extra_args(&self, source_root: Option) -> &Vec { + self.cargo_extraArgs(source_root) } - pub fn extra_env(&self) -> &FxHashMap { - self.cargo_extraEnv() + pub fn extra_env(&self, source_root: Option) -> &FxHashMap { + self.cargo_extraEnv(source_root) } - pub fn check_extra_args(&self) -> Vec { - let mut extra_args = self.extra_args().clone(); - extra_args.extend_from_slice(self.check_extraArgs()); + pub fn check_extra_args(&self, source_root: Option) -> Vec { + let mut extra_args = self.extra_args(source_root).clone(); + extra_args.extend_from_slice(self.check_extraArgs(source_root)); extra_args } - pub fn check_extra_env(&self) -> FxHashMap { - let mut extra_env = self.cargo_extraEnv().clone(); - extra_env.extend(self.check_extraEnv().clone()); + pub fn check_extra_env(&self, source_root: Option) -> FxHashMap { + let mut extra_env = self.cargo_extraEnv(source_root).clone(); + extra_env.extend(self.check_extraEnv(source_root).clone()); extra_env } @@ -1761,8 +1769,11 @@ impl Config { Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path))) } - pub fn ignored_proc_macros(&self) -> &FxHashMap, Box<[Box]>> { - self.procMacro_ignored() + pub fn ignored_proc_macros( + &self, + source_root: Option, + ) -> &FxHashMap, Box<[Box]>> { + self.procMacro_ignored(source_root) } pub fn expand_proc_macros(&self) -> bool { @@ -1787,23 +1798,23 @@ impl Config { } } - pub fn cargo_autoreload_config(&self) -> bool { - self.cargo_autoreload().to_owned() + pub fn cargo_autoreload_config(&self, source_root: Option) -> bool { + self.cargo_autoreload(source_root).to_owned() } - pub fn run_build_scripts(&self) -> bool { - self.cargo_buildScripts_enable().to_owned() || self.procMacro_enable().to_owned() + pub fn run_build_scripts(&self, source_root: Option) -> bool { + self.cargo_buildScripts_enable(source_root).to_owned() || self.procMacro_enable().to_owned() } - pub fn cargo(&self) -> CargoConfig { - let rustc_source = self.rustc_source().as_ref().map(|rustc_src| { + pub fn cargo(&self, source_root: Option) -> CargoConfig { + let rustc_source = self.rustc_source(source_root).as_ref().map(|rustc_src| { if rustc_src == "discover" { RustLibSource::Discover } else { RustLibSource::Path(self.root_path.join(rustc_src)) } }); - let sysroot = self.cargo_sysroot().as_ref().map(|sysroot| { + let sysroot = self.cargo_sysroot(source_root).as_ref().map(|sysroot| { if sysroot == "discover" { RustLibSource::Discover } else { @@ -1811,24 +1822,24 @@ impl Config { } }); let sysroot_src = - self.cargo_sysrootSrc().as_ref().map(|sysroot| self.root_path.join(sysroot)); + self.cargo_sysrootSrc(source_root).as_ref().map(|sysroot| self.root_path.join(sysroot)); CargoConfig { - all_targets: *self.cargo_allTargets(), - features: match &self.cargo_features() { + all_targets: *self.cargo_allTargets(source_root), + features: match &self.cargo_features(source_root) { CargoFeaturesDef::All => CargoFeatures::All, CargoFeaturesDef::Selected(features) => CargoFeatures::Selected { features: features.clone(), - no_default_features: self.cargo_noDefaultFeatures().to_owned(), + no_default_features: self.cargo_noDefaultFeatures(source_root).to_owned(), }, }, - target: self.cargo_target().clone(), + target: self.cargo_target(source_root).clone(), sysroot, sysroot_src, rustc_source, cfg_overrides: project_model::CfgOverrides { global: CfgDiff::new( - self.cargo_cfgs() + self.cargo_cfgs(source_root) .iter() .map(|(key, val)| match val { Some(val) => CfgAtom::KeyValue { @@ -1843,15 +1854,15 @@ impl Config { .unwrap(), selective: Default::default(), }, - wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(), - invocation_strategy: match self.cargo_buildScripts_invocationStrategy() { + wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(source_root), + invocation_strategy: match self.cargo_buildScripts_invocationStrategy(source_root) { InvocationStrategy::Once => project_model::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace, }, - 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(), + run_build_script_command: self.cargo_buildScripts_overrideCommand(source_root).clone(), + extra_args: self.cargo_extraArgs(source_root).clone(), + extra_env: self.cargo_extraEnv(source_root).clone(), + target_dir: self.target_dir_from_config(source_root), } } @@ -1869,37 +1880,37 @@ impl Config { } } - pub fn flycheck_workspace(&self) -> bool { - *self.check_workspace() + pub fn flycheck_workspace(&self, source_root: Option) -> bool { + *self.check_workspace(source_root) } - pub(crate) fn cargo_test_options(&self) -> CargoOptions { + pub(crate) fn cargo_test_options(&self, source_root: Option) -> CargoOptions { CargoOptions { - target_triples: self.cargo_target().clone().into_iter().collect(), + target_triples: self.cargo_target(source_root).clone().into_iter().collect(), all_targets: false, - no_default_features: *self.cargo_noDefaultFeatures(), - all_features: matches!(self.cargo_features(), CargoFeaturesDef::All), - features: match self.cargo_features().clone() { + no_default_features: *self.cargo_noDefaultFeatures(source_root), + all_features: matches!(self.cargo_features(source_root), CargoFeaturesDef::All), + features: match self.cargo_features(source_root).clone() { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, }, - extra_args: self.extra_args().clone(), - extra_test_bin_args: self.runnables_extraTestBinaryArgs().clone(), - extra_env: self.extra_env().clone(), - target_dir: self.target_dir_from_config(), + extra_args: self.extra_args(source_root).clone(), + extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), + extra_env: self.extra_env(source_root).clone(), + target_dir: self.target_dir_from_config(source_root), } } - pub(crate) fn flycheck(&self) -> FlycheckConfig { - match &self.check_overrideCommand() { + pub(crate) fn flycheck(&self, source_root: Option) -> FlycheckConfig { + match &self.check_overrideCommand(source_root) { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); FlycheckConfig::CustomCommand { command, args, - extra_env: self.check_extra_env(), - invocation_strategy: match self.check_invocationStrategy() { + extra_env: self.check_extra_env(source_root), + invocation_strategy: match self.check_invocationStrategy(source_root) { InvocationStrategy::Once => crate::flycheck::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => { crate::flycheck::InvocationStrategy::PerWorkspace @@ -1908,44 +1919,50 @@ impl Config { } } Some(_) | None => FlycheckConfig::CargoCommand { - command: self.check_command().clone(), + command: self.check_command(source_root).clone(), options: CargoOptions { target_triples: self - .check_targets() + .check_targets(source_root) .clone() .and_then(|targets| match &targets.0[..] { [] => None, targets => Some(targets.into()), }) - .unwrap_or_else(|| self.cargo_target().clone().into_iter().collect()), - all_targets: self.check_allTargets().unwrap_or(*self.cargo_allTargets()), + .unwrap_or_else(|| { + self.cargo_target(source_root).clone().into_iter().collect() + }), + all_targets: self + .check_allTargets(source_root) + .unwrap_or(*self.cargo_allTargets(source_root)), no_default_features: self - .check_noDefaultFeatures() - .unwrap_or(*self.cargo_noDefaultFeatures()), + .check_noDefaultFeatures(source_root) + .unwrap_or(*self.cargo_noDefaultFeatures(source_root)), all_features: matches!( - self.check_features().as_ref().unwrap_or(self.cargo_features()), + self.check_features(source_root) + .as_ref() + .unwrap_or(self.cargo_features(source_root)), CargoFeaturesDef::All ), features: match self - .check_features() + .check_features(source_root) .clone() - .unwrap_or_else(|| self.cargo_features().clone()) + .unwrap_or_else(|| self.cargo_features(source_root).clone()) { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, }, - extra_args: self.check_extra_args(), - extra_test_bin_args: self.runnables_extraTestBinaryArgs().clone(), - extra_env: self.check_extra_env(), - target_dir: self.target_dir_from_config(), + extra_args: self.check_extra_args(source_root), + extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), + extra_env: self.check_extra_env(source_root), + target_dir: self.target_dir_from_config(source_root), }, ansi_color_output: self.color_diagnostic_output(), }, } } - fn target_dir_from_config(&self) -> Option { - self.cargo_targetDir().as_ref().and_then(|target_dir| match target_dir { + fn target_dir_from_config(&self, source_root: Option) -> Option { + self.cargo_targetDir(source_root).as_ref().and_then(|target_dir| match target_dir { TargetDirectory::UseSubdirectory(true) => { Some(Utf8PathBuf::from("target/rust-analyzer")) } @@ -1955,19 +1972,19 @@ impl Config { }) } - pub fn check_on_save(&self) -> bool { - *self.checkOnSave() + pub fn check_on_save(&self, source_root: Option) -> bool { + *self.checkOnSave(source_root) } - pub fn script_rebuild_on_save(&self) -> bool { - *self.cargo_buildScripts_rebuildOnSave() + pub fn script_rebuild_on_save(&self, source_root: Option) -> bool { + *self.cargo_buildScripts_rebuildOnSave(source_root) } - pub fn runnables(&self) -> RunnablesConfig { + pub fn runnables(&self, source_root: Option) -> RunnablesConfig { RunnablesConfig { - override_cargo: self.runnables_command().clone(), - cargo_extra_args: self.runnables_extraArgs().clone(), - extra_test_binary_args: self.runnables_extraTestBinaryArgs().clone(), + override_cargo: self.runnables_command(source_root).clone(), + cargo_extra_args: self.runnables_extraArgs(source_root).clone(), + extra_test_binary_args: self.runnables_extraTestBinaryArgs(source_root).clone(), } } @@ -2006,19 +2023,19 @@ impl Config { } } - pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig { + pub fn workspace_symbol(&self, source_root: Option) -> WorkspaceSymbolConfig { WorkspaceSymbolConfig { - search_scope: match self.workspace_symbol_search_scope() { + search_scope: match self.workspace_symbol_search_scope(source_root) { WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace, WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => { WorkspaceSymbolSearchScope::WorkspaceAndDependencies } }, - search_kind: match self.workspace_symbol_search_kind() { + search_kind: match self.workspace_symbol_search_kind(source_root) { WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes, WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols, }, - search_limit: *self.workspace_symbol_search_limit(), + search_limit: *self.workspace_symbol_search_limit(source_root), } } @@ -3522,9 +3539,9 @@ mod tests { })); (config, _, _) = config.apply_change(change); - assert_eq!(config.cargo_targetDir(), &None); + assert_eq!(config.cargo_targetDir(None), &None); assert!( - matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none()) + matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none()) ); } @@ -3540,9 +3557,9 @@ mod tests { (config, _, _) = config.apply_change(change); - assert_eq!(config.cargo_targetDir(), &Some(TargetDirectory::UseSubdirectory(true))); + assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true))); assert!( - matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer"))) + matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer"))) ); } @@ -3559,83 +3576,11 @@ mod tests { (config, _, _) = config.apply_change(change); assert_eq!( - config.cargo_targetDir(), + config.cargo_targetDir(None), &Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder"))) ); assert!( - matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("other_folder"))) + matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("other_folder"))) ); } - - #[test] - fn toml_unknown_key() { - let config = - Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); - - let mut change = ConfigChange::default(); - - change.change_user_config(Some( - toml::toml! { - [cargo.cfgs] - these = "these" - should = "should" - be = "be" - valid = "valid" - - [invalid.config] - err = "error" - - [cargo] - target = "ok" - - // FIXME: This should be an error - [cargo.sysroot] - non-table = "expected" - } - .to_string() - .into(), - )); - - let (config, e, _) = config.apply_change(change); - expect_test::expect![[r#" - ConfigErrors( - [ - Toml { - config_key: "invalid/config/err", - error: Error { - inner: Error { - inner: TomlError { - message: "unexpected field", - raw: None, - keys: [], - span: None, - }, - }, - }, - }, - ], - ) - "#]] - .assert_debug_eq(&e); - let mut change = ConfigChange::default(); - - change.change_user_config(Some( - toml::toml! { - [cargo.cfgs] - these = "these" - should = "should" - be = "be" - valid = "valid" - } - .to_string() - .into(), - )); - let (_, e, _) = config.apply_change(change); - expect_test::expect![[r#" - ConfigErrors( - [], - ) - "#]] - .assert_debug_eq(&e); - } } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 9d0082c370..35e1da80bf 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -631,6 +631,10 @@ impl GlobalStateSnapshot { file_id_to_url(&self.vfs_read(), id) } + pub(crate) fn vfs_path_to_file_id(&self, vfs_path: &VfsPath) -> anyhow::Result { + vfs_path_to_file_id(&self.vfs_read(), vfs_path) + } + pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable { let endings = self.vfs.read().1[&file_id]; let index = self.analysis.file_line_index(file_id)?; @@ -725,3 +729,9 @@ pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> anyhow::Result anyhow::Result { + let res = + vfs.file_id(vfs_path).ok_or_else(|| anyhow::format_err!("file not found: {vfs_path}"))?; + Ok(res) +} diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs index 38b88ff2d0..e8d1a7e4df 100644 --- a/crates/rust-analyzer/src/handlers/notification.rs +++ b/crates/rust-analyzer/src/handlers/notification.rs @@ -145,14 +145,18 @@ pub(crate) fn handle_did_save_text_document( state: &mut GlobalState, params: DidSaveTextDocumentParams, ) -> anyhow::Result<()> { - if state.config.script_rebuild_on_save() && state.build_deps_changed { - state.build_deps_changed = false; - state - .fetch_build_data_queue - .request_op("build_deps_changed - save notification".to_owned(), ()); - } - if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) { + let snap = state.snapshot(); + let file_id = snap.vfs_path_to_file_id(&vfs_path)?; + let sr = snap.analysis.source_root_id(file_id)?; + + if state.config.script_rebuild_on_save(Some(sr)) && state.build_deps_changed { + state.build_deps_changed = false; + state + .fetch_build_data_queue + .request_op("build_deps_changed - save notification".to_owned(), ()); + } + // Re-fetch workspaces if a workspace related file has changed if let Some(path) = vfs_path.as_path() { let additional_files = &state @@ -182,15 +186,16 @@ pub(crate) fn handle_did_save_text_document( } } - if !state.config.check_on_save() || run_flycheck(state, vfs_path) { + if !state.config.check_on_save(Some(sr)) || run_flycheck(state, vfs_path) { return Ok(()); } - } else if state.config.check_on_save() { + } else if state.config.check_on_save(None) { // No specific flycheck was triggered, so let's trigger all of them. for flycheck in state.flycheck.iter() { flycheck.restart_workspace(None); } } + Ok(()) } @@ -288,6 +293,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { let file_id = state.vfs.read().0.file_id(&vfs_path); if let Some(file_id) = file_id { let world = state.snapshot(); + let source_root_id = world.analysis.source_root_id(file_id).ok(); let mut updated = false; let task = move || -> std::result::Result<(), ide::Cancelled> { // Is the target binary? If so we let flycheck run only for the workspace that contains the crate. @@ -373,9 +379,9 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { for (id, package) in workspace_ids.clone() { if id == flycheck.id() { updated = true; - match package - .filter(|_| !world.config.flycheck_workspace() || target.is_some()) - { + match package.filter(|_| { + !world.config.flycheck_workspace(source_root_id) || target.is_some() + }) { Some(package) => flycheck .restart_for_package(package, target.clone().map(TupleExt::head)), None => flycheck.restart_workspace(saved_file.clone()), diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 50df9dc876..09d5086201 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -256,7 +256,7 @@ pub(crate) fn handle_run_test( let handle = CargoTestHandle::new( test_path, - state.config.cargo_test_options(), + state.config.cargo_test_options(None), cargo.workspace_root(), test_target, state.test_run_sender.clone(), @@ -565,7 +565,7 @@ pub(crate) fn handle_workspace_symbol( ) -> anyhow::Result> { let _p = tracing::info_span!("handle_workspace_symbol").entered(); - let config = snap.config.workspace_symbol(); + let config = snap.config.workspace_symbol(None); let (all_symbols, libs) = decide_search_scope_and_kind(¶ms, &config); let query = { @@ -852,6 +852,7 @@ pub(crate) fn handle_runnables( ) -> anyhow::Result> { let _p = tracing::info_span!("handle_runnables").entered(); let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let source_root = snap.analysis.source_root_id(file_id).ok(); let line_index = snap.file_line_index(file_id)?; let offset = params.position.and_then(|it| from_proto::offset(&line_index, it).ok()); let target_spec = TargetSpec::for_file(&snap, file_id)?; @@ -894,7 +895,7 @@ pub(crate) fn handle_runnables( } // Add `cargo check` and `cargo test` for all targets of the whole package - let config = snap.config.runnables(); + let config = snap.config.runnables(source_root); match target_spec { Some(TargetSpec::Cargo(spec)) => { let is_crate_no_std = snap.analysis.is_crate_no_std(spec.crate_id)?; @@ -2119,7 +2120,7 @@ fn run_rustfmt( RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => { // FIXME: Set RUSTUP_TOOLCHAIN let mut cmd = process::Command::new(toolchain::Tool::Rustfmt.path()); - cmd.envs(snap.config.extra_env()); + cmd.envs(snap.config.extra_env(source_root_id)); cmd.args(extra_args); if let Some(edition) = edition { @@ -2177,7 +2178,7 @@ fn run_rustfmt( _ => process::Command::new(cmd), }; - cmd.envs(snap.config.extra_env()); + cmd.envs(snap.config.extra_env(source_root_id)); cmd.args(args); cmd } diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index 7eed9ce2f3..6dff8abb0f 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -1369,8 +1369,9 @@ pub(crate) fn runnable( snap: &GlobalStateSnapshot, runnable: Runnable, ) -> Cancellable> { - let config = snap.config.runnables(); let target_spec = TargetSpec::for_file(snap, runnable.nav.file_id)?; + let source_root = snap.analysis.source_root_id(runnable.nav.file_id).ok(); + let config = snap.config.runnables(source_root); match target_spec { Some(TargetSpec::Cargo(spec)) => { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 616d6b4935..ec71b4a7a1 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -404,7 +404,7 @@ impl GlobalState { if self.is_quiescent() { let became_quiescent = !was_quiescent; if became_quiescent { - if self.config.check_on_save() { + if self.config.check_on_save(None) { // Project has loaded properly, kick off initial flycheck self.flycheck.iter().for_each(|flycheck| flycheck.restart_workspace(None)); } @@ -434,7 +434,7 @@ impl GlobalState { let project_or_mem_docs_changed = became_quiescent || state_changed || memdocs_added_or_removed; - if project_or_mem_docs_changed && self.config.publish_diagnostics() { + if project_or_mem_docs_changed && self.config.publish_diagnostics(None) { self.update_diagnostics(); } if project_or_mem_docs_changed && self.config.test_explorer() { @@ -455,7 +455,7 @@ impl GlobalState { } } - if self.config.cargo_autoreload_config() + if self.config.cargo_autoreload_config(None) || self.config.discover_workspace_config().is_some() { if let Some((cause, FetchWorkspaceRequest { path, force_crate_graph_reload })) = @@ -925,7 +925,7 @@ impl GlobalState { FlycheckMessage::AddDiagnostic { id, workspace_root, diagnostic } => { let snap = self.snapshot(); let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( - &self.config.diagnostics_map(), + &self.config.diagnostics_map(None), &diagnostic, &workspace_root, &snap, @@ -973,9 +973,9 @@ impl GlobalState { // When we're running multiple flychecks, we have to include a disambiguator in // the title, or the editor complains. Note that this is a user-facing string. let title = if self.flycheck.len() == 1 { - format!("{}", self.config.flycheck()) + format!("{}", self.config.flycheck(None)) } else { - format!("{} (#{})", self.config.flycheck(), id + 1) + format!("{} (#{})", self.config.flycheck(None), id + 1) }; self.report_progress( &title, diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 68366136ed..12667e3c90 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -100,7 +100,7 @@ impl GlobalState { { let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false }; self.fetch_workspaces_queue.request_op("discovered projects changed".to_owned(), req) - } else if self.config.flycheck() != old_config.flycheck() { + } else if self.config.flycheck(None) != old_config.flycheck(None) { self.reload_flycheck(); } @@ -122,7 +122,7 @@ impl GlobalState { }; let mut message = String::new(); - if !self.config.cargo_autoreload() + if !self.config.cargo_autoreload_config(None) && self.is_quiescent() && self.fetch_workspaces_queue.op_requested() && self.config.discover_workspace_config().is_none() @@ -264,7 +264,7 @@ impl GlobalState { .map(ManifestPath::try_from) .filter_map(Result::ok) .collect(); - let cargo_config = self.config.cargo(); + let cargo_config = self.config.cargo(None); let discover_command = self.config.discover_workspace_config().cloned(); let is_quiescent = !(self.discover_workspace_queue.op_in_progress() || self.vfs_progress_config_version < self.vfs_config_version @@ -357,7 +357,7 @@ impl GlobalState { pub(crate) fn fetch_build_data(&mut self, cause: Cause) { info!(%cause, "will fetch build data"); let workspaces = Arc::clone(&self.workspaces); - let config = self.config.cargo(); + let config = self.config.cargo(None); let root_path = self.config.root_path().clone(); self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| { @@ -382,7 +382,7 @@ impl GlobalState { pub(crate) fn fetch_proc_macros(&mut self, cause: Cause, paths: Vec) { info!(%cause, "will load proc macros"); - let ignored_proc_macros = self.config.ignored_proc_macros().clone(); + let ignored_proc_macros = self.config.ignored_proc_macros(None).clone(); let proc_macro_clients = self.proc_macro_clients.clone(); self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| { @@ -507,7 +507,7 @@ impl GlobalState { // FIXME: can we abort the build scripts here if they are already running? self.workspaces = Arc::new(workspaces); - if self.config.run_build_scripts() { + if self.config.run_build_scripts(None) { self.build_deps_changed = false; self.fetch_build_data_queue.request_op("workspace updated".to_owned(), ()); } @@ -627,7 +627,7 @@ impl GlobalState { .. } => cargo_config_extra_env .iter() - .chain(self.config.extra_env()) + .chain(self.config.extra_env(None)) .map(|(a, b)| (a.clone(), b.clone())) .chain( ws.sysroot @@ -702,7 +702,7 @@ impl GlobalState { vfs.file_id(&vfs_path) }; - ws_to_crate_graph(&self.workspaces, self.config.extra_env(), load) + ws_to_crate_graph(&self.workspaces, self.config.extra_env(None), load) }; let mut change = ChangeWithProcMacros::new(); if self.config.expand_proc_macros() { @@ -791,7 +791,7 @@ impl GlobalState { fn reload_flycheck(&mut self) { let _p = tracing::info_span!("GlobalState::reload_flycheck").entered(); - let config = self.config.flycheck(); + let config = self.config.flycheck(None); let sender = self.flycheck_sender.clone(); let invocation_strategy = match config { FlycheckConfig::CargoCommand { .. } => { diff --git a/crates/rust-analyzer/src/target_spec.rs b/crates/rust-analyzer/src/target_spec.rs index 954e13cbf2..b4aa73d278 100644 --- a/crates/rust-analyzer/src/target_spec.rs +++ b/crates/rust-analyzer/src/target_spec.rs @@ -113,7 +113,7 @@ impl CargoTargetSpec { kind: &RunnableKind, cfg: &Option, ) -> (Vec, Vec) { - let config = snap.config.runnables(); + let config = snap.config.runnables(None); let extra_test_binary_args = config.extra_test_binary_args; let mut cargo_args = Vec::new(); @@ -168,7 +168,7 @@ impl CargoTargetSpec { (Default::default(), Default::default()) }; - let cargo_config = snap.config.cargo(); + let cargo_config = snap.config.cargo(None); match &cargo_config.features { CargoFeatures::All => {