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 /// Use semantic tokens for strings.
390 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
391 /// By disabling semantic tokens for strings, other grammars can be used to highlight
393 semanticHighlighting_strings_enable: bool = "true",
394 /// Use semantic tokens for punctuations.
396 /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
397 /// they are tagged with modifiers.
398 semanticHighlighting_punctuation_enable: bool = "false",
399 /// Use specialized semantic tokens for punctuations.
401 /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
402 /// of the generic `punctuation` token type.
403 semanticHighlighting_punctuation_specialization_enable: bool = "false",
404 /// Use semantic tokens for operators.
406 /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
407 /// they are tagged with modifiers.
408 semanticHighlighting_operator_enable: bool = "true",
409 /// Use specialized semantic tokens for operators.
411 /// When enabled, rust-analyzer will emit special token types for operator tokens instead
412 /// of the generic `operator` token type.
413 semanticHighlighting_operator_specialization_enable: bool = "false",
415 /// Show full signature of the callable. Only shows parameters if disabled.
416 signatureInfo_detail: SignatureDetail = "\"full\"",
417 /// Show documentation.
418 signatureInfo_documentation_enable: bool = "true",
420 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
421 typing_autoClosingAngleBrackets_enable: bool = "false",
423 /// Workspace symbol search kind.
424 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
425 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
426 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
427 /// Other clients requires all results upfront and might require a higher limit.
428 workspace_symbol_search_limit: usize = "128",
429 /// Workspace symbol search scope.
430 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
434 impl Default for ConfigData {
435 fn default() -> Self {
436 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
440 #[derive(Debug, Clone)]
442 pub discovered_projects: Option<Vec<ProjectManifest>>,
443 caps: lsp_types::ClientCapabilities,
444 root_path: AbsPathBuf,
446 detached_files: Vec<AbsPathBuf>,
447 snippets: Vec<Snippet>,
450 type ParallelCachePrimingNumThreads = u8;
452 #[derive(Debug, Clone, Eq, PartialEq)]
453 pub enum LinkedProject {
454 ProjectManifest(ProjectManifest),
455 InlineJsonProject(ProjectJson),
458 impl From<ProjectManifest> for LinkedProject {
459 fn from(v: ProjectManifest) -> Self {
460 LinkedProject::ProjectManifest(v)
464 impl From<ProjectJson> for LinkedProject {
465 fn from(v: ProjectJson) -> Self {
466 LinkedProject::InlineJsonProject(v)
470 pub struct CallInfoConfig {
471 pub params_only: bool,
475 #[derive(Clone, Debug, PartialEq, Eq)]
476 pub struct LensConfig {
482 pub implementations: bool,
485 pub method_refs: bool,
486 pub refs_adt: bool, // for Struct, Enum, Union and Trait
487 pub refs_trait: bool, // for Struct, Enum, Union and Trait
488 pub enum_variant_refs: bool,
492 pub fn any(&self) -> bool {
495 || self.implementations
499 || self.enum_variant_refs
502 pub fn none(&self) -> bool {
506 pub fn runnable(&self) -> bool {
507 self.run || self.debug
510 pub fn references(&self) -> bool {
511 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
515 #[derive(Clone, Debug, PartialEq, Eq)]
516 pub struct HoverActionsConfig {
517 pub implementations: bool,
518 pub references: bool,
521 pub goto_type_def: bool,
524 impl HoverActionsConfig {
525 pub const NO_ACTIONS: Self = Self {
526 implementations: false,
530 goto_type_def: false,
533 pub fn any(&self) -> bool {
534 self.implementations || self.references || self.runnable() || self.goto_type_def
537 pub fn none(&self) -> bool {
541 pub fn runnable(&self) -> bool {
542 self.run || self.debug
546 #[derive(Debug, Clone)]
547 pub struct FilesConfig {
548 pub watcher: FilesWatcher,
549 pub exclude: Vec<AbsPathBuf>,
552 #[derive(Debug, Clone)]
553 pub enum FilesWatcher {
558 #[derive(Debug, Clone)]
559 pub struct NotificationsConfig {
560 pub cargo_toml_not_found: bool,
563 #[derive(Debug, Clone)]
564 pub enum RustfmtConfig {
565 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
566 CustomCommand { command: String, args: Vec<String> },
569 /// Configuration for runnable items, such as `main` function or tests.
570 #[derive(Debug, Clone)]
571 pub struct RunnablesConfig {
572 /// Custom command to be executed instead of `cargo` for runnables.
573 pub override_cargo: Option<String>,
574 /// Additional arguments for the `cargo`, e.g. `--release`.
575 pub cargo_extra_args: Vec<String>,
578 /// Configuration for workspace symbol search requests.
579 #[derive(Debug, Clone)]
580 pub struct WorkspaceSymbolConfig {
581 /// In what scope should the symbol be searched in.
582 pub search_scope: WorkspaceSymbolSearchScope,
583 /// What kind of symbol is being searched for.
584 pub search_kind: WorkspaceSymbolSearchKind,
585 /// How many items are returned at most.
586 pub search_limit: usize,
589 pub struct ClientCommandsConfig {
590 pub run_single: bool,
591 pub debug_single: bool,
592 pub show_reference: bool,
593 pub goto_location: bool,
594 pub trigger_parameter_hints: bool,
598 pub struct ConfigUpdateError {
599 errors: Vec<(String, serde_json::Error)>,
602 impl fmt::Display for ConfigUpdateError {
603 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
611 "rust-analyzer found {} invalid config value{}:\n{}",
613 if self.errors.len() == 1 { "" } else { "s" },
620 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
623 data: ConfigData::default(),
624 detached_files: Vec::new(),
625 discovered_projects: None,
627 snippets: Default::default(),
631 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
632 tracing::info!("updating config from JSON: {:#}", json);
633 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
636 let mut errors = Vec::new();
637 self.detached_files =
638 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
640 .map(AbsPathBuf::assert)
642 patch_old_style::patch_json_for_outdated_configs(&mut json);
643 self.data = ConfigData::from_json(json, &mut errors);
644 tracing::debug!("deserialized config data: {:#?}", self.data);
645 self.snippets.clear();
646 for (name, def) in self.data.completion_snippets_custom.iter() {
647 if def.prefix.is_empty() && def.postfix.is_empty() {
650 let scope = match def.scope {
651 SnippetScopeDef::Expr => SnippetScope::Expr,
652 SnippetScopeDef::Type => SnippetScope::Type,
653 SnippetScopeDef::Item => SnippetScope::Item,
659 def.description.as_ref().unwrap_or(name),
663 Some(snippet) => self.snippets.push(snippet),
664 None => errors.push((
665 format!("snippet {name} is invalid"),
666 <serde_json::Error as serde::de::Error>::custom(
667 "snippet path is invalid or triggers are missing",
673 self.validate(&mut errors);
675 if errors.is_empty() {
678 Err(ConfigUpdateError { errors })
682 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
683 use serde::de::Error;
684 if self.data.checkOnSave_command.is_empty() {
686 "/checkOnSave/command".to_string(),
687 serde_json::Error::custom("expected a non-empty string"),
692 pub fn json_schema() -> serde_json::Value {
693 ConfigData::json_schema()
696 pub fn root_path(&self) -> &AbsPathBuf {
700 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
704 pub fn detached_files(&self) -> &[AbsPathBuf] {
711 || -> _ { Some($expr) }()
714 macro_rules! try_or {
715 ($expr:expr, $or:expr) => {
716 try_!($expr).unwrap_or($or)
720 macro_rules! try_or_def {
722 try_!($expr).unwrap_or_default()
727 pub fn linked_projects(&self) -> Vec<LinkedProject> {
728 match self.data.linkedProjects.as_slice() {
729 [] => match self.discovered_projects.as_ref() {
730 Some(discovered_projects) => {
731 let exclude_dirs: Vec<_> = self
735 .map(|p| self.root_path.join(p))
740 let (ProjectManifest::ProjectJson(path)
741 | ProjectManifest::CargoToml(path)) = p;
742 !exclude_dirs.iter().any(|p| path.starts_with(p))
745 .map(LinkedProject::from)
750 linked_projects => linked_projects
752 .filter_map(|linked_project| match linked_project {
753 ManifestOrProjectJson::Manifest(it) => {
754 let path = self.root_path.join(it);
755 ProjectManifest::from_manifest_file(path)
756 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
760 ManifestOrProjectJson::ProjectJson(it) => {
761 Some(ProjectJson::new(&self.root_path, it.clone()).into())
768 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
769 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
770 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
773 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
775 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
779 pub fn prefill_caches(&self) -> bool {
780 self.data.cachePriming_enable
783 pub fn location_link(&self) -> bool {
784 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
787 pub fn line_folding_only(&self) -> bool {
788 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
791 pub fn hierarchical_symbols(&self) -> bool {
798 .hierarchical_document_symbol_support?
802 pub fn code_action_literals(&self) -> bool {
809 .code_action_literal_support
814 pub fn work_done_progress(&self) -> bool {
815 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
818 pub fn will_rename(&self) -> bool {
819 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
822 pub fn change_annotation_support(&self) -> bool {
829 .change_annotation_support
834 pub fn code_action_resolve(&self) -> bool {
846 .any(|it| it == "edit")
849 pub fn signature_help_label_offsets(&self) -> bool {
856 .signature_information
858 .parameter_information
860 .label_offset_support?
864 pub fn completion_label_details_support(&self) -> bool {
873 .label_details_support
878 pub fn offset_encoding(&self) -> OffsetEncoding {
879 if supports_utf8(&self.caps) {
882 OffsetEncoding::Utf16
886 fn experimental(&self, index: &'static str) -> bool {
887 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
890 pub fn code_action_group(&self) -> bool {
891 self.experimental("codeActionGroup")
894 pub fn server_status_notification(&self) -> bool {
895 self.experimental("serverStatusNotification")
898 pub fn publish_diagnostics(&self) -> bool {
899 self.data.diagnostics_enable
902 pub fn diagnostics(&self) -> DiagnosticsConfig {
904 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
905 proc_macros_enabled: self.data.procMacro_enable,
906 disable_experimental: !self.data.diagnostics_experimental_enable,
907 disabled: self.data.diagnostics_disabled.clone(),
908 expr_fill_default: match self.data.assist_expressionFillDefault {
909 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
910 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
912 insert_use: self.insert_use_config(),
916 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
917 DiagnosticsMapConfig {
918 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
919 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
920 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
924 pub fn lru_capacity(&self) -> Option<usize> {
925 self.data.lru_capacity
928 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
929 if !self.data.procMacro_enable {
932 let path = match &self.data.procMacro_server {
933 Some(it) => self.root_path.join(it),
934 None => AbsPathBuf::assert(std::env::current_exe().ok()?),
936 Some((path, vec!["proc-macro".into()]))
939 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
940 &self.data.procMacro_ignored
943 pub fn expand_proc_attr_macros(&self) -> bool {
944 self.data.procMacro_enable && self.data.procMacro_attributes_enable
947 pub fn files(&self) -> FilesConfig {
949 watcher: match self.data.files_watcher {
950 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
953 _ => FilesWatcher::Server,
955 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
959 pub fn notifications(&self) -> NotificationsConfig {
960 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
963 pub fn cargo_autoreload(&self) -> bool {
964 self.data.cargo_autoreload
967 pub fn run_build_scripts(&self) -> bool {
968 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
971 pub fn cargo(&self) -> CargoConfig {
972 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
973 if rustc_src == "discover" {
974 RustcSource::Discover
976 RustcSource::Path(self.root_path.join(rustc_src))
981 no_default_features: self.data.cargo_noDefaultFeatures,
982 all_features: matches!(self.data.cargo_features, CargoFeatures::All),
983 features: match &self.data.cargo_features {
984 CargoFeatures::All => vec![],
985 CargoFeatures::Listed(it) => it.clone(),
987 target: self.data.cargo_target.clone(),
988 no_sysroot: self.data.cargo_noSysroot,
990 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
991 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
992 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
996 pub fn rustfmt(&self) -> RustfmtConfig {
997 match &self.data.rustfmt_overrideCommand {
998 Some(args) if !args.is_empty() => {
999 let mut args = args.clone();
1000 let command = args.remove(0);
1001 RustfmtConfig::CustomCommand { command, args }
1003 Some(_) | None => RustfmtConfig::Rustfmt {
1004 extra_args: self.data.rustfmt_extraArgs.clone(),
1005 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
1010 pub fn flycheck(&self) -> Option<FlycheckConfig> {
1011 if !self.data.checkOnSave_enable {
1014 let flycheck_config = match &self.data.checkOnSave_overrideCommand {
1015 Some(args) if !args.is_empty() => {
1016 let mut args = args.clone();
1017 let command = args.remove(0);
1018 FlycheckConfig::CustomCommand { command, args }
1020 Some(_) | None => FlycheckConfig::CargoCommand {
1021 command: self.data.checkOnSave_command.clone(),
1026 .or_else(|| self.data.cargo_target.clone()),
1027 all_targets: self.data.checkOnSave_allTargets,
1028 no_default_features: self
1030 .checkOnSave_noDefaultFeatures
1031 .unwrap_or(self.data.cargo_noDefaultFeatures),
1032 all_features: matches!(
1033 self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
1036 features: match self
1038 .checkOnSave_features
1040 .unwrap_or_else(|| self.data.cargo_features.clone())
1042 CargoFeatures::All => vec![],
1043 CargoFeatures::Listed(it) => it,
1045 extra_args: self.data.checkOnSave_extraArgs.clone(),
1048 Some(flycheck_config)
1051 pub fn runnables(&self) -> RunnablesConfig {
1053 override_cargo: self.data.runnables_command.clone(),
1054 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1058 pub fn inlay_hints(&self) -> InlayHintsConfig {
1060 render_colons: self.data.inlayHints_renderColons,
1061 type_hints: self.data.inlayHints_typeHints_enable,
1062 parameter_hints: self.data.inlayHints_parameterHints_enable,
1063 chaining_hints: self.data.inlayHints_chainingHints_enable,
1064 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1065 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1066 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1067 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1069 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1070 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1071 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1072 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1074 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1075 hide_closure_initialization_hints: self
1077 .inlayHints_typeHints_hideClosureInitialization,
1078 reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1079 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1080 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1081 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1083 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1084 param_names_for_lifetime_elision_hints: self
1086 .inlayHints_lifetimeElisionHints_useParameterNames,
1087 max_length: self.data.inlayHints_maxLength,
1088 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1089 Some(self.data.inlayHints_closingBraceHints_minLines)
1096 fn insert_use_config(&self) -> InsertUseConfig {
1098 granularity: match self.data.imports_granularity_group {
1099 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1100 ImportGranularityDef::Item => ImportGranularity::Item,
1101 ImportGranularityDef::Crate => ImportGranularity::Crate,
1102 ImportGranularityDef::Module => ImportGranularity::Module,
1104 enforce_granularity: self.data.imports_granularity_enforce,
1105 prefix_kind: match self.data.imports_prefix {
1106 ImportPrefixDef::Plain => PrefixKind::Plain,
1107 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1108 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1110 group: self.data.imports_group_enable,
1111 skip_glob_imports: !self.data.imports_merge_glob,
1115 pub fn completion(&self) -> CompletionConfig {
1117 enable_postfix_completions: self.data.completion_postfix_enable,
1118 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1119 && completion_item_edit_resolve(&self.caps),
1120 enable_self_on_the_fly: self.data.completion_autoself_enable,
1121 enable_private_editable: self.data.completion_privateEditable_enable,
1122 callable: match self.data.completion_callable_snippets {
1123 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1124 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1125 CallableCompletionDef::None => None,
1127 insert_use: self.insert_use_config(),
1128 snippet_cap: SnippetCap::new(try_or_def!(
1138 snippets: self.snippets.clone(),
1142 pub fn snippet_cap(&self) -> bool {
1143 self.experimental("snippetTextEdit")
1146 pub fn assist(&self) -> AssistConfig {
1148 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1150 insert_use: self.insert_use_config(),
1154 pub fn join_lines(&self) -> JoinLinesConfig {
1156 join_else_if: self.data.joinLines_joinElseIf,
1157 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1158 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1159 join_assignments: self.data.joinLines_joinAssignments,
1163 pub fn call_info(&self) -> CallInfoConfig {
1165 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1166 docs: self.data.signatureInfo_documentation_enable,
1170 pub fn lens(&self) -> LensConfig {
1172 run: self.data.lens_enable && self.data.lens_run_enable,
1173 debug: self.data.lens_enable && self.data.lens_debug_enable,
1174 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1175 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1176 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1177 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1178 enum_variant_refs: self.data.lens_enable
1179 && self.data.lens_references_enumVariant_enable,
1183 pub fn hover_actions(&self) -> HoverActionsConfig {
1184 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1185 HoverActionsConfig {
1186 implementations: enable && self.data.hover_actions_implementations_enable,
1187 references: enable && self.data.hover_actions_references_enable,
1188 run: enable && self.data.hover_actions_run_enable,
1189 debug: enable && self.data.hover_actions_debug_enable,
1190 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1194 pub fn highlighting_config(&self) -> HighlightConfig {
1196 strings: self.data.semanticHighlighting_strings_enable,
1197 punctuation: self.data.semanticHighlighting_punctuation_enable,
1198 specialize_punctuation: self
1200 .semanticHighlighting_punctuation_specialization_enable,
1201 operator: self.data.semanticHighlighting_operator_enable,
1202 specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
1203 syntactic_name_ref_highlighting: false,
1207 pub fn hover(&self) -> HoverConfig {
1209 links_in_hover: self.data.hover_links_enable,
1210 documentation: self.data.hover_documentation_enable.then(|| {
1211 let is_markdown = try_or_def!(self
1220 .contains(&MarkupKind::Markdown);
1222 HoverDocFormat::Markdown
1224 HoverDocFormat::PlainText
1227 keywords: self.data.hover_documentation_keywords_enable,
1231 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1232 WorkspaceSymbolConfig {
1233 search_scope: match self.data.workspace_symbol_search_scope {
1234 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1235 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1236 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1239 search_kind: match self.data.workspace_symbol_search_kind {
1240 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1241 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1243 search_limit: self.data.workspace_symbol_search_limit,
1247 pub fn semantic_tokens_refresh(&self) -> bool {
1248 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1251 pub fn code_lens_refresh(&self) -> bool {
1252 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1255 pub fn insert_replace_support(&self) -> bool {
1264 .insert_replace_support?
1268 pub fn client_commands(&self) -> ClientCommandsConfig {
1270 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1271 let commands: Option<lsp_ext::ClientCommandOptions> =
1272 serde_json::from_value(commands.clone()).ok();
1273 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1274 let commands = commands.map(|it| it.commands).unwrap_or_default();
1276 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1278 ClientCommandsConfig {
1279 run_single: get("rust-analyzer.runSingle"),
1280 debug_single: get("rust-analyzer.debugSingle"),
1281 show_reference: get("rust-analyzer.showReferences"),
1282 goto_location: get("rust-analyzer.gotoLocation"),
1283 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1287 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1288 HighlightRelatedConfig {
1289 references: self.data.highlightRelated_references_enable,
1290 break_points: self.data.highlightRelated_breakPoints_enable,
1291 exit_points: self.data.highlightRelated_exitPoints_enable,
1292 yield_points: self.data.highlightRelated_yieldPoints_enable,
1296 pub fn prime_caches_num_threads(&self) -> u8 {
1297 match self.data.cachePriming_numThreads {
1298 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1303 pub fn typing_autoclose_angle(&self) -> bool {
1304 self.data.typing_autoClosingAngleBrackets_enable
1307 // Deserialization definitions
1309 macro_rules! create_bool_or_string_de {
1310 ($ident:ident<$bool:literal, $string:literal>) => {
1311 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1313 D: serde::Deserializer<'de>,
1316 impl<'de> serde::de::Visitor<'de> for V {
1319 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1320 formatter.write_str(concat!(
1323 stringify!($string),
1328 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1330 E: serde::de::Error,
1334 _ => Err(serde::de::Error::invalid_value(
1335 serde::de::Unexpected::Bool(v),
1341 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1343 E: serde::de::Error,
1347 _ => Err(serde::de::Error::invalid_value(
1348 serde::de::Unexpected::Str(v),
1354 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1356 A: serde::de::EnumAccess<'de>,
1358 use serde::de::VariantAccess;
1359 let (variant, va) = a.variant::<&'de str>()?;
1363 _ => Err(serde::de::Error::invalid_value(
1364 serde::de::Unexpected::Str(variant),
1370 d.deserialize_any(V)
1374 create_bool_or_string_de!(true_or_always<true, "always">);
1375 create_bool_or_string_de!(false_or_never<false, "never">);
1377 macro_rules! named_unit_variant {
1378 ($variant:ident) => {
1379 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1381 D: serde::Deserializer<'de>,
1384 impl<'de> serde::de::Visitor<'de> for V {
1386 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1387 f.write_str(concat!("\"", stringify!($variant), "\""))
1389 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1390 if value == stringify!($variant) {
1393 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1397 deserializer.deserialize_str(V)
1403 named_unit_variant!(all);
1404 named_unit_variant!(skip_trivial);
1405 named_unit_variant!(mutable);
1406 named_unit_variant!(with_block);
1409 #[derive(Deserialize, Debug, Clone, Copy)]
1410 #[serde(rename_all = "snake_case")]
1411 enum SnippetScopeDef {
1417 impl Default for SnippetScopeDef {
1418 fn default() -> Self {
1419 SnippetScopeDef::Expr
1423 #[derive(Deserialize, Debug, Clone, Default)]
1426 #[serde(deserialize_with = "single_or_array")]
1427 prefix: Vec<String>,
1428 #[serde(deserialize_with = "single_or_array")]
1429 postfix: Vec<String>,
1430 description: Option<String>,
1431 #[serde(deserialize_with = "single_or_array")]
1433 #[serde(deserialize_with = "single_or_array")]
1434 requires: Vec<String>,
1435 scope: SnippetScopeDef,
1438 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1440 D: serde::Deserializer<'de>,
1444 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1445 type Value = Vec<String>;
1447 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1448 formatter.write_str("string or array of strings")
1451 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1453 E: serde::de::Error,
1455 Ok(vec![value.to_owned()])
1458 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1460 A: serde::de::SeqAccess<'de>,
1462 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1466 deserializer.deserialize_any(SingleOrVec)
1469 #[derive(Deserialize, Debug, Clone)]
1471 enum ManifestOrProjectJson {
1473 ProjectJson(ProjectJsonData),
1476 #[derive(Deserialize, Debug, Clone)]
1477 #[serde(rename_all = "snake_case")]
1478 enum ExprFillDefaultDef {
1483 #[derive(Deserialize, Debug, Clone)]
1484 #[serde(rename_all = "snake_case")]
1485 enum ImportGranularityDef {
1492 #[derive(Deserialize, Debug, Copy, Clone)]
1493 #[serde(rename_all = "snake_case")]
1494 enum CallableCompletionDef {
1500 #[derive(Deserialize, Debug, Clone)]
1502 enum CargoFeatures {
1503 #[serde(deserialize_with = "de_unit_v::all")]
1505 Listed(Vec<String>),
1508 #[derive(Deserialize, Debug, Clone)]
1510 enum LifetimeElisionDef {
1511 #[serde(deserialize_with = "true_or_always")]
1513 #[serde(deserialize_with = "false_or_never")]
1515 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1519 #[derive(Deserialize, Debug, Clone)]
1521 enum ClosureReturnTypeHintsDef {
1522 #[serde(deserialize_with = "true_or_always")]
1524 #[serde(deserialize_with = "false_or_never")]
1526 #[serde(deserialize_with = "de_unit_v::with_block")]
1530 #[derive(Deserialize, Debug, Clone)]
1532 enum ReborrowHintsDef {
1533 #[serde(deserialize_with = "true_or_always")]
1535 #[serde(deserialize_with = "false_or_never")]
1537 #[serde(deserialize_with = "de_unit_v::mutable")]
1541 #[derive(Deserialize, Debug, Clone)]
1542 #[serde(rename_all = "snake_case")]
1543 enum FilesWatcherDef {
1549 #[derive(Deserialize, Debug, Clone)]
1550 #[serde(rename_all = "snake_case")]
1551 enum ImportPrefixDef {
1553 #[serde(alias = "self")]
1555 #[serde(alias = "crate")]
1559 #[derive(Deserialize, Debug, Clone)]
1560 #[serde(rename_all = "snake_case")]
1561 enum WorkspaceSymbolSearchScopeDef {
1563 WorkspaceAndDependencies,
1566 #[derive(Deserialize, Debug, Clone)]
1567 #[serde(rename_all = "snake_case")]
1568 enum SignatureDetail {
1573 #[derive(Deserialize, Debug, Clone)]
1574 #[serde(rename_all = "snake_case")]
1575 enum WorkspaceSymbolSearchKindDef {
1580 macro_rules! _config_data {
1581 (struct $name:ident {
1583 $(#[doc=$doc:literal])*
1584 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1587 #[allow(non_snake_case)]
1588 #[derive(Debug, Clone)]
1589 struct $name { $($field: $ty,)* }
1591 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1597 None$(.or(Some(stringify!($alias))))*,
1603 fn json_schema() -> serde_json::Value {
1606 let field = stringify!($field);
1607 let ty = stringify!($ty);
1609 (field, ty, &[$($doc),*], $default)
1615 fn manual() -> String {
1618 let field = stringify!($field);
1619 let ty = stringify!($ty);
1621 (field, ty, &[$($doc),*], $default)
1628 fn fields_are_sorted() {
1629 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1633 use _config_data as config_data;
1635 fn get_field<T: DeserializeOwned>(
1636 json: &mut serde_json::Value,
1637 error_sink: &mut Vec<(String, serde_json::Error)>,
1638 field: &'static str,
1639 alias: Option<&'static str>,
1642 let default = serde_json::from_str(default).unwrap();
1643 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1644 // defaults instead of sending an empty object.
1647 .chain(iter::once(field))
1648 .find_map(move |field| {
1649 let mut pointer = field.replace('_', "/");
1650 pointer.insert(0, '/');
1651 json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1654 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1655 error_sink.push((pointer, e));
1663 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1664 for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1665 fn key(f: &str) -> &str {
1666 f.splitn(2, '_').next().unwrap()
1668 assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1673 .map(|(field, ty, doc, default)| {
1674 let name = field.replace('_', ".");
1675 let name = format!("rust-analyzer.{}", name);
1676 let props = field_props(field, ty, doc, default);
1679 .collect::<serde_json::Map<_, _>>();
1683 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1684 let doc = doc_comment_to_string(doc);
1685 let doc = doc.trim_end_matches('\n');
1687 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1688 "bad docs for {}: {:?}",
1692 let default = default.parse::<serde_json::Value>().unwrap();
1694 let mut map = serde_json::Map::default();
1696 ($($key:literal: $value:tt),*$(,)?) => {{$(
1697 map.insert($key.into(), serde_json::json!($value));
1700 set!("markdownDescription": doc);
1701 set!("default": default);
1704 "bool" => set!("type": "boolean"),
1705 "usize" => set!("type": "integer", "minimum": 0),
1706 "String" => set!("type": "string"),
1707 "Vec<String>" => set! {
1709 "items": { "type": "string" },
1711 "Vec<PathBuf>" => set! {
1713 "items": { "type": "string" },
1715 "FxHashSet<String>" => set! {
1717 "items": { "type": "string" },
1718 "uniqueItems": true,
1720 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1723 "FxHashMap<String, SnippetDef>" => set! {
1726 "FxHashMap<String, String>" => set! {
1729 "Option<usize>" => set! {
1730 "type": ["null", "integer"],
1733 "Option<String>" => set! {
1734 "type": ["null", "string"],
1736 "Option<PathBuf>" => set! {
1737 "type": ["null", "string"],
1739 "Option<bool>" => set! {
1740 "type": ["null", "boolean"],
1742 "Option<Vec<String>>" => set! {
1743 "type": ["null", "array"],
1744 "items": { "type": "string" },
1746 "MergeBehaviorDef" => set! {
1748 "enum": ["none", "crate", "module"],
1749 "enumDescriptions": [
1750 "Do not merge imports at all.",
1751 "Merge imports from the same crate into a single `use` statement.",
1752 "Merge imports from the same module into a single `use` statement."
1755 "ExprFillDefaultDef" => set! {
1757 "enum": ["todo", "default"],
1758 "enumDescriptions": [
1759 "Fill missing expressions with the `todo` macro",
1760 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1763 "ImportGranularityDef" => set! {
1765 "enum": ["preserve", "crate", "module", "item"],
1766 "enumDescriptions": [
1767 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1768 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1769 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1770 "Flatten imports so that each has its own use statement."
1773 "ImportPrefixDef" => set! {
1780 "enumDescriptions": [
1781 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1782 "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.",
1783 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1786 "Vec<ManifestOrProjectJson>" => set! {
1788 "items": { "type": ["string", "object"] },
1790 "WorkspaceSymbolSearchScopeDef" => set! {
1792 "enum": ["workspace", "workspace_and_dependencies"],
1793 "enumDescriptions": [
1794 "Search in current workspace only.",
1795 "Search in current workspace and dependencies."
1798 "WorkspaceSymbolSearchKindDef" => set! {
1800 "enum": ["only_types", "all_symbols"],
1801 "enumDescriptions": [
1802 "Search for types only.",
1803 "Search for all symbols kinds."
1806 "ParallelCachePrimingNumThreads" => set! {
1811 "LifetimeElisionDef" => set! {
1818 "enumDescriptions": [
1819 "Always show lifetime elision hints.",
1820 "Never show lifetime elision hints.",
1821 "Only show lifetime elision hints if a return type is involved."
1824 "ClosureReturnTypeHintsDef" => set! {
1831 "enumDescriptions": [
1832 "Always show type hints for return types of closures.",
1833 "Never show type hints for return types of closures.",
1834 "Only show type hints for return types of closures with blocks."
1837 "ReborrowHintsDef" => set! {
1844 "enumDescriptions": [
1845 "Always show reborrow hints.",
1846 "Never show reborrow hints.",
1847 "Only show mutable reborrow hints."
1850 "CargoFeatures" => set! {
1857 "enumDescriptions": [
1858 "Pass `--all-features` to cargo",
1863 "items": { "type": "string" }
1867 "Option<CargoFeatures>" => set! {
1874 "enumDescriptions": [
1875 "Pass `--all-features` to cargo",
1880 "items": { "type": "string" }
1885 "CallableCompletionDef" => set! {
1892 "enumDescriptions": [
1893 "Add call parentheses and pre-fill arguments.",
1894 "Add call parentheses.",
1895 "Do no snippet completions for callables."
1898 "SignatureDetail" => set! {
1900 "enum": ["full", "parameters"],
1901 "enumDescriptions": [
1902 "Show the entire signature.",
1903 "Show only the parameters."
1906 "FilesWatcherDef" => set! {
1908 "enum": ["client", "server"],
1909 "enumDescriptions": [
1910 "Use the client (editor) to watch files for changes",
1911 "Use server-side file watching",
1914 _ => panic!("missing entry for {}: {}", ty, default),
1921 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1924 .map(|(field, _ty, doc, default)| {
1925 let name = format!("rust-analyzer.{}", field.replace('_', "."));
1926 let doc = doc_comment_to_string(*doc);
1927 if default.contains('\n') {
1939 name, name, default, doc
1942 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1945 .collect::<String>()
1948 fn doc_comment_to_string(doc: &[&str]) -> String {
1949 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1956 use test_utils::{ensure_file_contents, project_root};
1961 fn generate_package_json_config() {
1962 let s = Config::json_schema();
1963 let schema = format!("{:#}", s);
1964 let mut schema = schema
1965 .trim_start_matches('{')
1966 .trim_end_matches('}')
1968 .replace('\n', "\n ")
1969 .trim_start_matches('\n')
1972 schema.push_str(",\n");
1974 // Transform the asciidoc form link to markdown style.
1976 // https://link[text] => [text](https://link)
1977 let url_matches = schema.match_indices("https://");
1978 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1979 url_offsets.reverse();
1980 for idx in url_offsets {
1981 let link = &schema[idx..];
1982 // matching on whitespace to ignore normal links
1983 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
1984 if link.chars().nth(link_end) == Some('[') {
1985 if let Some(link_text_end) = link.find(']') {
1986 let link_text = link[link_end..(link_text_end + 1)].to_string();
1988 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
1989 schema.insert(idx, '(');
1990 schema.insert(idx + link_end + 1, ')');
1991 schema.insert_str(idx, &link_text);
1997 let package_json_path = project_root().join("editors/code/package.json");
1998 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
2000 let start_marker = " \"$generated-start\": {},\n";
2001 let end_marker = " \"$generated-end\": {}\n";
2003 let start = package_json.find(start_marker).unwrap() + start_marker.len();
2004 let end = package_json.find(end_marker).unwrap();
2006 let p = remove_ws(&package_json[start..end]);
2007 let s = remove_ws(&schema);
2008 if !p.contains(&s) {
2009 package_json.replace_range(start..end, &schema);
2010 ensure_file_contents(&package_json_path, &package_json)
2015 fn generate_config_documentation() {
2016 let docs_path = project_root().join("docs/user/generated_config.adoc");
2017 let expected = ConfigData::manual();
2018 ensure_file_contents(&docs_path, &expected);
2021 fn remove_ws(text: &str) -> String {
2022 text.replace(char::is_whitespace, "")