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