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