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