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