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