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 therefore 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 checking 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 instead of `cargo check` for
116 /// diagnostics on save. The command is required to output json and
117 /// should therefor include `--message-format=json` or a similar option.
119 /// If you're changing this because you're using some tool wrapping
120 /// Cargo, you might also want to change
121 /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
123 /// An example command would be:
126 /// cargo check --workspace --message-format=json --all-targets
129 checkOnSave_overrideCommand: Option<Vec<String>> = "null",
130 /// Check for a specific target. Defaults to
131 /// `#rust-analyzer.cargo.target#`.
132 checkOnSave_target: Option<String> = "null",
134 /// Toggles the additional completions that automatically add imports when completed.
135 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
136 completion_autoimport_enable: bool = "true",
137 /// Toggles the additional completions that automatically show method calls and field accesses
138 /// with `self` prefixed to them when inside a method.
139 completion_autoself_enable: bool = "true",
140 /// Whether to add parenthesis and argument snippets when completing function.
141 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
142 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
143 completion_postfix_enable: bool = "true",
144 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
145 completion_privateEditable_enable: bool = "false",
146 /// Custom completion snippets.
147 // NOTE: Keep this list in sync with the feature docs of user snippets.
148 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
151 "body": "Arc::new(${receiver})",
152 "requires": "std::sync::Arc",
153 "description": "Put the expression into an `Arc`",
158 "body": "Rc::new(${receiver})",
159 "requires": "std::rc::Rc",
160 "description": "Put the expression into an `Rc`",
165 "body": "Box::pin(${receiver})",
166 "requires": "std::boxed::Box",
167 "description": "Put the expression into a pinned `Box`",
172 "body": "Ok(${receiver})",
173 "description": "Wrap the expression in a `Result::Ok`",
178 "body": "Err(${receiver})",
179 "description": "Wrap the expression in a `Result::Err`",
184 "body": "Some(${receiver})",
185 "description": "Wrap the expression in an `Option::Some`",
190 /// List of rust-analyzer diagnostics to disable.
191 diagnostics_disabled: FxHashSet<String> = "[]",
192 /// Whether to show native rust-analyzer diagnostics.
193 diagnostics_enable: bool = "true",
194 /// Whether to show experimental rust-analyzer diagnostics that might
195 /// have more false positives than usual.
196 diagnostics_experimental_enable: bool = "false",
197 /// Map of prefixes to be substituted when parsing diagnostic file paths.
198 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
199 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
200 /// List of warnings that should be displayed with hint severity.
202 /// The warnings will be indicated by faded text or three dots in code
203 /// and will not show up in the `Problems Panel`.
204 diagnostics_warningsAsHint: Vec<String> = "[]",
205 /// List of warnings that should be displayed with info severity.
207 /// The warnings will be indicated by a blue squiggly underline in code
208 /// and a blue icon in the `Problems Panel`.
209 diagnostics_warningsAsInfo: Vec<String> = "[]",
211 /// These directories will be ignored by rust-analyzer. They are
212 /// relative to the workspace root, and globs are not supported. You may
213 /// also need to add the folders to Code's `files.watcherExclude`.
214 files_excludeDirs: Vec<PathBuf> = "[]",
215 /// Controls file watching implementation.
216 files_watcher: FilesWatcherDef = "\"client\"",
218 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
219 highlightRelated_breakPoints_enable: bool = "true",
220 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
221 highlightRelated_exitPoints_enable: bool = "true",
222 /// Enables highlighting of related references while the cursor is on any identifier.
223 highlightRelated_references_enable: bool = "true",
224 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
225 highlightRelated_yieldPoints_enable: bool = "true",
227 /// Whether to show `Debug` action. Only applies when
228 /// `#rust-analyzer.hover.actions.enable#` is set.
229 hover_actions_debug_enable: bool = "true",
230 /// Whether to show HoverActions in Rust files.
231 hover_actions_enable: bool = "true",
232 /// Whether to show `Go to Type Definition` action. Only applies when
233 /// `#rust-analyzer.hover.actions.enable#` is set.
234 hover_actions_gotoTypeDef_enable: bool = "true",
235 /// Whether to show `Implementations` action. Only applies when
236 /// `#rust-analyzer.hover.actions.enable#` is set.
237 hover_actions_implementations_enable: bool = "true",
238 /// Whether to show `References` action. Only applies when
239 /// `#rust-analyzer.hover.actions.enable#` is set.
240 hover_actions_references_enable: bool = "false",
241 /// Whether to show `Run` action. Only applies when
242 /// `#rust-analyzer.hover.actions.enable#` is set.
243 hover_actions_run_enable: bool = "true",
245 /// Whether to show documentation on hover.
246 hover_documentation_enable: bool = "true",
247 /// Use markdown syntax for links in hover.
248 hover_links_enable: bool = "true",
250 /// 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.
251 imports_granularity_enforce: bool = "false",
252 /// How imports should be grouped into use statements.
253 imports_granularity_group: ImportGranularityDef = "\"crate\"",
254 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
255 imports_group_enable: bool = "true",
256 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
257 imports_merge_glob: bool = "true",
258 /// The path structure for newly inserted paths to use.
259 imports_prefix: ImportPrefixDef = "\"plain\"",
261 /// Whether to show inlay type hints for binding modes.
262 inlayHints_bindingModeHints_enable: bool = "false",
263 /// Whether to show inlay type hints for method chains.
264 inlayHints_chainingHints_enable: bool = "true",
265 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
266 inlayHints_closingBraceHints_enable: bool = "true",
267 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
268 /// to always show them).
269 inlayHints_closingBraceHints_minLines: usize = "25",
270 /// Whether to show inlay type hints for return types of closures.
271 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
272 /// Whether to show inlay type hints for elided lifetimes in function signatures.
273 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
274 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
275 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
276 /// Maximum length for inlay hints. Set to null to have an unlimited length.
277 inlayHints_maxLength: Option<usize> = "25",
278 /// Whether to show function parameter name inlay hints at the call
280 inlayHints_parameterHints_enable: bool = "true",
281 /// Whether to show inlay type hints for compiler inserted reborrows.
282 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
283 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
284 inlayHints_renderColons: bool = "true",
285 /// Whether to show inlay type hints for variables.
286 inlayHints_typeHints_enable: bool = "true",
287 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
288 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
289 inlayHints_typeHints_hideClosureInitialization: bool = "false",
290 /// Whether to hide inlay type hints for constructors.
291 inlayHints_typeHints_hideNamedConstructor: bool = "false",
293 /// Join lines merges consecutive declaration and initialization of an assignment.
294 joinLines_joinAssignments: bool = "true",
295 /// Join lines inserts else between consecutive ifs.
296 joinLines_joinElseIf: bool = "true",
297 /// Join lines removes trailing commas.
298 joinLines_removeTrailingComma: bool = "true",
299 /// Join lines unwraps trivial blocks.
300 joinLines_unwrapTrivialBlock: bool = "true",
302 /// Whether to show `Debug` lens. Only applies when
303 /// `#rust-analyzer.lens.enable#` is set.
304 lens_debug_enable: bool = "true",
305 /// Whether to show CodeLens in Rust files.
306 lens_enable: bool = "true",
307 /// Internal config: use custom client-side commands even when the
308 /// client doesn't set the corresponding capability.
309 lens_forceCustomCommands: bool = "true",
310 /// Whether to show `Implementations` lens. Only applies when
311 /// `#rust-analyzer.lens.enable#` is set.
312 lens_implementations_enable: bool = "true",
313 /// Whether to show `References` lens for Struct, Enum, and Union.
314 /// Only applies when `#rust-analyzer.lens.enable#` is set.
315 lens_references_adt_enable: bool = "false",
316 /// Whether to show `References` lens for Enum Variants.
317 /// Only applies when `#rust-analyzer.lens.enable#` is set.
318 lens_references_enumVariant_enable: bool = "false",
319 /// Whether to show `Method References` lens. Only applies when
320 /// `#rust-analyzer.lens.enable#` is set.
321 lens_references_method_enable: bool = "false",
322 /// Whether to show `References` lens for Trait.
323 /// Only applies when `#rust-analyzer.lens.enable#` is set.
324 lens_references_trait_enable: bool = "false",
325 /// Whether to show `Run` lens. Only applies when
326 /// `#rust-analyzer.lens.enable#` is set.
327 lens_run_enable: bool = "true",
329 /// Disable project auto-discovery in favor of explicitly specified set
332 /// Elements must be paths pointing to `Cargo.toml`,
333 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
334 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
336 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
337 lru_capacity: Option<usize> = "null",
339 /// Whether to show `can't find Cargo.toml` error message.
340 notifications_cargoTomlNotFound: bool = "true",
342 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
343 procMacro_attributes_enable: bool = "true",
344 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
345 procMacro_enable: bool = "true",
346 /// These proc-macros will be ignored when trying to expand them.
348 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
349 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
350 /// Internal config, path to proc-macro server executable (typically,
351 /// this is rust-analyzer itself, but we override this in tests).
352 procMacro_server: Option<PathBuf> = "null",
354 /// Command to be executed instead of 'cargo' for runnables.
355 runnables_command: Option<String> = "null",
356 /// Additional arguments to be passed to cargo for runnables such as
357 /// tests or binaries. For example, it may be `--release`.
358 runnables_extraArgs: Vec<String> = "[]",
360 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
361 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
364 /// Any project which uses rust-analyzer with the rustcPrivate
365 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
367 /// This option does not take effect until rust-analyzer is restarted.
368 rustc_source: Option<String> = "null",
370 /// Additional arguments to `rustfmt`.
371 rustfmt_extraArgs: Vec<String> = "[]",
372 /// Advanced option, fully override the command rust-analyzer uses for
374 rustfmt_overrideCommand: Option<Vec<String>> = "null",
375 /// Enables the use of rustfmt's unstable range formatting command for the
376 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
377 /// available on a nightly build.
378 rustfmt_rangeFormatting_enable: bool = "false",
380 /// Use semantic tokens for strings.
382 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
383 /// By disabling semantic tokens for strings, other grammars can be used to highlight
385 semanticHighlighting_strings_enable: bool = "true",
387 /// Show full signature of the callable. Only shows parameters if disabled.
388 signatureInfo_detail: SignatureDetail = "\"full\"",
389 /// Show documentation.
390 signatureInfo_documentation_enable: bool = "true",
392 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
393 typing_autoClosingAngleBrackets_enable: bool = "false",
395 /// Workspace symbol search kind.
396 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
397 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
398 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
399 /// Other clients requires all results upfront and might require a higher limit.
400 workspace_symbol_search_limit: usize = "128",
401 /// Workspace symbol search scope.
402 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
406 impl Default for ConfigData {
407 fn default() -> Self {
408 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
412 #[derive(Debug, Clone)]
414 pub discovered_projects: Option<Vec<ProjectManifest>>,
415 caps: lsp_types::ClientCapabilities,
416 root_path: AbsPathBuf,
418 detached_files: Vec<AbsPathBuf>,
419 snippets: Vec<Snippet>,
422 type ParallelCachePrimingNumThreads = u8;
424 #[derive(Debug, Clone, Eq, PartialEq)]
425 pub enum LinkedProject {
426 ProjectManifest(ProjectManifest),
427 InlineJsonProject(ProjectJson),
430 impl From<ProjectManifest> for LinkedProject {
431 fn from(v: ProjectManifest) -> Self {
432 LinkedProject::ProjectManifest(v)
436 impl From<ProjectJson> for LinkedProject {
437 fn from(v: ProjectJson) -> Self {
438 LinkedProject::InlineJsonProject(v)
442 pub struct CallInfoConfig {
443 pub params_only: bool,
447 #[derive(Clone, Debug, PartialEq, Eq)]
448 pub struct LensConfig {
454 pub implementations: bool,
457 pub method_refs: bool,
458 pub refs_adt: bool, // for Struct, Enum, Union and Trait
459 pub refs_trait: bool, // for Struct, Enum, Union and Trait
460 pub enum_variant_refs: bool,
464 pub fn any(&self) -> bool {
467 || self.implementations
471 || self.enum_variant_refs
474 pub fn none(&self) -> bool {
478 pub fn runnable(&self) -> bool {
479 self.run || self.debug
482 pub fn references(&self) -> bool {
483 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
487 #[derive(Clone, Debug, PartialEq, Eq)]
488 pub struct HoverActionsConfig {
489 pub implementations: bool,
490 pub references: bool,
493 pub goto_type_def: bool,
496 impl HoverActionsConfig {
497 pub const NO_ACTIONS: Self = Self {
498 implementations: false,
502 goto_type_def: false,
505 pub fn any(&self) -> bool {
506 self.implementations || self.references || self.runnable() || self.goto_type_def
509 pub fn none(&self) -> bool {
513 pub fn runnable(&self) -> bool {
514 self.run || self.debug
518 #[derive(Debug, Clone)]
519 pub struct FilesConfig {
520 pub watcher: FilesWatcher,
521 pub exclude: Vec<AbsPathBuf>,
524 #[derive(Debug, Clone)]
525 pub enum FilesWatcher {
530 #[derive(Debug, Clone)]
531 pub struct NotificationsConfig {
532 pub cargo_toml_not_found: bool,
535 #[derive(Debug, Clone)]
536 pub enum RustfmtConfig {
537 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
538 CustomCommand { command: String, args: Vec<String> },
541 /// Configuration for runnable items, such as `main` function or tests.
542 #[derive(Debug, Clone)]
543 pub struct RunnablesConfig {
544 /// Custom command to be executed instead of `cargo` for runnables.
545 pub override_cargo: Option<String>,
546 /// Additional arguments for the `cargo`, e.g. `--release`.
547 pub cargo_extra_args: Vec<String>,
550 /// Configuration for workspace symbol search requests.
551 #[derive(Debug, Clone)]
552 pub struct WorkspaceSymbolConfig {
553 /// In what scope should the symbol be searched in.
554 pub search_scope: WorkspaceSymbolSearchScope,
555 /// What kind of symbol is being searched for.
556 pub search_kind: WorkspaceSymbolSearchKind,
557 /// How many items are returned at most.
558 pub search_limit: usize,
561 pub struct ClientCommandsConfig {
562 pub run_single: bool,
563 pub debug_single: bool,
564 pub show_reference: bool,
565 pub goto_location: bool,
566 pub trigger_parameter_hints: bool,
570 pub struct ConfigUpdateError {
571 errors: Vec<(String, serde_json::Error)>,
574 impl fmt::Display for ConfigUpdateError {
575 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
576 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
583 "rust-analyzer found {} invalid config value{}:\n{}",
585 if self.errors.len() == 1 { "" } else { "s" },
592 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
595 data: ConfigData::default(),
596 detached_files: Vec::new(),
597 discovered_projects: None,
599 snippets: Default::default(),
603 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
604 tracing::info!("updating config from JSON: {:#}", json);
605 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
608 let mut errors = Vec::new();
609 self.detached_files =
610 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
612 .map(AbsPathBuf::assert)
614 patch_old_style::patch_json_for_outdated_configs(&mut json);
615 self.data = ConfigData::from_json(json, &mut errors);
616 tracing::debug!("deserialized config data: {:#?}", self.data);
617 self.snippets.clear();
618 for (name, def) in self.data.completion_snippets_custom.iter() {
619 if def.prefix.is_empty() && def.postfix.is_empty() {
622 let scope = match def.scope {
623 SnippetScopeDef::Expr => SnippetScope::Expr,
624 SnippetScopeDef::Type => SnippetScope::Type,
625 SnippetScopeDef::Item => SnippetScope::Item,
631 def.description.as_ref().unwrap_or(name),
635 Some(snippet) => self.snippets.push(snippet),
636 None => errors.push((
637 format!("snippet {name} is invalid"),
638 <serde_json::Error as serde::de::Error>::custom(
639 "snippet path is invalid or triggers are missing",
645 self.validate(&mut errors);
647 if errors.is_empty() {
650 Err(ConfigUpdateError { errors })
654 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
655 use serde::de::Error;
656 if self.data.checkOnSave_command.is_empty() {
658 "/checkOnSave/command".to_string(),
659 serde_json::Error::custom("expected a non-empty string"),
664 pub fn json_schema() -> serde_json::Value {
665 ConfigData::json_schema()
668 pub fn root_path(&self) -> &AbsPathBuf {
672 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
676 pub fn detached_files(&self) -> &[AbsPathBuf] {
683 || -> _ { Some($expr) }()
686 macro_rules! try_or {
687 ($expr:expr, $or:expr) => {
688 try_!($expr).unwrap_or($or)
692 macro_rules! try_or_def {
694 try_!($expr).unwrap_or_default()
699 pub fn linked_projects(&self) -> Vec<LinkedProject> {
700 match self.data.linkedProjects.as_slice() {
701 [] => match self.discovered_projects.as_ref() {
702 Some(discovered_projects) => {
703 let exclude_dirs: Vec<_> = self
707 .map(|p| self.root_path.join(p))
712 let (ProjectManifest::ProjectJson(path)
713 | ProjectManifest::CargoToml(path)) = p;
714 !exclude_dirs.iter().any(|p| path.starts_with(p))
717 .map(LinkedProject::from)
722 linked_projects => linked_projects
724 .filter_map(|linked_project| match linked_project {
725 ManifestOrProjectJson::Manifest(it) => {
726 let path = self.root_path.join(it);
727 ProjectManifest::from_manifest_file(path)
728 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
732 ManifestOrProjectJson::ProjectJson(it) => {
733 Some(ProjectJson::new(&self.root_path, it.clone()).into())
740 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
741 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
742 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
745 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
747 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
751 pub fn prefill_caches(&self) -> bool {
752 self.data.cachePriming_enable
755 pub fn location_link(&self) -> bool {
756 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
759 pub fn line_folding_only(&self) -> bool {
760 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
763 pub fn hierarchical_symbols(&self) -> bool {
770 .hierarchical_document_symbol_support?
774 pub fn code_action_literals(&self) -> bool {
781 .code_action_literal_support
786 pub fn work_done_progress(&self) -> bool {
787 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
790 pub fn will_rename(&self) -> bool {
791 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
794 pub fn change_annotation_support(&self) -> bool {
801 .change_annotation_support
806 pub fn code_action_resolve(&self) -> bool {
818 .any(|it| it == "edit")
821 pub fn signature_help_label_offsets(&self) -> bool {
828 .signature_information
830 .parameter_information
832 .label_offset_support?
836 pub fn completion_label_details_support(&self) -> bool {
845 .label_details_support
850 pub fn offset_encoding(&self) -> OffsetEncoding {
851 if supports_utf8(&self.caps) {
854 OffsetEncoding::Utf16
858 fn experimental(&self, index: &'static str) -> bool {
859 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
862 pub fn code_action_group(&self) -> bool {
863 self.experimental("codeActionGroup")
866 pub fn server_status_notification(&self) -> bool {
867 self.experimental("serverStatusNotification")
870 pub fn publish_diagnostics(&self) -> bool {
871 self.data.diagnostics_enable
874 pub fn diagnostics(&self) -> DiagnosticsConfig {
876 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
877 proc_macros_enabled: self.data.procMacro_enable,
878 disable_experimental: !self.data.diagnostics_experimental_enable,
879 disabled: self.data.diagnostics_disabled.clone(),
880 expr_fill_default: match self.data.assist_expressionFillDefault {
881 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
882 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
884 insert_use: self.insert_use_config(),
888 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
889 DiagnosticsMapConfig {
890 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
891 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
892 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
896 pub fn lru_capacity(&self) -> Option<usize> {
897 self.data.lru_capacity
900 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
901 if !self.data.procMacro_enable {
904 let path = match &self.data.procMacro_server {
905 Some(it) => self.root_path.join(it),
906 None => AbsPathBuf::assert(std::env::current_exe().ok()?),
908 Some((path, vec!["proc-macro".into()]))
911 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
912 &self.data.procMacro_ignored
915 pub fn expand_proc_attr_macros(&self) -> bool {
916 self.data.procMacro_enable && self.data.procMacro_attributes_enable
919 pub fn files(&self) -> FilesConfig {
921 watcher: match self.data.files_watcher {
922 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
925 _ => FilesWatcher::Server,
927 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
931 pub fn notifications(&self) -> NotificationsConfig {
932 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
935 pub fn cargo_autoreload(&self) -> bool {
936 self.data.cargo_autoreload
939 pub fn run_build_scripts(&self) -> bool {
940 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
943 pub fn cargo(&self) -> CargoConfig {
944 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
945 if rustc_src == "discover" {
946 RustcSource::Discover
948 RustcSource::Path(self.root_path.join(rustc_src))
953 no_default_features: self.data.cargo_noDefaultFeatures,
954 all_features: matches!(self.data.cargo_features, CargoFeatures::All),
955 features: match &self.data.cargo_features {
956 CargoFeatures::All => vec![],
957 CargoFeatures::Listed(it) => it.clone(),
959 target: self.data.cargo_target.clone(),
960 no_sysroot: self.data.cargo_noSysroot,
962 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
963 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
964 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
968 pub fn rustfmt(&self) -> RustfmtConfig {
969 match &self.data.rustfmt_overrideCommand {
970 Some(args) if !args.is_empty() => {
971 let mut args = args.clone();
972 let command = args.remove(0);
973 RustfmtConfig::CustomCommand { command, args }
975 Some(_) | None => RustfmtConfig::Rustfmt {
976 extra_args: self.data.rustfmt_extraArgs.clone(),
977 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
982 pub fn flycheck(&self) -> Option<FlycheckConfig> {
983 if !self.data.checkOnSave_enable {
986 let flycheck_config = match &self.data.checkOnSave_overrideCommand {
987 Some(args) if !args.is_empty() => {
988 let mut args = args.clone();
989 let command = args.remove(0);
990 FlycheckConfig::CustomCommand { command, args }
992 Some(_) | None => FlycheckConfig::CargoCommand {
993 command: self.data.checkOnSave_command.clone(),
998 .or_else(|| self.data.cargo_target.clone()),
999 all_targets: self.data.checkOnSave_allTargets,
1000 no_default_features: self
1002 .checkOnSave_noDefaultFeatures
1003 .unwrap_or(self.data.cargo_noDefaultFeatures),
1004 all_features: matches!(
1005 self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
1008 features: match self
1010 .checkOnSave_features
1012 .unwrap_or_else(|| self.data.cargo_features.clone())
1014 CargoFeatures::All => vec![],
1015 CargoFeatures::Listed(it) => it,
1017 extra_args: self.data.checkOnSave_extraArgs.clone(),
1020 Some(flycheck_config)
1023 pub fn runnables(&self) -> RunnablesConfig {
1025 override_cargo: self.data.runnables_command.clone(),
1026 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1030 pub fn inlay_hints(&self) -> InlayHintsConfig {
1032 render_colons: self.data.inlayHints_renderColons,
1033 type_hints: self.data.inlayHints_typeHints_enable,
1034 parameter_hints: self.data.inlayHints_parameterHints_enable,
1035 chaining_hints: self.data.inlayHints_chainingHints_enable,
1036 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1037 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1038 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1039 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1041 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1042 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1043 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1044 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1046 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1047 hide_closure_initialization_hints: self
1049 .inlayHints_typeHints_hideClosureInitialization,
1050 reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1051 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1052 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1053 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1055 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1056 param_names_for_lifetime_elision_hints: self
1058 .inlayHints_lifetimeElisionHints_useParameterNames,
1059 max_length: self.data.inlayHints_maxLength,
1060 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1061 Some(self.data.inlayHints_closingBraceHints_minLines)
1068 fn insert_use_config(&self) -> InsertUseConfig {
1070 granularity: match self.data.imports_granularity_group {
1071 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1072 ImportGranularityDef::Item => ImportGranularity::Item,
1073 ImportGranularityDef::Crate => ImportGranularity::Crate,
1074 ImportGranularityDef::Module => ImportGranularity::Module,
1076 enforce_granularity: self.data.imports_granularity_enforce,
1077 prefix_kind: match self.data.imports_prefix {
1078 ImportPrefixDef::Plain => PrefixKind::Plain,
1079 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1080 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1082 group: self.data.imports_group_enable,
1083 skip_glob_imports: !self.data.imports_merge_glob,
1087 pub fn completion(&self) -> CompletionConfig {
1089 enable_postfix_completions: self.data.completion_postfix_enable,
1090 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1091 && completion_item_edit_resolve(&self.caps),
1092 enable_self_on_the_fly: self.data.completion_autoself_enable,
1093 enable_private_editable: self.data.completion_privateEditable_enable,
1094 callable: match self.data.completion_callable_snippets {
1095 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1096 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1097 CallableCompletionDef::None => None,
1099 insert_use: self.insert_use_config(),
1100 snippet_cap: SnippetCap::new(try_or_def!(
1110 snippets: self.snippets.clone(),
1114 pub fn snippet_cap(&self) -> bool {
1115 self.experimental("snippetTextEdit")
1118 pub fn assist(&self) -> AssistConfig {
1120 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1122 insert_use: self.insert_use_config(),
1126 pub fn join_lines(&self) -> JoinLinesConfig {
1128 join_else_if: self.data.joinLines_joinElseIf,
1129 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1130 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1131 join_assignments: self.data.joinLines_joinAssignments,
1135 pub fn call_info(&self) -> CallInfoConfig {
1137 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1138 docs: self.data.signatureInfo_documentation_enable,
1142 pub fn lens(&self) -> LensConfig {
1144 run: self.data.lens_enable && self.data.lens_run_enable,
1145 debug: self.data.lens_enable && self.data.lens_debug_enable,
1146 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1147 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1148 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1149 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1150 enum_variant_refs: self.data.lens_enable
1151 && self.data.lens_references_enumVariant_enable,
1155 pub fn hover_actions(&self) -> HoverActionsConfig {
1156 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1157 HoverActionsConfig {
1158 implementations: enable && self.data.hover_actions_implementations_enable,
1159 references: enable && self.data.hover_actions_references_enable,
1160 run: enable && self.data.hover_actions_run_enable,
1161 debug: enable && self.data.hover_actions_debug_enable,
1162 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1166 pub fn highlighting_strings(&self) -> bool {
1167 self.data.semanticHighlighting_strings_enable
1170 pub fn hover(&self) -> HoverConfig {
1172 links_in_hover: self.data.hover_links_enable,
1173 documentation: self.data.hover_documentation_enable.then(|| {
1174 let is_markdown = try_or_def!(self
1183 .contains(&MarkupKind::Markdown);
1185 HoverDocFormat::Markdown
1187 HoverDocFormat::PlainText
1193 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1194 WorkspaceSymbolConfig {
1195 search_scope: match self.data.workspace_symbol_search_scope {
1196 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1197 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1198 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1201 search_kind: match self.data.workspace_symbol_search_kind {
1202 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1203 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1205 search_limit: self.data.workspace_symbol_search_limit,
1209 pub fn semantic_tokens_refresh(&self) -> bool {
1210 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1213 pub fn code_lens_refresh(&self) -> bool {
1214 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1217 pub fn insert_replace_support(&self) -> bool {
1226 .insert_replace_support?
1230 pub fn client_commands(&self) -> ClientCommandsConfig {
1232 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1233 let commands: Option<lsp_ext::ClientCommandOptions> =
1234 serde_json::from_value(commands.clone()).ok();
1235 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1236 let commands = commands.map(|it| it.commands).unwrap_or_default();
1238 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1240 ClientCommandsConfig {
1241 run_single: get("rust-analyzer.runSingle"),
1242 debug_single: get("rust-analyzer.debugSingle"),
1243 show_reference: get("rust-analyzer.showReferences"),
1244 goto_location: get("rust-analyzer.gotoLocation"),
1245 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1249 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1250 HighlightRelatedConfig {
1251 references: self.data.highlightRelated_references_enable,
1252 break_points: self.data.highlightRelated_breakPoints_enable,
1253 exit_points: self.data.highlightRelated_exitPoints_enable,
1254 yield_points: self.data.highlightRelated_yieldPoints_enable,
1258 pub fn prime_caches_num_threads(&self) -> u8 {
1259 match self.data.cachePriming_numThreads {
1260 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1265 pub fn typing_autoclose_angle(&self) -> bool {
1266 self.data.typing_autoClosingAngleBrackets_enable
1269 // Deserialization definitions
1271 macro_rules! create_bool_or_string_de {
1272 ($ident:ident<$bool:literal, $string:literal>) => {
1273 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1275 D: serde::Deserializer<'de>,
1278 impl<'de> serde::de::Visitor<'de> for V {
1281 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1282 formatter.write_str(concat!(
1285 stringify!($string),
1290 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1292 E: serde::de::Error,
1296 _ => Err(serde::de::Error::invalid_value(
1297 serde::de::Unexpected::Bool(v),
1303 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1305 E: serde::de::Error,
1309 _ => Err(serde::de::Error::invalid_value(
1310 serde::de::Unexpected::Str(v),
1316 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1318 A: serde::de::EnumAccess<'de>,
1320 use serde::de::VariantAccess;
1321 let (variant, va) = a.variant::<&'de str>()?;
1325 _ => Err(serde::de::Error::invalid_value(
1326 serde::de::Unexpected::Str(variant),
1332 d.deserialize_any(V)
1336 create_bool_or_string_de!(true_or_always<true, "always">);
1337 create_bool_or_string_de!(false_or_never<false, "never">);
1339 macro_rules! named_unit_variant {
1340 ($variant:ident) => {
1341 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1343 D: serde::Deserializer<'de>,
1346 impl<'de> serde::de::Visitor<'de> for V {
1348 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1349 f.write_str(concat!("\"", stringify!($variant), "\""))
1351 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1352 if value == stringify!($variant) {
1355 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1359 deserializer.deserialize_str(V)
1365 named_unit_variant!(all);
1366 named_unit_variant!(skip_trivial);
1367 named_unit_variant!(mutable);
1368 named_unit_variant!(with_block);
1371 #[derive(Deserialize, Debug, Clone, Copy)]
1372 #[serde(rename_all = "snake_case")]
1373 enum SnippetScopeDef {
1379 impl Default for SnippetScopeDef {
1380 fn default() -> Self {
1381 SnippetScopeDef::Expr
1385 #[derive(Deserialize, Debug, Clone, Default)]
1388 #[serde(deserialize_with = "single_or_array")]
1389 prefix: Vec<String>,
1390 #[serde(deserialize_with = "single_or_array")]
1391 postfix: Vec<String>,
1392 description: Option<String>,
1393 #[serde(deserialize_with = "single_or_array")]
1395 #[serde(deserialize_with = "single_or_array")]
1396 requires: Vec<String>,
1397 scope: SnippetScopeDef,
1400 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1402 D: serde::Deserializer<'de>,
1406 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1407 type Value = Vec<String>;
1409 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1410 formatter.write_str("string or array of strings")
1413 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1415 E: serde::de::Error,
1417 Ok(vec![value.to_owned()])
1420 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1422 A: serde::de::SeqAccess<'de>,
1424 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1428 deserializer.deserialize_any(SingleOrVec)
1431 #[derive(Deserialize, Debug, Clone)]
1433 enum ManifestOrProjectJson {
1435 ProjectJson(ProjectJsonData),
1438 #[derive(Deserialize, Debug, Clone)]
1439 #[serde(rename_all = "snake_case")]
1440 enum ExprFillDefaultDef {
1445 #[derive(Deserialize, Debug, Clone)]
1446 #[serde(rename_all = "snake_case")]
1447 enum ImportGranularityDef {
1454 #[derive(Deserialize, Debug, Copy, Clone)]
1455 #[serde(rename_all = "snake_case")]
1456 enum CallableCompletionDef {
1462 #[derive(Deserialize, Debug, Clone)]
1464 enum CargoFeatures {
1465 #[serde(deserialize_with = "de_unit_v::all")]
1467 Listed(Vec<String>),
1470 #[derive(Deserialize, Debug, Clone)]
1472 enum LifetimeElisionDef {
1473 #[serde(deserialize_with = "true_or_always")]
1475 #[serde(deserialize_with = "false_or_never")]
1477 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1481 #[derive(Deserialize, Debug, Clone)]
1483 enum ClosureReturnTypeHintsDef {
1484 #[serde(deserialize_with = "true_or_always")]
1486 #[serde(deserialize_with = "false_or_never")]
1488 #[serde(deserialize_with = "de_unit_v::with_block")]
1492 #[derive(Deserialize, Debug, Clone)]
1494 enum ReborrowHintsDef {
1495 #[serde(deserialize_with = "true_or_always")]
1497 #[serde(deserialize_with = "false_or_never")]
1499 #[serde(deserialize_with = "de_unit_v::mutable")]
1503 #[derive(Deserialize, Debug, Clone)]
1504 #[serde(rename_all = "snake_case")]
1505 enum FilesWatcherDef {
1511 #[derive(Deserialize, Debug, Clone)]
1512 #[serde(rename_all = "snake_case")]
1513 enum ImportPrefixDef {
1515 #[serde(alias = "self")]
1517 #[serde(alias = "crate")]
1521 #[derive(Deserialize, Debug, Clone)]
1522 #[serde(rename_all = "snake_case")]
1523 enum WorkspaceSymbolSearchScopeDef {
1525 WorkspaceAndDependencies,
1528 #[derive(Deserialize, Debug, Clone)]
1529 #[serde(rename_all = "snake_case")]
1530 enum SignatureDetail {
1535 #[derive(Deserialize, Debug, Clone)]
1536 #[serde(rename_all = "snake_case")]
1537 enum WorkspaceSymbolSearchKindDef {
1542 macro_rules! _config_data {
1543 (struct $name:ident {
1545 $(#[doc=$doc:literal])*
1546 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1549 #[allow(non_snake_case)]
1550 #[derive(Debug, Clone)]
1551 struct $name { $($field: $ty,)* }
1553 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1559 None$(.or(Some(stringify!($alias))))*,
1565 fn json_schema() -> serde_json::Value {
1568 let field = stringify!($field);
1569 let ty = stringify!($ty);
1571 (field, ty, &[$($doc),*], $default)
1577 fn manual() -> String {
1580 let field = stringify!($field);
1581 let ty = stringify!($ty);
1583 (field, ty, &[$($doc),*], $default)
1590 fn fields_are_sorted() {
1591 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1595 use _config_data as config_data;
1597 fn get_field<T: DeserializeOwned>(
1598 json: &mut serde_json::Value,
1599 error_sink: &mut Vec<(String, serde_json::Error)>,
1600 field: &'static str,
1601 alias: Option<&'static str>,
1604 let default = serde_json::from_str(default).unwrap();
1605 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1606 // defaults instead of sending an empty object.
1609 .chain(iter::once(field))
1610 .find_map(move |field| {
1611 let mut pointer = field.replace('_', "/");
1612 pointer.insert(0, '/');
1613 json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1616 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1617 error_sink.push((pointer, e));
1625 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1626 for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1627 fn key(f: &str) -> &str {
1628 f.splitn(2, '_').next().unwrap()
1630 assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1635 .map(|(field, ty, doc, default)| {
1636 let name = field.replace('_', ".");
1637 let name = format!("rust-analyzer.{}", name);
1638 let props = field_props(field, ty, doc, default);
1641 .collect::<serde_json::Map<_, _>>();
1645 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1646 let doc = doc_comment_to_string(doc);
1647 let doc = doc.trim_end_matches('\n');
1649 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1650 "bad docs for {}: {:?}",
1654 let default = default.parse::<serde_json::Value>().unwrap();
1656 let mut map = serde_json::Map::default();
1658 ($($key:literal: $value:tt),*$(,)?) => {{$(
1659 map.insert($key.into(), serde_json::json!($value));
1662 set!("markdownDescription": doc);
1663 set!("default": default);
1666 "bool" => set!("type": "boolean"),
1667 "usize" => set!("type": "integer", "minimum": 0),
1668 "String" => set!("type": "string"),
1669 "Vec<String>" => set! {
1671 "items": { "type": "string" },
1673 "Vec<PathBuf>" => set! {
1675 "items": { "type": "string" },
1677 "FxHashSet<String>" => set! {
1679 "items": { "type": "string" },
1680 "uniqueItems": true,
1682 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1685 "FxHashMap<String, SnippetDef>" => set! {
1688 "FxHashMap<String, String>" => set! {
1691 "Option<usize>" => set! {
1692 "type": ["null", "integer"],
1695 "Option<String>" => set! {
1696 "type": ["null", "string"],
1698 "Option<PathBuf>" => set! {
1699 "type": ["null", "string"],
1701 "Option<bool>" => set! {
1702 "type": ["null", "boolean"],
1704 "Option<Vec<String>>" => set! {
1705 "type": ["null", "array"],
1706 "items": { "type": "string" },
1708 "MergeBehaviorDef" => set! {
1710 "enum": ["none", "crate", "module"],
1711 "enumDescriptions": [
1712 "Do not merge imports at all.",
1713 "Merge imports from the same crate into a single `use` statement.",
1714 "Merge imports from the same module into a single `use` statement."
1717 "ExprFillDefaultDef" => set! {
1719 "enum": ["todo", "default"],
1720 "enumDescriptions": [
1721 "Fill missing expressions with the `todo` macro",
1722 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1725 "ImportGranularityDef" => set! {
1727 "enum": ["preserve", "crate", "module", "item"],
1728 "enumDescriptions": [
1729 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1730 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1731 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1732 "Flatten imports so that each has its own use statement."
1735 "ImportPrefixDef" => set! {
1742 "enumDescriptions": [
1743 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1744 "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.",
1745 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1748 "Vec<ManifestOrProjectJson>" => set! {
1750 "items": { "type": ["string", "object"] },
1752 "WorkspaceSymbolSearchScopeDef" => set! {
1754 "enum": ["workspace", "workspace_and_dependencies"],
1755 "enumDescriptions": [
1756 "Search in current workspace only.",
1757 "Search in current workspace and dependencies."
1760 "WorkspaceSymbolSearchKindDef" => set! {
1762 "enum": ["only_types", "all_symbols"],
1763 "enumDescriptions": [
1764 "Search for types only.",
1765 "Search for all symbols kinds."
1768 "ParallelCachePrimingNumThreads" => set! {
1773 "LifetimeElisionDef" => set! {
1780 "enumDescriptions": [
1781 "Always show lifetime elision hints.",
1782 "Never show lifetime elision hints.",
1783 "Only show lifetime elision hints if a return type is involved."
1786 "ClosureReturnTypeHintsDef" => set! {
1793 "enumDescriptions": [
1794 "Always show type hints for return types of closures.",
1795 "Never show type hints for return types of closures.",
1796 "Only show type hints for return types of closures with blocks."
1799 "ReborrowHintsDef" => set! {
1806 "enumDescriptions": [
1807 "Always show reborrow hints.",
1808 "Never show reborrow hints.",
1809 "Only show mutable reborrow hints."
1812 "CargoFeatures" => set! {
1819 "enumDescriptions": [
1820 "Pass `--all-features` to cargo",
1825 "items": { "type": "string" }
1829 "Option<CargoFeatures>" => set! {
1836 "enumDescriptions": [
1837 "Pass `--all-features` to cargo",
1842 "items": { "type": "string" }
1847 "CallableCompletionDef" => set! {
1854 "enumDescriptions": [
1855 "Add call parentheses and pre-fill arguments.",
1856 "Add call parentheses.",
1857 "Do no snippet completions for callables."
1860 "SignatureDetail" => set! {
1862 "enum": ["full", "parameters"],
1863 "enumDescriptions": [
1864 "Show the entire signature.",
1865 "Show only the parameters."
1868 "FilesWatcherDef" => set! {
1870 "enum": ["client", "server"],
1871 "enumDescriptions": [
1872 "Use the client (editor) to watch files for changes",
1873 "Use server-side file watching",
1876 _ => panic!("missing entry for {}: {}", ty, default),
1883 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1886 .map(|(field, _ty, doc, default)| {
1887 let name = format!("rust-analyzer.{}", field.replace('_', "."));
1888 let doc = doc_comment_to_string(*doc);
1889 if default.contains('\n') {
1901 name, name, default, doc
1904 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1907 .collect::<String>()
1910 fn doc_comment_to_string(doc: &[&str]) -> String {
1911 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1918 use test_utils::{ensure_file_contents, project_root};
1923 fn generate_package_json_config() {
1924 let s = Config::json_schema();
1925 let schema = format!("{:#}", s);
1926 let mut schema = schema
1927 .trim_start_matches('{')
1928 .trim_end_matches('}')
1930 .replace('\n', "\n ")
1931 .trim_start_matches('\n')
1934 schema.push_str(",\n");
1936 // Transform the asciidoc form link to markdown style.
1938 // https://link[text] => [text](https://link)
1939 let url_matches = schema.match_indices("https://");
1940 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1941 url_offsets.reverse();
1942 for idx in url_offsets {
1943 let link = &schema[idx..];
1944 // matching on whitespace to ignore normal links
1945 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
1946 if link.chars().nth(link_end) == Some('[') {
1947 if let Some(link_text_end) = link.find(']') {
1948 let link_text = link[link_end..(link_text_end + 1)].to_string();
1950 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
1951 schema.insert(idx, '(');
1952 schema.insert(idx + link_end + 1, ')');
1953 schema.insert_str(idx, &link_text);
1959 let package_json_path = project_root().join("editors/code/package.json");
1960 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
1962 let start_marker = " \"$generated-start\": {},\n";
1963 let end_marker = " \"$generated-end\": {}\n";
1965 let start = package_json.find(start_marker).unwrap() + start_marker.len();
1966 let end = package_json.find(end_marker).unwrap();
1968 let p = remove_ws(&package_json[start..end]);
1969 let s = remove_ws(&schema);
1970 if !p.contains(&s) {
1971 package_json.replace_range(start..end, &schema);
1972 ensure_file_contents(&package_json_path, &package_json)
1977 fn generate_config_documentation() {
1978 let docs_path = project_root().join("docs/user/generated_config.adoc");
1979 let expected = ConfigData::manual();
1980 ensure_file_contents(&docs_path, &expected);
1983 fn remove_ws(text: &str) -> String {
1984 text.replace(char::is_whitespace, "")