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