]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
Rollup merge of #101088 - nicholasbishop:bishop-uefi-pdb, r=davidtwco
[rust.git] / src / tools / rust-analyzer / crates / rust-analyzer / src / config.rs
1 //! Config used by the language server.
2 //!
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.
5 //!
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.
9
10 use std::{ffi::OsString, fmt, iter, path::PathBuf};
11
12 use flycheck::FlycheckConfig;
13 use ide::{
14     AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
15     HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig,
16     Snippet, SnippetScope,
17 };
18 use ide_db::{
19     imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
20     SnippetCap,
21 };
22 use itertools::Itertools;
23 use lsp_types::{ClientCapabilities, MarkupKind};
24 use project_model::{
25     CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates,
26 };
27 use rustc_hash::{FxHashMap, FxHashSet};
28 use serde::{de::DeserializeOwned, Deserialize};
29 use vfs::AbsPathBuf;
30
31 use crate::{
32     caps::completion_item_edit_resolve,
33     diagnostics::DiagnosticsMapConfig,
34     line_index::OffsetEncoding,
35     lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
36 };
37
38 mod patch_old_style;
39
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
46
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.
50 //
51 // However, editor specific config, which the server doesn't know about, should
52 // be specified directly in `package.json`.
53 //
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.
56 config_data! {
57     struct ConfigData {
58         /// Placeholder expression to use for missing expressions in assists.
59         assist_expressionFillDefault: ExprFillDefaultDef              = "\"todo\"",
60
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",
65
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
74         /// option.
75         ///
76         /// By default, a cargo invocation will be constructed for the configured
77         /// targets and features, with the following base command line:
78         ///
79         /// ```bash
80         /// cargo check --quiet --workspace --message-format=json --all-targets
81         /// ```
82         /// .
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.
88         ///
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\"]",
99
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#`.
110         ///
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.
119         ///
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#`.
123         ///
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`).
127         ///
128         /// An example command would be:
129         ///
130         /// ```bash
131         /// cargo check --workspace --message-format=json --all-targets
132         /// ```
133         /// .
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",
138
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#"{
154             "Arc::new": {
155                 "postfix": "arc",
156                 "body": "Arc::new(${receiver})",
157                 "requires": "std::sync::Arc",
158                 "description": "Put the expression into an `Arc`",
159                 "scope": "expr"
160             },
161             "Rc::new": {
162                 "postfix": "rc",
163                 "body": "Rc::new(${receiver})",
164                 "requires": "std::rc::Rc",
165                 "description": "Put the expression into an `Rc`",
166                 "scope": "expr"
167             },
168             "Box::pin": {
169                 "postfix": "pinbox",
170                 "body": "Box::pin(${receiver})",
171                 "requires": "std::boxed::Box",
172                 "description": "Put the expression into a pinned `Box`",
173                 "scope": "expr"
174             },
175             "Ok": {
176                 "postfix": "ok",
177                 "body": "Ok(${receiver})",
178                 "description": "Wrap the expression in a `Result::Ok`",
179                 "scope": "expr"
180             },
181             "Err": {
182                 "postfix": "err",
183                 "body": "Err(${receiver})",
184                 "description": "Wrap the expression in a `Result::Err`",
185                 "scope": "expr"
186             },
187             "Some": {
188                 "postfix": "some",
189                 "body": "Some(${receiver})",
190                 "description": "Wrap the expression in an `Option::Some`",
191                 "scope": "expr"
192             }
193         }"#,
194
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.
206         ///
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.
211         ///
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> = "[]",
215
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\"",
222
223         /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
224         highlightRelated_breakPoints_enable: bool = "true",
225         /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
226         highlightRelated_exitPoints_enable: bool = "true",
227         /// Enables highlighting of related references while the cursor is on any identifier.
228         highlightRelated_references_enable: bool = "true",
229         /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
230         highlightRelated_yieldPoints_enable: bool = "true",
231
232         /// Whether to show `Debug` action. Only applies when
233         /// `#rust-analyzer.hover.actions.enable#` is set.
234         hover_actions_debug_enable: bool           = "true",
235         /// Whether to show HoverActions in Rust files.
236         hover_actions_enable: bool          = "true",
237         /// Whether to show `Go to Type Definition` action. Only applies when
238         /// `#rust-analyzer.hover.actions.enable#` is set.
239         hover_actions_gotoTypeDef_enable: bool     = "true",
240         /// Whether to show `Implementations` action. Only applies when
241         /// `#rust-analyzer.hover.actions.enable#` is set.
242         hover_actions_implementations_enable: bool = "true",
243         /// Whether to show `References` action. Only applies when
244         /// `#rust-analyzer.hover.actions.enable#` is set.
245         hover_actions_references_enable: bool      = "false",
246         /// Whether to show `Run` action. Only applies when
247         /// `#rust-analyzer.hover.actions.enable#` is set.
248         hover_actions_run_enable: bool             = "true",
249
250         /// Whether to show documentation on hover.
251         hover_documentation_enable: bool           = "true",
252         /// Whether to show keyword hover popups. Only applies when
253         /// `#rust-analyzer.hover.documentation.enable#` is set.
254         hover_documentation_keywords_enable: bool  = "true",
255         /// Use markdown syntax for links in hover.
256         hover_links_enable: bool = "true",
257
258         /// 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.
259         imports_granularity_enforce: bool              = "false",
260         /// How imports should be grouped into use statements.
261         imports_granularity_group: ImportGranularityDef  = "\"crate\"",
262         /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
263         imports_group_enable: bool                           = "true",
264         /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
265         imports_merge_glob: bool           = "true",
266         /// The path structure for newly inserted paths to use.
267         imports_prefix: ImportPrefixDef               = "\"plain\"",
268
269         /// Whether to show inlay type hints for binding modes.
270         inlayHints_bindingModeHints_enable: bool                   = "false",
271         /// Whether to show inlay type hints for method chains.
272         inlayHints_chainingHints_enable: bool                      = "true",
273         /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
274         inlayHints_closingBraceHints_enable: bool                  = "true",
275         /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
276         /// to always show them).
277         inlayHints_closingBraceHints_minLines: usize               = "25",
278         /// Whether to show inlay type hints for return types of closures.
279         inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef  = "\"never\"",
280         /// Whether to show inlay type hints for elided lifetimes in function signatures.
281         inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
282         /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
283         inlayHints_lifetimeElisionHints_useParameterNames: bool    = "false",
284         /// Maximum length for inlay hints. Set to null to have an unlimited length.
285         inlayHints_maxLength: Option<usize>                        = "25",
286         /// Whether to show function parameter name inlay hints at the call
287         /// site.
288         inlayHints_parameterHints_enable: bool                     = "true",
289         /// Whether to show inlay type hints for compiler inserted reborrows.
290         inlayHints_reborrowHints_enable: ReborrowHintsDef          = "\"never\"",
291         /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
292         inlayHints_renderColons: bool                              = "true",
293         /// Whether to show inlay type hints for variables.
294         inlayHints_typeHints_enable: bool                          = "true",
295         /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
296         /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
297         inlayHints_typeHints_hideClosureInitialization: bool       = "false",
298         /// Whether to hide inlay type hints for constructors.
299         inlayHints_typeHints_hideNamedConstructor: bool            = "false",
300
301         /// Join lines merges consecutive declaration and initialization of an assignment.
302         joinLines_joinAssignments: bool = "true",
303         /// Join lines inserts else between consecutive ifs.
304         joinLines_joinElseIf: bool = "true",
305         /// Join lines removes trailing commas.
306         joinLines_removeTrailingComma: bool = "true",
307         /// Join lines unwraps trivial blocks.
308         joinLines_unwrapTrivialBlock: bool = "true",
309
310         /// Whether to show `Debug` lens. Only applies when
311         /// `#rust-analyzer.lens.enable#` is set.
312         lens_debug_enable: bool            = "true",
313         /// Whether to show CodeLens in Rust files.
314         lens_enable: bool           = "true",
315         /// Internal config: use custom client-side commands even when the
316         /// client doesn't set the corresponding capability.
317         lens_forceCustomCommands: bool = "true",
318         /// Whether to show `Implementations` lens. Only applies when
319         /// `#rust-analyzer.lens.enable#` is set.
320         lens_implementations_enable: bool  = "true",
321         /// Whether to show `References` lens for Struct, Enum, and Union.
322         /// Only applies when `#rust-analyzer.lens.enable#` is set.
323         lens_references_adt_enable: bool = "false",
324         /// Whether to show `References` lens for Enum Variants.
325         /// Only applies when `#rust-analyzer.lens.enable#` is set.
326         lens_references_enumVariant_enable: bool = "false",
327         /// Whether to show `Method References` lens. Only applies when
328         /// `#rust-analyzer.lens.enable#` is set.
329         lens_references_method_enable: bool = "false",
330         /// Whether to show `References` lens for Trait.
331         /// Only applies when `#rust-analyzer.lens.enable#` is set.
332         lens_references_trait_enable: bool = "false",
333         /// Whether to show `Run` lens. Only applies when
334         /// `#rust-analyzer.lens.enable#` is set.
335         lens_run_enable: bool              = "true",
336
337         /// Disable project auto-discovery in favor of explicitly specified set
338         /// of projects.
339         ///
340         /// Elements must be paths pointing to `Cargo.toml`,
341         /// `rust-project.json`, or JSON objects in `rust-project.json` format.
342         linkedProjects: Vec<ManifestOrProjectJson> = "[]",
343
344         /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
345         lru_capacity: Option<usize>                 = "null",
346
347         /// Whether to show `can't find Cargo.toml` error message.
348         notifications_cargoTomlNotFound: bool      = "true",
349
350         /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
351         procMacro_attributes_enable: bool = "true",
352         /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
353         procMacro_enable: bool                     = "true",
354         /// These proc-macros will be ignored when trying to expand them.
355         ///
356         /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
357         procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>>          = "{}",
358         /// Internal config, path to proc-macro server executable (typically,
359         /// this is rust-analyzer itself, but we override this in tests).
360         procMacro_server: Option<PathBuf>          = "null",
361
362         /// Command to be executed instead of 'cargo' for runnables.
363         runnables_command: Option<String> = "null",
364         /// Additional arguments to be passed to cargo for runnables such as
365         /// tests or binaries. For example, it may be `--release`.
366         runnables_extraArgs: Vec<String>   = "[]",
367
368         /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
369         /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
370         /// is installed.
371         ///
372         /// Any project which uses rust-analyzer with the rustcPrivate
373         /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
374         ///
375         /// This option does not take effect until rust-analyzer is restarted.
376         rustc_source: Option<String> = "null",
377
378         /// Additional arguments to `rustfmt`.
379         rustfmt_extraArgs: Vec<String>               = "[]",
380         /// Advanced option, fully override the command rust-analyzer uses for
381         /// formatting.
382         rustfmt_overrideCommand: Option<Vec<String>> = "null",
383         /// Enables the use of rustfmt's unstable range formatting command for the
384         /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
385         /// available on a nightly build.
386         rustfmt_rangeFormatting_enable: bool = "false",
387
388         /// Use semantic tokens for strings.
389         ///
390         /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
391         /// By disabling semantic tokens for strings, other grammars can be used to highlight
392         /// their contents.
393         semanticHighlighting_strings_enable: bool = "true",
394
395         /// Show full signature of the callable. Only shows parameters if disabled.
396         signatureInfo_detail: SignatureDetail                           = "\"full\"",
397         /// Show documentation.
398         signatureInfo_documentation_enable: bool                       = "true",
399
400         /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
401         typing_autoClosingAngleBrackets_enable: bool = "false",
402
403         /// Workspace symbol search kind.
404         workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
405         /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
406         /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
407         /// Other clients requires all results upfront and might require a higher limit.
408         workspace_symbol_search_limit: usize = "128",
409         /// Workspace symbol search scope.
410         workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
411     }
412 }
413
414 impl Default for ConfigData {
415     fn default() -> Self {
416         ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
417     }
418 }
419
420 #[derive(Debug, Clone)]
421 pub struct Config {
422     pub discovered_projects: Option<Vec<ProjectManifest>>,
423     caps: lsp_types::ClientCapabilities,
424     root_path: AbsPathBuf,
425     data: ConfigData,
426     detached_files: Vec<AbsPathBuf>,
427     snippets: Vec<Snippet>,
428 }
429
430 type ParallelCachePrimingNumThreads = u8;
431
432 #[derive(Debug, Clone, Eq, PartialEq)]
433 pub enum LinkedProject {
434     ProjectManifest(ProjectManifest),
435     InlineJsonProject(ProjectJson),
436 }
437
438 impl From<ProjectManifest> for LinkedProject {
439     fn from(v: ProjectManifest) -> Self {
440         LinkedProject::ProjectManifest(v)
441     }
442 }
443
444 impl From<ProjectJson> for LinkedProject {
445     fn from(v: ProjectJson) -> Self {
446         LinkedProject::InlineJsonProject(v)
447     }
448 }
449
450 pub struct CallInfoConfig {
451     pub params_only: bool,
452     pub docs: bool,
453 }
454
455 #[derive(Clone, Debug, PartialEq, Eq)]
456 pub struct LensConfig {
457     // runnables
458     pub run: bool,
459     pub debug: bool,
460
461     // implementations
462     pub implementations: bool,
463
464     // references
465     pub method_refs: bool,
466     pub refs_adt: bool,   // for Struct, Enum, Union and Trait
467     pub refs_trait: bool, // for Struct, Enum, Union and Trait
468     pub enum_variant_refs: bool,
469 }
470
471 impl LensConfig {
472     pub fn any(&self) -> bool {
473         self.run
474             || self.debug
475             || self.implementations
476             || self.method_refs
477             || self.refs_adt
478             || self.refs_trait
479             || self.enum_variant_refs
480     }
481
482     pub fn none(&self) -> bool {
483         !self.any()
484     }
485
486     pub fn runnable(&self) -> bool {
487         self.run || self.debug
488     }
489
490     pub fn references(&self) -> bool {
491         self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
492     }
493 }
494
495 #[derive(Clone, Debug, PartialEq, Eq)]
496 pub struct HoverActionsConfig {
497     pub implementations: bool,
498     pub references: bool,
499     pub run: bool,
500     pub debug: bool,
501     pub goto_type_def: bool,
502 }
503
504 impl HoverActionsConfig {
505     pub const NO_ACTIONS: Self = Self {
506         implementations: false,
507         references: false,
508         run: false,
509         debug: false,
510         goto_type_def: false,
511     };
512
513     pub fn any(&self) -> bool {
514         self.implementations || self.references || self.runnable() || self.goto_type_def
515     }
516
517     pub fn none(&self) -> bool {
518         !self.any()
519     }
520
521     pub fn runnable(&self) -> bool {
522         self.run || self.debug
523     }
524 }
525
526 #[derive(Debug, Clone)]
527 pub struct FilesConfig {
528     pub watcher: FilesWatcher,
529     pub exclude: Vec<AbsPathBuf>,
530 }
531
532 #[derive(Debug, Clone)]
533 pub enum FilesWatcher {
534     Client,
535     Server,
536 }
537
538 #[derive(Debug, Clone)]
539 pub struct NotificationsConfig {
540     pub cargo_toml_not_found: bool,
541 }
542
543 #[derive(Debug, Clone)]
544 pub enum RustfmtConfig {
545     Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
546     CustomCommand { command: String, args: Vec<String> },
547 }
548
549 /// Configuration for runnable items, such as `main` function or tests.
550 #[derive(Debug, Clone)]
551 pub struct RunnablesConfig {
552     /// Custom command to be executed instead of `cargo` for runnables.
553     pub override_cargo: Option<String>,
554     /// Additional arguments for the `cargo`, e.g. `--release`.
555     pub cargo_extra_args: Vec<String>,
556 }
557
558 /// Configuration for workspace symbol search requests.
559 #[derive(Debug, Clone)]
560 pub struct WorkspaceSymbolConfig {
561     /// In what scope should the symbol be searched in.
562     pub search_scope: WorkspaceSymbolSearchScope,
563     /// What kind of symbol is being searched for.
564     pub search_kind: WorkspaceSymbolSearchKind,
565     /// How many items are returned at most.
566     pub search_limit: usize,
567 }
568
569 pub struct ClientCommandsConfig {
570     pub run_single: bool,
571     pub debug_single: bool,
572     pub show_reference: bool,
573     pub goto_location: bool,
574     pub trigger_parameter_hints: bool,
575 }
576
577 #[derive(Debug)]
578 pub struct ConfigUpdateError {
579     errors: Vec<(String, serde_json::Error)>,
580 }
581
582 impl fmt::Display for ConfigUpdateError {
583     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584         let errors = self.errors.iter().format_with("\n", |(key, e), f| {
585             f(key)?;
586             f(&": ")?;
587             f(e)
588         });
589         write!(
590             f,
591             "rust-analyzer found {} invalid config value{}:\n{}",
592             self.errors.len(),
593             if self.errors.len() == 1 { "" } else { "s" },
594             errors
595         )
596     }
597 }
598
599 impl Config {
600     pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
601         Config {
602             caps,
603             data: ConfigData::default(),
604             detached_files: Vec::new(),
605             discovered_projects: None,
606             root_path,
607             snippets: Default::default(),
608         }
609     }
610
611     pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
612         tracing::info!("updating config from JSON: {:#}", json);
613         if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
614             return Ok(());
615         }
616         let mut errors = Vec::new();
617         self.detached_files =
618             get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
619                 .into_iter()
620                 .map(AbsPathBuf::assert)
621                 .collect();
622         patch_old_style::patch_json_for_outdated_configs(&mut json);
623         self.data = ConfigData::from_json(json, &mut errors);
624         tracing::debug!("deserialized config data: {:#?}", self.data);
625         self.snippets.clear();
626         for (name, def) in self.data.completion_snippets_custom.iter() {
627             if def.prefix.is_empty() && def.postfix.is_empty() {
628                 continue;
629             }
630             let scope = match def.scope {
631                 SnippetScopeDef::Expr => SnippetScope::Expr,
632                 SnippetScopeDef::Type => SnippetScope::Type,
633                 SnippetScopeDef::Item => SnippetScope::Item,
634             };
635             match Snippet::new(
636                 &def.prefix,
637                 &def.postfix,
638                 &def.body,
639                 def.description.as_ref().unwrap_or(name),
640                 &def.requires,
641                 scope,
642             ) {
643                 Some(snippet) => self.snippets.push(snippet),
644                 None => errors.push((
645                     format!("snippet {name} is invalid"),
646                     <serde_json::Error as serde::de::Error>::custom(
647                         "snippet path is invalid or triggers are missing",
648                     ),
649                 )),
650             }
651         }
652
653         self.validate(&mut errors);
654
655         if errors.is_empty() {
656             Ok(())
657         } else {
658             Err(ConfigUpdateError { errors })
659         }
660     }
661
662     fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
663         use serde::de::Error;
664         if self.data.checkOnSave_command.is_empty() {
665             error_sink.push((
666                 "/checkOnSave/command".to_string(),
667                 serde_json::Error::custom("expected a non-empty string"),
668             ));
669         }
670     }
671
672     pub fn json_schema() -> serde_json::Value {
673         ConfigData::json_schema()
674     }
675
676     pub fn root_path(&self) -> &AbsPathBuf {
677         &self.root_path
678     }
679
680     pub fn caps(&self) -> &lsp_types::ClientCapabilities {
681         &self.caps
682     }
683
684     pub fn detached_files(&self) -> &[AbsPathBuf] {
685         &self.detached_files
686     }
687 }
688
689 macro_rules! try_ {
690     ($expr:expr) => {
691         || -> _ { Some($expr) }()
692     };
693 }
694 macro_rules! try_or {
695     ($expr:expr, $or:expr) => {
696         try_!($expr).unwrap_or($or)
697     };
698 }
699
700 macro_rules! try_or_def {
701     ($expr:expr) => {
702         try_!($expr).unwrap_or_default()
703     };
704 }
705
706 impl Config {
707     pub fn linked_projects(&self) -> Vec<LinkedProject> {
708         match self.data.linkedProjects.as_slice() {
709             [] => match self.discovered_projects.as_ref() {
710                 Some(discovered_projects) => {
711                     let exclude_dirs: Vec<_> = self
712                         .data
713                         .files_excludeDirs
714                         .iter()
715                         .map(|p| self.root_path.join(p))
716                         .collect();
717                     discovered_projects
718                         .iter()
719                         .filter(|p| {
720                             let (ProjectManifest::ProjectJson(path)
721                             | ProjectManifest::CargoToml(path)) = p;
722                             !exclude_dirs.iter().any(|p| path.starts_with(p))
723                         })
724                         .cloned()
725                         .map(LinkedProject::from)
726                         .collect()
727                 }
728                 None => Vec::new(),
729             },
730             linked_projects => linked_projects
731                 .iter()
732                 .filter_map(|linked_project| match linked_project {
733                     ManifestOrProjectJson::Manifest(it) => {
734                         let path = self.root_path.join(it);
735                         ProjectManifest::from_manifest_file(path)
736                             .map_err(|e| tracing::error!("failed to load linked project: {}", e))
737                             .ok()
738                             .map(Into::into)
739                     }
740                     ManifestOrProjectJson::ProjectJson(it) => {
741                         Some(ProjectJson::new(&self.root_path, it.clone()).into())
742                     }
743                 })
744                 .collect(),
745         }
746     }
747
748     pub fn did_save_text_document_dynamic_registration(&self) -> bool {
749         let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
750         caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
751     }
752
753     pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
754         try_or_def!(
755             self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
756         )
757     }
758
759     pub fn prefill_caches(&self) -> bool {
760         self.data.cachePriming_enable
761     }
762
763     pub fn location_link(&self) -> bool {
764         try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
765     }
766
767     pub fn line_folding_only(&self) -> bool {
768         try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
769     }
770
771     pub fn hierarchical_symbols(&self) -> bool {
772         try_or_def!(
773             self.caps
774                 .text_document
775                 .as_ref()?
776                 .document_symbol
777                 .as_ref()?
778                 .hierarchical_document_symbol_support?
779         )
780     }
781
782     pub fn code_action_literals(&self) -> bool {
783         try_!(self
784             .caps
785             .text_document
786             .as_ref()?
787             .code_action
788             .as_ref()?
789             .code_action_literal_support
790             .as_ref()?)
791         .is_some()
792     }
793
794     pub fn work_done_progress(&self) -> bool {
795         try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
796     }
797
798     pub fn will_rename(&self) -> bool {
799         try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
800     }
801
802     pub fn change_annotation_support(&self) -> bool {
803         try_!(self
804             .caps
805             .workspace
806             .as_ref()?
807             .workspace_edit
808             .as_ref()?
809             .change_annotation_support
810             .as_ref()?)
811         .is_some()
812     }
813
814     pub fn code_action_resolve(&self) -> bool {
815         try_or_def!(self
816             .caps
817             .text_document
818             .as_ref()?
819             .code_action
820             .as_ref()?
821             .resolve_support
822             .as_ref()?
823             .properties
824             .as_slice())
825         .iter()
826         .any(|it| it == "edit")
827     }
828
829     pub fn signature_help_label_offsets(&self) -> bool {
830         try_or_def!(
831             self.caps
832                 .text_document
833                 .as_ref()?
834                 .signature_help
835                 .as_ref()?
836                 .signature_information
837                 .as_ref()?
838                 .parameter_information
839                 .as_ref()?
840                 .label_offset_support?
841         )
842     }
843
844     pub fn completion_label_details_support(&self) -> bool {
845         try_!(self
846             .caps
847             .text_document
848             .as_ref()?
849             .completion
850             .as_ref()?
851             .completion_item
852             .as_ref()?
853             .label_details_support
854             .as_ref()?)
855         .is_some()
856     }
857
858     pub fn offset_encoding(&self) -> OffsetEncoding {
859         if supports_utf8(&self.caps) {
860             OffsetEncoding::Utf8
861         } else {
862             OffsetEncoding::Utf16
863         }
864     }
865
866     fn experimental(&self, index: &'static str) -> bool {
867         try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
868     }
869
870     pub fn code_action_group(&self) -> bool {
871         self.experimental("codeActionGroup")
872     }
873
874     pub fn server_status_notification(&self) -> bool {
875         self.experimental("serverStatusNotification")
876     }
877
878     pub fn publish_diagnostics(&self) -> bool {
879         self.data.diagnostics_enable
880     }
881
882     pub fn diagnostics(&self) -> DiagnosticsConfig {
883         DiagnosticsConfig {
884             proc_attr_macros_enabled: self.expand_proc_attr_macros(),
885             proc_macros_enabled: self.data.procMacro_enable,
886             disable_experimental: !self.data.diagnostics_experimental_enable,
887             disabled: self.data.diagnostics_disabled.clone(),
888             expr_fill_default: match self.data.assist_expressionFillDefault {
889                 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
890                 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
891             },
892             insert_use: self.insert_use_config(),
893         }
894     }
895
896     pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
897         DiagnosticsMapConfig {
898             remap_prefix: self.data.diagnostics_remapPrefix.clone(),
899             warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
900             warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
901         }
902     }
903
904     pub fn lru_capacity(&self) -> Option<usize> {
905         self.data.lru_capacity
906     }
907
908     pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
909         if !self.data.procMacro_enable {
910             return None;
911         }
912         let path = match &self.data.procMacro_server {
913             Some(it) => self.root_path.join(it),
914             None => AbsPathBuf::assert(std::env::current_exe().ok()?),
915         };
916         Some((path, vec!["proc-macro".into()]))
917     }
918
919     pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
920         &self.data.procMacro_ignored
921     }
922
923     pub fn expand_proc_attr_macros(&self) -> bool {
924         self.data.procMacro_enable && self.data.procMacro_attributes_enable
925     }
926
927     pub fn files(&self) -> FilesConfig {
928         FilesConfig {
929             watcher: match self.data.files_watcher {
930                 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
931                     FilesWatcher::Client
932                 }
933                 _ => FilesWatcher::Server,
934             },
935             exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
936         }
937     }
938
939     pub fn notifications(&self) -> NotificationsConfig {
940         NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
941     }
942
943     pub fn cargo_autoreload(&self) -> bool {
944         self.data.cargo_autoreload
945     }
946
947     pub fn run_build_scripts(&self) -> bool {
948         self.data.cargo_buildScripts_enable || self.data.procMacro_enable
949     }
950
951     pub fn cargo(&self) -> CargoConfig {
952         let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
953             if rustc_src == "discover" {
954                 RustcSource::Discover
955             } else {
956                 RustcSource::Path(self.root_path.join(rustc_src))
957             }
958         });
959
960         CargoConfig {
961             no_default_features: self.data.cargo_noDefaultFeatures,
962             all_features: matches!(self.data.cargo_features, CargoFeatures::All),
963             features: match &self.data.cargo_features {
964                 CargoFeatures::All => vec![],
965                 CargoFeatures::Listed(it) => it.clone(),
966             },
967             target: self.data.cargo_target.clone(),
968             no_sysroot: self.data.cargo_noSysroot,
969             rustc_source,
970             unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
971             wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
972             run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
973         }
974     }
975
976     pub fn rustfmt(&self) -> RustfmtConfig {
977         match &self.data.rustfmt_overrideCommand {
978             Some(args) if !args.is_empty() => {
979                 let mut args = args.clone();
980                 let command = args.remove(0);
981                 RustfmtConfig::CustomCommand { command, args }
982             }
983             Some(_) | None => RustfmtConfig::Rustfmt {
984                 extra_args: self.data.rustfmt_extraArgs.clone(),
985                 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
986             },
987         }
988     }
989
990     pub fn flycheck(&self) -> Option<FlycheckConfig> {
991         if !self.data.checkOnSave_enable {
992             return None;
993         }
994         let flycheck_config = match &self.data.checkOnSave_overrideCommand {
995             Some(args) if !args.is_empty() => {
996                 let mut args = args.clone();
997                 let command = args.remove(0);
998                 FlycheckConfig::CustomCommand { command, args }
999             }
1000             Some(_) | None => FlycheckConfig::CargoCommand {
1001                 command: self.data.checkOnSave_command.clone(),
1002                 target_triple: self
1003                     .data
1004                     .checkOnSave_target
1005                     .clone()
1006                     .or_else(|| self.data.cargo_target.clone()),
1007                 all_targets: self.data.checkOnSave_allTargets,
1008                 no_default_features: self
1009                     .data
1010                     .checkOnSave_noDefaultFeatures
1011                     .unwrap_or(self.data.cargo_noDefaultFeatures),
1012                 all_features: matches!(
1013                     self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
1014                     CargoFeatures::All
1015                 ),
1016                 features: match self
1017                     .data
1018                     .checkOnSave_features
1019                     .clone()
1020                     .unwrap_or_else(|| self.data.cargo_features.clone())
1021                 {
1022                     CargoFeatures::All => vec![],
1023                     CargoFeatures::Listed(it) => it,
1024                 },
1025                 extra_args: self.data.checkOnSave_extraArgs.clone(),
1026             },
1027         };
1028         Some(flycheck_config)
1029     }
1030
1031     pub fn runnables(&self) -> RunnablesConfig {
1032         RunnablesConfig {
1033             override_cargo: self.data.runnables_command.clone(),
1034             cargo_extra_args: self.data.runnables_extraArgs.clone(),
1035         }
1036     }
1037
1038     pub fn inlay_hints(&self) -> InlayHintsConfig {
1039         InlayHintsConfig {
1040             render_colons: self.data.inlayHints_renderColons,
1041             type_hints: self.data.inlayHints_typeHints_enable,
1042             parameter_hints: self.data.inlayHints_parameterHints_enable,
1043             chaining_hints: self.data.inlayHints_chainingHints_enable,
1044             closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1045                 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1046                 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1047                 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1048             },
1049             lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1050                 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1051                 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1052                 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1053             },
1054             hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1055             hide_closure_initialization_hints: self
1056                 .data
1057                 .inlayHints_typeHints_hideClosureInitialization,
1058             reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1059                 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1060                 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1061                 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1062             },
1063             binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1064             param_names_for_lifetime_elision_hints: self
1065                 .data
1066                 .inlayHints_lifetimeElisionHints_useParameterNames,
1067             max_length: self.data.inlayHints_maxLength,
1068             closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1069                 Some(self.data.inlayHints_closingBraceHints_minLines)
1070             } else {
1071                 None
1072             },
1073         }
1074     }
1075
1076     fn insert_use_config(&self) -> InsertUseConfig {
1077         InsertUseConfig {
1078             granularity: match self.data.imports_granularity_group {
1079                 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1080                 ImportGranularityDef::Item => ImportGranularity::Item,
1081                 ImportGranularityDef::Crate => ImportGranularity::Crate,
1082                 ImportGranularityDef::Module => ImportGranularity::Module,
1083             },
1084             enforce_granularity: self.data.imports_granularity_enforce,
1085             prefix_kind: match self.data.imports_prefix {
1086                 ImportPrefixDef::Plain => PrefixKind::Plain,
1087                 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1088                 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1089             },
1090             group: self.data.imports_group_enable,
1091             skip_glob_imports: !self.data.imports_merge_glob,
1092         }
1093     }
1094
1095     pub fn completion(&self) -> CompletionConfig {
1096         CompletionConfig {
1097             enable_postfix_completions: self.data.completion_postfix_enable,
1098             enable_imports_on_the_fly: self.data.completion_autoimport_enable
1099                 && completion_item_edit_resolve(&self.caps),
1100             enable_self_on_the_fly: self.data.completion_autoself_enable,
1101             enable_private_editable: self.data.completion_privateEditable_enable,
1102             callable: match self.data.completion_callable_snippets {
1103                 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1104                 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1105                 CallableCompletionDef::None => None,
1106             },
1107             insert_use: self.insert_use_config(),
1108             snippet_cap: SnippetCap::new(try_or_def!(
1109                 self.caps
1110                     .text_document
1111                     .as_ref()?
1112                     .completion
1113                     .as_ref()?
1114                     .completion_item
1115                     .as_ref()?
1116                     .snippet_support?
1117             )),
1118             snippets: self.snippets.clone(),
1119         }
1120     }
1121
1122     pub fn snippet_cap(&self) -> bool {
1123         self.experimental("snippetTextEdit")
1124     }
1125
1126     pub fn assist(&self) -> AssistConfig {
1127         AssistConfig {
1128             snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1129             allowed: None,
1130             insert_use: self.insert_use_config(),
1131         }
1132     }
1133
1134     pub fn join_lines(&self) -> JoinLinesConfig {
1135         JoinLinesConfig {
1136             join_else_if: self.data.joinLines_joinElseIf,
1137             remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1138             unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1139             join_assignments: self.data.joinLines_joinAssignments,
1140         }
1141     }
1142
1143     pub fn call_info(&self) -> CallInfoConfig {
1144         CallInfoConfig {
1145             params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1146             docs: self.data.signatureInfo_documentation_enable,
1147         }
1148     }
1149
1150     pub fn lens(&self) -> LensConfig {
1151         LensConfig {
1152             run: self.data.lens_enable && self.data.lens_run_enable,
1153             debug: self.data.lens_enable && self.data.lens_debug_enable,
1154             implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1155             method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1156             refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1157             refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1158             enum_variant_refs: self.data.lens_enable
1159                 && self.data.lens_references_enumVariant_enable,
1160         }
1161     }
1162
1163     pub fn hover_actions(&self) -> HoverActionsConfig {
1164         let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1165         HoverActionsConfig {
1166             implementations: enable && self.data.hover_actions_implementations_enable,
1167             references: enable && self.data.hover_actions_references_enable,
1168             run: enable && self.data.hover_actions_run_enable,
1169             debug: enable && self.data.hover_actions_debug_enable,
1170             goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1171         }
1172     }
1173
1174     pub fn highlighting_strings(&self) -> bool {
1175         self.data.semanticHighlighting_strings_enable
1176     }
1177
1178     pub fn hover(&self) -> HoverConfig {
1179         HoverConfig {
1180             links_in_hover: self.data.hover_links_enable,
1181             documentation: self.data.hover_documentation_enable.then(|| {
1182                 let is_markdown = try_or_def!(self
1183                     .caps
1184                     .text_document
1185                     .as_ref()?
1186                     .hover
1187                     .as_ref()?
1188                     .content_format
1189                     .as_ref()?
1190                     .as_slice())
1191                 .contains(&MarkupKind::Markdown);
1192                 if is_markdown {
1193                     HoverDocFormat::Markdown
1194                 } else {
1195                     HoverDocFormat::PlainText
1196                 }
1197             }),
1198             keywords: self.data.hover_documentation_keywords_enable,
1199         }
1200     }
1201
1202     pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1203         WorkspaceSymbolConfig {
1204             search_scope: match self.data.workspace_symbol_search_scope {
1205                 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1206                 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1207                     WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1208                 }
1209             },
1210             search_kind: match self.data.workspace_symbol_search_kind {
1211                 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1212                 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1213             },
1214             search_limit: self.data.workspace_symbol_search_limit,
1215         }
1216     }
1217
1218     pub fn semantic_tokens_refresh(&self) -> bool {
1219         try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1220     }
1221
1222     pub fn code_lens_refresh(&self) -> bool {
1223         try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1224     }
1225
1226     pub fn insert_replace_support(&self) -> bool {
1227         try_or_def!(
1228             self.caps
1229                 .text_document
1230                 .as_ref()?
1231                 .completion
1232                 .as_ref()?
1233                 .completion_item
1234                 .as_ref()?
1235                 .insert_replace_support?
1236         )
1237     }
1238
1239     pub fn client_commands(&self) -> ClientCommandsConfig {
1240         let commands =
1241             try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1242         let commands: Option<lsp_ext::ClientCommandOptions> =
1243             serde_json::from_value(commands.clone()).ok();
1244         let force = commands.is_none() && self.data.lens_forceCustomCommands;
1245         let commands = commands.map(|it| it.commands).unwrap_or_default();
1246
1247         let get = |name: &str| commands.iter().any(|it| it == name) || force;
1248
1249         ClientCommandsConfig {
1250             run_single: get("rust-analyzer.runSingle"),
1251             debug_single: get("rust-analyzer.debugSingle"),
1252             show_reference: get("rust-analyzer.showReferences"),
1253             goto_location: get("rust-analyzer.gotoLocation"),
1254             trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1255         }
1256     }
1257
1258     pub fn highlight_related(&self) -> HighlightRelatedConfig {
1259         HighlightRelatedConfig {
1260             references: self.data.highlightRelated_references_enable,
1261             break_points: self.data.highlightRelated_breakPoints_enable,
1262             exit_points: self.data.highlightRelated_exitPoints_enable,
1263             yield_points: self.data.highlightRelated_yieldPoints_enable,
1264         }
1265     }
1266
1267     pub fn prime_caches_num_threads(&self) -> u8 {
1268         match self.data.cachePriming_numThreads {
1269             0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1270             n => n,
1271         }
1272     }
1273
1274     pub fn typing_autoclose_angle(&self) -> bool {
1275         self.data.typing_autoClosingAngleBrackets_enable
1276     }
1277 }
1278 // Deserialization definitions
1279
1280 macro_rules! create_bool_or_string_de {
1281     ($ident:ident<$bool:literal, $string:literal>) => {
1282         fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1283         where
1284             D: serde::Deserializer<'de>,
1285         {
1286             struct V;
1287             impl<'de> serde::de::Visitor<'de> for V {
1288                 type Value = ();
1289
1290                 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1291                     formatter.write_str(concat!(
1292                         stringify!($bool),
1293                         " or \"",
1294                         stringify!($string),
1295                         "\""
1296                     ))
1297                 }
1298
1299                 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1300                 where
1301                     E: serde::de::Error,
1302                 {
1303                     match v {
1304                         $bool => Ok(()),
1305                         _ => Err(serde::de::Error::invalid_value(
1306                             serde::de::Unexpected::Bool(v),
1307                             &self,
1308                         )),
1309                     }
1310                 }
1311
1312                 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1313                 where
1314                     E: serde::de::Error,
1315                 {
1316                     match v {
1317                         $string => Ok(()),
1318                         _ => Err(serde::de::Error::invalid_value(
1319                             serde::de::Unexpected::Str(v),
1320                             &self,
1321                         )),
1322                     }
1323                 }
1324
1325                 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1326                 where
1327                     A: serde::de::EnumAccess<'de>,
1328                 {
1329                     use serde::de::VariantAccess;
1330                     let (variant, va) = a.variant::<&'de str>()?;
1331                     va.unit_variant()?;
1332                     match variant {
1333                         $string => Ok(()),
1334                         _ => Err(serde::de::Error::invalid_value(
1335                             serde::de::Unexpected::Str(variant),
1336                             &self,
1337                         )),
1338                     }
1339                 }
1340             }
1341             d.deserialize_any(V)
1342         }
1343     };
1344 }
1345 create_bool_or_string_de!(true_or_always<true, "always">);
1346 create_bool_or_string_de!(false_or_never<false, "never">);
1347
1348 macro_rules! named_unit_variant {
1349     ($variant:ident) => {
1350         pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1351         where
1352             D: serde::Deserializer<'de>,
1353         {
1354             struct V;
1355             impl<'de> serde::de::Visitor<'de> for V {
1356                 type Value = ();
1357                 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1358                     f.write_str(concat!("\"", stringify!($variant), "\""))
1359                 }
1360                 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1361                     if value == stringify!($variant) {
1362                         Ok(())
1363                     } else {
1364                         Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1365                     }
1366                 }
1367             }
1368             deserializer.deserialize_str(V)
1369         }
1370     };
1371 }
1372
1373 mod de_unit_v {
1374     named_unit_variant!(all);
1375     named_unit_variant!(skip_trivial);
1376     named_unit_variant!(mutable);
1377     named_unit_variant!(with_block);
1378 }
1379
1380 #[derive(Deserialize, Debug, Clone, Copy)]
1381 #[serde(rename_all = "snake_case")]
1382 enum SnippetScopeDef {
1383     Expr,
1384     Item,
1385     Type,
1386 }
1387
1388 impl Default for SnippetScopeDef {
1389     fn default() -> Self {
1390         SnippetScopeDef::Expr
1391     }
1392 }
1393
1394 #[derive(Deserialize, Debug, Clone, Default)]
1395 #[serde(default)]
1396 struct SnippetDef {
1397     #[serde(deserialize_with = "single_or_array")]
1398     prefix: Vec<String>,
1399     #[serde(deserialize_with = "single_or_array")]
1400     postfix: Vec<String>,
1401     description: Option<String>,
1402     #[serde(deserialize_with = "single_or_array")]
1403     body: Vec<String>,
1404     #[serde(deserialize_with = "single_or_array")]
1405     requires: Vec<String>,
1406     scope: SnippetScopeDef,
1407 }
1408
1409 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1410 where
1411     D: serde::Deserializer<'de>,
1412 {
1413     struct SingleOrVec;
1414
1415     impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1416         type Value = Vec<String>;
1417
1418         fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1419             formatter.write_str("string or array of strings")
1420         }
1421
1422         fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1423         where
1424             E: serde::de::Error,
1425         {
1426             Ok(vec![value.to_owned()])
1427         }
1428
1429         fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1430         where
1431             A: serde::de::SeqAccess<'de>,
1432         {
1433             Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1434         }
1435     }
1436
1437     deserializer.deserialize_any(SingleOrVec)
1438 }
1439
1440 #[derive(Deserialize, Debug, Clone)]
1441 #[serde(untagged)]
1442 enum ManifestOrProjectJson {
1443     Manifest(PathBuf),
1444     ProjectJson(ProjectJsonData),
1445 }
1446
1447 #[derive(Deserialize, Debug, Clone)]
1448 #[serde(rename_all = "snake_case")]
1449 enum ExprFillDefaultDef {
1450     Todo,
1451     Default,
1452 }
1453
1454 #[derive(Deserialize, Debug, Clone)]
1455 #[serde(rename_all = "snake_case")]
1456 enum ImportGranularityDef {
1457     Preserve,
1458     Item,
1459     Crate,
1460     Module,
1461 }
1462
1463 #[derive(Deserialize, Debug, Copy, Clone)]
1464 #[serde(rename_all = "snake_case")]
1465 enum CallableCompletionDef {
1466     FillArguments,
1467     AddParentheses,
1468     None,
1469 }
1470
1471 #[derive(Deserialize, Debug, Clone)]
1472 #[serde(untagged)]
1473 enum CargoFeatures {
1474     #[serde(deserialize_with = "de_unit_v::all")]
1475     All,
1476     Listed(Vec<String>),
1477 }
1478
1479 #[derive(Deserialize, Debug, Clone)]
1480 #[serde(untagged)]
1481 enum LifetimeElisionDef {
1482     #[serde(deserialize_with = "true_or_always")]
1483     Always,
1484     #[serde(deserialize_with = "false_or_never")]
1485     Never,
1486     #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1487     SkipTrivial,
1488 }
1489
1490 #[derive(Deserialize, Debug, Clone)]
1491 #[serde(untagged)]
1492 enum ClosureReturnTypeHintsDef {
1493     #[serde(deserialize_with = "true_or_always")]
1494     Always,
1495     #[serde(deserialize_with = "false_or_never")]
1496     Never,
1497     #[serde(deserialize_with = "de_unit_v::with_block")]
1498     WithBlock,
1499 }
1500
1501 #[derive(Deserialize, Debug, Clone)]
1502 #[serde(untagged)]
1503 enum ReborrowHintsDef {
1504     #[serde(deserialize_with = "true_or_always")]
1505     Always,
1506     #[serde(deserialize_with = "false_or_never")]
1507     Never,
1508     #[serde(deserialize_with = "de_unit_v::mutable")]
1509     Mutable,
1510 }
1511
1512 #[derive(Deserialize, Debug, Clone)]
1513 #[serde(rename_all = "snake_case")]
1514 enum FilesWatcherDef {
1515     Client,
1516     Notify,
1517     Server,
1518 }
1519
1520 #[derive(Deserialize, Debug, Clone)]
1521 #[serde(rename_all = "snake_case")]
1522 enum ImportPrefixDef {
1523     Plain,
1524     #[serde(alias = "self")]
1525     BySelf,
1526     #[serde(alias = "crate")]
1527     ByCrate,
1528 }
1529
1530 #[derive(Deserialize, Debug, Clone)]
1531 #[serde(rename_all = "snake_case")]
1532 enum WorkspaceSymbolSearchScopeDef {
1533     Workspace,
1534     WorkspaceAndDependencies,
1535 }
1536
1537 #[derive(Deserialize, Debug, Clone)]
1538 #[serde(rename_all = "snake_case")]
1539 enum SignatureDetail {
1540     Full,
1541     Parameters,
1542 }
1543
1544 #[derive(Deserialize, Debug, Clone)]
1545 #[serde(rename_all = "snake_case")]
1546 enum WorkspaceSymbolSearchKindDef {
1547     OnlyTypes,
1548     AllSymbols,
1549 }
1550
1551 macro_rules! _config_data {
1552     (struct $name:ident {
1553         $(
1554             $(#[doc=$doc:literal])*
1555             $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1556         )*
1557     }) => {
1558         #[allow(non_snake_case)]
1559         #[derive(Debug, Clone)]
1560         struct $name { $($field: $ty,)* }
1561         impl $name {
1562             fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1563                 $name {$(
1564                     $field: get_field(
1565                         &mut json,
1566                         error_sink,
1567                         stringify!($field),
1568                         None$(.or(Some(stringify!($alias))))*,
1569                         $default,
1570                     ),
1571                 )*}
1572             }
1573
1574             fn json_schema() -> serde_json::Value {
1575                 schema(&[
1576                     $({
1577                         let field = stringify!($field);
1578                         let ty = stringify!($ty);
1579
1580                         (field, ty, &[$($doc),*], $default)
1581                     },)*
1582                 ])
1583             }
1584
1585             #[cfg(test)]
1586             fn manual() -> String {
1587                 manual(&[
1588                     $({
1589                         let field = stringify!($field);
1590                         let ty = stringify!($ty);
1591
1592                         (field, ty, &[$($doc),*], $default)
1593                     },)*
1594                 ])
1595             }
1596         }
1597
1598         #[test]
1599         fn fields_are_sorted() {
1600             [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1601         }
1602     };
1603 }
1604 use _config_data as config_data;
1605
1606 fn get_field<T: DeserializeOwned>(
1607     json: &mut serde_json::Value,
1608     error_sink: &mut Vec<(String, serde_json::Error)>,
1609     field: &'static str,
1610     alias: Option<&'static str>,
1611     default: &str,
1612 ) -> T {
1613     let default = serde_json::from_str(default).unwrap();
1614     // XXX: check alias first, to work-around the VS Code where it pre-fills the
1615     // defaults instead of sending an empty object.
1616     alias
1617         .into_iter()
1618         .chain(iter::once(field))
1619         .find_map(move |field| {
1620             let mut pointer = field.replace('_', "/");
1621             pointer.insert(0, '/');
1622             json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1623                 Ok(it) => Some(it),
1624                 Err(e) => {
1625                     tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1626                     error_sink.push((pointer, e));
1627                     None
1628                 }
1629             })
1630         })
1631         .unwrap_or(default)
1632 }
1633
1634 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1635     for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1636         fn key(f: &str) -> &str {
1637             f.splitn(2, '_').next().unwrap()
1638         }
1639         assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1640     }
1641
1642     let map = fields
1643         .iter()
1644         .map(|(field, ty, doc, default)| {
1645             let name = field.replace('_', ".");
1646             let name = format!("rust-analyzer.{}", name);
1647             let props = field_props(field, ty, doc, default);
1648             (name, props)
1649         })
1650         .collect::<serde_json::Map<_, _>>();
1651     map.into()
1652 }
1653
1654 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1655     let doc = doc_comment_to_string(doc);
1656     let doc = doc.trim_end_matches('\n');
1657     assert!(
1658         doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1659         "bad docs for {}: {:?}",
1660         field,
1661         doc
1662     );
1663     let default = default.parse::<serde_json::Value>().unwrap();
1664
1665     let mut map = serde_json::Map::default();
1666     macro_rules! set {
1667         ($($key:literal: $value:tt),*$(,)?) => {{$(
1668             map.insert($key.into(), serde_json::json!($value));
1669         )*}};
1670     }
1671     set!("markdownDescription": doc);
1672     set!("default": default);
1673
1674     match ty {
1675         "bool" => set!("type": "boolean"),
1676         "usize" => set!("type": "integer", "minimum": 0),
1677         "String" => set!("type": "string"),
1678         "Vec<String>" => set! {
1679             "type": "array",
1680             "items": { "type": "string" },
1681         },
1682         "Vec<PathBuf>" => set! {
1683             "type": "array",
1684             "items": { "type": "string" },
1685         },
1686         "FxHashSet<String>" => set! {
1687             "type": "array",
1688             "items": { "type": "string" },
1689             "uniqueItems": true,
1690         },
1691         "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1692             "type": "object",
1693         },
1694         "FxHashMap<String, SnippetDef>" => set! {
1695             "type": "object",
1696         },
1697         "FxHashMap<String, String>" => set! {
1698             "type": "object",
1699         },
1700         "Option<usize>" => set! {
1701             "type": ["null", "integer"],
1702             "minimum": 0,
1703         },
1704         "Option<String>" => set! {
1705             "type": ["null", "string"],
1706         },
1707         "Option<PathBuf>" => set! {
1708             "type": ["null", "string"],
1709         },
1710         "Option<bool>" => set! {
1711             "type": ["null", "boolean"],
1712         },
1713         "Option<Vec<String>>" => set! {
1714             "type": ["null", "array"],
1715             "items": { "type": "string" },
1716         },
1717         "MergeBehaviorDef" => set! {
1718             "type": "string",
1719             "enum": ["none", "crate", "module"],
1720             "enumDescriptions": [
1721                 "Do not merge imports at all.",
1722                 "Merge imports from the same crate into a single `use` statement.",
1723                 "Merge imports from the same module into a single `use` statement."
1724             ],
1725         },
1726         "ExprFillDefaultDef" => set! {
1727             "type": "string",
1728             "enum": ["todo", "default"],
1729             "enumDescriptions": [
1730                 "Fill missing expressions with the `todo` macro",
1731                 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1732             ],
1733         },
1734         "ImportGranularityDef" => set! {
1735             "type": "string",
1736             "enum": ["preserve", "crate", "module", "item"],
1737             "enumDescriptions": [
1738                 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1739                 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1740                 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1741                 "Flatten imports so that each has its own use statement."
1742             ],
1743         },
1744         "ImportPrefixDef" => set! {
1745             "type": "string",
1746             "enum": [
1747                 "plain",
1748                 "self",
1749                 "crate"
1750             ],
1751             "enumDescriptions": [
1752                 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1753                 "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.",
1754                 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1755             ],
1756         },
1757         "Vec<ManifestOrProjectJson>" => set! {
1758             "type": "array",
1759             "items": { "type": ["string", "object"] },
1760         },
1761         "WorkspaceSymbolSearchScopeDef" => set! {
1762             "type": "string",
1763             "enum": ["workspace", "workspace_and_dependencies"],
1764             "enumDescriptions": [
1765                 "Search in current workspace only.",
1766                 "Search in current workspace and dependencies."
1767             ],
1768         },
1769         "WorkspaceSymbolSearchKindDef" => set! {
1770             "type": "string",
1771             "enum": ["only_types", "all_symbols"],
1772             "enumDescriptions": [
1773                 "Search for types only.",
1774                 "Search for all symbols kinds."
1775             ],
1776         },
1777         "ParallelCachePrimingNumThreads" => set! {
1778             "type": "number",
1779             "minimum": 0,
1780             "maximum": 255
1781         },
1782         "LifetimeElisionDef" => set! {
1783             "type": "string",
1784             "enum": [
1785                 "always",
1786                 "never",
1787                 "skip_trivial"
1788             ],
1789             "enumDescriptions": [
1790                 "Always show lifetime elision hints.",
1791                 "Never show lifetime elision hints.",
1792                 "Only show lifetime elision hints if a return type is involved."
1793             ]
1794         },
1795         "ClosureReturnTypeHintsDef" => set! {
1796             "type": "string",
1797             "enum": [
1798                 "always",
1799                 "never",
1800                 "with_block"
1801             ],
1802             "enumDescriptions": [
1803                 "Always show type hints for return types of closures.",
1804                 "Never show type hints for return types of closures.",
1805                 "Only show type hints for return types of closures with blocks."
1806             ]
1807         },
1808         "ReborrowHintsDef" => set! {
1809             "type": "string",
1810             "enum": [
1811                 "always",
1812                 "never",
1813                 "mutable"
1814             ],
1815             "enumDescriptions": [
1816                 "Always show reborrow hints.",
1817                 "Never show reborrow hints.",
1818                 "Only show mutable reborrow hints."
1819             ]
1820         },
1821         "CargoFeatures" => set! {
1822             "anyOf": [
1823                 {
1824                     "type": "string",
1825                     "enum": [
1826                         "all"
1827                     ],
1828                     "enumDescriptions": [
1829                         "Pass `--all-features` to cargo",
1830                     ]
1831                 },
1832                 {
1833                     "type": "array",
1834                     "items": { "type": "string" }
1835                 }
1836             ],
1837         },
1838         "Option<CargoFeatures>" => set! {
1839             "anyOf": [
1840                 {
1841                     "type": "string",
1842                     "enum": [
1843                         "all"
1844                     ],
1845                     "enumDescriptions": [
1846                         "Pass `--all-features` to cargo",
1847                     ]
1848                 },
1849                 {
1850                     "type": "array",
1851                     "items": { "type": "string" }
1852                 },
1853                 { "type": "null" }
1854             ],
1855         },
1856         "CallableCompletionDef" => set! {
1857             "type": "string",
1858             "enum": [
1859                 "fill_arguments",
1860                 "add_parentheses",
1861                 "none",
1862             ],
1863             "enumDescriptions": [
1864                 "Add call parentheses and pre-fill arguments.",
1865                 "Add call parentheses.",
1866                 "Do no snippet completions for callables."
1867             ]
1868         },
1869         "SignatureDetail" => set! {
1870             "type": "string",
1871             "enum": ["full", "parameters"],
1872             "enumDescriptions": [
1873                 "Show the entire signature.",
1874                 "Show only the parameters."
1875             ],
1876         },
1877         "FilesWatcherDef" => set! {
1878             "type": "string",
1879             "enum": ["client", "server"],
1880             "enumDescriptions": [
1881                 "Use the client (editor) to watch files for changes",
1882                 "Use server-side file watching",
1883             ],
1884         },
1885         _ => panic!("missing entry for {}: {}", ty, default),
1886     }
1887
1888     map.into()
1889 }
1890
1891 #[cfg(test)]
1892 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1893     fields
1894         .iter()
1895         .map(|(field, _ty, doc, default)| {
1896             let name = format!("rust-analyzer.{}", field.replace('_', "."));
1897             let doc = doc_comment_to_string(*doc);
1898             if default.contains('\n') {
1899                 format!(
1900                     r#"[[{}]]{}::
1901 +
1902 --
1903 Default:
1904 ----
1905 {}
1906 ----
1907 {}
1908 --
1909 "#,
1910                     name, name, default, doc
1911                 )
1912             } else {
1913                 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1914             }
1915         })
1916         .collect::<String>()
1917 }
1918
1919 fn doc_comment_to_string(doc: &[&str]) -> String {
1920     doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1921 }
1922
1923 #[cfg(test)]
1924 mod tests {
1925     use std::fs;
1926
1927     use test_utils::{ensure_file_contents, project_root};
1928
1929     use super::*;
1930
1931     #[test]
1932     fn generate_package_json_config() {
1933         let s = Config::json_schema();
1934         let schema = format!("{:#}", s);
1935         let mut schema = schema
1936             .trim_start_matches('{')
1937             .trim_end_matches('}')
1938             .replace("  ", "    ")
1939             .replace('\n', "\n            ")
1940             .trim_start_matches('\n')
1941             .trim_end()
1942             .to_string();
1943         schema.push_str(",\n");
1944
1945         // Transform the asciidoc form link to markdown style.
1946         //
1947         // https://link[text] => [text](https://link)
1948         let url_matches = schema.match_indices("https://");
1949         let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1950         url_offsets.reverse();
1951         for idx in url_offsets {
1952             let link = &schema[idx..];
1953             // matching on whitespace to ignore normal links
1954             if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
1955                 if link.chars().nth(link_end) == Some('[') {
1956                     if let Some(link_text_end) = link.find(']') {
1957                         let link_text = link[link_end..(link_text_end + 1)].to_string();
1958
1959                         schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
1960                         schema.insert(idx, '(');
1961                         schema.insert(idx + link_end + 1, ')');
1962                         schema.insert_str(idx, &link_text);
1963                     }
1964                 }
1965             }
1966         }
1967
1968         let package_json_path = project_root().join("editors/code/package.json");
1969         let mut package_json = fs::read_to_string(&package_json_path).unwrap();
1970
1971         let start_marker = "                \"$generated-start\": {},\n";
1972         let end_marker = "                \"$generated-end\": {}\n";
1973
1974         let start = package_json.find(start_marker).unwrap() + start_marker.len();
1975         let end = package_json.find(end_marker).unwrap();
1976
1977         let p = remove_ws(&package_json[start..end]);
1978         let s = remove_ws(&schema);
1979         if !p.contains(&s) {
1980             package_json.replace_range(start..end, &schema);
1981             ensure_file_contents(&package_json_path, &package_json)
1982         }
1983     }
1984
1985     #[test]
1986     fn generate_config_documentation() {
1987         let docs_path = project_root().join("docs/user/generated_config.adoc");
1988         let expected = ConfigData::manual();
1989         ensure_file_contents(&docs_path, &expected);
1990     }
1991
1992     fn remove_ws(text: &str) -> String {
1993         text.replace(char::is_whitespace, "")
1994     }
1995 }