1 //! Config used by the language server.
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.
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.
10 use std::{ffi::OsString, fmt, iter, path::PathBuf};
12 use flycheck::FlycheckConfig;
14 AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
15 HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig,
16 Snippet, SnippetScope,
19 imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
22 use itertools::Itertools;
23 use lsp_types::{ClientCapabilities, MarkupKind};
25 CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates,
27 use rustc_hash::{FxHashMap, FxHashSet};
28 use serde::{de::DeserializeOwned, Deserialize};
32 caps::completion_item_edit_resolve,
33 diagnostics::DiagnosticsMapConfig,
34 line_index::OffsetEncoding,
35 lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
40 // Conventions for configuration keys to preserve maximal extendability without breakage:
41 // - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable`
42 // This has the benefit of namespaces being extensible, and if the suffix doesn't fit later it can be changed without breakage.
43 // - In general be wary of using the namespace of something verbatim, it prevents us from adding subkeys in the future
44 // - Don't use abbreviations unless really necessary
45 // - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
47 // Defines the server-side configuration of the rust-analyzer. We generate
48 // *parts* of VS Code's `package.json` config from this.
50 // However, editor specific config, which the server doesn't know about, should
51 // be specified directly in `package.json`.
53 // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
54 // parsing the old name.
57 /// Placeholder expression to use for missing expressions in assists.
58 assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
60 /// Warm up caches on project load.
61 cachePriming_enable: bool = "true",
62 /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
63 cachePriming_numThreads: ParallelCachePrimingNumThreads = "0",
65 /// Automatically refresh project info via `cargo metadata` on
66 /// `Cargo.toml` or `.cargo/config.toml` changes.
67 cargo_autoreload: bool = "true",
68 /// Run build scripts (`build.rs`) for more precise code analysis.
69 cargo_buildScripts_enable: bool = "true",
70 /// Override the command rust-analyzer uses to run build scripts and
71 /// build procedural macros. The command is required to output json
72 /// and should therefor include `--message-format=json` or a similar
75 /// By default, a cargo invocation will be constructed for the configured
76 /// targets and features, with the following base command line:
79 /// cargo check --quiet --workspace --message-format=json --all-targets
82 cargo_buildScripts_overrideCommand: Option<Vec<String>> = "null",
83 /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
84 /// avoid compiling unnecessary things.
85 cargo_buildScripts_useRustcWrapper: bool = "true",
86 /// List of features to activate.
88 /// Set this to `"all"` to pass `--all-features` to cargo.
89 cargo_features: CargoFeatures = "[]",
90 /// Whether to pass `--no-default-features` to cargo.
91 cargo_noDefaultFeatures: bool = "false",
92 /// Internal config for debugging, disables loading of sysroot crates.
93 cargo_noSysroot: bool = "false",
94 /// Compilation target override (target triple).
95 cargo_target: Option<String> = "null",
96 /// Unsets `#[cfg(test)]` for the specified crates.
97 cargo_unsetTest: Vec<String> = "[\"core\"]",
99 /// Check all targets and tests (`--all-targets`).
100 checkOnSave_allTargets: bool = "true",
101 /// Cargo command to use for `cargo check`.
102 checkOnSave_command: String = "\"check\"",
103 /// Run specified `cargo check` command for diagnostics on save.
104 checkOnSave_enable: bool = "true",
105 /// Extra arguments for `cargo check`.
106 checkOnSave_extraArgs: Vec<String> = "[]",
107 /// List of features to activate. Defaults to
108 /// `#rust-analyzer.cargo.features#`.
110 /// Set to `"all"` to pass `--all-features` to cargo.
111 checkOnSave_features: Option<CargoFeatures> = "null",
112 /// Whether to pass `--no-default-features` to cargo. Defaults to
113 /// `#rust-analyzer.cargo.noDefaultFeatures#`.
114 checkOnSave_noDefaultFeatures: Option<bool> = "null",
115 /// Override the command rust-analyzer uses to run build scripts and
116 /// build procedural macros. The command is required to output json
117 /// and should therefor include `--message-format=json` or a similar
120 /// An example command would be:
123 /// cargo check --workspace --message-format=json --all-targets
126 checkOnSave_overrideCommand: Option<Vec<String>> = "null",
127 /// Check for a specific target. Defaults to
128 /// `#rust-analyzer.cargo.target#`.
129 checkOnSave_target: Option<String> = "null",
131 /// Toggles the additional completions that automatically add imports when completed.
132 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
133 completion_autoimport_enable: bool = "true",
134 /// Toggles the additional completions that automatically show method calls and field accesses
135 /// with `self` prefixed to them when inside a method.
136 completion_autoself_enable: bool = "true",
137 /// Whether to add parenthesis and argument snippets when completing function.
138 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
139 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
140 completion_postfix_enable: bool = "true",
141 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
142 completion_privateEditable_enable: bool = "false",
143 /// Custom completion snippets.
144 // NOTE: Keep this list in sync with the feature docs of user snippets.
145 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
148 "body": "Arc::new(${receiver})",
149 "requires": "std::sync::Arc",
150 "description": "Put the expression into an `Arc`",
155 "body": "Rc::new(${receiver})",
156 "requires": "std::rc::Rc",
157 "description": "Put the expression into an `Rc`",
162 "body": "Box::pin(${receiver})",
163 "requires": "std::boxed::Box",
164 "description": "Put the expression into a pinned `Box`",
169 "body": "Ok(${receiver})",
170 "description": "Wrap the expression in a `Result::Ok`",
175 "body": "Err(${receiver})",
176 "description": "Wrap the expression in a `Result::Err`",
181 "body": "Some(${receiver})",
182 "description": "Wrap the expression in an `Option::Some`",
187 /// List of rust-analyzer diagnostics to disable.
188 diagnostics_disabled: FxHashSet<String> = "[]",
189 /// Whether to show native rust-analyzer diagnostics.
190 diagnostics_enable: bool = "true",
191 /// Whether to show experimental rust-analyzer diagnostics that might
192 /// have more false positives than usual.
193 diagnostics_experimental_enable: bool = "false",
194 /// Map of prefixes to be substituted when parsing diagnostic file paths.
195 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
196 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
197 /// List of warnings that should be displayed with hint severity.
199 /// The warnings will be indicated by faded text or three dots in code
200 /// and will not show up in the `Problems Panel`.
201 diagnostics_warningsAsHint: Vec<String> = "[]",
202 /// List of warnings that should be displayed with info severity.
204 /// The warnings will be indicated by a blue squiggly underline in code
205 /// and a blue icon in the `Problems Panel`.
206 diagnostics_warningsAsInfo: Vec<String> = "[]",
208 /// These directories will be ignored by rust-analyzer. They are
209 /// relative to the workspace root, and globs are not supported. You may
210 /// also need to add the folders to Code's `files.watcherExclude`.
211 files_excludeDirs: Vec<PathBuf> = "[]",
212 /// Controls file watching implementation.
213 files_watcher: String = "\"client\"",
215 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
216 highlightRelated_breakPoints_enable: bool = "true",
217 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
218 highlightRelated_exitPoints_enable: bool = "true",
219 /// Enables highlighting of related references while the cursor is on any identifier.
220 highlightRelated_references_enable: bool = "true",
221 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
222 highlightRelated_yieldPoints_enable: bool = "true",
224 /// Whether to show `Debug` action. Only applies when
225 /// `#rust-analyzer.hover.actions.enable#` is set.
226 hover_actions_debug_enable: bool = "true",
227 /// Whether to show HoverActions in Rust files.
228 hover_actions_enable: bool = "true",
229 /// Whether to show `Go to Type Definition` action. Only applies when
230 /// `#rust-analyzer.hover.actions.enable#` is set.
231 hover_actions_gotoTypeDef_enable: bool = "true",
232 /// Whether to show `Implementations` action. Only applies when
233 /// `#rust-analyzer.hover.actions.enable#` is set.
234 hover_actions_implementations_enable: bool = "true",
235 /// Whether to show `References` action. Only applies when
236 /// `#rust-analyzer.hover.actions.enable#` is set.
237 hover_actions_references_enable: bool = "false",
238 /// Whether to show `Run` action. Only applies when
239 /// `#rust-analyzer.hover.actions.enable#` is set.
240 hover_actions_run_enable: bool = "true",
242 /// Whether to show documentation on hover.
243 hover_documentation_enable: bool = "true",
244 /// Use markdown syntax for links in hover.
245 hover_links_enable: bool = "true",
247 /// 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.
248 imports_granularity_enforce: bool = "false",
249 /// How imports should be grouped into use statements.
250 imports_granularity_group: ImportGranularityDef = "\"crate\"",
251 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
252 imports_group_enable: bool = "true",
253 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
254 imports_merge_glob: bool = "true",
255 /// The path structure for newly inserted paths to use.
256 imports_prefix: ImportPrefixDef = "\"plain\"",
258 /// Whether to show inlay type hints for binding modes.
259 inlayHints_bindingModeHints_enable: bool = "false",
260 /// Whether to show inlay type hints for method chains.
261 inlayHints_chainingHints_enable: bool = "true",
262 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
263 inlayHints_closingBraceHints_enable: bool = "true",
264 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
265 /// to always show them).
266 inlayHints_closingBraceHints_minLines: usize = "25",
267 /// Whether to show inlay type hints for return types of closures with blocks.
268 inlayHints_closureReturnTypeHints_enable: bool = "false",
269 /// Whether to show inlay type hints for elided lifetimes in function signatures.
270 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
271 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
272 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
273 /// Maximum length for inlay hints. Set to null to have an unlimited length.
274 inlayHints_maxLength: Option<usize> = "25",
275 /// Whether to show function parameter name inlay hints at the call
277 inlayHints_parameterHints_enable: bool = "true",
278 /// Whether to show inlay type hints for compiler inserted reborrows.
279 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
280 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
281 inlayHints_renderColons: bool = "true",
282 /// Whether to show inlay type hints for variables.
283 inlayHints_typeHints_enable: bool = "true",
284 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
285 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
286 inlayHints_typeHints_hideClosureInitialization: bool = "false",
287 /// Whether to hide inlay type hints for constructors.
288 inlayHints_typeHints_hideNamedConstructor: bool = "false",
290 /// Join lines merges consecutive declaration and initialization of an assignment.
291 joinLines_joinAssignments: bool = "true",
292 /// Join lines inserts else between consecutive ifs.
293 joinLines_joinElseIf: bool = "true",
294 /// Join lines removes trailing commas.
295 joinLines_removeTrailingComma: bool = "true",
296 /// Join lines unwraps trivial blocks.
297 joinLines_unwrapTrivialBlock: bool = "true",
299 /// Whether to show `Debug` lens. Only applies when
300 /// `#rust-analyzer.lens.enable#` is set.
301 lens_debug_enable: bool = "true",
302 /// Whether to show CodeLens in Rust files.
303 lens_enable: bool = "true",
304 /// Internal config: use custom client-side commands even when the
305 /// client doesn't set the corresponding capability.
306 lens_forceCustomCommands: bool = "true",
307 /// Whether to show `Implementations` lens. Only applies when
308 /// `#rust-analyzer.lens.enable#` is set.
309 lens_implementations_enable: bool = "true",
310 /// Whether to show `References` lens for Struct, Enum, and Union.
311 /// Only applies when `#rust-analyzer.lens.enable#` is set.
312 lens_references_adt_enable: bool = "false",
313 /// Whether to show `References` lens for Enum Variants.
314 /// Only applies when `#rust-analyzer.lens.enable#` is set.
315 lens_references_enumVariant_enable: bool = "false",
316 /// Whether to show `Method References` lens. Only applies when
317 /// `#rust-analyzer.lens.enable#` is set.
318 lens_references_method_enable: bool = "false",
319 /// Whether to show `References` lens for Trait.
320 /// Only applies when `#rust-analyzer.lens.enable#` is set.
321 lens_references_trait_enable: bool = "false",
322 /// Whether to show `Run` lens. Only applies when
323 /// `#rust-analyzer.lens.enable#` is set.
324 lens_run_enable: bool = "true",
326 /// Disable project auto-discovery in favor of explicitly specified set
329 /// Elements must be paths pointing to `Cargo.toml`,
330 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
331 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
333 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
334 lru_capacity: Option<usize> = "null",
336 /// Whether to show `can't find Cargo.toml` error message.
337 notifications_cargoTomlNotFound: bool = "true",
339 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
340 procMacro_attributes_enable: bool = "true",
341 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
342 procMacro_enable: bool = "true",
343 /// These proc-macros will be ignored when trying to expand them.
345 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
346 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
347 /// Internal config, path to proc-macro server executable (typically,
348 /// this is rust-analyzer itself, but we override this in tests).
349 procMacro_server: Option<PathBuf> = "null",
351 /// Command to be executed instead of 'cargo' for runnables.
352 runnables_command: Option<String> = "null",
353 /// Additional arguments to be passed to cargo for runnables such as
354 /// tests or binaries. For example, it may be `--release`.
355 runnables_extraArgs: Vec<String> = "[]",
357 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
358 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
361 /// Any project which uses rust-analyzer with the rustcPrivate
362 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
364 /// This option does not take effect until rust-analyzer is restarted.
365 rustc_source: Option<String> = "null",
367 /// Additional arguments to `rustfmt`.
368 rustfmt_extraArgs: Vec<String> = "[]",
369 /// Advanced option, fully override the command rust-analyzer uses for
371 rustfmt_overrideCommand: Option<Vec<String>> = "null",
372 /// Enables the use of rustfmt's unstable range formatting command for the
373 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
374 /// available on a nightly build.
375 rustfmt_rangeFormatting_enable: bool = "false",
377 /// Use semantic tokens for strings.
379 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
380 /// By disabling semantic tokens for strings, other grammars can be used to highlight
382 semanticHighlighting_strings_enable: bool = "true",
384 /// Show full signature of the callable. Only shows parameters if disabled.
385 signatureInfo_detail: SignatureDetail = "\"full\"",
386 /// Show documentation.
387 signatureInfo_documentation_enable: bool = "true",
389 /// Workspace symbol search kind.
390 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
391 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
392 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
393 /// Other clients requires all results upfront and might require a higher limit.
394 workspace_symbol_search_limit: usize = "128",
395 /// Workspace symbol search scope.
396 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
400 impl Default for ConfigData {
401 fn default() -> Self {
402 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
406 #[derive(Debug, Clone)]
408 pub discovered_projects: Option<Vec<ProjectManifest>>,
409 caps: lsp_types::ClientCapabilities,
410 root_path: AbsPathBuf,
412 detached_files: Vec<AbsPathBuf>,
413 snippets: Vec<Snippet>,
416 type ParallelCachePrimingNumThreads = u8;
418 #[derive(Debug, Clone, Eq, PartialEq)]
419 pub enum LinkedProject {
420 ProjectManifest(ProjectManifest),
421 InlineJsonProject(ProjectJson),
424 impl From<ProjectManifest> for LinkedProject {
425 fn from(v: ProjectManifest) -> Self {
426 LinkedProject::ProjectManifest(v)
430 impl From<ProjectJson> for LinkedProject {
431 fn from(v: ProjectJson) -> Self {
432 LinkedProject::InlineJsonProject(v)
436 pub struct CallInfoConfig {
437 pub params_only: bool,
441 #[derive(Clone, Debug, PartialEq, Eq)]
442 pub struct LensConfig {
448 pub implementations: bool,
451 pub method_refs: bool,
452 pub refs_adt: bool, // for Struct, Enum, Union and Trait
453 pub refs_trait: bool, // for Struct, Enum, Union and Trait
454 pub enum_variant_refs: bool,
458 pub fn any(&self) -> bool {
461 || self.implementations
465 || self.enum_variant_refs
468 pub fn none(&self) -> bool {
472 pub fn runnable(&self) -> bool {
473 self.run || self.debug
476 pub fn references(&self) -> bool {
477 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
481 #[derive(Clone, Debug, PartialEq, Eq)]
482 pub struct HoverActionsConfig {
483 pub implementations: bool,
484 pub references: bool,
487 pub goto_type_def: bool,
490 impl HoverActionsConfig {
491 pub const NO_ACTIONS: Self = Self {
492 implementations: false,
496 goto_type_def: false,
499 pub fn any(&self) -> bool {
500 self.implementations || self.references || self.runnable() || self.goto_type_def
503 pub fn none(&self) -> bool {
507 pub fn runnable(&self) -> bool {
508 self.run || self.debug
512 #[derive(Debug, Clone)]
513 pub struct FilesConfig {
514 pub watcher: FilesWatcher,
515 pub exclude: Vec<AbsPathBuf>,
518 #[derive(Debug, Clone)]
519 pub enum FilesWatcher {
524 #[derive(Debug, Clone)]
525 pub struct NotificationsConfig {
526 pub cargo_toml_not_found: bool,
529 #[derive(Debug, Clone)]
530 pub enum RustfmtConfig {
531 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
532 CustomCommand { command: String, args: Vec<String> },
535 /// Configuration for runnable items, such as `main` function or tests.
536 #[derive(Debug, Clone)]
537 pub struct RunnablesConfig {
538 /// Custom command to be executed instead of `cargo` for runnables.
539 pub override_cargo: Option<String>,
540 /// Additional arguments for the `cargo`, e.g. `--release`.
541 pub cargo_extra_args: Vec<String>,
544 /// Configuration for workspace symbol search requests.
545 #[derive(Debug, Clone)]
546 pub struct WorkspaceSymbolConfig {
547 /// In what scope should the symbol be searched in.
548 pub search_scope: WorkspaceSymbolSearchScope,
549 /// What kind of symbol is being searched for.
550 pub search_kind: WorkspaceSymbolSearchKind,
551 /// How many items are returned at most.
552 pub search_limit: usize,
555 pub struct ClientCommandsConfig {
556 pub run_single: bool,
557 pub debug_single: bool,
558 pub show_reference: bool,
559 pub goto_location: bool,
560 pub trigger_parameter_hints: bool,
564 pub struct ConfigUpdateError {
565 errors: Vec<(String, serde_json::Error)>,
568 impl fmt::Display for ConfigUpdateError {
569 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
577 "rust-analyzer found {} invalid config value{}:\n{}",
579 if self.errors.len() == 1 { "" } else { "s" },
586 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
589 data: ConfigData::default(),
590 detached_files: Vec::new(),
591 discovered_projects: None,
593 snippets: Default::default(),
597 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
598 tracing::info!("updating config from JSON: {:#}", json);
599 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
602 let mut errors = Vec::new();
603 self.detached_files =
604 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
606 .map(AbsPathBuf::assert)
608 patch_old_style::patch_json_for_outdated_configs(&mut json);
609 self.data = ConfigData::from_json(json, &mut errors);
610 tracing::debug!("deserialized config data: {:#?}", self.data);
611 self.snippets.clear();
612 for (name, def) in self.data.completion_snippets_custom.iter() {
613 if def.prefix.is_empty() && def.postfix.is_empty() {
616 let scope = match def.scope {
617 SnippetScopeDef::Expr => SnippetScope::Expr,
618 SnippetScopeDef::Type => SnippetScope::Type,
619 SnippetScopeDef::Item => SnippetScope::Item,
625 def.description.as_ref().unwrap_or(name),
629 Some(snippet) => self.snippets.push(snippet),
630 None => errors.push((
631 format!("snippet {name} is invalid"),
632 <serde_json::Error as serde::de::Error>::custom(
633 "snippet path is invalid or triggers are missing",
639 self.validate(&mut errors);
641 if errors.is_empty() {
644 Err(ConfigUpdateError { errors })
648 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
649 use serde::de::Error;
650 if self.data.checkOnSave_command.is_empty() {
652 "/checkOnSave/command".to_string(),
653 serde_json::Error::custom("expected a non-empty string"),
658 pub fn json_schema() -> serde_json::Value {
659 ConfigData::json_schema()
662 pub fn root_path(&self) -> &AbsPathBuf {
666 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
670 pub fn detached_files(&self) -> &[AbsPathBuf] {
677 || -> _ { Some($expr) }()
680 macro_rules! try_or {
681 ($expr:expr, $or:expr) => {
682 try_!($expr).unwrap_or($or)
686 macro_rules! try_or_def {
688 try_!($expr).unwrap_or_default()
693 pub fn linked_projects(&self) -> Vec<LinkedProject> {
694 match self.data.linkedProjects.as_slice() {
695 [] => match self.discovered_projects.as_ref() {
696 Some(discovered_projects) => {
697 discovered_projects.iter().cloned().map(LinkedProject::from).collect()
701 linked_projects => linked_projects
703 .filter_map(|linked_project| match linked_project {
704 ManifestOrProjectJson::Manifest(it) => {
705 let path = self.root_path.join(it);
706 ProjectManifest::from_manifest_file(path)
707 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
711 ManifestOrProjectJson::ProjectJson(it) => {
712 Some(ProjectJson::new(&self.root_path, it.clone()).into())
719 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
720 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
721 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
724 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
726 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
730 pub fn prefill_caches(&self) -> bool {
731 self.data.cachePriming_enable
734 pub fn location_link(&self) -> bool {
735 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
738 pub fn line_folding_only(&self) -> bool {
739 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
742 pub fn hierarchical_symbols(&self) -> bool {
749 .hierarchical_document_symbol_support?
753 pub fn code_action_literals(&self) -> bool {
760 .code_action_literal_support
765 pub fn work_done_progress(&self) -> bool {
766 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
769 pub fn will_rename(&self) -> bool {
770 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
773 pub fn change_annotation_support(&self) -> bool {
780 .change_annotation_support
785 pub fn code_action_resolve(&self) -> bool {
797 .any(|it| it == "edit")
800 pub fn signature_help_label_offsets(&self) -> bool {
807 .signature_information
809 .parameter_information
811 .label_offset_support?
815 pub fn offset_encoding(&self) -> OffsetEncoding {
816 if supports_utf8(&self.caps) {
819 OffsetEncoding::Utf16
823 fn experimental(&self, index: &'static str) -> bool {
824 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
827 pub fn code_action_group(&self) -> bool {
828 self.experimental("codeActionGroup")
831 pub fn server_status_notification(&self) -> bool {
832 self.experimental("serverStatusNotification")
835 pub fn publish_diagnostics(&self) -> bool {
836 self.data.diagnostics_enable
839 pub fn diagnostics(&self) -> DiagnosticsConfig {
841 disable_experimental: !self.data.diagnostics_experimental_enable,
842 disabled: self.data.diagnostics_disabled.clone(),
843 expr_fill_default: match self.data.assist_expressionFillDefault {
844 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
845 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
850 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
851 DiagnosticsMapConfig {
852 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
853 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
854 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
858 pub fn lru_capacity(&self) -> Option<usize> {
859 self.data.lru_capacity
862 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
863 if !self.data.procMacro_enable {
866 let path = match &self.data.procMacro_server {
867 Some(it) => self.root_path.join(it),
868 None => AbsPathBuf::assert(std::env::current_exe().ok()?),
870 Some((path, vec!["proc-macro".into()]))
873 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
874 &self.data.procMacro_ignored
877 pub fn expand_proc_attr_macros(&self) -> bool {
878 self.data.procMacro_attributes_enable
881 pub fn files(&self) -> FilesConfig {
883 watcher: match self.data.files_watcher.as_str() {
884 "notify" => FilesWatcher::Notify,
885 "client" if self.did_change_watched_files_dynamic_registration() => {
888 _ => FilesWatcher::Notify,
890 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
894 pub fn notifications(&self) -> NotificationsConfig {
895 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
898 pub fn cargo_autoreload(&self) -> bool {
899 self.data.cargo_autoreload
902 pub fn run_build_scripts(&self) -> bool {
903 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
906 pub fn cargo(&self) -> CargoConfig {
907 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
908 if rustc_src == "discover" {
909 RustcSource::Discover
911 RustcSource::Path(self.root_path.join(rustc_src))
916 no_default_features: self.data.cargo_noDefaultFeatures,
917 all_features: matches!(self.data.cargo_features, CargoFeatures::All),
918 features: match &self.data.cargo_features {
919 CargoFeatures::All => vec![],
920 CargoFeatures::Listed(it) => it.clone(),
922 target: self.data.cargo_target.clone(),
923 no_sysroot: self.data.cargo_noSysroot,
925 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
926 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
927 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
931 pub fn rustfmt(&self) -> RustfmtConfig {
932 match &self.data.rustfmt_overrideCommand {
933 Some(args) if !args.is_empty() => {
934 let mut args = args.clone();
935 let command = args.remove(0);
936 RustfmtConfig::CustomCommand { command, args }
938 Some(_) | None => RustfmtConfig::Rustfmt {
939 extra_args: self.data.rustfmt_extraArgs.clone(),
940 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
945 pub fn flycheck(&self) -> Option<FlycheckConfig> {
946 if !self.data.checkOnSave_enable {
949 let flycheck_config = match &self.data.checkOnSave_overrideCommand {
950 Some(args) if !args.is_empty() => {
951 let mut args = args.clone();
952 let command = args.remove(0);
953 FlycheckConfig::CustomCommand { command, args }
955 Some(_) | None => FlycheckConfig::CargoCommand {
956 command: self.data.checkOnSave_command.clone(),
961 .or_else(|| self.data.cargo_target.clone()),
962 all_targets: self.data.checkOnSave_allTargets,
963 no_default_features: self
965 .checkOnSave_noDefaultFeatures
966 .unwrap_or(self.data.cargo_noDefaultFeatures),
967 all_features: matches!(
968 self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
973 .checkOnSave_features
975 .unwrap_or_else(|| self.data.cargo_features.clone())
977 CargoFeatures::All => vec![],
978 CargoFeatures::Listed(it) => it,
980 extra_args: self.data.checkOnSave_extraArgs.clone(),
983 Some(flycheck_config)
986 pub fn runnables(&self) -> RunnablesConfig {
988 override_cargo: self.data.runnables_command.clone(),
989 cargo_extra_args: self.data.runnables_extraArgs.clone(),
993 pub fn inlay_hints(&self) -> InlayHintsConfig {
995 render_colons: self.data.inlayHints_renderColons,
996 type_hints: self.data.inlayHints_typeHints_enable,
997 parameter_hints: self.data.inlayHints_parameterHints_enable,
998 chaining_hints: self.data.inlayHints_chainingHints_enable,
999 closure_return_type_hints: self.data.inlayHints_closureReturnTypeHints_enable,
1000 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1001 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1002 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1003 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1005 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1006 hide_closure_initialization_hints: self
1008 .inlayHints_typeHints_hideClosureInitialization,
1009 reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1010 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1011 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1012 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1014 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1015 param_names_for_lifetime_elision_hints: self
1017 .inlayHints_lifetimeElisionHints_useParameterNames,
1018 max_length: self.data.inlayHints_maxLength,
1019 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1020 Some(self.data.inlayHints_closingBraceHints_minLines)
1027 fn insert_use_config(&self) -> InsertUseConfig {
1029 granularity: match self.data.imports_granularity_group {
1030 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1031 ImportGranularityDef::Item => ImportGranularity::Item,
1032 ImportGranularityDef::Crate => ImportGranularity::Crate,
1033 ImportGranularityDef::Module => ImportGranularity::Module,
1035 enforce_granularity: self.data.imports_granularity_enforce,
1036 prefix_kind: match self.data.imports_prefix {
1037 ImportPrefixDef::Plain => PrefixKind::Plain,
1038 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1039 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1041 group: self.data.imports_group_enable,
1042 skip_glob_imports: !self.data.imports_merge_glob,
1046 pub fn completion(&self) -> CompletionConfig {
1048 enable_postfix_completions: self.data.completion_postfix_enable,
1049 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1050 && completion_item_edit_resolve(&self.caps),
1051 enable_self_on_the_fly: self.data.completion_autoself_enable,
1052 enable_private_editable: self.data.completion_privateEditable_enable,
1053 callable: match self.data.completion_callable_snippets {
1054 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1055 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1056 CallableCompletionDef::None => None,
1058 insert_use: self.insert_use_config(),
1059 snippet_cap: SnippetCap::new(try_or_def!(
1069 snippets: self.snippets.clone(),
1073 pub fn snippet_cap(&self) -> bool {
1074 self.experimental("snippetTextEdit")
1077 pub fn assist(&self) -> AssistConfig {
1079 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1081 insert_use: self.insert_use_config(),
1085 pub fn join_lines(&self) -> JoinLinesConfig {
1087 join_else_if: self.data.joinLines_joinElseIf,
1088 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1089 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1090 join_assignments: self.data.joinLines_joinAssignments,
1094 pub fn call_info(&self) -> CallInfoConfig {
1096 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1097 docs: self.data.signatureInfo_documentation_enable,
1101 pub fn lens(&self) -> LensConfig {
1103 run: self.data.lens_enable && self.data.lens_run_enable,
1104 debug: self.data.lens_enable && self.data.lens_debug_enable,
1105 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1106 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1107 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1108 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1109 enum_variant_refs: self.data.lens_enable
1110 && self.data.lens_references_enumVariant_enable,
1114 pub fn hover_actions(&self) -> HoverActionsConfig {
1115 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1116 HoverActionsConfig {
1117 implementations: enable && self.data.hover_actions_implementations_enable,
1118 references: enable && self.data.hover_actions_references_enable,
1119 run: enable && self.data.hover_actions_run_enable,
1120 debug: enable && self.data.hover_actions_debug_enable,
1121 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1125 pub fn highlighting_strings(&self) -> bool {
1126 self.data.semanticHighlighting_strings_enable
1129 pub fn hover(&self) -> HoverConfig {
1131 links_in_hover: self.data.hover_links_enable,
1132 documentation: self.data.hover_documentation_enable.then(|| {
1133 let is_markdown = try_or_def!(self
1142 .contains(&MarkupKind::Markdown);
1144 HoverDocFormat::Markdown
1146 HoverDocFormat::PlainText
1152 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1153 WorkspaceSymbolConfig {
1154 search_scope: match self.data.workspace_symbol_search_scope {
1155 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1156 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1157 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1160 search_kind: match self.data.workspace_symbol_search_kind {
1161 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1162 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1164 search_limit: self.data.workspace_symbol_search_limit,
1168 pub fn semantic_tokens_refresh(&self) -> bool {
1169 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1172 pub fn code_lens_refresh(&self) -> bool {
1173 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1176 pub fn insert_replace_support(&self) -> bool {
1185 .insert_replace_support?
1189 pub fn client_commands(&self) -> ClientCommandsConfig {
1191 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1192 let commands: Option<lsp_ext::ClientCommandOptions> =
1193 serde_json::from_value(commands.clone()).ok();
1194 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1195 let commands = commands.map(|it| it.commands).unwrap_or_default();
1197 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1199 ClientCommandsConfig {
1200 run_single: get("rust-analyzer.runSingle"),
1201 debug_single: get("rust-analyzer.debugSingle"),
1202 show_reference: get("rust-analyzer.showReferences"),
1203 goto_location: get("rust-analyzer.gotoLocation"),
1204 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1208 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1209 HighlightRelatedConfig {
1210 references: self.data.highlightRelated_references_enable,
1211 break_points: self.data.highlightRelated_breakPoints_enable,
1212 exit_points: self.data.highlightRelated_exitPoints_enable,
1213 yield_points: self.data.highlightRelated_yieldPoints_enable,
1217 pub fn prime_caches_num_threads(&self) -> u8 {
1218 match self.data.cachePriming_numThreads {
1219 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1224 // Deserialization definitions
1226 macro_rules! create_bool_or_string_de {
1227 ($ident:ident<$bool:literal, $string:literal>) => {
1228 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1230 D: serde::Deserializer<'de>,
1233 impl<'de> serde::de::Visitor<'de> for V {
1236 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1237 formatter.write_str(concat!(
1240 stringify!($string),
1245 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1247 E: serde::de::Error,
1251 _ => Err(serde::de::Error::invalid_value(
1252 serde::de::Unexpected::Bool(v),
1258 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1260 E: serde::de::Error,
1264 _ => Err(serde::de::Error::invalid_value(
1265 serde::de::Unexpected::Str(v),
1271 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1273 A: serde::de::EnumAccess<'de>,
1275 use serde::de::VariantAccess;
1276 let (variant, va) = a.variant::<&'de str>()?;
1280 _ => Err(serde::de::Error::invalid_value(
1281 serde::de::Unexpected::Str(variant),
1287 d.deserialize_any(V)
1291 create_bool_or_string_de!(true_or_always<true, "always">);
1292 create_bool_or_string_de!(false_or_never<false, "never">);
1294 macro_rules! named_unit_variant {
1295 ($variant:ident) => {
1296 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1298 D: serde::Deserializer<'de>,
1301 impl<'de> serde::de::Visitor<'de> for V {
1303 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1304 f.write_str(concat!("\"", stringify!($variant), "\""))
1306 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1307 if value == stringify!($variant) {
1310 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1314 deserializer.deserialize_str(V)
1320 named_unit_variant!(all);
1321 named_unit_variant!(skip_trivial);
1322 named_unit_variant!(mutable);
1325 #[derive(Deserialize, Debug, Clone, Copy)]
1326 #[serde(rename_all = "snake_case")]
1327 enum SnippetScopeDef {
1333 impl Default for SnippetScopeDef {
1334 fn default() -> Self {
1335 SnippetScopeDef::Expr
1339 #[derive(Deserialize, Debug, Clone, Default)]
1342 #[serde(deserialize_with = "single_or_array")]
1343 prefix: Vec<String>,
1344 #[serde(deserialize_with = "single_or_array")]
1345 postfix: Vec<String>,
1346 description: Option<String>,
1347 #[serde(deserialize_with = "single_or_array")]
1349 #[serde(deserialize_with = "single_or_array")]
1350 requires: Vec<String>,
1351 scope: SnippetScopeDef,
1354 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1356 D: serde::Deserializer<'de>,
1360 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1361 type Value = Vec<String>;
1363 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1364 formatter.write_str("string or array of strings")
1367 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1369 E: serde::de::Error,
1371 Ok(vec![value.to_owned()])
1374 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1376 A: serde::de::SeqAccess<'de>,
1378 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1382 deserializer.deserialize_any(SingleOrVec)
1385 #[derive(Deserialize, Debug, Clone)]
1387 enum ManifestOrProjectJson {
1389 ProjectJson(ProjectJsonData),
1392 #[derive(Deserialize, Debug, Clone)]
1393 #[serde(rename_all = "snake_case")]
1394 pub enum ExprFillDefaultDef {
1399 #[derive(Deserialize, Debug, Clone)]
1400 #[serde(rename_all = "snake_case")]
1401 enum ImportGranularityDef {
1408 #[derive(Deserialize, Debug, Copy, Clone)]
1409 #[serde(rename_all = "snake_case")]
1410 enum CallableCompletionDef {
1416 #[derive(Deserialize, Debug, Clone)]
1418 enum CargoFeatures {
1419 #[serde(deserialize_with = "de_unit_v::all")]
1421 Listed(Vec<String>),
1424 #[derive(Deserialize, Debug, Clone)]
1426 enum LifetimeElisionDef {
1427 #[serde(deserialize_with = "true_or_always")]
1429 #[serde(deserialize_with = "false_or_never")]
1431 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1435 #[derive(Deserialize, Debug, Clone)]
1437 enum ReborrowHintsDef {
1438 #[serde(deserialize_with = "true_or_always")]
1440 #[serde(deserialize_with = "false_or_never")]
1442 #[serde(deserialize_with = "de_unit_v::mutable")]
1446 #[derive(Deserialize, Debug, Clone)]
1447 #[serde(rename_all = "snake_case")]
1448 enum ImportPrefixDef {
1450 #[serde(alias = "self")]
1452 #[serde(alias = "crate")]
1456 #[derive(Deserialize, Debug, Clone)]
1457 #[serde(rename_all = "snake_case")]
1458 enum WorkspaceSymbolSearchScopeDef {
1460 WorkspaceAndDependencies,
1463 #[derive(Deserialize, Debug, Clone)]
1464 #[serde(rename_all = "snake_case")]
1465 enum SignatureDetail {
1470 #[derive(Deserialize, Debug, Clone)]
1471 #[serde(rename_all = "snake_case")]
1472 enum WorkspaceSymbolSearchKindDef {
1477 macro_rules! _config_data {
1478 (struct $name:ident {
1480 $(#[doc=$doc:literal])*
1481 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1484 #[allow(non_snake_case)]
1485 #[derive(Debug, Clone)]
1486 struct $name { $($field: $ty,)* }
1488 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1494 None$(.or(Some(stringify!($alias))))*,
1500 fn json_schema() -> serde_json::Value {
1503 let field = stringify!($field);
1504 let ty = stringify!($ty);
1506 (field, ty, &[$($doc),*], $default)
1512 fn manual() -> String {
1515 let field = stringify!($field);
1516 let ty = stringify!($ty);
1518 (field, ty, &[$($doc),*], $default)
1525 fn fields_are_sorted() {
1526 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1530 use _config_data as config_data;
1532 fn get_field<T: DeserializeOwned>(
1533 json: &mut serde_json::Value,
1534 error_sink: &mut Vec<(String, serde_json::Error)>,
1535 field: &'static str,
1536 alias: Option<&'static str>,
1539 let default = serde_json::from_str(default).unwrap();
1540 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1541 // defaults instead of sending an empty object.
1544 .chain(iter::once(field))
1545 .find_map(move |field| {
1546 let mut pointer = field.replace('_', "/");
1547 pointer.insert(0, '/');
1548 json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1551 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1552 error_sink.push((pointer, e));
1560 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1561 for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1562 fn key(f: &str) -> &str {
1563 f.splitn(2, '_').next().unwrap()
1565 assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1570 .map(|(field, ty, doc, default)| {
1571 let name = field.replace('_', ".");
1572 let name = format!("rust-analyzer.{}", name);
1573 let props = field_props(field, ty, doc, default);
1576 .collect::<serde_json::Map<_, _>>();
1580 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1581 let doc = doc_comment_to_string(doc);
1582 let doc = doc.trim_end_matches('\n');
1584 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1585 "bad docs for {}: {:?}",
1589 let default = default.parse::<serde_json::Value>().unwrap();
1591 let mut map = serde_json::Map::default();
1593 ($($key:literal: $value:tt),*$(,)?) => {{$(
1594 map.insert($key.into(), serde_json::json!($value));
1597 set!("markdownDescription": doc);
1598 set!("default": default);
1601 "bool" => set!("type": "boolean"),
1602 "usize" => set!("type": "integer", "minimum": 0),
1603 "String" => set!("type": "string"),
1604 "Vec<String>" => set! {
1606 "items": { "type": "string" },
1608 "Vec<PathBuf>" => set! {
1610 "items": { "type": "string" },
1612 "FxHashSet<String>" => set! {
1614 "items": { "type": "string" },
1615 "uniqueItems": true,
1617 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1620 "FxHashMap<String, SnippetDef>" => set! {
1623 "FxHashMap<String, String>" => set! {
1626 "Option<usize>" => set! {
1627 "type": ["null", "integer"],
1630 "Option<String>" => set! {
1631 "type": ["null", "string"],
1633 "Option<PathBuf>" => set! {
1634 "type": ["null", "string"],
1636 "Option<bool>" => set! {
1637 "type": ["null", "boolean"],
1639 "Option<Vec<String>>" => set! {
1640 "type": ["null", "array"],
1641 "items": { "type": "string" },
1643 "MergeBehaviorDef" => set! {
1645 "enum": ["none", "crate", "module"],
1646 "enumDescriptions": [
1647 "Do not merge imports at all.",
1648 "Merge imports from the same crate into a single `use` statement.",
1649 "Merge imports from the same module into a single `use` statement."
1652 "ExprFillDefaultDef" => set! {
1654 "enum": ["todo", "default"],
1655 "enumDescriptions": [
1656 "Fill missing expressions with the `todo` macro",
1657 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1660 "ImportGranularityDef" => set! {
1662 "enum": ["preserve", "crate", "module", "item"],
1663 "enumDescriptions": [
1664 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1665 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1666 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1667 "Flatten imports so that each has its own use statement."
1670 "ImportPrefixDef" => set! {
1677 "enumDescriptions": [
1678 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1679 "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.",
1680 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1683 "Vec<ManifestOrProjectJson>" => set! {
1685 "items": { "type": ["string", "object"] },
1687 "WorkspaceSymbolSearchScopeDef" => set! {
1689 "enum": ["workspace", "workspace_and_dependencies"],
1690 "enumDescriptions": [
1691 "Search in current workspace only.",
1692 "Search in current workspace and dependencies."
1695 "WorkspaceSymbolSearchKindDef" => set! {
1697 "enum": ["only_types", "all_symbols"],
1698 "enumDescriptions": [
1699 "Search for types only.",
1700 "Search for all symbols kinds."
1703 "ParallelCachePrimingNumThreads" => set! {
1708 "LifetimeElisionDef" => set! {
1715 "enumDescriptions": [
1716 "Always show lifetime elision hints.",
1717 "Never show lifetime elision hints.",
1718 "Only show lifetime elision hints if a return type is involved."
1721 "ReborrowHintsDef" => set! {
1728 "enumDescriptions": [
1729 "Always show reborrow hints.",
1730 "Never show reborrow hints.",
1731 "Only show mutable reborrow hints."
1734 "CargoFeatures" => set! {
1741 "enumDescriptions": [
1742 "Pass `--all-features` to cargo",
1747 "items": { "type": "string" }
1751 "Option<CargoFeatures>" => set! {
1758 "enumDescriptions": [
1759 "Pass `--all-features` to cargo",
1764 "items": { "type": "string" }
1769 "CallableCompletionDef" => set! {
1776 "enumDescriptions": [
1777 "Add call parentheses and pre-fill arguments.",
1778 "Add call parentheses.",
1779 "Do no snippet completions for callables."
1782 "SignatureDetail" => set! {
1784 "enum": ["full", "parameters"],
1785 "enumDescriptions": [
1786 "Show the entire signature.",
1787 "Show only the parameters."
1790 _ => panic!("missing entry for {}: {}", ty, default),
1797 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1800 .map(|(field, _ty, doc, default)| {
1801 let name = format!("rust-analyzer.{}", field.replace('_', "."));
1802 let doc = doc_comment_to_string(*doc);
1803 if default.contains('\n') {
1815 name, name, default, doc
1818 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1821 .collect::<String>()
1824 fn doc_comment_to_string(doc: &[&str]) -> String {
1825 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1832 use test_utils::{ensure_file_contents, project_root};
1837 fn generate_package_json_config() {
1838 let s = Config::json_schema();
1839 let schema = format!("{:#}", s);
1840 let mut schema = schema
1841 .trim_start_matches('{')
1842 .trim_end_matches('}')
1844 .replace('\n', "\n ")
1845 .trim_start_matches('\n')
1848 schema.push_str(",\n");
1850 // Transform the asciidoc form link to markdown style.
1852 // https://link[text] => [text](https://link)
1853 let url_matches = schema.match_indices("https://");
1854 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1855 url_offsets.reverse();
1856 for idx in url_offsets {
1857 let link = &schema[idx..];
1858 // matching on whitespace to ignore normal links
1859 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
1860 if link.chars().nth(link_end) == Some('[') {
1861 if let Some(link_text_end) = link.find(']') {
1862 let link_text = link[link_end..(link_text_end + 1)].to_string();
1864 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
1865 schema.insert(idx, '(');
1866 schema.insert(idx + link_end + 1, ')');
1867 schema.insert_str(idx, &link_text);
1873 let package_json_path = project_root().join("editors/code/package.json");
1874 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
1876 let start_marker = " \"$generated-start\": {},\n";
1877 let end_marker = " \"$generated-end\": {}\n";
1879 let start = package_json.find(start_marker).unwrap() + start_marker.len();
1880 let end = package_json.find(end_marker).unwrap();
1882 let p = remove_ws(&package_json[start..end]);
1883 let s = remove_ws(&schema);
1884 if !p.contains(&s) {
1885 package_json.replace_range(start..end, &schema);
1886 ensure_file_contents(&package_json_path, &package_json)
1891 fn generate_config_documentation() {
1892 let docs_path = project_root().join("docs/user/generated_config.adoc");
1893 let expected = ConfigData::manual();
1894 ensure_file_contents(&docs_path, &expected);
1897 fn remove_ws(text: &str) -> String {
1898 text.replace(char::is_whitespace, "")