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