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 /// Extra environment variables that will be set when running cargo, rustc
88 /// or other commands within the workspace. Useful for setting RUSTFLAGS.
89 cargo_extraEnv: FxHashMap<String, String> = "{}",
90 /// List of features to activate.
92 /// Set this to `"all"` to pass `--all-features` to cargo.
93 cargo_features: CargoFeatures = "[]",
94 /// Whether to pass `--no-default-features` to cargo.
95 cargo_noDefaultFeatures: bool = "false",
96 /// Internal config for debugging, disables loading of sysroot crates.
97 cargo_noSysroot: bool = "false",
98 /// Compilation target override (target triple).
99 cargo_target: Option<String> = "null",
100 /// Unsets `#[cfg(test)]` for the specified crates.
101 cargo_unsetTest: Vec<String> = "[\"core\"]",
103 /// Check all targets and tests (`--all-targets`).
104 checkOnSave_allTargets: bool = "true",
105 /// Cargo command to use for `cargo check`.
106 checkOnSave_command: String = "\"check\"",
107 /// Run specified `cargo check` command for diagnostics on save.
108 checkOnSave_enable: bool = "true",
109 /// Extra arguments for `cargo check`.
110 checkOnSave_extraArgs: Vec<String> = "[]",
111 /// Extra environment variables that will be set when running `cargo check`.
112 checkOnSave_extraEnv: FxHashMap<String, String> = "{}",
113 /// List of features to activate. Defaults to
114 /// `#rust-analyzer.cargo.features#`.
116 /// Set to `"all"` to pass `--all-features` to Cargo.
117 checkOnSave_features: Option<CargoFeatures> = "null",
118 /// Whether to pass `--no-default-features` to Cargo. Defaults to
119 /// `#rust-analyzer.cargo.noDefaultFeatures#`.
120 checkOnSave_noDefaultFeatures: Option<bool> = "null",
121 /// Override the command rust-analyzer uses instead of `cargo check` for
122 /// diagnostics on save. The command is required to output json and
123 /// should therefor include `--message-format=json` or a similar option.
125 /// If you're changing this because you're using some tool wrapping
126 /// Cargo, you might also want to change
127 /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
129 /// If there are multiple linked projects, this command is invoked for
130 /// each of them, with the working directory being the project root
131 /// (i.e., the folder containing the `Cargo.toml`).
133 /// An example command would be:
136 /// cargo check --workspace --message-format=json --all-targets
139 checkOnSave_overrideCommand: Option<Vec<String>> = "null",
140 /// Check for a specific target. Defaults to
141 /// `#rust-analyzer.cargo.target#`.
142 checkOnSave_target: Option<String> = "null",
144 /// Toggles the additional completions that automatically add imports when completed.
145 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
146 completion_autoimport_enable: bool = "true",
147 /// Toggles the additional completions that automatically show method calls and field accesses
148 /// with `self` prefixed to them when inside a method.
149 completion_autoself_enable: bool = "true",
150 /// Whether to add parenthesis and argument snippets when completing function.
151 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
152 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
153 completion_postfix_enable: bool = "true",
154 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
155 completion_privateEditable_enable: bool = "false",
156 /// Custom completion snippets.
157 // NOTE: Keep this list in sync with the feature docs of user snippets.
158 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
161 "body": "Arc::new(${receiver})",
162 "requires": "std::sync::Arc",
163 "description": "Put the expression into an `Arc`",
168 "body": "Rc::new(${receiver})",
169 "requires": "std::rc::Rc",
170 "description": "Put the expression into an `Rc`",
175 "body": "Box::pin(${receiver})",
176 "requires": "std::boxed::Box",
177 "description": "Put the expression into a pinned `Box`",
182 "body": "Ok(${receiver})",
183 "description": "Wrap the expression in a `Result::Ok`",
188 "body": "Err(${receiver})",
189 "description": "Wrap the expression in a `Result::Err`",
194 "body": "Some(${receiver})",
195 "description": "Wrap the expression in an `Option::Some`",
200 /// List of rust-analyzer diagnostics to disable.
201 diagnostics_disabled: FxHashSet<String> = "[]",
202 /// Whether to show native rust-analyzer diagnostics.
203 diagnostics_enable: bool = "true",
204 /// Whether to show experimental rust-analyzer diagnostics that might
205 /// have more false positives than usual.
206 diagnostics_experimental_enable: bool = "false",
207 /// Map of prefixes to be substituted when parsing diagnostic file paths.
208 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
209 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
210 /// List of warnings that should be displayed with hint severity.
212 /// The warnings will be indicated by faded text or three dots in code
213 /// and will not show up in the `Problems Panel`.
214 diagnostics_warningsAsHint: Vec<String> = "[]",
215 /// List of warnings that should be displayed with info severity.
217 /// The warnings will be indicated by a blue squiggly underline in code
218 /// and a blue icon in the `Problems Panel`.
219 diagnostics_warningsAsInfo: Vec<String> = "[]",
221 /// These directories will be ignored by rust-analyzer. They are
222 /// relative to the workspace root, and globs are not supported. You may
223 /// also need to add the folders to Code's `files.watcherExclude`.
224 files_excludeDirs: Vec<PathBuf> = "[]",
225 /// Controls file watching implementation.
226 files_watcher: FilesWatcherDef = "\"client\"",
227 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
228 highlightRelated_breakPoints_enable: bool = "true",
229 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
230 highlightRelated_exitPoints_enable: bool = "true",
231 /// Enables highlighting of related references while the cursor is on any identifier.
232 highlightRelated_references_enable: bool = "true",
233 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
234 highlightRelated_yieldPoints_enable: bool = "true",
236 /// Whether to show `Debug` action. Only applies when
237 /// `#rust-analyzer.hover.actions.enable#` is set.
238 hover_actions_debug_enable: bool = "true",
239 /// Whether to show HoverActions in Rust files.
240 hover_actions_enable: bool = "true",
241 /// Whether to show `Go to Type Definition` action. Only applies when
242 /// `#rust-analyzer.hover.actions.enable#` is set.
243 hover_actions_gotoTypeDef_enable: bool = "true",
244 /// Whether to show `Implementations` action. Only applies when
245 /// `#rust-analyzer.hover.actions.enable#` is set.
246 hover_actions_implementations_enable: bool = "true",
247 /// Whether to show `References` action. Only applies when
248 /// `#rust-analyzer.hover.actions.enable#` is set.
249 hover_actions_references_enable: bool = "false",
250 /// Whether to show `Run` action. Only applies when
251 /// `#rust-analyzer.hover.actions.enable#` is set.
252 hover_actions_run_enable: bool = "true",
254 /// Whether to show documentation on hover.
255 hover_documentation_enable: bool = "true",
256 /// Whether to show keyword hover popups. Only applies when
257 /// `#rust-analyzer.hover.documentation.enable#` is set.
258 hover_documentation_keywords_enable: bool = "true",
259 /// Use markdown syntax for links in hover.
260 hover_links_enable: bool = "true",
262 /// 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.
263 imports_granularity_enforce: bool = "false",
264 /// How imports should be grouped into use statements.
265 imports_granularity_group: ImportGranularityDef = "\"crate\"",
266 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
267 imports_group_enable: bool = "true",
268 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
269 imports_merge_glob: bool = "true",
270 /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate.
271 imports_prefer_no_std: bool = "false",
272 /// The path structure for newly inserted paths to use.
273 imports_prefix: ImportPrefixDef = "\"plain\"",
275 /// Whether to show inlay type hints for binding modes.
276 inlayHints_bindingModeHints_enable: bool = "false",
277 /// Whether to show inlay type hints for method chains.
278 inlayHints_chainingHints_enable: bool = "true",
279 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
280 inlayHints_closingBraceHints_enable: bool = "true",
281 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
282 /// to always show them).
283 inlayHints_closingBraceHints_minLines: usize = "25",
284 /// Whether to show inlay type hints for return types of closures.
285 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
286 /// Whether to show inlay type hints for elided lifetimes in function signatures.
287 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
288 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
289 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
290 /// Maximum length for inlay hints. Set to null to have an unlimited length.
291 inlayHints_maxLength: Option<usize> = "25",
292 /// Whether to show function parameter name inlay hints at the call
294 inlayHints_parameterHints_enable: bool = "true",
295 /// Whether to show inlay type hints for compiler inserted reborrows.
296 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
297 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
298 inlayHints_renderColons: bool = "true",
299 /// Whether to show inlay type hints for variables.
300 inlayHints_typeHints_enable: bool = "true",
301 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
302 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
303 inlayHints_typeHints_hideClosureInitialization: bool = "false",
304 /// Whether to hide inlay type hints for constructors.
305 inlayHints_typeHints_hideNamedConstructor: bool = "false",
307 /// Join lines merges consecutive declaration and initialization of an assignment.
308 joinLines_joinAssignments: bool = "true",
309 /// Join lines inserts else between consecutive ifs.
310 joinLines_joinElseIf: bool = "true",
311 /// Join lines removes trailing commas.
312 joinLines_removeTrailingComma: bool = "true",
313 /// Join lines unwraps trivial blocks.
314 joinLines_unwrapTrivialBlock: bool = "true",
317 /// Whether to show `Debug` lens. Only applies when
318 /// `#rust-analyzer.lens.enable#` is set.
319 lens_debug_enable: bool = "true",
320 /// Whether to show CodeLens in Rust files.
321 lens_enable: bool = "true",
322 /// Internal config: use custom client-side commands even when the
323 /// client doesn't set the corresponding capability.
324 lens_forceCustomCommands: bool = "true",
325 /// Whether to show `Implementations` lens. Only applies when
326 /// `#rust-analyzer.lens.enable#` is set.
327 lens_implementations_enable: bool = "true",
328 /// Where to render annotations.
329 lens_location: AnnotationLocation = "\"above_name\"",
330 /// Whether to show `References` lens for Struct, Enum, and Union.
331 /// Only applies when `#rust-analyzer.lens.enable#` is set.
332 lens_references_adt_enable: bool = "false",
333 /// Whether to show `References` lens for Enum Variants.
334 /// Only applies when `#rust-analyzer.lens.enable#` is set.
335 lens_references_enumVariant_enable: bool = "false",
336 /// Whether to show `Method References` lens. Only applies when
337 /// `#rust-analyzer.lens.enable#` is set.
338 lens_references_method_enable: bool = "false",
339 /// Whether to show `References` lens for Trait.
340 /// Only applies when `#rust-analyzer.lens.enable#` is set.
341 lens_references_trait_enable: bool = "false",
342 /// Whether to show `Run` lens. Only applies when
343 /// `#rust-analyzer.lens.enable#` is set.
344 lens_run_enable: bool = "true",
346 /// Disable project auto-discovery in favor of explicitly specified set
349 /// Elements must be paths pointing to `Cargo.toml`,
350 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
351 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
353 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
354 lru_capacity: Option<usize> = "null",
356 /// Whether to show `can't find Cargo.toml` error message.
357 notifications_cargoTomlNotFound: bool = "true",
359 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
360 procMacro_attributes_enable: bool = "true",
361 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
362 procMacro_enable: bool = "true",
363 /// These proc-macros will be ignored when trying to expand them.
365 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
366 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
367 /// Internal config, path to proc-macro server executable (typically,
368 /// this is rust-analyzer itself, but we override this in tests).
369 procMacro_server: Option<PathBuf> = "null",
371 /// Exclude imports from find-all-references.
372 references_excludeImports: bool = "false",
374 /// Command to be executed instead of 'cargo' for runnables.
375 runnables_command: Option<String> = "null",
376 /// Additional arguments to be passed to cargo for runnables such as
377 /// tests or binaries. For example, it may be `--release`.
378 runnables_extraArgs: Vec<String> = "[]",
380 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
381 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
384 /// Any project which uses rust-analyzer with the rustcPrivate
385 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
387 /// This option does not take effect until rust-analyzer is restarted.
388 rustc_source: Option<String> = "null",
390 /// Additional arguments to `rustfmt`.
391 rustfmt_extraArgs: Vec<String> = "[]",
392 /// Advanced option, fully override the command rust-analyzer uses for
394 rustfmt_overrideCommand: Option<Vec<String>> = "null",
395 /// Enables the use of rustfmt's unstable range formatting command for the
396 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
397 /// available on a nightly build.
398 rustfmt_rangeFormatting_enable: bool = "false",
400 /// Inject additional highlighting into doc comments.
402 /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
404 semanticHighlighting_doc_comment_inject_enable: bool = "true",
405 /// Use semantic tokens for operators.
407 /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
408 /// they are tagged with modifiers.
409 semanticHighlighting_operator_enable: bool = "true",
410 /// Use specialized semantic tokens for operators.
412 /// When enabled, rust-analyzer will emit special token types for operator tokens instead
413 /// of the generic `operator` token type.
414 semanticHighlighting_operator_specialization_enable: bool = "false",
415 /// Use semantic tokens for punctuations.
417 /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
418 /// they are tagged with modifiers or have a special role.
419 semanticHighlighting_punctuation_enable: bool = "false",
420 /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
422 semanticHighlighting_punctuation_separate_macro_bang: bool = "false",
423 /// Use specialized semantic tokens for punctuations.
425 /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
426 /// of the generic `punctuation` token type.
427 semanticHighlighting_punctuation_specialization_enable: bool = "false",
428 /// Use semantic tokens for strings.
430 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
431 /// By disabling semantic tokens for strings, other grammars can be used to highlight
433 semanticHighlighting_strings_enable: bool = "true",
435 /// Show full signature of the callable. Only shows parameters if disabled.
436 signatureInfo_detail: SignatureDetail = "\"full\"",
437 /// Show documentation.
438 signatureInfo_documentation_enable: bool = "true",
440 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
441 typing_autoClosingAngleBrackets_enable: bool = "false",
443 /// Workspace symbol search kind.
444 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
445 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
446 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
447 /// Other clients requires all results upfront and might require a higher limit.
448 workspace_symbol_search_limit: usize = "128",
449 /// Workspace symbol search scope.
450 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
454 impl Default for ConfigData {
455 fn default() -> Self {
456 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
460 #[derive(Debug, Clone)]
462 pub discovered_projects: Option<Vec<ProjectManifest>>,
463 caps: lsp_types::ClientCapabilities,
464 root_path: AbsPathBuf,
466 detached_files: Vec<AbsPathBuf>,
467 snippets: Vec<Snippet>,
470 type ParallelCachePrimingNumThreads = u8;
472 #[derive(Debug, Clone, Eq, PartialEq)]
473 pub enum LinkedProject {
474 ProjectManifest(ProjectManifest),
475 InlineJsonProject(ProjectJson),
478 impl From<ProjectManifest> for LinkedProject {
479 fn from(v: ProjectManifest) -> Self {
480 LinkedProject::ProjectManifest(v)
484 impl From<ProjectJson> for LinkedProject {
485 fn from(v: ProjectJson) -> Self {
486 LinkedProject::InlineJsonProject(v)
490 pub struct CallInfoConfig {
491 pub params_only: bool,
495 #[derive(Clone, Debug, PartialEq, Eq)]
496 pub struct LensConfig {
502 pub implementations: bool,
505 pub method_refs: bool,
506 pub refs_adt: bool, // for Struct, Enum, Union and Trait
507 pub refs_trait: bool, // for Struct, Enum, Union and Trait
508 pub enum_variant_refs: bool,
511 pub location: AnnotationLocation,
514 #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
515 #[serde(rename_all = "snake_case")]
516 pub enum AnnotationLocation {
521 impl From<AnnotationLocation> for ide::AnnotationLocation {
522 fn from(location: AnnotationLocation) -> Self {
524 AnnotationLocation::AboveName => ide::AnnotationLocation::AboveName,
525 AnnotationLocation::AboveWholeItem => ide::AnnotationLocation::AboveWholeItem,
531 pub fn any(&self) -> bool {
534 || self.implementations
538 || self.enum_variant_refs
541 pub fn none(&self) -> bool {
545 pub fn runnable(&self) -> bool {
546 self.run || self.debug
549 pub fn references(&self) -> bool {
550 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
554 #[derive(Clone, Debug, PartialEq, Eq)]
555 pub struct HoverActionsConfig {
556 pub implementations: bool,
557 pub references: bool,
560 pub goto_type_def: bool,
563 impl HoverActionsConfig {
564 pub const NO_ACTIONS: Self = Self {
565 implementations: false,
569 goto_type_def: false,
572 pub fn any(&self) -> bool {
573 self.implementations || self.references || self.runnable() || self.goto_type_def
576 pub fn none(&self) -> bool {
580 pub fn runnable(&self) -> bool {
581 self.run || self.debug
585 #[derive(Debug, Clone)]
586 pub struct FilesConfig {
587 pub watcher: FilesWatcher,
588 pub exclude: Vec<AbsPathBuf>,
591 #[derive(Debug, Clone)]
592 pub enum FilesWatcher {
597 #[derive(Debug, Clone)]
598 pub struct NotificationsConfig {
599 pub cargo_toml_not_found: bool,
602 #[derive(Debug, Clone)]
603 pub enum RustfmtConfig {
604 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
605 CustomCommand { command: String, args: Vec<String> },
608 /// Configuration for runnable items, such as `main` function or tests.
609 #[derive(Debug, Clone)]
610 pub struct RunnablesConfig {
611 /// Custom command to be executed instead of `cargo` for runnables.
612 pub override_cargo: Option<String>,
613 /// Additional arguments for the `cargo`, e.g. `--release`.
614 pub cargo_extra_args: Vec<String>,
617 /// Configuration for workspace symbol search requests.
618 #[derive(Debug, Clone)]
619 pub struct WorkspaceSymbolConfig {
620 /// In what scope should the symbol be searched in.
621 pub search_scope: WorkspaceSymbolSearchScope,
622 /// What kind of symbol is being searched for.
623 pub search_kind: WorkspaceSymbolSearchKind,
624 /// How many items are returned at most.
625 pub search_limit: usize,
628 pub struct ClientCommandsConfig {
629 pub run_single: bool,
630 pub debug_single: bool,
631 pub show_reference: bool,
632 pub goto_location: bool,
633 pub trigger_parameter_hints: bool,
637 pub struct ConfigUpdateError {
638 errors: Vec<(String, serde_json::Error)>,
641 impl fmt::Display for ConfigUpdateError {
642 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
643 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
650 "rust-analyzer found {} invalid config value{}:\n{}",
652 if self.errors.len() == 1 { "" } else { "s" },
659 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
662 data: ConfigData::default(),
663 detached_files: Vec::new(),
664 discovered_projects: None,
666 snippets: Default::default(),
670 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
671 tracing::info!("updating config from JSON: {:#}", json);
672 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
675 let mut errors = Vec::new();
676 self.detached_files =
677 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
679 .map(AbsPathBuf::assert)
681 patch_old_style::patch_json_for_outdated_configs(&mut json);
682 self.data = ConfigData::from_json(json, &mut errors);
683 tracing::debug!("deserialized config data: {:#?}", self.data);
684 self.snippets.clear();
685 for (name, def) in self.data.completion_snippets_custom.iter() {
686 if def.prefix.is_empty() && def.postfix.is_empty() {
689 let scope = match def.scope {
690 SnippetScopeDef::Expr => SnippetScope::Expr,
691 SnippetScopeDef::Type => SnippetScope::Type,
692 SnippetScopeDef::Item => SnippetScope::Item,
698 def.description.as_ref().unwrap_or(name),
702 Some(snippet) => self.snippets.push(snippet),
703 None => errors.push((
704 format!("snippet {name} is invalid"),
705 <serde_json::Error as serde::de::Error>::custom(
706 "snippet path is invalid or triggers are missing",
712 self.validate(&mut errors);
714 if errors.is_empty() {
717 Err(ConfigUpdateError { errors })
721 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
722 use serde::de::Error;
723 if self.data.checkOnSave_command.is_empty() {
725 "/checkOnSave/command".to_string(),
726 serde_json::Error::custom("expected a non-empty string"),
731 pub fn json_schema() -> serde_json::Value {
732 ConfigData::json_schema()
735 pub fn root_path(&self) -> &AbsPathBuf {
739 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
743 pub fn detached_files(&self) -> &[AbsPathBuf] {
750 || -> _ { Some($expr) }()
753 macro_rules! try_or {
754 ($expr:expr, $or:expr) => {
755 try_!($expr).unwrap_or($or)
759 macro_rules! try_or_def {
761 try_!($expr).unwrap_or_default()
766 pub fn linked_projects(&self) -> Vec<LinkedProject> {
767 match self.data.linkedProjects.as_slice() {
768 [] => match self.discovered_projects.as_ref() {
769 Some(discovered_projects) => {
770 let exclude_dirs: Vec<_> = self
774 .map(|p| self.root_path.join(p))
779 let (ProjectManifest::ProjectJson(path)
780 | ProjectManifest::CargoToml(path)) = p;
781 !exclude_dirs.iter().any(|p| path.starts_with(p))
784 .map(LinkedProject::from)
789 linked_projects => linked_projects
791 .filter_map(|linked_project| match linked_project {
792 ManifestOrProjectJson::Manifest(it) => {
793 let path = self.root_path.join(it);
794 ProjectManifest::from_manifest_file(path)
795 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
799 ManifestOrProjectJson::ProjectJson(it) => {
800 Some(ProjectJson::new(&self.root_path, it.clone()).into())
807 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
808 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
809 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
812 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
814 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
818 pub fn prefill_caches(&self) -> bool {
819 self.data.cachePriming_enable
822 pub fn location_link(&self) -> bool {
823 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
826 pub fn line_folding_only(&self) -> bool {
827 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
830 pub fn hierarchical_symbols(&self) -> bool {
837 .hierarchical_document_symbol_support?
841 pub fn code_action_literals(&self) -> bool {
848 .code_action_literal_support
853 pub fn work_done_progress(&self) -> bool {
854 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
857 pub fn will_rename(&self) -> bool {
858 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
861 pub fn change_annotation_support(&self) -> bool {
868 .change_annotation_support
873 pub fn code_action_resolve(&self) -> bool {
885 .any(|it| it == "edit")
888 pub fn signature_help_label_offsets(&self) -> bool {
895 .signature_information
897 .parameter_information
899 .label_offset_support?
903 pub fn completion_label_details_support(&self) -> bool {
912 .label_details_support
917 pub fn offset_encoding(&self) -> OffsetEncoding {
918 if supports_utf8(&self.caps) {
921 OffsetEncoding::Utf16
925 fn experimental(&self, index: &'static str) -> bool {
926 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
929 pub fn code_action_group(&self) -> bool {
930 self.experimental("codeActionGroup")
933 pub fn server_status_notification(&self) -> bool {
934 self.experimental("serverStatusNotification")
937 pub fn publish_diagnostics(&self) -> bool {
938 self.data.diagnostics_enable
941 pub fn diagnostics(&self) -> DiagnosticsConfig {
943 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
944 proc_macros_enabled: self.data.procMacro_enable,
945 disable_experimental: !self.data.diagnostics_experimental_enable,
946 disabled: self.data.diagnostics_disabled.clone(),
947 expr_fill_default: match self.data.assist_expressionFillDefault {
948 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
949 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
951 insert_use: self.insert_use_config(),
952 prefer_no_std: self.data.imports_prefer_no_std,
956 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
957 DiagnosticsMapConfig {
958 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
959 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
960 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
964 pub fn extra_env(&self) -> &FxHashMap<String, String> {
965 &self.data.cargo_extraEnv
968 pub fn check_on_save_extra_env(&self) -> FxHashMap<String, String> {
969 let mut extra_env = self.data.cargo_extraEnv.clone();
970 extra_env.extend(self.data.checkOnSave_extraEnv.clone());
974 pub fn lru_capacity(&self) -> Option<usize> {
975 self.data.lru_capacity
978 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
979 if !self.data.procMacro_enable {
982 let path = match &self.data.procMacro_server {
983 Some(it) => self.root_path.join(it),
984 None => AbsPathBuf::assert(std::env::current_exe().ok()?),
986 Some((path, vec!["proc-macro".into()]))
989 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
990 &self.data.procMacro_ignored
993 pub fn expand_proc_attr_macros(&self) -> bool {
994 self.data.procMacro_enable && self.data.procMacro_attributes_enable
997 pub fn files(&self) -> FilesConfig {
999 watcher: match self.data.files_watcher {
1000 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
1001 FilesWatcher::Client
1003 _ => FilesWatcher::Server,
1005 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
1009 pub fn notifications(&self) -> NotificationsConfig {
1010 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
1013 pub fn cargo_autoreload(&self) -> bool {
1014 self.data.cargo_autoreload
1017 pub fn run_build_scripts(&self) -> bool {
1018 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
1021 pub fn cargo(&self) -> CargoConfig {
1022 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
1023 if rustc_src == "discover" {
1024 RustcSource::Discover
1026 RustcSource::Path(self.root_path.join(rustc_src))
1031 no_default_features: self.data.cargo_noDefaultFeatures,
1032 all_features: matches!(self.data.cargo_features, CargoFeatures::All),
1033 features: match &self.data.cargo_features {
1034 CargoFeatures::All => vec![],
1035 CargoFeatures::Listed(it) => it.clone(),
1037 target: self.data.cargo_target.clone(),
1038 no_sysroot: self.data.cargo_noSysroot,
1040 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
1041 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1042 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
1043 extra_env: self.data.cargo_extraEnv.clone(),
1047 pub fn rustfmt(&self) -> RustfmtConfig {
1048 match &self.data.rustfmt_overrideCommand {
1049 Some(args) if !args.is_empty() => {
1050 let mut args = args.clone();
1051 let command = args.remove(0);
1052 RustfmtConfig::CustomCommand { command, args }
1054 Some(_) | None => RustfmtConfig::Rustfmt {
1055 extra_args: self.data.rustfmt_extraArgs.clone(),
1056 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
1061 pub fn flycheck(&self) -> Option<FlycheckConfig> {
1062 if !self.data.checkOnSave_enable {
1065 let flycheck_config = match &self.data.checkOnSave_overrideCommand {
1066 Some(args) if !args.is_empty() => {
1067 let mut args = args.clone();
1068 let command = args.remove(0);
1069 FlycheckConfig::CustomCommand {
1072 extra_env: self.check_on_save_extra_env(),
1075 Some(_) | None => FlycheckConfig::CargoCommand {
1076 command: self.data.checkOnSave_command.clone(),
1081 .or_else(|| self.data.cargo_target.clone()),
1082 all_targets: self.data.checkOnSave_allTargets,
1083 no_default_features: self
1085 .checkOnSave_noDefaultFeatures
1086 .unwrap_or(self.data.cargo_noDefaultFeatures),
1087 all_features: matches!(
1088 self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
1091 features: match self
1093 .checkOnSave_features
1095 .unwrap_or_else(|| self.data.cargo_features.clone())
1097 CargoFeatures::All => vec![],
1098 CargoFeatures::Listed(it) => it,
1100 extra_args: self.data.checkOnSave_extraArgs.clone(),
1101 extra_env: self.check_on_save_extra_env(),
1104 Some(flycheck_config)
1107 pub fn runnables(&self) -> RunnablesConfig {
1109 override_cargo: self.data.runnables_command.clone(),
1110 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1114 pub fn inlay_hints(&self) -> InlayHintsConfig {
1116 render_colons: self.data.inlayHints_renderColons,
1117 type_hints: self.data.inlayHints_typeHints_enable,
1118 parameter_hints: self.data.inlayHints_parameterHints_enable,
1119 chaining_hints: self.data.inlayHints_chainingHints_enable,
1120 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1121 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1122 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1123 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1125 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1126 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1127 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1128 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1130 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1131 hide_closure_initialization_hints: self
1133 .inlayHints_typeHints_hideClosureInitialization,
1134 reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1135 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1136 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1137 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1139 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1140 param_names_for_lifetime_elision_hints: self
1142 .inlayHints_lifetimeElisionHints_useParameterNames,
1143 max_length: self.data.inlayHints_maxLength,
1144 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1145 Some(self.data.inlayHints_closingBraceHints_minLines)
1152 fn insert_use_config(&self) -> InsertUseConfig {
1154 granularity: match self.data.imports_granularity_group {
1155 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1156 ImportGranularityDef::Item => ImportGranularity::Item,
1157 ImportGranularityDef::Crate => ImportGranularity::Crate,
1158 ImportGranularityDef::Module => ImportGranularity::Module,
1160 enforce_granularity: self.data.imports_granularity_enforce,
1161 prefix_kind: match self.data.imports_prefix {
1162 ImportPrefixDef::Plain => PrefixKind::Plain,
1163 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1164 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1166 group: self.data.imports_group_enable,
1167 skip_glob_imports: !self.data.imports_merge_glob,
1171 pub fn completion(&self) -> CompletionConfig {
1173 enable_postfix_completions: self.data.completion_postfix_enable,
1174 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1175 && completion_item_edit_resolve(&self.caps),
1176 enable_self_on_the_fly: self.data.completion_autoself_enable,
1177 enable_private_editable: self.data.completion_privateEditable_enable,
1178 callable: match self.data.completion_callable_snippets {
1179 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1180 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1181 CallableCompletionDef::None => None,
1183 insert_use: self.insert_use_config(),
1184 prefer_no_std: self.data.imports_prefer_no_std,
1185 snippet_cap: SnippetCap::new(try_or_def!(
1195 snippets: self.snippets.clone(),
1199 pub fn find_all_refs_exclude_imports(&self) -> bool {
1200 self.data.references_excludeImports
1203 pub fn snippet_cap(&self) -> bool {
1204 self.experimental("snippetTextEdit")
1207 pub fn assist(&self) -> AssistConfig {
1209 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1211 insert_use: self.insert_use_config(),
1212 prefer_no_std: self.data.imports_prefer_no_std,
1216 pub fn join_lines(&self) -> JoinLinesConfig {
1218 join_else_if: self.data.joinLines_joinElseIf,
1219 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1220 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1221 join_assignments: self.data.joinLines_joinAssignments,
1225 pub fn call_info(&self) -> CallInfoConfig {
1227 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1228 docs: self.data.signatureInfo_documentation_enable,
1232 pub fn lens(&self) -> LensConfig {
1234 run: self.data.lens_enable && self.data.lens_run_enable,
1235 debug: self.data.lens_enable && self.data.lens_debug_enable,
1236 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1237 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1238 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1239 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1240 enum_variant_refs: self.data.lens_enable
1241 && self.data.lens_references_enumVariant_enable,
1242 location: self.data.lens_location,
1246 pub fn hover_actions(&self) -> HoverActionsConfig {
1247 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1248 HoverActionsConfig {
1249 implementations: enable && self.data.hover_actions_implementations_enable,
1250 references: enable && self.data.hover_actions_references_enable,
1251 run: enable && self.data.hover_actions_run_enable,
1252 debug: enable && self.data.hover_actions_debug_enable,
1253 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1257 pub fn highlighting_config(&self) -> HighlightConfig {
1259 strings: self.data.semanticHighlighting_strings_enable,
1260 punctuation: self.data.semanticHighlighting_punctuation_enable,
1261 specialize_punctuation: self
1263 .semanticHighlighting_punctuation_specialization_enable,
1264 macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang,
1265 operator: self.data.semanticHighlighting_operator_enable,
1266 specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
1267 inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable,
1268 syntactic_name_ref_highlighting: false,
1272 pub fn hover(&self) -> HoverConfig {
1274 links_in_hover: self.data.hover_links_enable,
1275 documentation: self.data.hover_documentation_enable.then(|| {
1276 let is_markdown = try_or_def!(self
1285 .contains(&MarkupKind::Markdown);
1287 HoverDocFormat::Markdown
1289 HoverDocFormat::PlainText
1292 keywords: self.data.hover_documentation_keywords_enable,
1296 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1297 WorkspaceSymbolConfig {
1298 search_scope: match self.data.workspace_symbol_search_scope {
1299 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1300 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1301 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1304 search_kind: match self.data.workspace_symbol_search_kind {
1305 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1306 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1308 search_limit: self.data.workspace_symbol_search_limit,
1312 pub fn semantic_tokens_refresh(&self) -> bool {
1313 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1316 pub fn code_lens_refresh(&self) -> bool {
1317 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1320 pub fn insert_replace_support(&self) -> bool {
1329 .insert_replace_support?
1333 pub fn client_commands(&self) -> ClientCommandsConfig {
1335 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1336 let commands: Option<lsp_ext::ClientCommandOptions> =
1337 serde_json::from_value(commands.clone()).ok();
1338 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1339 let commands = commands.map(|it| it.commands).unwrap_or_default();
1341 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1343 ClientCommandsConfig {
1344 run_single: get("rust-analyzer.runSingle"),
1345 debug_single: get("rust-analyzer.debugSingle"),
1346 show_reference: get("rust-analyzer.showReferences"),
1347 goto_location: get("rust-analyzer.gotoLocation"),
1348 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1352 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1353 HighlightRelatedConfig {
1354 references: self.data.highlightRelated_references_enable,
1355 break_points: self.data.highlightRelated_breakPoints_enable,
1356 exit_points: self.data.highlightRelated_exitPoints_enable,
1357 yield_points: self.data.highlightRelated_yieldPoints_enable,
1361 pub fn prime_caches_num_threads(&self) -> u8 {
1362 match self.data.cachePriming_numThreads {
1363 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1368 pub fn typing_autoclose_angle(&self) -> bool {
1369 self.data.typing_autoClosingAngleBrackets_enable
1372 // Deserialization definitions
1374 macro_rules! create_bool_or_string_de {
1375 ($ident:ident<$bool:literal, $string:literal>) => {
1376 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1378 D: serde::Deserializer<'de>,
1381 impl<'de> serde::de::Visitor<'de> for V {
1384 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1385 formatter.write_str(concat!(
1388 stringify!($string),
1393 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1395 E: serde::de::Error,
1399 _ => Err(serde::de::Error::invalid_value(
1400 serde::de::Unexpected::Bool(v),
1406 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1408 E: serde::de::Error,
1412 _ => Err(serde::de::Error::invalid_value(
1413 serde::de::Unexpected::Str(v),
1419 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1421 A: serde::de::EnumAccess<'de>,
1423 use serde::de::VariantAccess;
1424 let (variant, va) = a.variant::<&'de str>()?;
1428 _ => Err(serde::de::Error::invalid_value(
1429 serde::de::Unexpected::Str(variant),
1435 d.deserialize_any(V)
1439 create_bool_or_string_de!(true_or_always<true, "always">);
1440 create_bool_or_string_de!(false_or_never<false, "never">);
1442 macro_rules! named_unit_variant {
1443 ($variant:ident) => {
1444 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1446 D: serde::Deserializer<'de>,
1449 impl<'de> serde::de::Visitor<'de> for V {
1451 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1452 f.write_str(concat!("\"", stringify!($variant), "\""))
1454 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1455 if value == stringify!($variant) {
1458 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1462 deserializer.deserialize_str(V)
1468 named_unit_variant!(all);
1469 named_unit_variant!(skip_trivial);
1470 named_unit_variant!(mutable);
1471 named_unit_variant!(with_block);
1474 #[derive(Deserialize, Debug, Clone, Copy)]
1475 #[serde(rename_all = "snake_case")]
1476 enum SnippetScopeDef {
1482 impl Default for SnippetScopeDef {
1483 fn default() -> Self {
1484 SnippetScopeDef::Expr
1488 #[derive(Deserialize, Debug, Clone, Default)]
1491 #[serde(deserialize_with = "single_or_array")]
1492 prefix: Vec<String>,
1493 #[serde(deserialize_with = "single_or_array")]
1494 postfix: Vec<String>,
1495 description: Option<String>,
1496 #[serde(deserialize_with = "single_or_array")]
1498 #[serde(deserialize_with = "single_or_array")]
1499 requires: Vec<String>,
1500 scope: SnippetScopeDef,
1503 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1505 D: serde::Deserializer<'de>,
1509 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1510 type Value = Vec<String>;
1512 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1513 formatter.write_str("string or array of strings")
1516 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1518 E: serde::de::Error,
1520 Ok(vec![value.to_owned()])
1523 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1525 A: serde::de::SeqAccess<'de>,
1527 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1531 deserializer.deserialize_any(SingleOrVec)
1534 #[derive(Deserialize, Debug, Clone)]
1536 enum ManifestOrProjectJson {
1538 ProjectJson(ProjectJsonData),
1541 #[derive(Deserialize, Debug, Clone)]
1542 #[serde(rename_all = "snake_case")]
1543 enum ExprFillDefaultDef {
1548 #[derive(Deserialize, Debug, Clone)]
1549 #[serde(rename_all = "snake_case")]
1550 enum ImportGranularityDef {
1557 #[derive(Deserialize, Debug, Copy, Clone)]
1558 #[serde(rename_all = "snake_case")]
1559 enum CallableCompletionDef {
1565 #[derive(Deserialize, Debug, Clone)]
1567 enum CargoFeatures {
1568 #[serde(deserialize_with = "de_unit_v::all")]
1570 Listed(Vec<String>),
1573 #[derive(Deserialize, Debug, Clone)]
1575 enum LifetimeElisionDef {
1576 #[serde(deserialize_with = "true_or_always")]
1578 #[serde(deserialize_with = "false_or_never")]
1580 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1584 #[derive(Deserialize, Debug, Clone)]
1586 enum ClosureReturnTypeHintsDef {
1587 #[serde(deserialize_with = "true_or_always")]
1589 #[serde(deserialize_with = "false_or_never")]
1591 #[serde(deserialize_with = "de_unit_v::with_block")]
1595 #[derive(Deserialize, Debug, Clone)]
1597 enum ReborrowHintsDef {
1598 #[serde(deserialize_with = "true_or_always")]
1600 #[serde(deserialize_with = "false_or_never")]
1602 #[serde(deserialize_with = "de_unit_v::mutable")]
1606 #[derive(Deserialize, Debug, Clone)]
1607 #[serde(rename_all = "snake_case")]
1608 enum FilesWatcherDef {
1614 #[derive(Deserialize, Debug, Clone)]
1615 #[serde(rename_all = "snake_case")]
1616 enum ImportPrefixDef {
1618 #[serde(alias = "self")]
1620 #[serde(alias = "crate")]
1624 #[derive(Deserialize, Debug, Clone)]
1625 #[serde(rename_all = "snake_case")]
1626 enum WorkspaceSymbolSearchScopeDef {
1628 WorkspaceAndDependencies,
1631 #[derive(Deserialize, Debug, Clone)]
1632 #[serde(rename_all = "snake_case")]
1633 enum SignatureDetail {
1638 #[derive(Deserialize, Debug, Clone)]
1639 #[serde(rename_all = "snake_case")]
1640 enum WorkspaceSymbolSearchKindDef {
1645 macro_rules! _config_data {
1646 (struct $name:ident {
1648 $(#[doc=$doc:literal])*
1649 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1652 #[allow(non_snake_case)]
1653 #[derive(Debug, Clone)]
1654 struct $name { $($field: $ty,)* }
1656 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1662 None$(.or(Some(stringify!($alias))))*,
1668 fn json_schema() -> serde_json::Value {
1671 let field = stringify!($field);
1672 let ty = stringify!($ty);
1674 (field, ty, &[$($doc),*], $default)
1680 fn manual() -> String {
1683 let field = stringify!($field);
1684 let ty = stringify!($ty);
1686 (field, ty, &[$($doc),*], $default)
1693 fn fields_are_sorted() {
1694 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1698 use _config_data as config_data;
1700 fn get_field<T: DeserializeOwned>(
1701 json: &mut serde_json::Value,
1702 error_sink: &mut Vec<(String, serde_json::Error)>,
1703 field: &'static str,
1704 alias: Option<&'static str>,
1707 let default = serde_json::from_str(default).unwrap();
1708 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1709 // defaults instead of sending an empty object.
1712 .chain(iter::once(field))
1713 .find_map(move |field| {
1714 let mut pointer = field.replace('_', "/");
1715 pointer.insert(0, '/');
1716 json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1719 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1720 error_sink.push((pointer, e));
1728 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1729 for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1730 fn key(f: &str) -> &str {
1731 f.splitn(2, '_').next().unwrap()
1733 assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1738 .map(|(field, ty, doc, default)| {
1739 let name = field.replace('_', ".");
1740 let name = format!("rust-analyzer.{}", name);
1741 let props = field_props(field, ty, doc, default);
1744 .collect::<serde_json::Map<_, _>>();
1748 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1749 let doc = doc_comment_to_string(doc);
1750 let doc = doc.trim_end_matches('\n');
1752 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1753 "bad docs for {}: {:?}",
1757 let default = default.parse::<serde_json::Value>().unwrap();
1759 let mut map = serde_json::Map::default();
1761 ($($key:literal: $value:tt),*$(,)?) => {{$(
1762 map.insert($key.into(), serde_json::json!($value));
1765 set!("markdownDescription": doc);
1766 set!("default": default);
1769 "bool" => set!("type": "boolean"),
1770 "usize" => set!("type": "integer", "minimum": 0),
1771 "String" => set!("type": "string"),
1772 "Vec<String>" => set! {
1774 "items": { "type": "string" },
1776 "Vec<PathBuf>" => set! {
1778 "items": { "type": "string" },
1780 "FxHashSet<String>" => set! {
1782 "items": { "type": "string" },
1783 "uniqueItems": true,
1785 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1788 "FxHashMap<String, SnippetDef>" => set! {
1791 "FxHashMap<String, String>" => set! {
1794 "Option<usize>" => set! {
1795 "type": ["null", "integer"],
1798 "Option<String>" => set! {
1799 "type": ["null", "string"],
1801 "Option<PathBuf>" => set! {
1802 "type": ["null", "string"],
1804 "Option<bool>" => set! {
1805 "type": ["null", "boolean"],
1807 "Option<Vec<String>>" => set! {
1808 "type": ["null", "array"],
1809 "items": { "type": "string" },
1811 "MergeBehaviorDef" => set! {
1813 "enum": ["none", "crate", "module"],
1814 "enumDescriptions": [
1815 "Do not merge imports at all.",
1816 "Merge imports from the same crate into a single `use` statement.",
1817 "Merge imports from the same module into a single `use` statement."
1820 "ExprFillDefaultDef" => set! {
1822 "enum": ["todo", "default"],
1823 "enumDescriptions": [
1824 "Fill missing expressions with the `todo` macro",
1825 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1828 "ImportGranularityDef" => set! {
1830 "enum": ["preserve", "crate", "module", "item"],
1831 "enumDescriptions": [
1832 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1833 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1834 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1835 "Flatten imports so that each has its own use statement."
1838 "ImportPrefixDef" => set! {
1845 "enumDescriptions": [
1846 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1847 "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.",
1848 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1851 "Vec<ManifestOrProjectJson>" => set! {
1853 "items": { "type": ["string", "object"] },
1855 "WorkspaceSymbolSearchScopeDef" => set! {
1857 "enum": ["workspace", "workspace_and_dependencies"],
1858 "enumDescriptions": [
1859 "Search in current workspace only.",
1860 "Search in current workspace and dependencies."
1863 "WorkspaceSymbolSearchKindDef" => set! {
1865 "enum": ["only_types", "all_symbols"],
1866 "enumDescriptions": [
1867 "Search for types only.",
1868 "Search for all symbols kinds."
1871 "ParallelCachePrimingNumThreads" => set! {
1876 "LifetimeElisionDef" => set! {
1883 "enumDescriptions": [
1884 "Always show lifetime elision hints.",
1885 "Never show lifetime elision hints.",
1886 "Only show lifetime elision hints if a return type is involved."
1889 "ClosureReturnTypeHintsDef" => set! {
1896 "enumDescriptions": [
1897 "Always show type hints for return types of closures.",
1898 "Never show type hints for return types of closures.",
1899 "Only show type hints for return types of closures with blocks."
1902 "ReborrowHintsDef" => set! {
1909 "enumDescriptions": [
1910 "Always show reborrow hints.",
1911 "Never show reborrow hints.",
1912 "Only show mutable reborrow hints."
1915 "CargoFeatures" => set! {
1922 "enumDescriptions": [
1923 "Pass `--all-features` to cargo",
1928 "items": { "type": "string" }
1932 "Option<CargoFeatures>" => set! {
1939 "enumDescriptions": [
1940 "Pass `--all-features` to cargo",
1945 "items": { "type": "string" }
1950 "CallableCompletionDef" => set! {
1957 "enumDescriptions": [
1958 "Add call parentheses and pre-fill arguments.",
1959 "Add call parentheses.",
1960 "Do no snippet completions for callables."
1963 "SignatureDetail" => set! {
1965 "enum": ["full", "parameters"],
1966 "enumDescriptions": [
1967 "Show the entire signature.",
1968 "Show only the parameters."
1971 "FilesWatcherDef" => set! {
1973 "enum": ["client", "server"],
1974 "enumDescriptions": [
1975 "Use the client (editor) to watch files for changes",
1976 "Use server-side file watching",
1979 "AnnotationLocation" => set! {
1981 "enum": ["above_name", "above_whole_item"],
1982 "enumDescriptions": [
1983 "Render annotations above the name of the item.",
1984 "Render annotations above the whole item, including documentation comments and attributes."
1987 _ => panic!("missing entry for {}: {}", ty, default),
1994 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1997 .map(|(field, _ty, doc, default)| {
1998 let name = format!("rust-analyzer.{}", field.replace('_', "."));
1999 let doc = doc_comment_to_string(*doc);
2000 if default.contains('\n') {
2012 name, name, default, doc
2015 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
2018 .collect::<String>()
2021 fn doc_comment_to_string(doc: &[&str]) -> String {
2022 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
2029 use test_utils::{ensure_file_contents, project_root};
2034 fn generate_package_json_config() {
2035 let s = Config::json_schema();
2036 let schema = format!("{:#}", s);
2037 let mut schema = schema
2038 .trim_start_matches('{')
2039 .trim_end_matches('}')
2041 .replace('\n', "\n ")
2042 .trim_start_matches('\n')
2045 schema.push_str(",\n");
2047 // Transform the asciidoc form link to markdown style.
2049 // https://link[text] => [text](https://link)
2050 let url_matches = schema.match_indices("https://");
2051 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
2052 url_offsets.reverse();
2053 for idx in url_offsets {
2054 let link = &schema[idx..];
2055 // matching on whitespace to ignore normal links
2056 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
2057 if link.chars().nth(link_end) == Some('[') {
2058 if let Some(link_text_end) = link.find(']') {
2059 let link_text = link[link_end..(link_text_end + 1)].to_string();
2061 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
2062 schema.insert(idx, '(');
2063 schema.insert(idx + link_end + 1, ')');
2064 schema.insert_str(idx, &link_text);
2070 let package_json_path = project_root().join("editors/code/package.json");
2071 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
2073 let start_marker = " \"$generated-start\": {},\n";
2074 let end_marker = " \"$generated-end\": {}\n";
2076 let start = package_json.find(start_marker).unwrap() + start_marker.len();
2077 let end = package_json.find(end_marker).unwrap();
2079 let p = remove_ws(&package_json[start..end]);
2080 let s = remove_ws(&schema);
2081 if !p.contains(&s) {
2082 package_json.replace_range(start..end, &schema);
2083 ensure_file_contents(&package_json_path, &package_json)
2088 fn generate_config_documentation() {
2089 let docs_path = project_root().join("docs/user/generated_config.adoc");
2090 let expected = ConfigData::manual();
2091 ensure_file_contents(&docs_path, &expected);
2094 fn remove_ws(text: &str) -> String {
2095 text.replace(char::is_whitespace, "")