ra_ap_rust_analyzer/
config.rs

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