rust_analyzer/
config.rs

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