Skip to main content

ra_ap_rust_analyzer/
config.rs

1//! Config used by the language server.
2//!
3//! Of particular interest is the `feature_flags` hash map: while other fields
4//! configure the server itself, feature flags are passed into analysis, and
5//! tweak things like automatic insertion of `()` in completions.
6use std::{env, fmt, iter, ops::Not, sync::OnceLock};
7
8use cfg::{CfgAtom, CfgDiff};
9use hir::Symbol;
10use ide::{
11    AnnotationConfig, AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig,
12    CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, GotoDefinitionConfig,
13    GotoImplementationConfig, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat,
14    InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig,
15    MemoryLayoutHoverRenderKind, RenameConfig, Snippet, SnippetScope, SourceRootId,
16};
17use ide_db::{
18    MiniCore, SnippetCap,
19    assists::ExprFillDefaultMode,
20    imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
21};
22use itertools::{Either, Itertools};
23use paths::{Utf8Path, Utf8PathBuf};
24use project_model::{
25    CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectJsonFromCommand,
26    ProjectManifest, RustLibSource, TargetDirectoryConfig,
27};
28use rustc_hash::{FxHashMap, FxHashSet};
29use semver::Version;
30use serde::{
31    Deserialize, Serialize,
32    de::{DeserializeOwned, Error},
33};
34use stdx::format_to_acc;
35use triomphe::Arc;
36use vfs::{AbsPath, AbsPathBuf, VfsPath};
37
38use crate::{
39    diagnostics::DiagnosticsMapConfig,
40    flycheck::{CargoOptions, FlycheckConfig},
41    lsp::capabilities::ClientCapabilities,
42    lsp_ext::{WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
43};
44
45type FxIndexMap<K, V> = indexmap::IndexMap<K, V, rustc_hash::FxBuildHasher>;
46
47mod patch_old_style;
48
49// Conventions for configuration keys to preserve maximal extendability without breakage:
50//  - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable`
51//    This has the benefit of namespaces being extensible, and if the suffix doesn't fit later it can be changed without breakage.
52//  - In general be wary of using the namespace of something verbatim, it prevents us from adding subkeys in the future
53//  - Don't use abbreviations unless really necessary
54//  - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
55
56#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub enum MaxSubstitutionLength {
59    Hide,
60    #[serde(untagged)]
61    Limit(usize),
62}
63
64// Defines the server-side configuration of the rust-analyzer. We generate *parts* of VS Code's
65// `package.json` config from this. Run `cargo test` to re-generate that file.
66//
67// However, editor specific config, which the server doesn't know about, should be specified
68// directly in `package.json`.
69//
70// To deprecate an option by replacing it with another name use `new_name` | `old_name` so that we
71// keep parsing the old name.
72config_data! {
73    /// Configs that apply on a workspace-wide scope. There are 2 levels on which a global
74    /// configuration can be configured
75    ///
76    /// 1. `rust-analyzer.toml` file under user's config directory (e.g
77    ///    ~/.config/rust-analyzer/rust-analyzer.toml)
78    /// 2. Client's own configurations (e.g `settings.json` on VS Code)
79    ///
80    /// A config is searched for by traversing a "config tree" in a bottom up fashion. It is chosen
81    /// by the nearest first principle.
82    global: struct GlobalDefaultConfigData <- GlobalConfigInput -> {
83        /// Warm up caches on project load.
84        cachePriming_enable: bool = true,
85
86        /// How many worker threads to handle priming caches. The default `0` means to pick
87        /// automatically.
88        cachePriming_numThreads: NumThreads = NumThreads::Physical,
89
90        /// Custom completion snippets.
91        completion_snippets_custom: FxIndexMap<String, SnippetDef> =
92            Config::completion_snippets_default(),
93
94        /// List of files to ignore
95        ///
96        /// These paths (file/directories) will be ignored by rust-analyzer. They are relative to
97        /// the workspace root, and globs are not supported. You may also need to add the folders to
98        /// Code's `files.watcherExclude`.
99        files_exclude | files_excludeDirs: Vec<Utf8PathBuf> = vec![],
100
101        /// If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type.
102        gotoImplementations_filterAdjacentDerives: bool = false,
103
104        /// Highlight related return values while the cursor is on any `match`, `if`, or match arm
105        /// arrow (`=>`).
106        highlightRelated_branchExitPoints_enable: bool = true,
107
108        /// Highlight related references while the cursor is on `break`, `loop`, `while`, or `for`
109        /// keywords.
110        highlightRelated_breakPoints_enable: bool = true,
111
112        /// Highlight all captures of a closure while the cursor is on the `|` or move keyword of a closure.
113        highlightRelated_closureCaptures_enable: bool = true,
114
115        /// Highlight all exit points while the cursor is on any `return`, `?`, `fn`, or return type
116        /// arrow (`->`).
117        highlightRelated_exitPoints_enable: bool = true,
118
119        /// Highlight related references while the cursor is on any identifier.
120        highlightRelated_references_enable: bool = true,
121
122        /// Highlight all break points for a loop or block context while the cursor is on any
123        /// `async` or `await` keywords.
124        highlightRelated_yieldPoints_enable: bool = true,
125
126        /// Show `Debug` action. Only applies when `#rust-analyzer.hover.actions.enable#` is set.
127        hover_actions_debug_enable: bool = true,
128
129        /// Show HoverActions in Rust files.
130        hover_actions_enable: bool = true,
131
132        /// Show `Go to Type Definition` action. Only applies when
133        /// `#rust-analyzer.hover.actions.enable#` is set.
134        hover_actions_gotoTypeDef_enable: bool = true,
135
136        /// Show `Implementations` action. Only applies when `#rust-analyzer.hover.actions.enable#`
137        /// is set.
138        hover_actions_implementations_enable: bool = true,
139
140        /// Show `References` action. Only applies when `#rust-analyzer.hover.actions.enable#` is
141        /// set.
142        hover_actions_references_enable: bool = false,
143
144        /// Show `Run` action. Only applies when `#rust-analyzer.hover.actions.enable#` is set.
145        hover_actions_run_enable: bool = true,
146
147        /// Show `Update Test` action. Only applies when `#rust-analyzer.hover.actions.enable#` and
148        /// `#rust-analyzer.hover.actions.run.enable#` are set.
149        hover_actions_updateTest_enable: bool = true,
150
151        /// Show documentation on hover.
152        hover_documentation_enable: bool = true,
153
154        /// Show keyword hover popups. Only applies when
155        /// `#rust-analyzer.hover.documentation.enable#` is set.
156        hover_documentation_keywords_enable: bool = true,
157
158        /// Show drop glue information on hover.
159        hover_dropGlue_enable: bool = true,
160
161        /// Use markdown syntax for links on hover.
162        hover_links_enable: bool = true,
163
164        /// Show what types are used as generic arguments in calls etc. on hover, and limit the max
165        /// length to show such types, beyond which they will be shown with ellipsis.
166        ///
167        /// This can take three values: `null` means "unlimited", the string `"hide"` means to not
168        /// show generic substitutions at all, and a number means to limit them to X characters.
169        ///
170        /// The default is 20 characters.
171        hover_maxSubstitutionLength: Option<MaxSubstitutionLength> =
172            Some(MaxSubstitutionLength::Limit(20)),
173
174        /// How to render the align information in a memory layout hover.
175        hover_memoryLayout_alignment: Option<MemoryLayoutHoverRenderKindDef> =
176            Some(MemoryLayoutHoverRenderKindDef::Hexadecimal),
177
178        /// Show memory layout data on hover.
179        hover_memoryLayout_enable: bool = true,
180
181        /// How to render the niche information in a memory layout hover.
182        hover_memoryLayout_niches: Option<bool> = Some(false),
183
184        /// How to render the offset information in a memory layout hover.
185        hover_memoryLayout_offset: Option<MemoryLayoutHoverRenderKindDef> =
186            Some(MemoryLayoutHoverRenderKindDef::Hexadecimal),
187
188        /// How to render the padding information in a memory layout hover.
189        hover_memoryLayout_padding: Option<MemoryLayoutHoverRenderKindDef> = None,
190
191        /// How to render the size information in a memory layout hover.
192        hover_memoryLayout_size: Option<MemoryLayoutHoverRenderKindDef> =
193            Some(MemoryLayoutHoverRenderKindDef::Both),
194
195        /// How many variants of an enum to display when hovering on. Show none if empty.
196        hover_show_enumVariants: Option<usize> = Some(5),
197
198        /// How many fields of a struct, variant or union to display when hovering on. Show none if
199        /// empty.
200        hover_show_fields: Option<usize> = Some(5),
201
202        /// How many associated items of a trait to display when hovering a trait.
203        hover_show_traitAssocItems: Option<usize> = None,
204
205        /// Show inlay type hints for binding modes.
206        inlayHints_bindingModeHints_enable: bool = false,
207
208        /// Show inlay type hints for method chains.
209        inlayHints_chainingHints_enable: bool = true,
210
211        /// Show inlay hints after a closing `}` to indicate what item it belongs to.
212        inlayHints_closingBraceHints_enable: bool = true,
213
214        /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
215        /// to always show them).
216        inlayHints_closingBraceHints_minLines: usize = 25,
217
218        /// Show inlay hints for closure captures.
219        inlayHints_closureCaptureHints_enable: bool = false,
220
221        /// Show inlay type hints for return types of closures.
222        inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef =
223            ClosureReturnTypeHintsDef::Never,
224
225        /// Closure notation in type and chaining inlay hints.
226        inlayHints_closureStyle: ClosureStyle = ClosureStyle::ImplFn,
227
228        /// Show enum variant discriminant hints.
229        inlayHints_discriminantHints_enable: DiscriminantHintsDef =
230            DiscriminantHintsDef::Never,
231
232        /// Disable reborrows in expression adjustments inlay hints.
233        ///
234        /// Reborrows are a pair of a builtin deref then borrow, i.e. `&*`. They are inserted by the compiler but are mostly useless to the programmer.
235        ///
236        /// Note: if the deref is not builtin (an overloaded deref), or the borrow is `&raw const`/`&raw mut`, they are not removed.
237        inlayHints_expressionAdjustmentHints_disableReborrows: bool =
238            true,
239
240        /// Show inlay hints for type adjustments.
241        inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef =
242            AdjustmentHintsDef::Never,
243
244        /// Hide inlay hints for type adjustments outside of `unsafe` blocks.
245        inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = false,
246
247        /// Show inlay hints as postfix ops (`.*` instead of `*`, etc).
248        inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef =
249            AdjustmentHintsModeDef::Prefix,
250
251        /// Show const generic parameter name inlay hints.
252        inlayHints_genericParameterHints_const_enable: bool = true,
253
254        /// Show generic lifetime parameter name inlay hints.
255        inlayHints_genericParameterHints_lifetime_enable: bool = false,
256
257        /// Show generic type parameter name inlay hints.
258        inlayHints_genericParameterHints_type_enable: bool = false,
259
260        /// Show implicit drop hints.
261        inlayHints_implicitDrops_enable: bool = false,
262
263        /// Show inlay hints for the implied type parameter `Sized` bound.
264        inlayHints_implicitSizedBoundHints_enable: bool = false,
265
266        /// Show inlay hints for the implied `dyn` keyword in trait object types.
267        inlayHints_impliedDynTraitHints_enable: bool = true,
268
269        /// Show inlay type hints for elided lifetimes in function signatures.
270        inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = LifetimeElisionDef::Never,
271
272        /// Prefer using parameter names as the name for elided lifetime hints if possible.
273        inlayHints_lifetimeElisionHints_useParameterNames: bool = false,
274
275        /// Maximum length for inlay hints. Set to null to have an unlimited length.
276        ///
277        /// **Note:** This is mostly a hint, and we don't guarantee to strictly follow the limit.
278        inlayHints_maxLength: Option<usize> = Some(25),
279
280        /// Show function parameter name inlay hints at the call site.
281        inlayHints_parameterHints_enable: bool = true,
282
283        /// Show parameter name inlay hints for missing arguments at the call site.
284        inlayHints_parameterHints_missingArguments_enable: bool = false,
285
286        /// Show exclusive range inlay hints.
287        inlayHints_rangeExclusiveHints_enable: bool = false,
288
289        /// Show inlay hints for compiler inserted reborrows.
290        ///
291        /// This setting is deprecated in favor of
292        /// #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#.
293        inlayHints_reborrowHints_enable: ReborrowHintsDef = ReborrowHintsDef::Never,
294
295        /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
296        inlayHints_renderColons: bool = true,
297
298        /// Show inlay type hints for variables.
299        inlayHints_typeHints_enable: bool = true,
300
301        /// Hide inlay type hints for `let` statements that initialize to a closure.
302        ///
303        /// Only applies to closures with blocks, same as
304        /// `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
305        inlayHints_typeHints_hideClosureInitialization: bool = false,
306
307        /// Hide inlay parameter type hints for closures.
308        inlayHints_typeHints_hideClosureParameter: bool = false,
309
310        /// Hide inlay type hints for inferred types.
311        inlayHints_typeHints_hideInferredTypes: bool = false,
312
313        /// Hide inlay type hints for constructors.
314        inlayHints_typeHints_hideNamedConstructor: bool = false,
315
316        /// Enable the experimental support for interpreting tests.
317        interpret_tests: bool = false,
318
319        /// Join lines merges consecutive declaration and initialization of an assignment.
320        joinLines_joinAssignments: bool = true,
321
322        /// Join lines inserts else between consecutive ifs.
323        joinLines_joinElseIf: bool = true,
324
325        /// Join lines removes trailing commas.
326        joinLines_removeTrailingComma: bool = true,
327
328        /// Join lines unwraps trivial blocks.
329        joinLines_unwrapTrivialBlock: bool = true,
330
331        /// Show `Debug` lens. Only applies when `#rust-analyzer.lens.enable#` is set.
332        lens_debug_enable: bool = true,
333
334        /// Show CodeLens in Rust files.
335        lens_enable: bool = true,
336
337        /// Show `Implementations` lens. Only applies when `#rust-analyzer.lens.enable#` is set.
338        lens_implementations_enable: bool = true,
339
340        /// Where to render annotations.
341        lens_location: AnnotationLocation = AnnotationLocation::AboveName,
342
343        /// Show `References` lens for Struct, Enum, and Union. Only applies when
344        /// `#rust-analyzer.lens.enable#` is set.
345        lens_references_adt_enable: bool = false,
346
347        /// Show `References` lens for Enum Variants. Only applies when
348        /// `#rust-analyzer.lens.enable#` is set.
349        lens_references_enumVariant_enable: bool = false,
350
351        /// Show `Method References` lens. Only applies when `#rust-analyzer.lens.enable#` is set.
352        lens_references_method_enable: bool = false,
353
354        /// Show `References` lens for Trait. Only applies when `#rust-analyzer.lens.enable#` is
355        /// set.
356        lens_references_trait_enable: bool = false,
357
358        /// Show `Run` lens. Only applies when `#rust-analyzer.lens.enable#` is set.
359        lens_run_enable: bool = true,
360
361        /// Show `Update Test` lens. Only applies when `#rust-analyzer.lens.enable#` and
362        /// `#rust-analyzer.lens.run.enable#` are set.
363        lens_updateTest_enable: bool = true,
364
365        /// Disable project auto-discovery in favor of explicitly specified set of projects.
366        ///
367        /// Elements must be paths pointing to `Cargo.toml`, `rust-project.json`, `.rs` files (which
368        /// will be treated as standalone files) or JSON objects in `rust-project.json` format.
369        linkedProjects: Vec<ManifestOrProjectJson> = vec![],
370
371        /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
372        lru_capacity: Option<u16> = None,
373
374        /// The LRU capacity of the specified queries.
375        lru_query_capacities: FxHashMap<Box<str>, u16> = FxHashMap::default(),
376
377        /// Show `can't find Cargo.toml` error message.
378        notifications_cargoTomlNotFound: bool = true,
379
380        /// The number of worker threads in the main loop. The default `null` means to pick
381        /// automatically.
382        numThreads: Option<NumThreads> = None,
383
384        /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
385        procMacro_attributes_enable: bool = true,
386
387        /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
388        procMacro_enable: bool = true,
389
390        /// Number of proc-macro server processes to spawn.
391        ///
392        /// Controls how many independent `proc-macro-srv` processes rust-analyzer
393        /// runs in parallel to handle macro expansion.
394        procMacro_processes: NumProcesses = NumProcesses::Concrete(1),
395
396        /// Internal config, path to proc-macro server executable.
397        procMacro_server: Option<Utf8PathBuf> = None,
398
399        /// The path where to save memory profiling output.
400        ///
401        /// **Note:** Memory profiling is not enabled by default in rust-analyzer builds, you need to build
402        /// from source for it.
403        profiling_memoryProfile: Option<Utf8PathBuf> = None,
404
405        /// Exclude imports from find-all-references.
406        references_excludeImports: bool = false,
407
408        /// Exclude tests from find-all-references and call-hierarchy.
409        references_excludeTests: bool = false,
410
411        /// Use semantic tokens for comments.
412        ///
413        /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
414        /// By disabling semantic tokens for comments, other grammars can be used to highlight
415        /// their contents.
416        semanticHighlighting_comments_enable: bool = true,
417
418        /// Inject additional highlighting into doc comments.
419        ///
420        /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
421        /// doc links.
422        semanticHighlighting_doc_comment_inject_enable: bool = true,
423
424        /// Emit non-standard tokens and modifiers
425        ///
426        /// When enabled, rust-analyzer will emit tokens and modifiers that are not part of the
427        /// standard set of semantic tokens.
428        semanticHighlighting_nonStandardTokens: bool = true,
429
430        /// Use semantic tokens for operators.
431        ///
432        /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
433        /// they are tagged with modifiers.
434        semanticHighlighting_operator_enable: bool = true,
435
436        /// Use specialized semantic tokens for operators.
437        ///
438        /// When enabled, rust-analyzer will emit special token types for operator tokens instead
439        /// of the generic `operator` token type.
440        semanticHighlighting_operator_specialization_enable: bool = false,
441
442        /// Use semantic tokens for punctuation.
443        ///
444        /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
445        /// they are tagged with modifiers or have a special role.
446        semanticHighlighting_punctuation_enable: bool = false,
447
448        /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
449        /// calls.
450        semanticHighlighting_punctuation_separate_macro_bang: bool = false,
451
452        /// Use specialized semantic tokens for punctuation.
453        ///
454        /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
455        /// of the generic `punctuation` token type.
456        semanticHighlighting_punctuation_specialization_enable: bool = false,
457
458        /// Use semantic tokens for strings.
459        ///
460        /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
461        /// By disabling semantic tokens for strings, other grammars can be used to highlight
462        /// their contents.
463        semanticHighlighting_strings_enable: bool = true,
464
465        /// Show full signature of the callable. Only shows parameters if disabled.
466        signatureInfo_detail: SignatureDetail = SignatureDetail::Full,
467
468        /// Show documentation.
469        signatureInfo_documentation_enable: bool = true,
470
471        /// Specify the characters allowed to invoke special on typing triggers.
472        ///
473        /// - typing `=` after `let` tries to smartly add `;` if `=` is followed by an existing
474        ///   expression
475        /// - typing `=` between two expressions adds `;` when in statement position
476        /// - typing `=` to turn an assignment into an equality comparison removes `;` when in
477        ///   expression position
478        /// - typing `.` in a chain method call auto-indents
479        /// - typing `{` or `(` in front of an expression inserts a closing `}` or `)` after the
480        ///   expression
481        /// - typing `{` in a use item adds a closing `}` in the right place
482        /// - typing `>` to complete a return type `->` will insert a whitespace after it
483        /// - typing `<` in a path or type position inserts a closing `>` after the path or type.
484        typing_triggerChars: Option<String> = Some("=.".to_owned()),
485
486
487        /// Configure a command that rust-analyzer can invoke to
488        /// obtain configuration.
489        ///
490        /// This is an alternative to manually generating
491        /// `rust-project.json`: it enables rust-analyzer to generate
492        /// rust-project.json on the fly, and regenerate it when
493        /// switching or modifying projects.
494        ///
495        /// This is an object with three fields:
496        ///
497        /// * `command`: the shell command to invoke
498        ///
499        /// * `filesToWatch`: which build system-specific files should
500        /// be watched to trigger regenerating the configuration
501        ///
502        /// * `progressLabel`: the name of the command, used in
503        /// progress indicators in the IDE
504        ///
505        /// Here's an example of a valid configuration:
506        ///
507        /// ```json
508        /// "rust-analyzer.workspace.discoverConfig": {
509        ///     "command": [
510        ///         "rust-project",
511        ///         "develop-json",
512        ///         "{arg}"
513        ///     ],
514        ///     "progressLabel": "buck2/rust-project",
515        ///     "filesToWatch": [
516        ///         "BUCK"
517        ///     ]
518        /// }
519        /// ```
520        ///
521        /// ## Argument Substitutions
522        ///
523        /// If `command` includes the argument `{arg}`, that argument will be substituted
524        /// with the JSON-serialized form of the following enum:
525        ///
526        /// ```norun
527        /// #[derive(PartialEq, Clone, Debug, Serialize)]
528        /// #[serde(rename_all = "camelCase")]
529        /// pub enum DiscoverArgument {
530        ///    Path(AbsPathBuf),
531        ///    Buildfile(AbsPathBuf),
532        /// }
533        /// ```
534        ///
535        /// rust-analyzer will use the path invocation to find and
536        /// generate a `rust-project.json` and therefore a
537        /// workspace. Example:
538        ///
539        ///
540        /// ```norun
541        /// rust-project develop-json '{ "path": "myproject/src/main.rs" }'
542        /// ```
543        ///
544        /// rust-analyzer will use build file invocations to update an
545        /// existing workspace. Example:
546        ///
547        /// Or with a build file and the configuration above:
548        ///
549        /// ```norun
550        /// rust-project develop-json '{ "buildfile": "myproject/BUCK" }'
551        /// ```
552        ///
553        /// As a reference for implementors, buck2's `rust-project`
554        /// will likely be useful:
555        /// <https://github.com/facebook/buck2/tree/main/integrations/rust-project>.
556        ///
557        /// ## Discover Command Output
558        ///
559        /// **Warning**: This format is provisional and subject to change.
560        ///
561        /// The discover command should output JSON objects, one per
562        /// line (JSONL format). These objects should correspond to
563        /// this Rust data type:
564        ///
565        /// ```norun
566        /// #[derive(Debug, Clone, Deserialize, Serialize)]
567        /// #[serde(tag = "kind")]
568        /// #[serde(rename_all = "snake_case")]
569        /// enum DiscoverProjectData {
570        ///     Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },
571        ///     Error { error: String, source: Option<String> },
572        ///     Progress { message: String },
573        /// }
574        /// ```
575        ///
576        /// For example, a progress event:
577        ///
578        /// ```json
579        /// {"kind":"progress","message":"generating rust-project.json"}
580        /// ```
581        ///
582        /// A finished event can look like this (expanded and
583        /// commented for readability):
584        ///
585        /// ```json
586        /// {
587        ///     // the internally-tagged representation of the enum.
588        ///     "kind": "finished",
589        ///     // the file used by a non-Cargo build system to define
590        ///     // a package or target.
591        ///     "buildfile": "rust-analyzer/BUCK",
592        ///     // the contents of a rust-project.json, elided for brevity
593        ///     "project": {
594        ///         "sysroot": "foo",
595        ///         "crates": []
596        ///     }
597        /// }
598        /// ```
599        ///
600        /// Only the finished event is required, but the other
601        /// variants are encouraged to give users more feedback about
602        /// progress or errors.
603        workspace_discoverConfig: Option<DiscoverWorkspaceConfig> = None,
604    }
605}
606
607config_data! {
608    /// Local configurations can be defined per `SourceRoot`. This almost always corresponds to a `Crate`.
609    local: struct LocalDefaultConfigData <- LocalConfigInput ->  {
610        /// Insert #[must_use] when generating `as_` methods for enum variants.
611        assist_emitMustUse: bool = false,
612
613        /// Placeholder expression to use for missing expressions in assists.
614        assist_expressionFillDefault: ExprFillDefaultDef = ExprFillDefaultDef::Todo,
615
616        /// Prefer to use `Self` over the type name when inserting a type (e.g. in "fill match arms" assist).
617        assist_preferSelf: bool = false,
618
619        /// Enable borrow checking for term search code assists. If set to false, also there will be
620        /// more suggestions, but some of them may not borrow-check.
621        assist_termSearch_borrowcheck: bool = true,
622
623        /// Term search fuel in "units of work" for assists (Defaults to 1800).
624        assist_termSearch_fuel: usize = 1800,
625
626        /// Automatically add a semicolon when completing unit-returning functions.
627        ///
628        /// In `match` arms it completes a comma instead.
629        completion_addSemicolonToUnit: bool = true,
630
631        /// Show method calls and field accesses completions with `await` prefixed to them when
632        /// completing on a future.
633        completion_autoAwait_enable: bool = true,
634
635        /// Show method call completions with `iter()` or `into_iter()` prefixed to them when
636        /// completing on a type that has them.
637        completion_autoIter_enable: bool = true,
638
639        /// Show completions that automatically add imports when completed.
640        ///
641        /// Note that your client must specify the `additionalTextEdits` LSP client capability to
642        /// truly have this feature enabled.
643        completion_autoimport_enable: bool = true,
644
645        /// A list of full paths to items to exclude from auto-importing completions.
646        ///
647        /// Traits in this list won't have their methods suggested in completions unless the trait
648        /// is in scope.
649        ///
650        /// You can either specify a string path which defaults to type "always" or use the more
651        /// verbose form `{ "path": "path::to::item", type: "always" }`.
652        ///
653        /// For traits the type "methods" can be used to only exclude the methods but not the trait
654        /// itself.
655        ///
656        /// This setting also inherits `#rust-analyzer.completion.excludeTraits#`.
657        completion_autoimport_exclude: Vec<AutoImportExclusion> = vec![
658            AutoImportExclusion::Verbose { path: "core::borrow::Borrow".to_owned(), r#type: AutoImportExclusionType::Methods },
659            AutoImportExclusion::Verbose { path: "core::borrow::BorrowMut".to_owned(), r#type: AutoImportExclusionType::Methods },
660        ],
661
662        /// Show method calls and field access completions with `self` prefixed to them when
663        /// inside a method.
664        completion_autoself_enable: bool = true,
665
666        /// Add parenthesis and argument snippets when completing function.
667        completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments,
668
669        /// A list of full paths to traits whose methods to exclude from completion.
670        ///
671        /// Methods from these traits won't be completed, even if the trait is in scope. However,
672        /// they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or
673        /// `T where T: Trait`.
674        ///
675        /// Note that the trait themselves can still be completed.
676        completion_excludeTraits: Vec<String> = Vec::new(),
677
678        /// Show full function / method signatures in completion docs.
679        completion_fullFunctionSignatures_enable: bool = false,
680
681        /// Omit deprecated items from completions. By default they are marked as deprecated but not
682        /// hidden.
683        completion_hideDeprecated: bool = false,
684
685        /// Maximum number of completions to return. If `None`, the limit is infinite.
686        completion_limit: Option<usize> = None,
687
688        /// Show postfix snippets like `dbg`, `if`, `not`, etc.
689        completion_postfix_enable: bool = true,
690
691        /// Show completions of private items and fields that are defined in the current workspace
692        /// even if they are not visible at the current position.
693        completion_privateEditable_enable: bool = false,
694
695        /// Enable term search based snippets like `Some(foo.bar().baz())`.
696        completion_termSearch_enable: bool = false,
697
698        /// Term search fuel in "units of work" for autocompletion (Defaults to 1000).
699        completion_termSearch_fuel: usize = 1000,
700
701        /// List of rust-analyzer diagnostics to disable.
702        diagnostics_disabled: FxHashSet<String> = FxHashSet::default(),
703
704        /// Show native rust-analyzer diagnostics.
705        diagnostics_enable: bool = true,
706
707        /// Show experimental rust-analyzer diagnostics that might have more false positives than
708        /// usual.
709        diagnostics_experimental_enable: bool = false,
710
711        /// Map of prefixes to be substituted when parsing diagnostic file paths. This should be the
712        /// reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
713        diagnostics_remapPrefix: FxHashMap<String, String> = FxHashMap::default(),
714
715        /// Run additional style lints.
716        diagnostics_styleLints_enable: bool = false,
717
718        /// List of warnings that should be displayed with hint severity.
719        ///
720        /// The warnings will be indicated by faded text or three dots in code and will not show up
721        /// in the `Problems Panel`.
722        diagnostics_warningsAsHint: Vec<String> = vec![],
723
724        /// List of warnings that should be displayed with info severity.
725        ///
726        /// The warnings will be indicated by a blue squiggly underline in code and a blue icon in
727        /// the `Problems Panel`.
728        diagnostics_warningsAsInfo: Vec<String> = vec![],
729
730        /// Enforce the import granularity setting for all files. If set to false rust-analyzer will
731        /// try to keep import styles consistent per file.
732        imports_granularity_enforce: bool = false,
733
734        /// How imports should be grouped into use statements.
735        imports_granularity_group: ImportGranularityDef = ImportGranularityDef::Crate,
736
737        /// Group inserted imports by the [following
738        /// order](https://rust-analyzer.github.io/book/features.html#auto-import). Groups are
739        /// separated by newlines.
740        imports_group_enable: bool = true,
741
742        /// Allow import insertion to merge new imports into single path glob imports like `use
743        /// std::fmt::*;`.
744        imports_merge_glob: bool = true,
745
746        /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate.
747        imports_preferNoStd | imports_prefer_no_std: bool = false,
748
749        /// Prefer import paths containing a `prelude` module.
750        imports_preferPrelude: bool = false,
751
752        /// The path structure for newly inserted paths to use.
753        imports_prefix: ImportPrefixDef = ImportPrefixDef::ByCrate,
754
755        /// Prefix external (including std, core) crate imports with `::`.
756        ///
757        /// E.g. `use ::std::io::Read;`.
758        imports_prefixExternPrelude: bool = false,
759
760        /// Whether to warn when a rename will cause conflicts (change the meaning of the code).
761        rename_showConflicts: bool = true,
762    }
763}
764
765config_data! {
766    workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> {
767        /// Pass `--all-targets` to cargo invocation.
768        cargo_allTargets: bool           = true,
769        /// Automatically refresh project info via `cargo metadata` on
770        /// `Cargo.toml` or `.cargo/config.toml` changes.
771        cargo_autoreload: bool           = true,
772        /// Run build scripts (`build.rs`) for more precise code analysis.
773        cargo_buildScripts_enable: bool  = true,
774        /// Specifies the invocation strategy to use when running the build scripts command.
775        /// If `per_workspace` is set, the command will be executed for each Rust workspace with the
776        /// workspace as the working directory.
777        /// If `once` is set, the command will be executed once with the opened project as the
778        /// working directory.
779        /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
780        /// is set.
781        cargo_buildScripts_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace,
782        /// Override the command rust-analyzer uses to run build scripts and
783        /// build procedural macros. The command is required to output json
784        /// and should therefore include `--message-format=json` or a similar
785        /// option.
786        ///
787        /// If there are multiple linked projects/workspaces, this command is invoked for
788        /// each of them, with the working directory being the workspace root
789        /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten
790        /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`.
791        ///
792        /// By default, a cargo invocation will be constructed for the configured
793        /// targets and features, with the following base command line:
794        ///
795        /// ```bash
796        /// cargo check --quiet --workspace --message-format=json --all-targets --keep-going
797        /// ```
798        ///
799        /// Note: The option must be specified as an array of command line arguments, with
800        /// the first argument being the name of the command to run.
801        cargo_buildScripts_overrideCommand: Option<Vec<String>> = None,
802        /// Rerun proc-macros building/build-scripts running when proc-macro
803        /// or build-script sources change and are saved.
804        cargo_buildScripts_rebuildOnSave: bool = true,
805        /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
806        /// avoid checking unnecessary things.
807        cargo_buildScripts_useRustcWrapper: bool = true,
808        /// List of cfg options to enable with the given values.
809        ///
810        /// To enable a name without a value, use `"key"`.
811        /// To enable a name with a value, use `"key=value"`.
812        /// To disable, prefix the entry with a `!`.
813        cargo_cfgs: Vec<String> = {
814            vec!["debug_assertions".into(), "miri".into()]
815        },
816        /// Extra arguments that are passed to every cargo invocation.
817        cargo_extraArgs: Vec<String> = vec![],
818        /// Extra environment variables that will be set when running cargo, rustc
819        /// or other commands within the workspace. Useful for setting RUSTFLAGS.
820        cargo_extraEnv: FxHashMap<String, Option<String>> = FxHashMap::default(),
821        /// List of features to activate.
822        ///
823        /// Set this to `"all"` to pass `--all-features` to cargo.
824        cargo_features: CargoFeaturesDef      = CargoFeaturesDef::Selected(vec![]),
825        /// Whether to pass `--no-default-features` to cargo.
826        cargo_noDefaultFeatures: bool    = false,
827        /// Whether to skip fetching dependencies. If set to "true", the analysis is performed
828        /// entirely offline, and Cargo metadata for dependencies is not fetched.
829        cargo_noDeps: bool = false,
830        /// Relative path to the sysroot, or "discover" to try to automatically find it via
831        /// "rustc --print sysroot".
832        ///
833        /// Unsetting this disables sysroot loading.
834        ///
835        /// This option does not take effect until rust-analyzer is restarted.
836        cargo_sysroot: Option<String>    = Some("discover".to_owned()),
837        /// Relative path to the sysroot library sources. If left unset, this will default to
838        /// `{cargo.sysroot}/lib/rustlib/src/rust/library`.
839        ///
840        /// This option does not take effect until rust-analyzer is restarted.
841        cargo_sysrootSrc: Option<String>    = None,
842        /// Compilation target override (target tuple).
843        // FIXME(@poliorcetics): move to multiple targets here too, but this will need more work
844        // than `checkOnSave_target`
845        cargo_target: Option<String>     = None,
846        /// Optional path to a rust-analyzer specific target directory.
847        /// This prevents rust-analyzer's `cargo check` and initial build-script and proc-macro
848        /// building from locking the `Cargo.lock` at the expense of duplicating build artifacts.
849        ///
850        /// Set to `true` to use a subdirectory of the existing target directory or
851        /// set to a path relative to the workspace to use that path.
852        cargo_targetDir | ra_ap_rust_analyzerTargetDir: Option<TargetDirectory> = None,
853
854        /// Set `cfg(test)` for local crates. Defaults to true.
855        cfg_setTest: bool = true,
856
857        /// Run the check command for diagnostics on save.
858        checkOnSave | checkOnSave_enable: bool                         = true,
859
860
861        /// Check all targets and tests (`--all-targets`). Defaults to
862        /// `#rust-analyzer.cargo.allTargets#`.
863        check_allTargets | checkOnSave_allTargets: Option<bool>          = None,
864        /// Cargo command to use for `cargo check`.
865        check_command | checkOnSave_command: String                      = "check".to_owned(),
866        /// Extra arguments for `cargo check`.
867        check_extraArgs | checkOnSave_extraArgs: Vec<String>             = vec![],
868        /// Extra environment variables that will be set when running `cargo check`.
869        /// Extends `#rust-analyzer.cargo.extraEnv#`.
870        check_extraEnv | checkOnSave_extraEnv: FxHashMap<String, Option<String>> = FxHashMap::default(),
871        /// List of features to activate. Defaults to
872        /// `#rust-analyzer.cargo.features#`.
873        ///
874        /// Set to `"all"` to pass `--all-features` to Cargo.
875        check_features | checkOnSave_features: Option<CargoFeaturesDef>  = None,
876        /// List of `cargo check` (or other command specified in `check.command`) diagnostics to ignore.
877        ///
878        /// For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,...
879        check_ignore: FxHashSet<String> = FxHashSet::default(),
880        /// Specifies the invocation strategy to use when running the check command.
881        /// If `per_workspace` is set, the command will be executed for each workspace.
882        /// If `once` is set, the command will be executed once.
883        /// This config only has an effect when `#rust-analyzer.check.overrideCommand#`
884        /// is set.
885        check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace,
886        /// Whether to pass `--no-default-features` to Cargo. Defaults to
887        /// `#rust-analyzer.cargo.noDefaultFeatures#`.
888        check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool>         = None,
889        /// Override the command rust-analyzer uses instead of `cargo check` for
890        /// diagnostics on save. The command is required to output json and
891        /// should therefore include `--message-format=json` or a similar option
892        /// (if your client supports the `colorDiagnosticOutput` experimental
893        /// capability, you can use `--message-format=json-diagnostic-rendered-ansi`).
894        ///
895        /// If you're changing this because you're using some tool wrapping
896        /// Cargo, you might also want to change
897        /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
898        ///
899        /// If there are multiple linked projects/workspaces, this command is invoked for
900        /// each of them, with the working directory being the workspace root
901        /// (i.e., the folder containing the `Cargo.toml`). This can be overwritten
902        /// by changing `#rust-analyzer.check.invocationStrategy#`.
903        ///
904        /// It supports two interpolation syntaxes, both mainly intended to be used with
905        /// [non-Cargo build systems](./non_cargo_based_projects.md):
906        ///
907        /// - If `{saved_file}` is part of the command, rust-analyzer will pass
908        ///   the absolute path of the saved file to the provided command.
909        ///   (A previous version, `$saved_file`, also works.)
910        /// - If `{label}` is part of the command, rust-analyzer will pass the
911        ///   Cargo package ID, which can be used with `cargo check -p`, or a build label from
912        ///   `rust-project.json`. If `{label}` is included, rust-analyzer behaves much like
913        ///   [`"rust-analyzer.check.workspace": false`](#check.workspace).
914        ///
915        ///
916        ///
917        /// An example command would be:
918        ///
919        /// ```bash
920        /// cargo check --workspace --message-format=json --all-targets
921        /// ```
922        ///
923        /// Note: The option must be specified as an array of command line arguments, with
924        /// the first argument being the name of the command to run.
925        check_overrideCommand | checkOnSave_overrideCommand: Option<Vec<String>>             = None,
926        /// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.
927        ///
928        /// Can be a single target, e.g. `"x86_64-unknown-linux-gnu"` or a list of targets, e.g.
929        /// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`.
930        ///
931        /// Aliased as `"checkOnSave.targets"`.
932        check_targets | checkOnSave_targets | checkOnSave_target: Option<CheckOnSaveTargets> = None,
933        /// Whether `--workspace` should be passed to `cargo check`.
934        /// If false, `-p <package>` will be passed instead if applicable. In case it is not, no
935        /// check will be performed.
936        check_workspace: bool = true,
937
938        /// Exclude all locals from document symbol search.
939        document_symbol_search_excludeLocals: bool = true,
940
941        /// These proc-macros will be ignored when trying to expand them.
942        ///
943        /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
944        procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>>          = FxHashMap::default(),
945
946        /// Subcommand used for bench runnables instead of `bench`.
947        runnables_bench_command: String = "bench".to_owned(),
948        /// Override the command used for bench runnables.
949        /// The first element of the array should be the program to execute (for example, `cargo`).
950        ///
951        /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
952        /// replace the package name, target option (such as `--bin` or `--example`), the target name and
953        /// the test name (name of test function or test mod path).
954        runnables_bench_overrideCommand: Option<Vec<String>> = None,
955        /// Command to be executed instead of 'cargo' for runnables.
956        runnables_command: Option<String> = None,
957        /// Override the command used for bench runnables.
958        /// The first element of the array should be the program to execute (for example, `cargo`).
959        ///
960        /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
961        /// replace the package name, target option (such as `--bin` or `--example`), the target name and
962        /// the test name (name of test function or test mod path).
963        runnables_doctest_overrideCommand: Option<Vec<String>> = None,
964        /// Additional arguments to be passed to cargo for runnables such as
965        /// tests or binaries. For example, it may be `--release`.
966        runnables_extraArgs: Vec<String>   = vec![],
967        /// Additional arguments to be passed through Cargo to launched tests, benchmarks, or
968        /// doc-tests.
969        ///
970        /// Unless the launched target uses a
971        /// [custom test harness](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-harness-field),
972        /// they will end up being interpreted as options to
973        /// [`rustc`’s built-in test harness (“libtest”)](https://doc.rust-lang.org/rustc/tests/index.html#cli-arguments).
974        runnables_extraTestBinaryArgs: Vec<String> = vec!["--nocapture".to_owned()],
975        /// Subcommand used for test runnables instead of `test`.
976        runnables_test_command: String = "test".to_owned(),
977        /// Override the command used for test runnables.
978        /// The first element of the array should be the program to execute (for example, `cargo`).
979        ///
980        /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
981        /// replace the package name, target option (such as `--bin` or `--example`), the target name and
982        /// the test name (name of test function or test mod path).
983        runnables_test_overrideCommand: Option<Vec<String>> = None,
984
985        /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
986        /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
987        /// is installed.
988        ///
989        /// Any project which uses rust-analyzer with the rustcPrivate
990        /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
991        ///
992        /// This option does not take effect until rust-analyzer is restarted.
993        rustc_source: Option<String> = None,
994
995        /// Additional arguments to `rustfmt`.
996        rustfmt_extraArgs: Vec<String>               = vec![],
997        /// Advanced option, fully override the command rust-analyzer uses for
998        /// formatting. This should be the equivalent of `rustfmt` here, and
999        /// not that of `cargo fmt`. The file contents will be passed on the
1000        /// standard input and the formatted result will be read from the
1001        /// standard output.
1002        ///
1003        /// Note: The option must be specified as an array of command line arguments, with
1004        /// the first argument being the name of the command to run.
1005        rustfmt_overrideCommand: Option<Vec<String>> = None,
1006        /// Enables the use of rustfmt's unstable range formatting command for the
1007        /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
1008        /// available on a nightly build.
1009        rustfmt_rangeFormatting_enable: bool = false,
1010
1011        /// Additional paths to include in the VFS. Generally for code that is
1012        /// generated or otherwise managed by a build system outside of Cargo,
1013        /// though Cargo might be the eventual consumer.
1014        vfs_extraIncludes: Vec<String> = vec![],
1015
1016        /// Exclude all imports from workspace symbol search.
1017        ///
1018        /// In addition to regular imports (which are always excluded),
1019        /// this option removes public imports (better known as re-exports)
1020        /// and removes imports that rename the imported symbol.
1021        workspace_symbol_search_excludeImports: bool = false,
1022        /// Workspace symbol search kind.
1023        workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = WorkspaceSymbolSearchKindDef::OnlyTypes,
1024        /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
1025        /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
1026        /// Other clients requires all results upfront and might require a higher limit.
1027        workspace_symbol_search_limit: usize = 128,
1028        /// Workspace symbol search scope.
1029        workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = WorkspaceSymbolSearchScopeDef::Workspace,
1030    }
1031}
1032
1033config_data! {
1034    /// Configs that only make sense when they are set by a client. As such they can only be defined
1035    /// by setting them using client's settings (e.g `settings.json` on VS Code).
1036    client: struct ClientDefaultConfigData <- ClientConfigInput -> {
1037
1038        /// Controls file watching implementation.
1039        files_watcher: FilesWatcherDef = FilesWatcherDef::Client,
1040
1041
1042    }
1043}
1044
1045#[derive(Debug)]
1046pub enum RatomlFileKind {
1047    Workspace,
1048    Crate,
1049}
1050
1051#[derive(Debug, Clone)]
1052#[allow(clippy::large_enum_variant)]
1053enum RatomlFile {
1054    Workspace(WorkspaceLocalConfigInput),
1055    Crate(LocalConfigInput),
1056}
1057
1058#[derive(Clone, Debug)]
1059struct ClientInfo {
1060    name: String,
1061    version: Option<Version>,
1062}
1063
1064#[derive(Clone)]
1065pub struct Config {
1066    /// Projects that have a Cargo.toml or a rust-project.json in a
1067    /// parent directory, so we can discover them by walking the
1068    /// file system.
1069    discovered_projects_from_filesystem: Vec<ProjectManifest>,
1070    /// Projects whose configuration was generated by a command
1071    /// configured in discoverConfig.
1072    discovered_projects_from_command: Vec<ProjectJsonFromCommand>,
1073    /// The workspace roots as registered by the LSP client
1074    workspace_roots: Vec<AbsPathBuf>,
1075    caps: ClientCapabilities,
1076    /// The LSP root path, deprecated in favor of `workspace_roots`
1077    root_path: AbsPathBuf,
1078    snippets: Vec<Snippet>,
1079    client_info: Option<ClientInfo>,
1080
1081    default_config: &'static DefaultConfigData,
1082    /// Config node that obtains its initial value during the server initialization and
1083    /// by receiving a `lsp_types::notification::DidChangeConfiguration`.
1084    client_config: (FullConfigInput, ConfigErrors),
1085
1086    /// Config node whose values apply to **every** Rust project.
1087    user_config: Option<(GlobalWorkspaceLocalConfigInput, ConfigErrors)>,
1088
1089    ratoml_file: FxHashMap<SourceRootId, (RatomlFile, ConfigErrors)>,
1090
1091    /// Clone of the value that is stored inside a `GlobalState`.
1092    source_root_parent_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
1093
1094    /// Use case : It is an error to have an empty value for `check_command`.
1095    /// Since it is a `global` command at the moment, its final value can only be determined by
1096    /// traversing through `global` configs and the `client` config. However the non-null value constraint
1097    /// is config level agnostic, so this requires an independent error storage
1098    validation_errors: ConfigErrors,
1099
1100    detached_files: Vec<AbsPathBuf>,
1101}
1102
1103impl fmt::Debug for Config {
1104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1105        f.debug_struct("Config")
1106            .field("discovered_projects_from_filesystem", &self.discovered_projects_from_filesystem)
1107            .field("discovered_projects_from_command", &self.discovered_projects_from_command)
1108            .field("workspace_roots", &self.workspace_roots)
1109            .field("caps", &self.caps)
1110            .field("root_path", &self.root_path)
1111            .field("snippets", &self.snippets)
1112            .field("client_info", &self.client_info)
1113            .field("client_config", &self.client_config)
1114            .field("user_config", &self.user_config)
1115            .field("ratoml_file", &self.ratoml_file)
1116            .field("source_root_parent_map", &self.source_root_parent_map)
1117            .field("validation_errors", &self.validation_errors)
1118            .field("detached_files", &self.detached_files)
1119            .finish()
1120    }
1121}
1122
1123// Delegate capability fetching methods
1124impl std::ops::Deref for Config {
1125    type Target = ClientCapabilities;
1126
1127    fn deref(&self) -> &Self::Target {
1128        &self.caps
1129    }
1130}
1131
1132impl Config {
1133    /// Path to the user configuration dir. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer` in Linux.
1134    pub fn user_config_dir_path() -> Option<AbsPathBuf> {
1135        let user_config_path = if let Some(path) = env::var_os("__TEST_RA_USER_CONFIG_DIR") {
1136            std::path::PathBuf::from(path)
1137        } else {
1138            dirs::config_dir()?.join("rust-analyzer")
1139        };
1140        Some(AbsPathBuf::assert_utf8(user_config_path))
1141    }
1142
1143    pub fn same_source_root_parent_map(
1144        &self,
1145        other: &Arc<FxHashMap<SourceRootId, SourceRootId>>,
1146    ) -> bool {
1147        Arc::ptr_eq(&self.source_root_parent_map, other)
1148    }
1149
1150    // FIXME @alibektas : Server's health uses error sink but in other places it is not used atm.
1151    /// Changes made to client and global configurations will partially not be reflected even after `.apply_change()` was called.
1152    /// The return tuple's bool component signals whether the `GlobalState` should call its `update_configuration()` method.
1153    fn apply_change_with_sink(&self, change: ConfigChange) -> (Config, bool) {
1154        let mut config = self.clone();
1155        config.validation_errors = ConfigErrors::default();
1156
1157        let mut should_update = false;
1158
1159        if let Some(change) = change.user_config_change {
1160            tracing::info!("updating config from user config toml: {:#}", change);
1161            if let Ok(table) = toml::from_str(&change) {
1162                let mut toml_errors = vec![];
1163                validate_toml_table(
1164                    GlobalWorkspaceLocalConfigInput::FIELDS,
1165                    &table,
1166                    &mut String::new(),
1167                    &mut toml_errors,
1168                );
1169                config.user_config = Some((
1170                    GlobalWorkspaceLocalConfigInput::from_toml(table, &mut toml_errors),
1171                    ConfigErrors(
1172                        toml_errors
1173                            .into_iter()
1174                            .map(|(a, b)| ConfigErrorInner::Toml { config_key: a, error: b })
1175                            .map(Arc::new)
1176                            .collect(),
1177                    ),
1178                ));
1179                should_update = true;
1180            }
1181        }
1182
1183        if let Some(mut json) = change.client_config_change {
1184            tracing::info!("updating config from JSON: {:#}", json);
1185
1186            if !(json.is_null() || json.as_object().is_some_and(|it| it.is_empty())) {
1187                let detached_files = get_field_json::<Vec<Utf8PathBuf>>(
1188                    &mut json,
1189                    &mut Vec::new(),
1190                    "detachedFiles",
1191                    None,
1192                )
1193                .unwrap_or_default()
1194                .into_iter()
1195                .map(AbsPathBuf::assert)
1196                .collect();
1197
1198                patch_old_style::patch_json_for_outdated_configs(&mut json);
1199
1200                let mut json_errors = vec![];
1201
1202                let input = FullConfigInput::from_json(json, &mut json_errors);
1203
1204                // IMPORTANT : This holds as long as ` completion_snippets_custom` is declared `client`.
1205                config.snippets.clear();
1206
1207                let snips = input
1208                    .global
1209                    .completion_snippets_custom
1210                    .as_ref()
1211                    .unwrap_or(&self.default_config.global.completion_snippets_custom);
1212                #[allow(dead_code)]
1213                let _ = Self::completion_snippets_custom;
1214                for (name, def) in snips.iter() {
1215                    if def.prefix.is_empty() && def.postfix.is_empty() {
1216                        continue;
1217                    }
1218                    let scope = match def.scope {
1219                        SnippetScopeDef::Expr => SnippetScope::Expr,
1220                        SnippetScopeDef::Type => SnippetScope::Type,
1221                        SnippetScopeDef::Item => SnippetScope::Item,
1222                    };
1223                    match Snippet::new(
1224                        &def.prefix,
1225                        &def.postfix,
1226                        &def.body,
1227                        def.description.as_ref().unwrap_or(name),
1228                        &def.requires,
1229                        scope,
1230                    ) {
1231                        Some(snippet) => config.snippets.push(snippet),
1232                        None => json_errors.push((
1233                            name.to_owned(),
1234                            <serde_json::Error as serde::de::Error>::custom(format!(
1235                                "snippet {name} is invalid or triggers are missing",
1236                            )),
1237                        )),
1238                    }
1239                }
1240
1241                config.client_config = (
1242                    input,
1243                    ConfigErrors(
1244                        json_errors
1245                            .into_iter()
1246                            .map(|(a, b)| ConfigErrorInner::Json { config_key: a, error: b })
1247                            .map(Arc::new)
1248                            .collect(),
1249                    ),
1250                );
1251                config.detached_files = detached_files;
1252            }
1253            should_update = true;
1254        }
1255
1256        if let Some(change) = change.ratoml_file_change {
1257            for (source_root_id, (kind, _, text)) in change {
1258                match kind {
1259                    RatomlFileKind::Crate => {
1260                        if let Some(text) = text {
1261                            let mut toml_errors = vec![];
1262                            tracing::info!("updating ra-toml crate config: {:#}", text);
1263                            match toml::from_str(&text) {
1264                                Ok(table) => {
1265                                    validate_toml_table(
1266                                        &[LocalConfigInput::FIELDS],
1267                                        &table,
1268                                        &mut String::new(),
1269                                        &mut toml_errors,
1270                                    );
1271                                    config.ratoml_file.insert(
1272                                        source_root_id,
1273                                        (
1274                                            RatomlFile::Crate(LocalConfigInput::from_toml(
1275                                                &table,
1276                                                &mut toml_errors,
1277                                            )),
1278                                            ConfigErrors(
1279                                                toml_errors
1280                                                    .into_iter()
1281                                                    .map(|(a, b)| ConfigErrorInner::Toml {
1282                                                        config_key: a,
1283                                                        error: b,
1284                                                    })
1285                                                    .map(Arc::new)
1286                                                    .collect(),
1287                                            ),
1288                                        ),
1289                                    );
1290                                }
1291                                Err(e) => {
1292                                    config.validation_errors.0.push(
1293                                        ConfigErrorInner::ParseError {
1294                                            reason: e.message().to_owned(),
1295                                        }
1296                                        .into(),
1297                                    );
1298                                }
1299                            }
1300                        }
1301                    }
1302                    RatomlFileKind::Workspace => {
1303                        if let Some(text) = text {
1304                            tracing::info!("updating ra-toml workspace config: {:#}", text);
1305                            let mut toml_errors = vec![];
1306                            match toml::from_str(&text) {
1307                                Ok(table) => {
1308                                    validate_toml_table(
1309                                        WorkspaceLocalConfigInput::FIELDS,
1310                                        &table,
1311                                        &mut String::new(),
1312                                        &mut toml_errors,
1313                                    );
1314                                    config.ratoml_file.insert(
1315                                        source_root_id,
1316                                        (
1317                                            RatomlFile::Workspace(
1318                                                WorkspaceLocalConfigInput::from_toml(
1319                                                    table,
1320                                                    &mut toml_errors,
1321                                                ),
1322                                            ),
1323                                            ConfigErrors(
1324                                                toml_errors
1325                                                    .into_iter()
1326                                                    .map(|(a, b)| ConfigErrorInner::Toml {
1327                                                        config_key: a,
1328                                                        error: b,
1329                                                    })
1330                                                    .map(Arc::new)
1331                                                    .collect(),
1332                                            ),
1333                                        ),
1334                                    );
1335                                    should_update = true;
1336                                }
1337                                Err(e) => {
1338                                    config.validation_errors.0.push(
1339                                        ConfigErrorInner::ParseError {
1340                                            reason: e.message().to_owned(),
1341                                        }
1342                                        .into(),
1343                                    );
1344                                }
1345                            }
1346                        }
1347                    }
1348                }
1349            }
1350        }
1351
1352        if let Some(source_root_map) = change.source_map_change {
1353            config.source_root_parent_map = source_root_map;
1354        }
1355
1356        if config.check_command(None).is_empty() {
1357            config.validation_errors.0.push(Arc::new(ConfigErrorInner::Json {
1358                config_key: "/check/command".to_owned(),
1359                error: serde_json::Error::custom("expected a non-empty string"),
1360            }));
1361        }
1362
1363        (config, should_update)
1364    }
1365
1366    /// Given `change` this generates a new `Config`, thereby collecting errors of type `ConfigError`.
1367    /// If there are changes that have global/client level effect, the last component of the return type
1368    /// will be set to `true`, which should be used by the `GlobalState` to update itself.
1369    pub fn apply_change(&self, change: ConfigChange) -> (Config, ConfigErrors, bool) {
1370        let (config, should_update) = self.apply_change_with_sink(change);
1371        let e = ConfigErrors(
1372            config
1373                .client_config
1374                .1
1375                .0
1376                .iter()
1377                .chain(config.user_config.as_ref().into_iter().flat_map(|it| it.1.0.iter()))
1378                .chain(config.ratoml_file.values().flat_map(|it| it.1.0.iter()))
1379                .chain(config.validation_errors.0.iter())
1380                .cloned()
1381                .collect(),
1382        );
1383        (config, e, should_update)
1384    }
1385
1386    pub fn add_discovered_project_from_command(
1387        &mut self,
1388        data: ProjectJsonData,
1389        buildfile: AbsPathBuf,
1390    ) {
1391        for proj in self.discovered_projects_from_command.iter_mut() {
1392            if proj.buildfile == buildfile {
1393                proj.data = data;
1394                return;
1395            }
1396        }
1397
1398        self.discovered_projects_from_command.push(ProjectJsonFromCommand { data, buildfile });
1399    }
1400
1401    pub fn workspace_roots(&self) -> &[AbsPathBuf] {
1402        &self.workspace_roots
1403    }
1404}
1405
1406#[derive(Default, Debug)]
1407pub struct ConfigChange {
1408    user_config_change: Option<Arc<str>>,
1409    client_config_change: Option<serde_json::Value>,
1410    ratoml_file_change:
1411        Option<FxHashMap<SourceRootId, (RatomlFileKind, VfsPath, Option<Arc<str>>)>>,
1412    source_map_change: Option<Arc<FxHashMap<SourceRootId, SourceRootId>>>,
1413}
1414
1415impl ConfigChange {
1416    pub fn change_ratoml(
1417        &mut self,
1418        source_root: SourceRootId,
1419        vfs_path: VfsPath,
1420        content: Option<Arc<str>>,
1421    ) -> Option<(RatomlFileKind, VfsPath, Option<Arc<str>>)> {
1422        self.ratoml_file_change
1423            .get_or_insert_with(Default::default)
1424            .insert(source_root, (RatomlFileKind::Crate, vfs_path, content))
1425    }
1426
1427    pub fn change_user_config(&mut self, content: Option<Arc<str>>) {
1428        assert!(self.user_config_change.is_none()); // Otherwise it is a double write.
1429        self.user_config_change = content;
1430    }
1431
1432    pub fn change_workspace_ratoml(
1433        &mut self,
1434        source_root: SourceRootId,
1435        vfs_path: VfsPath,
1436        content: Option<Arc<str>>,
1437    ) -> Option<(RatomlFileKind, VfsPath, Option<Arc<str>>)> {
1438        self.ratoml_file_change
1439            .get_or_insert_with(Default::default)
1440            .insert(source_root, (RatomlFileKind::Workspace, vfs_path, content))
1441    }
1442
1443    pub fn change_client_config(&mut self, change: serde_json::Value) {
1444        self.client_config_change = Some(change);
1445    }
1446
1447    pub fn change_source_root_parent_map(
1448        &mut self,
1449        source_root_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
1450    ) {
1451        assert!(self.source_map_change.is_none());
1452        self.source_map_change = Some(source_root_map);
1453    }
1454}
1455
1456#[derive(Debug, Clone, Eq, PartialEq)]
1457pub enum LinkedProject {
1458    ProjectManifest(ProjectManifest),
1459    InlineProjectJson(ProjectJson),
1460}
1461
1462impl From<ProjectManifest> for LinkedProject {
1463    fn from(v: ProjectManifest) -> Self {
1464        LinkedProject::ProjectManifest(v)
1465    }
1466}
1467
1468impl From<ProjectJson> for LinkedProject {
1469    fn from(v: ProjectJson) -> Self {
1470        LinkedProject::InlineProjectJson(v)
1471    }
1472}
1473
1474#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1475#[serde(rename_all = "camelCase")]
1476pub struct DiscoverWorkspaceConfig {
1477    pub command: Vec<String>,
1478    pub progress_label: String,
1479    pub files_to_watch: Vec<String>,
1480}
1481
1482pub struct CallInfoConfig {
1483    pub params_only: bool,
1484    pub docs: bool,
1485}
1486
1487#[derive(Clone, Debug, PartialEq, Eq)]
1488pub struct LensConfig {
1489    // runnables
1490    pub run: bool,
1491    pub debug: bool,
1492    pub update_test: bool,
1493    pub interpret: bool,
1494
1495    // implementations
1496    pub implementations: bool,
1497
1498    // references
1499    pub method_refs: bool,
1500    pub refs_adt: bool,   // for Struct, Enum, Union and Trait
1501    pub refs_trait: bool, // for Struct, Enum, Union and Trait
1502    pub enum_variant_refs: bool,
1503
1504    // annotations
1505    pub location: AnnotationLocation,
1506    pub filter_adjacent_derive_implementations: bool,
1507}
1508
1509#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1510#[serde(rename_all = "snake_case")]
1511pub enum AnnotationLocation {
1512    AboveName,
1513    AboveWholeItem,
1514}
1515
1516impl From<AnnotationLocation> for ide::AnnotationLocation {
1517    fn from(location: AnnotationLocation) -> Self {
1518        match location {
1519            AnnotationLocation::AboveName => ide::AnnotationLocation::AboveName,
1520            AnnotationLocation::AboveWholeItem => ide::AnnotationLocation::AboveWholeItem,
1521        }
1522    }
1523}
1524
1525impl LensConfig {
1526    pub fn any(&self) -> bool {
1527        self.run
1528            || self.debug
1529            || self.update_test
1530            || self.implementations
1531            || self.method_refs
1532            || self.refs_adt
1533            || self.refs_trait
1534            || self.enum_variant_refs
1535    }
1536
1537    pub fn none(&self) -> bool {
1538        !self.any()
1539    }
1540
1541    pub fn runnable(&self) -> bool {
1542        self.run || self.debug || self.update_test
1543    }
1544
1545    pub fn references(&self) -> bool {
1546        self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
1547    }
1548
1549    pub fn into_annotation_config<'a>(
1550        self,
1551        binary_target: bool,
1552        minicore: MiniCore<'a>,
1553    ) -> AnnotationConfig<'a> {
1554        AnnotationConfig {
1555            binary_target,
1556            annotate_runnables: self.runnable(),
1557            annotate_impls: self.implementations,
1558            annotate_references: self.refs_adt,
1559            annotate_method_references: self.method_refs,
1560            annotate_enum_variant_references: self.enum_variant_refs,
1561            location: self.location.into(),
1562            minicore,
1563            filter_adjacent_derive_implementations: self.filter_adjacent_derive_implementations,
1564        }
1565    }
1566}
1567
1568#[derive(Clone, Debug, PartialEq, Eq)]
1569pub struct HoverActionsConfig {
1570    pub implementations: bool,
1571    pub references: bool,
1572    pub run: bool,
1573    pub debug: bool,
1574    pub update_test: bool,
1575    pub goto_type_def: bool,
1576}
1577
1578impl HoverActionsConfig {
1579    pub const NO_ACTIONS: Self = Self {
1580        implementations: false,
1581        references: false,
1582        run: false,
1583        debug: false,
1584        update_test: false,
1585        goto_type_def: false,
1586    };
1587
1588    pub fn any(&self) -> bool {
1589        self.implementations || self.references || self.runnable() || self.goto_type_def
1590    }
1591
1592    pub fn none(&self) -> bool {
1593        !self.any()
1594    }
1595
1596    pub fn runnable(&self) -> bool {
1597        self.run || self.debug || self.update_test
1598    }
1599}
1600
1601#[derive(Debug, Clone)]
1602pub struct FilesConfig {
1603    pub watcher: FilesWatcher,
1604    pub exclude: Vec<AbsPathBuf>,
1605}
1606
1607#[derive(Debug, Clone)]
1608pub enum FilesWatcher {
1609    Client,
1610    Server,
1611}
1612
1613/// Configuration for document symbol search requests.
1614#[derive(Debug, Clone)]
1615pub struct DocumentSymbolConfig {
1616    /// Should locals be excluded.
1617    pub search_exclude_locals: bool,
1618}
1619
1620#[derive(Debug, Clone)]
1621pub struct NotificationsConfig {
1622    pub cargo_toml_not_found: bool,
1623}
1624
1625#[derive(Debug, Clone)]
1626pub enum RustfmtConfig {
1627    Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
1628    CustomCommand { command: String, args: Vec<String> },
1629}
1630
1631/// Configuration for runnable items, such as `main` function or tests.
1632#[derive(Debug, Clone)]
1633pub struct RunnablesConfig {
1634    /// Custom command to be executed instead of `cargo` for runnables.
1635    pub override_cargo: Option<String>,
1636    /// Additional arguments for the `cargo`, e.g. `--release`.
1637    pub cargo_extra_args: Vec<String>,
1638    /// Additional arguments for the binary being run, if it is a test or benchmark.
1639    pub extra_test_binary_args: Vec<String>,
1640    /// Subcommand used for doctest runnables instead of `test`.
1641    pub test_command: String,
1642    /// Override the command used for test runnables.
1643    pub test_override_command: Option<Vec<String>>,
1644    /// Subcommand used for doctest runnables instead of `bench`.
1645    pub bench_command: String,
1646    /// Override the command used for bench runnables.
1647    pub bench_override_command: Option<Vec<String>>,
1648    /// Override the command used for doctest runnables.
1649    pub doc_test_override_command: Option<Vec<String>>,
1650}
1651
1652/// Configuration for workspace symbol search requests.
1653#[derive(Debug, Clone)]
1654pub struct WorkspaceSymbolConfig {
1655    /// Should imports be excluded.
1656    pub search_exclude_imports: bool,
1657    /// In what scope should the symbol be searched in.
1658    pub search_scope: WorkspaceSymbolSearchScope,
1659    /// What kind of symbol is being searched for.
1660    pub search_kind: WorkspaceSymbolSearchKind,
1661    /// How many items are returned at most.
1662    pub search_limit: usize,
1663}
1664#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1665pub struct ClientCommandsConfig {
1666    pub run_single: bool,
1667    pub debug_single: bool,
1668    pub show_reference: bool,
1669    pub goto_location: bool,
1670    pub trigger_parameter_hints: bool,
1671    pub rename: bool,
1672}
1673
1674#[derive(Debug)]
1675pub enum ConfigErrorInner {
1676    Json { config_key: String, error: serde_json::Error },
1677    Toml { config_key: String, error: toml::de::Error },
1678    ParseError { reason: String },
1679}
1680
1681#[derive(Clone, Debug, Default)]
1682pub struct ConfigErrors(Vec<Arc<ConfigErrorInner>>);
1683
1684impl ConfigErrors {
1685    pub fn is_empty(&self) -> bool {
1686        self.0.is_empty()
1687    }
1688}
1689
1690impl fmt::Display for ConfigErrors {
1691    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1692        let errors = self.0.iter().format_with("\n", |inner, f| {
1693            match &**inner {
1694                ConfigErrorInner::Json { config_key: key, error: e } => {
1695                    f(key)?;
1696                    f(&": ")?;
1697                    f(e)
1698                }
1699                ConfigErrorInner::Toml { config_key: key, error: e } => {
1700                    f(key)?;
1701                    f(&": ")?;
1702                    f(e)
1703                }
1704                ConfigErrorInner::ParseError { reason } => f(reason),
1705            }?;
1706            f(&";")
1707        });
1708        write!(f, "invalid config value{}:\n{}", if self.0.len() == 1 { "" } else { "s" }, errors)
1709    }
1710}
1711
1712impl std::error::Error for ConfigErrors {}
1713
1714impl Config {
1715    pub fn new(
1716        root_path: AbsPathBuf,
1717        caps: lsp_types::ClientCapabilities,
1718        workspace_roots: Vec<AbsPathBuf>,
1719        client_info: Option<lsp_types::ClientInfo>,
1720    ) -> Self {
1721        static DEFAULT_CONFIG_DATA: OnceLock<&'static DefaultConfigData> = OnceLock::new();
1722
1723        Config {
1724            caps: ClientCapabilities::new(caps),
1725            discovered_projects_from_filesystem: Vec::new(),
1726            discovered_projects_from_command: Vec::new(),
1727            root_path,
1728            snippets: Default::default(),
1729            workspace_roots,
1730            client_info: client_info.map(|it| ClientInfo {
1731                name: it.name,
1732                version: it.version.as_deref().map(Version::parse).and_then(Result::ok),
1733            }),
1734            client_config: (FullConfigInput::default(), ConfigErrors(vec![])),
1735            default_config: DEFAULT_CONFIG_DATA.get_or_init(|| Box::leak(Box::default())),
1736            source_root_parent_map: Arc::new(FxHashMap::default()),
1737            user_config: None,
1738            detached_files: Default::default(),
1739            validation_errors: Default::default(),
1740            ratoml_file: Default::default(),
1741        }
1742    }
1743
1744    pub fn rediscover_workspaces(&mut self) {
1745        let discovered = ProjectManifest::discover_all(&self.workspace_roots);
1746        tracing::info!("discovered projects: {:?}", discovered);
1747        if discovered.is_empty() {
1748            tracing::error!("failed to find any projects in {:?}", &self.workspace_roots);
1749        }
1750        self.discovered_projects_from_filesystem = discovered;
1751    }
1752
1753    pub fn remove_workspace(&mut self, path: &AbsPath) {
1754        if let Some(position) = self.workspace_roots.iter().position(|it| it == path) {
1755            self.workspace_roots.remove(position);
1756        }
1757    }
1758
1759    pub fn add_workspaces(&mut self, paths: impl Iterator<Item = AbsPathBuf>) {
1760        self.workspace_roots.extend(paths);
1761    }
1762
1763    pub fn json_schema() -> serde_json::Value {
1764        let mut s = FullConfigInput::json_schema();
1765
1766        fn sort_objects_by_field(json: &mut serde_json::Value) {
1767            if let serde_json::Value::Object(object) = json {
1768                let old = std::mem::take(object);
1769                old.into_iter().sorted_by(|(k, _), (k2, _)| k.cmp(k2)).for_each(|(k, mut v)| {
1770                    sort_objects_by_field(&mut v);
1771                    object.insert(k, v);
1772                });
1773            }
1774        }
1775        sort_objects_by_field(&mut s);
1776        s
1777    }
1778
1779    pub fn root_path(&self) -> &AbsPathBuf {
1780        // We should probably use `workspace_roots` here if set
1781        &self.root_path
1782    }
1783
1784    pub fn caps(&self) -> &ClientCapabilities {
1785        &self.caps
1786    }
1787
1788    pub fn assist(&self, source_root: Option<SourceRootId>) -> AssistConfig {
1789        AssistConfig {
1790            snippet_cap: self.snippet_cap(),
1791            allowed: None,
1792            insert_use: self.insert_use_config(source_root),
1793            prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
1794            assist_emit_must_use: self.assist_emitMustUse(source_root).to_owned(),
1795            prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
1796            prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
1797            term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
1798            term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(),
1799            code_action_grouping: self.code_action_group(),
1800            expr_fill_default: match self.assist_expressionFillDefault(source_root) {
1801                ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
1802                ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
1803                ExprFillDefaultDef::Underscore => ExprFillDefaultMode::Underscore,
1804            },
1805            prefer_self_ty: *self.assist_preferSelf(source_root),
1806            show_rename_conflicts: *self.rename_showConflicts(source_root),
1807        }
1808    }
1809
1810    pub fn rename(&self, source_root: Option<SourceRootId>) -> RenameConfig {
1811        RenameConfig {
1812            prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
1813            prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
1814            prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
1815            show_conflicts: *self.rename_showConflicts(source_root),
1816        }
1817    }
1818
1819    pub fn call_hierarchy<'a>(&self, minicore: MiniCore<'a>) -> CallHierarchyConfig<'a> {
1820        CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned(), minicore }
1821    }
1822
1823    pub fn completion<'a>(
1824        &'a self,
1825        source_root: Option<SourceRootId>,
1826        minicore: MiniCore<'a>,
1827    ) -> CompletionConfig<'a> {
1828        let client_capability_fields = self.completion_resolve_support_properties();
1829        CompletionConfig {
1830            enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(),
1831            enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned()
1832                && self.caps.has_completion_item_resolve_additionalTextEdits(),
1833            enable_self_on_the_fly: self.completion_autoself_enable(source_root).to_owned(),
1834            enable_auto_iter: *self.completion_autoIter_enable(source_root),
1835            enable_auto_await: *self.completion_autoAwait_enable(source_root),
1836            enable_private_editable: self.completion_privateEditable_enable(source_root).to_owned(),
1837            full_function_signatures: self
1838                .completion_fullFunctionSignatures_enable(source_root)
1839                .to_owned(),
1840            callable: match self.completion_callable_snippets(source_root) {
1841                CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1842                CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1843                CallableCompletionDef::None => None,
1844            },
1845            add_semicolon_to_unit: *self.completion_addSemicolonToUnit(source_root),
1846            snippet_cap: SnippetCap::new(self.completion_snippet()),
1847            insert_use: self.insert_use_config(source_root),
1848            prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
1849            prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
1850            prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
1851            snippets: self.snippets.clone().to_vec(),
1852            limit: self.completion_limit(source_root).to_owned(),
1853            enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
1854            term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
1855            fields_to_resolve: if self.client_is_neovim() {
1856                CompletionFieldsToResolve::empty()
1857            } else {
1858                CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields)
1859            },
1860            exclude_flyimport: self
1861                .completion_autoimport_exclude(source_root)
1862                .iter()
1863                .map(|it| match it {
1864                    AutoImportExclusion::Path(path) => {
1865                        (path.clone(), ide_completion::AutoImportExclusionType::Always)
1866                    }
1867                    AutoImportExclusion::Verbose { path, r#type } => (
1868                        path.clone(),
1869                        match r#type {
1870                            AutoImportExclusionType::Always => {
1871                                ide_completion::AutoImportExclusionType::Always
1872                            }
1873                            AutoImportExclusionType::Methods => {
1874                                ide_completion::AutoImportExclusionType::Methods
1875                            }
1876                        },
1877                    ),
1878                })
1879                .collect(),
1880            exclude_traits: self.completion_excludeTraits(source_root),
1881            minicore,
1882        }
1883    }
1884
1885    pub fn completion_hide_deprecated(&self) -> bool {
1886        *self.completion_hideDeprecated(None)
1887    }
1888
1889    pub fn detached_files(&self) -> &Vec<AbsPathBuf> {
1890        // FIXME @alibektas : This is the only config that is confusing. If it's a proper configuration
1891        // why is it not among the others? If it's client only which I doubt it is current state should be alright
1892        &self.detached_files
1893    }
1894
1895    pub fn diagnostics(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig {
1896        DiagnosticsConfig {
1897            enabled: *self.diagnostics_enable(source_root),
1898            proc_attr_macros_enabled: self.expand_proc_attr_macros(),
1899            proc_macros_enabled: *self.procMacro_enable(),
1900            disable_experimental: !self.diagnostics_experimental_enable(source_root),
1901            disabled: self.diagnostics_disabled(source_root).clone(),
1902            expr_fill_default: match self.assist_expressionFillDefault(source_root) {
1903                ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
1904                ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
1905                ExprFillDefaultDef::Underscore => ExprFillDefaultMode::Underscore,
1906            },
1907            snippet_cap: self.snippet_cap(),
1908            insert_use: self.insert_use_config(source_root),
1909            prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
1910            prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
1911            prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
1912            style_lints: self.diagnostics_styleLints_enable(source_root).to_owned(),
1913            term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
1914            term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(),
1915            show_rename_conflicts: *self.rename_showConflicts(source_root),
1916        }
1917    }
1918
1919    pub fn diagnostic_fixes(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig {
1920        // We always want to show quickfixes for diagnostics, even when diagnostics/experimental diagnostics are disabled.
1921        DiagnosticsConfig {
1922            enabled: true,
1923            disable_experimental: false,
1924            ..self.diagnostics(source_root)
1925        }
1926    }
1927
1928    pub fn expand_proc_attr_macros(&self) -> bool {
1929        self.procMacro_enable().to_owned() && self.procMacro_attributes_enable().to_owned()
1930    }
1931
1932    pub fn highlight_related(&self, _source_root: Option<SourceRootId>) -> HighlightRelatedConfig {
1933        HighlightRelatedConfig {
1934            references: self.highlightRelated_references_enable().to_owned(),
1935            break_points: self.highlightRelated_breakPoints_enable().to_owned(),
1936            exit_points: self.highlightRelated_exitPoints_enable().to_owned(),
1937            yield_points: self.highlightRelated_yieldPoints_enable().to_owned(),
1938            closure_captures: self.highlightRelated_closureCaptures_enable().to_owned(),
1939            branch_exit_points: self.highlightRelated_branchExitPoints_enable().to_owned(),
1940        }
1941    }
1942
1943    pub fn hover_actions(&self) -> HoverActionsConfig {
1944        let enable = self.caps.hover_actions() && self.hover_actions_enable().to_owned();
1945        HoverActionsConfig {
1946            implementations: enable && self.hover_actions_implementations_enable().to_owned(),
1947            references: enable && self.hover_actions_references_enable().to_owned(),
1948            run: enable && self.hover_actions_run_enable().to_owned(),
1949            debug: enable && self.hover_actions_debug_enable().to_owned(),
1950            update_test: enable
1951                && self.hover_actions_run_enable().to_owned()
1952                && self.hover_actions_updateTest_enable().to_owned(),
1953            goto_type_def: enable && self.hover_actions_gotoTypeDef_enable().to_owned(),
1954        }
1955    }
1956
1957    pub fn hover<'a>(&self, minicore: MiniCore<'a>) -> HoverConfig<'a> {
1958        let mem_kind = |kind| match kind {
1959            MemoryLayoutHoverRenderKindDef::Both => MemoryLayoutHoverRenderKind::Both,
1960            MemoryLayoutHoverRenderKindDef::Decimal => MemoryLayoutHoverRenderKind::Decimal,
1961            MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal,
1962        };
1963        HoverConfig {
1964            links_in_hover: self.hover_links_enable().to_owned(),
1965            memory_layout: self.hover_memoryLayout_enable().then_some(MemoryLayoutHoverConfig {
1966                size: self.hover_memoryLayout_size().map(mem_kind),
1967                offset: self.hover_memoryLayout_offset().map(mem_kind),
1968                alignment: self.hover_memoryLayout_alignment().map(mem_kind),
1969                padding: self.hover_memoryLayout_padding().map(mem_kind),
1970                niches: self.hover_memoryLayout_niches().unwrap_or_default(),
1971            }),
1972            documentation: self.hover_documentation_enable().to_owned(),
1973            format: {
1974                if self.caps.hover_markdown_support() {
1975                    HoverDocFormat::Markdown
1976                } else {
1977                    HoverDocFormat::PlainText
1978                }
1979            },
1980            keywords: self.hover_documentation_keywords_enable().to_owned(),
1981            max_trait_assoc_items_count: self.hover_show_traitAssocItems().to_owned(),
1982            max_fields_count: self.hover_show_fields().to_owned(),
1983            max_enum_variants_count: self.hover_show_enumVariants().to_owned(),
1984            max_subst_ty_len: match self.hover_maxSubstitutionLength() {
1985                Some(MaxSubstitutionLength::Hide) => ide::SubstTyLen::Hide,
1986                Some(MaxSubstitutionLength::Limit(limit)) => ide::SubstTyLen::LimitTo(*limit),
1987                None => ide::SubstTyLen::Unlimited,
1988            },
1989            show_drop_glue: *self.hover_dropGlue_enable(),
1990            minicore,
1991        }
1992    }
1993
1994    pub fn goto_definition<'a>(&self, minicore: MiniCore<'a>) -> GotoDefinitionConfig<'a> {
1995        GotoDefinitionConfig { minicore }
1996    }
1997
1998    pub fn inlay_hints<'a>(&self, minicore: MiniCore<'a>) -> InlayHintsConfig<'a> {
1999        let client_capability_fields = self.inlay_hint_resolve_support_properties();
2000
2001        InlayHintsConfig {
2002            render_colons: self.inlayHints_renderColons().to_owned(),
2003            type_hints: self.inlayHints_typeHints_enable().to_owned(),
2004            sized_bound: self.inlayHints_implicitSizedBoundHints_enable().to_owned(),
2005            parameter_hints: self.inlayHints_parameterHints_enable().to_owned(),
2006            parameter_hints_for_missing_arguments: self
2007                .inlayHints_parameterHints_missingArguments_enable()
2008                .to_owned(),
2009            generic_parameter_hints: GenericParameterHints {
2010                type_hints: self.inlayHints_genericParameterHints_type_enable().to_owned(),
2011                lifetime_hints: self.inlayHints_genericParameterHints_lifetime_enable().to_owned(),
2012                const_hints: self.inlayHints_genericParameterHints_const_enable().to_owned(),
2013            },
2014            chaining_hints: self.inlayHints_chainingHints_enable().to_owned(),
2015            discriminant_hints: match self.inlayHints_discriminantHints_enable() {
2016                DiscriminantHintsDef::Always => ide::DiscriminantHints::Always,
2017                DiscriminantHintsDef::Never => ide::DiscriminantHints::Never,
2018                DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless,
2019            },
2020            closure_return_type_hints: match self.inlayHints_closureReturnTypeHints_enable() {
2021                ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
2022                ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
2023                ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
2024            },
2025            lifetime_elision_hints: match self.inlayHints_lifetimeElisionHints_enable() {
2026                LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
2027                LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
2028                LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
2029            },
2030            hide_named_constructor_hints: self
2031                .inlayHints_typeHints_hideNamedConstructor()
2032                .to_owned(),
2033            hide_inferred_type_hints: self.inlayHints_typeHints_hideInferredTypes().to_owned(),
2034            hide_closure_initialization_hints: self
2035                .inlayHints_typeHints_hideClosureInitialization()
2036                .to_owned(),
2037            hide_closure_parameter_hints: self
2038                .inlayHints_typeHints_hideClosureParameter()
2039                .to_owned(),
2040            closure_style: match self.inlayHints_closureStyle() {
2041                ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn,
2042                ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation,
2043                ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId,
2044                ClosureStyle::Hide => hir::ClosureStyle::Hide,
2045            },
2046            closure_capture_hints: self.inlayHints_closureCaptureHints_enable().to_owned(),
2047            adjustment_hints: match self.inlayHints_expressionAdjustmentHints_enable() {
2048                AdjustmentHintsDef::Always => ide::AdjustmentHints::Always,
2049                AdjustmentHintsDef::Never => match self.inlayHints_reborrowHints_enable() {
2050                    ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => {
2051                        ide::AdjustmentHints::BorrowsOnly
2052                    }
2053                    ReborrowHintsDef::Never => ide::AdjustmentHints::Never,
2054                },
2055                AdjustmentHintsDef::Borrows => ide::AdjustmentHints::BorrowsOnly,
2056            },
2057            adjustment_hints_disable_reborrows: *self
2058                .inlayHints_expressionAdjustmentHints_disableReborrows(),
2059            adjustment_hints_mode: match self.inlayHints_expressionAdjustmentHints_mode() {
2060                AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix,
2061                AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix,
2062                AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix,
2063                AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix,
2064            },
2065            adjustment_hints_hide_outside_unsafe: self
2066                .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe()
2067                .to_owned(),
2068            binding_mode_hints: self.inlayHints_bindingModeHints_enable().to_owned(),
2069            param_names_for_lifetime_elision_hints: self
2070                .inlayHints_lifetimeElisionHints_useParameterNames()
2071                .to_owned(),
2072            max_length: self.inlayHints_maxLength().to_owned(),
2073            closing_brace_hints_min_lines: if self.inlayHints_closingBraceHints_enable().to_owned()
2074            {
2075                Some(self.inlayHints_closingBraceHints_minLines().to_owned())
2076            } else {
2077                None
2078            },
2079            fields_to_resolve: InlayFieldsToResolve::from_client_capabilities(
2080                &client_capability_fields,
2081            ),
2082            implicit_drop_hints: self.inlayHints_implicitDrops_enable().to_owned(),
2083            implied_dyn_trait_hints: self.inlayHints_impliedDynTraitHints_enable().to_owned(),
2084            range_exclusive_hints: self.inlayHints_rangeExclusiveHints_enable().to_owned(),
2085            minicore,
2086        }
2087    }
2088
2089    fn insert_use_config(&self, source_root: Option<SourceRootId>) -> InsertUseConfig {
2090        InsertUseConfig {
2091            granularity: match self.imports_granularity_group(source_root) {
2092                ImportGranularityDef::Item | ImportGranularityDef::Preserve => {
2093                    ImportGranularity::Item
2094                }
2095                ImportGranularityDef::Crate => ImportGranularity::Crate,
2096                ImportGranularityDef::Module => ImportGranularity::Module,
2097                ImportGranularityDef::One => ImportGranularity::One,
2098            },
2099            enforce_granularity: self.imports_granularity_enforce(source_root).to_owned(),
2100            prefix_kind: match self.imports_prefix(source_root) {
2101                ImportPrefixDef::Plain => PrefixKind::Plain,
2102                ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
2103                ImportPrefixDef::BySelf => PrefixKind::BySelf,
2104            },
2105            group: self.imports_group_enable(source_root).to_owned(),
2106            skip_glob_imports: !self.imports_merge_glob(source_root),
2107        }
2108    }
2109
2110    pub fn join_lines(&self) -> JoinLinesConfig {
2111        JoinLinesConfig {
2112            join_else_if: self.joinLines_joinElseIf().to_owned(),
2113            remove_trailing_comma: self.joinLines_removeTrailingComma().to_owned(),
2114            unwrap_trivial_blocks: self.joinLines_unwrapTrivialBlock().to_owned(),
2115            join_assignments: self.joinLines_joinAssignments().to_owned(),
2116        }
2117    }
2118
2119    pub fn highlighting_non_standard_tokens(&self) -> bool {
2120        self.semanticHighlighting_nonStandardTokens().to_owned()
2121    }
2122
2123    pub fn highlighting_config<'a>(&self, minicore: MiniCore<'a>) -> HighlightConfig<'a> {
2124        HighlightConfig {
2125            strings: self.semanticHighlighting_strings_enable().to_owned(),
2126            comments: self.semanticHighlighting_comments_enable().to_owned(),
2127            punctuation: self.semanticHighlighting_punctuation_enable().to_owned(),
2128            specialize_punctuation: self
2129                .semanticHighlighting_punctuation_specialization_enable()
2130                .to_owned(),
2131            macro_bang: self.semanticHighlighting_punctuation_separate_macro_bang().to_owned(),
2132            operator: self.semanticHighlighting_operator_enable().to_owned(),
2133            specialize_operator: self
2134                .semanticHighlighting_operator_specialization_enable()
2135                .to_owned(),
2136            inject_doc_comment: self.semanticHighlighting_doc_comment_inject_enable().to_owned(),
2137            syntactic_name_ref_highlighting: false,
2138            minicore,
2139        }
2140    }
2141
2142    pub fn has_linked_projects(&self) -> bool {
2143        !self.linkedProjects().is_empty()
2144    }
2145
2146    pub fn linked_manifests(&self) -> impl Iterator<Item = &Utf8Path> + '_ {
2147        self.linkedProjects().iter().filter_map(|it| match it {
2148            ManifestOrProjectJson::Manifest(p) => Some(&**p),
2149            // despite having a buildfile, using this variant as a manifest
2150            // will fail.
2151            ManifestOrProjectJson::DiscoveredProjectJson { .. } => None,
2152            ManifestOrProjectJson::ProjectJson { .. } => None,
2153        })
2154    }
2155
2156    pub fn has_linked_project_jsons(&self) -> bool {
2157        self.linkedProjects()
2158            .iter()
2159            .any(|it| matches!(it, ManifestOrProjectJson::ProjectJson { .. }))
2160    }
2161
2162    pub fn discover_workspace_config(&self) -> Option<&DiscoverWorkspaceConfig> {
2163        self.workspace_discoverConfig().as_ref()
2164    }
2165
2166    fn discovered_projects(&self) -> Vec<ManifestOrProjectJson> {
2167        let exclude_dirs: Vec<_> =
2168            self.files_exclude().iter().map(|p| self.root_path.join(p)).collect();
2169
2170        let mut projects = vec![];
2171        for fs_proj in &self.discovered_projects_from_filesystem {
2172            let manifest_path = fs_proj.manifest_path();
2173            if exclude_dirs.iter().any(|p| manifest_path.starts_with(p)) {
2174                continue;
2175            }
2176
2177            let buf: Utf8PathBuf = manifest_path.to_path_buf().into();
2178            projects.push(ManifestOrProjectJson::Manifest(buf));
2179        }
2180
2181        for dis_proj in &self.discovered_projects_from_command {
2182            projects.push(ManifestOrProjectJson::DiscoveredProjectJson {
2183                data: dis_proj.data.clone(),
2184                buildfile: dis_proj.buildfile.clone(),
2185            });
2186        }
2187
2188        projects
2189    }
2190
2191    pub fn linked_or_discovered_projects(&self) -> Vec<LinkedProject> {
2192        let linked_projects = self.linkedProjects();
2193        let projects = if linked_projects.is_empty() {
2194            self.discovered_projects()
2195        } else {
2196            linked_projects.clone()
2197        };
2198
2199        projects
2200            .iter()
2201            .filter_map(|linked_project| match linked_project {
2202                ManifestOrProjectJson::Manifest(it) => {
2203                    let path = self.root_path.join(it);
2204                    ProjectManifest::from_manifest_file(path)
2205                        .map_err(|e| tracing::error!("failed to load linked project: {}", e))
2206                        .ok()
2207                        .map(Into::into)
2208                }
2209                ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile } => {
2210                    let root_path = buildfile.parent().expect("Unable to get parent of buildfile");
2211
2212                    Some(ProjectJson::new(None, root_path, data.clone()).into())
2213                }
2214                ManifestOrProjectJson::ProjectJson(it) => {
2215                    Some(ProjectJson::new(None, &self.root_path, it.clone()).into())
2216                }
2217            })
2218            .collect()
2219    }
2220
2221    pub fn prefill_caches(&self) -> bool {
2222        self.cachePriming_enable().to_owned()
2223    }
2224
2225    pub fn publish_diagnostics(&self, source_root: Option<SourceRootId>) -> bool {
2226        self.diagnostics_enable(source_root).to_owned()
2227    }
2228
2229    pub fn diagnostics_map(&self, source_root: Option<SourceRootId>) -> DiagnosticsMapConfig {
2230        DiagnosticsMapConfig {
2231            remap_prefix: self.diagnostics_remapPrefix(source_root).clone(),
2232            warnings_as_info: self.diagnostics_warningsAsInfo(source_root).clone(),
2233            warnings_as_hint: self.diagnostics_warningsAsHint(source_root).clone(),
2234            check_ignore: self.check_ignore(source_root).clone(),
2235        }
2236    }
2237
2238    pub fn extra_args(&self, source_root: Option<SourceRootId>) -> &Vec<String> {
2239        self.cargo_extraArgs(source_root)
2240    }
2241
2242    pub fn extra_env(
2243        &self,
2244        source_root: Option<SourceRootId>,
2245    ) -> &FxHashMap<String, Option<String>> {
2246        self.cargo_extraEnv(source_root)
2247    }
2248
2249    pub fn check_extra_args(&self, source_root: Option<SourceRootId>) -> Vec<String> {
2250        let mut extra_args = self.extra_args(source_root).clone();
2251        extra_args.extend_from_slice(self.check_extraArgs(source_root));
2252        extra_args
2253    }
2254
2255    pub fn check_extra_env(
2256        &self,
2257        source_root: Option<SourceRootId>,
2258    ) -> FxHashMap<String, Option<String>> {
2259        let mut extra_env = self.cargo_extraEnv(source_root).clone();
2260        extra_env.extend(self.check_extraEnv(source_root).clone());
2261        extra_env
2262    }
2263
2264    pub fn lru_parse_query_capacity(&self) -> Option<u16> {
2265        self.lru_capacity().to_owned()
2266    }
2267
2268    pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap<Box<str>, u16>> {
2269        self.lru_query_capacities().is_empty().not().then(|| self.lru_query_capacities())
2270    }
2271
2272    pub fn proc_macro_srv(&self) -> Option<AbsPathBuf> {
2273        let path = self.procMacro_server().clone()?;
2274        Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path)))
2275    }
2276
2277    pub fn dhat_output_file(&self) -> Option<AbsPathBuf> {
2278        let path = self.profiling_memoryProfile().clone()?;
2279        Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path)))
2280    }
2281
2282    pub fn ignored_proc_macros(
2283        &self,
2284        source_root: Option<SourceRootId>,
2285    ) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
2286        self.procMacro_ignored(source_root)
2287    }
2288
2289    pub fn expand_proc_macros(&self) -> bool {
2290        self.procMacro_enable().to_owned()
2291    }
2292
2293    pub fn files(&self) -> FilesConfig {
2294        FilesConfig {
2295            watcher: match self.files_watcher() {
2296                FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
2297                    FilesWatcher::Client
2298                }
2299                _ => FilesWatcher::Server,
2300            },
2301            exclude: self.excluded().collect(),
2302        }
2303    }
2304
2305    pub fn excluded(&self) -> impl Iterator<Item = AbsPathBuf> + use<'_> {
2306        self.files_exclude().iter().map(|it| self.root_path.join(it))
2307    }
2308
2309    pub fn notifications(&self) -> NotificationsConfig {
2310        NotificationsConfig {
2311            cargo_toml_not_found: self.notifications_cargoTomlNotFound().to_owned(),
2312        }
2313    }
2314
2315    pub fn cargo_autoreload_config(&self, source_root: Option<SourceRootId>) -> bool {
2316        self.cargo_autoreload(source_root).to_owned()
2317    }
2318
2319    pub fn run_build_scripts(&self, source_root: Option<SourceRootId>) -> bool {
2320        self.cargo_buildScripts_enable(source_root).to_owned() || self.procMacro_enable().to_owned()
2321    }
2322
2323    pub fn cargo(&self, source_root: Option<SourceRootId>) -> CargoConfig {
2324        let rustc_source = self.rustc_source(source_root).as_ref().map(|rustc_src| {
2325            if rustc_src == "discover" {
2326                RustLibSource::Discover
2327            } else {
2328                RustLibSource::Path(self.root_path.join(rustc_src))
2329            }
2330        });
2331        let sysroot = self.cargo_sysroot(source_root).as_ref().map(|sysroot| {
2332            if sysroot == "discover" {
2333                RustLibSource::Discover
2334            } else {
2335                RustLibSource::Path(self.root_path.join(sysroot))
2336            }
2337        });
2338        let sysroot_src =
2339            self.cargo_sysrootSrc(source_root).as_ref().map(|sysroot| self.root_path.join(sysroot));
2340        let extra_includes = self
2341            .vfs_extraIncludes(source_root)
2342            .iter()
2343            .map(String::as_str)
2344            .map(AbsPathBuf::try_from)
2345            .filter_map(Result::ok)
2346            .collect();
2347
2348        CargoConfig {
2349            all_targets: *self.cargo_allTargets(source_root),
2350            features: match &self.cargo_features(source_root) {
2351                CargoFeaturesDef::All => CargoFeatures::All,
2352                CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
2353                    features: features.clone(),
2354                    no_default_features: self.cargo_noDefaultFeatures(source_root).to_owned(),
2355                },
2356            },
2357            target: self.cargo_target(source_root).clone(),
2358            sysroot,
2359            sysroot_src,
2360            rustc_source,
2361            extra_includes,
2362            cfg_overrides: project_model::CfgOverrides {
2363                global: {
2364                    let (enabled, disabled): (Vec<_>, Vec<_>) =
2365                        self.cargo_cfgs(source_root).iter().partition_map(|s| {
2366                            s.strip_prefix("!").map_or(Either::Left(s), Either::Right)
2367                        });
2368                    CfgDiff::new(
2369                        enabled
2370                            .into_iter()
2371                            // parse any cfg setting formatted as key=value or just key (without value)
2372                            .map(|s| match s.split_once("=") {
2373                                Some((key, val)) => CfgAtom::KeyValue {
2374                                    key: Symbol::intern(key),
2375                                    value: Symbol::intern(val),
2376                                },
2377                                None => CfgAtom::Flag(Symbol::intern(s)),
2378                            })
2379                            .collect(),
2380                        disabled
2381                            .into_iter()
2382                            .map(|s| match s.split_once("=") {
2383                                Some((key, val)) => CfgAtom::KeyValue {
2384                                    key: Symbol::intern(key),
2385                                    value: Symbol::intern(val),
2386                                },
2387                                None => CfgAtom::Flag(Symbol::intern(s)),
2388                            })
2389                            .collect(),
2390                    )
2391                },
2392                selective: Default::default(),
2393            },
2394            wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(source_root),
2395            invocation_strategy: match self.cargo_buildScripts_invocationStrategy(source_root) {
2396                InvocationStrategy::Once => project_model::InvocationStrategy::Once,
2397                InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
2398            },
2399            run_build_script_command: self.cargo_buildScripts_overrideCommand(source_root).clone(),
2400            extra_args: self.cargo_extraArgs(source_root).clone(),
2401            extra_env: self.cargo_extraEnv(source_root).clone(),
2402            target_dir_config: self.target_dir_from_config(source_root),
2403            set_test: *self.cfg_setTest(source_root),
2404            no_deps: *self.cargo_noDeps(source_root),
2405        }
2406    }
2407
2408    pub fn cfg_set_test(&self, source_root: Option<SourceRootId>) -> bool {
2409        *self.cfg_setTest(source_root)
2410    }
2411
2412    pub(crate) fn completion_snippets_default() -> FxIndexMap<String, SnippetDef> {
2413        serde_json::from_str(
2414            r#"{
2415            "Ok": {
2416                "postfix": "ok",
2417                "body": "Ok(${receiver})",
2418                "description": "Wrap the expression in a `Result::Ok`",
2419                "scope": "expr"
2420            },
2421            "Box::pin": {
2422                "postfix": "pinbox",
2423                "body": "Box::pin(${receiver})",
2424                "requires": "std::boxed::Box",
2425                "description": "Put the expression into a pinned `Box`",
2426                "scope": "expr"
2427            },
2428            "Arc::new": {
2429                "postfix": "arc",
2430                "body": "Arc::new(${receiver})",
2431                "requires": "std::sync::Arc",
2432                "description": "Put the expression into an `Arc`",
2433                "scope": "expr"
2434            },
2435            "Some": {
2436                "postfix": "some",
2437                "body": "Some(${receiver})",
2438                "description": "Wrap the expression in an `Option::Some`",
2439                "scope": "expr"
2440            },
2441            "Err": {
2442                "postfix": "err",
2443                "body": "Err(${receiver})",
2444                "description": "Wrap the expression in a `Result::Err`",
2445                "scope": "expr"
2446            },
2447            "Rc::new": {
2448                "postfix": "rc",
2449                "body": "Rc::new(${receiver})",
2450                "requires": "std::rc::Rc",
2451                "description": "Put the expression into an `Rc`",
2452                "scope": "expr"
2453            }
2454        }"#,
2455        )
2456        .unwrap()
2457    }
2458
2459    pub fn rustfmt(&self, source_root_id: Option<SourceRootId>) -> RustfmtConfig {
2460        match &self.rustfmt_overrideCommand(source_root_id) {
2461            Some(args) if !args.is_empty() => {
2462                let mut args = args.clone();
2463                let command = args.remove(0);
2464                RustfmtConfig::CustomCommand { command, args }
2465            }
2466            Some(_) | None => RustfmtConfig::Rustfmt {
2467                extra_args: self.rustfmt_extraArgs(source_root_id).clone(),
2468                enable_range_formatting: *self.rustfmt_rangeFormatting_enable(source_root_id),
2469            },
2470        }
2471    }
2472
2473    pub fn flycheck_workspace(&self, source_root: Option<SourceRootId>) -> bool {
2474        *self.check_workspace(source_root)
2475    }
2476
2477    pub(crate) fn cargo_test_options(&self, source_root: Option<SourceRootId>) -> CargoOptions {
2478        CargoOptions {
2479            // Might be nice to allow users to specify test_command = "nextest"
2480            subcommand: "test".into(),
2481            target_tuples: self.cargo_target(source_root).clone().into_iter().collect(),
2482            all_targets: false,
2483            no_default_features: *self.cargo_noDefaultFeatures(source_root),
2484            all_features: matches!(self.cargo_features(source_root), CargoFeaturesDef::All),
2485            features: match self.cargo_features(source_root).clone() {
2486                CargoFeaturesDef::All => vec![],
2487                CargoFeaturesDef::Selected(it) => it,
2488            },
2489            extra_args: self.extra_args(source_root).clone(),
2490            extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
2491            extra_env: self.extra_env(source_root).clone(),
2492            target_dir_config: self.target_dir_from_config(source_root),
2493            set_test: true,
2494        }
2495    }
2496
2497    pub(crate) fn flycheck(&self, source_root: Option<SourceRootId>) -> FlycheckConfig {
2498        match &self.check_overrideCommand(source_root) {
2499            Some(args) if !args.is_empty() => {
2500                let mut args = args.clone();
2501                let command = args.remove(0);
2502                FlycheckConfig::CustomCommand {
2503                    command,
2504                    args,
2505                    extra_env: self.check_extra_env(source_root),
2506                    invocation_strategy: match self.check_invocationStrategy(source_root) {
2507                        InvocationStrategy::Once => crate::flycheck::InvocationStrategy::Once,
2508                        InvocationStrategy::PerWorkspace => {
2509                            crate::flycheck::InvocationStrategy::PerWorkspace
2510                        }
2511                    },
2512                }
2513            }
2514            Some(_) | None => FlycheckConfig::Automatic {
2515                cargo_options: CargoOptions {
2516                    subcommand: self.check_command(source_root).clone(),
2517                    target_tuples: self
2518                        .check_targets(source_root)
2519                        .clone()
2520                        .and_then(|targets| match &targets.0[..] {
2521                            [] => None,
2522                            targets => Some(targets.into()),
2523                        })
2524                        .unwrap_or_else(|| {
2525                            self.cargo_target(source_root).clone().into_iter().collect()
2526                        }),
2527                    all_targets: self
2528                        .check_allTargets(source_root)
2529                        .unwrap_or(*self.cargo_allTargets(source_root)),
2530                    no_default_features: self
2531                        .check_noDefaultFeatures(source_root)
2532                        .unwrap_or(*self.cargo_noDefaultFeatures(source_root)),
2533                    all_features: matches!(
2534                        self.check_features(source_root)
2535                            .as_ref()
2536                            .unwrap_or(self.cargo_features(source_root)),
2537                        CargoFeaturesDef::All
2538                    ),
2539                    features: match self
2540                        .check_features(source_root)
2541                        .clone()
2542                        .unwrap_or_else(|| self.cargo_features(source_root).clone())
2543                    {
2544                        CargoFeaturesDef::All => vec![],
2545                        CargoFeaturesDef::Selected(it) => it,
2546                    },
2547                    extra_args: self.check_extra_args(source_root),
2548                    extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
2549                    extra_env: self.check_extra_env(source_root),
2550                    target_dir_config: self.target_dir_from_config(source_root),
2551                    set_test: *self.cfg_setTest(source_root),
2552                },
2553                ansi_color_output: self.color_diagnostic_output(),
2554            },
2555        }
2556    }
2557
2558    fn target_dir_from_config(&self, source_root: Option<SourceRootId>) -> TargetDirectoryConfig {
2559        match &self.cargo_targetDir(source_root) {
2560            Some(TargetDirectory::UseSubdirectory(true)) => TargetDirectoryConfig::UseSubdirectory,
2561            Some(TargetDirectory::UseSubdirectory(false)) | None => TargetDirectoryConfig::None,
2562            Some(TargetDirectory::Directory(dir)) => TargetDirectoryConfig::Directory(dir.clone()),
2563        }
2564    }
2565
2566    pub fn check_on_save(&self, source_root: Option<SourceRootId>) -> bool {
2567        *self.checkOnSave(source_root)
2568    }
2569
2570    pub fn script_rebuild_on_save(&self, source_root: Option<SourceRootId>) -> bool {
2571        *self.cargo_buildScripts_rebuildOnSave(source_root)
2572    }
2573
2574    pub fn runnables(&self, source_root: Option<SourceRootId>) -> RunnablesConfig {
2575        RunnablesConfig {
2576            override_cargo: self.runnables_command(source_root).clone(),
2577            cargo_extra_args: self.runnables_extraArgs(source_root).clone(),
2578            extra_test_binary_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
2579            test_command: self.runnables_test_command(source_root).clone(),
2580            test_override_command: self.runnables_test_overrideCommand(source_root).clone(),
2581            bench_command: self.runnables_bench_command(source_root).clone(),
2582            bench_override_command: self.runnables_bench_overrideCommand(source_root).clone(),
2583            doc_test_override_command: self.runnables_doctest_overrideCommand(source_root).clone(),
2584        }
2585    }
2586
2587    pub fn find_all_refs_exclude_imports(&self) -> bool {
2588        *self.references_excludeImports()
2589    }
2590
2591    pub fn find_all_refs_exclude_tests(&self) -> bool {
2592        *self.references_excludeTests()
2593    }
2594
2595    pub fn snippet_cap(&self) -> Option<SnippetCap> {
2596        // FIXME: Also detect the proposed lsp version at caps.workspace.workspaceEdit.snippetEditSupport
2597        // once lsp-types has it.
2598        SnippetCap::new(self.snippet_text_edit())
2599    }
2600
2601    pub fn call_info(&self) -> CallInfoConfig {
2602        CallInfoConfig {
2603            params_only: matches!(self.signatureInfo_detail(), SignatureDetail::Parameters),
2604            docs: *self.signatureInfo_documentation_enable(),
2605        }
2606    }
2607
2608    pub fn lens(&self) -> LensConfig {
2609        LensConfig {
2610            run: *self.lens_enable() && *self.lens_run_enable(),
2611            debug: *self.lens_enable() && *self.lens_debug_enable(),
2612            update_test: *self.lens_enable()
2613                && *self.lens_updateTest_enable()
2614                && *self.lens_run_enable(),
2615            interpret: *self.lens_enable() && *self.lens_run_enable() && *self.interpret_tests(),
2616            implementations: *self.lens_enable() && *self.lens_implementations_enable(),
2617            method_refs: *self.lens_enable() && *self.lens_references_method_enable(),
2618            refs_adt: *self.lens_enable() && *self.lens_references_adt_enable(),
2619            refs_trait: *self.lens_enable() && *self.lens_references_trait_enable(),
2620            enum_variant_refs: *self.lens_enable() && *self.lens_references_enumVariant_enable(),
2621            location: *self.lens_location(),
2622            filter_adjacent_derive_implementations: *self
2623                .gotoImplementations_filterAdjacentDerives(),
2624        }
2625    }
2626
2627    pub fn goto_implementation(&self) -> GotoImplementationConfig {
2628        GotoImplementationConfig {
2629            filter_adjacent_derive_implementations: *self
2630                .gotoImplementations_filterAdjacentDerives(),
2631        }
2632    }
2633
2634    pub fn document_symbol(&self, source_root: Option<SourceRootId>) -> DocumentSymbolConfig {
2635        DocumentSymbolConfig {
2636            search_exclude_locals: *self.document_symbol_search_excludeLocals(source_root),
2637        }
2638    }
2639
2640    pub fn workspace_symbol(&self, source_root: Option<SourceRootId>) -> WorkspaceSymbolConfig {
2641        WorkspaceSymbolConfig {
2642            search_exclude_imports: *self.workspace_symbol_search_excludeImports(source_root),
2643            search_scope: match self.workspace_symbol_search_scope(source_root) {
2644                WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
2645                WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
2646                    WorkspaceSymbolSearchScope::WorkspaceAndDependencies
2647                }
2648            },
2649            search_kind: match self.workspace_symbol_search_kind(source_root) {
2650                WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
2651                WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
2652            },
2653            search_limit: *self.workspace_symbol_search_limit(source_root),
2654        }
2655    }
2656
2657    pub fn client_commands(&self) -> ClientCommandsConfig {
2658        let commands = self.commands().map(|it| it.commands).unwrap_or_default();
2659
2660        let get = |name: &str| commands.iter().any(|it| it == name);
2661
2662        ClientCommandsConfig {
2663            run_single: get("rust-analyzer.runSingle"),
2664            debug_single: get("rust-analyzer.debugSingle"),
2665            show_reference: get("rust-analyzer.showReferences"),
2666            goto_location: get("rust-analyzer.gotoLocation"),
2667            trigger_parameter_hints: get("rust-analyzer.triggerParameterHints"),
2668            rename: get("rust-analyzer.rename"),
2669        }
2670    }
2671
2672    pub fn prime_caches_num_threads(&self) -> usize {
2673        match self.cachePriming_numThreads() {
2674            NumThreads::Concrete(0) | NumThreads::Physical => num_cpus::get_physical(),
2675            &NumThreads::Concrete(n) => n,
2676            NumThreads::Logical => num_cpus::get(),
2677        }
2678    }
2679
2680    pub fn proc_macro_num_processes(&self) -> usize {
2681        match self.procMacro_processes() {
2682            NumProcesses::Concrete(0) | NumProcesses::Physical => num_cpus::get_physical(),
2683            &NumProcesses::Concrete(n) => n,
2684        }
2685    }
2686
2687    pub fn main_loop_num_threads(&self) -> usize {
2688        match self.numThreads() {
2689            Some(NumThreads::Concrete(0)) | None | Some(NumThreads::Physical) => {
2690                num_cpus::get_physical()
2691            }
2692            &Some(NumThreads::Concrete(n)) => n,
2693            Some(NumThreads::Logical) => num_cpus::get(),
2694        }
2695    }
2696
2697    pub fn typing_trigger_chars(&self) -> &str {
2698        self.typing_triggerChars().as_deref().unwrap_or_default()
2699    }
2700
2701    // VSCode is our reference implementation, so we allow ourselves to work around issues by
2702    // special casing certain versions
2703    pub fn visual_studio_code_version(&self) -> Option<&Version> {
2704        self.client_info
2705            .as_ref()
2706            .filter(|it| it.name.starts_with("Visual Studio Code"))
2707            .and_then(|it| it.version.as_ref())
2708    }
2709
2710    pub fn client_is_neovim(&self) -> bool {
2711        self.client_info.as_ref().map(|it| it.name == "Neovim").unwrap_or_default()
2712    }
2713}
2714// Deserialization definitions
2715
2716macro_rules! create_bool_or_string_serde {
2717    ($ident:ident<$bool:literal, $string:literal>) => {
2718        mod $ident {
2719            pub(super) fn deserialize<'de, D>(d: D) -> Result<(), D::Error>
2720            where
2721                D: serde::Deserializer<'de>,
2722            {
2723                struct V;
2724                impl<'de> serde::de::Visitor<'de> for V {
2725                    type Value = ();
2726
2727                    fn expecting(
2728                        &self,
2729                        formatter: &mut std::fmt::Formatter<'_>,
2730                    ) -> std::fmt::Result {
2731                        formatter.write_str(concat!(
2732                            stringify!($bool),
2733                            " or \"",
2734                            stringify!($string),
2735                            "\""
2736                        ))
2737                    }
2738
2739                    fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
2740                    where
2741                        E: serde::de::Error,
2742                    {
2743                        match v {
2744                            $bool => Ok(()),
2745                            _ => Err(serde::de::Error::invalid_value(
2746                                serde::de::Unexpected::Bool(v),
2747                                &self,
2748                            )),
2749                        }
2750                    }
2751
2752                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
2753                    where
2754                        E: serde::de::Error,
2755                    {
2756                        match v {
2757                            $string => Ok(()),
2758                            _ => Err(serde::de::Error::invalid_value(
2759                                serde::de::Unexpected::Str(v),
2760                                &self,
2761                            )),
2762                        }
2763                    }
2764
2765                    fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
2766                    where
2767                        A: serde::de::EnumAccess<'de>,
2768                    {
2769                        use serde::de::VariantAccess;
2770                        let (variant, va) = a.variant::<&'de str>()?;
2771                        va.unit_variant()?;
2772                        match variant {
2773                            $string => Ok(()),
2774                            _ => Err(serde::de::Error::invalid_value(
2775                                serde::de::Unexpected::Str(variant),
2776                                &self,
2777                            )),
2778                        }
2779                    }
2780                }
2781                d.deserialize_any(V)
2782            }
2783
2784            pub(super) fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
2785            where
2786                S: serde::Serializer,
2787            {
2788                serializer.serialize_str($string)
2789            }
2790        }
2791    };
2792}
2793create_bool_or_string_serde!(true_or_always<true, "always">);
2794create_bool_or_string_serde!(false_or_never<false, "never">);
2795
2796#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
2797#[serde(rename_all = "snake_case")]
2798#[derive(Default)]
2799enum SnippetScopeDef {
2800    #[default]
2801    Expr,
2802    Item,
2803    Type,
2804}
2805
2806#[derive(Serialize, Deserialize, Debug, Clone, Default)]
2807#[serde(default)]
2808pub(crate) struct SnippetDef {
2809    #[serde(with = "single_or_array")]
2810    #[serde(skip_serializing_if = "Vec::is_empty")]
2811    prefix: Vec<String>,
2812
2813    #[serde(with = "single_or_array")]
2814    #[serde(skip_serializing_if = "Vec::is_empty")]
2815    postfix: Vec<String>,
2816
2817    #[serde(with = "single_or_array")]
2818    #[serde(skip_serializing_if = "Vec::is_empty")]
2819    body: Vec<String>,
2820
2821    #[serde(with = "single_or_array")]
2822    #[serde(skip_serializing_if = "Vec::is_empty")]
2823    requires: Vec<String>,
2824
2825    #[serde(skip_serializing_if = "Option::is_none")]
2826    description: Option<String>,
2827
2828    scope: SnippetScopeDef,
2829}
2830
2831mod single_or_array {
2832    use serde::{Deserialize, Serialize};
2833
2834    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
2835    where
2836        D: serde::Deserializer<'de>,
2837    {
2838        struct SingleOrVec;
2839
2840        impl<'de> serde::de::Visitor<'de> for SingleOrVec {
2841            type Value = Vec<String>;
2842
2843            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2844                formatter.write_str("string or array of strings")
2845            }
2846
2847            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
2848            where
2849                E: serde::de::Error,
2850            {
2851                Ok(vec![value.to_owned()])
2852            }
2853
2854            fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
2855            where
2856                A: serde::de::SeqAccess<'de>,
2857            {
2858                Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
2859            }
2860        }
2861
2862        deserializer.deserialize_any(SingleOrVec)
2863    }
2864
2865    pub(super) fn serialize<S>(vec: &[String], serializer: S) -> Result<S::Ok, S::Error>
2866    where
2867        S: serde::Serializer,
2868    {
2869        match vec {
2870            // []  case is handled by skip_serializing_if
2871            [single] => serializer.serialize_str(single),
2872            slice => slice.serialize(serializer),
2873        }
2874    }
2875}
2876
2877#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
2878#[serde(untagged)]
2879enum ManifestOrProjectJson {
2880    Manifest(Utf8PathBuf),
2881    ProjectJson(ProjectJsonData),
2882    DiscoveredProjectJson {
2883        data: ProjectJsonData,
2884        #[serde(serialize_with = "serialize_abs_pathbuf")]
2885        #[serde(deserialize_with = "deserialize_abs_pathbuf")]
2886        buildfile: AbsPathBuf,
2887    },
2888}
2889
2890fn deserialize_abs_pathbuf<'de, D>(de: D) -> std::result::Result<AbsPathBuf, D::Error>
2891where
2892    D: serde::de::Deserializer<'de>,
2893{
2894    let path = String::deserialize(de)?;
2895
2896    AbsPathBuf::try_from(path.as_ref())
2897        .map_err(|err| serde::de::Error::custom(format!("invalid path name: {err:?}")))
2898}
2899
2900fn serialize_abs_pathbuf<S>(path: &AbsPathBuf, se: S) -> Result<S::Ok, S::Error>
2901where
2902    S: serde::Serializer,
2903{
2904    let path: &Utf8Path = path.as_ref();
2905    se.serialize_str(path.as_str())
2906}
2907
2908#[derive(Serialize, Deserialize, Debug, Clone)]
2909#[serde(rename_all = "snake_case")]
2910enum ExprFillDefaultDef {
2911    Todo,
2912    Default,
2913    Underscore,
2914}
2915
2916#[derive(Serialize, Deserialize, Debug, Clone)]
2917#[serde(untagged)]
2918#[serde(rename_all = "snake_case")]
2919pub enum AutoImportExclusion {
2920    Path(String),
2921    Verbose { path: String, r#type: AutoImportExclusionType },
2922}
2923
2924#[derive(Serialize, Deserialize, Debug, Clone)]
2925#[serde(rename_all = "snake_case")]
2926pub enum AutoImportExclusionType {
2927    Always,
2928    Methods,
2929}
2930
2931#[derive(Serialize, Deserialize, Debug, Clone)]
2932#[serde(rename_all = "snake_case")]
2933enum ImportGranularityDef {
2934    Preserve,
2935    Item,
2936    Crate,
2937    Module,
2938    One,
2939}
2940
2941#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
2942#[serde(rename_all = "snake_case")]
2943pub(crate) enum CallableCompletionDef {
2944    FillArguments,
2945    AddParentheses,
2946    None,
2947}
2948
2949#[derive(Serialize, Deserialize, Debug, Clone)]
2950#[serde(rename_all = "snake_case")]
2951enum CargoFeaturesDef {
2952    All,
2953    #[serde(untagged)]
2954    Selected(Vec<String>),
2955}
2956
2957#[derive(Serialize, Deserialize, Debug, Clone)]
2958#[serde(rename_all = "snake_case")]
2959pub(crate) enum InvocationStrategy {
2960    Once,
2961    PerWorkspace,
2962}
2963
2964#[derive(Serialize, Deserialize, Debug, Clone)]
2965struct CheckOnSaveTargets(#[serde(with = "single_or_array")] Vec<String>);
2966
2967#[derive(Serialize, Deserialize, Debug, Clone)]
2968#[serde(rename_all = "snake_case")]
2969enum LifetimeElisionDef {
2970    SkipTrivial,
2971    #[serde(with = "true_or_always")]
2972    #[serde(untagged)]
2973    Always,
2974    #[serde(with = "false_or_never")]
2975    #[serde(untagged)]
2976    Never,
2977}
2978
2979#[derive(Serialize, Deserialize, Debug, Clone)]
2980#[serde(rename_all = "snake_case")]
2981enum ClosureReturnTypeHintsDef {
2982    WithBlock,
2983    #[serde(with = "true_or_always")]
2984    #[serde(untagged)]
2985    Always,
2986    #[serde(with = "false_or_never")]
2987    #[serde(untagged)]
2988    Never,
2989}
2990
2991#[derive(Serialize, Deserialize, Debug, Clone)]
2992#[serde(rename_all = "snake_case")]
2993enum ClosureStyle {
2994    ImplFn,
2995    RustAnalyzer,
2996    WithId,
2997    Hide,
2998}
2999
3000#[derive(Serialize, Deserialize, Debug, Clone)]
3001#[serde(rename_all = "snake_case")]
3002enum ReborrowHintsDef {
3003    Mutable,
3004    #[serde(with = "true_or_always")]
3005    #[serde(untagged)]
3006    Always,
3007    #[serde(with = "false_or_never")]
3008    #[serde(untagged)]
3009    Never,
3010}
3011
3012#[derive(Serialize, Deserialize, Debug, Clone)]
3013#[serde(rename_all = "snake_case")]
3014enum AdjustmentHintsDef {
3015    #[serde(alias = "reborrow")]
3016    Borrows,
3017    #[serde(with = "true_or_always")]
3018    #[serde(untagged)]
3019    Always,
3020    #[serde(with = "false_or_never")]
3021    #[serde(untagged)]
3022    Never,
3023}
3024
3025#[derive(Serialize, Deserialize, Debug, Clone)]
3026#[serde(rename_all = "snake_case")]
3027enum DiscriminantHintsDef {
3028    Fieldless,
3029    #[serde(with = "true_or_always")]
3030    #[serde(untagged)]
3031    Always,
3032    #[serde(with = "false_or_never")]
3033    #[serde(untagged)]
3034    Never,
3035}
3036
3037#[derive(Serialize, Deserialize, Debug, Clone)]
3038#[serde(rename_all = "snake_case")]
3039enum AdjustmentHintsModeDef {
3040    Prefix,
3041    Postfix,
3042    PreferPrefix,
3043    PreferPostfix,
3044}
3045
3046#[derive(Serialize, Deserialize, Debug, Clone)]
3047#[serde(rename_all = "snake_case")]
3048enum FilesWatcherDef {
3049    Client,
3050    Notify,
3051    Server,
3052}
3053
3054#[derive(Serialize, Deserialize, Debug, Clone)]
3055#[serde(rename_all = "snake_case")]
3056enum ImportPrefixDef {
3057    Plain,
3058    #[serde(rename = "self")]
3059    #[serde(alias = "by_self")]
3060    BySelf,
3061    #[serde(rename = "crate")]
3062    #[serde(alias = "by_crate")]
3063    ByCrate,
3064}
3065
3066#[derive(Serialize, Deserialize, Debug, Clone)]
3067#[serde(rename_all = "snake_case")]
3068enum WorkspaceSymbolSearchScopeDef {
3069    Workspace,
3070    WorkspaceAndDependencies,
3071}
3072
3073#[derive(Serialize, Deserialize, Debug, Clone)]
3074#[serde(rename_all = "snake_case")]
3075enum SignatureDetail {
3076    Full,
3077    Parameters,
3078}
3079
3080#[derive(Serialize, Deserialize, Debug, Clone)]
3081#[serde(rename_all = "snake_case")]
3082enum WorkspaceSymbolSearchKindDef {
3083    OnlyTypes,
3084    AllSymbols,
3085}
3086
3087#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
3088#[serde(rename_all = "snake_case")]
3089enum MemoryLayoutHoverRenderKindDef {
3090    Decimal,
3091    Hexadecimal,
3092    Both,
3093}
3094
3095#[test]
3096fn untagged_option_hover_render_kind() {
3097    let hex = MemoryLayoutHoverRenderKindDef::Hexadecimal;
3098
3099    let ser = serde_json::to_string(&Some(hex)).unwrap();
3100    assert_eq!(&ser, "\"hexadecimal\"");
3101
3102    let opt: Option<_> = serde_json::from_str("\"hexadecimal\"").unwrap();
3103    assert_eq!(opt, Some(hex));
3104}
3105
3106#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
3107#[serde(rename_all = "snake_case")]
3108#[serde(untagged)]
3109pub enum TargetDirectory {
3110    UseSubdirectory(bool),
3111    Directory(Utf8PathBuf),
3112}
3113
3114#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
3115#[serde(rename_all = "snake_case")]
3116pub enum NumThreads {
3117    Physical,
3118    Logical,
3119    #[serde(untagged)]
3120    Concrete(usize),
3121}
3122
3123#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
3124#[serde(rename_all = "snake_case")]
3125pub enum NumProcesses {
3126    Physical,
3127    #[serde(untagged)]
3128    Concrete(usize),
3129}
3130
3131macro_rules! _default_val {
3132    ($default:expr, $ty:ty) => {{
3133        let default_: $ty = $default;
3134        default_
3135    }};
3136}
3137use _default_val as default_val;
3138
3139macro_rules! _default_str {
3140    ($default:expr, $ty:ty) => {{
3141        let val = default_val!($default, $ty);
3142        serde_json::to_string_pretty(&val).unwrap()
3143    }};
3144}
3145use _default_str as default_str;
3146
3147macro_rules! _impl_for_config_data {
3148    (local, $(
3149            $(#[doc=$doc:literal])*
3150            $vis:vis $field:ident : $ty:ty = $default:expr,
3151        )*
3152    ) => {
3153        impl Config {
3154            $(
3155                $($doc)*
3156                #[allow(non_snake_case)]
3157                $vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
3158                    let mut source_root = source_root.as_ref();
3159                    while let Some(sr) = source_root {
3160                        if let Some((file, _)) = self.ratoml_file.get(&sr) {
3161                            match file {
3162                                RatomlFile::Workspace(config) => {
3163                                    if let Some(v) = config.local.$field.as_ref() {
3164                                        return &v;
3165                                    }
3166                                },
3167                                RatomlFile::Crate(config) => {
3168                                    if let Some(value) = config.$field.as_ref() {
3169                                        return value;
3170                                    }
3171                                }
3172                            }
3173                        }
3174                        source_root = self.source_root_parent_map.get(&sr);
3175                    }
3176
3177                    if let Some(v) = self.client_config.0.local.$field.as_ref() {
3178                        return &v;
3179                    }
3180
3181                    if let Some((user_config, _)) = self.user_config.as_ref() {
3182                        if let Some(v) = user_config.local.$field.as_ref() {
3183                            return &v;
3184                        }
3185                    }
3186
3187                    &self.default_config.local.$field
3188                }
3189            )*
3190        }
3191    };
3192    (workspace, $(
3193            $(#[doc=$doc:literal])*
3194            $vis:vis $field:ident : $ty:ty = $default:expr,
3195        )*
3196    ) => {
3197        impl Config {
3198            $(
3199                $($doc)*
3200                #[allow(non_snake_case)]
3201                $vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
3202                    let mut source_root = source_root.as_ref();
3203                    while let Some(sr) = source_root {
3204                        if let Some((RatomlFile::Workspace(config), _)) = self.ratoml_file.get(&sr) {
3205                            if let Some(v) = config.workspace.$field.as_ref() {
3206                                return &v;
3207                            }
3208                        }
3209                        source_root = self.source_root_parent_map.get(&sr);
3210                    }
3211
3212                    if let Some(v) = self.client_config.0.workspace.$field.as_ref() {
3213                        return &v;
3214                    }
3215
3216                    if let Some((user_config, _)) = self.user_config.as_ref() {
3217                        if let Some(v) = user_config.workspace.$field.as_ref() {
3218                            return &v;
3219                        }
3220                    }
3221
3222                    &self.default_config.workspace.$field
3223                }
3224            )*
3225        }
3226    };
3227    (global, $(
3228            $(#[doc=$doc:literal])*
3229            $vis:vis $field:ident : $ty:ty = $default:expr,
3230        )*
3231    ) => {
3232        impl Config {
3233            $(
3234                $($doc)*
3235                #[allow(non_snake_case)]
3236                $vis fn $field(&self) -> &$ty {
3237                    if let Some(v) = self.client_config.0.global.$field.as_ref() {
3238                        return &v;
3239                    }
3240
3241                    if let Some((user_config, _)) = self.user_config.as_ref() {
3242                        if let Some(v) = user_config.global.$field.as_ref() {
3243                            return &v;
3244                        }
3245                    }
3246
3247
3248                    &self.default_config.global.$field
3249                }
3250            )*
3251        }
3252    };
3253    (client, $(
3254            $(#[doc=$doc:literal])*
3255            $vis:vis $field:ident : $ty:ty = $default:expr,
3256       )*
3257    ) => {
3258        impl Config {
3259            $(
3260                $($doc)*
3261                #[allow(non_snake_case)]
3262                $vis fn $field(&self) -> &$ty {
3263                    if let Some(v) = self.client_config.0.client.$field.as_ref() {
3264                        return &v;
3265                    }
3266
3267                    &self.default_config.client.$field
3268                }
3269            )*
3270        }
3271    };
3272}
3273use _impl_for_config_data as impl_for_config_data;
3274
3275macro_rules! _config_data {
3276    // modname is for the tests
3277    ($(#[doc=$dox:literal])* $modname:ident: struct $name:ident <- $input:ident -> {
3278        $(
3279            $(#[doc=$doc:literal])*
3280            $vis:vis $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
3281        )*
3282    }) => {
3283        /// Default config values for this grouping.
3284        #[allow(non_snake_case)]
3285        #[derive(Debug, Clone)]
3286        struct $name { $($field: $ty,)* }
3287
3288        impl_for_config_data!{
3289            $modname,
3290            $(
3291                $vis $field : $ty = $default,
3292            )*
3293        }
3294
3295        /// All fields `Option<T>`, `None` representing fields not set in a particular JSON/TOML blob.
3296        #[allow(non_snake_case)]
3297        #[derive(Clone, Default)]
3298        struct $input { $(
3299            $field: Option<$ty>,
3300        )* }
3301
3302        impl std::fmt::Debug for $input {
3303            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3304                let mut s = f.debug_struct(stringify!($input));
3305                $(
3306                    if let Some(val) = self.$field.as_ref() {
3307                        s.field(stringify!($field), val);
3308                    }
3309                )*
3310                s.finish()
3311            }
3312        }
3313
3314        impl Default for $name {
3315            fn default() -> Self {
3316                $name {$(
3317                    $field: default_val!($default, $ty),
3318                )*}
3319            }
3320        }
3321
3322        #[allow(unused, clippy::ptr_arg)]
3323        impl $input {
3324            const FIELDS: &'static [&'static str] = &[$(stringify!($field)),*];
3325
3326            fn from_json(json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> Self {
3327                Self {$(
3328                    $field: get_field_json(
3329                        json,
3330                        error_sink,
3331                        stringify!($field),
3332                        None$(.or(Some(stringify!($alias))))*,
3333                    ),
3334                )*}
3335            }
3336
3337            fn from_toml(toml: &toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>) -> Self {
3338                Self {$(
3339                    $field: get_field_toml::<$ty>(
3340                        toml,
3341                        error_sink,
3342                        stringify!($field),
3343                        None$(.or(Some(stringify!($alias))))*,
3344                    ),
3345                )*}
3346            }
3347
3348            fn schema_fields(sink: &mut Vec<SchemaField>) {
3349                sink.extend_from_slice(&[
3350                    $({
3351                        let field = stringify!($field);
3352                        let ty = stringify!($ty);
3353                        let default = default_str!($default, $ty);
3354
3355                        (field, ty, &[$($doc),*], default)
3356                    },)*
3357                ])
3358            }
3359        }
3360
3361        mod $modname {
3362            #[test]
3363            fn fields_are_sorted() {
3364                super::$input::FIELDS.windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
3365            }
3366        }
3367    };
3368}
3369use _config_data as config_data;
3370
3371#[derive(Default, Debug, Clone)]
3372struct DefaultConfigData {
3373    global: GlobalDefaultConfigData,
3374    workspace: WorkspaceDefaultConfigData,
3375    local: LocalDefaultConfigData,
3376    client: ClientDefaultConfigData,
3377}
3378
3379/// All of the config levels, all fields `Option<T>`, to describe fields that are actually set by
3380/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
3381/// all fields being None.
3382#[derive(Debug, Clone, Default)]
3383struct FullConfigInput {
3384    global: GlobalConfigInput,
3385    workspace: WorkspaceConfigInput,
3386    local: LocalConfigInput,
3387    client: ClientConfigInput,
3388}
3389
3390impl FullConfigInput {
3391    fn from_json(
3392        mut json: serde_json::Value,
3393        error_sink: &mut Vec<(String, serde_json::Error)>,
3394    ) -> FullConfigInput {
3395        FullConfigInput {
3396            global: GlobalConfigInput::from_json(&mut json, error_sink),
3397            local: LocalConfigInput::from_json(&mut json, error_sink),
3398            client: ClientConfigInput::from_json(&mut json, error_sink),
3399            workspace: WorkspaceConfigInput::from_json(&mut json, error_sink),
3400        }
3401    }
3402
3403    fn schema_fields() -> Vec<SchemaField> {
3404        let mut fields = Vec::new();
3405        GlobalConfigInput::schema_fields(&mut fields);
3406        LocalConfigInput::schema_fields(&mut fields);
3407        ClientConfigInput::schema_fields(&mut fields);
3408        WorkspaceConfigInput::schema_fields(&mut fields);
3409        fields.sort_by_key(|&(x, ..)| x);
3410        fields
3411            .iter()
3412            .tuple_windows()
3413            .for_each(|(a, b)| assert!(a.0 != b.0, "{a:?} duplicate field"));
3414        fields
3415    }
3416
3417    fn json_schema() -> serde_json::Value {
3418        schema(&Self::schema_fields())
3419    }
3420
3421    #[cfg(test)]
3422    fn manual() -> String {
3423        manual(&Self::schema_fields())
3424    }
3425}
3426
3427/// All of the config levels, all fields `Option<T>`, to describe fields that are actually set by
3428/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
3429/// all fields being None.
3430#[derive(Debug, Clone, Default)]
3431struct GlobalWorkspaceLocalConfigInput {
3432    global: GlobalConfigInput,
3433    local: LocalConfigInput,
3434    workspace: WorkspaceConfigInput,
3435}
3436
3437impl GlobalWorkspaceLocalConfigInput {
3438    const FIELDS: &'static [&'static [&'static str]] =
3439        &[GlobalConfigInput::FIELDS, LocalConfigInput::FIELDS];
3440    fn from_toml(
3441        toml: toml::Table,
3442        error_sink: &mut Vec<(String, toml::de::Error)>,
3443    ) -> GlobalWorkspaceLocalConfigInput {
3444        GlobalWorkspaceLocalConfigInput {
3445            global: GlobalConfigInput::from_toml(&toml, error_sink),
3446            local: LocalConfigInput::from_toml(&toml, error_sink),
3447            workspace: WorkspaceConfigInput::from_toml(&toml, error_sink),
3448        }
3449    }
3450}
3451
3452/// Workspace and local config levels, all fields `Option<T>`, to describe fields that are actually set by
3453/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
3454/// all fields being None.
3455#[derive(Debug, Clone, Default)]
3456#[allow(dead_code)]
3457struct WorkspaceLocalConfigInput {
3458    workspace: WorkspaceConfigInput,
3459    local: LocalConfigInput,
3460}
3461
3462impl WorkspaceLocalConfigInput {
3463    #[allow(dead_code)]
3464    const FIELDS: &'static [&'static [&'static str]] =
3465        &[WorkspaceConfigInput::FIELDS, LocalConfigInput::FIELDS];
3466    fn from_toml(toml: toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>) -> Self {
3467        Self {
3468            workspace: WorkspaceConfigInput::from_toml(&toml, error_sink),
3469            local: LocalConfigInput::from_toml(&toml, error_sink),
3470        }
3471    }
3472}
3473
3474fn get_field_json<T: DeserializeOwned>(
3475    json: &mut serde_json::Value,
3476    error_sink: &mut Vec<(String, serde_json::Error)>,
3477    field: &'static str,
3478    alias: Option<&'static str>,
3479) -> Option<T> {
3480    // XXX: check alias first, to work around the VS Code where it pre-fills the
3481    // defaults instead of sending an empty object.
3482    alias
3483        .into_iter()
3484        .chain(iter::once(field))
3485        .filter_map(move |field| {
3486            let mut pointer = field.replace('_', "/");
3487            pointer.insert(0, '/');
3488            json.pointer_mut(&pointer)
3489                .map(|it| serde_json::from_value(it.take()).map_err(|e| (e, pointer)))
3490        })
3491        .flat_map(|res| match res {
3492            Ok(it) => Some(it),
3493            Err((e, pointer)) => {
3494                tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
3495                error_sink.push((pointer, e));
3496                None
3497            }
3498        })
3499        .next()
3500}
3501
3502fn get_field_toml<T: DeserializeOwned>(
3503    toml: &toml::Table,
3504    error_sink: &mut Vec<(String, toml::de::Error)>,
3505    field: &'static str,
3506    alias: Option<&'static str>,
3507) -> Option<T> {
3508    // XXX: check alias first, to work around the VS Code where it pre-fills the
3509    // defaults instead of sending an empty object.
3510    alias
3511        .into_iter()
3512        .chain(iter::once(field))
3513        .filter_map(move |field| {
3514            let mut pointer = field.replace('_', "/");
3515            pointer.insert(0, '/');
3516            toml_pointer(toml, &pointer)
3517                .map(|it| <_>::deserialize(it.clone()).map_err(|e| (e, pointer)))
3518        })
3519        .find(Result::is_ok)
3520        .and_then(|res| match res {
3521            Ok(it) => Some(it),
3522            Err((e, pointer)) => {
3523                tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
3524                error_sink.push((pointer, e));
3525                None
3526            }
3527        })
3528}
3529
3530fn toml_pointer<'a>(toml: &'a toml::Table, pointer: &str) -> Option<&'a toml::Value> {
3531    fn parse_index(s: &str) -> Option<usize> {
3532        if s.starts_with('+') || (s.starts_with('0') && s.len() != 1) {
3533            return None;
3534        }
3535        s.parse().ok()
3536    }
3537
3538    if pointer.is_empty() {
3539        return None;
3540    }
3541    if !pointer.starts_with('/') {
3542        return None;
3543    }
3544    let mut parts = pointer.split('/').skip(1);
3545    let first = parts.next()?;
3546    let init = toml.get(first)?;
3547    parts.map(|x| x.replace("~1", "/").replace("~0", "~")).try_fold(init, |target, token| {
3548        match target {
3549            toml::Value::Table(table) => table.get(&token),
3550            toml::Value::Array(list) => parse_index(&token).and_then(move |x| list.get(x)),
3551            _ => None,
3552        }
3553    })
3554}
3555
3556type SchemaField = (&'static str, &'static str, &'static [&'static str], String);
3557
3558fn schema(fields: &[SchemaField]) -> serde_json::Value {
3559    let map = fields
3560        .iter()
3561        .map(|(field, ty, doc, default)| {
3562            let name = field.replace('_', ".");
3563            let category = name
3564                .split_once(".")
3565                .map(|(category, _name)| to_title_case(category))
3566                .unwrap_or("rust-analyzer".into());
3567            let name = format!("rust-analyzer.{name}");
3568            let props = field_props(field, ty, doc, default);
3569            serde_json::json!({
3570                "title": category,
3571                "properties": {
3572                    name: props
3573                }
3574            })
3575        })
3576        .collect::<Vec<_>>();
3577    map.into()
3578}
3579
3580/// Translate a field name to a title case string suitable for use in the category names on the
3581/// vscode settings page.
3582///
3583/// First letter of word should be uppercase, if an uppercase letter is encountered, add a space
3584/// before it e.g. "fooBar" -> "Foo Bar", "fooBarBaz" -> "Foo Bar Baz", "foo" -> "Foo"
3585///
3586/// This likely should be in stdx (or just use heck instead), but it doesn't handle any edge cases
3587/// and is intentionally simple.
3588fn to_title_case(s: &str) -> String {
3589    let mut result = String::with_capacity(s.len());
3590    let mut chars = s.chars();
3591    if let Some(first) = chars.next() {
3592        result.push(first.to_ascii_uppercase());
3593        for c in chars {
3594            if c.is_uppercase() {
3595                result.push(' ');
3596            }
3597            result.push(c);
3598        }
3599    }
3600    result
3601}
3602
3603fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
3604    let doc = doc_comment_to_string(doc);
3605    let doc = doc.trim_end_matches('\n');
3606    assert!(
3607        doc.ends_with('.') && doc.starts_with(char::is_uppercase),
3608        "bad docs for {field}: {doc:?}"
3609    );
3610    let default = default.parse::<serde_json::Value>().unwrap();
3611
3612    let mut map = serde_json::Map::default();
3613    macro_rules! set {
3614        ($($key:literal: $value:tt),*$(,)?) => {{$(
3615            map.insert($key.into(), serde_json::json!($value));
3616        )*}};
3617    }
3618    set!("markdownDescription": doc);
3619    set!("default": default);
3620
3621    match ty {
3622        "bool" => set!("type": "boolean"),
3623        "usize" => set!("type": "integer", "minimum": 0),
3624        "String" => set!("type": "string"),
3625        "Vec<String>" => set! {
3626            "type": "array",
3627            "items": { "type": "string" },
3628        },
3629        "Vec<Utf8PathBuf>" => set! {
3630            "type": "array",
3631            "items": { "type": "string" },
3632        },
3633        "FxHashSet<String>" => set! {
3634            "type": "array",
3635            "items": { "type": "string" },
3636            "uniqueItems": true,
3637        },
3638        "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
3639            "type": "object",
3640        },
3641        "FxIndexMap<String, SnippetDef>" => set! {
3642            "type": "object",
3643        },
3644        "FxHashMap<String, String>" => set! {
3645            "type": "object",
3646        },
3647        "FxHashMap<Box<str>, u16>" => set! {
3648            "type": "object",
3649        },
3650        "FxHashMap<String, Option<String>>" => set! {
3651            "type": "object",
3652        },
3653        "Option<usize>" => set! {
3654            "type": ["null", "integer"],
3655            "minimum": 0,
3656        },
3657        "Option<u16>" => set! {
3658            "type": ["null", "integer"],
3659            "minimum": 0,
3660            "maximum": 65535,
3661        },
3662        "Option<String>" => set! {
3663            "type": ["null", "string"],
3664        },
3665        "Option<Utf8PathBuf>" => set! {
3666            "type": ["null", "string"],
3667        },
3668        "Option<bool>" => set! {
3669            "type": ["null", "boolean"],
3670        },
3671        "Option<Vec<String>>" => set! {
3672            "type": ["null", "array"],
3673            "items": { "type": "string" },
3674        },
3675        "ExprFillDefaultDef" => set! {
3676            "type": "string",
3677            "enum": ["todo", "default"],
3678            "enumDescriptions": [
3679                "Fill missing expressions with the `todo` macro",
3680                "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
3681            ],
3682        },
3683        "ImportGranularityDef" => set! {
3684            "type": "string",
3685            "enum": ["crate", "module", "item", "one", "preserve"],
3686            "enumDescriptions": [
3687                "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
3688                "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
3689                "Flatten imports so that each has its own use statement.",
3690                "Merge all imports into a single use statement as long as they have the same visibility and attributes.",
3691                "Deprecated - unless `enforceGranularity` is `true`, the style of the current file is preferred over this setting. Behaves like `item`."
3692            ],
3693        },
3694        "ImportPrefixDef" => set! {
3695            "type": "string",
3696            "enum": [
3697                "plain",
3698                "self",
3699                "crate"
3700            ],
3701            "enumDescriptions": [
3702                "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
3703                "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.",
3704                "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
3705            ],
3706        },
3707        "Vec<ManifestOrProjectJson>" => set! {
3708            "type": "array",
3709            "items": { "type": ["string", "object"] },
3710        },
3711        "WorkspaceSymbolSearchScopeDef" => set! {
3712            "type": "string",
3713            "enum": ["workspace", "workspace_and_dependencies"],
3714            "enumDescriptions": [
3715                "Search in current workspace only.",
3716                "Search in current workspace and dependencies."
3717            ],
3718        },
3719        "WorkspaceSymbolSearchKindDef" => set! {
3720            "type": "string",
3721            "enum": ["only_types", "all_symbols"],
3722            "enumDescriptions": [
3723                "Search for types only.",
3724                "Search for all symbols kinds."
3725            ],
3726        },
3727        "LifetimeElisionDef" => set! {
3728            "type": "string",
3729            "enum": [
3730                "always",
3731                "never",
3732                "skip_trivial"
3733            ],
3734            "enumDescriptions": [
3735                "Always show lifetime elision hints.",
3736                "Never show lifetime elision hints.",
3737                "Only show lifetime elision hints if a return type is involved."
3738            ]
3739        },
3740        "ClosureReturnTypeHintsDef" => set! {
3741            "type": "string",
3742            "enum": [
3743                "always",
3744                "never",
3745                "with_block"
3746            ],
3747            "enumDescriptions": [
3748                "Always show type hints for return types of closures.",
3749                "Never show type hints for return types of closures.",
3750                "Only show type hints for return types of closures with blocks."
3751            ]
3752        },
3753        "ReborrowHintsDef" => set! {
3754            "type": "string",
3755            "enum": [
3756                "always",
3757                "never",
3758                "mutable"
3759            ],
3760            "enumDescriptions": [
3761                "Always show reborrow hints.",
3762                "Never show reborrow hints.",
3763                "Only show mutable reborrow hints."
3764            ]
3765        },
3766        "AdjustmentHintsDef" => set! {
3767            "type": "string",
3768            "enum": [
3769                "always",
3770                "never",
3771                "reborrow"
3772            ],
3773            "enumDescriptions": [
3774                "Always show all adjustment hints.",
3775                "Never show adjustment hints.",
3776                "Only show auto borrow and dereference adjustment hints."
3777            ]
3778        },
3779        "DiscriminantHintsDef" => set! {
3780            "type": "string",
3781            "enum": [
3782                "always",
3783                "never",
3784                "fieldless"
3785            ],
3786            "enumDescriptions": [
3787                "Always show all discriminant hints.",
3788                "Never show discriminant hints.",
3789                "Only show discriminant hints on fieldless enum variants."
3790            ]
3791        },
3792        "AdjustmentHintsModeDef" => set! {
3793            "type": "string",
3794            "enum": [
3795                "prefix",
3796                "postfix",
3797                "prefer_prefix",
3798                "prefer_postfix",
3799            ],
3800            "enumDescriptions": [
3801                "Always show adjustment hints as prefix (`*expr`).",
3802                "Always show adjustment hints as postfix (`expr.*`).",
3803                "Show prefix or postfix depending on which uses less parenthesis, preferring prefix.",
3804                "Show prefix or postfix depending on which uses less parenthesis, preferring postfix.",
3805            ]
3806        },
3807        "CargoFeaturesDef" => set! {
3808            "anyOf": [
3809                {
3810                    "type": "string",
3811                    "enum": [
3812                        "all"
3813                    ],
3814                    "enumDescriptions": [
3815                        "Pass `--all-features` to cargo",
3816                    ]
3817                },
3818                {
3819                    "type": "array",
3820                    "items": { "type": "string" }
3821                }
3822            ],
3823        },
3824        "Option<CargoFeaturesDef>" => set! {
3825            "anyOf": [
3826                {
3827                    "type": "string",
3828                    "enum": [
3829                        "all"
3830                    ],
3831                    "enumDescriptions": [
3832                        "Pass `--all-features` to cargo",
3833                    ]
3834                },
3835                {
3836                    "type": "array",
3837                    "items": { "type": "string" }
3838                },
3839                { "type": "null" }
3840            ],
3841        },
3842        "CallableCompletionDef" => set! {
3843            "type": "string",
3844            "enum": [
3845                "fill_arguments",
3846                "add_parentheses",
3847                "none",
3848            ],
3849            "enumDescriptions": [
3850                "Add call parentheses and pre-fill arguments.",
3851                "Add call parentheses.",
3852                "Do no snippet completions for callables."
3853            ]
3854        },
3855        "SignatureDetail" => set! {
3856            "type": "string",
3857            "enum": ["full", "parameters"],
3858            "enumDescriptions": [
3859                "Show the entire signature.",
3860                "Show only the parameters."
3861            ],
3862        },
3863        "FilesWatcherDef" => set! {
3864            "type": "string",
3865            "enum": ["client", "server"],
3866            "enumDescriptions": [
3867                "Use the client (editor) to watch files for changes",
3868                "Use server-side file watching",
3869            ],
3870        },
3871        "AnnotationLocation" => set! {
3872            "type": "string",
3873            "enum": ["above_name", "above_whole_item"],
3874            "enumDescriptions": [
3875                "Render annotations above the name of the item.",
3876                "Render annotations above the whole item, including documentation comments and attributes."
3877            ],
3878        },
3879        "InvocationStrategy" => set! {
3880            "type": "string",
3881            "enum": ["per_workspace", "once"],
3882            "enumDescriptions": [
3883                "The command will be executed for each Rust workspace with the workspace as the working directory.",
3884                "The command will be executed once with the opened project as the working directory."
3885            ],
3886        },
3887        "Option<CheckOnSaveTargets>" => set! {
3888            "anyOf": [
3889                {
3890                    "type": "null"
3891                },
3892                {
3893                    "type": "string",
3894                },
3895                {
3896                    "type": "array",
3897                    "items": { "type": "string" }
3898                },
3899            ],
3900        },
3901        "ClosureStyle" => set! {
3902            "type": "string",
3903            "enum": ["impl_fn", "ra_ap_rust_analyzer", "with_id", "hide"],
3904            "enumDescriptions": [
3905                "`impl_fn`: `impl FnMut(i32, u64) -> i8`",
3906                "`ra_ap_rust_analyzer`: `|i32, u64| -> i8`",
3907                "`with_id`: `{closure#14352}`, where that id is the unique number of the closure in r-a internals",
3908                "`hide`: Shows `...` for every closure type",
3909            ],
3910        },
3911        "Option<MemoryLayoutHoverRenderKindDef>" => set! {
3912            "anyOf": [
3913                {
3914                    "type": "null"
3915                },
3916                {
3917                    "type": "string",
3918                    "enum": ["both", "decimal", "hexadecimal", ],
3919                    "enumDescriptions": [
3920                        "Render as 12 (0xC)",
3921                        "Render as 12",
3922                        "Render as 0xC"
3923                    ],
3924                },
3925            ],
3926        },
3927        "Option<TargetDirectory>" => set! {
3928            "anyOf": [
3929                {
3930                    "type": "null"
3931                },
3932                {
3933                    "type": "boolean"
3934                },
3935                {
3936                    "type": "string"
3937                },
3938            ],
3939        },
3940        "NumThreads" => set! {
3941            "anyOf": [
3942                {
3943                    "type": "number",
3944                    "minimum": 0,
3945                    "maximum": 255
3946                },
3947                {
3948                    "type": "string",
3949                    "enum": ["physical", "logical", ],
3950                    "enumDescriptions": [
3951                        "Use the number of physical cores",
3952                        "Use the number of logical cores",
3953                    ],
3954                },
3955            ],
3956        },
3957        "NumProcesses" => set! {
3958            "anyOf": [
3959                {
3960                    "type": "number",
3961                    "minimum": 0,
3962                    "maximum": 255
3963                },
3964                {
3965                    "type": "string",
3966                    "enum": ["physical"],
3967                    "enumDescriptions": [
3968                        "Use the number of physical cores",
3969                    ],
3970                },
3971            ],
3972        },
3973        "Option<NumThreads>" => set! {
3974            "anyOf": [
3975                {
3976                    "type": "null"
3977                },
3978                {
3979                    "type": "number",
3980                    "minimum": 0,
3981                    "maximum": 255
3982                },
3983                {
3984                    "type": "string",
3985                    "enum": ["physical", "logical", ],
3986                    "enumDescriptions": [
3987                        "Use the number of physical cores",
3988                        "Use the number of logical cores",
3989                    ],
3990                },
3991            ],
3992        },
3993        "Option<DiscoverWorkspaceConfig>" => set! {
3994            "anyOf": [
3995                {
3996                    "type": "null"
3997                },
3998                {
3999                    "type": "object",
4000                    "properties": {
4001                        "command": {
4002                            "type": "array",
4003                            "items": { "type": "string" }
4004                        },
4005                        "progressLabel": {
4006                            "type": "string"
4007                        },
4008                        "filesToWatch": {
4009                            "type": "array",
4010                            "items": { "type": "string" }
4011                        },
4012                    }
4013                }
4014            ]
4015        },
4016        "Option<MaxSubstitutionLength>" => set! {
4017            "anyOf": [
4018                {
4019                    "type": "null"
4020                },
4021                {
4022                    "type": "string",
4023                    "enum": ["hide"]
4024                },
4025                {
4026                    "type": "integer"
4027                }
4028            ]
4029        },
4030        "Vec<AutoImportExclusion>" => set! {
4031            "type": "array",
4032            "items": {
4033                "anyOf": [
4034                    {
4035                        "type": "string",
4036                    },
4037                    {
4038                        "type": "object",
4039                        "properties": {
4040                            "path": {
4041                                "type": "string",
4042                            },
4043                            "type": {
4044                                "type": "string",
4045                                "enum": ["always", "methods"],
4046                                "enumDescriptions": [
4047                                    "Do not show this item or its methods (if it is a trait) in auto-import completions.",
4048                                    "Do not show this traits methods in auto-import completions."
4049                                ],
4050                            },
4051                        }
4052                    }
4053                ]
4054             }
4055        },
4056        _ => panic!("missing entry for {ty}: {default} (field {field})"),
4057    }
4058
4059    map.into()
4060}
4061
4062fn validate_toml_table(
4063    known_ptrs: &[&[&'static str]],
4064    toml: &toml::Table,
4065    ptr: &mut String,
4066    error_sink: &mut Vec<(String, toml::de::Error)>,
4067) {
4068    let verify = |ptr: &String| known_ptrs.iter().any(|ptrs| ptrs.contains(&ptr.as_str()));
4069
4070    let l = ptr.len();
4071    for (k, v) in toml {
4072        if !ptr.is_empty() {
4073            ptr.push('_');
4074        }
4075        ptr.push_str(k);
4076
4077        match v {
4078            // This is a table config, any entry in it is therefore valid
4079            toml::Value::Table(_) if verify(ptr) => (),
4080            toml::Value::Table(table) => validate_toml_table(known_ptrs, table, ptr, error_sink),
4081            _ if !verify(ptr) => error_sink
4082                .push((ptr.replace('_', "/"), toml::de::Error::custom("unexpected field"))),
4083            _ => (),
4084        }
4085
4086        ptr.truncate(l);
4087    }
4088}
4089
4090#[cfg(test)]
4091fn manual(fields: &[SchemaField]) -> String {
4092    fields.iter().fold(String::new(), |mut acc, (field, _ty, doc, default)| {
4093        let id = field.replace('_', ".");
4094        let name = format!("rust-analyzer.{id}");
4095        let doc = doc_comment_to_string(doc);
4096        if default.contains('\n') {
4097            format_to_acc!(
4098                acc,
4099                "## {name} {{#{id}}}\n\nDefault:\n```json\n{default}\n```\n\n{doc}\n\n"
4100            )
4101        } else {
4102            format_to_acc!(acc, "## {name} {{#{id}}}\n\nDefault: `{default}`\n\n{doc}\n\n")
4103        }
4104    })
4105}
4106
4107fn doc_comment_to_string(doc: &[&str]) -> String {
4108    doc.iter()
4109        .map(|it| it.strip_prefix(' ').unwrap_or(it))
4110        .fold(String::new(), |mut acc, it| format_to_acc!(acc, "{it}\n"))
4111}
4112
4113#[cfg(test)]
4114mod tests {
4115    use std::{borrow::Cow, fs};
4116
4117    use test_utils::{ensure_file_contents, project_root};
4118
4119    use super::*;
4120
4121    #[test]
4122    fn generate_package_json_config() {
4123        let s = Config::json_schema();
4124
4125        let schema = format!("{s:#}");
4126        let mut schema = schema
4127            .trim_start_matches('[')
4128            .trim_end_matches(']')
4129            .replace("  ", "    ")
4130            .replace('\n', "\n        ")
4131            .trim_start_matches('\n')
4132            .trim_end()
4133            .to_owned();
4134        schema.push_str(",\n");
4135
4136        // Transform the asciidoc form link to markdown style.
4137        //
4138        // https://link[text] => [text](https://link)
4139        let url_matches = schema.match_indices("https://");
4140        let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
4141        url_offsets.reverse();
4142        for idx in url_offsets {
4143            let link = &schema[idx..];
4144            // matching on whitespace to ignore normal links
4145            if let Some(link_end) = link.find([' ', '['])
4146                && link.chars().nth(link_end) == Some('[')
4147                && let Some(link_text_end) = link.find(']')
4148            {
4149                let link_text = link[link_end..(link_text_end + 1)].to_string();
4150
4151                schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
4152                schema.insert(idx, '(');
4153                schema.insert(idx + link_end + 1, ')');
4154                schema.insert_str(idx, &link_text);
4155            }
4156        }
4157
4158        let package_json_path = project_root().join("editors/code/package.json");
4159        let mut package_json = fs::read_to_string(&package_json_path).unwrap();
4160
4161        let start_marker =
4162            "            {\n                \"title\": \"$generated-start\"\n            },\n";
4163        let end_marker =
4164            "            {\n                \"title\": \"$generated-end\"\n            }\n";
4165
4166        let start = package_json.find(start_marker).unwrap() + start_marker.len();
4167        let end = package_json.find(end_marker).unwrap();
4168
4169        let p = remove_ws(&package_json[start..end]);
4170        let s = remove_ws(&schema);
4171        if !p.contains(&s) {
4172            package_json.replace_range(start..end, &schema);
4173            ensure_file_contents(package_json_path.as_std_path(), &package_json)
4174        }
4175    }
4176
4177    #[test]
4178    fn generate_config_documentation() {
4179        let docs_path = project_root().join("docs/book/src/configuration_generated.md");
4180        let expected = FullConfigInput::manual();
4181        ensure_file_contents(docs_path.as_std_path(), &expected);
4182    }
4183
4184    fn remove_ws(text: &str) -> String {
4185        text.replace(char::is_whitespace, "")
4186    }
4187
4188    #[test]
4189    fn proc_macro_srv_null() {
4190        let mut config =
4191            Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4192
4193        let mut change = ConfigChange::default();
4194        change.change_client_config(serde_json::json!({
4195            "procMacro" : {
4196                "server": null,
4197        }}));
4198
4199        (config, _, _) = config.apply_change(change);
4200        assert_eq!(config.proc_macro_srv(), None);
4201    }
4202
4203    #[test]
4204    fn proc_macro_srv_abs() {
4205        let mut config =
4206            Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4207        let mut change = ConfigChange::default();
4208        change.change_client_config(serde_json::json!({
4209        "procMacro" : {
4210            "server": project_root().to_string(),
4211        }}));
4212
4213        (config, _, _) = config.apply_change(change);
4214        assert_eq!(config.proc_macro_srv(), Some(AbsPathBuf::assert(project_root())));
4215    }
4216
4217    #[test]
4218    fn proc_macro_srv_rel() {
4219        let mut config =
4220            Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4221
4222        let mut change = ConfigChange::default();
4223
4224        change.change_client_config(serde_json::json!({
4225        "procMacro" : {
4226            "server": "./server"
4227        }}));
4228
4229        (config, _, _) = config.apply_change(change);
4230
4231        assert_eq!(
4232            config.proc_macro_srv(),
4233            Some(AbsPathBuf::try_from(project_root().join("./server")).unwrap())
4234        );
4235    }
4236
4237    #[test]
4238    fn cargo_target_dir_unset() {
4239        let mut config =
4240            Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4241
4242        let mut change = ConfigChange::default();
4243
4244        change.change_client_config(serde_json::json!({
4245            "rust" : { "analyzerTargetDir" : null }
4246        }));
4247
4248        (config, _, _) = config.apply_change(change);
4249        assert_eq!(config.cargo_targetDir(None), &None);
4250        assert!(matches!(
4251            config.flycheck(None),
4252            FlycheckConfig::Automatic {
4253                cargo_options: CargoOptions { target_dir_config: TargetDirectoryConfig::None, .. },
4254                ..
4255            }
4256        ));
4257    }
4258
4259    #[test]
4260    fn cargo_target_dir_subdir() {
4261        let mut config =
4262            Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4263
4264        let mut change = ConfigChange::default();
4265        change.change_client_config(serde_json::json!({
4266            "rust" : { "analyzerTargetDir" : true }
4267        }));
4268
4269        (config, _, _) = config.apply_change(change);
4270
4271        assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true)));
4272        let ws_target_dir =
4273            Utf8PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap_or("target".to_owned()));
4274        assert!(matches!(
4275            config.flycheck(None),
4276            FlycheckConfig::Automatic {
4277                cargo_options: CargoOptions { target_dir_config, .. },
4278                ..
4279            } if target_dir_config.target_dir(Some(&ws_target_dir)).map(Cow::into_owned)
4280                == Some(ws_target_dir.join("rust-analyzer"))
4281        ));
4282    }
4283
4284    #[test]
4285    fn cargo_target_dir_relative_dir() {
4286        let mut config =
4287            Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4288
4289        let mut change = ConfigChange::default();
4290        change.change_client_config(serde_json::json!({
4291            "rust" : { "analyzerTargetDir" : "other_folder" }
4292        }));
4293
4294        (config, _, _) = config.apply_change(change);
4295
4296        assert_eq!(
4297            config.cargo_targetDir(None),
4298            &Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder")))
4299        );
4300        assert!(matches!(
4301            config.flycheck(None),
4302            FlycheckConfig::Automatic {
4303                cargo_options: CargoOptions { target_dir_config, .. },
4304                ..
4305            } if target_dir_config.target_dir(None).map(Cow::into_owned)
4306                == Some(Utf8PathBuf::from("other_folder"))
4307        ));
4308    }
4309}