Skip to main content

ra_ap_rust_analyzer/
config.rs

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