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, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource,
28 use rustc_hash::{FxHashMap, FxHashSet};
29 use serde::{de::DeserializeOwned, Deserialize};
33 caps::completion_item_edit_resolve,
34 diagnostics::DiagnosticsMapConfig,
35 line_index::OffsetEncoding,
36 lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
41 // Conventions for configuration keys to preserve maximal extendability without breakage:
42 // - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable`
43 // This has the benefit of namespaces being extensible, and if the suffix doesn't fit later it can be changed without breakage.
44 // - In general be wary of using the namespace of something verbatim, it prevents us from adding subkeys in the future
45 // - Don't use abbreviations unless really necessary
46 // - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
48 // Defines the server-side configuration of the rust-analyzer. We generate
49 // *parts* of VS Code's `package.json` config from this. Run `cargo test` to
50 // re-generate that file.
52 // However, editor specific config, which the server doesn't know about, should
53 // be specified directly in `package.json`.
55 // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
56 // parsing the old name.
59 /// Placeholder expression to use for missing expressions in assists.
60 assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
62 /// Warm up caches on project load.
63 cachePriming_enable: bool = "true",
64 /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
65 cachePriming_numThreads: ParallelCachePrimingNumThreads = "0",
67 /// Automatically refresh project info via `cargo metadata` on
68 /// `Cargo.toml` or `.cargo/config.toml` changes.
69 cargo_autoreload: bool = "true",
70 /// Run build scripts (`build.rs`) for more precise code analysis.
71 cargo_buildScripts_enable: bool = "true",
72 /// Override the command rust-analyzer uses to run build scripts and
73 /// build procedural macros. The command is required to output json
74 /// and should therefore include `--message-format=json` or a similar
77 /// By default, a cargo invocation will be constructed for the configured
78 /// targets and features, with the following base command line:
81 /// cargo check --quiet --workspace --message-format=json --all-targets
84 cargo_buildScripts_overrideCommand: Option<Vec<String>> = "null",
85 /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
86 /// avoid checking unnecessary things.
87 cargo_buildScripts_useRustcWrapper: bool = "true",
88 /// Extra environment variables that will be set when running cargo, rustc
89 /// or other commands within the workspace. Useful for setting RUSTFLAGS.
90 cargo_extraEnv: FxHashMap<String, String> = "{}",
91 /// List of features to activate.
93 /// Set this to `"all"` to pass `--all-features` to cargo.
94 cargo_features: CargoFeaturesDef = "[]",
95 /// Whether to pass `--no-default-features` to cargo.
96 cargo_noDefaultFeatures: bool = "false",
97 /// Internal config for debugging, disables loading of sysroot crates.
98 cargo_noSysroot: bool = "false",
99 /// Compilation target override (target triple).
100 cargo_target: Option<String> = "null",
101 /// Unsets `#[cfg(test)]` for the specified crates.
102 cargo_unsetTest: Vec<String> = "[\"core\"]",
104 /// Check all targets and tests (`--all-targets`).
105 checkOnSave_allTargets: bool = "true",
106 /// Cargo command to use for `cargo check`.
107 checkOnSave_command: String = "\"check\"",
108 /// Run specified `cargo check` command for diagnostics on save.
109 checkOnSave_enable: bool = "true",
110 /// Extra arguments for `cargo check`.
111 checkOnSave_extraArgs: Vec<String> = "[]",
112 /// Extra environment variables that will be set when running `cargo check`.
113 /// Extends `#rust-analyzer.cargo.extraEnv#`.
114 checkOnSave_extraEnv: FxHashMap<String, String> = "{}",
115 /// List of features to activate. Defaults to
116 /// `#rust-analyzer.cargo.features#`.
118 /// Set to `"all"` to pass `--all-features` to Cargo.
119 checkOnSave_features: Option<CargoFeaturesDef> = "null",
120 /// Whether to pass `--no-default-features` to Cargo. Defaults to
121 /// `#rust-analyzer.cargo.noDefaultFeatures#`.
122 checkOnSave_noDefaultFeatures: Option<bool> = "null",
123 /// Override the command rust-analyzer uses instead of `cargo check` for
124 /// diagnostics on save. The command is required to output json and
125 /// should therefor include `--message-format=json` or a similar option.
127 /// If you're changing this because you're using some tool wrapping
128 /// Cargo, you might also want to change
129 /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
131 /// If there are multiple linked projects, this command is invoked for
132 /// each of them, with the working directory being the project root
133 /// (i.e., the folder containing the `Cargo.toml`).
135 /// An example command would be:
138 /// cargo check --workspace --message-format=json --all-targets
141 checkOnSave_overrideCommand: Option<Vec<String>> = "null",
142 /// Check for a specific target. Defaults to
143 /// `#rust-analyzer.cargo.target#`.
144 checkOnSave_target: Option<String> = "null",
146 /// Toggles the additional completions that automatically add imports when completed.
147 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
148 completion_autoimport_enable: bool = "true",
149 /// Toggles the additional completions that automatically show method calls and field accesses
150 /// with `self` prefixed to them when inside a method.
151 completion_autoself_enable: bool = "true",
152 /// Whether to add parenthesis and argument snippets when completing function.
153 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
154 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
155 completion_postfix_enable: bool = "true",
156 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
157 completion_privateEditable_enable: bool = "false",
158 /// Custom completion snippets.
159 // NOTE: Keep this list in sync with the feature docs of user snippets.
160 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
163 "body": "Arc::new(${receiver})",
164 "requires": "std::sync::Arc",
165 "description": "Put the expression into an `Arc`",
170 "body": "Rc::new(${receiver})",
171 "requires": "std::rc::Rc",
172 "description": "Put the expression into an `Rc`",
177 "body": "Box::pin(${receiver})",
178 "requires": "std::boxed::Box",
179 "description": "Put the expression into a pinned `Box`",
184 "body": "Ok(${receiver})",
185 "description": "Wrap the expression in a `Result::Ok`",
190 "body": "Err(${receiver})",
191 "description": "Wrap the expression in a `Result::Err`",
196 "body": "Some(${receiver})",
197 "description": "Wrap the expression in an `Option::Some`",
202 /// List of rust-analyzer diagnostics to disable.
203 diagnostics_disabled: FxHashSet<String> = "[]",
204 /// Whether to show native rust-analyzer diagnostics.
205 diagnostics_enable: bool = "true",
206 /// Whether to show experimental rust-analyzer diagnostics that might
207 /// have more false positives than usual.
208 diagnostics_experimental_enable: bool = "false",
209 /// Map of prefixes to be substituted when parsing diagnostic file paths.
210 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
211 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
212 /// List of warnings that should be displayed with hint severity.
214 /// The warnings will be indicated by faded text or three dots in code
215 /// and will not show up in the `Problems Panel`.
216 diagnostics_warningsAsHint: Vec<String> = "[]",
217 /// List of warnings that should be displayed with info severity.
219 /// The warnings will be indicated by a blue squiggly underline in code
220 /// and a blue icon in the `Problems Panel`.
221 diagnostics_warningsAsInfo: Vec<String> = "[]",
223 /// These directories will be ignored by rust-analyzer. They are
224 /// relative to the workspace root, and globs are not supported. You may
225 /// also need to add the folders to Code's `files.watcherExclude`.
226 files_excludeDirs: Vec<PathBuf> = "[]",
227 /// Controls file watching implementation.
228 files_watcher: FilesWatcherDef = "\"client\"",
229 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
230 highlightRelated_breakPoints_enable: bool = "true",
231 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
232 highlightRelated_exitPoints_enable: bool = "true",
233 /// Enables highlighting of related references while the cursor is on any identifier.
234 highlightRelated_references_enable: bool = "true",
235 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
236 highlightRelated_yieldPoints_enable: bool = "true",
238 /// Whether to show `Debug` action. Only applies when
239 /// `#rust-analyzer.hover.actions.enable#` is set.
240 hover_actions_debug_enable: bool = "true",
241 /// Whether to show HoverActions in Rust files.
242 hover_actions_enable: bool = "true",
243 /// Whether to show `Go to Type Definition` action. Only applies when
244 /// `#rust-analyzer.hover.actions.enable#` is set.
245 hover_actions_gotoTypeDef_enable: bool = "true",
246 /// Whether to show `Implementations` action. Only applies when
247 /// `#rust-analyzer.hover.actions.enable#` is set.
248 hover_actions_implementations_enable: bool = "true",
249 /// Whether to show `References` action. Only applies when
250 /// `#rust-analyzer.hover.actions.enable#` is set.
251 hover_actions_references_enable: bool = "false",
252 /// Whether to show `Run` action. Only applies when
253 /// `#rust-analyzer.hover.actions.enable#` is set.
254 hover_actions_run_enable: bool = "true",
256 /// Whether to show documentation on hover.
257 hover_documentation_enable: bool = "true",
258 /// Whether to show keyword hover popups. Only applies when
259 /// `#rust-analyzer.hover.documentation.enable#` is set.
260 hover_documentation_keywords_enable: bool = "true",
261 /// Use markdown syntax for links in hover.
262 hover_links_enable: bool = "true",
264 /// 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.
265 imports_granularity_enforce: bool = "false",
266 /// How imports should be grouped into use statements.
267 imports_granularity_group: ImportGranularityDef = "\"crate\"",
268 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
269 imports_group_enable: bool = "true",
270 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
271 imports_merge_glob: bool = "true",
272 /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate.
273 imports_prefer_no_std: bool = "false",
274 /// The path structure for newly inserted paths to use.
275 imports_prefix: ImportPrefixDef = "\"plain\"",
277 /// Whether to show inlay type hints for binding modes.
278 inlayHints_bindingModeHints_enable: bool = "false",
279 /// Whether to show inlay type hints for method chains.
280 inlayHints_chainingHints_enable: bool = "true",
281 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
282 inlayHints_closingBraceHints_enable: bool = "true",
283 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
284 /// to always show them).
285 inlayHints_closingBraceHints_minLines: usize = "25",
286 /// Whether to show inlay type hints for return types of closures.
287 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
288 /// Whether to show inlay type hints for elided lifetimes in function signatures.
289 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
290 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
291 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
292 /// Maximum length for inlay hints. Set to null to have an unlimited length.
293 inlayHints_maxLength: Option<usize> = "25",
294 /// Whether to show function parameter name inlay hints at the call
296 inlayHints_parameterHints_enable: bool = "true",
297 /// Whether to show inlay type hints for compiler inserted reborrows.
298 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
299 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
300 inlayHints_renderColons: bool = "true",
301 /// Whether to show inlay type hints for variables.
302 inlayHints_typeHints_enable: bool = "true",
303 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
304 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
305 inlayHints_typeHints_hideClosureInitialization: bool = "false",
306 /// Whether to hide inlay type hints for constructors.
307 inlayHints_typeHints_hideNamedConstructor: bool = "false",
309 /// Join lines merges consecutive declaration and initialization of an assignment.
310 joinLines_joinAssignments: bool = "true",
311 /// Join lines inserts else between consecutive ifs.
312 joinLines_joinElseIf: bool = "true",
313 /// Join lines removes trailing commas.
314 joinLines_removeTrailingComma: bool = "true",
315 /// Join lines unwraps trivial blocks.
316 joinLines_unwrapTrivialBlock: bool = "true",
319 /// Whether to show `Debug` lens. Only applies when
320 /// `#rust-analyzer.lens.enable#` is set.
321 lens_debug_enable: bool = "true",
322 /// Whether to show CodeLens in Rust files.
323 lens_enable: bool = "true",
324 /// Internal config: use custom client-side commands even when the
325 /// client doesn't set the corresponding capability.
326 lens_forceCustomCommands: bool = "true",
327 /// Whether to show `Implementations` lens. Only applies when
328 /// `#rust-analyzer.lens.enable#` is set.
329 lens_implementations_enable: bool = "true",
330 /// Where to render annotations.
331 lens_location: AnnotationLocation = "\"above_name\"",
332 /// Whether to show `References` lens for Struct, Enum, and Union.
333 /// Only applies when `#rust-analyzer.lens.enable#` is set.
334 lens_references_adt_enable: bool = "false",
335 /// Whether to show `References` lens for Enum Variants.
336 /// Only applies when `#rust-analyzer.lens.enable#` is set.
337 lens_references_enumVariant_enable: bool = "false",
338 /// Whether to show `Method References` lens. Only applies when
339 /// `#rust-analyzer.lens.enable#` is set.
340 lens_references_method_enable: bool = "false",
341 /// Whether to show `References` lens for Trait.
342 /// Only applies when `#rust-analyzer.lens.enable#` is set.
343 lens_references_trait_enable: bool = "false",
344 /// Whether to show `Run` lens. Only applies when
345 /// `#rust-analyzer.lens.enable#` is set.
346 lens_run_enable: bool = "true",
348 /// Disable project auto-discovery in favor of explicitly specified set
351 /// Elements must be paths pointing to `Cargo.toml`,
352 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
353 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
355 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
356 lru_capacity: Option<usize> = "null",
358 /// Whether to show `can't find Cargo.toml` error message.
359 notifications_cargoTomlNotFound: bool = "true",
361 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
362 procMacro_attributes_enable: bool = "true",
363 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
364 procMacro_enable: bool = "true",
365 /// These proc-macros will be ignored when trying to expand them.
367 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
368 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
369 /// Internal config, path to proc-macro server executable (typically,
370 /// this is rust-analyzer itself, but we override this in tests).
371 procMacro_server: Option<PathBuf> = "null",
373 /// Exclude imports from find-all-references.
374 references_excludeImports: bool = "false",
376 /// Command to be executed instead of 'cargo' for runnables.
377 runnables_command: Option<String> = "null",
378 /// Additional arguments to be passed to cargo for runnables such as
379 /// tests or binaries. For example, it may be `--release`.
380 runnables_extraArgs: Vec<String> = "[]",
382 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
383 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
386 /// Any project which uses rust-analyzer with the rustcPrivate
387 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
389 /// This option does not take effect until rust-analyzer is restarted.
390 rustc_source: Option<String> = "null",
392 /// Additional arguments to `rustfmt`.
393 rustfmt_extraArgs: Vec<String> = "[]",
394 /// Advanced option, fully override the command rust-analyzer uses for
396 rustfmt_overrideCommand: Option<Vec<String>> = "null",
397 /// Enables the use of rustfmt's unstable range formatting command for the
398 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
399 /// available on a nightly build.
400 rustfmt_rangeFormatting_enable: bool = "false",
402 /// Inject additional highlighting into doc comments.
404 /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
406 semanticHighlighting_doc_comment_inject_enable: bool = "true",
407 /// Use semantic tokens for operators.
409 /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
410 /// they are tagged with modifiers.
411 semanticHighlighting_operator_enable: bool = "true",
412 /// Use specialized semantic tokens for operators.
414 /// When enabled, rust-analyzer will emit special token types for operator tokens instead
415 /// of the generic `operator` token type.
416 semanticHighlighting_operator_specialization_enable: bool = "false",
417 /// Use semantic tokens for punctuations.
419 /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
420 /// they are tagged with modifiers or have a special role.
421 semanticHighlighting_punctuation_enable: bool = "false",
422 /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
424 semanticHighlighting_punctuation_separate_macro_bang: bool = "false",
425 /// Use specialized semantic tokens for punctuations.
427 /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
428 /// of the generic `punctuation` token type.
429 semanticHighlighting_punctuation_specialization_enable: bool = "false",
430 /// Use semantic tokens for strings.
432 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
433 /// By disabling semantic tokens for strings, other grammars can be used to highlight
435 semanticHighlighting_strings_enable: bool = "true",
437 /// Show full signature of the callable. Only shows parameters if disabled.
438 signatureInfo_detail: SignatureDetail = "\"full\"",
439 /// Show documentation.
440 signatureInfo_documentation_enable: bool = "true",
442 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
443 typing_autoClosingAngleBrackets_enable: bool = "false",
445 /// Workspace symbol search kind.
446 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
447 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
448 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
449 /// Other clients requires all results upfront and might require a higher limit.
450 workspace_symbol_search_limit: usize = "128",
451 /// Workspace symbol search scope.
452 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
456 impl Default for ConfigData {
457 fn default() -> Self {
458 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
462 #[derive(Debug, Clone)]
464 pub discovered_projects: Option<Vec<ProjectManifest>>,
465 caps: lsp_types::ClientCapabilities,
466 root_path: AbsPathBuf,
468 detached_files: Vec<AbsPathBuf>,
469 snippets: Vec<Snippet>,
472 type ParallelCachePrimingNumThreads = u8;
474 #[derive(Debug, Clone, Eq, PartialEq)]
475 pub enum LinkedProject {
476 ProjectManifest(ProjectManifest),
477 InlineJsonProject(ProjectJson),
480 impl From<ProjectManifest> for LinkedProject {
481 fn from(v: ProjectManifest) -> Self {
482 LinkedProject::ProjectManifest(v)
486 impl From<ProjectJson> for LinkedProject {
487 fn from(v: ProjectJson) -> Self {
488 LinkedProject::InlineJsonProject(v)
492 pub struct CallInfoConfig {
493 pub params_only: bool,
497 #[derive(Clone, Debug, PartialEq, Eq)]
498 pub struct LensConfig {
504 pub implementations: bool,
507 pub method_refs: bool,
508 pub refs_adt: bool, // for Struct, Enum, Union and Trait
509 pub refs_trait: bool, // for Struct, Enum, Union and Trait
510 pub enum_variant_refs: bool,
513 pub location: AnnotationLocation,
516 #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
517 #[serde(rename_all = "snake_case")]
518 pub enum AnnotationLocation {
523 impl From<AnnotationLocation> for ide::AnnotationLocation {
524 fn from(location: AnnotationLocation) -> Self {
526 AnnotationLocation::AboveName => ide::AnnotationLocation::AboveName,
527 AnnotationLocation::AboveWholeItem => ide::AnnotationLocation::AboveWholeItem,
533 pub fn any(&self) -> bool {
536 || self.implementations
540 || self.enum_variant_refs
543 pub fn none(&self) -> bool {
547 pub fn runnable(&self) -> bool {
548 self.run || self.debug
551 pub fn references(&self) -> bool {
552 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
556 #[derive(Clone, Debug, PartialEq, Eq)]
557 pub struct HoverActionsConfig {
558 pub implementations: bool,
559 pub references: bool,
562 pub goto_type_def: bool,
565 impl HoverActionsConfig {
566 pub const NO_ACTIONS: Self = Self {
567 implementations: false,
571 goto_type_def: false,
574 pub fn any(&self) -> bool {
575 self.implementations || self.references || self.runnable() || self.goto_type_def
578 pub fn none(&self) -> bool {
582 pub fn runnable(&self) -> bool {
583 self.run || self.debug
587 #[derive(Debug, Clone)]
588 pub struct FilesConfig {
589 pub watcher: FilesWatcher,
590 pub exclude: Vec<AbsPathBuf>,
593 #[derive(Debug, Clone)]
594 pub enum FilesWatcher {
599 #[derive(Debug, Clone)]
600 pub struct NotificationsConfig {
601 pub cargo_toml_not_found: bool,
604 #[derive(Debug, Clone)]
605 pub enum RustfmtConfig {
606 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
607 CustomCommand { command: String, args: Vec<String> },
610 /// Configuration for runnable items, such as `main` function or tests.
611 #[derive(Debug, Clone)]
612 pub struct RunnablesConfig {
613 /// Custom command to be executed instead of `cargo` for runnables.
614 pub override_cargo: Option<String>,
615 /// Additional arguments for the `cargo`, e.g. `--release`.
616 pub cargo_extra_args: Vec<String>,
619 /// Configuration for workspace symbol search requests.
620 #[derive(Debug, Clone)]
621 pub struct WorkspaceSymbolConfig {
622 /// In what scope should the symbol be searched in.
623 pub search_scope: WorkspaceSymbolSearchScope,
624 /// What kind of symbol is being searched for.
625 pub search_kind: WorkspaceSymbolSearchKind,
626 /// How many items are returned at most.
627 pub search_limit: usize,
630 pub struct ClientCommandsConfig {
631 pub run_single: bool,
632 pub debug_single: bool,
633 pub show_reference: bool,
634 pub goto_location: bool,
635 pub trigger_parameter_hints: bool,
639 pub struct ConfigUpdateError {
640 errors: Vec<(String, serde_json::Error)>,
643 impl fmt::Display for ConfigUpdateError {
644 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
645 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
652 "rust-analyzer found {} invalid config value{}:\n{}",
654 if self.errors.len() == 1 { "" } else { "s" },
661 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
664 data: ConfigData::default(),
665 detached_files: Vec::new(),
666 discovered_projects: None,
668 snippets: Default::default(),
672 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
673 tracing::info!("updating config from JSON: {:#}", json);
674 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
677 let mut errors = Vec::new();
678 self.detached_files =
679 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
681 .map(AbsPathBuf::assert)
683 patch_old_style::patch_json_for_outdated_configs(&mut json);
684 self.data = ConfigData::from_json(json, &mut errors);
685 tracing::debug!("deserialized config data: {:#?}", self.data);
686 self.snippets.clear();
687 for (name, def) in self.data.completion_snippets_custom.iter() {
688 if def.prefix.is_empty() && def.postfix.is_empty() {
691 let scope = match def.scope {
692 SnippetScopeDef::Expr => SnippetScope::Expr,
693 SnippetScopeDef::Type => SnippetScope::Type,
694 SnippetScopeDef::Item => SnippetScope::Item,
700 def.description.as_ref().unwrap_or(name),
704 Some(snippet) => self.snippets.push(snippet),
705 None => errors.push((
706 format!("snippet {name} is invalid"),
707 <serde_json::Error as serde::de::Error>::custom(
708 "snippet path is invalid or triggers are missing",
714 self.validate(&mut errors);
716 if errors.is_empty() {
719 Err(ConfigUpdateError { errors })
723 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
724 use serde::de::Error;
725 if self.data.checkOnSave_command.is_empty() {
727 "/checkOnSave/command".to_string(),
728 serde_json::Error::custom("expected a non-empty string"),
733 pub fn json_schema() -> serde_json::Value {
734 ConfigData::json_schema()
737 pub fn root_path(&self) -> &AbsPathBuf {
741 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
745 pub fn detached_files(&self) -> &[AbsPathBuf] {
752 || -> _ { Some($expr) }()
755 macro_rules! try_or {
756 ($expr:expr, $or:expr) => {
757 try_!($expr).unwrap_or($or)
761 macro_rules! try_or_def {
763 try_!($expr).unwrap_or_default()
768 pub fn linked_projects(&self) -> Vec<LinkedProject> {
769 match self.data.linkedProjects.as_slice() {
770 [] => match self.discovered_projects.as_ref() {
771 Some(discovered_projects) => {
772 let exclude_dirs: Vec<_> = self
776 .map(|p| self.root_path.join(p))
781 let (ProjectManifest::ProjectJson(path)
782 | ProjectManifest::CargoToml(path)) = p;
783 !exclude_dirs.iter().any(|p| path.starts_with(p))
786 .map(LinkedProject::from)
791 linked_projects => linked_projects
793 .filter_map(|linked_project| match linked_project {
794 ManifestOrProjectJson::Manifest(it) => {
795 let path = self.root_path.join(it);
796 ProjectManifest::from_manifest_file(path)
797 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
801 ManifestOrProjectJson::ProjectJson(it) => {
802 Some(ProjectJson::new(&self.root_path, it.clone()).into())
809 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
810 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
811 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
814 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
816 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
820 pub fn prefill_caches(&self) -> bool {
821 self.data.cachePriming_enable
824 pub fn location_link(&self) -> bool {
825 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
828 pub fn line_folding_only(&self) -> bool {
829 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
832 pub fn hierarchical_symbols(&self) -> bool {
839 .hierarchical_document_symbol_support?
843 pub fn code_action_literals(&self) -> bool {
850 .code_action_literal_support
855 pub fn work_done_progress(&self) -> bool {
856 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
859 pub fn will_rename(&self) -> bool {
860 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
863 pub fn change_annotation_support(&self) -> bool {
870 .change_annotation_support
875 pub fn code_action_resolve(&self) -> bool {
887 .any(|it| it == "edit")
890 pub fn signature_help_label_offsets(&self) -> bool {
897 .signature_information
899 .parameter_information
901 .label_offset_support?
905 pub fn completion_label_details_support(&self) -> bool {
914 .label_details_support
919 pub fn offset_encoding(&self) -> OffsetEncoding {
920 if supports_utf8(&self.caps) {
923 OffsetEncoding::Utf16
927 fn experimental(&self, index: &'static str) -> bool {
928 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
931 pub fn code_action_group(&self) -> bool {
932 self.experimental("codeActionGroup")
935 pub fn server_status_notification(&self) -> bool {
936 self.experimental("serverStatusNotification")
939 pub fn publish_diagnostics(&self) -> bool {
940 self.data.diagnostics_enable
943 pub fn diagnostics(&self) -> DiagnosticsConfig {
945 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
946 proc_macros_enabled: self.data.procMacro_enable,
947 disable_experimental: !self.data.diagnostics_experimental_enable,
948 disabled: self.data.diagnostics_disabled.clone(),
949 expr_fill_default: match self.data.assist_expressionFillDefault {
950 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
951 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
953 insert_use: self.insert_use_config(),
954 prefer_no_std: self.data.imports_prefer_no_std,
958 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
959 DiagnosticsMapConfig {
960 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
961 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
962 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
966 pub fn extra_env(&self) -> &FxHashMap<String, String> {
967 &self.data.cargo_extraEnv
970 pub fn check_on_save_extra_env(&self) -> FxHashMap<String, String> {
971 let mut extra_env = self.data.cargo_extraEnv.clone();
972 extra_env.extend(self.data.checkOnSave_extraEnv.clone());
976 pub fn lru_capacity(&self) -> Option<usize> {
977 self.data.lru_capacity
980 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
981 if !self.data.procMacro_enable {
984 let path = match &self.data.procMacro_server {
985 Some(it) => self.root_path.join(it),
986 None => AbsPathBuf::assert(std::env::current_exe().ok()?),
988 Some((path, vec!["proc-macro".into()]))
991 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
992 &self.data.procMacro_ignored
995 pub fn expand_proc_attr_macros(&self) -> bool {
996 self.data.procMacro_enable && self.data.procMacro_attributes_enable
999 pub fn files(&self) -> FilesConfig {
1001 watcher: match self.data.files_watcher {
1002 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
1003 FilesWatcher::Client
1005 _ => FilesWatcher::Server,
1007 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
1011 pub fn notifications(&self) -> NotificationsConfig {
1012 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
1015 pub fn cargo_autoreload(&self) -> bool {
1016 self.data.cargo_autoreload
1019 pub fn run_build_scripts(&self) -> bool {
1020 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
1023 pub fn cargo(&self) -> CargoConfig {
1024 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
1025 if rustc_src == "discover" {
1026 RustcSource::Discover
1028 RustcSource::Path(self.root_path.join(rustc_src))
1033 features: match &self.data.cargo_features {
1034 CargoFeaturesDef::All => CargoFeatures::All,
1035 CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
1036 features: features.clone(),
1037 no_default_features: self.data.cargo_noDefaultFeatures,
1040 target: self.data.cargo_target.clone(),
1041 no_sysroot: self.data.cargo_noSysroot,
1043 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
1044 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1045 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
1046 extra_env: self.data.cargo_extraEnv.clone(),
1050 pub fn rustfmt(&self) -> RustfmtConfig {
1051 match &self.data.rustfmt_overrideCommand {
1052 Some(args) if !args.is_empty() => {
1053 let mut args = args.clone();
1054 let command = args.remove(0);
1055 RustfmtConfig::CustomCommand { command, args }
1057 Some(_) | None => RustfmtConfig::Rustfmt {
1058 extra_args: self.data.rustfmt_extraArgs.clone(),
1059 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
1064 pub fn flycheck(&self) -> Option<FlycheckConfig> {
1065 if !self.data.checkOnSave_enable {
1068 let flycheck_config = match &self.data.checkOnSave_overrideCommand {
1069 Some(args) if !args.is_empty() => {
1070 let mut args = args.clone();
1071 let command = args.remove(0);
1072 FlycheckConfig::CustomCommand {
1075 extra_env: self.check_on_save_extra_env(),
1078 Some(_) | None => FlycheckConfig::CargoCommand {
1079 command: self.data.checkOnSave_command.clone(),
1084 .or_else(|| self.data.cargo_target.clone()),
1085 all_targets: self.data.checkOnSave_allTargets,
1086 no_default_features: self
1088 .checkOnSave_noDefaultFeatures
1089 .unwrap_or(self.data.cargo_noDefaultFeatures),
1090 all_features: matches!(
1091 self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
1092 CargoFeaturesDef::All
1094 features: match self
1096 .checkOnSave_features
1098 .unwrap_or_else(|| self.data.cargo_features.clone())
1100 CargoFeaturesDef::All => vec![],
1101 CargoFeaturesDef::Selected(it) => it,
1103 extra_args: self.data.checkOnSave_extraArgs.clone(),
1104 extra_env: self.check_on_save_extra_env(),
1107 Some(flycheck_config)
1110 pub fn runnables(&self) -> RunnablesConfig {
1112 override_cargo: self.data.runnables_command.clone(),
1113 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1117 pub fn inlay_hints(&self) -> InlayHintsConfig {
1119 render_colons: self.data.inlayHints_renderColons,
1120 type_hints: self.data.inlayHints_typeHints_enable,
1121 parameter_hints: self.data.inlayHints_parameterHints_enable,
1122 chaining_hints: self.data.inlayHints_chainingHints_enable,
1123 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1124 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1125 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1126 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1128 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1129 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1130 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1131 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1133 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1134 hide_closure_initialization_hints: self
1136 .inlayHints_typeHints_hideClosureInitialization,
1137 reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1138 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1139 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1140 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1142 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1143 param_names_for_lifetime_elision_hints: self
1145 .inlayHints_lifetimeElisionHints_useParameterNames,
1146 max_length: self.data.inlayHints_maxLength,
1147 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1148 Some(self.data.inlayHints_closingBraceHints_minLines)
1155 fn insert_use_config(&self) -> InsertUseConfig {
1157 granularity: match self.data.imports_granularity_group {
1158 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1159 ImportGranularityDef::Item => ImportGranularity::Item,
1160 ImportGranularityDef::Crate => ImportGranularity::Crate,
1161 ImportGranularityDef::Module => ImportGranularity::Module,
1163 enforce_granularity: self.data.imports_granularity_enforce,
1164 prefix_kind: match self.data.imports_prefix {
1165 ImportPrefixDef::Plain => PrefixKind::Plain,
1166 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1167 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1169 group: self.data.imports_group_enable,
1170 skip_glob_imports: !self.data.imports_merge_glob,
1174 pub fn completion(&self) -> CompletionConfig {
1176 enable_postfix_completions: self.data.completion_postfix_enable,
1177 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1178 && completion_item_edit_resolve(&self.caps),
1179 enable_self_on_the_fly: self.data.completion_autoself_enable,
1180 enable_private_editable: self.data.completion_privateEditable_enable,
1181 callable: match self.data.completion_callable_snippets {
1182 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1183 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1184 CallableCompletionDef::None => None,
1186 insert_use: self.insert_use_config(),
1187 prefer_no_std: self.data.imports_prefer_no_std,
1188 snippet_cap: SnippetCap::new(try_or_def!(
1198 snippets: self.snippets.clone(),
1202 pub fn find_all_refs_exclude_imports(&self) -> bool {
1203 self.data.references_excludeImports
1206 pub fn snippet_cap(&self) -> bool {
1207 self.experimental("snippetTextEdit")
1210 pub fn assist(&self) -> AssistConfig {
1212 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1214 insert_use: self.insert_use_config(),
1215 prefer_no_std: self.data.imports_prefer_no_std,
1219 pub fn join_lines(&self) -> JoinLinesConfig {
1221 join_else_if: self.data.joinLines_joinElseIf,
1222 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1223 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1224 join_assignments: self.data.joinLines_joinAssignments,
1228 pub fn call_info(&self) -> CallInfoConfig {
1230 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1231 docs: self.data.signatureInfo_documentation_enable,
1235 pub fn lens(&self) -> LensConfig {
1237 run: self.data.lens_enable && self.data.lens_run_enable,
1238 debug: self.data.lens_enable && self.data.lens_debug_enable,
1239 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1240 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1241 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1242 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1243 enum_variant_refs: self.data.lens_enable
1244 && self.data.lens_references_enumVariant_enable,
1245 location: self.data.lens_location,
1249 pub fn hover_actions(&self) -> HoverActionsConfig {
1250 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1251 HoverActionsConfig {
1252 implementations: enable && self.data.hover_actions_implementations_enable,
1253 references: enable && self.data.hover_actions_references_enable,
1254 run: enable && self.data.hover_actions_run_enable,
1255 debug: enable && self.data.hover_actions_debug_enable,
1256 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1260 pub fn highlighting_config(&self) -> HighlightConfig {
1262 strings: self.data.semanticHighlighting_strings_enable,
1263 punctuation: self.data.semanticHighlighting_punctuation_enable,
1264 specialize_punctuation: self
1266 .semanticHighlighting_punctuation_specialization_enable,
1267 macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang,
1268 operator: self.data.semanticHighlighting_operator_enable,
1269 specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
1270 inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable,
1271 syntactic_name_ref_highlighting: false,
1275 pub fn hover(&self) -> HoverConfig {
1277 links_in_hover: self.data.hover_links_enable,
1278 documentation: self.data.hover_documentation_enable.then(|| {
1279 let is_markdown = try_or_def!(self
1288 .contains(&MarkupKind::Markdown);
1290 HoverDocFormat::Markdown
1292 HoverDocFormat::PlainText
1295 keywords: self.data.hover_documentation_keywords_enable,
1299 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1300 WorkspaceSymbolConfig {
1301 search_scope: match self.data.workspace_symbol_search_scope {
1302 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1303 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1304 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1307 search_kind: match self.data.workspace_symbol_search_kind {
1308 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1309 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1311 search_limit: self.data.workspace_symbol_search_limit,
1315 pub fn semantic_tokens_refresh(&self) -> bool {
1316 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1319 pub fn code_lens_refresh(&self) -> bool {
1320 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1323 pub fn insert_replace_support(&self) -> bool {
1332 .insert_replace_support?
1336 pub fn client_commands(&self) -> ClientCommandsConfig {
1338 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1339 let commands: Option<lsp_ext::ClientCommandOptions> =
1340 serde_json::from_value(commands.clone()).ok();
1341 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1342 let commands = commands.map(|it| it.commands).unwrap_or_default();
1344 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1346 ClientCommandsConfig {
1347 run_single: get("rust-analyzer.runSingle"),
1348 debug_single: get("rust-analyzer.debugSingle"),
1349 show_reference: get("rust-analyzer.showReferences"),
1350 goto_location: get("rust-analyzer.gotoLocation"),
1351 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1355 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1356 HighlightRelatedConfig {
1357 references: self.data.highlightRelated_references_enable,
1358 break_points: self.data.highlightRelated_breakPoints_enable,
1359 exit_points: self.data.highlightRelated_exitPoints_enable,
1360 yield_points: self.data.highlightRelated_yieldPoints_enable,
1364 pub fn prime_caches_num_threads(&self) -> u8 {
1365 match self.data.cachePriming_numThreads {
1366 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1371 pub fn typing_autoclose_angle(&self) -> bool {
1372 self.data.typing_autoClosingAngleBrackets_enable
1375 // Deserialization definitions
1377 macro_rules! create_bool_or_string_de {
1378 ($ident:ident<$bool:literal, $string:literal>) => {
1379 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1381 D: serde::Deserializer<'de>,
1384 impl<'de> serde::de::Visitor<'de> for V {
1387 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1388 formatter.write_str(concat!(
1391 stringify!($string),
1396 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1398 E: serde::de::Error,
1402 _ => Err(serde::de::Error::invalid_value(
1403 serde::de::Unexpected::Bool(v),
1409 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1411 E: serde::de::Error,
1415 _ => Err(serde::de::Error::invalid_value(
1416 serde::de::Unexpected::Str(v),
1422 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1424 A: serde::de::EnumAccess<'de>,
1426 use serde::de::VariantAccess;
1427 let (variant, va) = a.variant::<&'de str>()?;
1431 _ => Err(serde::de::Error::invalid_value(
1432 serde::de::Unexpected::Str(variant),
1438 d.deserialize_any(V)
1442 create_bool_or_string_de!(true_or_always<true, "always">);
1443 create_bool_or_string_de!(false_or_never<false, "never">);
1445 macro_rules! named_unit_variant {
1446 ($variant:ident) => {
1447 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1449 D: serde::Deserializer<'de>,
1452 impl<'de> serde::de::Visitor<'de> for V {
1454 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1455 f.write_str(concat!("\"", stringify!($variant), "\""))
1457 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1458 if value == stringify!($variant) {
1461 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1465 deserializer.deserialize_str(V)
1471 named_unit_variant!(all);
1472 named_unit_variant!(skip_trivial);
1473 named_unit_variant!(mutable);
1474 named_unit_variant!(with_block);
1477 #[derive(Deserialize, Debug, Clone, Copy)]
1478 #[serde(rename_all = "snake_case")]
1479 enum SnippetScopeDef {
1485 impl Default for SnippetScopeDef {
1486 fn default() -> Self {
1487 SnippetScopeDef::Expr
1491 #[derive(Deserialize, Debug, Clone, Default)]
1494 #[serde(deserialize_with = "single_or_array")]
1495 prefix: Vec<String>,
1496 #[serde(deserialize_with = "single_or_array")]
1497 postfix: Vec<String>,
1498 description: Option<String>,
1499 #[serde(deserialize_with = "single_or_array")]
1501 #[serde(deserialize_with = "single_or_array")]
1502 requires: Vec<String>,
1503 scope: SnippetScopeDef,
1506 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1508 D: serde::Deserializer<'de>,
1512 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1513 type Value = Vec<String>;
1515 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1516 formatter.write_str("string or array of strings")
1519 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1521 E: serde::de::Error,
1523 Ok(vec![value.to_owned()])
1526 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1528 A: serde::de::SeqAccess<'de>,
1530 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1534 deserializer.deserialize_any(SingleOrVec)
1537 #[derive(Deserialize, Debug, Clone)]
1539 enum ManifestOrProjectJson {
1541 ProjectJson(ProjectJsonData),
1544 #[derive(Deserialize, Debug, Clone)]
1545 #[serde(rename_all = "snake_case")]
1546 enum ExprFillDefaultDef {
1551 #[derive(Deserialize, Debug, Clone)]
1552 #[serde(rename_all = "snake_case")]
1553 enum ImportGranularityDef {
1560 #[derive(Deserialize, Debug, Copy, Clone)]
1561 #[serde(rename_all = "snake_case")]
1562 enum CallableCompletionDef {
1568 #[derive(Deserialize, Debug, Clone)]
1570 enum CargoFeaturesDef {
1571 #[serde(deserialize_with = "de_unit_v::all")]
1573 Selected(Vec<String>),
1576 #[derive(Deserialize, Debug, Clone)]
1578 enum LifetimeElisionDef {
1579 #[serde(deserialize_with = "true_or_always")]
1581 #[serde(deserialize_with = "false_or_never")]
1583 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1587 #[derive(Deserialize, Debug, Clone)]
1589 enum ClosureReturnTypeHintsDef {
1590 #[serde(deserialize_with = "true_or_always")]
1592 #[serde(deserialize_with = "false_or_never")]
1594 #[serde(deserialize_with = "de_unit_v::with_block")]
1598 #[derive(Deserialize, Debug, Clone)]
1600 enum ReborrowHintsDef {
1601 #[serde(deserialize_with = "true_or_always")]
1603 #[serde(deserialize_with = "false_or_never")]
1605 #[serde(deserialize_with = "de_unit_v::mutable")]
1609 #[derive(Deserialize, Debug, Clone)]
1610 #[serde(rename_all = "snake_case")]
1611 enum FilesWatcherDef {
1617 #[derive(Deserialize, Debug, Clone)]
1618 #[serde(rename_all = "snake_case")]
1619 enum ImportPrefixDef {
1621 #[serde(alias = "self")]
1623 #[serde(alias = "crate")]
1627 #[derive(Deserialize, Debug, Clone)]
1628 #[serde(rename_all = "snake_case")]
1629 enum WorkspaceSymbolSearchScopeDef {
1631 WorkspaceAndDependencies,
1634 #[derive(Deserialize, Debug, Clone)]
1635 #[serde(rename_all = "snake_case")]
1636 enum SignatureDetail {
1641 #[derive(Deserialize, Debug, Clone)]
1642 #[serde(rename_all = "snake_case")]
1643 enum WorkspaceSymbolSearchKindDef {
1648 macro_rules! _config_data {
1649 (struct $name:ident {
1651 $(#[doc=$doc:literal])*
1652 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1655 #[allow(non_snake_case)]
1656 #[derive(Debug, Clone)]
1657 struct $name { $($field: $ty,)* }
1659 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1665 None$(.or(Some(stringify!($alias))))*,
1671 fn json_schema() -> serde_json::Value {
1674 let field = stringify!($field);
1675 let ty = stringify!($ty);
1677 (field, ty, &[$($doc),*], $default)
1683 fn manual() -> String {
1686 let field = stringify!($field);
1687 let ty = stringify!($ty);
1689 (field, ty, &[$($doc),*], $default)
1696 fn fields_are_sorted() {
1697 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1701 use _config_data as config_data;
1703 fn get_field<T: DeserializeOwned>(
1704 json: &mut serde_json::Value,
1705 error_sink: &mut Vec<(String, serde_json::Error)>,
1706 field: &'static str,
1707 alias: Option<&'static str>,
1710 let default = serde_json::from_str(default).unwrap();
1711 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1712 // defaults instead of sending an empty object.
1715 .chain(iter::once(field))
1716 .find_map(move |field| {
1717 let mut pointer = field.replace('_', "/");
1718 pointer.insert(0, '/');
1719 json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1722 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1723 error_sink.push((pointer, e));
1731 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1732 for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1733 fn key(f: &str) -> &str {
1734 f.splitn(2, '_').next().unwrap()
1736 assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1741 .map(|(field, ty, doc, default)| {
1742 let name = field.replace('_', ".");
1743 let name = format!("rust-analyzer.{}", name);
1744 let props = field_props(field, ty, doc, default);
1747 .collect::<serde_json::Map<_, _>>();
1751 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1752 let doc = doc_comment_to_string(doc);
1753 let doc = doc.trim_end_matches('\n');
1755 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1756 "bad docs for {}: {:?}",
1760 let default = default.parse::<serde_json::Value>().unwrap();
1762 let mut map = serde_json::Map::default();
1764 ($($key:literal: $value:tt),*$(,)?) => {{$(
1765 map.insert($key.into(), serde_json::json!($value));
1768 set!("markdownDescription": doc);
1769 set!("default": default);
1772 "bool" => set!("type": "boolean"),
1773 "usize" => set!("type": "integer", "minimum": 0),
1774 "String" => set!("type": "string"),
1775 "Vec<String>" => set! {
1777 "items": { "type": "string" },
1779 "Vec<PathBuf>" => set! {
1781 "items": { "type": "string" },
1783 "FxHashSet<String>" => set! {
1785 "items": { "type": "string" },
1786 "uniqueItems": true,
1788 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1791 "FxHashMap<String, SnippetDef>" => set! {
1794 "FxHashMap<String, String>" => set! {
1797 "Option<usize>" => set! {
1798 "type": ["null", "integer"],
1801 "Option<String>" => set! {
1802 "type": ["null", "string"],
1804 "Option<PathBuf>" => set! {
1805 "type": ["null", "string"],
1807 "Option<bool>" => set! {
1808 "type": ["null", "boolean"],
1810 "Option<Vec<String>>" => set! {
1811 "type": ["null", "array"],
1812 "items": { "type": "string" },
1814 "MergeBehaviorDef" => set! {
1816 "enum": ["none", "crate", "module"],
1817 "enumDescriptions": [
1818 "Do not merge imports at all.",
1819 "Merge imports from the same crate into a single `use` statement.",
1820 "Merge imports from the same module into a single `use` statement."
1823 "ExprFillDefaultDef" => set! {
1825 "enum": ["todo", "default"],
1826 "enumDescriptions": [
1827 "Fill missing expressions with the `todo` macro",
1828 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1831 "ImportGranularityDef" => set! {
1833 "enum": ["preserve", "crate", "module", "item"],
1834 "enumDescriptions": [
1835 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1836 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1837 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1838 "Flatten imports so that each has its own use statement."
1841 "ImportPrefixDef" => set! {
1848 "enumDescriptions": [
1849 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1850 "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.",
1851 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1854 "Vec<ManifestOrProjectJson>" => set! {
1856 "items": { "type": ["string", "object"] },
1858 "WorkspaceSymbolSearchScopeDef" => set! {
1860 "enum": ["workspace", "workspace_and_dependencies"],
1861 "enumDescriptions": [
1862 "Search in current workspace only.",
1863 "Search in current workspace and dependencies."
1866 "WorkspaceSymbolSearchKindDef" => set! {
1868 "enum": ["only_types", "all_symbols"],
1869 "enumDescriptions": [
1870 "Search for types only.",
1871 "Search for all symbols kinds."
1874 "ParallelCachePrimingNumThreads" => set! {
1879 "LifetimeElisionDef" => set! {
1886 "enumDescriptions": [
1887 "Always show lifetime elision hints.",
1888 "Never show lifetime elision hints.",
1889 "Only show lifetime elision hints if a return type is involved."
1892 "ClosureReturnTypeHintsDef" => set! {
1899 "enumDescriptions": [
1900 "Always show type hints for return types of closures.",
1901 "Never show type hints for return types of closures.",
1902 "Only show type hints for return types of closures with blocks."
1905 "ReborrowHintsDef" => set! {
1912 "enumDescriptions": [
1913 "Always show reborrow hints.",
1914 "Never show reborrow hints.",
1915 "Only show mutable reborrow hints."
1918 "CargoFeaturesDef" => set! {
1925 "enumDescriptions": [
1926 "Pass `--all-features` to cargo",
1931 "items": { "type": "string" }
1935 "Option<CargoFeaturesDef>" => set! {
1942 "enumDescriptions": [
1943 "Pass `--all-features` to cargo",
1948 "items": { "type": "string" }
1953 "CallableCompletionDef" => set! {
1960 "enumDescriptions": [
1961 "Add call parentheses and pre-fill arguments.",
1962 "Add call parentheses.",
1963 "Do no snippet completions for callables."
1966 "SignatureDetail" => set! {
1968 "enum": ["full", "parameters"],
1969 "enumDescriptions": [
1970 "Show the entire signature.",
1971 "Show only the parameters."
1974 "FilesWatcherDef" => set! {
1976 "enum": ["client", "server"],
1977 "enumDescriptions": [
1978 "Use the client (editor) to watch files for changes",
1979 "Use server-side file watching",
1982 "AnnotationLocation" => set! {
1984 "enum": ["above_name", "above_whole_item"],
1985 "enumDescriptions": [
1986 "Render annotations above the name of the item.",
1987 "Render annotations above the whole item, including documentation comments and attributes."
1990 _ => panic!("missing entry for {}: {}", ty, default),
1997 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
2000 .map(|(field, _ty, doc, default)| {
2001 let name = format!("rust-analyzer.{}", field.replace('_', "."));
2002 let doc = doc_comment_to_string(*doc);
2003 if default.contains('\n') {
2015 name, name, default, doc
2018 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
2021 .collect::<String>()
2024 fn doc_comment_to_string(doc: &[&str]) -> String {
2025 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
2032 use test_utils::{ensure_file_contents, project_root};
2037 fn generate_package_json_config() {
2038 let s = Config::json_schema();
2039 let schema = format!("{:#}", s);
2040 let mut schema = schema
2041 .trim_start_matches('{')
2042 .trim_end_matches('}')
2044 .replace('\n', "\n ")
2045 .trim_start_matches('\n')
2048 schema.push_str(",\n");
2050 // Transform the asciidoc form link to markdown style.
2052 // https://link[text] => [text](https://link)
2053 let url_matches = schema.match_indices("https://");
2054 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
2055 url_offsets.reverse();
2056 for idx in url_offsets {
2057 let link = &schema[idx..];
2058 // matching on whitespace to ignore normal links
2059 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
2060 if link.chars().nth(link_end) == Some('[') {
2061 if let Some(link_text_end) = link.find(']') {
2062 let link_text = link[link_end..(link_text_end + 1)].to_string();
2064 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
2065 schema.insert(idx, '(');
2066 schema.insert(idx + link_end + 1, ')');
2067 schema.insert_str(idx, &link_text);
2073 let package_json_path = project_root().join("editors/code/package.json");
2074 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
2076 let start_marker = " \"$generated-start\": {},\n";
2077 let end_marker = " \"$generated-end\": {}\n";
2079 let start = package_json.find(start_marker).unwrap() + start_marker.len();
2080 let end = package_json.find(end_marker).unwrap();
2082 let p = remove_ws(&package_json[start..end]);
2083 let s = remove_ws(&schema);
2084 if !p.contains(&s) {
2085 package_json.replace_range(start..end, &schema);
2086 ensure_file_contents(&package_json_path, &package_json)
2091 fn generate_config_documentation() {
2092 let docs_path = project_root().join("docs/user/generated_config.adoc");
2093 let expected = ConfigData::manual();
2094 ensure_file_contents(&docs_path, &expected);
2097 fn remove_ws(text: &str) -> String {
2098 text.replace(char::is_whitespace, "")