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