1 //! Config used by the language server.
3 //! We currently get this config from `initialize` LSP request, which is not the
4 //! best way to do it, but was the simplest thing we could implement.
6 //! Of particular interest is the `feature_flags` hash map: while other fields
7 //! configure the server itself, feature flags are passed into analysis, and
8 //! tweak things like automatic insertion of `()` in completions.
10 use std::{ffi::OsString, fmt, iter, path::PathBuf};
12 use flycheck::FlycheckConfig;
14 AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
15 HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
16 JoinLinesConfig, Snippet, SnippetScope,
19 imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
22 use itertools::Itertools;
23 use lsp_types::{ClientCapabilities, MarkupKind};
25 CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates,
27 use rustc_hash::{FxHashMap, FxHashSet};
28 use serde::{de::DeserializeOwned, Deserialize};
32 caps::completion_item_edit_resolve,
33 diagnostics::DiagnosticsMapConfig,
34 line_index::OffsetEncoding,
35 lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
40 // Conventions for configuration keys to preserve maximal extendability without breakage:
41 // - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable`
42 // This has the benefit of namespaces being extensible, and if the suffix doesn't fit later it can be changed without breakage.
43 // - In general be wary of using the namespace of something verbatim, it prevents us from adding subkeys in the future
44 // - Don't use abbreviations unless really necessary
45 // - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
47 // Defines the server-side configuration of the rust-analyzer. We generate
48 // *parts* of VS Code's `package.json` config from this. Run `cargo test` to
49 // re-generate that file.
51 // However, editor specific config, which the server doesn't know about, should
52 // be specified directly in `package.json`.
54 // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
55 // parsing the old name.
58 /// Placeholder expression to use for missing expressions in assists.
59 assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
61 /// Warm up caches on project load.
62 cachePriming_enable: bool = "true",
63 /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
64 cachePriming_numThreads: ParallelCachePrimingNumThreads = "0",
66 /// Automatically refresh project info via `cargo metadata` on
67 /// `Cargo.toml` or `.cargo/config.toml` changes.
68 cargo_autoreload: bool = "true",
69 /// Run build scripts (`build.rs`) for more precise code analysis.
70 cargo_buildScripts_enable: bool = "true",
71 /// Override the command rust-analyzer uses to run build scripts and
72 /// build procedural macros. The command is required to output json
73 /// and should therefore include `--message-format=json` or a similar
76 /// By default, a cargo invocation will be constructed for the configured
77 /// targets and features, with the following base command line:
80 /// cargo check --quiet --workspace --message-format=json --all-targets
83 cargo_buildScripts_overrideCommand: Option<Vec<String>> = "null",
84 /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
85 /// avoid checking unnecessary things.
86 cargo_buildScripts_useRustcWrapper: bool = "true",
87 /// List of features to activate.
89 /// Set this to `"all"` to pass `--all-features` to cargo.
90 cargo_features: CargoFeatures = "[]",
91 /// Whether to pass `--no-default-features` to cargo.
92 cargo_noDefaultFeatures: bool = "false",
93 /// Internal config for debugging, disables loading of sysroot crates.
94 cargo_noSysroot: bool = "false",
95 /// Compilation target override (target triple).
96 cargo_target: Option<String> = "null",
97 /// Unsets `#[cfg(test)]` for the specified crates.
98 cargo_unsetTest: Vec<String> = "[\"core\"]",
100 /// Check all targets and tests (`--all-targets`).
101 checkOnSave_allTargets: bool = "true",
102 /// Cargo command to use for `cargo check`.
103 checkOnSave_command: String = "\"check\"",
104 /// Run specified `cargo check` command for diagnostics on save.
105 checkOnSave_enable: bool = "true",
106 /// Extra arguments for `cargo check`.
107 checkOnSave_extraArgs: Vec<String> = "[]",
108 /// List of features to activate. Defaults to
109 /// `#rust-analyzer.cargo.features#`.
111 /// Set to `"all"` to pass `--all-features` to Cargo.
112 checkOnSave_features: Option<CargoFeatures> = "null",
113 /// Whether to pass `--no-default-features` to Cargo. Defaults to
114 /// `#rust-analyzer.cargo.noDefaultFeatures#`.
115 checkOnSave_noDefaultFeatures: Option<bool> = "null",
116 /// Override the command rust-analyzer uses instead of `cargo check` for
117 /// diagnostics on save. The command is required to output json and
118 /// should therefor include `--message-format=json` or a similar option.
120 /// If you're changing this because you're using some tool wrapping
121 /// Cargo, you might also want to change
122 /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
124 /// If there are multiple linked projects, this command is invoked for
125 /// each of them, with the working directory being the project root
126 /// (i.e., the folder containing the `Cargo.toml`).
128 /// An example command would be:
131 /// cargo check --workspace --message-format=json --all-targets
134 checkOnSave_overrideCommand: Option<Vec<String>> = "null",
135 /// Check for a specific target. Defaults to
136 /// `#rust-analyzer.cargo.target#`.
137 checkOnSave_target: Option<String> = "null",
139 /// Toggles the additional completions that automatically add imports when completed.
140 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
141 completion_autoimport_enable: bool = "true",
142 /// Toggles the additional completions that automatically show method calls and field accesses
143 /// with `self` prefixed to them when inside a method.
144 completion_autoself_enable: bool = "true",
145 /// Whether to add parenthesis and argument snippets when completing function.
146 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
147 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
148 completion_postfix_enable: bool = "true",
149 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
150 completion_privateEditable_enable: bool = "false",
151 /// Custom completion snippets.
152 // NOTE: Keep this list in sync with the feature docs of user snippets.
153 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
156 "body": "Arc::new(${receiver})",
157 "requires": "std::sync::Arc",
158 "description": "Put the expression into an `Arc`",
163 "body": "Rc::new(${receiver})",
164 "requires": "std::rc::Rc",
165 "description": "Put the expression into an `Rc`",
170 "body": "Box::pin(${receiver})",
171 "requires": "std::boxed::Box",
172 "description": "Put the expression into a pinned `Box`",
177 "body": "Ok(${receiver})",
178 "description": "Wrap the expression in a `Result::Ok`",
183 "body": "Err(${receiver})",
184 "description": "Wrap the expression in a `Result::Err`",
189 "body": "Some(${receiver})",
190 "description": "Wrap the expression in an `Option::Some`",
195 /// List of rust-analyzer diagnostics to disable.
196 diagnostics_disabled: FxHashSet<String> = "[]",
197 /// Whether to show native rust-analyzer diagnostics.
198 diagnostics_enable: bool = "true",
199 /// Whether to show experimental rust-analyzer diagnostics that might
200 /// have more false positives than usual.
201 diagnostics_experimental_enable: bool = "false",
202 /// Map of prefixes to be substituted when parsing diagnostic file paths.
203 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
204 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
205 /// List of warnings that should be displayed with hint severity.
207 /// The warnings will be indicated by faded text or three dots in code
208 /// and will not show up in the `Problems Panel`.
209 diagnostics_warningsAsHint: Vec<String> = "[]",
210 /// List of warnings that should be displayed with info severity.
212 /// The warnings will be indicated by a blue squiggly underline in code
213 /// and a blue icon in the `Problems Panel`.
214 diagnostics_warningsAsInfo: Vec<String> = "[]",
216 /// These directories will be ignored by rust-analyzer. They are
217 /// relative to the workspace root, and globs are not supported. You may
218 /// also need to add the folders to Code's `files.watcherExclude`.
219 files_excludeDirs: Vec<PathBuf> = "[]",
220 /// Controls file watching implementation.
221 files_watcher: FilesWatcherDef = "\"client\"",
223 /// Exclude imports from find-all-references.
224 findAllRefs_excludeImports: bool = "false",
226 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
227 highlightRelated_breakPoints_enable: bool = "true",
228 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
229 highlightRelated_exitPoints_enable: bool = "true",
230 /// Enables highlighting of related references while the cursor is on any identifier.
231 highlightRelated_references_enable: bool = "true",
232 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
233 highlightRelated_yieldPoints_enable: bool = "true",
235 /// Whether to show `Debug` action. Only applies when
236 /// `#rust-analyzer.hover.actions.enable#` is set.
237 hover_actions_debug_enable: bool = "true",
238 /// Whether to show HoverActions in Rust files.
239 hover_actions_enable: bool = "true",
240 /// Whether to show `Go to Type Definition` action. Only applies when
241 /// `#rust-analyzer.hover.actions.enable#` is set.
242 hover_actions_gotoTypeDef_enable: bool = "true",
243 /// Whether to show `Implementations` action. Only applies when
244 /// `#rust-analyzer.hover.actions.enable#` is set.
245 hover_actions_implementations_enable: bool = "true",
246 /// Whether to show `References` action. Only applies when
247 /// `#rust-analyzer.hover.actions.enable#` is set.
248 hover_actions_references_enable: bool = "false",
249 /// Whether to show `Run` action. Only applies when
250 /// `#rust-analyzer.hover.actions.enable#` is set.
251 hover_actions_run_enable: bool = "true",
253 /// Whether to show documentation on hover.
254 hover_documentation_enable: bool = "true",
255 /// Whether to show keyword hover popups. Only applies when
256 /// `#rust-analyzer.hover.documentation.enable#` is set.
257 hover_documentation_keywords_enable: bool = "true",
258 /// Use markdown syntax for links in hover.
259 hover_links_enable: bool = "true",
261 /// 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.
262 imports_granularity_enforce: bool = "false",
263 /// How imports should be grouped into use statements.
264 imports_granularity_group: ImportGranularityDef = "\"crate\"",
265 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
266 imports_group_enable: bool = "true",
267 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
268 imports_merge_glob: bool = "true",
269 /// The path structure for newly inserted paths to use.
270 imports_prefix: ImportPrefixDef = "\"plain\"",
272 /// Whether to show inlay type hints for binding modes.
273 inlayHints_bindingModeHints_enable: bool = "false",
274 /// Whether to show inlay type hints for method chains.
275 inlayHints_chainingHints_enable: bool = "true",
276 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
277 inlayHints_closingBraceHints_enable: bool = "true",
278 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
279 /// to always show them).
280 inlayHints_closingBraceHints_minLines: usize = "25",
281 /// Whether to show inlay type hints for return types of closures.
282 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
283 /// Whether to show inlay type hints for elided lifetimes in function signatures.
284 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
285 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
286 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
287 /// Maximum length for inlay hints. Set to null to have an unlimited length.
288 inlayHints_maxLength: Option<usize> = "25",
289 /// Whether to show function parameter name inlay hints at the call
291 inlayHints_parameterHints_enable: bool = "true",
292 /// Whether to show inlay type hints for compiler inserted reborrows.
293 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
294 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
295 inlayHints_renderColons: bool = "true",
296 /// Whether to show inlay type hints for variables.
297 inlayHints_typeHints_enable: bool = "true",
298 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
299 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
300 inlayHints_typeHints_hideClosureInitialization: bool = "false",
301 /// Whether to hide inlay type hints for constructors.
302 inlayHints_typeHints_hideNamedConstructor: bool = "false",
304 /// Join lines merges consecutive declaration and initialization of an assignment.
305 joinLines_joinAssignments: bool = "true",
306 /// Join lines inserts else between consecutive ifs.
307 joinLines_joinElseIf: bool = "true",
308 /// Join lines removes trailing commas.
309 joinLines_removeTrailingComma: bool = "true",
310 /// Join lines unwraps trivial blocks.
311 joinLines_unwrapTrivialBlock: bool = "true",
313 /// Whether to show `Debug` lens. Only applies when
314 /// `#rust-analyzer.lens.enable#` is set.
315 lens_debug_enable: bool = "true",
316 /// Whether to show CodeLens in Rust files.
317 lens_enable: bool = "true",
318 /// Internal config: use custom client-side commands even when the
319 /// client doesn't set the corresponding capability.
320 lens_forceCustomCommands: bool = "true",
321 /// Whether to show `Implementations` lens. Only applies when
322 /// `#rust-analyzer.lens.enable#` is set.
323 lens_implementations_enable: bool = "true",
324 /// Whether to show `References` lens for Struct, Enum, and Union.
325 /// Only applies when `#rust-analyzer.lens.enable#` is set.
326 lens_references_adt_enable: bool = "false",
327 /// Whether to show `References` lens for Enum Variants.
328 /// Only applies when `#rust-analyzer.lens.enable#` is set.
329 lens_references_enumVariant_enable: bool = "false",
330 /// Whether to show `Method References` lens. Only applies when
331 /// `#rust-analyzer.lens.enable#` is set.
332 lens_references_method_enable: bool = "false",
333 /// Whether to show `References` lens for Trait.
334 /// Only applies when `#rust-analyzer.lens.enable#` is set.
335 lens_references_trait_enable: bool = "false",
336 /// Whether to show `Run` lens. Only applies when
337 /// `#rust-analyzer.lens.enable#` is set.
338 lens_run_enable: bool = "true",
340 /// Disable project auto-discovery in favor of explicitly specified set
343 /// Elements must be paths pointing to `Cargo.toml`,
344 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
345 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
347 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
348 lru_capacity: Option<usize> = "null",
350 /// Whether to show `can't find Cargo.toml` error message.
351 notifications_cargoTomlNotFound: bool = "true",
353 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
354 procMacro_attributes_enable: bool = "true",
355 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
356 procMacro_enable: bool = "true",
357 /// These proc-macros will be ignored when trying to expand them.
359 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
360 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
361 /// Internal config, path to proc-macro server executable (typically,
362 /// this is rust-analyzer itself, but we override this in tests).
363 procMacro_server: Option<PathBuf> = "null",
365 /// Command to be executed instead of 'cargo' for runnables.
366 runnables_command: Option<String> = "null",
367 /// Additional arguments to be passed to cargo for runnables such as
368 /// tests or binaries. For example, it may be `--release`.
369 runnables_extraArgs: Vec<String> = "[]",
371 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
372 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
375 /// Any project which uses rust-analyzer with the rustcPrivate
376 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
378 /// This option does not take effect until rust-analyzer is restarted.
379 rustc_source: Option<String> = "null",
381 /// Additional arguments to `rustfmt`.
382 rustfmt_extraArgs: Vec<String> = "[]",
383 /// Advanced option, fully override the command rust-analyzer uses for
385 rustfmt_overrideCommand: Option<Vec<String>> = "null",
386 /// Enables the use of rustfmt's unstable range formatting command for the
387 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
388 /// available on a nightly build.
389 rustfmt_rangeFormatting_enable: bool = "false",
391 /// Inject additional highlighting into doc comments.
393 /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
395 semanticHighlighting_doc_comment_inject_enable: bool = "true",
396 /// Use semantic tokens for operators.
398 /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
399 /// they are tagged with modifiers.
400 semanticHighlighting_operator_enable: bool = "true",
401 /// Use specialized semantic tokens for operators.
403 /// When enabled, rust-analyzer will emit special token types for operator tokens instead
404 /// of the generic `operator` token type.
405 semanticHighlighting_operator_specialization_enable: bool = "false",
406 /// Use semantic tokens for punctuations.
408 /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
409 /// they are tagged with modifiers or have a special role.
410 semanticHighlighting_punctuation_enable: bool = "false",
411 /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
413 semanticHighlighting_punctuation_separate_macro_bang: bool = "false",
414 /// Use specialized semantic tokens for punctuations.
416 /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
417 /// of the generic `punctuation` token type.
418 semanticHighlighting_punctuation_specialization_enable: bool = "false",
419 /// Use semantic tokens for strings.
421 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
422 /// By disabling semantic tokens for strings, other grammars can be used to highlight
424 semanticHighlighting_strings_enable: bool = "true",
426 /// Show full signature of the callable. Only shows parameters if disabled.
427 signatureInfo_detail: SignatureDetail = "\"full\"",
428 /// Show documentation.
429 signatureInfo_documentation_enable: bool = "true",
431 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
432 typing_autoClosingAngleBrackets_enable: bool = "false",
434 /// Workspace symbol search kind.
435 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
436 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
437 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
438 /// Other clients requires all results upfront and might require a higher limit.
439 workspace_symbol_search_limit: usize = "128",
440 /// Workspace symbol search scope.
441 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
445 impl Default for ConfigData {
446 fn default() -> Self {
447 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
451 #[derive(Debug, Clone)]
453 pub discovered_projects: Option<Vec<ProjectManifest>>,
454 caps: lsp_types::ClientCapabilities,
455 root_path: AbsPathBuf,
457 detached_files: Vec<AbsPathBuf>,
458 snippets: Vec<Snippet>,
461 type ParallelCachePrimingNumThreads = u8;
463 #[derive(Debug, Clone, Eq, PartialEq)]
464 pub enum LinkedProject {
465 ProjectManifest(ProjectManifest),
466 InlineJsonProject(ProjectJson),
469 impl From<ProjectManifest> for LinkedProject {
470 fn from(v: ProjectManifest) -> Self {
471 LinkedProject::ProjectManifest(v)
475 impl From<ProjectJson> for LinkedProject {
476 fn from(v: ProjectJson) -> Self {
477 LinkedProject::InlineJsonProject(v)
481 pub struct CallInfoConfig {
482 pub params_only: bool,
486 #[derive(Clone, Debug, PartialEq, Eq)]
487 pub struct LensConfig {
493 pub implementations: bool,
496 pub method_refs: bool,
497 pub refs_adt: bool, // for Struct, Enum, Union and Trait
498 pub refs_trait: bool, // for Struct, Enum, Union and Trait
499 pub enum_variant_refs: bool,
503 pub fn any(&self) -> bool {
506 || self.implementations
510 || self.enum_variant_refs
513 pub fn none(&self) -> bool {
517 pub fn runnable(&self) -> bool {
518 self.run || self.debug
521 pub fn references(&self) -> bool {
522 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
526 #[derive(Clone, Debug, PartialEq, Eq)]
527 pub struct HoverActionsConfig {
528 pub implementations: bool,
529 pub references: bool,
532 pub goto_type_def: bool,
535 impl HoverActionsConfig {
536 pub const NO_ACTIONS: Self = Self {
537 implementations: false,
541 goto_type_def: false,
544 pub fn any(&self) -> bool {
545 self.implementations || self.references || self.runnable() || self.goto_type_def
548 pub fn none(&self) -> bool {
552 pub fn runnable(&self) -> bool {
553 self.run || self.debug
557 #[derive(Debug, Clone)]
558 pub struct FilesConfig {
559 pub watcher: FilesWatcher,
560 pub exclude: Vec<AbsPathBuf>,
563 #[derive(Debug, Clone)]
564 pub enum FilesWatcher {
569 #[derive(Debug, Clone)]
570 pub struct NotificationsConfig {
571 pub cargo_toml_not_found: bool,
574 #[derive(Debug, Clone)]
575 pub enum RustfmtConfig {
576 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
577 CustomCommand { command: String, args: Vec<String> },
580 /// Configuration for runnable items, such as `main` function or tests.
581 #[derive(Debug, Clone)]
582 pub struct RunnablesConfig {
583 /// Custom command to be executed instead of `cargo` for runnables.
584 pub override_cargo: Option<String>,
585 /// Additional arguments for the `cargo`, e.g. `--release`.
586 pub cargo_extra_args: Vec<String>,
589 /// Configuration for workspace symbol search requests.
590 #[derive(Debug, Clone)]
591 pub struct WorkspaceSymbolConfig {
592 /// In what scope should the symbol be searched in.
593 pub search_scope: WorkspaceSymbolSearchScope,
594 /// What kind of symbol is being searched for.
595 pub search_kind: WorkspaceSymbolSearchKind,
596 /// How many items are returned at most.
597 pub search_limit: usize,
600 pub struct ClientCommandsConfig {
601 pub run_single: bool,
602 pub debug_single: bool,
603 pub show_reference: bool,
604 pub goto_location: bool,
605 pub trigger_parameter_hints: bool,
609 pub struct ConfigUpdateError {
610 errors: Vec<(String, serde_json::Error)>,
613 impl fmt::Display for ConfigUpdateError {
614 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
622 "rust-analyzer found {} invalid config value{}:\n{}",
624 if self.errors.len() == 1 { "" } else { "s" },
631 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
634 data: ConfigData::default(),
635 detached_files: Vec::new(),
636 discovered_projects: None,
638 snippets: Default::default(),
642 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
643 tracing::info!("updating config from JSON: {:#}", json);
644 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
647 let mut errors = Vec::new();
648 self.detached_files =
649 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
651 .map(AbsPathBuf::assert)
653 patch_old_style::patch_json_for_outdated_configs(&mut json);
654 self.data = ConfigData::from_json(json, &mut errors);
655 tracing::debug!("deserialized config data: {:#?}", self.data);
656 self.snippets.clear();
657 for (name, def) in self.data.completion_snippets_custom.iter() {
658 if def.prefix.is_empty() && def.postfix.is_empty() {
661 let scope = match def.scope {
662 SnippetScopeDef::Expr => SnippetScope::Expr,
663 SnippetScopeDef::Type => SnippetScope::Type,
664 SnippetScopeDef::Item => SnippetScope::Item,
670 def.description.as_ref().unwrap_or(name),
674 Some(snippet) => self.snippets.push(snippet),
675 None => errors.push((
676 format!("snippet {name} is invalid"),
677 <serde_json::Error as serde::de::Error>::custom(
678 "snippet path is invalid or triggers are missing",
684 self.validate(&mut errors);
686 if errors.is_empty() {
689 Err(ConfigUpdateError { errors })
693 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
694 use serde::de::Error;
695 if self.data.checkOnSave_command.is_empty() {
697 "/checkOnSave/command".to_string(),
698 serde_json::Error::custom("expected a non-empty string"),
703 pub fn json_schema() -> serde_json::Value {
704 ConfigData::json_schema()
707 pub fn root_path(&self) -> &AbsPathBuf {
711 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
715 pub fn detached_files(&self) -> &[AbsPathBuf] {
722 || -> _ { Some($expr) }()
725 macro_rules! try_or {
726 ($expr:expr, $or:expr) => {
727 try_!($expr).unwrap_or($or)
731 macro_rules! try_or_def {
733 try_!($expr).unwrap_or_default()
738 pub fn linked_projects(&self) -> Vec<LinkedProject> {
739 match self.data.linkedProjects.as_slice() {
740 [] => match self.discovered_projects.as_ref() {
741 Some(discovered_projects) => {
742 let exclude_dirs: Vec<_> = self
746 .map(|p| self.root_path.join(p))
751 let (ProjectManifest::ProjectJson(path)
752 | ProjectManifest::CargoToml(path)) = p;
753 !exclude_dirs.iter().any(|p| path.starts_with(p))
756 .map(LinkedProject::from)
761 linked_projects => linked_projects
763 .filter_map(|linked_project| match linked_project {
764 ManifestOrProjectJson::Manifest(it) => {
765 let path = self.root_path.join(it);
766 ProjectManifest::from_manifest_file(path)
767 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
771 ManifestOrProjectJson::ProjectJson(it) => {
772 Some(ProjectJson::new(&self.root_path, it.clone()).into())
779 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
780 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
781 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
784 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
786 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
790 pub fn prefill_caches(&self) -> bool {
791 self.data.cachePriming_enable
794 pub fn location_link(&self) -> bool {
795 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
798 pub fn line_folding_only(&self) -> bool {
799 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
802 pub fn hierarchical_symbols(&self) -> bool {
809 .hierarchical_document_symbol_support?
813 pub fn code_action_literals(&self) -> bool {
820 .code_action_literal_support
825 pub fn work_done_progress(&self) -> bool {
826 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
829 pub fn will_rename(&self) -> bool {
830 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
833 pub fn change_annotation_support(&self) -> bool {
840 .change_annotation_support
845 pub fn code_action_resolve(&self) -> bool {
857 .any(|it| it == "edit")
860 pub fn signature_help_label_offsets(&self) -> bool {
867 .signature_information
869 .parameter_information
871 .label_offset_support?
875 pub fn completion_label_details_support(&self) -> bool {
884 .label_details_support
889 pub fn offset_encoding(&self) -> OffsetEncoding {
890 if supports_utf8(&self.caps) {
893 OffsetEncoding::Utf16
897 fn experimental(&self, index: &'static str) -> bool {
898 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
901 pub fn code_action_group(&self) -> bool {
902 self.experimental("codeActionGroup")
905 pub fn server_status_notification(&self) -> bool {
906 self.experimental("serverStatusNotification")
909 pub fn publish_diagnostics(&self) -> bool {
910 self.data.diagnostics_enable
913 pub fn diagnostics(&self) -> DiagnosticsConfig {
915 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
916 proc_macros_enabled: self.data.procMacro_enable,
917 disable_experimental: !self.data.diagnostics_experimental_enable,
918 disabled: self.data.diagnostics_disabled.clone(),
919 expr_fill_default: match self.data.assist_expressionFillDefault {
920 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
921 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
923 insert_use: self.insert_use_config(),
927 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
928 DiagnosticsMapConfig {
929 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
930 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
931 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
935 pub fn lru_capacity(&self) -> Option<usize> {
936 self.data.lru_capacity
939 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
940 if !self.data.procMacro_enable {
943 let path = match &self.data.procMacro_server {
944 Some(it) => self.root_path.join(it),
945 None => AbsPathBuf::assert(std::env::current_exe().ok()?),
947 Some((path, vec!["proc-macro".into()]))
950 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
951 &self.data.procMacro_ignored
954 pub fn expand_proc_attr_macros(&self) -> bool {
955 self.data.procMacro_enable && self.data.procMacro_attributes_enable
958 pub fn files(&self) -> FilesConfig {
960 watcher: match self.data.files_watcher {
961 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
964 _ => FilesWatcher::Server,
966 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
970 pub fn notifications(&self) -> NotificationsConfig {
971 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
974 pub fn cargo_autoreload(&self) -> bool {
975 self.data.cargo_autoreload
978 pub fn run_build_scripts(&self) -> bool {
979 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
982 pub fn cargo(&self) -> CargoConfig {
983 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
984 if rustc_src == "discover" {
985 RustcSource::Discover
987 RustcSource::Path(self.root_path.join(rustc_src))
992 no_default_features: self.data.cargo_noDefaultFeatures,
993 all_features: matches!(self.data.cargo_features, CargoFeatures::All),
994 features: match &self.data.cargo_features {
995 CargoFeatures::All => vec![],
996 CargoFeatures::Listed(it) => it.clone(),
998 target: self.data.cargo_target.clone(),
999 no_sysroot: self.data.cargo_noSysroot,
1001 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
1002 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1003 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
1007 pub fn rustfmt(&self) -> RustfmtConfig {
1008 match &self.data.rustfmt_overrideCommand {
1009 Some(args) if !args.is_empty() => {
1010 let mut args = args.clone();
1011 let command = args.remove(0);
1012 RustfmtConfig::CustomCommand { command, args }
1014 Some(_) | None => RustfmtConfig::Rustfmt {
1015 extra_args: self.data.rustfmt_extraArgs.clone(),
1016 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
1021 pub fn flycheck(&self) -> Option<FlycheckConfig> {
1022 if !self.data.checkOnSave_enable {
1025 let flycheck_config = match &self.data.checkOnSave_overrideCommand {
1026 Some(args) if !args.is_empty() => {
1027 let mut args = args.clone();
1028 let command = args.remove(0);
1029 FlycheckConfig::CustomCommand { command, args }
1031 Some(_) | None => FlycheckConfig::CargoCommand {
1032 command: self.data.checkOnSave_command.clone(),
1037 .or_else(|| self.data.cargo_target.clone()),
1038 all_targets: self.data.checkOnSave_allTargets,
1039 no_default_features: self
1041 .checkOnSave_noDefaultFeatures
1042 .unwrap_or(self.data.cargo_noDefaultFeatures),
1043 all_features: matches!(
1044 self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
1047 features: match self
1049 .checkOnSave_features
1051 .unwrap_or_else(|| self.data.cargo_features.clone())
1053 CargoFeatures::All => vec![],
1054 CargoFeatures::Listed(it) => it,
1056 extra_args: self.data.checkOnSave_extraArgs.clone(),
1059 Some(flycheck_config)
1062 pub fn runnables(&self) -> RunnablesConfig {
1064 override_cargo: self.data.runnables_command.clone(),
1065 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1069 pub fn inlay_hints(&self) -> InlayHintsConfig {
1071 render_colons: self.data.inlayHints_renderColons,
1072 type_hints: self.data.inlayHints_typeHints_enable,
1073 parameter_hints: self.data.inlayHints_parameterHints_enable,
1074 chaining_hints: self.data.inlayHints_chainingHints_enable,
1075 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1076 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1077 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1078 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1080 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1081 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1082 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1083 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1085 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1086 hide_closure_initialization_hints: self
1088 .inlayHints_typeHints_hideClosureInitialization,
1089 reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1090 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1091 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1092 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1094 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1095 param_names_for_lifetime_elision_hints: self
1097 .inlayHints_lifetimeElisionHints_useParameterNames,
1098 max_length: self.data.inlayHints_maxLength,
1099 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1100 Some(self.data.inlayHints_closingBraceHints_minLines)
1107 fn insert_use_config(&self) -> InsertUseConfig {
1109 granularity: match self.data.imports_granularity_group {
1110 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1111 ImportGranularityDef::Item => ImportGranularity::Item,
1112 ImportGranularityDef::Crate => ImportGranularity::Crate,
1113 ImportGranularityDef::Module => ImportGranularity::Module,
1115 enforce_granularity: self.data.imports_granularity_enforce,
1116 prefix_kind: match self.data.imports_prefix {
1117 ImportPrefixDef::Plain => PrefixKind::Plain,
1118 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1119 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1121 group: self.data.imports_group_enable,
1122 skip_glob_imports: !self.data.imports_merge_glob,
1126 pub fn completion(&self) -> CompletionConfig {
1128 enable_postfix_completions: self.data.completion_postfix_enable,
1129 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1130 && completion_item_edit_resolve(&self.caps),
1131 enable_self_on_the_fly: self.data.completion_autoself_enable,
1132 enable_private_editable: self.data.completion_privateEditable_enable,
1133 callable: match self.data.completion_callable_snippets {
1134 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1135 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1136 CallableCompletionDef::None => None,
1138 insert_use: self.insert_use_config(),
1139 snippet_cap: SnippetCap::new(try_or_def!(
1149 snippets: self.snippets.clone(),
1153 pub fn find_all_refs_exclude_imports(&self) -> bool {
1154 self.data.findAllRefs_excludeImports
1157 pub fn snippet_cap(&self) -> bool {
1158 self.experimental("snippetTextEdit")
1161 pub fn assist(&self) -> AssistConfig {
1163 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1165 insert_use: self.insert_use_config(),
1169 pub fn join_lines(&self) -> JoinLinesConfig {
1171 join_else_if: self.data.joinLines_joinElseIf,
1172 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1173 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1174 join_assignments: self.data.joinLines_joinAssignments,
1178 pub fn call_info(&self) -> CallInfoConfig {
1180 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1181 docs: self.data.signatureInfo_documentation_enable,
1185 pub fn lens(&self) -> LensConfig {
1187 run: self.data.lens_enable && self.data.lens_run_enable,
1188 debug: self.data.lens_enable && self.data.lens_debug_enable,
1189 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1190 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1191 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1192 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1193 enum_variant_refs: self.data.lens_enable
1194 && self.data.lens_references_enumVariant_enable,
1198 pub fn hover_actions(&self) -> HoverActionsConfig {
1199 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1200 HoverActionsConfig {
1201 implementations: enable && self.data.hover_actions_implementations_enable,
1202 references: enable && self.data.hover_actions_references_enable,
1203 run: enable && self.data.hover_actions_run_enable,
1204 debug: enable && self.data.hover_actions_debug_enable,
1205 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1209 pub fn highlighting_config(&self) -> HighlightConfig {
1211 strings: self.data.semanticHighlighting_strings_enable,
1212 punctuation: self.data.semanticHighlighting_punctuation_enable,
1213 specialize_punctuation: self
1215 .semanticHighlighting_punctuation_specialization_enable,
1216 macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang,
1217 operator: self.data.semanticHighlighting_operator_enable,
1218 specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
1219 inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable,
1220 syntactic_name_ref_highlighting: false,
1224 pub fn hover(&self) -> HoverConfig {
1226 links_in_hover: self.data.hover_links_enable,
1227 documentation: self.data.hover_documentation_enable.then(|| {
1228 let is_markdown = try_or_def!(self
1237 .contains(&MarkupKind::Markdown);
1239 HoverDocFormat::Markdown
1241 HoverDocFormat::PlainText
1244 keywords: self.data.hover_documentation_keywords_enable,
1248 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1249 WorkspaceSymbolConfig {
1250 search_scope: match self.data.workspace_symbol_search_scope {
1251 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1252 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1253 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1256 search_kind: match self.data.workspace_symbol_search_kind {
1257 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1258 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1260 search_limit: self.data.workspace_symbol_search_limit,
1264 pub fn semantic_tokens_refresh(&self) -> bool {
1265 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1268 pub fn code_lens_refresh(&self) -> bool {
1269 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1272 pub fn insert_replace_support(&self) -> bool {
1281 .insert_replace_support?
1285 pub fn client_commands(&self) -> ClientCommandsConfig {
1287 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1288 let commands: Option<lsp_ext::ClientCommandOptions> =
1289 serde_json::from_value(commands.clone()).ok();
1290 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1291 let commands = commands.map(|it| it.commands).unwrap_or_default();
1293 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1295 ClientCommandsConfig {
1296 run_single: get("rust-analyzer.runSingle"),
1297 debug_single: get("rust-analyzer.debugSingle"),
1298 show_reference: get("rust-analyzer.showReferences"),
1299 goto_location: get("rust-analyzer.gotoLocation"),
1300 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1304 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1305 HighlightRelatedConfig {
1306 references: self.data.highlightRelated_references_enable,
1307 break_points: self.data.highlightRelated_breakPoints_enable,
1308 exit_points: self.data.highlightRelated_exitPoints_enable,
1309 yield_points: self.data.highlightRelated_yieldPoints_enable,
1313 pub fn prime_caches_num_threads(&self) -> u8 {
1314 match self.data.cachePriming_numThreads {
1315 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1320 pub fn typing_autoclose_angle(&self) -> bool {
1321 self.data.typing_autoClosingAngleBrackets_enable
1324 // Deserialization definitions
1326 macro_rules! create_bool_or_string_de {
1327 ($ident:ident<$bool:literal, $string:literal>) => {
1328 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1330 D: serde::Deserializer<'de>,
1333 impl<'de> serde::de::Visitor<'de> for V {
1336 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1337 formatter.write_str(concat!(
1340 stringify!($string),
1345 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1347 E: serde::de::Error,
1351 _ => Err(serde::de::Error::invalid_value(
1352 serde::de::Unexpected::Bool(v),
1358 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1360 E: serde::de::Error,
1364 _ => Err(serde::de::Error::invalid_value(
1365 serde::de::Unexpected::Str(v),
1371 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1373 A: serde::de::EnumAccess<'de>,
1375 use serde::de::VariantAccess;
1376 let (variant, va) = a.variant::<&'de str>()?;
1380 _ => Err(serde::de::Error::invalid_value(
1381 serde::de::Unexpected::Str(variant),
1387 d.deserialize_any(V)
1391 create_bool_or_string_de!(true_or_always<true, "always">);
1392 create_bool_or_string_de!(false_or_never<false, "never">);
1394 macro_rules! named_unit_variant {
1395 ($variant:ident) => {
1396 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1398 D: serde::Deserializer<'de>,
1401 impl<'de> serde::de::Visitor<'de> for V {
1403 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1404 f.write_str(concat!("\"", stringify!($variant), "\""))
1406 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1407 if value == stringify!($variant) {
1410 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1414 deserializer.deserialize_str(V)
1420 named_unit_variant!(all);
1421 named_unit_variant!(skip_trivial);
1422 named_unit_variant!(mutable);
1423 named_unit_variant!(with_block);
1426 #[derive(Deserialize, Debug, Clone, Copy)]
1427 #[serde(rename_all = "snake_case")]
1428 enum SnippetScopeDef {
1434 impl Default for SnippetScopeDef {
1435 fn default() -> Self {
1436 SnippetScopeDef::Expr
1440 #[derive(Deserialize, Debug, Clone, Default)]
1443 #[serde(deserialize_with = "single_or_array")]
1444 prefix: Vec<String>,
1445 #[serde(deserialize_with = "single_or_array")]
1446 postfix: Vec<String>,
1447 description: Option<String>,
1448 #[serde(deserialize_with = "single_or_array")]
1450 #[serde(deserialize_with = "single_or_array")]
1451 requires: Vec<String>,
1452 scope: SnippetScopeDef,
1455 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1457 D: serde::Deserializer<'de>,
1461 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1462 type Value = Vec<String>;
1464 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1465 formatter.write_str("string or array of strings")
1468 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1470 E: serde::de::Error,
1472 Ok(vec![value.to_owned()])
1475 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1477 A: serde::de::SeqAccess<'de>,
1479 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1483 deserializer.deserialize_any(SingleOrVec)
1486 #[derive(Deserialize, Debug, Clone)]
1488 enum ManifestOrProjectJson {
1490 ProjectJson(ProjectJsonData),
1493 #[derive(Deserialize, Debug, Clone)]
1494 #[serde(rename_all = "snake_case")]
1495 enum ExprFillDefaultDef {
1500 #[derive(Deserialize, Debug, Clone)]
1501 #[serde(rename_all = "snake_case")]
1502 enum ImportGranularityDef {
1509 #[derive(Deserialize, Debug, Copy, Clone)]
1510 #[serde(rename_all = "snake_case")]
1511 enum CallableCompletionDef {
1517 #[derive(Deserialize, Debug, Clone)]
1519 enum CargoFeatures {
1520 #[serde(deserialize_with = "de_unit_v::all")]
1522 Listed(Vec<String>),
1525 #[derive(Deserialize, Debug, Clone)]
1527 enum LifetimeElisionDef {
1528 #[serde(deserialize_with = "true_or_always")]
1530 #[serde(deserialize_with = "false_or_never")]
1532 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1536 #[derive(Deserialize, Debug, Clone)]
1538 enum ClosureReturnTypeHintsDef {
1539 #[serde(deserialize_with = "true_or_always")]
1541 #[serde(deserialize_with = "false_or_never")]
1543 #[serde(deserialize_with = "de_unit_v::with_block")]
1547 #[derive(Deserialize, Debug, Clone)]
1549 enum ReborrowHintsDef {
1550 #[serde(deserialize_with = "true_or_always")]
1552 #[serde(deserialize_with = "false_or_never")]
1554 #[serde(deserialize_with = "de_unit_v::mutable")]
1558 #[derive(Deserialize, Debug, Clone)]
1559 #[serde(rename_all = "snake_case")]
1560 enum FilesWatcherDef {
1566 #[derive(Deserialize, Debug, Clone)]
1567 #[serde(rename_all = "snake_case")]
1568 enum ImportPrefixDef {
1570 #[serde(alias = "self")]
1572 #[serde(alias = "crate")]
1576 #[derive(Deserialize, Debug, Clone)]
1577 #[serde(rename_all = "snake_case")]
1578 enum WorkspaceSymbolSearchScopeDef {
1580 WorkspaceAndDependencies,
1583 #[derive(Deserialize, Debug, Clone)]
1584 #[serde(rename_all = "snake_case")]
1585 enum SignatureDetail {
1590 #[derive(Deserialize, Debug, Clone)]
1591 #[serde(rename_all = "snake_case")]
1592 enum WorkspaceSymbolSearchKindDef {
1597 macro_rules! _config_data {
1598 (struct $name:ident {
1600 $(#[doc=$doc:literal])*
1601 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1604 #[allow(non_snake_case)]
1605 #[derive(Debug, Clone)]
1606 struct $name { $($field: $ty,)* }
1608 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1614 None$(.or(Some(stringify!($alias))))*,
1620 fn json_schema() -> serde_json::Value {
1623 let field = stringify!($field);
1624 let ty = stringify!($ty);
1626 (field, ty, &[$($doc),*], $default)
1632 fn manual() -> String {
1635 let field = stringify!($field);
1636 let ty = stringify!($ty);
1638 (field, ty, &[$($doc),*], $default)
1645 fn fields_are_sorted() {
1646 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1650 use _config_data as config_data;
1652 fn get_field<T: DeserializeOwned>(
1653 json: &mut serde_json::Value,
1654 error_sink: &mut Vec<(String, serde_json::Error)>,
1655 field: &'static str,
1656 alias: Option<&'static str>,
1659 let default = serde_json::from_str(default).unwrap();
1660 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1661 // defaults instead of sending an empty object.
1664 .chain(iter::once(field))
1665 .find_map(move |field| {
1666 let mut pointer = field.replace('_', "/");
1667 pointer.insert(0, '/');
1668 json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1671 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1672 error_sink.push((pointer, e));
1680 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1681 for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1682 fn key(f: &str) -> &str {
1683 f.splitn(2, '_').next().unwrap()
1685 assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1690 .map(|(field, ty, doc, default)| {
1691 let name = field.replace('_', ".");
1692 let name = format!("rust-analyzer.{}", name);
1693 let props = field_props(field, ty, doc, default);
1696 .collect::<serde_json::Map<_, _>>();
1700 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1701 let doc = doc_comment_to_string(doc);
1702 let doc = doc.trim_end_matches('\n');
1704 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1705 "bad docs for {}: {:?}",
1709 let default = default.parse::<serde_json::Value>().unwrap();
1711 let mut map = serde_json::Map::default();
1713 ($($key:literal: $value:tt),*$(,)?) => {{$(
1714 map.insert($key.into(), serde_json::json!($value));
1717 set!("markdownDescription": doc);
1718 set!("default": default);
1721 "bool" => set!("type": "boolean"),
1722 "usize" => set!("type": "integer", "minimum": 0),
1723 "String" => set!("type": "string"),
1724 "Vec<String>" => set! {
1726 "items": { "type": "string" },
1728 "Vec<PathBuf>" => set! {
1730 "items": { "type": "string" },
1732 "FxHashSet<String>" => set! {
1734 "items": { "type": "string" },
1735 "uniqueItems": true,
1737 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1740 "FxHashMap<String, SnippetDef>" => set! {
1743 "FxHashMap<String, String>" => set! {
1746 "Option<usize>" => set! {
1747 "type": ["null", "integer"],
1750 "Option<String>" => set! {
1751 "type": ["null", "string"],
1753 "Option<PathBuf>" => set! {
1754 "type": ["null", "string"],
1756 "Option<bool>" => set! {
1757 "type": ["null", "boolean"],
1759 "Option<Vec<String>>" => set! {
1760 "type": ["null", "array"],
1761 "items": { "type": "string" },
1763 "MergeBehaviorDef" => set! {
1765 "enum": ["none", "crate", "module"],
1766 "enumDescriptions": [
1767 "Do not merge imports at all.",
1768 "Merge imports from the same crate into a single `use` statement.",
1769 "Merge imports from the same module into a single `use` statement."
1772 "ExprFillDefaultDef" => set! {
1774 "enum": ["todo", "default"],
1775 "enumDescriptions": [
1776 "Fill missing expressions with the `todo` macro",
1777 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1780 "ImportGranularityDef" => set! {
1782 "enum": ["preserve", "crate", "module", "item"],
1783 "enumDescriptions": [
1784 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1785 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1786 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1787 "Flatten imports so that each has its own use statement."
1790 "ImportPrefixDef" => set! {
1797 "enumDescriptions": [
1798 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1799 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item. Prefixes `self` in front of the path if it starts with a module.",
1800 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1803 "Vec<ManifestOrProjectJson>" => set! {
1805 "items": { "type": ["string", "object"] },
1807 "WorkspaceSymbolSearchScopeDef" => set! {
1809 "enum": ["workspace", "workspace_and_dependencies"],
1810 "enumDescriptions": [
1811 "Search in current workspace only.",
1812 "Search in current workspace and dependencies."
1815 "WorkspaceSymbolSearchKindDef" => set! {
1817 "enum": ["only_types", "all_symbols"],
1818 "enumDescriptions": [
1819 "Search for types only.",
1820 "Search for all symbols kinds."
1823 "ParallelCachePrimingNumThreads" => set! {
1828 "LifetimeElisionDef" => set! {
1835 "enumDescriptions": [
1836 "Always show lifetime elision hints.",
1837 "Never show lifetime elision hints.",
1838 "Only show lifetime elision hints if a return type is involved."
1841 "ClosureReturnTypeHintsDef" => set! {
1848 "enumDescriptions": [
1849 "Always show type hints for return types of closures.",
1850 "Never show type hints for return types of closures.",
1851 "Only show type hints for return types of closures with blocks."
1854 "ReborrowHintsDef" => set! {
1861 "enumDescriptions": [
1862 "Always show reborrow hints.",
1863 "Never show reborrow hints.",
1864 "Only show mutable reborrow hints."
1867 "CargoFeatures" => set! {
1874 "enumDescriptions": [
1875 "Pass `--all-features` to cargo",
1880 "items": { "type": "string" }
1884 "Option<CargoFeatures>" => set! {
1891 "enumDescriptions": [
1892 "Pass `--all-features` to cargo",
1897 "items": { "type": "string" }
1902 "CallableCompletionDef" => set! {
1909 "enumDescriptions": [
1910 "Add call parentheses and pre-fill arguments.",
1911 "Add call parentheses.",
1912 "Do no snippet completions for callables."
1915 "SignatureDetail" => set! {
1917 "enum": ["full", "parameters"],
1918 "enumDescriptions": [
1919 "Show the entire signature.",
1920 "Show only the parameters."
1923 "FilesWatcherDef" => set! {
1925 "enum": ["client", "server"],
1926 "enumDescriptions": [
1927 "Use the client (editor) to watch files for changes",
1928 "Use server-side file watching",
1931 _ => panic!("missing entry for {}: {}", ty, default),
1938 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1941 .map(|(field, _ty, doc, default)| {
1942 let name = format!("rust-analyzer.{}", field.replace('_', "."));
1943 let doc = doc_comment_to_string(*doc);
1944 if default.contains('\n') {
1956 name, name, default, doc
1959 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1962 .collect::<String>()
1965 fn doc_comment_to_string(doc: &[&str]) -> String {
1966 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1973 use test_utils::{ensure_file_contents, project_root};
1978 fn generate_package_json_config() {
1979 let s = Config::json_schema();
1980 let schema = format!("{:#}", s);
1981 let mut schema = schema
1982 .trim_start_matches('{')
1983 .trim_end_matches('}')
1985 .replace('\n', "\n ")
1986 .trim_start_matches('\n')
1989 schema.push_str(",\n");
1991 // Transform the asciidoc form link to markdown style.
1993 // https://link[text] => [text](https://link)
1994 let url_matches = schema.match_indices("https://");
1995 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1996 url_offsets.reverse();
1997 for idx in url_offsets {
1998 let link = &schema[idx..];
1999 // matching on whitespace to ignore normal links
2000 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
2001 if link.chars().nth(link_end) == Some('[') {
2002 if let Some(link_text_end) = link.find(']') {
2003 let link_text = link[link_end..(link_text_end + 1)].to_string();
2005 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
2006 schema.insert(idx, '(');
2007 schema.insert(idx + link_end + 1, ')');
2008 schema.insert_str(idx, &link_text);
2014 let package_json_path = project_root().join("editors/code/package.json");
2015 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
2017 let start_marker = " \"$generated-start\": {},\n";
2018 let end_marker = " \"$generated-end\": {}\n";
2020 let start = package_json.find(start_marker).unwrap() + start_marker.len();
2021 let end = package_json.find(end_marker).unwrap();
2023 let p = remove_ws(&package_json[start..end]);
2024 let s = remove_ws(&schema);
2025 if !p.contains(&s) {
2026 package_json.replace_range(start..end, &schema);
2027 ensure_file_contents(&package_json_path, &package_json)
2032 fn generate_config_documentation() {
2033 let docs_path = project_root().join("docs/user/generated_config.adoc");
2034 let expected = ConfigData::manual();
2035 ensure_file_contents(&docs_path, &expected);
2038 fn remove_ws(text: &str) -> String {
2039 text.replace(char::is_whitespace, "")