Skip to main content

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