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