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 HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
16 JoinLinesConfig, 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. Run `cargo test` to
49 // re-generate that file.
51 // However, editor specific config, which the server doesn't know about, should
52 // be specified directly in `package.json`.
54 // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
55 // parsing the old name.
58 /// Placeholder expression to use for missing expressions in assists.
59 assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
61 /// Warm up caches on project load.
62 cachePriming_enable: bool = "true",
63 /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
64 cachePriming_numThreads: ParallelCachePrimingNumThreads = "0",
66 /// Automatically refresh project info via `cargo metadata` on
67 /// `Cargo.toml` or `.cargo/config.toml` changes.
68 cargo_autoreload: bool = "true",
69 /// Run build scripts (`build.rs`) for more precise code analysis.
70 cargo_buildScripts_enable: bool = "true",
71 /// Override the command rust-analyzer uses to run build scripts and
72 /// build procedural macros. The command is required to output json
73 /// and should therefore include `--message-format=json` or a similar
76 /// By default, a cargo invocation will be constructed for the configured
77 /// targets and features, with the following base command line:
80 /// cargo check --quiet --workspace --message-format=json --all-targets
83 cargo_buildScripts_overrideCommand: Option<Vec<String>> = "null",
84 /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
85 /// avoid checking unnecessary things.
86 cargo_buildScripts_useRustcWrapper: bool = "true",
87 /// List of features to activate.
89 /// Set this to `"all"` to pass `--all-features` to cargo.
90 cargo_features: CargoFeatures = "[]",
91 /// Whether to pass `--no-default-features` to cargo.
92 cargo_noDefaultFeatures: bool = "false",
93 /// Internal config for debugging, disables loading of sysroot crates.
94 cargo_noSysroot: bool = "false",
95 /// Compilation target override (target triple).
96 cargo_target: Option<String> = "null",
97 /// Unsets `#[cfg(test)]` for the specified crates.
98 cargo_unsetTest: Vec<String> = "[\"core\"]",
100 /// Check all targets and tests (`--all-targets`).
101 checkOnSave_allTargets: bool = "true",
102 /// Cargo command to use for `cargo check`.
103 checkOnSave_command: String = "\"check\"",
104 /// Run specified `cargo check` command for diagnostics on save.
105 checkOnSave_enable: bool = "true",
106 /// Extra arguments for `cargo check`.
107 checkOnSave_extraArgs: Vec<String> = "[]",
108 /// List of features to activate. Defaults to
109 /// `#rust-analyzer.cargo.features#`.
111 /// Set to `"all"` to pass `--all-features` to Cargo.
112 checkOnSave_features: Option<CargoFeatures> = "null",
113 /// Whether to pass `--no-default-features` to Cargo. Defaults to
114 /// `#rust-analyzer.cargo.noDefaultFeatures#`.
115 checkOnSave_noDefaultFeatures: Option<bool> = "null",
116 /// Override the command rust-analyzer uses instead of `cargo check` for
117 /// diagnostics on save. The command is required to output json and
118 /// should therefor include `--message-format=json` or a similar option.
120 /// If you're changing this because you're using some tool wrapping
121 /// Cargo, you might also want to change
122 /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
124 /// If there are multiple linked projects, this command is invoked for
125 /// each of them, with the working directory being the project root
126 /// (i.e., the folder containing the `Cargo.toml`).
128 /// An example command would be:
131 /// cargo check --workspace --message-format=json --all-targets
134 checkOnSave_overrideCommand: Option<Vec<String>> = "null",
135 /// Check for a specific target. Defaults to
136 /// `#rust-analyzer.cargo.target#`.
137 checkOnSave_target: Option<String> = "null",
139 /// Toggles the additional completions that automatically add imports when completed.
140 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
141 completion_autoimport_enable: bool = "true",
142 /// Toggles the additional completions that automatically show method calls and field accesses
143 /// with `self` prefixed to them when inside a method.
144 completion_autoself_enable: bool = "true",
145 /// Whether to add parenthesis and argument snippets when completing function.
146 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
147 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
148 completion_postfix_enable: bool = "true",
149 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
150 completion_privateEditable_enable: bool = "false",
151 /// Custom completion snippets.
152 // NOTE: Keep this list in sync with the feature docs of user snippets.
153 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
156 "body": "Arc::new(${receiver})",
157 "requires": "std::sync::Arc",
158 "description": "Put the expression into an `Arc`",
163 "body": "Rc::new(${receiver})",
164 "requires": "std::rc::Rc",
165 "description": "Put the expression into an `Rc`",
170 "body": "Box::pin(${receiver})",
171 "requires": "std::boxed::Box",
172 "description": "Put the expression into a pinned `Box`",
177 "body": "Ok(${receiver})",
178 "description": "Wrap the expression in a `Result::Ok`",
183 "body": "Err(${receiver})",
184 "description": "Wrap the expression in a `Result::Err`",
189 "body": "Some(${receiver})",
190 "description": "Wrap the expression in an `Option::Some`",
195 /// List of rust-analyzer diagnostics to disable.
196 diagnostics_disabled: FxHashSet<String> = "[]",
197 /// Whether to show native rust-analyzer diagnostics.
198 diagnostics_enable: bool = "true",
199 /// Whether to show experimental rust-analyzer diagnostics that might
200 /// have more false positives than usual.
201 diagnostics_experimental_enable: bool = "false",
202 /// Map of prefixes to be substituted when parsing diagnostic file paths.
203 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
204 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
205 /// List of warnings that should be displayed with hint severity.
207 /// The warnings will be indicated by faded text or three dots in code
208 /// and will not show up in the `Problems Panel`.
209 diagnostics_warningsAsHint: Vec<String> = "[]",
210 /// List of warnings that should be displayed with info severity.
212 /// The warnings will be indicated by a blue squiggly underline in code
213 /// and a blue icon in the `Problems Panel`.
214 diagnostics_warningsAsInfo: Vec<String> = "[]",
216 /// These directories will be ignored by rust-analyzer. They are
217 /// relative to the workspace root, and globs are not supported. You may
218 /// also need to add the folders to Code's `files.watcherExclude`.
219 files_excludeDirs: Vec<PathBuf> = "[]",
220 /// Controls file watching implementation.
221 files_watcher: FilesWatcherDef = "\"client\"",
223 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
224 highlightRelated_breakPoints_enable: bool = "true",
225 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
226 highlightRelated_exitPoints_enable: bool = "true",
227 /// Enables highlighting of related references while the cursor is on any identifier.
228 highlightRelated_references_enable: bool = "true",
229 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
230 highlightRelated_yieldPoints_enable: bool = "true",
232 /// Whether to show `Debug` action. Only applies when
233 /// `#rust-analyzer.hover.actions.enable#` is set.
234 hover_actions_debug_enable: bool = "true",
235 /// Whether to show HoverActions in Rust files.
236 hover_actions_enable: bool = "true",
237 /// Whether to show `Go to Type Definition` action. Only applies when
238 /// `#rust-analyzer.hover.actions.enable#` is set.
239 hover_actions_gotoTypeDef_enable: bool = "true",
240 /// Whether to show `Implementations` action. Only applies when
241 /// `#rust-analyzer.hover.actions.enable#` is set.
242 hover_actions_implementations_enable: bool = "true",
243 /// Whether to show `References` action. Only applies when
244 /// `#rust-analyzer.hover.actions.enable#` is set.
245 hover_actions_references_enable: bool = "false",
246 /// Whether to show `Run` action. Only applies when
247 /// `#rust-analyzer.hover.actions.enable#` is set.
248 hover_actions_run_enable: bool = "true",
250 /// Whether to show documentation on hover.
251 hover_documentation_enable: bool = "true",
252 /// Whether to show keyword hover popups. Only applies when
253 /// `#rust-analyzer.hover.documentation.enable#` is set.
254 hover_documentation_keywords_enable: bool = "true",
255 /// Use markdown syntax for links in hover.
256 hover_links_enable: bool = "true",
258 /// 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.
259 imports_granularity_enforce: bool = "false",
260 /// How imports should be grouped into use statements.
261 imports_granularity_group: ImportGranularityDef = "\"crate\"",
262 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
263 imports_group_enable: bool = "true",
264 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
265 imports_merge_glob: bool = "true",
266 /// The path structure for newly inserted paths to use.
267 imports_prefix: ImportPrefixDef = "\"plain\"",
269 /// Whether to show inlay type hints for binding modes.
270 inlayHints_bindingModeHints_enable: bool = "false",
271 /// Whether to show inlay type hints for method chains.
272 inlayHints_chainingHints_enable: bool = "true",
273 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
274 inlayHints_closingBraceHints_enable: bool = "true",
275 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
276 /// to always show them).
277 inlayHints_closingBraceHints_minLines: usize = "25",
278 /// Whether to show inlay type hints for return types of closures.
279 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
280 /// Whether to show inlay type hints for elided lifetimes in function signatures.
281 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
282 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
283 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
284 /// Maximum length for inlay hints. Set to null to have an unlimited length.
285 inlayHints_maxLength: Option<usize> = "25",
286 /// Whether to show function parameter name inlay hints at the call
288 inlayHints_parameterHints_enable: bool = "true",
289 /// Whether to show inlay type hints for compiler inserted reborrows.
290 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
291 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
292 inlayHints_renderColons: bool = "true",
293 /// Whether to show inlay type hints for variables.
294 inlayHints_typeHints_enable: bool = "true",
295 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
296 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
297 inlayHints_typeHints_hideClosureInitialization: bool = "false",
298 /// Whether to hide inlay type hints for constructors.
299 inlayHints_typeHints_hideNamedConstructor: bool = "false",
301 /// Join lines merges consecutive declaration and initialization of an assignment.
302 joinLines_joinAssignments: bool = "true",
303 /// Join lines inserts else between consecutive ifs.
304 joinLines_joinElseIf: bool = "true",
305 /// Join lines removes trailing commas.
306 joinLines_removeTrailingComma: bool = "true",
307 /// Join lines unwraps trivial blocks.
308 joinLines_unwrapTrivialBlock: bool = "true",
310 /// Whether to show `Debug` lens. Only applies when
311 /// `#rust-analyzer.lens.enable#` is set.
312 lens_debug_enable: bool = "true",
313 /// Whether to show CodeLens in Rust files.
314 lens_enable: bool = "true",
315 /// Internal config: use custom client-side commands even when the
316 /// client doesn't set the corresponding capability.
317 lens_forceCustomCommands: bool = "true",
318 /// Whether to show `Implementations` lens. Only applies when
319 /// `#rust-analyzer.lens.enable#` is set.
320 lens_implementations_enable: bool = "true",
321 /// Whether to show `References` lens for Struct, Enum, and Union.
322 /// Only applies when `#rust-analyzer.lens.enable#` is set.
323 lens_references_adt_enable: bool = "false",
324 /// Whether to show `References` lens for Enum Variants.
325 /// Only applies when `#rust-analyzer.lens.enable#` is set.
326 lens_references_enumVariant_enable: bool = "false",
327 /// Whether to show `Method References` lens. Only applies when
328 /// `#rust-analyzer.lens.enable#` is set.
329 lens_references_method_enable: bool = "false",
330 /// Whether to show `References` lens for Trait.
331 /// Only applies when `#rust-analyzer.lens.enable#` is set.
332 lens_references_trait_enable: bool = "false",
333 /// Whether to show `Run` lens. Only applies when
334 /// `#rust-analyzer.lens.enable#` is set.
335 lens_run_enable: bool = "true",
337 /// Disable project auto-discovery in favor of explicitly specified set
340 /// Elements must be paths pointing to `Cargo.toml`,
341 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
342 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
344 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
345 lru_capacity: Option<usize> = "null",
347 /// Whether to show `can't find Cargo.toml` error message.
348 notifications_cargoTomlNotFound: bool = "true",
350 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
351 procMacro_attributes_enable: bool = "true",
352 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
353 procMacro_enable: bool = "true",
354 /// These proc-macros will be ignored when trying to expand them.
356 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
357 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
358 /// Internal config, path to proc-macro server executable (typically,
359 /// this is rust-analyzer itself, but we override this in tests).
360 procMacro_server: Option<PathBuf> = "null",
362 /// Command to be executed instead of 'cargo' for runnables.
363 runnables_command: Option<String> = "null",
364 /// Additional arguments to be passed to cargo for runnables such as
365 /// tests or binaries. For example, it may be `--release`.
366 runnables_extraArgs: Vec<String> = "[]",
368 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
369 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
372 /// Any project which uses rust-analyzer with the rustcPrivate
373 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
375 /// This option does not take effect until rust-analyzer is restarted.
376 rustc_source: Option<String> = "null",
378 /// Additional arguments to `rustfmt`.
379 rustfmt_extraArgs: Vec<String> = "[]",
380 /// Advanced option, fully override the command rust-analyzer uses for
382 rustfmt_overrideCommand: Option<Vec<String>> = "null",
383 /// Enables the use of rustfmt's unstable range formatting command for the
384 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
385 /// available on a nightly build.
386 rustfmt_rangeFormatting_enable: bool = "false",
388 /// Inject additional highlighting into doc comments.
390 /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
392 semanticHighlighting_doc_comment_inject_enable: bool = "true",
393 /// Use semantic tokens for operators.
395 /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
396 /// they are tagged with modifiers.
397 semanticHighlighting_operator_enable: bool = "true",
398 /// Use specialized semantic tokens for operators.
400 /// When enabled, rust-analyzer will emit special token types for operator tokens instead
401 /// of the generic `operator` token type.
402 semanticHighlighting_operator_specialization_enable: bool = "false",
403 /// Use semantic tokens for punctuations.
405 /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
406 /// they are tagged with modifiers or have a special role.
407 semanticHighlighting_punctuation_enable: bool = "false",
408 /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
410 semanticHighlighting_punctuation_separate_macro_bang: bool = "false",
411 /// Use specialized semantic tokens for punctuations.
413 /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
414 /// of the generic `punctuation` token type.
415 semanticHighlighting_punctuation_specialization_enable: bool = "false",
416 /// Use semantic tokens for strings.
418 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
419 /// By disabling semantic tokens for strings, other grammars can be used to highlight
421 semanticHighlighting_strings_enable: bool = "true",
423 /// Show full signature of the callable. Only shows parameters if disabled.
424 signatureInfo_detail: SignatureDetail = "\"full\"",
425 /// Show documentation.
426 signatureInfo_documentation_enable: bool = "true",
428 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
429 typing_autoClosingAngleBrackets_enable: bool = "false",
431 /// Workspace symbol search kind.
432 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
433 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
434 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
435 /// Other clients requires all results upfront and might require a higher limit.
436 workspace_symbol_search_limit: usize = "128",
437 /// Workspace symbol search scope.
438 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
442 impl Default for ConfigData {
443 fn default() -> Self {
444 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
448 #[derive(Debug, Clone)]
450 pub discovered_projects: Option<Vec<ProjectManifest>>,
451 caps: lsp_types::ClientCapabilities,
452 root_path: AbsPathBuf,
454 detached_files: Vec<AbsPathBuf>,
455 snippets: Vec<Snippet>,
458 type ParallelCachePrimingNumThreads = u8;
460 #[derive(Debug, Clone, Eq, PartialEq)]
461 pub enum LinkedProject {
462 ProjectManifest(ProjectManifest),
463 InlineJsonProject(ProjectJson),
466 impl From<ProjectManifest> for LinkedProject {
467 fn from(v: ProjectManifest) -> Self {
468 LinkedProject::ProjectManifest(v)
472 impl From<ProjectJson> for LinkedProject {
473 fn from(v: ProjectJson) -> Self {
474 LinkedProject::InlineJsonProject(v)
478 pub struct CallInfoConfig {
479 pub params_only: bool,
483 #[derive(Clone, Debug, PartialEq, Eq)]
484 pub struct LensConfig {
490 pub implementations: bool,
493 pub method_refs: bool,
494 pub refs_adt: bool, // for Struct, Enum, Union and Trait
495 pub refs_trait: bool, // for Struct, Enum, Union and Trait
496 pub enum_variant_refs: bool,
500 pub fn any(&self) -> bool {
503 || self.implementations
507 || self.enum_variant_refs
510 pub fn none(&self) -> bool {
514 pub fn runnable(&self) -> bool {
515 self.run || self.debug
518 pub fn references(&self) -> bool {
519 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
523 #[derive(Clone, Debug, PartialEq, Eq)]
524 pub struct HoverActionsConfig {
525 pub implementations: bool,
526 pub references: bool,
529 pub goto_type_def: bool,
532 impl HoverActionsConfig {
533 pub const NO_ACTIONS: Self = Self {
534 implementations: false,
538 goto_type_def: false,
541 pub fn any(&self) -> bool {
542 self.implementations || self.references || self.runnable() || self.goto_type_def
545 pub fn none(&self) -> bool {
549 pub fn runnable(&self) -> bool {
550 self.run || self.debug
554 #[derive(Debug, Clone)]
555 pub struct FilesConfig {
556 pub watcher: FilesWatcher,
557 pub exclude: Vec<AbsPathBuf>,
560 #[derive(Debug, Clone)]
561 pub enum FilesWatcher {
566 #[derive(Debug, Clone)]
567 pub struct NotificationsConfig {
568 pub cargo_toml_not_found: bool,
571 #[derive(Debug, Clone)]
572 pub enum RustfmtConfig {
573 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
574 CustomCommand { command: String, args: Vec<String> },
577 /// Configuration for runnable items, such as `main` function or tests.
578 #[derive(Debug, Clone)]
579 pub struct RunnablesConfig {
580 /// Custom command to be executed instead of `cargo` for runnables.
581 pub override_cargo: Option<String>,
582 /// Additional arguments for the `cargo`, e.g. `--release`.
583 pub cargo_extra_args: Vec<String>,
586 /// Configuration for workspace symbol search requests.
587 #[derive(Debug, Clone)]
588 pub struct WorkspaceSymbolConfig {
589 /// In what scope should the symbol be searched in.
590 pub search_scope: WorkspaceSymbolSearchScope,
591 /// What kind of symbol is being searched for.
592 pub search_kind: WorkspaceSymbolSearchKind,
593 /// How many items are returned at most.
594 pub search_limit: usize,
597 pub struct ClientCommandsConfig {
598 pub run_single: bool,
599 pub debug_single: bool,
600 pub show_reference: bool,
601 pub goto_location: bool,
602 pub trigger_parameter_hints: bool,
606 pub struct ConfigUpdateError {
607 errors: Vec<(String, serde_json::Error)>,
610 impl fmt::Display for ConfigUpdateError {
611 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
619 "rust-analyzer found {} invalid config value{}:\n{}",
621 if self.errors.len() == 1 { "" } else { "s" },
628 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
631 data: ConfigData::default(),
632 detached_files: Vec::new(),
633 discovered_projects: None,
635 snippets: Default::default(),
639 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
640 tracing::info!("updating config from JSON: {:#}", json);
641 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
644 let mut errors = Vec::new();
645 self.detached_files =
646 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
648 .map(AbsPathBuf::assert)
650 patch_old_style::patch_json_for_outdated_configs(&mut json);
651 self.data = ConfigData::from_json(json, &mut errors);
652 tracing::debug!("deserialized config data: {:#?}", self.data);
653 self.snippets.clear();
654 for (name, def) in self.data.completion_snippets_custom.iter() {
655 if def.prefix.is_empty() && def.postfix.is_empty() {
658 let scope = match def.scope {
659 SnippetScopeDef::Expr => SnippetScope::Expr,
660 SnippetScopeDef::Type => SnippetScope::Type,
661 SnippetScopeDef::Item => SnippetScope::Item,
667 def.description.as_ref().unwrap_or(name),
671 Some(snippet) => self.snippets.push(snippet),
672 None => errors.push((
673 format!("snippet {name} is invalid"),
674 <serde_json::Error as serde::de::Error>::custom(
675 "snippet path is invalid or triggers are missing",
681 self.validate(&mut errors);
683 if errors.is_empty() {
686 Err(ConfigUpdateError { errors })
690 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
691 use serde::de::Error;
692 if self.data.checkOnSave_command.is_empty() {
694 "/checkOnSave/command".to_string(),
695 serde_json::Error::custom("expected a non-empty string"),
700 pub fn json_schema() -> serde_json::Value {
701 ConfigData::json_schema()
704 pub fn root_path(&self) -> &AbsPathBuf {
708 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
712 pub fn detached_files(&self) -> &[AbsPathBuf] {
719 || -> _ { Some($expr) }()
722 macro_rules! try_or {
723 ($expr:expr, $or:expr) => {
724 try_!($expr).unwrap_or($or)
728 macro_rules! try_or_def {
730 try_!($expr).unwrap_or_default()
735 pub fn linked_projects(&self) -> Vec<LinkedProject> {
736 match self.data.linkedProjects.as_slice() {
737 [] => match self.discovered_projects.as_ref() {
738 Some(discovered_projects) => {
739 let exclude_dirs: Vec<_> = self
743 .map(|p| self.root_path.join(p))
748 let (ProjectManifest::ProjectJson(path)
749 | ProjectManifest::CargoToml(path)) = p;
750 !exclude_dirs.iter().any(|p| path.starts_with(p))
753 .map(LinkedProject::from)
758 linked_projects => linked_projects
760 .filter_map(|linked_project| match linked_project {
761 ManifestOrProjectJson::Manifest(it) => {
762 let path = self.root_path.join(it);
763 ProjectManifest::from_manifest_file(path)
764 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
768 ManifestOrProjectJson::ProjectJson(it) => {
769 Some(ProjectJson::new(&self.root_path, it.clone()).into())
776 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
777 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
778 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
781 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
783 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
787 pub fn prefill_caches(&self) -> bool {
788 self.data.cachePriming_enable
791 pub fn location_link(&self) -> bool {
792 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
795 pub fn line_folding_only(&self) -> bool {
796 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
799 pub fn hierarchical_symbols(&self) -> bool {
806 .hierarchical_document_symbol_support?
810 pub fn code_action_literals(&self) -> bool {
817 .code_action_literal_support
822 pub fn work_done_progress(&self) -> bool {
823 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
826 pub fn will_rename(&self) -> bool {
827 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
830 pub fn change_annotation_support(&self) -> bool {
837 .change_annotation_support
842 pub fn code_action_resolve(&self) -> bool {
854 .any(|it| it == "edit")
857 pub fn signature_help_label_offsets(&self) -> bool {
864 .signature_information
866 .parameter_information
868 .label_offset_support?
872 pub fn completion_label_details_support(&self) -> bool {
881 .label_details_support
886 pub fn offset_encoding(&self) -> OffsetEncoding {
887 if supports_utf8(&self.caps) {
890 OffsetEncoding::Utf16
894 fn experimental(&self, index: &'static str) -> bool {
895 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
898 pub fn code_action_group(&self) -> bool {
899 self.experimental("codeActionGroup")
902 pub fn server_status_notification(&self) -> bool {
903 self.experimental("serverStatusNotification")
906 pub fn publish_diagnostics(&self) -> bool {
907 self.data.diagnostics_enable
910 pub fn diagnostics(&self) -> DiagnosticsConfig {
912 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
913 proc_macros_enabled: self.data.procMacro_enable,
914 disable_experimental: !self.data.diagnostics_experimental_enable,
915 disabled: self.data.diagnostics_disabled.clone(),
916 expr_fill_default: match self.data.assist_expressionFillDefault {
917 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
918 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
920 insert_use: self.insert_use_config(),
924 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
925 DiagnosticsMapConfig {
926 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
927 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
928 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
932 pub fn lru_capacity(&self) -> Option<usize> {
933 self.data.lru_capacity
936 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
937 if !self.data.procMacro_enable {
940 let path = match &self.data.procMacro_server {
941 Some(it) => self.root_path.join(it),
942 None => AbsPathBuf::assert(std::env::current_exe().ok()?),
944 Some((path, vec!["proc-macro".into()]))
947 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
948 &self.data.procMacro_ignored
951 pub fn expand_proc_attr_macros(&self) -> bool {
952 self.data.procMacro_enable && self.data.procMacro_attributes_enable
955 pub fn files(&self) -> FilesConfig {
957 watcher: match self.data.files_watcher {
958 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
961 _ => FilesWatcher::Server,
963 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
967 pub fn notifications(&self) -> NotificationsConfig {
968 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
971 pub fn cargo_autoreload(&self) -> bool {
972 self.data.cargo_autoreload
975 pub fn run_build_scripts(&self) -> bool {
976 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
979 pub fn cargo(&self) -> CargoConfig {
980 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
981 if rustc_src == "discover" {
982 RustcSource::Discover
984 RustcSource::Path(self.root_path.join(rustc_src))
989 no_default_features: self.data.cargo_noDefaultFeatures,
990 all_features: matches!(self.data.cargo_features, CargoFeatures::All),
991 features: match &self.data.cargo_features {
992 CargoFeatures::All => vec![],
993 CargoFeatures::Listed(it) => it.clone(),
995 target: self.data.cargo_target.clone(),
996 no_sysroot: self.data.cargo_noSysroot,
998 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
999 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1000 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
1004 pub fn rustfmt(&self) -> RustfmtConfig {
1005 match &self.data.rustfmt_overrideCommand {
1006 Some(args) if !args.is_empty() => {
1007 let mut args = args.clone();
1008 let command = args.remove(0);
1009 RustfmtConfig::CustomCommand { command, args }
1011 Some(_) | None => RustfmtConfig::Rustfmt {
1012 extra_args: self.data.rustfmt_extraArgs.clone(),
1013 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
1018 pub fn flycheck(&self) -> Option<FlycheckConfig> {
1019 if !self.data.checkOnSave_enable {
1022 let flycheck_config = match &self.data.checkOnSave_overrideCommand {
1023 Some(args) if !args.is_empty() => {
1024 let mut args = args.clone();
1025 let command = args.remove(0);
1026 FlycheckConfig::CustomCommand { command, args }
1028 Some(_) | None => FlycheckConfig::CargoCommand {
1029 command: self.data.checkOnSave_command.clone(),
1034 .or_else(|| self.data.cargo_target.clone()),
1035 all_targets: self.data.checkOnSave_allTargets,
1036 no_default_features: self
1038 .checkOnSave_noDefaultFeatures
1039 .unwrap_or(self.data.cargo_noDefaultFeatures),
1040 all_features: matches!(
1041 self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
1044 features: match self
1046 .checkOnSave_features
1048 .unwrap_or_else(|| self.data.cargo_features.clone())
1050 CargoFeatures::All => vec![],
1051 CargoFeatures::Listed(it) => it,
1053 extra_args: self.data.checkOnSave_extraArgs.clone(),
1056 Some(flycheck_config)
1059 pub fn runnables(&self) -> RunnablesConfig {
1061 override_cargo: self.data.runnables_command.clone(),
1062 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1066 pub fn inlay_hints(&self) -> InlayHintsConfig {
1068 render_colons: self.data.inlayHints_renderColons,
1069 type_hints: self.data.inlayHints_typeHints_enable,
1070 parameter_hints: self.data.inlayHints_parameterHints_enable,
1071 chaining_hints: self.data.inlayHints_chainingHints_enable,
1072 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1073 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1074 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1075 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1077 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1078 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1079 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1080 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1082 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1083 hide_closure_initialization_hints: self
1085 .inlayHints_typeHints_hideClosureInitialization,
1086 reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1087 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1088 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1089 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1091 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1092 param_names_for_lifetime_elision_hints: self
1094 .inlayHints_lifetimeElisionHints_useParameterNames,
1095 max_length: self.data.inlayHints_maxLength,
1096 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1097 Some(self.data.inlayHints_closingBraceHints_minLines)
1104 fn insert_use_config(&self) -> InsertUseConfig {
1106 granularity: match self.data.imports_granularity_group {
1107 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1108 ImportGranularityDef::Item => ImportGranularity::Item,
1109 ImportGranularityDef::Crate => ImportGranularity::Crate,
1110 ImportGranularityDef::Module => ImportGranularity::Module,
1112 enforce_granularity: self.data.imports_granularity_enforce,
1113 prefix_kind: match self.data.imports_prefix {
1114 ImportPrefixDef::Plain => PrefixKind::Plain,
1115 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1116 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1118 group: self.data.imports_group_enable,
1119 skip_glob_imports: !self.data.imports_merge_glob,
1123 pub fn completion(&self) -> CompletionConfig {
1125 enable_postfix_completions: self.data.completion_postfix_enable,
1126 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1127 && completion_item_edit_resolve(&self.caps),
1128 enable_self_on_the_fly: self.data.completion_autoself_enable,
1129 enable_private_editable: self.data.completion_privateEditable_enable,
1130 callable: match self.data.completion_callable_snippets {
1131 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1132 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1133 CallableCompletionDef::None => None,
1135 insert_use: self.insert_use_config(),
1136 snippet_cap: SnippetCap::new(try_or_def!(
1146 snippets: self.snippets.clone(),
1150 pub fn snippet_cap(&self) -> bool {
1151 self.experimental("snippetTextEdit")
1154 pub fn assist(&self) -> AssistConfig {
1156 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1158 insert_use: self.insert_use_config(),
1162 pub fn join_lines(&self) -> JoinLinesConfig {
1164 join_else_if: self.data.joinLines_joinElseIf,
1165 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1166 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1167 join_assignments: self.data.joinLines_joinAssignments,
1171 pub fn call_info(&self) -> CallInfoConfig {
1173 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1174 docs: self.data.signatureInfo_documentation_enable,
1178 pub fn lens(&self) -> LensConfig {
1180 run: self.data.lens_enable && self.data.lens_run_enable,
1181 debug: self.data.lens_enable && self.data.lens_debug_enable,
1182 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1183 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1184 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1185 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1186 enum_variant_refs: self.data.lens_enable
1187 && self.data.lens_references_enumVariant_enable,
1191 pub fn hover_actions(&self) -> HoverActionsConfig {
1192 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1193 HoverActionsConfig {
1194 implementations: enable && self.data.hover_actions_implementations_enable,
1195 references: enable && self.data.hover_actions_references_enable,
1196 run: enable && self.data.hover_actions_run_enable,
1197 debug: enable && self.data.hover_actions_debug_enable,
1198 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1202 pub fn highlighting_config(&self) -> HighlightConfig {
1204 strings: self.data.semanticHighlighting_strings_enable,
1205 punctuation: self.data.semanticHighlighting_punctuation_enable,
1206 specialize_punctuation: self
1208 .semanticHighlighting_punctuation_specialization_enable,
1209 macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang,
1210 operator: self.data.semanticHighlighting_operator_enable,
1211 specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
1212 inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable,
1213 syntactic_name_ref_highlighting: false,
1217 pub fn hover(&self) -> HoverConfig {
1219 links_in_hover: self.data.hover_links_enable,
1220 documentation: self.data.hover_documentation_enable.then(|| {
1221 let is_markdown = try_or_def!(self
1230 .contains(&MarkupKind::Markdown);
1232 HoverDocFormat::Markdown
1234 HoverDocFormat::PlainText
1237 keywords: self.data.hover_documentation_keywords_enable,
1241 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1242 WorkspaceSymbolConfig {
1243 search_scope: match self.data.workspace_symbol_search_scope {
1244 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1245 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1246 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1249 search_kind: match self.data.workspace_symbol_search_kind {
1250 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1251 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1253 search_limit: self.data.workspace_symbol_search_limit,
1257 pub fn semantic_tokens_refresh(&self) -> bool {
1258 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1261 pub fn code_lens_refresh(&self) -> bool {
1262 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1265 pub fn insert_replace_support(&self) -> bool {
1274 .insert_replace_support?
1278 pub fn client_commands(&self) -> ClientCommandsConfig {
1280 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1281 let commands: Option<lsp_ext::ClientCommandOptions> =
1282 serde_json::from_value(commands.clone()).ok();
1283 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1284 let commands = commands.map(|it| it.commands).unwrap_or_default();
1286 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1288 ClientCommandsConfig {
1289 run_single: get("rust-analyzer.runSingle"),
1290 debug_single: get("rust-analyzer.debugSingle"),
1291 show_reference: get("rust-analyzer.showReferences"),
1292 goto_location: get("rust-analyzer.gotoLocation"),
1293 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1297 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1298 HighlightRelatedConfig {
1299 references: self.data.highlightRelated_references_enable,
1300 break_points: self.data.highlightRelated_breakPoints_enable,
1301 exit_points: self.data.highlightRelated_exitPoints_enable,
1302 yield_points: self.data.highlightRelated_yieldPoints_enable,
1306 pub fn prime_caches_num_threads(&self) -> u8 {
1307 match self.data.cachePriming_numThreads {
1308 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1313 pub fn typing_autoclose_angle(&self) -> bool {
1314 self.data.typing_autoClosingAngleBrackets_enable
1317 // Deserialization definitions
1319 macro_rules! create_bool_or_string_de {
1320 ($ident:ident<$bool:literal, $string:literal>) => {
1321 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1323 D: serde::Deserializer<'de>,
1326 impl<'de> serde::de::Visitor<'de> for V {
1329 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1330 formatter.write_str(concat!(
1333 stringify!($string),
1338 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1340 E: serde::de::Error,
1344 _ => Err(serde::de::Error::invalid_value(
1345 serde::de::Unexpected::Bool(v),
1351 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1353 E: serde::de::Error,
1357 _ => Err(serde::de::Error::invalid_value(
1358 serde::de::Unexpected::Str(v),
1364 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1366 A: serde::de::EnumAccess<'de>,
1368 use serde::de::VariantAccess;
1369 let (variant, va) = a.variant::<&'de str>()?;
1373 _ => Err(serde::de::Error::invalid_value(
1374 serde::de::Unexpected::Str(variant),
1380 d.deserialize_any(V)
1384 create_bool_or_string_de!(true_or_always<true, "always">);
1385 create_bool_or_string_de!(false_or_never<false, "never">);
1387 macro_rules! named_unit_variant {
1388 ($variant:ident) => {
1389 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1391 D: serde::Deserializer<'de>,
1394 impl<'de> serde::de::Visitor<'de> for V {
1396 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1397 f.write_str(concat!("\"", stringify!($variant), "\""))
1399 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1400 if value == stringify!($variant) {
1403 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1407 deserializer.deserialize_str(V)
1413 named_unit_variant!(all);
1414 named_unit_variant!(skip_trivial);
1415 named_unit_variant!(mutable);
1416 named_unit_variant!(with_block);
1419 #[derive(Deserialize, Debug, Clone, Copy)]
1420 #[serde(rename_all = "snake_case")]
1421 enum SnippetScopeDef {
1427 impl Default for SnippetScopeDef {
1428 fn default() -> Self {
1429 SnippetScopeDef::Expr
1433 #[derive(Deserialize, Debug, Clone, Default)]
1436 #[serde(deserialize_with = "single_or_array")]
1437 prefix: Vec<String>,
1438 #[serde(deserialize_with = "single_or_array")]
1439 postfix: Vec<String>,
1440 description: Option<String>,
1441 #[serde(deserialize_with = "single_or_array")]
1443 #[serde(deserialize_with = "single_or_array")]
1444 requires: Vec<String>,
1445 scope: SnippetScopeDef,
1448 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1450 D: serde::Deserializer<'de>,
1454 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1455 type Value = Vec<String>;
1457 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1458 formatter.write_str("string or array of strings")
1461 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1463 E: serde::de::Error,
1465 Ok(vec![value.to_owned()])
1468 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1470 A: serde::de::SeqAccess<'de>,
1472 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1476 deserializer.deserialize_any(SingleOrVec)
1479 #[derive(Deserialize, Debug, Clone)]
1481 enum ManifestOrProjectJson {
1483 ProjectJson(ProjectJsonData),
1486 #[derive(Deserialize, Debug, Clone)]
1487 #[serde(rename_all = "snake_case")]
1488 enum ExprFillDefaultDef {
1493 #[derive(Deserialize, Debug, Clone)]
1494 #[serde(rename_all = "snake_case")]
1495 enum ImportGranularityDef {
1502 #[derive(Deserialize, Debug, Copy, Clone)]
1503 #[serde(rename_all = "snake_case")]
1504 enum CallableCompletionDef {
1510 #[derive(Deserialize, Debug, Clone)]
1512 enum CargoFeatures {
1513 #[serde(deserialize_with = "de_unit_v::all")]
1515 Listed(Vec<String>),
1518 #[derive(Deserialize, Debug, Clone)]
1520 enum LifetimeElisionDef {
1521 #[serde(deserialize_with = "true_or_always")]
1523 #[serde(deserialize_with = "false_or_never")]
1525 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1529 #[derive(Deserialize, Debug, Clone)]
1531 enum ClosureReturnTypeHintsDef {
1532 #[serde(deserialize_with = "true_or_always")]
1534 #[serde(deserialize_with = "false_or_never")]
1536 #[serde(deserialize_with = "de_unit_v::with_block")]
1540 #[derive(Deserialize, Debug, Clone)]
1542 enum ReborrowHintsDef {
1543 #[serde(deserialize_with = "true_or_always")]
1545 #[serde(deserialize_with = "false_or_never")]
1547 #[serde(deserialize_with = "de_unit_v::mutable")]
1551 #[derive(Deserialize, Debug, Clone)]
1552 #[serde(rename_all = "snake_case")]
1553 enum FilesWatcherDef {
1559 #[derive(Deserialize, Debug, Clone)]
1560 #[serde(rename_all = "snake_case")]
1561 enum ImportPrefixDef {
1563 #[serde(alias = "self")]
1565 #[serde(alias = "crate")]
1569 #[derive(Deserialize, Debug, Clone)]
1570 #[serde(rename_all = "snake_case")]
1571 enum WorkspaceSymbolSearchScopeDef {
1573 WorkspaceAndDependencies,
1576 #[derive(Deserialize, Debug, Clone)]
1577 #[serde(rename_all = "snake_case")]
1578 enum SignatureDetail {
1583 #[derive(Deserialize, Debug, Clone)]
1584 #[serde(rename_all = "snake_case")]
1585 enum WorkspaceSymbolSearchKindDef {
1590 macro_rules! _config_data {
1591 (struct $name:ident {
1593 $(#[doc=$doc:literal])*
1594 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1597 #[allow(non_snake_case)]
1598 #[derive(Debug, Clone)]
1599 struct $name { $($field: $ty,)* }
1601 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1607 None$(.or(Some(stringify!($alias))))*,
1613 fn json_schema() -> serde_json::Value {
1616 let field = stringify!($field);
1617 let ty = stringify!($ty);
1619 (field, ty, &[$($doc),*], $default)
1625 fn manual() -> String {
1628 let field = stringify!($field);
1629 let ty = stringify!($ty);
1631 (field, ty, &[$($doc),*], $default)
1638 fn fields_are_sorted() {
1639 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1643 use _config_data as config_data;
1645 fn get_field<T: DeserializeOwned>(
1646 json: &mut serde_json::Value,
1647 error_sink: &mut Vec<(String, serde_json::Error)>,
1648 field: &'static str,
1649 alias: Option<&'static str>,
1652 let default = serde_json::from_str(default).unwrap();
1653 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1654 // defaults instead of sending an empty object.
1657 .chain(iter::once(field))
1658 .find_map(move |field| {
1659 let mut pointer = field.replace('_', "/");
1660 pointer.insert(0, '/');
1661 json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1664 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1665 error_sink.push((pointer, e));
1673 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1674 for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1675 fn key(f: &str) -> &str {
1676 f.splitn(2, '_').next().unwrap()
1678 assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1683 .map(|(field, ty, doc, default)| {
1684 let name = field.replace('_', ".");
1685 let name = format!("rust-analyzer.{}", name);
1686 let props = field_props(field, ty, doc, default);
1689 .collect::<serde_json::Map<_, _>>();
1693 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1694 let doc = doc_comment_to_string(doc);
1695 let doc = doc.trim_end_matches('\n');
1697 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1698 "bad docs for {}: {:?}",
1702 let default = default.parse::<serde_json::Value>().unwrap();
1704 let mut map = serde_json::Map::default();
1706 ($($key:literal: $value:tt),*$(,)?) => {{$(
1707 map.insert($key.into(), serde_json::json!($value));
1710 set!("markdownDescription": doc);
1711 set!("default": default);
1714 "bool" => set!("type": "boolean"),
1715 "usize" => set!("type": "integer", "minimum": 0),
1716 "String" => set!("type": "string"),
1717 "Vec<String>" => set! {
1719 "items": { "type": "string" },
1721 "Vec<PathBuf>" => set! {
1723 "items": { "type": "string" },
1725 "FxHashSet<String>" => set! {
1727 "items": { "type": "string" },
1728 "uniqueItems": true,
1730 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1733 "FxHashMap<String, SnippetDef>" => set! {
1736 "FxHashMap<String, String>" => set! {
1739 "Option<usize>" => set! {
1740 "type": ["null", "integer"],
1743 "Option<String>" => set! {
1744 "type": ["null", "string"],
1746 "Option<PathBuf>" => set! {
1747 "type": ["null", "string"],
1749 "Option<bool>" => set! {
1750 "type": ["null", "boolean"],
1752 "Option<Vec<String>>" => set! {
1753 "type": ["null", "array"],
1754 "items": { "type": "string" },
1756 "MergeBehaviorDef" => set! {
1758 "enum": ["none", "crate", "module"],
1759 "enumDescriptions": [
1760 "Do not merge imports at all.",
1761 "Merge imports from the same crate into a single `use` statement.",
1762 "Merge imports from the same module into a single `use` statement."
1765 "ExprFillDefaultDef" => set! {
1767 "enum": ["todo", "default"],
1768 "enumDescriptions": [
1769 "Fill missing expressions with the `todo` macro",
1770 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1773 "ImportGranularityDef" => set! {
1775 "enum": ["preserve", "crate", "module", "item"],
1776 "enumDescriptions": [
1777 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1778 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1779 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1780 "Flatten imports so that each has its own use statement."
1783 "ImportPrefixDef" => set! {
1790 "enumDescriptions": [
1791 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1792 "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.",
1793 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1796 "Vec<ManifestOrProjectJson>" => set! {
1798 "items": { "type": ["string", "object"] },
1800 "WorkspaceSymbolSearchScopeDef" => set! {
1802 "enum": ["workspace", "workspace_and_dependencies"],
1803 "enumDescriptions": [
1804 "Search in current workspace only.",
1805 "Search in current workspace and dependencies."
1808 "WorkspaceSymbolSearchKindDef" => set! {
1810 "enum": ["only_types", "all_symbols"],
1811 "enumDescriptions": [
1812 "Search for types only.",
1813 "Search for all symbols kinds."
1816 "ParallelCachePrimingNumThreads" => set! {
1821 "LifetimeElisionDef" => set! {
1828 "enumDescriptions": [
1829 "Always show lifetime elision hints.",
1830 "Never show lifetime elision hints.",
1831 "Only show lifetime elision hints if a return type is involved."
1834 "ClosureReturnTypeHintsDef" => set! {
1841 "enumDescriptions": [
1842 "Always show type hints for return types of closures.",
1843 "Never show type hints for return types of closures.",
1844 "Only show type hints for return types of closures with blocks."
1847 "ReborrowHintsDef" => set! {
1854 "enumDescriptions": [
1855 "Always show reborrow hints.",
1856 "Never show reborrow hints.",
1857 "Only show mutable reborrow hints."
1860 "CargoFeatures" => set! {
1867 "enumDescriptions": [
1868 "Pass `--all-features` to cargo",
1873 "items": { "type": "string" }
1877 "Option<CargoFeatures>" => set! {
1884 "enumDescriptions": [
1885 "Pass `--all-features` to cargo",
1890 "items": { "type": "string" }
1895 "CallableCompletionDef" => set! {
1902 "enumDescriptions": [
1903 "Add call parentheses and pre-fill arguments.",
1904 "Add call parentheses.",
1905 "Do no snippet completions for callables."
1908 "SignatureDetail" => set! {
1910 "enum": ["full", "parameters"],
1911 "enumDescriptions": [
1912 "Show the entire signature.",
1913 "Show only the parameters."
1916 "FilesWatcherDef" => set! {
1918 "enum": ["client", "server"],
1919 "enumDescriptions": [
1920 "Use the client (editor) to watch files for changes",
1921 "Use server-side file watching",
1924 _ => panic!("missing entry for {}: {}", ty, default),
1931 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1934 .map(|(field, _ty, doc, default)| {
1935 let name = format!("rust-analyzer.{}", field.replace('_', "."));
1936 let doc = doc_comment_to_string(*doc);
1937 if default.contains('\n') {
1949 name, name, default, doc
1952 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1955 .collect::<String>()
1958 fn doc_comment_to_string(doc: &[&str]) -> String {
1959 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1966 use test_utils::{ensure_file_contents, project_root};
1971 fn generate_package_json_config() {
1972 let s = Config::json_schema();
1973 let schema = format!("{:#}", s);
1974 let mut schema = schema
1975 .trim_start_matches('{')
1976 .trim_end_matches('}')
1978 .replace('\n', "\n ")
1979 .trim_start_matches('\n')
1982 schema.push_str(",\n");
1984 // Transform the asciidoc form link to markdown style.
1986 // https://link[text] => [text](https://link)
1987 let url_matches = schema.match_indices("https://");
1988 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1989 url_offsets.reverse();
1990 for idx in url_offsets {
1991 let link = &schema[idx..];
1992 // matching on whitespace to ignore normal links
1993 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
1994 if link.chars().nth(link_end) == Some('[') {
1995 if let Some(link_text_end) = link.find(']') {
1996 let link_text = link[link_end..(link_text_end + 1)].to_string();
1998 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
1999 schema.insert(idx, '(');
2000 schema.insert(idx + link_end + 1, ')');
2001 schema.insert_str(idx, &link_text);
2007 let package_json_path = project_root().join("editors/code/package.json");
2008 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
2010 let start_marker = " \"$generated-start\": {},\n";
2011 let end_marker = " \"$generated-end\": {}\n";
2013 let start = package_json.find(start_marker).unwrap() + start_marker.len();
2014 let end = package_json.find(end_marker).unwrap();
2016 let p = remove_ws(&package_json[start..end]);
2017 let s = remove_ws(&schema);
2018 if !p.contains(&s) {
2019 package_json.replace_range(start..end, &schema);
2020 ensure_file_contents(&package_json_path, &package_json)
2025 fn generate_config_documentation() {
2026 let docs_path = project_root().join("docs/user/generated_config.adoc");
2027 let expected = ConfigData::manual();
2028 ensure_file_contents(&docs_path, &expected);
2031 fn remove_ws(text: &str) -> String {
2032 text.replace(char::is_whitespace, "")