]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / rust-analyzer / src / config.rs
1 //! Config used by the language server.
2 //!
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.
5 //!
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.
9
10 use std::{fmt, iter, path::PathBuf};
11
12 use flycheck::FlycheckConfig;
13 use ide::{
14 AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
15 HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
16 JoinLinesConfig, Snippet, SnippetScope,
17 };
18 use ide_db::{
19 imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
20 SnippetCap,
21 };
22 use itertools::Itertools;
23 use lsp_types::{ClientCapabilities, MarkupKind};
24 use project_model::{
25 CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource,
26 UnsetTestCrates,
27 };
28 use rustc_hash::{FxHashMap, FxHashSet};
29 use serde::{de::DeserializeOwned, Deserialize};
30 use vfs::AbsPathBuf;
31
32 use crate::{
33 caps::completion_item_edit_resolve,
34 diagnostics::DiagnosticsMapConfig,
35 line_index::PositionEncoding,
36 lsp_ext::{self, negotiated_encoding, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
37 };
38
39 mod patch_old_style;
40
41 // Conventions for configuration keys to preserve maximal extendability without breakage:
42 // - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable`
43 // This has the benefit of namespaces being extensible, and if the suffix doesn't fit later it can be changed without breakage.
44 // - In general be wary of using the namespace of something verbatim, it prevents us from adding subkeys in the future
45 // - Don't use abbreviations unless really necessary
46 // - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
47
48 // Defines the server-side configuration of the rust-analyzer. We generate
49 // *parts* of VS Code's `package.json` config from this. Run `cargo test` to
50 // re-generate that file.
51 //
52 // However, editor specific config, which the server doesn't know about, should
53 // be specified directly in `package.json`.
54 //
55 // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
56 // parsing the old name.
57 config_data! {
58 struct ConfigData {
59 /// Whether to insert #[must_use] when generating `as_` methods
60 /// for enum variants.
61 assist_emitMustUse: bool = "false",
62 /// Placeholder expression to use for missing expressions in assists.
63 assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
64
65 /// Warm up caches on project load.
66 cachePriming_enable: bool = "true",
67 /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
68 cachePriming_numThreads: ParallelCachePrimingNumThreads = "0",
69
70 /// Automatically refresh project info via `cargo metadata` on
71 /// `Cargo.toml` or `.cargo/config.toml` changes.
72 cargo_autoreload: bool = "true",
73 /// Run build scripts (`build.rs`) for more precise code analysis.
74 cargo_buildScripts_enable: bool = "true",
75 /// Specifies the working directory for running build scripts.
76 /// - "workspace": run build scripts for a workspace in the workspace's root directory.
77 /// This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.
78 /// - "root": run build scripts in the project's root directory.
79 /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
80 /// is set.
81 cargo_buildScripts_invocationLocation: InvocationLocation = "\"workspace\"",
82 /// Specifies the invocation strategy to use when running the build scripts command.
83 /// If `per_workspace` is set, the command will be executed for each workspace.
84 /// If `once` is set, the command will be executed once.
85 /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
86 /// is set.
87 cargo_buildScripts_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
88 /// Override the command rust-analyzer uses to run build scripts and
89 /// build procedural macros. The command is required to output json
90 /// and should therefore include `--message-format=json` or a similar
91 /// option.
92 ///
93 /// By default, a cargo invocation will be constructed for the configured
94 /// targets and features, with the following base command line:
95 ///
96 /// ```bash
97 /// cargo check --quiet --workspace --message-format=json --all-targets
98 /// ```
99 /// .
100 cargo_buildScripts_overrideCommand: Option<Vec<String>> = "null",
101 /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
102 /// avoid checking unnecessary things.
103 cargo_buildScripts_useRustcWrapper: bool = "true",
104 /// Extra arguments that are passed to every cargo invocation.
105 cargo_extraArgs: Vec<String> = "[]",
106 /// Extra environment variables that will be set when running cargo, rustc
107 /// or other commands within the workspace. Useful for setting RUSTFLAGS.
108 cargo_extraEnv: FxHashMap<String, String> = "{}",
109 /// List of features to activate.
110 ///
111 /// Set this to `"all"` to pass `--all-features` to cargo.
112 cargo_features: CargoFeaturesDef = "[]",
113 /// Whether to pass `--no-default-features` to cargo.
114 cargo_noDefaultFeatures: bool = "false",
115 /// Relative path to the sysroot, or "discover" to try to automatically find it via
116 /// "rustc --print sysroot".
117 ///
118 /// Unsetting this disables sysroot loading.
119 ///
120 /// This option does not take effect until rust-analyzer is restarted.
121 cargo_sysroot: Option<String> = "\"discover\"",
122 /// Relative path to the sysroot library sources. If left unset, this will default to
123 /// `{cargo.sysroot}/lib/rustlib/src/rust/library`.
124 ///
125 /// This option does not take effect until rust-analyzer is restarted.
126 cargo_sysrootSrc: Option<String> = "null",
127 /// Compilation target override (target triple).
128 // FIXME(@poliorcetics): move to multiple targets here too, but this will need more work
129 // than `checkOnSave_target`
130 cargo_target: Option<String> = "null",
131 /// Unsets `#[cfg(test)]` for the specified crates.
132 cargo_unsetTest: Vec<String> = "[\"core\"]",
133
134 /// Run the check command for diagnostics on save.
135 checkOnSave | checkOnSave_enable: bool = "true",
136
137 /// Check all targets and tests (`--all-targets`).
138 check_allTargets | checkOnSave_allTargets: bool = "true",
139 /// Cargo command to use for `cargo check`.
140 check_command | checkOnSave_command: String = "\"check\"",
141 /// Extra arguments for `cargo check`.
142 check_extraArgs | checkOnSave_extraArgs: Vec<String> = "[]",
143 /// Extra environment variables that will be set when running `cargo check`.
144 /// Extends `#rust-analyzer.cargo.extraEnv#`.
145 check_extraEnv | checkOnSave_extraEnv: FxHashMap<String, String> = "{}",
146 /// List of features to activate. Defaults to
147 /// `#rust-analyzer.cargo.features#`.
148 ///
149 /// Set to `"all"` to pass `--all-features` to Cargo.
150 check_features | checkOnSave_features: Option<CargoFeaturesDef> = "null",
151 /// Specifies the working directory for running checks.
152 /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories.
153 // FIXME: Ideally we would support this in some way
154 /// This falls back to "root" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`.
155 /// - "root": run checks in the project's root directory.
156 /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
157 /// is set.
158 check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"",
159 /// Specifies the invocation strategy to use when running the checkOnSave command.
160 /// If `per_workspace` is set, the command will be executed for each workspace.
161 /// If `once` is set, the command will be executed once.
162 /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
163 /// is set.
164 check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
165 /// Whether to pass `--no-default-features` to Cargo. Defaults to
166 /// `#rust-analyzer.cargo.noDefaultFeatures#`.
167 check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool> = "null",
168 /// Override the command rust-analyzer uses instead of `cargo check` for
169 /// diagnostics on save. The command is required to output json and
170 /// should therefore include `--message-format=json` or a similar option
171 /// (if your client supports the `colorDiagnosticOutput` experimental
172 /// capability, you can use `--message-format=json-diagnostic-rendered-ansi`).
173 ///
174 /// If you're changing this because you're using some tool wrapping
175 /// Cargo, you might also want to change
176 /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
177 ///
178 /// If there are multiple linked projects, this command is invoked for
179 /// each of them, with the working directory being the project root
180 /// (i.e., the folder containing the `Cargo.toml`).
181 ///
182 /// An example command would be:
183 ///
184 /// ```bash
185 /// cargo check --workspace --message-format=json --all-targets
186 /// ```
187 /// .
188 check_overrideCommand | checkOnSave_overrideCommand: Option<Vec<String>> = "null",
189 /// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.
190 ///
191 /// Can be a single target, e.g. `"x86_64-unknown-linux-gnu"` or a list of targets, e.g.
192 /// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`.
193 ///
194 /// Aliased as `"checkOnSave.targets"`.
195 check_targets | checkOnSave_targets | checkOnSave_target: Option<CheckOnSaveTargets> = "null",
196
197 /// Toggles the additional completions that automatically add imports when completed.
198 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
199 completion_autoimport_enable: bool = "true",
200 /// Toggles the additional completions that automatically show method calls and field accesses
201 /// with `self` prefixed to them when inside a method.
202 completion_autoself_enable: bool = "true",
203 /// Whether to add parenthesis and argument snippets when completing function.
204 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
205 /// Maximum number of completions to return. If `None`, the limit is infinite.
206 completion_limit: Option<usize> = "null",
207 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
208 completion_postfix_enable: bool = "true",
209 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
210 completion_privateEditable_enable: bool = "false",
211 /// Custom completion snippets.
212 // NOTE: Keep this list in sync with the feature docs of user snippets.
213 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
214 "Arc::new": {
215 "postfix": "arc",
216 "body": "Arc::new(${receiver})",
217 "requires": "std::sync::Arc",
218 "description": "Put the expression into an `Arc`",
219 "scope": "expr"
220 },
221 "Rc::new": {
222 "postfix": "rc",
223 "body": "Rc::new(${receiver})",
224 "requires": "std::rc::Rc",
225 "description": "Put the expression into an `Rc`",
226 "scope": "expr"
227 },
228 "Box::pin": {
229 "postfix": "pinbox",
230 "body": "Box::pin(${receiver})",
231 "requires": "std::boxed::Box",
232 "description": "Put the expression into a pinned `Box`",
233 "scope": "expr"
234 },
235 "Ok": {
236 "postfix": "ok",
237 "body": "Ok(${receiver})",
238 "description": "Wrap the expression in a `Result::Ok`",
239 "scope": "expr"
240 },
241 "Err": {
242 "postfix": "err",
243 "body": "Err(${receiver})",
244 "description": "Wrap the expression in a `Result::Err`",
245 "scope": "expr"
246 },
247 "Some": {
248 "postfix": "some",
249 "body": "Some(${receiver})",
250 "description": "Wrap the expression in an `Option::Some`",
251 "scope": "expr"
252 }
253 }"#,
254
255 /// List of rust-analyzer diagnostics to disable.
256 diagnostics_disabled: FxHashSet<String> = "[]",
257 /// Whether to show native rust-analyzer diagnostics.
258 diagnostics_enable: bool = "true",
259 /// Whether to show experimental rust-analyzer diagnostics that might
260 /// have more false positives than usual.
261 diagnostics_experimental_enable: bool = "false",
262 /// Map of prefixes to be substituted when parsing diagnostic file paths.
263 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
264 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
265 /// List of warnings that should be displayed with hint severity.
266 ///
267 /// The warnings will be indicated by faded text or three dots in code
268 /// and will not show up in the `Problems Panel`.
269 diagnostics_warningsAsHint: Vec<String> = "[]",
270 /// List of warnings that should be displayed with info severity.
271 ///
272 /// The warnings will be indicated by a blue squiggly underline in code
273 /// and a blue icon in the `Problems Panel`.
274 diagnostics_warningsAsInfo: Vec<String> = "[]",
275 /// These directories will be ignored by rust-analyzer. They are
276 /// relative to the workspace root, and globs are not supported. You may
277 /// also need to add the folders to Code's `files.watcherExclude`.
278 files_excludeDirs: Vec<PathBuf> = "[]",
279 /// Controls file watching implementation.
280 files_watcher: FilesWatcherDef = "\"client\"",
281
282 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
283 highlightRelated_breakPoints_enable: bool = "true",
284 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
285 highlightRelated_exitPoints_enable: bool = "true",
286 /// Enables highlighting of related references while the cursor is on any identifier.
287 highlightRelated_references_enable: bool = "true",
288 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
289 highlightRelated_yieldPoints_enable: bool = "true",
290
291 /// Whether to show `Debug` action. Only applies when
292 /// `#rust-analyzer.hover.actions.enable#` is set.
293 hover_actions_debug_enable: bool = "true",
294 /// Whether to show HoverActions in Rust files.
295 hover_actions_enable: bool = "true",
296 /// Whether to show `Go to Type Definition` action. Only applies when
297 /// `#rust-analyzer.hover.actions.enable#` is set.
298 hover_actions_gotoTypeDef_enable: bool = "true",
299 /// Whether to show `Implementations` action. Only applies when
300 /// `#rust-analyzer.hover.actions.enable#` is set.
301 hover_actions_implementations_enable: bool = "true",
302 /// Whether to show `References` action. Only applies when
303 /// `#rust-analyzer.hover.actions.enable#` is set.
304 hover_actions_references_enable: bool = "false",
305 /// Whether to show `Run` action. Only applies when
306 /// `#rust-analyzer.hover.actions.enable#` is set.
307 hover_actions_run_enable: bool = "true",
308
309 /// Whether to show documentation on hover.
310 hover_documentation_enable: bool = "true",
311 /// Whether to show keyword hover popups. Only applies when
312 /// `#rust-analyzer.hover.documentation.enable#` is set.
313 hover_documentation_keywords_enable: bool = "true",
314 /// Use markdown syntax for links in hover.
315 hover_links_enable: bool = "true",
316
317 /// 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.
318 imports_granularity_enforce: bool = "false",
319 /// How imports should be grouped into use statements.
320 imports_granularity_group: ImportGranularityDef = "\"crate\"",
321 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
322 imports_group_enable: bool = "true",
323 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
324 imports_merge_glob: bool = "true",
325 /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate.
326 imports_prefer_no_std: bool = "false",
327 /// The path structure for newly inserted paths to use.
328 imports_prefix: ImportPrefixDef = "\"plain\"",
329
330 /// Whether to show inlay type hints for binding modes.
331 inlayHints_bindingModeHints_enable: bool = "false",
332 /// Whether to show inlay type hints for method chains.
333 inlayHints_chainingHints_enable: bool = "true",
334 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
335 inlayHints_closingBraceHints_enable: bool = "true",
336 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
337 /// to always show them).
338 inlayHints_closingBraceHints_minLines: usize = "25",
339 /// Whether to show inlay type hints for return types of closures.
340 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
341 /// Whether to show enum variant discriminant hints.
342 inlayHints_discriminantHints_enable: DiscriminantHintsDef = "\"never\"",
343 /// Whether to show inlay hints for type adjustments.
344 inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"",
345 /// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks.
346 inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false",
347 /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc).
348 inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"",
349 /// Whether to show inlay type hints for elided lifetimes in function signatures.
350 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
351 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
352 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
353 /// Maximum length for inlay hints. Set to null to have an unlimited length.
354 inlayHints_maxLength: Option<usize> = "25",
355 /// Whether to show function parameter name inlay hints at the call
356 /// site.
357 inlayHints_parameterHints_enable: bool = "true",
358 /// Whether to show inlay hints for compiler inserted reborrows.
359 /// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#.
360 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
361 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
362 inlayHints_renderColons: bool = "true",
363 /// Whether to show inlay type hints for variables.
364 inlayHints_typeHints_enable: bool = "true",
365 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
366 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
367 inlayHints_typeHints_hideClosureInitialization: bool = "false",
368 /// Whether to hide inlay type hints for constructors.
369 inlayHints_typeHints_hideNamedConstructor: bool = "false",
370 /// Enables the experimental support for interpreting tests.
371 interpret_tests: bool = "false",
372
373 /// Join lines merges consecutive declaration and initialization of an assignment.
374 joinLines_joinAssignments: bool = "true",
375 /// Join lines inserts else between consecutive ifs.
376 joinLines_joinElseIf: bool = "true",
377 /// Join lines removes trailing commas.
378 joinLines_removeTrailingComma: bool = "true",
379 /// Join lines unwraps trivial blocks.
380 joinLines_unwrapTrivialBlock: bool = "true",
381
382
383 /// Whether to show `Debug` lens. Only applies when
384 /// `#rust-analyzer.lens.enable#` is set.
385 lens_debug_enable: bool = "true",
386 /// Whether to show CodeLens in Rust files.
387 lens_enable: bool = "true",
388 /// Internal config: use custom client-side commands even when the
389 /// client doesn't set the corresponding capability.
390 lens_forceCustomCommands: bool = "true",
391 /// Whether to show `Implementations` lens. Only applies when
392 /// `#rust-analyzer.lens.enable#` is set.
393 lens_implementations_enable: bool = "true",
394 /// Where to render annotations.
395 lens_location: AnnotationLocation = "\"above_name\"",
396 /// Whether to show `References` lens for Struct, Enum, and Union.
397 /// Only applies when `#rust-analyzer.lens.enable#` is set.
398 lens_references_adt_enable: bool = "false",
399 /// Whether to show `References` lens for Enum Variants.
400 /// Only applies when `#rust-analyzer.lens.enable#` is set.
401 lens_references_enumVariant_enable: bool = "false",
402 /// Whether to show `Method References` lens. Only applies when
403 /// `#rust-analyzer.lens.enable#` is set.
404 lens_references_method_enable: bool = "false",
405 /// Whether to show `References` lens for Trait.
406 /// Only applies when `#rust-analyzer.lens.enable#` is set.
407 lens_references_trait_enable: bool = "false",
408 /// Whether to show `Run` lens. Only applies when
409 /// `#rust-analyzer.lens.enable#` is set.
410 lens_run_enable: bool = "true",
411
412 /// Disable project auto-discovery in favor of explicitly specified set
413 /// of projects.
414 ///
415 /// Elements must be paths pointing to `Cargo.toml`,
416 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
417 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
418
419 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
420 lru_capacity: Option<usize> = "null",
421
422 /// Whether to show `can't find Cargo.toml` error message.
423 notifications_cargoTomlNotFound: bool = "true",
424
425 /// How many worker threads in the main loop. The default `null` means to pick automatically.
426 numThreads: Option<usize> = "null",
427
428 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
429 procMacro_attributes_enable: bool = "true",
430 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
431 procMacro_enable: bool = "true",
432 /// These proc-macros will be ignored when trying to expand them.
433 ///
434 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
435 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
436 /// Internal config, path to proc-macro server executable (typically,
437 /// this is rust-analyzer itself, but we override this in tests).
438 procMacro_server: Option<PathBuf> = "null",
439
440 /// Exclude imports from find-all-references.
441 references_excludeImports: bool = "false",
442
443 /// Command to be executed instead of 'cargo' for runnables.
444 runnables_command: Option<String> = "null",
445 /// Additional arguments to be passed to cargo for runnables such as
446 /// tests or binaries. For example, it may be `--release`.
447 runnables_extraArgs: Vec<String> = "[]",
448
449 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
450 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
451 /// is installed.
452 ///
453 /// Any project which uses rust-analyzer with the rustcPrivate
454 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
455 ///
456 /// This option does not take effect until rust-analyzer is restarted.
457 rustc_source: Option<String> = "null",
458
459 /// Additional arguments to `rustfmt`.
460 rustfmt_extraArgs: Vec<String> = "[]",
461 /// Advanced option, fully override the command rust-analyzer uses for
462 /// formatting. This should be the equivalent of `rustfmt` here, and
463 /// not that of `cargo fmt`. The file contents will be passed on the
464 /// standard input and the formatted result will be read from the
465 /// standard output.
466 rustfmt_overrideCommand: Option<Vec<String>> = "null",
467 /// Enables the use of rustfmt's unstable range formatting command for the
468 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
469 /// available on a nightly build.
470 rustfmt_rangeFormatting_enable: bool = "false",
471
472 /// Inject additional highlighting into doc comments.
473 ///
474 /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
475 /// doc links.
476 semanticHighlighting_doc_comment_inject_enable: bool = "true",
477 /// Use semantic tokens for operators.
478 ///
479 /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
480 /// they are tagged with modifiers.
481 semanticHighlighting_operator_enable: bool = "true",
482 /// Use specialized semantic tokens for operators.
483 ///
484 /// When enabled, rust-analyzer will emit special token types for operator tokens instead
485 /// of the generic `operator` token type.
486 semanticHighlighting_operator_specialization_enable: bool = "false",
487 /// Use semantic tokens for punctuations.
488 ///
489 /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
490 /// they are tagged with modifiers or have a special role.
491 semanticHighlighting_punctuation_enable: bool = "false",
492 /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
493 /// calls.
494 semanticHighlighting_punctuation_separate_macro_bang: bool = "false",
495 /// Use specialized semantic tokens for punctuations.
496 ///
497 /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
498 /// of the generic `punctuation` token type.
499 semanticHighlighting_punctuation_specialization_enable: bool = "false",
500 /// Use semantic tokens for strings.
501 ///
502 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
503 /// By disabling semantic tokens for strings, other grammars can be used to highlight
504 /// their contents.
505 semanticHighlighting_strings_enable: bool = "true",
506
507 /// Show full signature of the callable. Only shows parameters if disabled.
508 signatureInfo_detail: SignatureDetail = "\"full\"",
509 /// Show documentation.
510 signatureInfo_documentation_enable: bool = "true",
511
512 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
513 typing_autoClosingAngleBrackets_enable: bool = "false",
514
515 /// Workspace symbol search kind.
516 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
517 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
518 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
519 /// Other clients requires all results upfront and might require a higher limit.
520 workspace_symbol_search_limit: usize = "128",
521 /// Workspace symbol search scope.
522 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
523 }
524 }
525
526 impl Default for ConfigData {
527 fn default() -> Self {
528 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
529 }
530 }
531
532 #[derive(Debug, Clone)]
533 pub struct Config {
534 pub discovered_projects: Option<Vec<ProjectManifest>>,
535 pub workspace_roots: Vec<AbsPathBuf>,
536 caps: lsp_types::ClientCapabilities,
537 root_path: AbsPathBuf,
538 data: ConfigData,
539 detached_files: Vec<AbsPathBuf>,
540 snippets: Vec<Snippet>,
541 }
542
543 type ParallelCachePrimingNumThreads = u8;
544
545 #[derive(Debug, Clone, Eq, PartialEq)]
546 pub enum LinkedProject {
547 ProjectManifest(ProjectManifest),
548 InlineJsonProject(ProjectJson),
549 }
550
551 impl From<ProjectManifest> for LinkedProject {
552 fn from(v: ProjectManifest) -> Self {
553 LinkedProject::ProjectManifest(v)
554 }
555 }
556
557 impl From<ProjectJson> for LinkedProject {
558 fn from(v: ProjectJson) -> Self {
559 LinkedProject::InlineJsonProject(v)
560 }
561 }
562
563 pub struct CallInfoConfig {
564 pub params_only: bool,
565 pub docs: bool,
566 }
567
568 #[derive(Clone, Debug, PartialEq, Eq)]
569 pub struct LensConfig {
570 // runnables
571 pub run: bool,
572 pub debug: bool,
573
574 // implementations
575 pub implementations: bool,
576
577 // references
578 pub method_refs: bool,
579 pub refs_adt: bool, // for Struct, Enum, Union and Trait
580 pub refs_trait: bool, // for Struct, Enum, Union and Trait
581 pub enum_variant_refs: bool,
582
583 // annotations
584 pub location: AnnotationLocation,
585 }
586
587 #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
588 #[serde(rename_all = "snake_case")]
589 pub enum AnnotationLocation {
590 AboveName,
591 AboveWholeItem,
592 }
593
594 impl From<AnnotationLocation> for ide::AnnotationLocation {
595 fn from(location: AnnotationLocation) -> Self {
596 match location {
597 AnnotationLocation::AboveName => ide::AnnotationLocation::AboveName,
598 AnnotationLocation::AboveWholeItem => ide::AnnotationLocation::AboveWholeItem,
599 }
600 }
601 }
602
603 impl LensConfig {
604 pub fn any(&self) -> bool {
605 self.run
606 || self.debug
607 || self.implementations
608 || self.method_refs
609 || self.refs_adt
610 || self.refs_trait
611 || self.enum_variant_refs
612 }
613
614 pub fn none(&self) -> bool {
615 !self.any()
616 }
617
618 pub fn runnable(&self) -> bool {
619 self.run || self.debug
620 }
621
622 pub fn references(&self) -> bool {
623 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
624 }
625 }
626
627 #[derive(Clone, Debug, PartialEq, Eq)]
628 pub struct HoverActionsConfig {
629 pub implementations: bool,
630 pub references: bool,
631 pub run: bool,
632 pub debug: bool,
633 pub goto_type_def: bool,
634 }
635
636 impl HoverActionsConfig {
637 pub const NO_ACTIONS: Self = Self {
638 implementations: false,
639 references: false,
640 run: false,
641 debug: false,
642 goto_type_def: false,
643 };
644
645 pub fn any(&self) -> bool {
646 self.implementations || self.references || self.runnable() || self.goto_type_def
647 }
648
649 pub fn none(&self) -> bool {
650 !self.any()
651 }
652
653 pub fn runnable(&self) -> bool {
654 self.run || self.debug
655 }
656 }
657
658 #[derive(Debug, Clone)]
659 pub struct FilesConfig {
660 pub watcher: FilesWatcher,
661 pub exclude: Vec<AbsPathBuf>,
662 }
663
664 #[derive(Debug, Clone)]
665 pub enum FilesWatcher {
666 Client,
667 Server,
668 }
669
670 #[derive(Debug, Clone)]
671 pub struct NotificationsConfig {
672 pub cargo_toml_not_found: bool,
673 }
674
675 #[derive(Debug, Clone)]
676 pub enum RustfmtConfig {
677 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
678 CustomCommand { command: String, args: Vec<String> },
679 }
680
681 /// Configuration for runnable items, such as `main` function or tests.
682 #[derive(Debug, Clone)]
683 pub struct RunnablesConfig {
684 /// Custom command to be executed instead of `cargo` for runnables.
685 pub override_cargo: Option<String>,
686 /// Additional arguments for the `cargo`, e.g. `--release`.
687 pub cargo_extra_args: Vec<String>,
688 }
689
690 /// Configuration for workspace symbol search requests.
691 #[derive(Debug, Clone)]
692 pub struct WorkspaceSymbolConfig {
693 /// In what scope should the symbol be searched in.
694 pub search_scope: WorkspaceSymbolSearchScope,
695 /// What kind of symbol is being searched for.
696 pub search_kind: WorkspaceSymbolSearchKind,
697 /// How many items are returned at most.
698 pub search_limit: usize,
699 }
700
701 pub struct ClientCommandsConfig {
702 pub run_single: bool,
703 pub debug_single: bool,
704 pub show_reference: bool,
705 pub goto_location: bool,
706 pub trigger_parameter_hints: bool,
707 }
708
709 #[derive(Debug)]
710 pub struct ConfigUpdateError {
711 errors: Vec<(String, serde_json::Error)>,
712 }
713
714 impl fmt::Display for ConfigUpdateError {
715 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
716 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
717 f(key)?;
718 f(&": ")?;
719 f(e)
720 });
721 write!(
722 f,
723 "rust-analyzer found {} invalid config value{}:\n{}",
724 self.errors.len(),
725 if self.errors.len() == 1 { "" } else { "s" },
726 errors
727 )
728 }
729 }
730
731 impl Config {
732 pub fn new(
733 root_path: AbsPathBuf,
734 caps: ClientCapabilities,
735 workspace_roots: Vec<AbsPathBuf>,
736 ) -> Self {
737 Config {
738 caps,
739 data: ConfigData::default(),
740 detached_files: Vec::new(),
741 discovered_projects: None,
742 root_path,
743 snippets: Default::default(),
744 workspace_roots,
745 }
746 }
747
748 pub fn rediscover_workspaces(&mut self) {
749 let discovered = ProjectManifest::discover_all(&self.workspace_roots);
750 tracing::info!("discovered projects: {:?}", discovered);
751 if discovered.is_empty() {
752 tracing::error!("failed to find any projects in {:?}", &self.workspace_roots);
753 }
754 self.discovered_projects = Some(discovered);
755 }
756
757 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
758 tracing::info!("updating config from JSON: {:#}", json);
759 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
760 return Ok(());
761 }
762 let mut errors = Vec::new();
763 self.detached_files =
764 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
765 .into_iter()
766 .map(AbsPathBuf::assert)
767 .collect();
768 patch_old_style::patch_json_for_outdated_configs(&mut json);
769 self.data = ConfigData::from_json(json, &mut errors);
770 tracing::debug!("deserialized config data: {:#?}", self.data);
771 self.snippets.clear();
772 for (name, def) in self.data.completion_snippets_custom.iter() {
773 if def.prefix.is_empty() && def.postfix.is_empty() {
774 continue;
775 }
776 let scope = match def.scope {
777 SnippetScopeDef::Expr => SnippetScope::Expr,
778 SnippetScopeDef::Type => SnippetScope::Type,
779 SnippetScopeDef::Item => SnippetScope::Item,
780 };
781 match Snippet::new(
782 &def.prefix,
783 &def.postfix,
784 &def.body,
785 def.description.as_ref().unwrap_or(name),
786 &def.requires,
787 scope,
788 ) {
789 Some(snippet) => self.snippets.push(snippet),
790 None => errors.push((
791 format!("snippet {name} is invalid"),
792 <serde_json::Error as serde::de::Error>::custom(
793 "snippet path is invalid or triggers are missing",
794 ),
795 )),
796 }
797 }
798
799 self.validate(&mut errors);
800
801 if errors.is_empty() {
802 Ok(())
803 } else {
804 Err(ConfigUpdateError { errors })
805 }
806 }
807
808 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
809 use serde::de::Error;
810 if self.data.check_command.is_empty() {
811 error_sink.push((
812 "/check/command".to_string(),
813 serde_json::Error::custom("expected a non-empty string"),
814 ));
815 }
816 }
817
818 pub fn json_schema() -> serde_json::Value {
819 ConfigData::json_schema()
820 }
821
822 pub fn root_path(&self) -> &AbsPathBuf {
823 &self.root_path
824 }
825
826 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
827 &self.caps
828 }
829
830 pub fn detached_files(&self) -> &[AbsPathBuf] {
831 &self.detached_files
832 }
833 }
834
835 macro_rules! try_ {
836 ($expr:expr) => {
837 || -> _ { Some($expr) }()
838 };
839 }
840 macro_rules! try_or {
841 ($expr:expr, $or:expr) => {
842 try_!($expr).unwrap_or($or)
843 };
844 }
845
846 macro_rules! try_or_def {
847 ($expr:expr) => {
848 try_!($expr).unwrap_or_default()
849 };
850 }
851
852 impl Config {
853 pub fn has_linked_projects(&self) -> bool {
854 !self.data.linkedProjects.is_empty()
855 }
856 pub fn linked_projects(&self) -> Vec<LinkedProject> {
857 match self.data.linkedProjects.as_slice() {
858 [] => {
859 match self.discovered_projects.as_ref() {
860 Some(discovered_projects) => {
861 let exclude_dirs: Vec<_> = self
862 .data
863 .files_excludeDirs
864 .iter()
865 .map(|p| self.root_path.join(p))
866 .collect();
867 discovered_projects
868 .iter()
869 .filter(|(ProjectManifest::ProjectJson(path) | ProjectManifest::CargoToml(path))| {
870 !exclude_dirs.iter().any(|p| path.starts_with(p))
871 })
872 .cloned()
873 .map(LinkedProject::from)
874 .collect()
875 }
876 None => Vec::new(),
877 }
878 }
879 linked_projects => linked_projects
880 .iter()
881 .filter_map(|linked_project| match linked_project {
882 ManifestOrProjectJson::Manifest(it) => {
883 let path = self.root_path.join(it);
884 ProjectManifest::from_manifest_file(path)
885 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
886 .ok()
887 .map(Into::into)
888 }
889 ManifestOrProjectJson::ProjectJson(it) => {
890 Some(ProjectJson::new(&self.root_path, it.clone()).into())
891 }
892 })
893 .collect(),
894 }
895 }
896
897 pub fn add_linked_projects(&mut self, linked_projects: Vec<ProjectJsonData>) {
898 let mut linked_projects = linked_projects
899 .into_iter()
900 .map(ManifestOrProjectJson::ProjectJson)
901 .collect::<Vec<ManifestOrProjectJson>>();
902
903 self.data.linkedProjects.append(&mut linked_projects);
904 }
905
906 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
907 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
908 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
909 }
910
911 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
912 try_or_def!(
913 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
914 )
915 }
916
917 pub fn prefill_caches(&self) -> bool {
918 self.data.cachePriming_enable
919 }
920
921 pub fn location_link(&self) -> bool {
922 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
923 }
924
925 pub fn line_folding_only(&self) -> bool {
926 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
927 }
928
929 pub fn hierarchical_symbols(&self) -> bool {
930 try_or_def!(
931 self.caps
932 .text_document
933 .as_ref()?
934 .document_symbol
935 .as_ref()?
936 .hierarchical_document_symbol_support?
937 )
938 }
939
940 pub fn code_action_literals(&self) -> bool {
941 try_!(self
942 .caps
943 .text_document
944 .as_ref()?
945 .code_action
946 .as_ref()?
947 .code_action_literal_support
948 .as_ref()?)
949 .is_some()
950 }
951
952 pub fn work_done_progress(&self) -> bool {
953 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
954 }
955
956 pub fn will_rename(&self) -> bool {
957 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
958 }
959
960 pub fn change_annotation_support(&self) -> bool {
961 try_!(self
962 .caps
963 .workspace
964 .as_ref()?
965 .workspace_edit
966 .as_ref()?
967 .change_annotation_support
968 .as_ref()?)
969 .is_some()
970 }
971
972 pub fn code_action_resolve(&self) -> bool {
973 try_or_def!(self
974 .caps
975 .text_document
976 .as_ref()?
977 .code_action
978 .as_ref()?
979 .resolve_support
980 .as_ref()?
981 .properties
982 .as_slice())
983 .iter()
984 .any(|it| it == "edit")
985 }
986
987 pub fn signature_help_label_offsets(&self) -> bool {
988 try_or_def!(
989 self.caps
990 .text_document
991 .as_ref()?
992 .signature_help
993 .as_ref()?
994 .signature_information
995 .as_ref()?
996 .parameter_information
997 .as_ref()?
998 .label_offset_support?
999 )
1000 }
1001
1002 pub fn completion_label_details_support(&self) -> bool {
1003 try_!(self
1004 .caps
1005 .text_document
1006 .as_ref()?
1007 .completion
1008 .as_ref()?
1009 .completion_item
1010 .as_ref()?
1011 .label_details_support
1012 .as_ref()?)
1013 .is_some()
1014 }
1015
1016 pub fn position_encoding(&self) -> PositionEncoding {
1017 negotiated_encoding(&self.caps)
1018 }
1019
1020 fn experimental(&self, index: &'static str) -> bool {
1021 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
1022 }
1023
1024 pub fn code_action_group(&self) -> bool {
1025 self.experimental("codeActionGroup")
1026 }
1027
1028 pub fn open_server_logs(&self) -> bool {
1029 self.experimental("openServerLogs")
1030 }
1031
1032 pub fn server_status_notification(&self) -> bool {
1033 self.experimental("serverStatusNotification")
1034 }
1035
1036 /// Whether the client supports colored output for full diagnostics from `checkOnSave`.
1037 pub fn color_diagnostic_output(&self) -> bool {
1038 self.experimental("colorDiagnosticOutput")
1039 }
1040
1041 pub fn publish_diagnostics(&self) -> bool {
1042 self.data.diagnostics_enable
1043 }
1044
1045 pub fn diagnostics(&self) -> DiagnosticsConfig {
1046 DiagnosticsConfig {
1047 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
1048 proc_macros_enabled: self.data.procMacro_enable,
1049 disable_experimental: !self.data.diagnostics_experimental_enable,
1050 disabled: self.data.diagnostics_disabled.clone(),
1051 expr_fill_default: match self.data.assist_expressionFillDefault {
1052 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
1053 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
1054 },
1055 insert_use: self.insert_use_config(),
1056 prefer_no_std: self.data.imports_prefer_no_std,
1057 }
1058 }
1059
1060 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
1061 DiagnosticsMapConfig {
1062 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
1063 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
1064 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
1065 }
1066 }
1067
1068 pub fn extra_args(&self) -> &Vec<String> {
1069 &self.data.cargo_extraArgs
1070 }
1071
1072 pub fn extra_env(&self) -> &FxHashMap<String, String> {
1073 &self.data.cargo_extraEnv
1074 }
1075
1076 pub fn check_extra_args(&self) -> Vec<String> {
1077 let mut extra_args = self.extra_args().clone();
1078 extra_args.extend_from_slice(&self.data.check_extraArgs);
1079 extra_args
1080 }
1081
1082 pub fn check_extra_env(&self) -> FxHashMap<String, String> {
1083 let mut extra_env = self.data.cargo_extraEnv.clone();
1084 extra_env.extend(self.data.check_extraEnv.clone());
1085 extra_env
1086 }
1087
1088 pub fn lru_capacity(&self) -> Option<usize> {
1089 self.data.lru_capacity
1090 }
1091
1092 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, /* is path explicitly set */ bool)> {
1093 if !self.data.procMacro_enable {
1094 return None;
1095 }
1096 Some(match &self.data.procMacro_server {
1097 Some(it) => (
1098 AbsPathBuf::try_from(it.clone()).unwrap_or_else(|path| self.root_path.join(path)),
1099 true,
1100 ),
1101 None => (AbsPathBuf::assert(std::env::current_exe().ok()?), false),
1102 })
1103 }
1104
1105 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
1106 &self.data.procMacro_ignored
1107 }
1108
1109 pub fn expand_proc_attr_macros(&self) -> bool {
1110 self.data.procMacro_enable && self.data.procMacro_attributes_enable
1111 }
1112
1113 pub fn files(&self) -> FilesConfig {
1114 FilesConfig {
1115 watcher: match self.data.files_watcher {
1116 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
1117 FilesWatcher::Client
1118 }
1119 _ => FilesWatcher::Server,
1120 },
1121 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
1122 }
1123 }
1124
1125 pub fn notifications(&self) -> NotificationsConfig {
1126 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
1127 }
1128
1129 pub fn cargo_autoreload(&self) -> bool {
1130 self.data.cargo_autoreload
1131 }
1132
1133 pub fn run_build_scripts(&self) -> bool {
1134 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
1135 }
1136
1137 pub fn cargo(&self) -> CargoConfig {
1138 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
1139 if rustc_src == "discover" {
1140 RustLibSource::Discover
1141 } else {
1142 RustLibSource::Path(self.root_path.join(rustc_src))
1143 }
1144 });
1145 let sysroot = self.data.cargo_sysroot.as_ref().map(|sysroot| {
1146 if sysroot == "discover" {
1147 RustLibSource::Discover
1148 } else {
1149 RustLibSource::Path(self.root_path.join(sysroot))
1150 }
1151 });
1152 let sysroot_src =
1153 self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot));
1154
1155 CargoConfig {
1156 features: match &self.data.cargo_features {
1157 CargoFeaturesDef::All => CargoFeatures::All,
1158 CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
1159 features: features.clone(),
1160 no_default_features: self.data.cargo_noDefaultFeatures,
1161 },
1162 },
1163 target: self.data.cargo_target.clone(),
1164 sysroot,
1165 sysroot_src,
1166 rustc_source,
1167 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
1168 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1169 invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy {
1170 InvocationStrategy::Once => project_model::InvocationStrategy::Once,
1171 InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
1172 },
1173 invocation_location: match self.data.cargo_buildScripts_invocationLocation {
1174 InvocationLocation::Root => {
1175 project_model::InvocationLocation::Root(self.root_path.clone())
1176 }
1177 InvocationLocation::Workspace => project_model::InvocationLocation::Workspace,
1178 },
1179 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
1180 extra_args: self.data.cargo_extraArgs.clone(),
1181 extra_env: self.data.cargo_extraEnv.clone(),
1182 }
1183 }
1184
1185 pub fn rustfmt(&self) -> RustfmtConfig {
1186 match &self.data.rustfmt_overrideCommand {
1187 Some(args) if !args.is_empty() => {
1188 let mut args = args.clone();
1189 let command = args.remove(0);
1190 RustfmtConfig::CustomCommand { command, args }
1191 }
1192 Some(_) | None => RustfmtConfig::Rustfmt {
1193 extra_args: self.data.rustfmt_extraArgs.clone(),
1194 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
1195 },
1196 }
1197 }
1198
1199 pub fn flycheck(&self) -> FlycheckConfig {
1200 match &self.data.check_overrideCommand {
1201 Some(args) if !args.is_empty() => {
1202 let mut args = args.clone();
1203 let command = args.remove(0);
1204 FlycheckConfig::CustomCommand {
1205 command,
1206 args,
1207 extra_env: self.check_extra_env(),
1208 invocation_strategy: match self.data.check_invocationStrategy {
1209 InvocationStrategy::Once => flycheck::InvocationStrategy::Once,
1210 InvocationStrategy::PerWorkspace => {
1211 flycheck::InvocationStrategy::PerWorkspace
1212 }
1213 },
1214 invocation_location: match self.data.check_invocationLocation {
1215 InvocationLocation::Root => {
1216 flycheck::InvocationLocation::Root(self.root_path.clone())
1217 }
1218 InvocationLocation::Workspace => flycheck::InvocationLocation::Workspace,
1219 },
1220 }
1221 }
1222 Some(_) | None => FlycheckConfig::CargoCommand {
1223 command: self.data.check_command.clone(),
1224 target_triples: self
1225 .data
1226 .check_targets
1227 .clone()
1228 .and_then(|targets| match &targets.0[..] {
1229 [] => None,
1230 targets => Some(targets.into()),
1231 })
1232 .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
1233 all_targets: self.data.check_allTargets,
1234 no_default_features: self
1235 .data
1236 .check_noDefaultFeatures
1237 .unwrap_or(self.data.cargo_noDefaultFeatures),
1238 all_features: matches!(
1239 self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
1240 CargoFeaturesDef::All
1241 ),
1242 features: match self
1243 .data
1244 .check_features
1245 .clone()
1246 .unwrap_or_else(|| self.data.cargo_features.clone())
1247 {
1248 CargoFeaturesDef::All => vec![],
1249 CargoFeaturesDef::Selected(it) => it,
1250 },
1251 extra_args: self.check_extra_args(),
1252 extra_env: self.check_extra_env(),
1253 ansi_color_output: self.color_diagnostic_output(),
1254 },
1255 }
1256 }
1257
1258 pub fn check_on_save(&self) -> bool {
1259 self.data.checkOnSave
1260 }
1261
1262 pub fn runnables(&self) -> RunnablesConfig {
1263 RunnablesConfig {
1264 override_cargo: self.data.runnables_command.clone(),
1265 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1266 }
1267 }
1268
1269 pub fn inlay_hints(&self) -> InlayHintsConfig {
1270 InlayHintsConfig {
1271 render_colons: self.data.inlayHints_renderColons,
1272 type_hints: self.data.inlayHints_typeHints_enable,
1273 parameter_hints: self.data.inlayHints_parameterHints_enable,
1274 chaining_hints: self.data.inlayHints_chainingHints_enable,
1275 discriminant_hints: match self.data.inlayHints_discriminantHints_enable {
1276 DiscriminantHintsDef::Always => ide::DiscriminantHints::Always,
1277 DiscriminantHintsDef::Never => ide::DiscriminantHints::Never,
1278 DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless,
1279 },
1280 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1281 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1282 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1283 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1284 },
1285 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1286 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1287 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1288 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1289 },
1290 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1291 hide_closure_initialization_hints: self
1292 .data
1293 .inlayHints_typeHints_hideClosureInitialization,
1294 adjustment_hints: match self.data.inlayHints_expressionAdjustmentHints_enable {
1295 AdjustmentHintsDef::Always => ide::AdjustmentHints::Always,
1296 AdjustmentHintsDef::Never => match self.data.inlayHints_reborrowHints_enable {
1297 ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => {
1298 ide::AdjustmentHints::ReborrowOnly
1299 }
1300 ReborrowHintsDef::Never => ide::AdjustmentHints::Never,
1301 },
1302 AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly,
1303 },
1304 adjustment_hints_mode: match self.data.inlayHints_expressionAdjustmentHints_mode {
1305 AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix,
1306 AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix,
1307 AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix,
1308 AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix,
1309 },
1310 adjustment_hints_hide_outside_unsafe: self
1311 .data
1312 .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe,
1313 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1314 param_names_for_lifetime_elision_hints: self
1315 .data
1316 .inlayHints_lifetimeElisionHints_useParameterNames,
1317 max_length: self.data.inlayHints_maxLength,
1318 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1319 Some(self.data.inlayHints_closingBraceHints_minLines)
1320 } else {
1321 None
1322 },
1323 }
1324 }
1325
1326 fn insert_use_config(&self) -> InsertUseConfig {
1327 InsertUseConfig {
1328 granularity: match self.data.imports_granularity_group {
1329 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1330 ImportGranularityDef::Item => ImportGranularity::Item,
1331 ImportGranularityDef::Crate => ImportGranularity::Crate,
1332 ImportGranularityDef::Module => ImportGranularity::Module,
1333 },
1334 enforce_granularity: self.data.imports_granularity_enforce,
1335 prefix_kind: match self.data.imports_prefix {
1336 ImportPrefixDef::Plain => PrefixKind::Plain,
1337 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1338 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1339 },
1340 group: self.data.imports_group_enable,
1341 skip_glob_imports: !self.data.imports_merge_glob,
1342 }
1343 }
1344
1345 pub fn completion(&self) -> CompletionConfig {
1346 CompletionConfig {
1347 enable_postfix_completions: self.data.completion_postfix_enable,
1348 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1349 && completion_item_edit_resolve(&self.caps),
1350 enable_self_on_the_fly: self.data.completion_autoself_enable,
1351 enable_private_editable: self.data.completion_privateEditable_enable,
1352 callable: match self.data.completion_callable_snippets {
1353 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1354 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1355 CallableCompletionDef::None => None,
1356 },
1357 insert_use: self.insert_use_config(),
1358 prefer_no_std: self.data.imports_prefer_no_std,
1359 snippet_cap: SnippetCap::new(try_or_def!(
1360 self.caps
1361 .text_document
1362 .as_ref()?
1363 .completion
1364 .as_ref()?
1365 .completion_item
1366 .as_ref()?
1367 .snippet_support?
1368 )),
1369 snippets: self.snippets.clone(),
1370 limit: self.data.completion_limit,
1371 }
1372 }
1373
1374 pub fn find_all_refs_exclude_imports(&self) -> bool {
1375 self.data.references_excludeImports
1376 }
1377
1378 pub fn snippet_cap(&self) -> bool {
1379 self.experimental("snippetTextEdit")
1380 }
1381
1382 pub fn assist(&self) -> AssistConfig {
1383 AssistConfig {
1384 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1385 allowed: None,
1386 insert_use: self.insert_use_config(),
1387 prefer_no_std: self.data.imports_prefer_no_std,
1388 assist_emit_must_use: self.data.assist_emitMustUse,
1389 }
1390 }
1391
1392 pub fn join_lines(&self) -> JoinLinesConfig {
1393 JoinLinesConfig {
1394 join_else_if: self.data.joinLines_joinElseIf,
1395 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1396 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1397 join_assignments: self.data.joinLines_joinAssignments,
1398 }
1399 }
1400
1401 pub fn call_info(&self) -> CallInfoConfig {
1402 CallInfoConfig {
1403 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1404 docs: self.data.signatureInfo_documentation_enable,
1405 }
1406 }
1407
1408 pub fn lens(&self) -> LensConfig {
1409 LensConfig {
1410 run: self.data.lens_enable && self.data.lens_run_enable,
1411 debug: self.data.lens_enable && self.data.lens_debug_enable,
1412 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1413 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1414 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1415 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1416 enum_variant_refs: self.data.lens_enable
1417 && self.data.lens_references_enumVariant_enable,
1418 location: self.data.lens_location,
1419 }
1420 }
1421
1422 pub fn hover_actions(&self) -> HoverActionsConfig {
1423 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1424 HoverActionsConfig {
1425 implementations: enable && self.data.hover_actions_implementations_enable,
1426 references: enable && self.data.hover_actions_references_enable,
1427 run: enable && self.data.hover_actions_run_enable,
1428 debug: enable && self.data.hover_actions_debug_enable,
1429 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1430 }
1431 }
1432
1433 pub fn highlighting_config(&self) -> HighlightConfig {
1434 HighlightConfig {
1435 strings: self.data.semanticHighlighting_strings_enable,
1436 punctuation: self.data.semanticHighlighting_punctuation_enable,
1437 specialize_punctuation: self
1438 .data
1439 .semanticHighlighting_punctuation_specialization_enable,
1440 macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang,
1441 operator: self.data.semanticHighlighting_operator_enable,
1442 specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
1443 inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable,
1444 syntactic_name_ref_highlighting: false,
1445 }
1446 }
1447
1448 pub fn hover(&self) -> HoverConfig {
1449 HoverConfig {
1450 links_in_hover: self.data.hover_links_enable,
1451 documentation: self.data.hover_documentation_enable,
1452 format: {
1453 let is_markdown = try_or_def!(self
1454 .caps
1455 .text_document
1456 .as_ref()?
1457 .hover
1458 .as_ref()?
1459 .content_format
1460 .as_ref()?
1461 .as_slice())
1462 .contains(&MarkupKind::Markdown);
1463 if is_markdown {
1464 HoverDocFormat::Markdown
1465 } else {
1466 HoverDocFormat::PlainText
1467 }
1468 },
1469 keywords: self.data.hover_documentation_keywords_enable,
1470 interpret_tests: self.data.interpret_tests,
1471 }
1472 }
1473
1474 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1475 WorkspaceSymbolConfig {
1476 search_scope: match self.data.workspace_symbol_search_scope {
1477 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1478 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1479 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1480 }
1481 },
1482 search_kind: match self.data.workspace_symbol_search_kind {
1483 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1484 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1485 },
1486 search_limit: self.data.workspace_symbol_search_limit,
1487 }
1488 }
1489
1490 pub fn semantic_tokens_refresh(&self) -> bool {
1491 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1492 }
1493
1494 pub fn code_lens_refresh(&self) -> bool {
1495 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1496 }
1497
1498 pub fn inlay_hints_refresh(&self) -> bool {
1499 try_or_def!(self.caps.workspace.as_ref()?.inlay_hint.as_ref()?.refresh_support?)
1500 }
1501
1502 pub fn insert_replace_support(&self) -> bool {
1503 try_or_def!(
1504 self.caps
1505 .text_document
1506 .as_ref()?
1507 .completion
1508 .as_ref()?
1509 .completion_item
1510 .as_ref()?
1511 .insert_replace_support?
1512 )
1513 }
1514
1515 pub fn client_commands(&self) -> ClientCommandsConfig {
1516 let commands =
1517 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1518 let commands: Option<lsp_ext::ClientCommandOptions> =
1519 serde_json::from_value(commands.clone()).ok();
1520 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1521 let commands = commands.map(|it| it.commands).unwrap_or_default();
1522
1523 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1524
1525 ClientCommandsConfig {
1526 run_single: get("rust-analyzer.runSingle"),
1527 debug_single: get("rust-analyzer.debugSingle"),
1528 show_reference: get("rust-analyzer.showReferences"),
1529 goto_location: get("rust-analyzer.gotoLocation"),
1530 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1531 }
1532 }
1533
1534 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1535 HighlightRelatedConfig {
1536 references: self.data.highlightRelated_references_enable,
1537 break_points: self.data.highlightRelated_breakPoints_enable,
1538 exit_points: self.data.highlightRelated_exitPoints_enable,
1539 yield_points: self.data.highlightRelated_yieldPoints_enable,
1540 }
1541 }
1542
1543 pub fn prime_caches_num_threads(&self) -> u8 {
1544 match self.data.cachePriming_numThreads {
1545 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1546 n => n,
1547 }
1548 }
1549
1550 pub fn main_loop_num_threads(&self) -> usize {
1551 self.data.numThreads.unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1))
1552 }
1553
1554 pub fn typing_autoclose_angle(&self) -> bool {
1555 self.data.typing_autoClosingAngleBrackets_enable
1556 }
1557 }
1558 // Deserialization definitions
1559
1560 macro_rules! create_bool_or_string_de {
1561 ($ident:ident<$bool:literal, $string:literal>) => {
1562 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1563 where
1564 D: serde::Deserializer<'de>,
1565 {
1566 struct V;
1567 impl<'de> serde::de::Visitor<'de> for V {
1568 type Value = ();
1569
1570 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1571 formatter.write_str(concat!(
1572 stringify!($bool),
1573 " or \"",
1574 stringify!($string),
1575 "\""
1576 ))
1577 }
1578
1579 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1580 where
1581 E: serde::de::Error,
1582 {
1583 match v {
1584 $bool => Ok(()),
1585 _ => Err(serde::de::Error::invalid_value(
1586 serde::de::Unexpected::Bool(v),
1587 &self,
1588 )),
1589 }
1590 }
1591
1592 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1593 where
1594 E: serde::de::Error,
1595 {
1596 match v {
1597 $string => Ok(()),
1598 _ => Err(serde::de::Error::invalid_value(
1599 serde::de::Unexpected::Str(v),
1600 &self,
1601 )),
1602 }
1603 }
1604
1605 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1606 where
1607 A: serde::de::EnumAccess<'de>,
1608 {
1609 use serde::de::VariantAccess;
1610 let (variant, va) = a.variant::<&'de str>()?;
1611 va.unit_variant()?;
1612 match variant {
1613 $string => Ok(()),
1614 _ => Err(serde::de::Error::invalid_value(
1615 serde::de::Unexpected::Str(variant),
1616 &self,
1617 )),
1618 }
1619 }
1620 }
1621 d.deserialize_any(V)
1622 }
1623 };
1624 }
1625 create_bool_or_string_de!(true_or_always<true, "always">);
1626 create_bool_or_string_de!(false_or_never<false, "never">);
1627
1628 macro_rules! named_unit_variant {
1629 ($variant:ident) => {
1630 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1631 where
1632 D: serde::Deserializer<'de>,
1633 {
1634 struct V;
1635 impl<'de> serde::de::Visitor<'de> for V {
1636 type Value = ();
1637 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1638 f.write_str(concat!("\"", stringify!($variant), "\""))
1639 }
1640 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1641 if value == stringify!($variant) {
1642 Ok(())
1643 } else {
1644 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1645 }
1646 }
1647 }
1648 deserializer.deserialize_str(V)
1649 }
1650 };
1651 }
1652
1653 mod de_unit_v {
1654 named_unit_variant!(all);
1655 named_unit_variant!(skip_trivial);
1656 named_unit_variant!(mutable);
1657 named_unit_variant!(reborrow);
1658 named_unit_variant!(fieldless);
1659 named_unit_variant!(with_block);
1660 }
1661
1662 #[derive(Deserialize, Debug, Clone, Copy)]
1663 #[serde(rename_all = "snake_case")]
1664 enum SnippetScopeDef {
1665 Expr,
1666 Item,
1667 Type,
1668 }
1669
1670 impl Default for SnippetScopeDef {
1671 fn default() -> Self {
1672 SnippetScopeDef::Expr
1673 }
1674 }
1675
1676 #[derive(Deserialize, Debug, Clone, Default)]
1677 #[serde(default)]
1678 struct SnippetDef {
1679 #[serde(deserialize_with = "single_or_array")]
1680 prefix: Vec<String>,
1681 #[serde(deserialize_with = "single_or_array")]
1682 postfix: Vec<String>,
1683 description: Option<String>,
1684 #[serde(deserialize_with = "single_or_array")]
1685 body: Vec<String>,
1686 #[serde(deserialize_with = "single_or_array")]
1687 requires: Vec<String>,
1688 scope: SnippetScopeDef,
1689 }
1690
1691 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1692 where
1693 D: serde::Deserializer<'de>,
1694 {
1695 struct SingleOrVec;
1696
1697 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1698 type Value = Vec<String>;
1699
1700 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1701 formatter.write_str("string or array of strings")
1702 }
1703
1704 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1705 where
1706 E: serde::de::Error,
1707 {
1708 Ok(vec![value.to_owned()])
1709 }
1710
1711 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1712 where
1713 A: serde::de::SeqAccess<'de>,
1714 {
1715 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1716 }
1717 }
1718
1719 deserializer.deserialize_any(SingleOrVec)
1720 }
1721
1722 #[derive(Deserialize, Debug, Clone)]
1723 #[serde(untagged)]
1724 enum ManifestOrProjectJson {
1725 Manifest(PathBuf),
1726 ProjectJson(ProjectJsonData),
1727 }
1728
1729 #[derive(Deserialize, Debug, Clone)]
1730 #[serde(rename_all = "snake_case")]
1731 enum ExprFillDefaultDef {
1732 Todo,
1733 Default,
1734 }
1735
1736 #[derive(Deserialize, Debug, Clone)]
1737 #[serde(rename_all = "snake_case")]
1738 enum ImportGranularityDef {
1739 Preserve,
1740 Item,
1741 Crate,
1742 Module,
1743 }
1744
1745 #[derive(Deserialize, Debug, Copy, Clone)]
1746 #[serde(rename_all = "snake_case")]
1747 enum CallableCompletionDef {
1748 FillArguments,
1749 AddParentheses,
1750 None,
1751 }
1752
1753 #[derive(Deserialize, Debug, Clone)]
1754 #[serde(untagged)]
1755 enum CargoFeaturesDef {
1756 #[serde(deserialize_with = "de_unit_v::all")]
1757 All,
1758 Selected(Vec<String>),
1759 }
1760
1761 #[derive(Deserialize, Debug, Clone)]
1762 #[serde(rename_all = "snake_case")]
1763 enum InvocationStrategy {
1764 Once,
1765 PerWorkspace,
1766 }
1767
1768 #[derive(Deserialize, Debug, Clone)]
1769 struct CheckOnSaveTargets(#[serde(deserialize_with = "single_or_array")] Vec<String>);
1770
1771 #[derive(Deserialize, Debug, Clone)]
1772 #[serde(rename_all = "snake_case")]
1773 enum InvocationLocation {
1774 Root,
1775 Workspace,
1776 }
1777
1778 #[derive(Deserialize, Debug, Clone)]
1779 #[serde(untagged)]
1780 enum LifetimeElisionDef {
1781 #[serde(deserialize_with = "true_or_always")]
1782 Always,
1783 #[serde(deserialize_with = "false_or_never")]
1784 Never,
1785 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1786 SkipTrivial,
1787 }
1788
1789 #[derive(Deserialize, Debug, Clone)]
1790 #[serde(untagged)]
1791 enum ClosureReturnTypeHintsDef {
1792 #[serde(deserialize_with = "true_or_always")]
1793 Always,
1794 #[serde(deserialize_with = "false_or_never")]
1795 Never,
1796 #[serde(deserialize_with = "de_unit_v::with_block")]
1797 WithBlock,
1798 }
1799
1800 #[derive(Deserialize, Debug, Clone)]
1801 #[serde(untagged)]
1802 enum ReborrowHintsDef {
1803 #[serde(deserialize_with = "true_or_always")]
1804 Always,
1805 #[serde(deserialize_with = "false_or_never")]
1806 Never,
1807 #[serde(deserialize_with = "de_unit_v::mutable")]
1808 Mutable,
1809 }
1810
1811 #[derive(Deserialize, Debug, Clone)]
1812 #[serde(untagged)]
1813 enum AdjustmentHintsDef {
1814 #[serde(deserialize_with = "true_or_always")]
1815 Always,
1816 #[serde(deserialize_with = "false_or_never")]
1817 Never,
1818 #[serde(deserialize_with = "de_unit_v::reborrow")]
1819 Reborrow,
1820 }
1821
1822 #[derive(Deserialize, Debug, Clone)]
1823 #[serde(untagged)]
1824 enum DiscriminantHintsDef {
1825 #[serde(deserialize_with = "true_or_always")]
1826 Always,
1827 #[serde(deserialize_with = "false_or_never")]
1828 Never,
1829 #[serde(deserialize_with = "de_unit_v::fieldless")]
1830 Fieldless,
1831 }
1832
1833 #[derive(Deserialize, Debug, Clone)]
1834 #[serde(rename_all = "snake_case")]
1835 enum AdjustmentHintsModeDef {
1836 Prefix,
1837 Postfix,
1838 PreferPrefix,
1839 PreferPostfix,
1840 }
1841
1842 #[derive(Deserialize, Debug, Clone)]
1843 #[serde(rename_all = "snake_case")]
1844 enum FilesWatcherDef {
1845 Client,
1846 Notify,
1847 Server,
1848 }
1849
1850 #[derive(Deserialize, Debug, Clone)]
1851 #[serde(rename_all = "snake_case")]
1852 enum ImportPrefixDef {
1853 Plain,
1854 #[serde(alias = "self")]
1855 BySelf,
1856 #[serde(alias = "crate")]
1857 ByCrate,
1858 }
1859
1860 #[derive(Deserialize, Debug, Clone)]
1861 #[serde(rename_all = "snake_case")]
1862 enum WorkspaceSymbolSearchScopeDef {
1863 Workspace,
1864 WorkspaceAndDependencies,
1865 }
1866
1867 #[derive(Deserialize, Debug, Clone)]
1868 #[serde(rename_all = "snake_case")]
1869 enum SignatureDetail {
1870 Full,
1871 Parameters,
1872 }
1873
1874 #[derive(Deserialize, Debug, Clone)]
1875 #[serde(rename_all = "snake_case")]
1876 enum WorkspaceSymbolSearchKindDef {
1877 OnlyTypes,
1878 AllSymbols,
1879 }
1880
1881 macro_rules! _config_data {
1882 (struct $name:ident {
1883 $(
1884 $(#[doc=$doc:literal])*
1885 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1886 )*
1887 }) => {
1888 #[allow(non_snake_case)]
1889 #[derive(Debug, Clone)]
1890 struct $name { $($field: $ty,)* }
1891 impl $name {
1892 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1893 $name {$(
1894 $field: get_field(
1895 &mut json,
1896 error_sink,
1897 stringify!($field),
1898 None$(.or(Some(stringify!($alias))))*,
1899 $default,
1900 ),
1901 )*}
1902 }
1903
1904 fn json_schema() -> serde_json::Value {
1905 schema(&[
1906 $({
1907 let field = stringify!($field);
1908 let ty = stringify!($ty);
1909
1910 (field, ty, &[$($doc),*], $default)
1911 },)*
1912 ])
1913 }
1914
1915 #[cfg(test)]
1916 fn manual() -> String {
1917 manual(&[
1918 $({
1919 let field = stringify!($field);
1920 let ty = stringify!($ty);
1921
1922 (field, ty, &[$($doc),*], $default)
1923 },)*
1924 ])
1925 }
1926 }
1927
1928 #[test]
1929 fn fields_are_sorted() {
1930 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1931 }
1932 };
1933 }
1934 use _config_data as config_data;
1935
1936 fn get_field<T: DeserializeOwned>(
1937 json: &mut serde_json::Value,
1938 error_sink: &mut Vec<(String, serde_json::Error)>,
1939 field: &'static str,
1940 alias: Option<&'static str>,
1941 default: &str,
1942 ) -> T {
1943 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1944 // defaults instead of sending an empty object.
1945 alias
1946 .into_iter()
1947 .chain(iter::once(field))
1948 .filter_map(move |field| {
1949 let mut pointer = field.replace('_', "/");
1950 pointer.insert(0, '/');
1951 json.pointer_mut(&pointer)
1952 .map(|it| serde_json::from_value(it.take()).map_err(|e| (e, pointer)))
1953 })
1954 .find(Result::is_ok)
1955 .and_then(|res| match res {
1956 Ok(it) => Some(it),
1957 Err((e, pointer)) => {
1958 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1959 error_sink.push((pointer, e));
1960 None
1961 }
1962 })
1963 .unwrap_or_else(|| serde_json::from_str(default).unwrap())
1964 }
1965
1966 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1967 let map = fields
1968 .iter()
1969 .map(|(field, ty, doc, default)| {
1970 let name = field.replace('_', ".");
1971 let name = format!("rust-analyzer.{name}");
1972 let props = field_props(field, ty, doc, default);
1973 (name, props)
1974 })
1975 .collect::<serde_json::Map<_, _>>();
1976 map.into()
1977 }
1978
1979 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1980 let doc = doc_comment_to_string(doc);
1981 let doc = doc.trim_end_matches('\n');
1982 assert!(
1983 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1984 "bad docs for {field}: {doc:?}"
1985 );
1986 let default = default.parse::<serde_json::Value>().unwrap();
1987
1988 let mut map = serde_json::Map::default();
1989 macro_rules! set {
1990 ($($key:literal: $value:tt),*$(,)?) => {{$(
1991 map.insert($key.into(), serde_json::json!($value));
1992 )*}};
1993 }
1994 set!("markdownDescription": doc);
1995 set!("default": default);
1996
1997 match ty {
1998 "bool" => set!("type": "boolean"),
1999 "usize" => set!("type": "integer", "minimum": 0),
2000 "String" => set!("type": "string"),
2001 "Vec<String>" => set! {
2002 "type": "array",
2003 "items": { "type": "string" },
2004 },
2005 "Vec<PathBuf>" => set! {
2006 "type": "array",
2007 "items": { "type": "string" },
2008 },
2009 "FxHashSet<String>" => set! {
2010 "type": "array",
2011 "items": { "type": "string" },
2012 "uniqueItems": true,
2013 },
2014 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
2015 "type": "object",
2016 },
2017 "FxHashMap<String, SnippetDef>" => set! {
2018 "type": "object",
2019 },
2020 "FxHashMap<String, String>" => set! {
2021 "type": "object",
2022 },
2023 "Option<usize>" => set! {
2024 "type": ["null", "integer"],
2025 "minimum": 0,
2026 },
2027 "Option<String>" => set! {
2028 "type": ["null", "string"],
2029 },
2030 "Option<PathBuf>" => set! {
2031 "type": ["null", "string"],
2032 },
2033 "Option<bool>" => set! {
2034 "type": ["null", "boolean"],
2035 },
2036 "Option<Vec<String>>" => set! {
2037 "type": ["null", "array"],
2038 "items": { "type": "string" },
2039 },
2040 "ExprFillDefaultDef" => set! {
2041 "type": "string",
2042 "enum": ["todo", "default"],
2043 "enumDescriptions": [
2044 "Fill missing expressions with the `todo` macro",
2045 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
2046 ],
2047 },
2048 "ImportGranularityDef" => set! {
2049 "type": "string",
2050 "enum": ["preserve", "crate", "module", "item"],
2051 "enumDescriptions": [
2052 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
2053 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
2054 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
2055 "Flatten imports so that each has its own use statement."
2056 ],
2057 },
2058 "ImportPrefixDef" => set! {
2059 "type": "string",
2060 "enum": [
2061 "plain",
2062 "self",
2063 "crate"
2064 ],
2065 "enumDescriptions": [
2066 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
2067 "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.",
2068 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
2069 ],
2070 },
2071 "Vec<ManifestOrProjectJson>" => set! {
2072 "type": "array",
2073 "items": { "type": ["string", "object"] },
2074 },
2075 "WorkspaceSymbolSearchScopeDef" => set! {
2076 "type": "string",
2077 "enum": ["workspace", "workspace_and_dependencies"],
2078 "enumDescriptions": [
2079 "Search in current workspace only.",
2080 "Search in current workspace and dependencies."
2081 ],
2082 },
2083 "WorkspaceSymbolSearchKindDef" => set! {
2084 "type": "string",
2085 "enum": ["only_types", "all_symbols"],
2086 "enumDescriptions": [
2087 "Search for types only.",
2088 "Search for all symbols kinds."
2089 ],
2090 },
2091 "ParallelCachePrimingNumThreads" => set! {
2092 "type": "number",
2093 "minimum": 0,
2094 "maximum": 255
2095 },
2096 "LifetimeElisionDef" => set! {
2097 "type": "string",
2098 "enum": [
2099 "always",
2100 "never",
2101 "skip_trivial"
2102 ],
2103 "enumDescriptions": [
2104 "Always show lifetime elision hints.",
2105 "Never show lifetime elision hints.",
2106 "Only show lifetime elision hints if a return type is involved."
2107 ]
2108 },
2109 "ClosureReturnTypeHintsDef" => set! {
2110 "type": "string",
2111 "enum": [
2112 "always",
2113 "never",
2114 "with_block"
2115 ],
2116 "enumDescriptions": [
2117 "Always show type hints for return types of closures.",
2118 "Never show type hints for return types of closures.",
2119 "Only show type hints for return types of closures with blocks."
2120 ]
2121 },
2122 "ReborrowHintsDef" => set! {
2123 "type": "string",
2124 "enum": [
2125 "always",
2126 "never",
2127 "mutable"
2128 ],
2129 "enumDescriptions": [
2130 "Always show reborrow hints.",
2131 "Never show reborrow hints.",
2132 "Only show mutable reborrow hints."
2133 ]
2134 },
2135 "AdjustmentHintsDef" => set! {
2136 "type": "string",
2137 "enum": [
2138 "always",
2139 "never",
2140 "reborrow"
2141 ],
2142 "enumDescriptions": [
2143 "Always show all adjustment hints.",
2144 "Never show adjustment hints.",
2145 "Only show auto borrow and dereference adjustment hints."
2146 ]
2147 },
2148 "DiscriminantHintsDef" => set! {
2149 "type": "string",
2150 "enum": [
2151 "always",
2152 "never",
2153 "fieldless"
2154 ],
2155 "enumDescriptions": [
2156 "Always show all discriminant hints.",
2157 "Never show discriminant hints.",
2158 "Only show discriminant hints on fieldless enum variants."
2159 ]
2160 },
2161 "AdjustmentHintsModeDef" => set! {
2162 "type": "string",
2163 "enum": [
2164 "prefix",
2165 "postfix",
2166 "prefer_prefix",
2167 "prefer_postfix",
2168 ],
2169 "enumDescriptions": [
2170 "Always show adjustment hints as prefix (`*expr`).",
2171 "Always show adjustment hints as postfix (`expr.*`).",
2172 "Show prefix or postfix depending on which uses less parenthesis, prefering prefix.",
2173 "Show prefix or postfix depending on which uses less parenthesis, prefering postfix.",
2174 ]
2175 },
2176 "CargoFeaturesDef" => set! {
2177 "anyOf": [
2178 {
2179 "type": "string",
2180 "enum": [
2181 "all"
2182 ],
2183 "enumDescriptions": [
2184 "Pass `--all-features` to cargo",
2185 ]
2186 },
2187 {
2188 "type": "array",
2189 "items": { "type": "string" }
2190 }
2191 ],
2192 },
2193 "Option<CargoFeaturesDef>" => set! {
2194 "anyOf": [
2195 {
2196 "type": "string",
2197 "enum": [
2198 "all"
2199 ],
2200 "enumDescriptions": [
2201 "Pass `--all-features` to cargo",
2202 ]
2203 },
2204 {
2205 "type": "array",
2206 "items": { "type": "string" }
2207 },
2208 { "type": "null" }
2209 ],
2210 },
2211 "CallableCompletionDef" => set! {
2212 "type": "string",
2213 "enum": [
2214 "fill_arguments",
2215 "add_parentheses",
2216 "none",
2217 ],
2218 "enumDescriptions": [
2219 "Add call parentheses and pre-fill arguments.",
2220 "Add call parentheses.",
2221 "Do no snippet completions for callables."
2222 ]
2223 },
2224 "SignatureDetail" => set! {
2225 "type": "string",
2226 "enum": ["full", "parameters"],
2227 "enumDescriptions": [
2228 "Show the entire signature.",
2229 "Show only the parameters."
2230 ],
2231 },
2232 "FilesWatcherDef" => set! {
2233 "type": "string",
2234 "enum": ["client", "server"],
2235 "enumDescriptions": [
2236 "Use the client (editor) to watch files for changes",
2237 "Use server-side file watching",
2238 ],
2239 },
2240 "AnnotationLocation" => set! {
2241 "type": "string",
2242 "enum": ["above_name", "above_whole_item"],
2243 "enumDescriptions": [
2244 "Render annotations above the name of the item.",
2245 "Render annotations above the whole item, including documentation comments and attributes."
2246 ],
2247 },
2248 "InvocationStrategy" => set! {
2249 "type": "string",
2250 "enum": ["per_workspace", "once"],
2251 "enumDescriptions": [
2252 "The command will be executed for each workspace.",
2253 "The command will be executed once."
2254 ],
2255 },
2256 "InvocationLocation" => set! {
2257 "type": "string",
2258 "enum": ["workspace", "root"],
2259 "enumDescriptions": [
2260 "The command will be executed in the corresponding workspace root.",
2261 "The command will be executed in the project root."
2262 ],
2263 },
2264 "Option<CheckOnSaveTargets>" => set! {
2265 "anyOf": [
2266 {
2267 "type": "null"
2268 },
2269 {
2270 "type": "string",
2271 },
2272 {
2273 "type": "array",
2274 "items": { "type": "string" }
2275 },
2276 ],
2277 },
2278 _ => panic!("missing entry for {ty}: {default}"),
2279 }
2280
2281 map.into()
2282 }
2283
2284 #[cfg(test)]
2285 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
2286 fields
2287 .iter()
2288 .map(|(field, _ty, doc, default)| {
2289 let name = format!("rust-analyzer.{}", field.replace('_', "."));
2290 let doc = doc_comment_to_string(doc);
2291 if default.contains('\n') {
2292 format!(
2293 r#"[[{name}]]{name}::
2294 +
2295 --
2296 Default:
2297 ----
2298 {default}
2299 ----
2300 {doc}
2301 --
2302 "#
2303 )
2304 } else {
2305 format!("[[{name}]]{name} (default: `{default}`)::\n+\n--\n{doc}--\n")
2306 }
2307 })
2308 .collect::<String>()
2309 }
2310
2311 fn doc_comment_to_string(doc: &[&str]) -> String {
2312 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{it}\n")).collect()
2313 }
2314
2315 #[cfg(test)]
2316 mod tests {
2317 use std::fs;
2318
2319 use test_utils::{ensure_file_contents, project_root};
2320
2321 use super::*;
2322
2323 #[test]
2324 fn generate_package_json_config() {
2325 let s = Config::json_schema();
2326 let schema = format!("{s:#}");
2327 let mut schema = schema
2328 .trim_start_matches('{')
2329 .trim_end_matches('}')
2330 .replace(" ", " ")
2331 .replace('\n', "\n ")
2332 .trim_start_matches('\n')
2333 .trim_end()
2334 .to_string();
2335 schema.push_str(",\n");
2336
2337 // Transform the asciidoc form link to markdown style.
2338 //
2339 // https://link[text] => [text](https://link)
2340 let url_matches = schema.match_indices("https://");
2341 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
2342 url_offsets.reverse();
2343 for idx in url_offsets {
2344 let link = &schema[idx..];
2345 // matching on whitespace to ignore normal links
2346 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
2347 if link.chars().nth(link_end) == Some('[') {
2348 if let Some(link_text_end) = link.find(']') {
2349 let link_text = link[link_end..(link_text_end + 1)].to_string();
2350
2351 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
2352 schema.insert(idx, '(');
2353 schema.insert(idx + link_end + 1, ')');
2354 schema.insert_str(idx, &link_text);
2355 }
2356 }
2357 }
2358 }
2359
2360 let package_json_path = project_root().join("editors/code/package.json");
2361 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
2362
2363 let start_marker = " \"$generated-start\": {},\n";
2364 let end_marker = " \"$generated-end\": {}\n";
2365
2366 let start = package_json.find(start_marker).unwrap() + start_marker.len();
2367 let end = package_json.find(end_marker).unwrap();
2368
2369 let p = remove_ws(&package_json[start..end]);
2370 let s = remove_ws(&schema);
2371 if !p.contains(&s) {
2372 package_json.replace_range(start..end, &schema);
2373 ensure_file_contents(&package_json_path, &package_json)
2374 }
2375 }
2376
2377 #[test]
2378 fn generate_config_documentation() {
2379 let docs_path = project_root().join("docs/user/generated_config.adoc");
2380 let expected = ConfigData::manual();
2381 ensure_file_contents(&docs_path, &expected);
2382 }
2383
2384 fn remove_ws(text: &str) -> String {
2385 text.replace(char::is_whitespace, "")
2386 }
2387 }