1 //! Config used by the language server.
3 //! We currently get this config from `initialize` LSP request, which is not the
4 //! best way to do it, but was the simplest thing we could implement.
6 //! Of particular interest is the `feature_flags` hash map: while other fields
7 //! configure the server itself, feature flags are passed into analysis, and
8 //! tweak things like automatic insertion of `()` in completions.
10 use std::{ffi::OsString, fmt, iter, path::PathBuf};
12 use flycheck::FlycheckConfig;
14 AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
15 HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig,
16 Snippet, SnippetScope,
19 imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
22 use itertools::Itertools;
23 use lsp_types::{ClientCapabilities, MarkupKind};
25 CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates,
27 use rustc_hash::{FxHashMap, FxHashSet};
28 use serde::{de::DeserializeOwned, Deserialize};
32 caps::completion_item_edit_resolve,
33 diagnostics::DiagnosticsMapConfig,
34 line_index::OffsetEncoding,
35 lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
40 // Conventions for configuration keys to preserve maximal extendability without breakage:
41 // - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable`
42 // This has the benefit of namespaces being extensible, and if the suffix doesn't fit later it can be changed without breakage.
43 // - In general be wary of using the namespace of something verbatim, it prevents us from adding subkeys in the future
44 // - Don't use abbreviations unless really necessary
45 // - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
47 // Defines the server-side configuration of the rust-analyzer. We generate
48 // *parts* of VS Code's `package.json` config from this. Run `cargo test` to
49 // re-generate that file.
51 // However, editor specific config, which the server doesn't know about, should
52 // be specified directly in `package.json`.
54 // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
55 // parsing the old name.
58 /// Placeholder expression to use for missing expressions in assists.
59 assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
61 /// Warm up caches on project load.
62 cachePriming_enable: bool = "true",
63 /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
64 cachePriming_numThreads: ParallelCachePrimingNumThreads = "0",
66 /// Automatically refresh project info via `cargo metadata` on
67 /// `Cargo.toml` or `.cargo/config.toml` changes.
68 cargo_autoreload: bool = "true",
69 /// Run build scripts (`build.rs`) for more precise code analysis.
70 cargo_buildScripts_enable: bool = "true",
71 /// Override the command rust-analyzer uses to run build scripts and
72 /// build procedural macros. The command is required to output json
73 /// and should therefore include `--message-format=json` or a similar
76 /// By default, a cargo invocation will be constructed for the configured
77 /// targets and features, with the following base command line:
80 /// cargo check --quiet --workspace --message-format=json --all-targets
83 cargo_buildScripts_overrideCommand: Option<Vec<String>> = "null",
84 /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
85 /// avoid checking unnecessary things.
86 cargo_buildScripts_useRustcWrapper: bool = "true",
87 /// List of features to activate.
89 /// Set this to `"all"` to pass `--all-features` to cargo.
90 cargo_features: CargoFeatures = "[]",
91 /// Whether to pass `--no-default-features` to cargo.
92 cargo_noDefaultFeatures: bool = "false",
93 /// Internal config for debugging, disables loading of sysroot crates.
94 cargo_noSysroot: bool = "false",
95 /// Compilation target override (target triple).
96 cargo_target: Option<String> = "null",
97 /// Unsets `#[cfg(test)]` for the specified crates.
98 cargo_unsetTest: Vec<String> = "[\"core\"]",
100 /// Check all targets and tests (`--all-targets`).
101 checkOnSave_allTargets: bool = "true",
102 /// Cargo command to use for `cargo check`.
103 checkOnSave_command: String = "\"check\"",
104 /// Run specified `cargo check` command for diagnostics on save.
105 checkOnSave_enable: bool = "true",
106 /// Extra arguments for `cargo check`.
107 checkOnSave_extraArgs: Vec<String> = "[]",
108 /// List of features to activate. Defaults to
109 /// `#rust-analyzer.cargo.features#`.
111 /// Set to `"all"` to pass `--all-features` to Cargo.
112 checkOnSave_features: Option<CargoFeatures> = "null",
113 /// Whether to pass `--no-default-features` to Cargo. Defaults to
114 /// `#rust-analyzer.cargo.noDefaultFeatures#`.
115 checkOnSave_noDefaultFeatures: Option<bool> = "null",
116 /// Override the command rust-analyzer uses instead of `cargo check` for
117 /// diagnostics on save. The command is required to output json and
118 /// should therefor include `--message-format=json` or a similar option.
120 /// If you're changing this because you're using some tool wrapping
121 /// Cargo, you might also want to change
122 /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
124 /// If there are multiple linked projects, this command is invoked for
125 /// each of them, with the working directory being the project root
126 /// (i.e., the folder containing the `Cargo.toml`).
128 /// An example command would be:
131 /// cargo check --workspace --message-format=json --all-targets
134 checkOnSave_overrideCommand: Option<Vec<String>> = "null",
135 /// Check for a specific target. Defaults to
136 /// `#rust-analyzer.cargo.target#`.
137 checkOnSave_target: Option<String> = "null",
139 /// Toggles the additional completions that automatically add imports when completed.
140 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
141 completion_autoimport_enable: bool = "true",
142 /// Toggles the additional completions that automatically show method calls and field accesses
143 /// with `self` prefixed to them when inside a method.
144 completion_autoself_enable: bool = "true",
145 /// Whether to add parenthesis and argument snippets when completing function.
146 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
147 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
148 completion_postfix_enable: bool = "true",
149 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
150 completion_privateEditable_enable: bool = "false",
151 /// Custom completion snippets.
152 // NOTE: Keep this list in sync with the feature docs of user snippets.
153 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
156 "body": "Arc::new(${receiver})",
157 "requires": "std::sync::Arc",
158 "description": "Put the expression into an `Arc`",
163 "body": "Rc::new(${receiver})",
164 "requires": "std::rc::Rc",
165 "description": "Put the expression into an `Rc`",
170 "body": "Box::pin(${receiver})",
171 "requires": "std::boxed::Box",
172 "description": "Put the expression into a pinned `Box`",
177 "body": "Ok(${receiver})",
178 "description": "Wrap the expression in a `Result::Ok`",
183 "body": "Err(${receiver})",
184 "description": "Wrap the expression in a `Result::Err`",
189 "body": "Some(${receiver})",
190 "description": "Wrap the expression in an `Option::Some`",
195 /// List of rust-analyzer diagnostics to disable.
196 diagnostics_disabled: FxHashSet<String> = "[]",
197 /// Whether to show native rust-analyzer diagnostics.
198 diagnostics_enable: bool = "true",
199 /// Whether to show experimental rust-analyzer diagnostics that might
200 /// have more false positives than usual.
201 diagnostics_experimental_enable: bool = "false",
202 /// Map of prefixes to be substituted when parsing diagnostic file paths.
203 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
204 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
205 /// List of warnings that should be displayed with hint severity.
207 /// The warnings will be indicated by faded text or three dots in code
208 /// and will not show up in the `Problems Panel`.
209 diagnostics_warningsAsHint: Vec<String> = "[]",
210 /// List of warnings that should be displayed with info severity.
212 /// The warnings will be indicated by a blue squiggly underline in code
213 /// and a blue icon in the `Problems Panel`.
214 diagnostics_warningsAsInfo: Vec<String> = "[]",
216 /// These directories will be ignored by rust-analyzer. They are
217 /// relative to the workspace root, and globs are not supported. You may
218 /// also need to add the folders to Code's `files.watcherExclude`.
219 files_excludeDirs: Vec<PathBuf> = "[]",
220 /// Controls file watching implementation.
221 files_watcher: FilesWatcherDef = "\"client\"",
223 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
224 highlightRelated_breakPoints_enable: bool = "true",
225 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
226 highlightRelated_exitPoints_enable: bool = "true",
227 /// Enables highlighting of related references while the cursor is on any identifier.
228 highlightRelated_references_enable: bool = "true",
229 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
230 highlightRelated_yieldPoints_enable: bool = "true",
232 /// Whether to show `Debug` action. Only applies when
233 /// `#rust-analyzer.hover.actions.enable#` is set.
234 hover_actions_debug_enable: bool = "true",
235 /// Whether to show HoverActions in Rust files.
236 hover_actions_enable: bool = "true",
237 /// Whether to show `Go to Type Definition` action. Only applies when
238 /// `#rust-analyzer.hover.actions.enable#` is set.
239 hover_actions_gotoTypeDef_enable: bool = "true",
240 /// Whether to show `Implementations` action. Only applies when
241 /// `#rust-analyzer.hover.actions.enable#` is set.
242 hover_actions_implementations_enable: bool = "true",
243 /// Whether to show `References` action. Only applies when
244 /// `#rust-analyzer.hover.actions.enable#` is set.
245 hover_actions_references_enable: bool = "false",
246 /// Whether to show `Run` action. Only applies when
247 /// `#rust-analyzer.hover.actions.enable#` is set.
248 hover_actions_run_enable: bool = "true",
250 /// Whether to show documentation on hover.
251 hover_documentation_enable: bool = "true",
252 /// Whether to show keyword hover popups. Only applies when
253 /// `#rust-analyzer.hover.documentation.enable#` is set.
254 hover_documentation_keywords_enable: bool = "true",
255 /// Use markdown syntax for links in hover.
256 hover_links_enable: bool = "true",
258 /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.
259 imports_granularity_enforce: bool = "false",
260 /// How imports should be grouped into use statements.
261 imports_granularity_group: ImportGranularityDef = "\"crate\"",
262 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
263 imports_group_enable: bool = "true",
264 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
265 imports_merge_glob: bool = "true",
266 /// The path structure for newly inserted paths to use.
267 imports_prefix: ImportPrefixDef = "\"plain\"",
269 /// Whether to show inlay type hints for binding modes.
270 inlayHints_bindingModeHints_enable: bool = "false",
271 /// Whether to show inlay type hints for method chains.
272 inlayHints_chainingHints_enable: bool = "true",
273 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
274 inlayHints_closingBraceHints_enable: bool = "true",
275 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
276 /// to always show them).
277 inlayHints_closingBraceHints_minLines: usize = "25",
278 /// Whether to show inlay type hints for return types of closures.
279 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
280 /// Whether to show inlay type hints for elided lifetimes in function signatures.
281 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
282 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
283 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
284 /// Maximum length for inlay hints. Set to null to have an unlimited length.
285 inlayHints_maxLength: Option<usize> = "25",
286 /// Whether to show function parameter name inlay hints at the call
288 inlayHints_parameterHints_enable: bool = "true",
289 /// Whether to show inlay type hints for compiler inserted reborrows.
290 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
291 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
292 inlayHints_renderColons: bool = "true",
293 /// Whether to show inlay type hints for variables.
294 inlayHints_typeHints_enable: bool = "true",
295 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
296 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
297 inlayHints_typeHints_hideClosureInitialization: bool = "false",
298 /// Whether to hide inlay type hints for constructors.
299 inlayHints_typeHints_hideNamedConstructor: bool = "false",
301 /// Join lines merges consecutive declaration and initialization of an assignment.
302 joinLines_joinAssignments: bool = "true",
303 /// Join lines inserts else between consecutive ifs.
304 joinLines_joinElseIf: bool = "true",
305 /// Join lines removes trailing commas.
306 joinLines_removeTrailingComma: bool = "true",
307 /// Join lines unwraps trivial blocks.
308 joinLines_unwrapTrivialBlock: bool = "true",
310 /// Whether to show `Debug` lens. Only applies when
311 /// `#rust-analyzer.lens.enable#` is set.
312 lens_debug_enable: bool = "true",
313 /// Whether to show CodeLens in Rust files.
314 lens_enable: bool = "true",
315 /// Internal config: use custom client-side commands even when the
316 /// client doesn't set the corresponding capability.
317 lens_forceCustomCommands: bool = "true",
318 /// Whether to show `Implementations` lens. Only applies when
319 /// `#rust-analyzer.lens.enable#` is set.
320 lens_implementations_enable: bool = "true",
321 /// Whether to show `References` lens for Struct, Enum, and Union.
322 /// Only applies when `#rust-analyzer.lens.enable#` is set.
323 lens_references_adt_enable: bool = "false",
324 /// Whether to show `References` lens for Enum Variants.
325 /// Only applies when `#rust-analyzer.lens.enable#` is set.
326 lens_references_enumVariant_enable: bool = "false",
327 /// Whether to show `Method References` lens. Only applies when
328 /// `#rust-analyzer.lens.enable#` is set.
329 lens_references_method_enable: bool = "false",
330 /// Whether to show `References` lens for Trait.
331 /// Only applies when `#rust-analyzer.lens.enable#` is set.
332 lens_references_trait_enable: bool = "false",
333 /// Whether to show `Run` lens. Only applies when
334 /// `#rust-analyzer.lens.enable#` is set.
335 lens_run_enable: bool = "true",
337 /// Disable project auto-discovery in favor of explicitly specified set
340 /// Elements must be paths pointing to `Cargo.toml`,
341 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
342 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
344 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
345 lru_capacity: Option<usize> = "null",
347 /// Whether to show `can't find Cargo.toml` error message.
348 notifications_cargoTomlNotFound: bool = "true",
350 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
351 procMacro_attributes_enable: bool = "true",
352 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
353 procMacro_enable: bool = "true",
354 /// These proc-macros will be ignored when trying to expand them.
356 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
357 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
358 /// Internal config, path to proc-macro server executable (typically,
359 /// this is rust-analyzer itself, but we override this in tests).
360 procMacro_server: Option<PathBuf> = "null",
362 /// Command to be executed instead of 'cargo' for runnables.
363 runnables_command: Option<String> = "null",
364 /// Additional arguments to be passed to cargo for runnables such as
365 /// tests or binaries. For example, it may be `--release`.
366 runnables_extraArgs: Vec<String> = "[]",
368 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
369 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
372 /// Any project which uses rust-analyzer with the rustcPrivate
373 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
375 /// This option does not take effect until rust-analyzer is restarted.
376 rustc_source: Option<String> = "null",
378 /// Additional arguments to `rustfmt`.
379 rustfmt_extraArgs: Vec<String> = "[]",
380 /// Advanced option, fully override the command rust-analyzer uses for
382 rustfmt_overrideCommand: Option<Vec<String>> = "null",
383 /// Enables the use of rustfmt's unstable range formatting command for the
384 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
385 /// available on a nightly build.
386 rustfmt_rangeFormatting_enable: bool = "false",
388 /// Use semantic tokens for strings.
390 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
391 /// By disabling semantic tokens for strings, other grammars can be used to highlight
393 semanticHighlighting_strings_enable: bool = "true",
395 /// Show full signature of the callable. Only shows parameters if disabled.
396 signatureInfo_detail: SignatureDetail = "\"full\"",
397 /// Show documentation.
398 signatureInfo_documentation_enable: bool = "true",
400 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
401 typing_autoClosingAngleBrackets_enable: bool = "false",
403 /// Workspace symbol search kind.
404 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
405 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
406 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
407 /// Other clients requires all results upfront and might require a higher limit.
408 workspace_symbol_search_limit: usize = "128",
409 /// Workspace symbol search scope.
410 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
414 impl Default for ConfigData {
415 fn default() -> Self {
416 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
420 #[derive(Debug, Clone)]
422 pub discovered_projects: Option<Vec<ProjectManifest>>,
423 caps: lsp_types::ClientCapabilities,
424 root_path: AbsPathBuf,
426 detached_files: Vec<AbsPathBuf>,
427 snippets: Vec<Snippet>,
430 type ParallelCachePrimingNumThreads = u8;
432 #[derive(Debug, Clone, Eq, PartialEq)]
433 pub enum LinkedProject {
434 ProjectManifest(ProjectManifest),
435 InlineJsonProject(ProjectJson),
438 impl From<ProjectManifest> for LinkedProject {
439 fn from(v: ProjectManifest) -> Self {
440 LinkedProject::ProjectManifest(v)
444 impl From<ProjectJson> for LinkedProject {
445 fn from(v: ProjectJson) -> Self {
446 LinkedProject::InlineJsonProject(v)
450 pub struct CallInfoConfig {
451 pub params_only: bool,
455 #[derive(Clone, Debug, PartialEq, Eq)]
456 pub struct LensConfig {
462 pub implementations: bool,
465 pub method_refs: bool,
466 pub refs_adt: bool, // for Struct, Enum, Union and Trait
467 pub refs_trait: bool, // for Struct, Enum, Union and Trait
468 pub enum_variant_refs: bool,
472 pub fn any(&self) -> bool {
475 || self.implementations
479 || self.enum_variant_refs
482 pub fn none(&self) -> bool {
486 pub fn runnable(&self) -> bool {
487 self.run || self.debug
490 pub fn references(&self) -> bool {
491 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
495 #[derive(Clone, Debug, PartialEq, Eq)]
496 pub struct HoverActionsConfig {
497 pub implementations: bool,
498 pub references: bool,
501 pub goto_type_def: bool,
504 impl HoverActionsConfig {
505 pub const NO_ACTIONS: Self = Self {
506 implementations: false,
510 goto_type_def: false,
513 pub fn any(&self) -> bool {
514 self.implementations || self.references || self.runnable() || self.goto_type_def
517 pub fn none(&self) -> bool {
521 pub fn runnable(&self) -> bool {
522 self.run || self.debug
526 #[derive(Debug, Clone)]
527 pub struct FilesConfig {
528 pub watcher: FilesWatcher,
529 pub exclude: Vec<AbsPathBuf>,
532 #[derive(Debug, Clone)]
533 pub enum FilesWatcher {
538 #[derive(Debug, Clone)]
539 pub struct NotificationsConfig {
540 pub cargo_toml_not_found: bool,
543 #[derive(Debug, Clone)]
544 pub enum RustfmtConfig {
545 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
546 CustomCommand { command: String, args: Vec<String> },
549 /// Configuration for runnable items, such as `main` function or tests.
550 #[derive(Debug, Clone)]
551 pub struct RunnablesConfig {
552 /// Custom command to be executed instead of `cargo` for runnables.
553 pub override_cargo: Option<String>,
554 /// Additional arguments for the `cargo`, e.g. `--release`.
555 pub cargo_extra_args: Vec<String>,
558 /// Configuration for workspace symbol search requests.
559 #[derive(Debug, Clone)]
560 pub struct WorkspaceSymbolConfig {
561 /// In what scope should the symbol be searched in.
562 pub search_scope: WorkspaceSymbolSearchScope,
563 /// What kind of symbol is being searched for.
564 pub search_kind: WorkspaceSymbolSearchKind,
565 /// How many items are returned at most.
566 pub search_limit: usize,
569 pub struct ClientCommandsConfig {
570 pub run_single: bool,
571 pub debug_single: bool,
572 pub show_reference: bool,
573 pub goto_location: bool,
574 pub trigger_parameter_hints: bool,
578 pub struct ConfigUpdateError {
579 errors: Vec<(String, serde_json::Error)>,
582 impl fmt::Display for ConfigUpdateError {
583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
591 "rust-analyzer found {} invalid config value{}:\n{}",
593 if self.errors.len() == 1 { "" } else { "s" },
600 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
603 data: ConfigData::default(),
604 detached_files: Vec::new(),
605 discovered_projects: None,
607 snippets: Default::default(),
611 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
612 tracing::info!("updating config from JSON: {:#}", json);
613 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
616 let mut errors = Vec::new();
617 self.detached_files =
618 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
620 .map(AbsPathBuf::assert)
622 patch_old_style::patch_json_for_outdated_configs(&mut json);
623 self.data = ConfigData::from_json(json, &mut errors);
624 tracing::debug!("deserialized config data: {:#?}", self.data);
625 self.snippets.clear();
626 for (name, def) in self.data.completion_snippets_custom.iter() {
627 if def.prefix.is_empty() && def.postfix.is_empty() {
630 let scope = match def.scope {
631 SnippetScopeDef::Expr => SnippetScope::Expr,
632 SnippetScopeDef::Type => SnippetScope::Type,
633 SnippetScopeDef::Item => SnippetScope::Item,
639 def.description.as_ref().unwrap_or(name),
643 Some(snippet) => self.snippets.push(snippet),
644 None => errors.push((
645 format!("snippet {name} is invalid"),
646 <serde_json::Error as serde::de::Error>::custom(
647 "snippet path is invalid or triggers are missing",
653 self.validate(&mut errors);
655 if errors.is_empty() {
658 Err(ConfigUpdateError { errors })
662 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
663 use serde::de::Error;
664 if self.data.checkOnSave_command.is_empty() {
666 "/checkOnSave/command".to_string(),
667 serde_json::Error::custom("expected a non-empty string"),
672 pub fn json_schema() -> serde_json::Value {
673 ConfigData::json_schema()
676 pub fn root_path(&self) -> &AbsPathBuf {
680 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
684 pub fn detached_files(&self) -> &[AbsPathBuf] {
691 || -> _ { Some($expr) }()
694 macro_rules! try_or {
695 ($expr:expr, $or:expr) => {
696 try_!($expr).unwrap_or($or)
700 macro_rules! try_or_def {
702 try_!($expr).unwrap_or_default()
707 pub fn linked_projects(&self) -> Vec<LinkedProject> {
708 match self.data.linkedProjects.as_slice() {
709 [] => match self.discovered_projects.as_ref() {
710 Some(discovered_projects) => {
711 let exclude_dirs: Vec<_> = self
715 .map(|p| self.root_path.join(p))
720 let (ProjectManifest::ProjectJson(path)
721 | ProjectManifest::CargoToml(path)) = p;
722 !exclude_dirs.iter().any(|p| path.starts_with(p))
725 .map(LinkedProject::from)
730 linked_projects => linked_projects
732 .filter_map(|linked_project| match linked_project {
733 ManifestOrProjectJson::Manifest(it) => {
734 let path = self.root_path.join(it);
735 ProjectManifest::from_manifest_file(path)
736 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
740 ManifestOrProjectJson::ProjectJson(it) => {
741 Some(ProjectJson::new(&self.root_path, it.clone()).into())
748 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
749 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
750 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
753 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
755 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
759 pub fn prefill_caches(&self) -> bool {
760 self.data.cachePriming_enable
763 pub fn location_link(&self) -> bool {
764 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
767 pub fn line_folding_only(&self) -> bool {
768 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
771 pub fn hierarchical_symbols(&self) -> bool {
778 .hierarchical_document_symbol_support?
782 pub fn code_action_literals(&self) -> bool {
789 .code_action_literal_support
794 pub fn work_done_progress(&self) -> bool {
795 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
798 pub fn will_rename(&self) -> bool {
799 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
802 pub fn change_annotation_support(&self) -> bool {
809 .change_annotation_support
814 pub fn code_action_resolve(&self) -> bool {
826 .any(|it| it == "edit")
829 pub fn signature_help_label_offsets(&self) -> bool {
836 .signature_information
838 .parameter_information
840 .label_offset_support?
844 pub fn completion_label_details_support(&self) -> bool {
853 .label_details_support
858 pub fn offset_encoding(&self) -> OffsetEncoding {
859 if supports_utf8(&self.caps) {
862 OffsetEncoding::Utf16
866 fn experimental(&self, index: &'static str) -> bool {
867 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
870 pub fn code_action_group(&self) -> bool {
871 self.experimental("codeActionGroup")
874 pub fn server_status_notification(&self) -> bool {
875 self.experimental("serverStatusNotification")
878 pub fn publish_diagnostics(&self) -> bool {
879 self.data.diagnostics_enable
882 pub fn diagnostics(&self) -> DiagnosticsConfig {
884 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
885 proc_macros_enabled: self.data.procMacro_enable,
886 disable_experimental: !self.data.diagnostics_experimental_enable,
887 disabled: self.data.diagnostics_disabled.clone(),
888 expr_fill_default: match self.data.assist_expressionFillDefault {
889 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
890 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
892 insert_use: self.insert_use_config(),
896 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
897 DiagnosticsMapConfig {
898 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
899 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
900 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
904 pub fn lru_capacity(&self) -> Option<usize> {
905 self.data.lru_capacity
908 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
909 if !self.data.procMacro_enable {
912 let path = match &self.data.procMacro_server {
913 Some(it) => self.root_path.join(it),
914 None => AbsPathBuf::assert(std::env::current_exe().ok()?),
916 Some((path, vec!["proc-macro".into()]))
919 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
920 &self.data.procMacro_ignored
923 pub fn expand_proc_attr_macros(&self) -> bool {
924 self.data.procMacro_enable && self.data.procMacro_attributes_enable
927 pub fn files(&self) -> FilesConfig {
929 watcher: match self.data.files_watcher {
930 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
933 _ => FilesWatcher::Server,
935 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
939 pub fn notifications(&self) -> NotificationsConfig {
940 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
943 pub fn cargo_autoreload(&self) -> bool {
944 self.data.cargo_autoreload
947 pub fn run_build_scripts(&self) -> bool {
948 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
951 pub fn cargo(&self) -> CargoConfig {
952 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
953 if rustc_src == "discover" {
954 RustcSource::Discover
956 RustcSource::Path(self.root_path.join(rustc_src))
961 no_default_features: self.data.cargo_noDefaultFeatures,
962 all_features: matches!(self.data.cargo_features, CargoFeatures::All),
963 features: match &self.data.cargo_features {
964 CargoFeatures::All => vec![],
965 CargoFeatures::Listed(it) => it.clone(),
967 target: self.data.cargo_target.clone(),
968 no_sysroot: self.data.cargo_noSysroot,
970 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
971 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
972 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
976 pub fn rustfmt(&self) -> RustfmtConfig {
977 match &self.data.rustfmt_overrideCommand {
978 Some(args) if !args.is_empty() => {
979 let mut args = args.clone();
980 let command = args.remove(0);
981 RustfmtConfig::CustomCommand { command, args }
983 Some(_) | None => RustfmtConfig::Rustfmt {
984 extra_args: self.data.rustfmt_extraArgs.clone(),
985 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
990 pub fn flycheck(&self) -> Option<FlycheckConfig> {
991 if !self.data.checkOnSave_enable {
994 let flycheck_config = match &self.data.checkOnSave_overrideCommand {
995 Some(args) if !args.is_empty() => {
996 let mut args = args.clone();
997 let command = args.remove(0);
998 FlycheckConfig::CustomCommand { command, args }
1000 Some(_) | None => FlycheckConfig::CargoCommand {
1001 command: self.data.checkOnSave_command.clone(),
1006 .or_else(|| self.data.cargo_target.clone()),
1007 all_targets: self.data.checkOnSave_allTargets,
1008 no_default_features: self
1010 .checkOnSave_noDefaultFeatures
1011 .unwrap_or(self.data.cargo_noDefaultFeatures),
1012 all_features: matches!(
1013 self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
1016 features: match self
1018 .checkOnSave_features
1020 .unwrap_or_else(|| self.data.cargo_features.clone())
1022 CargoFeatures::All => vec![],
1023 CargoFeatures::Listed(it) => it,
1025 extra_args: self.data.checkOnSave_extraArgs.clone(),
1028 Some(flycheck_config)
1031 pub fn runnables(&self) -> RunnablesConfig {
1033 override_cargo: self.data.runnables_command.clone(),
1034 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1038 pub fn inlay_hints(&self) -> InlayHintsConfig {
1040 render_colons: self.data.inlayHints_renderColons,
1041 type_hints: self.data.inlayHints_typeHints_enable,
1042 parameter_hints: self.data.inlayHints_parameterHints_enable,
1043 chaining_hints: self.data.inlayHints_chainingHints_enable,
1044 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1045 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1046 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1047 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1049 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1050 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1051 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1052 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1054 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1055 hide_closure_initialization_hints: self
1057 .inlayHints_typeHints_hideClosureInitialization,
1058 reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
1059 ReborrowHintsDef::Always => ide::ReborrowHints::Always,
1060 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
1061 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
1063 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1064 param_names_for_lifetime_elision_hints: self
1066 .inlayHints_lifetimeElisionHints_useParameterNames,
1067 max_length: self.data.inlayHints_maxLength,
1068 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1069 Some(self.data.inlayHints_closingBraceHints_minLines)
1076 fn insert_use_config(&self) -> InsertUseConfig {
1078 granularity: match self.data.imports_granularity_group {
1079 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1080 ImportGranularityDef::Item => ImportGranularity::Item,
1081 ImportGranularityDef::Crate => ImportGranularity::Crate,
1082 ImportGranularityDef::Module => ImportGranularity::Module,
1084 enforce_granularity: self.data.imports_granularity_enforce,
1085 prefix_kind: match self.data.imports_prefix {
1086 ImportPrefixDef::Plain => PrefixKind::Plain,
1087 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1088 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1090 group: self.data.imports_group_enable,
1091 skip_glob_imports: !self.data.imports_merge_glob,
1095 pub fn completion(&self) -> CompletionConfig {
1097 enable_postfix_completions: self.data.completion_postfix_enable,
1098 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1099 && completion_item_edit_resolve(&self.caps),
1100 enable_self_on_the_fly: self.data.completion_autoself_enable,
1101 enable_private_editable: self.data.completion_privateEditable_enable,
1102 callable: match self.data.completion_callable_snippets {
1103 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1104 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1105 CallableCompletionDef::None => None,
1107 insert_use: self.insert_use_config(),
1108 snippet_cap: SnippetCap::new(try_or_def!(
1118 snippets: self.snippets.clone(),
1122 pub fn snippet_cap(&self) -> bool {
1123 self.experimental("snippetTextEdit")
1126 pub fn assist(&self) -> AssistConfig {
1128 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1130 insert_use: self.insert_use_config(),
1134 pub fn join_lines(&self) -> JoinLinesConfig {
1136 join_else_if: self.data.joinLines_joinElseIf,
1137 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1138 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1139 join_assignments: self.data.joinLines_joinAssignments,
1143 pub fn call_info(&self) -> CallInfoConfig {
1145 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1146 docs: self.data.signatureInfo_documentation_enable,
1150 pub fn lens(&self) -> LensConfig {
1152 run: self.data.lens_enable && self.data.lens_run_enable,
1153 debug: self.data.lens_enable && self.data.lens_debug_enable,
1154 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1155 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1156 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1157 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1158 enum_variant_refs: self.data.lens_enable
1159 && self.data.lens_references_enumVariant_enable,
1163 pub fn hover_actions(&self) -> HoverActionsConfig {
1164 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1165 HoverActionsConfig {
1166 implementations: enable && self.data.hover_actions_implementations_enable,
1167 references: enable && self.data.hover_actions_references_enable,
1168 run: enable && self.data.hover_actions_run_enable,
1169 debug: enable && self.data.hover_actions_debug_enable,
1170 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1174 pub fn highlighting_strings(&self) -> bool {
1175 self.data.semanticHighlighting_strings_enable
1178 pub fn hover(&self) -> HoverConfig {
1180 links_in_hover: self.data.hover_links_enable,
1181 documentation: self.data.hover_documentation_enable.then(|| {
1182 let is_markdown = try_or_def!(self
1191 .contains(&MarkupKind::Markdown);
1193 HoverDocFormat::Markdown
1195 HoverDocFormat::PlainText
1198 keywords: self.data.hover_documentation_keywords_enable,
1202 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1203 WorkspaceSymbolConfig {
1204 search_scope: match self.data.workspace_symbol_search_scope {
1205 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1206 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1207 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1210 search_kind: match self.data.workspace_symbol_search_kind {
1211 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1212 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1214 search_limit: self.data.workspace_symbol_search_limit,
1218 pub fn semantic_tokens_refresh(&self) -> bool {
1219 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1222 pub fn code_lens_refresh(&self) -> bool {
1223 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1226 pub fn insert_replace_support(&self) -> bool {
1235 .insert_replace_support?
1239 pub fn client_commands(&self) -> ClientCommandsConfig {
1241 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1242 let commands: Option<lsp_ext::ClientCommandOptions> =
1243 serde_json::from_value(commands.clone()).ok();
1244 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1245 let commands = commands.map(|it| it.commands).unwrap_or_default();
1247 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1249 ClientCommandsConfig {
1250 run_single: get("rust-analyzer.runSingle"),
1251 debug_single: get("rust-analyzer.debugSingle"),
1252 show_reference: get("rust-analyzer.showReferences"),
1253 goto_location: get("rust-analyzer.gotoLocation"),
1254 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1258 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1259 HighlightRelatedConfig {
1260 references: self.data.highlightRelated_references_enable,
1261 break_points: self.data.highlightRelated_breakPoints_enable,
1262 exit_points: self.data.highlightRelated_exitPoints_enable,
1263 yield_points: self.data.highlightRelated_yieldPoints_enable,
1267 pub fn prime_caches_num_threads(&self) -> u8 {
1268 match self.data.cachePriming_numThreads {
1269 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1274 pub fn typing_autoclose_angle(&self) -> bool {
1275 self.data.typing_autoClosingAngleBrackets_enable
1278 // Deserialization definitions
1280 macro_rules! create_bool_or_string_de {
1281 ($ident:ident<$bool:literal, $string:literal>) => {
1282 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1284 D: serde::Deserializer<'de>,
1287 impl<'de> serde::de::Visitor<'de> for V {
1290 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1291 formatter.write_str(concat!(
1294 stringify!($string),
1299 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1301 E: serde::de::Error,
1305 _ => Err(serde::de::Error::invalid_value(
1306 serde::de::Unexpected::Bool(v),
1312 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1314 E: serde::de::Error,
1318 _ => Err(serde::de::Error::invalid_value(
1319 serde::de::Unexpected::Str(v),
1325 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1327 A: serde::de::EnumAccess<'de>,
1329 use serde::de::VariantAccess;
1330 let (variant, va) = a.variant::<&'de str>()?;
1334 _ => Err(serde::de::Error::invalid_value(
1335 serde::de::Unexpected::Str(variant),
1341 d.deserialize_any(V)
1345 create_bool_or_string_de!(true_or_always<true, "always">);
1346 create_bool_or_string_de!(false_or_never<false, "never">);
1348 macro_rules! named_unit_variant {
1349 ($variant:ident) => {
1350 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1352 D: serde::Deserializer<'de>,
1355 impl<'de> serde::de::Visitor<'de> for V {
1357 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1358 f.write_str(concat!("\"", stringify!($variant), "\""))
1360 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1361 if value == stringify!($variant) {
1364 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1368 deserializer.deserialize_str(V)
1374 named_unit_variant!(all);
1375 named_unit_variant!(skip_trivial);
1376 named_unit_variant!(mutable);
1377 named_unit_variant!(with_block);
1380 #[derive(Deserialize, Debug, Clone, Copy)]
1381 #[serde(rename_all = "snake_case")]
1382 enum SnippetScopeDef {
1388 impl Default for SnippetScopeDef {
1389 fn default() -> Self {
1390 SnippetScopeDef::Expr
1394 #[derive(Deserialize, Debug, Clone, Default)]
1397 #[serde(deserialize_with = "single_or_array")]
1398 prefix: Vec<String>,
1399 #[serde(deserialize_with = "single_or_array")]
1400 postfix: Vec<String>,
1401 description: Option<String>,
1402 #[serde(deserialize_with = "single_or_array")]
1404 #[serde(deserialize_with = "single_or_array")]
1405 requires: Vec<String>,
1406 scope: SnippetScopeDef,
1409 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1411 D: serde::Deserializer<'de>,
1415 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1416 type Value = Vec<String>;
1418 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1419 formatter.write_str("string or array of strings")
1422 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1424 E: serde::de::Error,
1426 Ok(vec![value.to_owned()])
1429 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1431 A: serde::de::SeqAccess<'de>,
1433 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1437 deserializer.deserialize_any(SingleOrVec)
1440 #[derive(Deserialize, Debug, Clone)]
1442 enum ManifestOrProjectJson {
1444 ProjectJson(ProjectJsonData),
1447 #[derive(Deserialize, Debug, Clone)]
1448 #[serde(rename_all = "snake_case")]
1449 enum ExprFillDefaultDef {
1454 #[derive(Deserialize, Debug, Clone)]
1455 #[serde(rename_all = "snake_case")]
1456 enum ImportGranularityDef {
1463 #[derive(Deserialize, Debug, Copy, Clone)]
1464 #[serde(rename_all = "snake_case")]
1465 enum CallableCompletionDef {
1471 #[derive(Deserialize, Debug, Clone)]
1473 enum CargoFeatures {
1474 #[serde(deserialize_with = "de_unit_v::all")]
1476 Listed(Vec<String>),
1479 #[derive(Deserialize, Debug, Clone)]
1481 enum LifetimeElisionDef {
1482 #[serde(deserialize_with = "true_or_always")]
1484 #[serde(deserialize_with = "false_or_never")]
1486 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1490 #[derive(Deserialize, Debug, Clone)]
1492 enum ClosureReturnTypeHintsDef {
1493 #[serde(deserialize_with = "true_or_always")]
1495 #[serde(deserialize_with = "false_or_never")]
1497 #[serde(deserialize_with = "de_unit_v::with_block")]
1501 #[derive(Deserialize, Debug, Clone)]
1503 enum ReborrowHintsDef {
1504 #[serde(deserialize_with = "true_or_always")]
1506 #[serde(deserialize_with = "false_or_never")]
1508 #[serde(deserialize_with = "de_unit_v::mutable")]
1512 #[derive(Deserialize, Debug, Clone)]
1513 #[serde(rename_all = "snake_case")]
1514 enum FilesWatcherDef {
1520 #[derive(Deserialize, Debug, Clone)]
1521 #[serde(rename_all = "snake_case")]
1522 enum ImportPrefixDef {
1524 #[serde(alias = "self")]
1526 #[serde(alias = "crate")]
1530 #[derive(Deserialize, Debug, Clone)]
1531 #[serde(rename_all = "snake_case")]
1532 enum WorkspaceSymbolSearchScopeDef {
1534 WorkspaceAndDependencies,
1537 #[derive(Deserialize, Debug, Clone)]
1538 #[serde(rename_all = "snake_case")]
1539 enum SignatureDetail {
1544 #[derive(Deserialize, Debug, Clone)]
1545 #[serde(rename_all = "snake_case")]
1546 enum WorkspaceSymbolSearchKindDef {
1551 macro_rules! _config_data {
1552 (struct $name:ident {
1554 $(#[doc=$doc:literal])*
1555 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1558 #[allow(non_snake_case)]
1559 #[derive(Debug, Clone)]
1560 struct $name { $($field: $ty,)* }
1562 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1568 None$(.or(Some(stringify!($alias))))*,
1574 fn json_schema() -> serde_json::Value {
1577 let field = stringify!($field);
1578 let ty = stringify!($ty);
1580 (field, ty, &[$($doc),*], $default)
1586 fn manual() -> String {
1589 let field = stringify!($field);
1590 let ty = stringify!($ty);
1592 (field, ty, &[$($doc),*], $default)
1599 fn fields_are_sorted() {
1600 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1604 use _config_data as config_data;
1606 fn get_field<T: DeserializeOwned>(
1607 json: &mut serde_json::Value,
1608 error_sink: &mut Vec<(String, serde_json::Error)>,
1609 field: &'static str,
1610 alias: Option<&'static str>,
1613 let default = serde_json::from_str(default).unwrap();
1614 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1615 // defaults instead of sending an empty object.
1618 .chain(iter::once(field))
1619 .find_map(move |field| {
1620 let mut pointer = field.replace('_', "/");
1621 pointer.insert(0, '/');
1622 json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
1625 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1626 error_sink.push((pointer, e));
1634 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1635 for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
1636 fn key(f: &str) -> &str {
1637 f.splitn(2, '_').next().unwrap()
1639 assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
1644 .map(|(field, ty, doc, default)| {
1645 let name = field.replace('_', ".");
1646 let name = format!("rust-analyzer.{}", name);
1647 let props = field_props(field, ty, doc, default);
1650 .collect::<serde_json::Map<_, _>>();
1654 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1655 let doc = doc_comment_to_string(doc);
1656 let doc = doc.trim_end_matches('\n');
1658 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1659 "bad docs for {}: {:?}",
1663 let default = default.parse::<serde_json::Value>().unwrap();
1665 let mut map = serde_json::Map::default();
1667 ($($key:literal: $value:tt),*$(,)?) => {{$(
1668 map.insert($key.into(), serde_json::json!($value));
1671 set!("markdownDescription": doc);
1672 set!("default": default);
1675 "bool" => set!("type": "boolean"),
1676 "usize" => set!("type": "integer", "minimum": 0),
1677 "String" => set!("type": "string"),
1678 "Vec<String>" => set! {
1680 "items": { "type": "string" },
1682 "Vec<PathBuf>" => set! {
1684 "items": { "type": "string" },
1686 "FxHashSet<String>" => set! {
1688 "items": { "type": "string" },
1689 "uniqueItems": true,
1691 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1694 "FxHashMap<String, SnippetDef>" => set! {
1697 "FxHashMap<String, String>" => set! {
1700 "Option<usize>" => set! {
1701 "type": ["null", "integer"],
1704 "Option<String>" => set! {
1705 "type": ["null", "string"],
1707 "Option<PathBuf>" => set! {
1708 "type": ["null", "string"],
1710 "Option<bool>" => set! {
1711 "type": ["null", "boolean"],
1713 "Option<Vec<String>>" => set! {
1714 "type": ["null", "array"],
1715 "items": { "type": "string" },
1717 "MergeBehaviorDef" => set! {
1719 "enum": ["none", "crate", "module"],
1720 "enumDescriptions": [
1721 "Do not merge imports at all.",
1722 "Merge imports from the same crate into a single `use` statement.",
1723 "Merge imports from the same module into a single `use` statement."
1726 "ExprFillDefaultDef" => set! {
1728 "enum": ["todo", "default"],
1729 "enumDescriptions": [
1730 "Fill missing expressions with the `todo` macro",
1731 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
1734 "ImportGranularityDef" => set! {
1736 "enum": ["preserve", "crate", "module", "item"],
1737 "enumDescriptions": [
1738 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
1739 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
1740 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
1741 "Flatten imports so that each has its own use statement."
1744 "ImportPrefixDef" => set! {
1751 "enumDescriptions": [
1752 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
1753 "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.",
1754 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
1757 "Vec<ManifestOrProjectJson>" => set! {
1759 "items": { "type": ["string", "object"] },
1761 "WorkspaceSymbolSearchScopeDef" => set! {
1763 "enum": ["workspace", "workspace_and_dependencies"],
1764 "enumDescriptions": [
1765 "Search in current workspace only.",
1766 "Search in current workspace and dependencies."
1769 "WorkspaceSymbolSearchKindDef" => set! {
1771 "enum": ["only_types", "all_symbols"],
1772 "enumDescriptions": [
1773 "Search for types only.",
1774 "Search for all symbols kinds."
1777 "ParallelCachePrimingNumThreads" => set! {
1782 "LifetimeElisionDef" => set! {
1789 "enumDescriptions": [
1790 "Always show lifetime elision hints.",
1791 "Never show lifetime elision hints.",
1792 "Only show lifetime elision hints if a return type is involved."
1795 "ClosureReturnTypeHintsDef" => set! {
1802 "enumDescriptions": [
1803 "Always show type hints for return types of closures.",
1804 "Never show type hints for return types of closures.",
1805 "Only show type hints for return types of closures with blocks."
1808 "ReborrowHintsDef" => set! {
1815 "enumDescriptions": [
1816 "Always show reborrow hints.",
1817 "Never show reborrow hints.",
1818 "Only show mutable reborrow hints."
1821 "CargoFeatures" => set! {
1828 "enumDescriptions": [
1829 "Pass `--all-features` to cargo",
1834 "items": { "type": "string" }
1838 "Option<CargoFeatures>" => set! {
1845 "enumDescriptions": [
1846 "Pass `--all-features` to cargo",
1851 "items": { "type": "string" }
1856 "CallableCompletionDef" => set! {
1863 "enumDescriptions": [
1864 "Add call parentheses and pre-fill arguments.",
1865 "Add call parentheses.",
1866 "Do no snippet completions for callables."
1869 "SignatureDetail" => set! {
1871 "enum": ["full", "parameters"],
1872 "enumDescriptions": [
1873 "Show the entire signature.",
1874 "Show only the parameters."
1877 "FilesWatcherDef" => set! {
1879 "enum": ["client", "server"],
1880 "enumDescriptions": [
1881 "Use the client (editor) to watch files for changes",
1882 "Use server-side file watching",
1885 _ => panic!("missing entry for {}: {}", ty, default),
1892 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
1895 .map(|(field, _ty, doc, default)| {
1896 let name = format!("rust-analyzer.{}", field.replace('_', "."));
1897 let doc = doc_comment_to_string(*doc);
1898 if default.contains('\n') {
1910 name, name, default, doc
1913 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
1916 .collect::<String>()
1919 fn doc_comment_to_string(doc: &[&str]) -> String {
1920 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
1927 use test_utils::{ensure_file_contents, project_root};
1932 fn generate_package_json_config() {
1933 let s = Config::json_schema();
1934 let schema = format!("{:#}", s);
1935 let mut schema = schema
1936 .trim_start_matches('{')
1937 .trim_end_matches('}')
1939 .replace('\n', "\n ")
1940 .trim_start_matches('\n')
1943 schema.push_str(",\n");
1945 // Transform the asciidoc form link to markdown style.
1947 // https://link[text] => [text](https://link)
1948 let url_matches = schema.match_indices("https://");
1949 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
1950 url_offsets.reverse();
1951 for idx in url_offsets {
1952 let link = &schema[idx..];
1953 // matching on whitespace to ignore normal links
1954 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
1955 if link.chars().nth(link_end) == Some('[') {
1956 if let Some(link_text_end) = link.find(']') {
1957 let link_text = link[link_end..(link_text_end + 1)].to_string();
1959 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
1960 schema.insert(idx, '(');
1961 schema.insert(idx + link_end + 1, ')');
1962 schema.insert_str(idx, &link_text);
1968 let package_json_path = project_root().join("editors/code/package.json");
1969 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
1971 let start_marker = " \"$generated-start\": {},\n";
1972 let end_marker = " \"$generated-end\": {}\n";
1974 let start = package_json.find(start_marker).unwrap() + start_marker.len();
1975 let end = package_json.find(end_marker).unwrap();
1977 let p = remove_ws(&package_json[start..end]);
1978 let s = remove_ws(&schema);
1979 if !p.contains(&s) {
1980 package_json.replace_range(start..end, &schema);
1981 ensure_file_contents(&package_json_path, &package_json)
1986 fn generate_config_documentation() {
1987 let docs_path = project_root().join("docs/user/generated_config.adoc");
1988 let expected = ConfigData::manual();
1989 ensure_file_contents(&docs_path, &expected);
1992 fn remove_ws(text: &str) -> String {
1993 text.replace(char::is_whitespace, "")