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