]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/config.rs
feat(diagnostics): use default expression instead of todo! when missing fields
[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         /// How assists will fill missing elements in an expression.
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         /// Enable support for procedural macros, implies `#rust-analyzer.cargo.runBuildScripts#`.
302         procMacro_enable: bool                     = "true",
303         /// Internal config, path to proc-macro server executable (typically,
304         /// this is rust-analyzer itself, but we override this in tests).
305         procMacro_server: Option<PathBuf>          = "null",
306
307         /// Command to be executed instead of 'cargo' for runnables.
308         runnables_overrideCargo: Option<String> = "null",
309         /// Additional arguments to be passed to cargo for runnables such as
310         /// tests or binaries. For example, it may be `--release`.
311         runnables_cargoExtraArgs: Vec<String>   = "[]",
312
313         /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
314         /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
315         /// is installed.
316         ///
317         /// Any project which uses rust-analyzer with the rustcPrivate
318         /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
319         ///
320         /// This option does not take effect until rust-analyzer is restarted.
321         rustcSource: Option<String> = "null",
322
323         /// Additional arguments to `rustfmt`.
324         rustfmt_extraArgs: Vec<String>               = "[]",
325         /// Advanced option, fully override the command rust-analyzer uses for
326         /// formatting.
327         rustfmt_overrideCommand: Option<Vec<String>> = "null",
328         /// Enables the use of rustfmt's unstable range formatting command for the
329         /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
330         /// available on a nightly build.
331         rustfmt_enableRangeFormatting: bool = "false",
332
333         /// Workspace symbol search scope.
334         workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
335         /// Workspace symbol search kind.
336         workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
337     }
338 }
339
340 impl Default for ConfigData {
341     fn default() -> Self {
342         ConfigData::from_json(serde_json::Value::Null)
343     }
344 }
345
346 #[derive(Debug, Clone)]
347 pub struct Config {
348     pub caps: lsp_types::ClientCapabilities,
349     data: ConfigData,
350     detached_files: Vec<AbsPathBuf>,
351     pub discovered_projects: Option<Vec<ProjectManifest>>,
352     pub root_path: AbsPathBuf,
353     snippets: Vec<Snippet>,
354 }
355
356 #[derive(Debug, Clone, Eq, PartialEq)]
357 pub enum LinkedProject {
358     ProjectManifest(ProjectManifest),
359     InlineJsonProject(ProjectJson),
360 }
361
362 impl From<ProjectManifest> for LinkedProject {
363     fn from(v: ProjectManifest) -> Self {
364         LinkedProject::ProjectManifest(v)
365     }
366 }
367
368 impl From<ProjectJson> for LinkedProject {
369     fn from(v: ProjectJson) -> Self {
370         LinkedProject::InlineJsonProject(v)
371     }
372 }
373
374 #[derive(Clone, Debug, PartialEq, Eq)]
375 pub struct LensConfig {
376     pub run: bool,
377     pub debug: bool,
378     pub implementations: bool,
379     pub method_refs: bool,
380     pub refs: bool, // for Struct, Enum, Union and Trait
381     pub enum_variant_refs: bool,
382 }
383
384 impl LensConfig {
385     pub fn any(&self) -> bool {
386         self.implementations || self.runnable() || self.references()
387     }
388
389     pub fn none(&self) -> bool {
390         !self.any()
391     }
392
393     pub fn runnable(&self) -> bool {
394         self.run || self.debug
395     }
396
397     pub fn references(&self) -> bool {
398         self.method_refs || self.refs || self.enum_variant_refs
399     }
400 }
401
402 #[derive(Clone, Debug, PartialEq, Eq)]
403 pub struct HoverActionsConfig {
404     pub implementations: bool,
405     pub references: bool,
406     pub run: bool,
407     pub debug: bool,
408     pub goto_type_def: bool,
409 }
410
411 impl HoverActionsConfig {
412     pub const NO_ACTIONS: Self = Self {
413         implementations: false,
414         references: false,
415         run: false,
416         debug: false,
417         goto_type_def: false,
418     };
419
420     pub fn any(&self) -> bool {
421         self.implementations || self.references || self.runnable() || self.goto_type_def
422     }
423
424     pub fn none(&self) -> bool {
425         !self.any()
426     }
427
428     pub fn runnable(&self) -> bool {
429         self.run || self.debug
430     }
431 }
432
433 #[derive(Debug, Clone)]
434 pub struct FilesConfig {
435     pub watcher: FilesWatcher,
436     pub exclude: Vec<AbsPathBuf>,
437 }
438
439 #[derive(Debug, Clone)]
440 pub enum FilesWatcher {
441     Client,
442     Notify,
443 }
444
445 #[derive(Debug, Clone)]
446 pub struct NotificationsConfig {
447     pub cargo_toml_not_found: bool,
448 }
449
450 #[derive(Debug, Clone)]
451 pub enum RustfmtConfig {
452     Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
453     CustomCommand { command: String, args: Vec<String> },
454 }
455
456 /// Configuration for runnable items, such as `main` function or tests.
457 #[derive(Debug, Clone)]
458 pub struct RunnablesConfig {
459     /// Custom command to be executed instead of `cargo` for runnables.
460     pub override_cargo: Option<String>,
461     /// Additional arguments for the `cargo`, e.g. `--release`.
462     pub cargo_extra_args: Vec<String>,
463 }
464
465 /// Configuration for workspace symbol search requests.
466 #[derive(Debug, Clone)]
467 pub struct WorkspaceSymbolConfig {
468     /// In what scope should the symbol be searched in.
469     pub search_scope: WorkspaceSymbolSearchScope,
470     /// What kind of symbol is being search for.
471     pub search_kind: WorkspaceSymbolSearchKind,
472 }
473
474 pub struct ClientCommandsConfig {
475     pub run_single: bool,
476     pub debug_single: bool,
477     pub show_reference: bool,
478     pub goto_location: bool,
479     pub trigger_parameter_hints: bool,
480 }
481
482 impl Config {
483     pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
484         Config {
485             caps,
486             data: ConfigData::default(),
487             detached_files: Vec::new(),
488             discovered_projects: None,
489             root_path,
490             snippets: Default::default(),
491         }
492     }
493     pub fn update(&mut self, mut json: serde_json::Value) {
494         tracing::info!("updating config from JSON: {:#}", json);
495         if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
496             return;
497         }
498         self.detached_files = get_field::<Vec<PathBuf>>(&mut json, "detachedFiles", None, "[]")
499             .into_iter()
500             .map(AbsPathBuf::assert)
501             .collect();
502         self.data = ConfigData::from_json(json);
503         self.snippets.clear();
504         for (name, def) in self.data.completion_snippets.iter() {
505             if def.prefix.is_empty() && def.postfix.is_empty() {
506                 continue;
507             }
508             let scope = match def.scope {
509                 SnippetScopeDef::Expr => SnippetScope::Expr,
510                 SnippetScopeDef::Type => SnippetScope::Type,
511                 SnippetScopeDef::Item => SnippetScope::Item,
512             };
513             match Snippet::new(
514                 &def.prefix,
515                 &def.postfix,
516                 &def.body,
517                 def.description.as_ref().unwrap_or(name),
518                 &def.requires,
519                 scope,
520             ) {
521                 Some(snippet) => self.snippets.push(snippet),
522                 None => tracing::info!("Invalid snippet {}", name),
523             }
524         }
525     }
526
527     pub fn json_schema() -> serde_json::Value {
528         ConfigData::json_schema()
529     }
530 }
531
532 macro_rules! try_ {
533     ($expr:expr) => {
534         || -> _ { Some($expr) }()
535     };
536 }
537 macro_rules! try_or {
538     ($expr:expr, $or:expr) => {
539         try_!($expr).unwrap_or($or)
540     };
541 }
542
543 impl Config {
544     pub fn linked_projects(&self) -> Vec<LinkedProject> {
545         if self.data.linkedProjects.is_empty() {
546             self.discovered_projects
547                 .as_ref()
548                 .into_iter()
549                 .flatten()
550                 .cloned()
551                 .map(LinkedProject::from)
552                 .collect()
553         } else {
554             self.data
555                 .linkedProjects
556                 .iter()
557                 .filter_map(|linked_project| {
558                     let res = match linked_project {
559                         ManifestOrProjectJson::Manifest(it) => {
560                             let path = self.root_path.join(it);
561                             ProjectManifest::from_manifest_file(path)
562                                 .map_err(|e| {
563                                     tracing::error!("failed to load linked project: {}", e)
564                                 })
565                                 .ok()?
566                                 .into()
567                         }
568                         ManifestOrProjectJson::ProjectJson(it) => {
569                             ProjectJson::new(&self.root_path, it.clone()).into()
570                         }
571                     };
572                     Some(res)
573                 })
574                 .collect()
575         }
576     }
577
578     pub fn detached_files(&self) -> &[AbsPathBuf] {
579         &self.detached_files
580     }
581
582     pub fn did_save_text_document_dynamic_registration(&self) -> bool {
583         let caps =
584             try_or!(self.caps.text_document.as_ref()?.synchronization.clone()?, Default::default());
585         caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
586     }
587     pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
588         try_or!(
589             self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?,
590             false
591         )
592     }
593
594     pub fn prefill_caches(&self) -> bool {
595         self.data.cache_warmup
596     }
597
598     pub fn location_link(&self) -> bool {
599         try_or!(self.caps.text_document.as_ref()?.definition?.link_support?, false)
600     }
601     pub fn line_folding_only(&self) -> bool {
602         try_or!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?, false)
603     }
604     pub fn hierarchical_symbols(&self) -> bool {
605         try_or!(
606             self.caps
607                 .text_document
608                 .as_ref()?
609                 .document_symbol
610                 .as_ref()?
611                 .hierarchical_document_symbol_support?,
612             false
613         )
614     }
615     pub fn code_action_literals(&self) -> bool {
616         try_!(self
617             .caps
618             .text_document
619             .as_ref()?
620             .code_action
621             .as_ref()?
622             .code_action_literal_support
623             .as_ref()?)
624         .is_some()
625     }
626     pub fn work_done_progress(&self) -> bool {
627         try_or!(self.caps.window.as_ref()?.work_done_progress?, false)
628     }
629     pub fn will_rename(&self) -> bool {
630         try_or!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?, false)
631     }
632     pub fn change_annotation_support(&self) -> bool {
633         try_!(self
634             .caps
635             .workspace
636             .as_ref()?
637             .workspace_edit
638             .as_ref()?
639             .change_annotation_support
640             .as_ref()?)
641         .is_some()
642     }
643     pub fn code_action_resolve(&self) -> bool {
644         try_or!(
645             self.caps
646                 .text_document
647                 .as_ref()?
648                 .code_action
649                 .as_ref()?
650                 .resolve_support
651                 .as_ref()?
652                 .properties
653                 .as_slice(),
654             &[]
655         )
656         .iter()
657         .any(|it| it == "edit")
658     }
659     pub fn signature_help_label_offsets(&self) -> bool {
660         try_or!(
661             self.caps
662                 .text_document
663                 .as_ref()?
664                 .signature_help
665                 .as_ref()?
666                 .signature_information
667                 .as_ref()?
668                 .parameter_information
669                 .as_ref()?
670                 .label_offset_support?,
671             false
672         )
673     }
674     pub fn offset_encoding(&self) -> OffsetEncoding {
675         if supports_utf8(&self.caps) {
676             OffsetEncoding::Utf8
677         } else {
678             OffsetEncoding::Utf16
679         }
680     }
681
682     fn experimental(&self, index: &'static str) -> bool {
683         try_or!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?, false)
684     }
685     pub fn code_action_group(&self) -> bool {
686         self.experimental("codeActionGroup")
687     }
688     pub fn server_status_notification(&self) -> bool {
689         self.experimental("serverStatusNotification")
690     }
691
692     pub fn publish_diagnostics(&self) -> bool {
693         self.data.diagnostics_enable
694     }
695     pub fn diagnostics(&self) -> DiagnosticsConfig {
696         DiagnosticsConfig {
697             disable_experimental: !self.data.diagnostics_enableExperimental,
698             disabled: self.data.diagnostics_disabled.clone(),
699             expr_fill_default: match self.data.assist_exprFillDefault {
700                 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
701                 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
702             },
703         }
704     }
705     pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
706         DiagnosticsMapConfig {
707             remap_prefix: self.data.diagnostics_remapPrefix.clone(),
708             warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
709             warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
710         }
711     }
712     pub fn lru_capacity(&self) -> Option<usize> {
713         self.data.lruCapacity
714     }
715     pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
716         if !self.data.procMacro_enable {
717             return None;
718         }
719         let path = match &self.data.procMacro_server {
720             Some(it) => self.root_path.join(it),
721             None => AbsPathBuf::assert(std::env::current_exe().ok()?),
722         };
723         Some((path, vec!["proc-macro".into()]))
724     }
725     pub fn expand_proc_attr_macros(&self) -> bool {
726         self.data.experimental_procAttrMacros
727     }
728     pub fn files(&self) -> FilesConfig {
729         FilesConfig {
730             watcher: match self.data.files_watcher.as_str() {
731                 "notify" => FilesWatcher::Notify,
732                 "client" | _ => FilesWatcher::Client,
733             },
734             exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
735         }
736     }
737     pub fn notifications(&self) -> NotificationsConfig {
738         NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
739     }
740     pub fn cargo_autoreload(&self) -> bool {
741         self.data.cargo_autoreload
742     }
743     pub fn run_build_scripts(&self) -> bool {
744         self.data.cargo_runBuildScripts || self.data.procMacro_enable
745     }
746     pub fn cargo(&self) -> CargoConfig {
747         let rustc_source = self.data.rustcSource.as_ref().map(|rustc_src| {
748             if rustc_src == "discover" {
749                 RustcSource::Discover
750             } else {
751                 RustcSource::Path(self.root_path.join(rustc_src))
752             }
753         });
754
755         CargoConfig {
756             no_default_features: self.data.cargo_noDefaultFeatures,
757             all_features: self.data.cargo_allFeatures,
758             features: self.data.cargo_features.clone(),
759             target: self.data.cargo_target.clone(),
760             no_sysroot: self.data.cargo_noSysroot,
761             rustc_source,
762             unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
763             wrap_rustc_in_build_scripts: self.data.cargo_useRustcWrapperForBuildScripts,
764         }
765     }
766
767     pub fn rustfmt(&self) -> RustfmtConfig {
768         match &self.data.rustfmt_overrideCommand {
769             Some(args) if !args.is_empty() => {
770                 let mut args = args.clone();
771                 let command = args.remove(0);
772                 RustfmtConfig::CustomCommand { command, args }
773             }
774             Some(_) | None => RustfmtConfig::Rustfmt {
775                 extra_args: self.data.rustfmt_extraArgs.clone(),
776                 enable_range_formatting: self.data.rustfmt_enableRangeFormatting,
777             },
778         }
779     }
780     pub fn flycheck(&self) -> Option<FlycheckConfig> {
781         if !self.data.checkOnSave_enable {
782             return None;
783         }
784         let flycheck_config = match &self.data.checkOnSave_overrideCommand {
785             Some(args) if !args.is_empty() => {
786                 let mut args = args.clone();
787                 let command = args.remove(0);
788                 FlycheckConfig::CustomCommand { command, args }
789             }
790             Some(_) | None => FlycheckConfig::CargoCommand {
791                 command: self.data.checkOnSave_command.clone(),
792                 target_triple: self
793                     .data
794                     .checkOnSave_target
795                     .clone()
796                     .or_else(|| self.data.cargo_target.clone()),
797                 all_targets: self.data.checkOnSave_allTargets,
798                 no_default_features: self
799                     .data
800                     .checkOnSave_noDefaultFeatures
801                     .unwrap_or(self.data.cargo_noDefaultFeatures),
802                 all_features: self
803                     .data
804                     .checkOnSave_allFeatures
805                     .unwrap_or(self.data.cargo_allFeatures),
806                 features: self
807                     .data
808                     .checkOnSave_features
809                     .clone()
810                     .unwrap_or_else(|| self.data.cargo_features.clone()),
811                 extra_args: self.data.checkOnSave_extraArgs.clone(),
812             },
813         };
814         Some(flycheck_config)
815     }
816     pub fn runnables(&self) -> RunnablesConfig {
817         RunnablesConfig {
818             override_cargo: self.data.runnables_overrideCargo.clone(),
819             cargo_extra_args: self.data.runnables_cargoExtraArgs.clone(),
820         }
821     }
822     pub fn inlay_hints(&self) -> InlayHintsConfig {
823         InlayHintsConfig {
824             type_hints: self.data.inlayHints_typeHints,
825             parameter_hints: self.data.inlayHints_parameterHints,
826             chaining_hints: self.data.inlayHints_chainingHints,
827             hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints,
828             max_length: self.data.inlayHints_maxLength,
829         }
830     }
831     fn insert_use_config(&self) -> InsertUseConfig {
832         InsertUseConfig {
833             granularity: match self.data.assist_importGranularity {
834                 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
835                 ImportGranularityDef::Item => ImportGranularity::Item,
836                 ImportGranularityDef::Crate => ImportGranularity::Crate,
837                 ImportGranularityDef::Module => ImportGranularity::Module,
838             },
839             enforce_granularity: self.data.assist_importEnforceGranularity,
840             prefix_kind: match self.data.assist_importPrefix {
841                 ImportPrefixDef::Plain => PrefixKind::Plain,
842                 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
843                 ImportPrefixDef::BySelf => PrefixKind::BySelf,
844             },
845             group: self.data.assist_importGroup,
846             skip_glob_imports: !self.data.assist_allowMergingIntoGlobImports,
847         }
848     }
849     pub fn completion(&self) -> CompletionConfig {
850         CompletionConfig {
851             enable_postfix_completions: self.data.completion_postfix_enable,
852             enable_imports_on_the_fly: self.data.completion_autoimport_enable
853                 && completion_item_edit_resolve(&self.caps),
854             enable_self_on_the_fly: self.data.completion_autoself_enable,
855             add_call_parenthesis: self.data.completion_addCallParenthesis,
856             add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
857             insert_use: self.insert_use_config(),
858             snippet_cap: SnippetCap::new(try_or!(
859                 self.caps
860                     .text_document
861                     .as_ref()?
862                     .completion
863                     .as_ref()?
864                     .completion_item
865                     .as_ref()?
866                     .snippet_support?,
867                 false
868             )),
869             snippets: self.snippets.clone(),
870         }
871     }
872     pub fn assist(&self) -> AssistConfig {
873         AssistConfig {
874             snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
875             allowed: None,
876             insert_use: self.insert_use_config(),
877         }
878     }
879     pub fn join_lines(&self) -> JoinLinesConfig {
880         JoinLinesConfig {
881             join_else_if: self.data.joinLines_joinElseIf,
882             remove_trailing_comma: self.data.joinLines_removeTrailingComma,
883             unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
884             join_assignments: self.data.joinLines_joinAssignments,
885         }
886     }
887     pub fn call_info_full(&self) -> bool {
888         self.data.callInfo_full
889     }
890     pub fn lens(&self) -> LensConfig {
891         LensConfig {
892             run: self.data.lens_enable && self.data.lens_run,
893             debug: self.data.lens_enable && self.data.lens_debug,
894             implementations: self.data.lens_enable && self.data.lens_implementations,
895             method_refs: self.data.lens_enable && self.data.lens_methodReferences,
896             refs: self.data.lens_enable && self.data.lens_references,
897             enum_variant_refs: self.data.lens_enable && self.data.lens_enumVariantReferences,
898         }
899     }
900     pub fn hover_actions(&self) -> HoverActionsConfig {
901         let enable = self.experimental("hoverActions") && self.data.hoverActions_enable;
902         HoverActionsConfig {
903             implementations: enable && self.data.hoverActions_implementations,
904             references: enable && self.data.hoverActions_references,
905             run: enable && self.data.hoverActions_run,
906             debug: enable && self.data.hoverActions_debug,
907             goto_type_def: enable && self.data.hoverActions_gotoTypeDef,
908         }
909     }
910     pub fn highlighting_strings(&self) -> bool {
911         self.data.highlighting_strings
912     }
913     pub fn hover(&self) -> HoverConfig {
914         HoverConfig {
915             links_in_hover: self.data.hover_linksInHover,
916             documentation: self.data.hover_documentation.then(|| {
917                 let is_markdown = try_or!(
918                     self.caps
919                         .text_document
920                         .as_ref()?
921                         .hover
922                         .as_ref()?
923                         .content_format
924                         .as_ref()?
925                         .as_slice(),
926                     &[]
927                 )
928                 .contains(&MarkupKind::Markdown);
929                 if is_markdown {
930                     HoverDocFormat::Markdown
931                 } else {
932                     HoverDocFormat::PlainText
933                 }
934             }),
935         }
936     }
937
938     pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
939         WorkspaceSymbolConfig {
940             search_scope: match self.data.workspace_symbol_search_scope {
941                 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
942                 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
943                     WorkspaceSymbolSearchScope::WorkspaceAndDependencies
944                 }
945             },
946             search_kind: match self.data.workspace_symbol_search_kind {
947                 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
948                 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
949             },
950         }
951     }
952
953     pub fn semantic_tokens_refresh(&self) -> bool {
954         try_or!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?, false)
955     }
956     pub fn code_lens_refresh(&self) -> bool {
957         try_or!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?, false)
958     }
959     pub fn insert_replace_support(&self) -> bool {
960         try_or!(
961             self.caps
962                 .text_document
963                 .as_ref()?
964                 .completion
965                 .as_ref()?
966                 .completion_item
967                 .as_ref()?
968                 .insert_replace_support?,
969             false
970         )
971     }
972     pub fn client_commands(&self) -> ClientCommandsConfig {
973         let commands =
974             try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
975         let commands: Option<lsp_ext::ClientCommandOptions> =
976             serde_json::from_value(commands.clone()).ok();
977         let force = commands.is_none() && self.data.lens_forceCustomCommands;
978         let commands = commands.map(|it| it.commands).unwrap_or_default();
979
980         let get = |name: &str| commands.iter().any(|it| it == name) || force;
981
982         ClientCommandsConfig {
983             run_single: get("rust-analyzer.runSingle"),
984             debug_single: get("rust-analyzer.debugSingle"),
985             show_reference: get("rust-analyzer.showReferences"),
986             goto_location: get("rust-analyzer.gotoLocation"),
987             trigger_parameter_hints: get("editor.action.triggerParameterHints"),
988         }
989     }
990
991     pub fn highlight_related(&self) -> HighlightRelatedConfig {
992         HighlightRelatedConfig {
993             references: self.data.highlightRelated_references,
994             break_points: self.data.highlightRelated_breakPoints,
995             exit_points: self.data.highlightRelated_exitPoints,
996             yield_points: self.data.highlightRelated_yieldPoints,
997         }
998     }
999 }
1000
1001 #[derive(Deserialize, Debug, Clone, Copy)]
1002 #[serde(rename_all = "snake_case")]
1003 enum SnippetScopeDef {
1004     Expr,
1005     Item,
1006     Type,
1007 }
1008
1009 impl Default for SnippetScopeDef {
1010     fn default() -> Self {
1011         SnippetScopeDef::Expr
1012     }
1013 }
1014
1015 #[derive(Deserialize, Debug, Clone, Default)]
1016 #[serde(default)]
1017 struct SnippetDef {
1018     #[serde(deserialize_with = "single_or_array")]
1019     prefix: Vec<String>,
1020     #[serde(deserialize_with = "single_or_array")]
1021     postfix: Vec<String>,
1022     description: Option<String>,
1023     #[serde(deserialize_with = "single_or_array")]
1024     body: Vec<String>,
1025     #[serde(deserialize_with = "single_or_array")]
1026     requires: Vec<String>,
1027     scope: SnippetScopeDef,
1028 }
1029
1030 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1031 where
1032     D: serde::Deserializer<'de>,
1033 {
1034     struct SingleOrVec;
1035
1036     impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1037         type Value = Vec<String>;
1038
1039         fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1040             formatter.write_str("string or array of strings")
1041         }
1042
1043         fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1044         where
1045             E: serde::de::Error,
1046         {
1047             Ok(vec![value.to_owned()])
1048         }
1049
1050         fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1051         where
1052             A: serde::de::SeqAccess<'de>,
1053         {
1054             Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1055         }
1056     }
1057
1058     deserializer.deserialize_any(SingleOrVec)
1059 }
1060
1061 #[derive(Deserialize, Debug, Clone)]
1062 #[serde(untagged)]
1063 enum ManifestOrProjectJson {
1064     Manifest(PathBuf),
1065     ProjectJson(ProjectJsonData),
1066 }
1067
1068 #[derive(Deserialize, Debug, Clone)]
1069 #[serde(rename_all = "snake_case")]
1070 pub enum ExprFillDefaultDef {
1071     #[serde(alias = "todo")]
1072     Todo,
1073     #[serde(alias = "default")]
1074     Default,
1075 }
1076
1077 #[derive(Deserialize, Debug, Clone)]
1078 #[serde(rename_all = "snake_case")]
1079 enum ImportGranularityDef {
1080     Preserve,
1081     #[serde(alias = "none")]
1082     Item,
1083     #[serde(alias = "full")]
1084     Crate,
1085     #[serde(alias = "last")]
1086     Module,
1087 }
1088
1089 #[derive(Deserialize, Debug, Clone)]
1090 #[serde(rename_all = "snake_case")]
1091 enum ImportPrefixDef {
1092     Plain,
1093     #[serde(alias = "self")]
1094     BySelf,
1095     #[serde(alias = "crate")]
1096     ByCrate,
1097 }
1098
1099 #[derive(Deserialize, Debug, Clone)]
1100 #[serde(rename_all = "snake_case")]
1101 enum WorkspaceSymbolSearchScopeDef {
1102     Workspace,
1103     WorkspaceAndDependencies,
1104 }
1105
1106 #[derive(Deserialize, Debug, Clone)]
1107 #[serde(rename_all = "snake_case")]
1108 enum WorkspaceSymbolSearchKindDef {
1109     OnlyTypes,
1110     AllSymbols,
1111 }
1112
1113 macro_rules! _config_data {
1114     (struct $name:ident {
1115         $(
1116             $(#[doc=$doc:literal])*
1117             $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1118         )*
1119     }) => {
1120         #[allow(non_snake_case)]
1121         #[derive(Debug, Clone)]
1122         struct $name { $($field: $ty,)* }
1123         impl $name {
1124             fn from_json(mut json: serde_json::Value) -> $name {
1125                 $name {$(
1126                     $field: get_field(
1127                         &mut json,
1128                         stringify!($field),
1129                         None$(.or(Some(stringify!($alias))))*,
1130                         $default,
1131                     ),
1132                 )*}
1133             }
1134
1135             fn json_schema() -> serde_json::Value {
1136                 schema(&[
1137                     $({
1138                         let field = stringify!($field);
1139                         let ty = stringify!($ty);
1140
1141                         (field, ty, &[$($doc),*], $default)
1142                     },)*
1143                 ])
1144             }
1145
1146             #[cfg(test)]
1147             fn manual() -> String {
1148                 manual(&[
1149                     $({
1150                         let field = stringify!($field);
1151                         let ty = stringify!($ty);
1152
1153                         (field, ty, &[$($doc),*], $default)
1154                     },)*
1155                 ])
1156             }
1157         }
1158     };
1159 }
1160 use _config_data as config_data;
1161
1162 fn get_field<T: DeserializeOwned>(
1163     json: &mut serde_json::Value,
1164     field: &'static str,
1165     alias: Option<&'static str>,
1166     default: &str,
1167 ) -> T {
1168     let default = serde_json::from_str(default).unwrap();
1169
1170     // XXX: check alias first, to work-around the VS Code where it pre-fills the
1171     // defaults instead of sending an empty object.
1172     alias
1173         .into_iter()
1174         .chain(iter::once(field))
1175         .find_map(move |field| {
1176             let mut pointer = field.replace('_', "/");
1177             pointer.insert(0, '/');
1178             json.pointer_mut(&pointer).and_then(|it| serde_json::from_value(it.take()).ok())
1179         })
1180         .unwrap_or(default)
1181 }
1182
1183 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1184     for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1185         fn key(f: &str) -> &str {
1186             f.splitn(2, '_').next().unwrap()
1187         }
1188         assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1189     }
1190
1191     let map = fields
1192         .iter()
1193         .map(|(field, ty, doc, default)| {
1194             let name = field.replace("_", ".");
1195             let name = format!("rust-analyzer.{}", name);
1196             let props = field_props(field, ty, doc, default);
1197             (name, props)
1198         })
1199         .collect::<serde_json::Map<_, _>>();
1200     map.into()
1201 }
1202
1203 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1204     let doc = doc_comment_to_string(doc);
1205     let doc = doc.trim_end_matches('\n');
1206     assert!(
1207         doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1208         "bad docs for {}: {:?}",
1209         field,
1210         doc
1211     );
1212     let default = default.parse::<serde_json::Value>().unwrap();
1213
1214     let mut map = serde_json::Map::default();
1215     macro_rules! set {
1216         ($($key:literal: $value:tt),*$(,)?) => {{$(
1217             map.insert($key.into(), serde_json::json!($value));
1218         )*}};
1219     }
1220     set!("markdownDescription": doc);
1221     set!("default": default);
1222
1223     match ty {
1224         "bool" => set!("type": "boolean"),
1225         "String" => set!("type": "string"),
1226         "Vec<String>" => set! {
1227             "type": "array",
1228             "items": { "type": "string" },
1229         },
1230         "Vec<PathBuf>" => set! {
1231             "type": "array",
1232             "items": { "type": "string" },
1233         },
1234         "FxHashSet<String>" => set! {
1235             "type": "array",
1236             "items": { "type": "string" },
1237             "uniqueItems": true,
1238         },
1239         "FxHashMap<String, SnippetDef>" => set! {
1240             "type": "object",
1241         },
1242         "FxHashMap<String, String>" => set! {
1243             "type": "object",
1244         },
1245         "Option<usize>" => set! {
1246             "type": ["null", "integer"],
1247             "minimum": 0,
1248         },
1249         "Option<String>" => set! {
1250             "type": ["null", "string"],
1251         },
1252         "Option<PathBuf>" => set! {
1253             "type": ["null", "string"],
1254         },
1255         "Option<bool>" => set! {
1256             "type": ["null", "boolean"],
1257         },
1258         "Option<Vec<String>>" => set! {
1259             "type": ["null", "array"],
1260             "items": { "type": "string" },
1261         },
1262         "MergeBehaviorDef" => set! {
1263             "type": "string",
1264             "enum": ["none", "crate", "module"],
1265             "enumDescriptions": [
1266                 "Do not merge imports at all.",
1267                 "Merge imports from the same crate into a single `use` statement.",
1268                 "Merge imports from the same module into a single `use` statement."
1269             ],
1270         },
1271         "ExprFillDefaultDef" => set! {
1272             "type": "string",
1273             "enum": ["todo", "default"],
1274             "enumDescriptions": [
1275                 "Fill missing expressions with the 'todo' macro",
1276                 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1277             ],
1278         },
1279         "ImportGranularityDef" => set! {
1280             "type": "string",
1281             "enum": ["preserve", "crate", "module", "item"],
1282             "enumDescriptions": [
1283                 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1284                 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1285                 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1286                 "Flatten imports so that each has its own use statement."
1287             ],
1288         },
1289         "ImportPrefixDef" => set! {
1290             "type": "string",
1291             "enum": [
1292                 "plain",
1293                 "self",
1294                 "crate"
1295             ],
1296             "enumDescriptions": [
1297                 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1298                 "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.",
1299                 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1300             ],
1301         },
1302         "Vec<ManifestOrProjectJson>" => set! {
1303             "type": "array",
1304             "items": { "type": ["string", "object"] },
1305         },
1306         "WorkspaceSymbolSearchScopeDef" => set! {
1307             "type": "string",
1308             "enum": ["workspace", "workspace_and_dependencies"],
1309             "enumDescriptions": [
1310                 "Search in current workspace only",
1311                 "Search in current workspace and dependencies"
1312             ],
1313         },
1314         "WorkspaceSymbolSearchKindDef" => set! {
1315             "type": "string",
1316             "enum": ["only_types", "all_symbols"],
1317             "enumDescriptions": [
1318                 "Search for types only",
1319                 "Search for all symbols kinds"
1320             ],
1321         },
1322         _ => panic!("{}: {}", ty, default),
1323     }
1324
1325     map.into()
1326 }
1327
1328 #[cfg(test)]
1329 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1330     fields
1331         .iter()
1332         .map(|(field, _ty, doc, default)| {
1333             let name = format!("rust-analyzer.{}", field.replace("_", "."));
1334             let doc = doc_comment_to_string(*doc);
1335             format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1336         })
1337         .collect::<String>()
1338 }
1339
1340 fn doc_comment_to_string(doc: &[&str]) -> String {
1341     doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1342 }
1343
1344 #[cfg(test)]
1345 mod tests {
1346     use std::fs;
1347
1348     use test_utils::{ensure_file_contents, project_root};
1349
1350     use super::*;
1351
1352     #[test]
1353     fn generate_package_json_config() {
1354         let s = Config::json_schema();
1355         let schema = format!("{:#}", s);
1356         let mut schema = schema
1357             .trim_start_matches('{')
1358             .trim_end_matches('}')
1359             .replace("  ", "    ")
1360             .replace("\n", "\n            ")
1361             .trim_start_matches('\n')
1362             .trim_end()
1363             .to_string();
1364         schema.push_str(",\n");
1365
1366         // Transform the asciidoc form link to markdown style.
1367         //
1368         // https://link[text] => [text](https://link)
1369         let url_matches = schema.match_indices("https://");
1370         let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1371         url_offsets.reverse();
1372         for idx in url_offsets {
1373             let link = &schema[idx..];
1374             // matching on whitespace to ignore normal links
1375             if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
1376                 if link.chars().nth(link_end) == Some('[') {
1377                     if let Some(link_text_end) = link.find(']') {
1378                         let link_text = link[link_end..(link_text_end + 1)].to_string();
1379
1380                         schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
1381                         schema.insert(idx, '(');
1382                         schema.insert(idx + link_end + 1, ')');
1383                         schema.insert_str(idx, &link_text);
1384                     }
1385                 }
1386             }
1387         }
1388
1389         let package_json_path = project_root().join("editors/code/package.json");
1390         let mut package_json = fs::read_to_string(&package_json_path).unwrap();
1391
1392         let start_marker = "                \"$generated-start\": {},\n";
1393         let end_marker = "                \"$generated-end\": {}\n";
1394
1395         let start = package_json.find(start_marker).unwrap() + start_marker.len();
1396         let end = package_json.find(end_marker).unwrap();
1397
1398         let p = remove_ws(&package_json[start..end]);
1399         let s = remove_ws(&schema);
1400         if !p.contains(&s) {
1401             package_json.replace_range(start..end, &schema);
1402             ensure_file_contents(&package_json_path, &package_json)
1403         }
1404     }
1405
1406     #[test]
1407     fn generate_config_documentation() {
1408         let docs_path = project_root().join("docs/user/generated_config.adoc");
1409         let expected = ConfigData::manual();
1410         ensure_file_contents(&docs_path, &expected);
1411     }
1412
1413     fn remove_ws(text: &str) -> String {
1414         text.replace(char::is_whitespace, "")
1415     }
1416 }