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