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