]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/config.rs
Replaced fold with for loop
[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, iter, path::PathBuf};
11
12 use flycheck::FlycheckConfig;
13 use ide::{
14     AssistConfig, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, HighlightRelatedConfig,
15     HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig, Snippet, SnippetScope,
16 };
17 use ide_db::helpers::{
18     insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
19     SnippetCap,
20 };
21 use lsp_types::{ClientCapabilities, MarkupKind};
22 use project_model::{
23     CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates,
24 };
25 use rustc_hash::{FxHashMap, FxHashSet};
26 use serde::{de::DeserializeOwned, Deserialize};
27 use vfs::AbsPathBuf;
28
29 use crate::{
30     caps::completion_item_edit_resolve,
31     diagnostics::DiagnosticsMapConfig,
32     line_index::OffsetEncoding,
33     lsp_ext::supports_utf8,
34     lsp_ext::WorkspaceSymbolSearchScope,
35     lsp_ext::{self, WorkspaceSymbolSearchKind},
36 };
37
38 // Defines the server-side configuration of the rust-analyzer. We generate
39 // *parts* of VS Code's `package.json` config from this.
40 //
41 // However, editor specific config, which the server doesn't know about, should
42 // be specified directly in `package.json`.
43 //
44 // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
45 // parsing the old name.
46 config_data! {
47     struct ConfigData {
48         /// Placeholder for missing expressions in assists.
49         assist_exprFillDefault: ExprFillDefaultDef              = "\"todo\"",
50         /// How imports should be grouped into use statements.
51         assist_importGranularity |
52         assist_importMergeBehavior |
53         assist_importMergeBehaviour: ImportGranularityDef  = "\"crate\"",
54         /// 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.
55         assist_importEnforceGranularity: bool              = "false",
56         /// The path structure for newly inserted paths to use.
57         assist_importPrefix: ImportPrefixDef               = "\"plain\"",
58         /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
59         assist_importGroup: bool                           = "true",
60         /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
61         assist_allowMergingIntoGlobImports: bool           = "true",
62
63         /// Warm up caches on project load.
64         cache_warmup: bool = "true",
65
66         /// Show function name and docs in parameter hints.
67         callInfo_full: bool                                = "true",
68
69         /// Automatically refresh project info via `cargo metadata` on
70         /// `Cargo.toml` changes.
71         cargo_autoreload: bool           = "true",
72         /// Activate all available features (`--all-features`).
73         cargo_allFeatures: bool          = "false",
74         /// Unsets `#[cfg(test)]` for the specified crates.
75         cargo_unsetTest: Vec<String>   = "[\"core\"]",
76         /// List of features to activate.
77         cargo_features: Vec<String>      = "[]",
78         /// Run build scripts (`build.rs`) for more precise code analysis.
79         cargo_runBuildScripts |
80         cargo_loadOutDirsFromCheck: bool = "true",
81         /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
82         /// avoid compiling unnecessary things.
83         cargo_useRustcWrapperForBuildScripts: bool = "true",
84         /// Do not activate the `default` feature.
85         cargo_noDefaultFeatures: bool    = "false",
86         /// Compilation target (target triple).
87         cargo_target: Option<String>     = "null",
88         /// Internal config for debugging, disables loading of sysroot crates.
89         cargo_noSysroot: bool            = "false",
90
91         /// Run specified `cargo check` command for diagnostics on save.
92         checkOnSave_enable: bool                         = "true",
93         /// Check with all features (`--all-features`).
94         /// Defaults to `#rust-analyzer.cargo.allFeatures#`.
95         checkOnSave_allFeatures: Option<bool>            = "null",
96         /// Check all targets and tests (`--all-targets`).
97         checkOnSave_allTargets: bool                     = "true",
98         /// Cargo command to use for `cargo check`.
99         checkOnSave_command: String                      = "\"check\"",
100         /// Do not activate the `default` feature.
101         checkOnSave_noDefaultFeatures: Option<bool>      = "null",
102         /// Check for a specific target. Defaults to
103         /// `#rust-analyzer.cargo.target#`.
104         checkOnSave_target: Option<String>               = "null",
105         /// Extra arguments for `cargo check`.
106         checkOnSave_extraArgs: Vec<String>               = "[]",
107         /// List of features to activate. Defaults to
108         /// `#rust-analyzer.cargo.features#`.
109         checkOnSave_features: Option<Vec<String>>        = "null",
110         /// Advanced option, fully override the command rust-analyzer uses for
111         /// checking. The command should include `--message-format=json` or
112         /// similar option.
113         checkOnSave_overrideCommand: Option<Vec<String>> = "null",
114
115         /// Whether to add argument snippets when completing functions.
116         /// Only applies when `#rust-analyzer.completion.addCallParenthesis#` is set.
117         completion_addCallArgumentSnippets: bool = "true",
118         /// Whether to add parenthesis when completing functions.
119         completion_addCallParenthesis: bool      = "true",
120         /// Custom completion snippets.
121         // NOTE: Keep this list in sync with the feature docs of user snippets.
122         completion_snippets: FxHashMap<String, SnippetDef> = r#"{
123             "Arc::new": {
124                 "postfix": "arc",
125                 "body": "Arc::new(${receiver})",
126                 "requires": "std::sync::Arc",
127                 "description": "Put the expression into an `Arc`",
128                 "scope": "expr"
129             },
130             "Rc::new": {
131                 "postfix": "rc",
132                 "body": "Rc::new(${receiver})",
133                 "requires": "std::rc::Rc",
134                 "description": "Put the expression into an `Rc`",
135                 "scope": "expr"
136             },
137             "Box::pin": {
138                 "postfix": "pinbox",
139                 "body": "Box::pin(${receiver})",
140                 "requires": "std::boxed::Box",
141                 "description": "Put the expression into a pinned `Box`",
142                 "scope": "expr"
143             },
144             "Ok": {
145                 "postfix": "ok",
146                 "body": "Ok(${receiver})",
147                 "description": "Wrap the expression in a `Result::Ok`",
148                 "scope": "expr"
149             },
150             "Err": {
151                 "postfix": "err",
152                 "body": "Err(${receiver})",
153                 "description": "Wrap the expression in a `Result::Err`",
154                 "scope": "expr"
155             },
156             "Some": {
157                 "postfix": "some",
158                 "body": "Some(${receiver})",
159                 "description": "Wrap the expression in an `Option::Some`",
160                 "scope": "expr"
161             }
162         }"#,
163         /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
164         completion_postfix_enable: bool          = "true",
165         /// Toggles the additional completions that automatically add imports when completed.
166         /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
167         completion_autoimport_enable: bool       = "true",
168         /// Toggles the additional completions that automatically show method calls and field accesses
169         /// with `self` prefixed to them when inside a method.
170         completion_autoself_enable: bool       = "true",
171
172         /// Whether to show native rust-analyzer diagnostics.
173         diagnostics_enable: bool                = "true",
174         /// Whether to show experimental rust-analyzer diagnostics that might
175         /// have more false positives than usual.
176         diagnostics_enableExperimental: bool    = "true",
177         /// List of rust-analyzer diagnostics to disable.
178         diagnostics_disabled: FxHashSet<String> = "[]",
179         /// Map of prefixes to be substituted when parsing diagnostic file paths.
180         /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
181         diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
182         /// List of warnings that should be displayed with hint severity.
183         ///
184         /// The warnings will be indicated by faded text or three dots in code
185         /// and will not show up in the `Problems Panel`.
186         diagnostics_warningsAsHint: Vec<String> = "[]",
187         /// List of warnings that should be displayed with info severity.
188         ///
189         /// The warnings will be indicated by a blue squiggly underline in code
190         /// and a blue icon in the `Problems Panel`.
191         diagnostics_warningsAsInfo: Vec<String> = "[]",
192
193         /// Expand attribute macros.
194         experimental_procAttrMacros: bool = "true",
195
196         /// Controls file watching implementation.
197         files_watcher: String = "\"client\"",
198         /// These directories will be ignored by rust-analyzer. They are
199         /// relative to the workspace root, and globs are not supported. You may
200         /// also need to add the folders to Code's `files.watcherExclude`.
201         files_excludeDirs: Vec<PathBuf> = "[]",
202
203         /// Enables highlighting of related references while hovering your mouse above any identifier.
204         highlightRelated_references: bool = "true",
205         /// Enables highlighting of all exit points while hovering your mouse above any `return`, `?`, or return type arrow (`->`).
206         highlightRelated_exitPoints: bool = "true",
207         /// Enables highlighting of related references while hovering your mouse `break`, `loop`, `while`, or `for` keywords.
208         highlightRelated_breakPoints: bool = "true",
209         /// Enables highlighting of all break points for a loop or block context while hovering your mouse above any `async` or `await` keywords.
210         highlightRelated_yieldPoints: bool = "true",
211
212         /// Use semantic tokens for strings.
213         ///
214         /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
215         /// By disabling semantic tokens for strings, other grammars can be used to highlight
216         /// their contents.
217         highlighting_strings: bool = "true",
218
219         /// Whether to show documentation on hover.
220         hover_documentation: bool       = "true",
221         /// Use markdown syntax for links in hover.
222         hover_linksInHover |
223         hoverActions_linksInHover: bool = "true",
224
225         /// Whether to show `Debug` action. Only applies when
226         /// `#rust-analyzer.hoverActions.enable#` is set.
227         hoverActions_debug: bool           = "true",
228         /// Whether to show HoverActions in Rust files.
229         hoverActions_enable: bool          = "true",
230         /// Whether to show `Go to Type Definition` action. Only applies when
231         /// `#rust-analyzer.hoverActions.enable#` is set.
232         hoverActions_gotoTypeDef: bool     = "true",
233         /// Whether to show `Implementations` action. Only applies when
234         /// `#rust-analyzer.hoverActions.enable#` is set.
235         hoverActions_implementations: bool = "true",
236         /// Whether to show `References` action. Only applies when
237         /// `#rust-analyzer.hoverActions.enable#` is set.
238         hoverActions_references: bool      = "false",
239         /// Whether to show `Run` action. Only applies when
240         /// `#rust-analyzer.hoverActions.enable#` is set.
241         hoverActions_run: bool             = "true",
242
243         /// Whether to show inlay type hints for method chains.
244         inlayHints_chainingHints: bool              = "true",
245         /// Maximum length for inlay hints. Set to null to have an unlimited length.
246         inlayHints_maxLength: Option<usize>         = "25",
247         /// Whether to show function parameter name inlay hints at the call
248         /// site.
249         inlayHints_parameterHints: bool             = "true",
250         /// Whether to show inlay type hints for variables.
251         inlayHints_typeHints: bool                  = "true",
252         /// Whether to hide inlay hints for constructors.
253         inlayHints_hideNamedConstructorHints: bool  = "false",
254
255         /// Join lines inserts else between consecutive ifs.
256         joinLines_joinElseIf: bool = "true",
257         /// Join lines removes trailing commas.
258         joinLines_removeTrailingComma: bool = "true",
259         /// Join lines unwraps trivial blocks.
260         joinLines_unwrapTrivialBlock: bool = "true",
261         /// Join lines merges consecutive declaration and initialization of an assignment.
262         joinLines_joinAssignments: bool = "true",
263
264         /// Whether to show `Debug` lens. Only applies when
265         /// `#rust-analyzer.lens.enable#` is set.
266         lens_debug: bool            = "true",
267         /// Whether to show CodeLens in Rust files.
268         lens_enable: bool           = "true",
269         /// Whether to show `Implementations` lens. Only applies when
270         /// `#rust-analyzer.lens.enable#` is set.
271         lens_implementations: bool  = "true",
272         /// Whether to show `Run` lens. Only applies when
273         /// `#rust-analyzer.lens.enable#` is set.
274         lens_run: bool              = "true",
275         /// Whether to show `Method References` lens. Only applies when
276         /// `#rust-analyzer.lens.enable#` is set.
277         lens_methodReferences: bool = "false",
278         /// Whether to show `References` lens for Struct, Enum, Union and Trait.
279         /// Only applies when `#rust-analyzer.lens.enable#` is set.
280         lens_references: bool = "false",
281         /// Whether to show `References` lens for Enum Variants.
282         /// Only applies when `#rust-analyzer.lens.enable#` is set.
283         lens_enumVariantReferences: bool = "false",
284         /// Internal config: use custom client-side commands even when the
285         /// client doesn't set the corresponding capability.
286         lens_forceCustomCommands: bool = "true",
287
288         /// Disable project auto-discovery in favor of explicitly specified set
289         /// of projects.
290         ///
291         /// Elements must be paths pointing to `Cargo.toml`,
292         /// `rust-project.json`, or JSON objects in `rust-project.json` format.
293         linkedProjects: Vec<ManifestOrProjectJson> = "[]",
294
295         /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
296         lruCapacity: Option<usize>                 = "null",
297
298         /// Whether to show `can't find Cargo.toml` error message.
299         notifications_cargoTomlNotFound: bool      = "true",
300
301         /// How many worker threads to to handle priming caches. The default `0` means to pick automatically.
302         primeCaches_numThreads: ParallelPrimeCachesNumThreads = "0",
303
304         /// Enable support for procedural macros, implies `#rust-analyzer.cargo.runBuildScripts#`.
305         procMacro_enable: bool                     = "true",
306         /// Internal config, path to proc-macro server executable (typically,
307         /// this is rust-analyzer itself, but we override this in tests).
308         procMacro_server: Option<PathBuf>          = "null",
309         /// These proc-macros will be ignored when trying to expand them.
310         ///
311         /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
312         procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>>          = "{}",
313
314         /// Command to be executed instead of 'cargo' for runnables.
315         runnables_overrideCargo: Option<String> = "null",
316         /// Additional arguments to be passed to cargo for runnables such as
317         /// tests or binaries. For example, it may be `--release`.
318         runnables_cargoExtraArgs: Vec<String>   = "[]",
319
320         /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
321         /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
322         /// is installed.
323         ///
324         /// Any project which uses rust-analyzer with the rustcPrivate
325         /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
326         ///
327         /// This option does not take effect until rust-analyzer is restarted.
328         rustcSource: Option<String> = "null",
329
330         /// Additional arguments to `rustfmt`.
331         rustfmt_extraArgs: Vec<String>               = "[]",
332         /// Advanced option, fully override the command rust-analyzer uses for
333         /// formatting.
334         rustfmt_overrideCommand: Option<Vec<String>> = "null",
335         /// Enables the use of rustfmt's unstable range formatting command for the
336         /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
337         /// available on a nightly build.
338         rustfmt_enableRangeFormatting: bool = "false",
339
340         /// Workspace symbol search scope.
341         workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
342         /// Workspace symbol search kind.
343         workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
344     }
345 }
346
347 impl Default for ConfigData {
348     fn default() -> Self {
349         ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
350     }
351 }
352
353 #[derive(Debug, Clone)]
354 pub struct Config {
355     pub caps: lsp_types::ClientCapabilities,
356     data: ConfigData,
357     detached_files: Vec<AbsPathBuf>,
358     pub discovered_projects: Option<Vec<ProjectManifest>>,
359     pub root_path: AbsPathBuf,
360     snippets: Vec<Snippet>,
361 }
362
363 #[derive(Debug, Clone, Eq, PartialEq)]
364 pub enum LinkedProject {
365     ProjectManifest(ProjectManifest),
366     InlineJsonProject(ProjectJson),
367 }
368
369 impl From<ProjectManifest> for LinkedProject {
370     fn from(v: ProjectManifest) -> Self {
371         LinkedProject::ProjectManifest(v)
372     }
373 }
374
375 impl From<ProjectJson> for LinkedProject {
376     fn from(v: ProjectJson) -> Self {
377         LinkedProject::InlineJsonProject(v)
378     }
379 }
380
381 #[derive(Clone, Debug, PartialEq, Eq)]
382 pub struct LensConfig {
383     pub run: bool,
384     pub debug: bool,
385     pub implementations: bool,
386     pub method_refs: bool,
387     pub refs: bool, // for Struct, Enum, Union and Trait
388     pub enum_variant_refs: bool,
389 }
390
391 impl LensConfig {
392     pub fn any(&self) -> bool {
393         self.implementations || self.runnable() || self.references()
394     }
395
396     pub fn none(&self) -> bool {
397         !self.any()
398     }
399
400     pub fn runnable(&self) -> bool {
401         self.run || self.debug
402     }
403
404     pub fn references(&self) -> bool {
405         self.method_refs || self.refs || self.enum_variant_refs
406     }
407 }
408
409 #[derive(Clone, Debug, PartialEq, Eq)]
410 pub struct HoverActionsConfig {
411     pub implementations: bool,
412     pub references: bool,
413     pub run: bool,
414     pub debug: bool,
415     pub goto_type_def: bool,
416 }
417
418 impl HoverActionsConfig {
419     pub const NO_ACTIONS: Self = Self {
420         implementations: false,
421         references: false,
422         run: false,
423         debug: false,
424         goto_type_def: false,
425     };
426
427     pub fn any(&self) -> bool {
428         self.implementations || self.references || self.runnable() || self.goto_type_def
429     }
430
431     pub fn none(&self) -> bool {
432         !self.any()
433     }
434
435     pub fn runnable(&self) -> bool {
436         self.run || self.debug
437     }
438 }
439
440 #[derive(Debug, Clone)]
441 pub struct FilesConfig {
442     pub watcher: FilesWatcher,
443     pub exclude: Vec<AbsPathBuf>,
444 }
445
446 #[derive(Debug, Clone)]
447 pub enum FilesWatcher {
448     Client,
449     Notify,
450 }
451
452 #[derive(Debug, Clone)]
453 pub struct NotificationsConfig {
454     pub cargo_toml_not_found: bool,
455 }
456
457 #[derive(Debug, Clone)]
458 pub enum RustfmtConfig {
459     Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
460     CustomCommand { command: String, args: Vec<String> },
461 }
462
463 /// Configuration for runnable items, such as `main` function or tests.
464 #[derive(Debug, Clone)]
465 pub struct RunnablesConfig {
466     /// Custom command to be executed instead of `cargo` for runnables.
467     pub override_cargo: Option<String>,
468     /// Additional arguments for the `cargo`, e.g. `--release`.
469     pub cargo_extra_args: Vec<String>,
470 }
471
472 /// Configuration for workspace symbol search requests.
473 #[derive(Debug, Clone)]
474 pub struct WorkspaceSymbolConfig {
475     /// In what scope should the symbol be searched in.
476     pub search_scope: WorkspaceSymbolSearchScope,
477     /// What kind of symbol is being search for.
478     pub search_kind: WorkspaceSymbolSearchKind,
479 }
480
481 pub struct ClientCommandsConfig {
482     pub run_single: bool,
483     pub debug_single: bool,
484     pub show_reference: bool,
485     pub goto_location: bool,
486     pub trigger_parameter_hints: bool,
487 }
488
489 impl Config {
490     pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
491         Config {
492             caps,
493             data: ConfigData::default(),
494             detached_files: Vec::new(),
495             discovered_projects: None,
496             root_path,
497             snippets: Default::default(),
498         }
499     }
500     pub fn update(
501         &mut self,
502         mut json: serde_json::Value,
503     ) -> Result<(), Vec<(String, serde_json::Error)>> {
504         tracing::info!("updating config from JSON: {:#}", json);
505         if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
506             return Ok(());
507         }
508         let mut errors = Vec::new();
509         self.detached_files =
510             get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
511                 .into_iter()
512                 .map(AbsPathBuf::assert)
513                 .collect();
514         self.data = ConfigData::from_json(json, &mut errors);
515         self.snippets.clear();
516         for (name, def) in self.data.completion_snippets.iter() {
517             if def.prefix.is_empty() && def.postfix.is_empty() {
518                 continue;
519             }
520             let scope = match def.scope {
521                 SnippetScopeDef::Expr => SnippetScope::Expr,
522                 SnippetScopeDef::Type => SnippetScope::Type,
523                 SnippetScopeDef::Item => SnippetScope::Item,
524             };
525             match Snippet::new(
526                 &def.prefix,
527                 &def.postfix,
528                 &def.body,
529                 def.description.as_ref().unwrap_or(name),
530                 &def.requires,
531                 scope,
532             ) {
533                 Some(snippet) => self.snippets.push(snippet),
534                 None => tracing::info!("Invalid snippet {}", name),
535             }
536         }
537         if errors.is_empty() {
538             Ok(())
539         } else {
540             Err(errors)
541         }
542     }
543
544     pub fn json_schema() -> serde_json::Value {
545         ConfigData::json_schema()
546     }
547 }
548
549 macro_rules! try_ {
550     ($expr:expr) => {
551         || -> _ { Some($expr) }()
552     };
553 }
554 macro_rules! try_or {
555     ($expr:expr, $or:expr) => {
556         try_!($expr).unwrap_or($or)
557     };
558 }
559
560 impl Config {
561     pub fn linked_projects(&self) -> Vec<LinkedProject> {
562         if self.data.linkedProjects.is_empty() {
563             self.discovered_projects
564                 .as_ref()
565                 .into_iter()
566                 .flatten()
567                 .cloned()
568                 .map(LinkedProject::from)
569                 .collect()
570         } else {
571             self.data
572                 .linkedProjects
573                 .iter()
574                 .filter_map(|linked_project| {
575                     let res = match linked_project {
576                         ManifestOrProjectJson::Manifest(it) => {
577                             let path = self.root_path.join(it);
578                             ProjectManifest::from_manifest_file(path)
579                                 .map_err(|e| {
580                                     tracing::error!("failed to load linked project: {}", e)
581                                 })
582                                 .ok()?
583                                 .into()
584                         }
585                         ManifestOrProjectJson::ProjectJson(it) => {
586                             ProjectJson::new(&self.root_path, it.clone()).into()
587                         }
588                     };
589                     Some(res)
590                 })
591                 .collect()
592         }
593     }
594
595     pub fn detached_files(&self) -> &[AbsPathBuf] {
596         &self.detached_files
597     }
598
599     pub fn did_save_text_document_dynamic_registration(&self) -> bool {
600         let caps =
601             try_or!(self.caps.text_document.as_ref()?.synchronization.clone()?, Default::default());
602         caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
603     }
604     pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
605         try_or!(
606             self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?,
607             false
608         )
609     }
610
611     pub fn prefill_caches(&self) -> bool {
612         self.data.cache_warmup
613     }
614
615     pub fn location_link(&self) -> bool {
616         try_or!(self.caps.text_document.as_ref()?.definition?.link_support?, false)
617     }
618     pub fn line_folding_only(&self) -> bool {
619         try_or!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?, false)
620     }
621     pub fn hierarchical_symbols(&self) -> bool {
622         try_or!(
623             self.caps
624                 .text_document
625                 .as_ref()?
626                 .document_symbol
627                 .as_ref()?
628                 .hierarchical_document_symbol_support?,
629             false
630         )
631     }
632     pub fn code_action_literals(&self) -> bool {
633         try_!(self
634             .caps
635             .text_document
636             .as_ref()?
637             .code_action
638             .as_ref()?
639             .code_action_literal_support
640             .as_ref()?)
641         .is_some()
642     }
643     pub fn work_done_progress(&self) -> bool {
644         try_or!(self.caps.window.as_ref()?.work_done_progress?, false)
645     }
646     pub fn will_rename(&self) -> bool {
647         try_or!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?, false)
648     }
649     pub fn change_annotation_support(&self) -> bool {
650         try_!(self
651             .caps
652             .workspace
653             .as_ref()?
654             .workspace_edit
655             .as_ref()?
656             .change_annotation_support
657             .as_ref()?)
658         .is_some()
659     }
660     pub fn code_action_resolve(&self) -> bool {
661         try_or!(
662             self.caps
663                 .text_document
664                 .as_ref()?
665                 .code_action
666                 .as_ref()?
667                 .resolve_support
668                 .as_ref()?
669                 .properties
670                 .as_slice(),
671             &[]
672         )
673         .iter()
674         .any(|it| it == "edit")
675     }
676     pub fn signature_help_label_offsets(&self) -> bool {
677         try_or!(
678             self.caps
679                 .text_document
680                 .as_ref()?
681                 .signature_help
682                 .as_ref()?
683                 .signature_information
684                 .as_ref()?
685                 .parameter_information
686                 .as_ref()?
687                 .label_offset_support?,
688             false
689         )
690     }
691     pub fn offset_encoding(&self) -> OffsetEncoding {
692         if supports_utf8(&self.caps) {
693             OffsetEncoding::Utf8
694         } else {
695             OffsetEncoding::Utf16
696         }
697     }
698
699     fn experimental(&self, index: &'static str) -> bool {
700         try_or!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?, false)
701     }
702     pub fn code_action_group(&self) -> bool {
703         self.experimental("codeActionGroup")
704     }
705     pub fn server_status_notification(&self) -> bool {
706         self.experimental("serverStatusNotification")
707     }
708
709     pub fn publish_diagnostics(&self) -> bool {
710         self.data.diagnostics_enable
711     }
712     pub fn diagnostics(&self) -> DiagnosticsConfig {
713         DiagnosticsConfig {
714             disable_experimental: !self.data.diagnostics_enableExperimental,
715             disabled: self.data.diagnostics_disabled.clone(),
716             expr_fill_default: match self.data.assist_exprFillDefault {
717                 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
718                 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
719             },
720         }
721     }
722     pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
723         DiagnosticsMapConfig {
724             remap_prefix: self.data.diagnostics_remapPrefix.clone(),
725             warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
726             warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
727         }
728     }
729     pub fn lru_capacity(&self) -> Option<usize> {
730         self.data.lruCapacity
731     }
732     pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
733         if !self.data.procMacro_enable {
734             return None;
735         }
736         let path = match &self.data.procMacro_server {
737             Some(it) => self.root_path.join(it),
738             None => AbsPathBuf::assert(std::env::current_exe().ok()?),
739         };
740         Some((path, vec!["proc-macro".into()]))
741     }
742     pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
743         &self.data.procMacro_ignored
744     }
745     pub fn expand_proc_attr_macros(&self) -> bool {
746         self.data.experimental_procAttrMacros
747     }
748     pub fn files(&self) -> FilesConfig {
749         FilesConfig {
750             watcher: match self.data.files_watcher.as_str() {
751                 "notify" => FilesWatcher::Notify,
752                 "client" if self.did_change_watched_files_dynamic_registration() => {
753                     FilesWatcher::Client
754                 }
755                 _ => FilesWatcher::Notify,
756             },
757             exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
758         }
759     }
760     pub fn notifications(&self) -> NotificationsConfig {
761         NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
762     }
763     pub fn cargo_autoreload(&self) -> bool {
764         self.data.cargo_autoreload
765     }
766     pub fn run_build_scripts(&self) -> bool {
767         self.data.cargo_runBuildScripts || self.data.procMacro_enable
768     }
769     pub fn cargo(&self) -> CargoConfig {
770         let rustc_source = self.data.rustcSource.as_ref().map(|rustc_src| {
771             if rustc_src == "discover" {
772                 RustcSource::Discover
773             } else {
774                 RustcSource::Path(self.root_path.join(rustc_src))
775             }
776         });
777
778         CargoConfig {
779             no_default_features: self.data.cargo_noDefaultFeatures,
780             all_features: self.data.cargo_allFeatures,
781             features: self.data.cargo_features.clone(),
782             target: self.data.cargo_target.clone(),
783             no_sysroot: self.data.cargo_noSysroot,
784             rustc_source,
785             unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
786             wrap_rustc_in_build_scripts: self.data.cargo_useRustcWrapperForBuildScripts,
787         }
788     }
789
790     pub fn rustfmt(&self) -> RustfmtConfig {
791         match &self.data.rustfmt_overrideCommand {
792             Some(args) if !args.is_empty() => {
793                 let mut args = args.clone();
794                 let command = args.remove(0);
795                 RustfmtConfig::CustomCommand { command, args }
796             }
797             Some(_) | None => RustfmtConfig::Rustfmt {
798                 extra_args: self.data.rustfmt_extraArgs.clone(),
799                 enable_range_formatting: self.data.rustfmt_enableRangeFormatting,
800             },
801         }
802     }
803     pub fn flycheck(&self) -> Option<FlycheckConfig> {
804         if !self.data.checkOnSave_enable {
805             return None;
806         }
807         let flycheck_config = match &self.data.checkOnSave_overrideCommand {
808             Some(args) if !args.is_empty() => {
809                 let mut args = args.clone();
810                 let command = args.remove(0);
811                 FlycheckConfig::CustomCommand { command, args }
812             }
813             Some(_) | None => FlycheckConfig::CargoCommand {
814                 command: self.data.checkOnSave_command.clone(),
815                 target_triple: self
816                     .data
817                     .checkOnSave_target
818                     .clone()
819                     .or_else(|| self.data.cargo_target.clone()),
820                 all_targets: self.data.checkOnSave_allTargets,
821                 no_default_features: self
822                     .data
823                     .checkOnSave_noDefaultFeatures
824                     .unwrap_or(self.data.cargo_noDefaultFeatures),
825                 all_features: self
826                     .data
827                     .checkOnSave_allFeatures
828                     .unwrap_or(self.data.cargo_allFeatures),
829                 features: self
830                     .data
831                     .checkOnSave_features
832                     .clone()
833                     .unwrap_or_else(|| self.data.cargo_features.clone()),
834                 extra_args: self.data.checkOnSave_extraArgs.clone(),
835             },
836         };
837         Some(flycheck_config)
838     }
839     pub fn runnables(&self) -> RunnablesConfig {
840         RunnablesConfig {
841             override_cargo: self.data.runnables_overrideCargo.clone(),
842             cargo_extra_args: self.data.runnables_cargoExtraArgs.clone(),
843         }
844     }
845     pub fn inlay_hints(&self) -> InlayHintsConfig {
846         InlayHintsConfig {
847             type_hints: self.data.inlayHints_typeHints,
848             parameter_hints: self.data.inlayHints_parameterHints,
849             chaining_hints: self.data.inlayHints_chainingHints,
850             hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints,
851             max_length: self.data.inlayHints_maxLength,
852         }
853     }
854     fn insert_use_config(&self) -> InsertUseConfig {
855         InsertUseConfig {
856             granularity: match self.data.assist_importGranularity {
857                 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
858                 ImportGranularityDef::Item => ImportGranularity::Item,
859                 ImportGranularityDef::Crate => ImportGranularity::Crate,
860                 ImportGranularityDef::Module => ImportGranularity::Module,
861             },
862             enforce_granularity: self.data.assist_importEnforceGranularity,
863             prefix_kind: match self.data.assist_importPrefix {
864                 ImportPrefixDef::Plain => PrefixKind::Plain,
865                 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
866                 ImportPrefixDef::BySelf => PrefixKind::BySelf,
867             },
868             group: self.data.assist_importGroup,
869             skip_glob_imports: !self.data.assist_allowMergingIntoGlobImports,
870         }
871     }
872     pub fn completion(&self) -> CompletionConfig {
873         CompletionConfig {
874             enable_postfix_completions: self.data.completion_postfix_enable,
875             enable_imports_on_the_fly: self.data.completion_autoimport_enable
876                 && completion_item_edit_resolve(&self.caps),
877             enable_self_on_the_fly: self.data.completion_autoself_enable,
878             add_call_parenthesis: self.data.completion_addCallParenthesis,
879             add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
880             insert_use: self.insert_use_config(),
881             snippet_cap: SnippetCap::new(try_or!(
882                 self.caps
883                     .text_document
884                     .as_ref()?
885                     .completion
886                     .as_ref()?
887                     .completion_item
888                     .as_ref()?
889                     .snippet_support?,
890                 false
891             )),
892             snippets: self.snippets.clone(),
893         }
894     }
895     pub fn assist(&self) -> AssistConfig {
896         AssistConfig {
897             snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
898             allowed: None,
899             insert_use: self.insert_use_config(),
900         }
901     }
902     pub fn join_lines(&self) -> JoinLinesConfig {
903         JoinLinesConfig {
904             join_else_if: self.data.joinLines_joinElseIf,
905             remove_trailing_comma: self.data.joinLines_removeTrailingComma,
906             unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
907             join_assignments: self.data.joinLines_joinAssignments,
908         }
909     }
910     pub fn call_info_full(&self) -> bool {
911         self.data.callInfo_full
912     }
913     pub fn lens(&self) -> LensConfig {
914         LensConfig {
915             run: self.data.lens_enable && self.data.lens_run,
916             debug: self.data.lens_enable && self.data.lens_debug,
917             implementations: self.data.lens_enable && self.data.lens_implementations,
918             method_refs: self.data.lens_enable && self.data.lens_methodReferences,
919             refs: self.data.lens_enable && self.data.lens_references,
920             enum_variant_refs: self.data.lens_enable && self.data.lens_enumVariantReferences,
921         }
922     }
923     pub fn hover_actions(&self) -> HoverActionsConfig {
924         let enable = self.experimental("hoverActions") && self.data.hoverActions_enable;
925         HoverActionsConfig {
926             implementations: enable && self.data.hoverActions_implementations,
927             references: enable && self.data.hoverActions_references,
928             run: enable && self.data.hoverActions_run,
929             debug: enable && self.data.hoverActions_debug,
930             goto_type_def: enable && self.data.hoverActions_gotoTypeDef,
931         }
932     }
933     pub fn highlighting_strings(&self) -> bool {
934         self.data.highlighting_strings
935     }
936     pub fn hover(&self) -> HoverConfig {
937         HoverConfig {
938             links_in_hover: self.data.hover_linksInHover,
939             documentation: self.data.hover_documentation.then(|| {
940                 let is_markdown = try_or!(
941                     self.caps
942                         .text_document
943                         .as_ref()?
944                         .hover
945                         .as_ref()?
946                         .content_format
947                         .as_ref()?
948                         .as_slice(),
949                     &[]
950                 )
951                 .contains(&MarkupKind::Markdown);
952                 if is_markdown {
953                     HoverDocFormat::Markdown
954                 } else {
955                     HoverDocFormat::PlainText
956                 }
957             }),
958         }
959     }
960
961     pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
962         WorkspaceSymbolConfig {
963             search_scope: match self.data.workspace_symbol_search_scope {
964                 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
965                 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
966                     WorkspaceSymbolSearchScope::WorkspaceAndDependencies
967                 }
968             },
969             search_kind: match self.data.workspace_symbol_search_kind {
970                 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
971                 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
972             },
973         }
974     }
975
976     pub fn semantic_tokens_refresh(&self) -> bool {
977         try_or!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?, false)
978     }
979     pub fn code_lens_refresh(&self) -> bool {
980         try_or!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?, false)
981     }
982     pub fn insert_replace_support(&self) -> bool {
983         try_or!(
984             self.caps
985                 .text_document
986                 .as_ref()?
987                 .completion
988                 .as_ref()?
989                 .completion_item
990                 .as_ref()?
991                 .insert_replace_support?,
992             false
993         )
994     }
995     pub fn client_commands(&self) -> ClientCommandsConfig {
996         let commands =
997             try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
998         let commands: Option<lsp_ext::ClientCommandOptions> =
999             serde_json::from_value(commands.clone()).ok();
1000         let force = commands.is_none() && self.data.lens_forceCustomCommands;
1001         let commands = commands.map(|it| it.commands).unwrap_or_default();
1002
1003         let get = |name: &str| commands.iter().any(|it| it == name) || force;
1004
1005         ClientCommandsConfig {
1006             run_single: get("rust-analyzer.runSingle"),
1007             debug_single: get("rust-analyzer.debugSingle"),
1008             show_reference: get("rust-analyzer.showReferences"),
1009             goto_location: get("rust-analyzer.gotoLocation"),
1010             trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1011         }
1012     }
1013
1014     pub fn highlight_related(&self) -> HighlightRelatedConfig {
1015         HighlightRelatedConfig {
1016             references: self.data.highlightRelated_references,
1017             break_points: self.data.highlightRelated_breakPoints,
1018             exit_points: self.data.highlightRelated_exitPoints,
1019             yield_points: self.data.highlightRelated_yieldPoints,
1020         }
1021     }
1022
1023     pub fn prime_caches_num_threads(&self) -> u8 {
1024         match self.data.primeCaches_numThreads {
1025             0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1026             n => n,
1027         }
1028     }
1029 }
1030
1031 #[derive(Deserialize, Debug, Clone, Copy)]
1032 #[serde(rename_all = "snake_case")]
1033 enum SnippetScopeDef {
1034     Expr,
1035     Item,
1036     Type,
1037 }
1038
1039 impl Default for SnippetScopeDef {
1040     fn default() -> Self {
1041         SnippetScopeDef::Expr
1042     }
1043 }
1044
1045 #[derive(Deserialize, Debug, Clone, Default)]
1046 #[serde(default)]
1047 struct SnippetDef {
1048     #[serde(deserialize_with = "single_or_array")]
1049     prefix: Vec<String>,
1050     #[serde(deserialize_with = "single_or_array")]
1051     postfix: Vec<String>,
1052     description: Option<String>,
1053     #[serde(deserialize_with = "single_or_array")]
1054     body: Vec<String>,
1055     #[serde(deserialize_with = "single_or_array")]
1056     requires: Vec<String>,
1057     scope: SnippetScopeDef,
1058 }
1059
1060 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1061 where
1062     D: serde::Deserializer<'de>,
1063 {
1064     struct SingleOrVec;
1065
1066     impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1067         type Value = Vec<String>;
1068
1069         fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1070             formatter.write_str("string or array of strings")
1071         }
1072
1073         fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1074         where
1075             E: serde::de::Error,
1076         {
1077             Ok(vec![value.to_owned()])
1078         }
1079
1080         fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1081         where
1082             A: serde::de::SeqAccess<'de>,
1083         {
1084             Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1085         }
1086     }
1087
1088     deserializer.deserialize_any(SingleOrVec)
1089 }
1090
1091 #[derive(Deserialize, Debug, Clone)]
1092 #[serde(untagged)]
1093 enum ManifestOrProjectJson {
1094     Manifest(PathBuf),
1095     ProjectJson(ProjectJsonData),
1096 }
1097
1098 #[derive(Deserialize, Debug, Clone)]
1099 #[serde(rename_all = "snake_case")]
1100 pub enum ExprFillDefaultDef {
1101     #[serde(alias = "todo")]
1102     Todo,
1103     #[serde(alias = "default")]
1104     Default,
1105 }
1106
1107 #[derive(Deserialize, Debug, Clone)]
1108 #[serde(rename_all = "snake_case")]
1109 enum ImportGranularityDef {
1110     Preserve,
1111     #[serde(alias = "none")]
1112     Item,
1113     #[serde(alias = "full")]
1114     Crate,
1115     #[serde(alias = "last")]
1116     Module,
1117 }
1118
1119 #[derive(Deserialize, Debug, Clone)]
1120 #[serde(rename_all = "snake_case")]
1121 enum ImportPrefixDef {
1122     Plain,
1123     #[serde(alias = "self")]
1124     BySelf,
1125     #[serde(alias = "crate")]
1126     ByCrate,
1127 }
1128
1129 #[derive(Deserialize, Debug, Clone)]
1130 #[serde(rename_all = "snake_case")]
1131 enum WorkspaceSymbolSearchScopeDef {
1132     Workspace,
1133     WorkspaceAndDependencies,
1134 }
1135
1136 #[derive(Deserialize, Debug, Clone)]
1137 #[serde(rename_all = "snake_case")]
1138 enum WorkspaceSymbolSearchKindDef {
1139     OnlyTypes,
1140     AllSymbols,
1141 }
1142
1143 type ParallelPrimeCachesNumThreads = u8;
1144
1145 macro_rules! _config_data {
1146     (struct $name:ident {
1147         $(
1148             $(#[doc=$doc:literal])*
1149             $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1150         )*
1151     }) => {
1152         #[allow(non_snake_case)]
1153         #[derive(Debug, Clone)]
1154         struct $name { $($field: $ty,)* }
1155         impl $name {
1156             fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1157                 $name {$(
1158                     $field: get_field(
1159                         &mut json,
1160                         error_sink,
1161                         stringify!($field),
1162                         None$(.or(Some(stringify!($alias))))*,
1163                         $default,
1164                     ),
1165                 )*}
1166             }
1167
1168             fn json_schema() -> serde_json::Value {
1169                 schema(&[
1170                     $({
1171                         let field = stringify!($field);
1172                         let ty = stringify!($ty);
1173
1174                         (field, ty, &[$($doc),*], $default)
1175                     },)*
1176                 ])
1177             }
1178
1179             #[cfg(test)]
1180             fn manual() -> String {
1181                 manual(&[
1182                     $({
1183                         let field = stringify!($field);
1184                         let ty = stringify!($ty);
1185
1186                         (field, ty, &[$($doc),*], $default)
1187                     },)*
1188                 ])
1189             }
1190         }
1191     };
1192 }
1193 use _config_data as config_data;
1194
1195 fn get_field<T: DeserializeOwned>(
1196     json: &mut serde_json::Value,
1197     error_sink: &mut Vec<(String, serde_json::Error)>,
1198     field: &'static str,
1199     alias: Option<&'static str>,
1200     default: &str,
1201 ) -> T {
1202     let default = serde_json::from_str(default).unwrap();
1203
1204     // XXX: check alias first, to work-around the VS Code where it pre-fills the
1205     // defaults instead of sending an empty object.
1206     alias
1207         .into_iter()
1208         .chain(iter::once(field))
1209         .find_map(move |field| {
1210             let mut pointer = field.replace('_', "/");
1211             pointer.insert(0, '/');
1212             json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1213                 Ok(it) => Some(it),
1214                 Err(e) => {
1215                     tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1216                     error_sink.push((pointer, e));
1217                     None
1218                 }
1219             })
1220         })
1221         .unwrap_or(default)
1222 }
1223
1224 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1225     for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1226         fn key(f: &str) -> &str {
1227             f.splitn(2, '_').next().unwrap()
1228         }
1229         assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1230     }
1231
1232     let map = fields
1233         .iter()
1234         .map(|(field, ty, doc, default)| {
1235             let name = field.replace("_", ".");
1236             let name = format!("rust-analyzer.{}", name);
1237             let props = field_props(field, ty, doc, default);
1238             (name, props)
1239         })
1240         .collect::<serde_json::Map<_, _>>();
1241     map.into()
1242 }
1243
1244 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1245     let doc = doc_comment_to_string(doc);
1246     let doc = doc.trim_end_matches('\n');
1247     assert!(
1248         doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1249         "bad docs for {}: {:?}",
1250         field,
1251         doc
1252     );
1253     let default = default.parse::<serde_json::Value>().unwrap();
1254
1255     let mut map = serde_json::Map::default();
1256     macro_rules! set {
1257         ($($key:literal: $value:tt),*$(,)?) => {{$(
1258             map.insert($key.into(), serde_json::json!($value));
1259         )*}};
1260     }
1261     set!("markdownDescription": doc);
1262     set!("default": default);
1263
1264     match ty {
1265         "bool" => set!("type": "boolean"),
1266         "String" => set!("type": "string"),
1267         "Vec<String>" => set! {
1268             "type": "array",
1269             "items": { "type": "string" },
1270         },
1271         "Vec<PathBuf>" => set! {
1272             "type": "array",
1273             "items": { "type": "string" },
1274         },
1275         "FxHashSet<String>" => set! {
1276             "type": "array",
1277             "items": { "type": "string" },
1278             "uniqueItems": true,
1279         },
1280         "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1281             "type": "object",
1282         },
1283         "FxHashMap<String, SnippetDef>" => set! {
1284             "type": "object",
1285         },
1286         "FxHashMap<String, String>" => set! {
1287             "type": "object",
1288         },
1289         "Option<usize>" => set! {
1290             "type": ["null", "integer"],
1291             "minimum": 0,
1292         },
1293         "Option<String>" => set! {
1294             "type": ["null", "string"],
1295         },
1296         "Option<PathBuf>" => set! {
1297             "type": ["null", "string"],
1298         },
1299         "Option<bool>" => set! {
1300             "type": ["null", "boolean"],
1301         },
1302         "Option<Vec<String>>" => set! {
1303             "type": ["null", "array"],
1304             "items": { "type": "string" },
1305         },
1306         "MergeBehaviorDef" => set! {
1307             "type": "string",
1308             "enum": ["none", "crate", "module"],
1309             "enumDescriptions": [
1310                 "Do not merge imports at all.",
1311                 "Merge imports from the same crate into a single `use` statement.",
1312                 "Merge imports from the same module into a single `use` statement."
1313             ],
1314         },
1315         "ExprFillDefaultDef" => set! {
1316             "type": "string",
1317             "enum": ["todo", "default"],
1318             "enumDescriptions": [
1319                 "Fill missing expressions with the `todo` macro",
1320                 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1321             ],
1322         },
1323         "ImportGranularityDef" => set! {
1324             "type": "string",
1325             "enum": ["preserve", "crate", "module", "item"],
1326             "enumDescriptions": [
1327                 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1328                 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1329                 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1330                 "Flatten imports so that each has its own use statement."
1331             ],
1332         },
1333         "ImportPrefixDef" => set! {
1334             "type": "string",
1335             "enum": [
1336                 "plain",
1337                 "self",
1338                 "crate"
1339             ],
1340             "enumDescriptions": [
1341                 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1342                 "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.",
1343                 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1344             ],
1345         },
1346         "Vec<ManifestOrProjectJson>" => set! {
1347             "type": "array",
1348             "items": { "type": ["string", "object"] },
1349         },
1350         "WorkspaceSymbolSearchScopeDef" => set! {
1351             "type": "string",
1352             "enum": ["workspace", "workspace_and_dependencies"],
1353             "enumDescriptions": [
1354                 "Search in current workspace only",
1355                 "Search in current workspace and dependencies"
1356             ],
1357         },
1358         "WorkspaceSymbolSearchKindDef" => set! {
1359             "type": "string",
1360             "enum": ["only_types", "all_symbols"],
1361             "enumDescriptions": [
1362                 "Search for types only",
1363                 "Search for all symbols kinds"
1364             ],
1365         },
1366         "ParallelPrimeCachesNumThreads" => set! {
1367             "type": "number",
1368             "minimum": 0,
1369             "maximum": 255
1370         },
1371         _ => panic!("{}: {}", ty, default),
1372     }
1373
1374     map.into()
1375 }
1376
1377 #[cfg(test)]
1378 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1379     fields
1380         .iter()
1381         .map(|(field, _ty, doc, default)| {
1382             let name = format!("rust-analyzer.{}", field.replace("_", "."));
1383             let doc = doc_comment_to_string(*doc);
1384             if default.contains('\n') {
1385                 format!(
1386                     r#"[[{}]]{}::
1387 +
1388 --
1389 Default:
1390 ----
1391 {}
1392 ----
1393 {}
1394 --
1395 "#,
1396                     name, name, default, doc
1397                 )
1398             } else {
1399                 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1400             }
1401         })
1402         .collect::<String>()
1403 }
1404
1405 fn doc_comment_to_string(doc: &[&str]) -> String {
1406     doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1407 }
1408
1409 #[cfg(test)]
1410 mod tests {
1411     use std::fs;
1412
1413     use test_utils::{ensure_file_contents, project_root};
1414
1415     use super::*;
1416
1417     #[test]
1418     fn generate_package_json_config() {
1419         let s = Config::json_schema();
1420         let schema = format!("{:#}", s);
1421         let mut schema = schema
1422             .trim_start_matches('{')
1423             .trim_end_matches('}')
1424             .replace("  ", "    ")
1425             .replace("\n", "\n            ")
1426             .trim_start_matches('\n')
1427             .trim_end()
1428             .to_string();
1429         schema.push_str(",\n");
1430
1431         // Transform the asciidoc form link to markdown style.
1432         //
1433         // https://link[text] => [text](https://link)
1434         let url_matches = schema.match_indices("https://");
1435         let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1436         url_offsets.reverse();
1437         for idx in url_offsets {
1438             let link = &schema[idx..];
1439             // matching on whitespace to ignore normal links
1440             if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
1441                 if link.chars().nth(link_end) == Some('[') {
1442                     if let Some(link_text_end) = link.find(']') {
1443                         let link_text = link[link_end..(link_text_end + 1)].to_string();
1444
1445                         schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
1446                         schema.insert(idx, '(');
1447                         schema.insert(idx + link_end + 1, ')');
1448                         schema.insert_str(idx, &link_text);
1449                     }
1450                 }
1451             }
1452         }
1453
1454         let package_json_path = project_root().join("editors/code/package.json");
1455         let mut package_json = fs::read_to_string(&package_json_path).unwrap();
1456
1457         let start_marker = "                \"$generated-start\": {},\n";
1458         let end_marker = "                \"$generated-end\": {}\n";
1459
1460         let start = package_json.find(start_marker).unwrap() + start_marker.len();
1461         let end = package_json.find(end_marker).unwrap();
1462
1463         let p = remove_ws(&package_json[start..end]);
1464         let s = remove_ws(&schema);
1465         if !p.contains(&s) {
1466             package_json.replace_range(start..end, &schema);
1467             ensure_file_contents(&package_json_path, &package_json)
1468         }
1469     }
1470
1471     #[test]
1472     fn generate_config_documentation() {
1473         let docs_path = project_root().join("docs/user/generated_config.adoc");
1474         let expected = ConfigData::manual();
1475         ensure_file_contents(&docs_path, &expected);
1476     }
1477
1478     fn remove_ws(text: &str) -> String {
1479         text.replace(char::is_whitespace, "")
1480     }
1481 }