1 //! Config used by the language server.
3 //! We currently get this config from `initialize` LSP request, which is not the
4 //! best way to do it, but was the simplest thing we could implement.
6 //! Of particular interest is the `feature_flags` hash map: while other fields
7 //! configure the server itself, feature flags are passed into analysis, and
8 //! tweak things like automatic insertion of `()` in completions.
10 use std::{fmt, iter, path::PathBuf};
12 use flycheck::FlycheckConfig;
14 AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
15 HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
16 JoinLinesConfig, Snippet, SnippetScope,
19 imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
22 use itertools::Itertools;
23 use lsp_types::{ClientCapabilities, ClientInfo, MarkupKind};
25 CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource,
28 use rustc_hash::{FxHashMap, FxHashSet};
29 use serde::{de::DeserializeOwned, Deserialize};
33 caps::completion_item_edit_resolve,
34 diagnostics::DiagnosticsMapConfig,
35 line_index::PositionEncoding,
36 lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
41 // Conventions for configuration keys to preserve maximal extendability without breakage:
42 // - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable`
43 // This has the benefit of namespaces being extensible, and if the suffix doesn't fit later it can be changed without breakage.
44 // - In general be wary of using the namespace of something verbatim, it prevents us from adding subkeys in the future
45 // - Don't use abbreviations unless really necessary
46 // - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
48 // Defines the server-side configuration of the rust-analyzer. We generate
49 // *parts* of VS Code's `package.json` config from this. Run `cargo test` to
50 // re-generate that file.
52 // However, editor specific config, which the server doesn't know about, should
53 // be specified directly in `package.json`.
55 // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
56 // parsing the old name.
59 /// Whether to insert #[must_use] when generating `as_` methods
60 /// for enum variants.
61 assist_emitMustUse: bool = "false",
62 /// Placeholder expression to use for missing expressions in assists.
63 assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
65 /// Warm up caches on project load.
66 cachePriming_enable: bool = "true",
67 /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
68 cachePriming_numThreads: ParallelCachePrimingNumThreads = "0",
70 /// Automatically refresh project info via `cargo metadata` on
71 /// `Cargo.toml` or `.cargo/config.toml` changes.
72 cargo_autoreload: bool = "true",
73 /// Run build scripts (`build.rs`) for more precise code analysis.
74 cargo_buildScripts_enable: bool = "true",
75 /// Specifies the working directory for running build scripts.
76 /// - "workspace": run build scripts for a workspace in the workspace's root directory.
77 /// This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.
78 /// - "root": run build scripts in the project's root directory.
79 /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
81 cargo_buildScripts_invocationLocation: InvocationLocation = "\"workspace\"",
82 /// Specifies the invocation strategy to use when running the build scripts command.
83 /// If `per_workspace` is set, the command will be executed for each workspace.
84 /// If `once` is set, the command will be executed once.
85 /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
87 cargo_buildScripts_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
88 /// Override the command rust-analyzer uses to run build scripts and
89 /// build procedural macros. The command is required to output json
90 /// and should therefore include `--message-format=json` or a similar
93 /// By default, a cargo invocation will be constructed for the configured
94 /// targets and features, with the following base command line:
97 /// cargo check --quiet --workspace --message-format=json --all-targets
100 cargo_buildScripts_overrideCommand: Option<Vec<String>> = "null",
101 /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
102 /// avoid checking unnecessary things.
103 cargo_buildScripts_useRustcWrapper: bool = "true",
104 /// Extra environment variables that will be set when running cargo, rustc
105 /// or other commands within the workspace. Useful for setting RUSTFLAGS.
106 cargo_extraEnv: FxHashMap<String, String> = "{}",
107 /// List of features to activate.
109 /// Set this to `"all"` to pass `--all-features` to cargo.
110 cargo_features: CargoFeaturesDef = "[]",
111 /// Whether to pass `--no-default-features` to cargo.
112 cargo_noDefaultFeatures: bool = "false",
113 /// Relative path to the sysroot, or "discover" to try to automatically find it via
114 /// "rustc --print sysroot".
116 /// Unsetting this disables sysroot loading.
118 /// This option does not take effect until rust-analyzer is restarted.
119 cargo_sysroot: Option<String> = "\"discover\"",
120 /// Compilation target override (target triple).
121 // FIXME(@poliorcetics): move to multiple targets here too, but this will need more work
122 // than `checkOnSave_target`
123 cargo_target: Option<String> = "null",
124 /// Unsets `#[cfg(test)]` for the specified crates.
125 cargo_unsetTest: Vec<String> = "[\"core\"]",
127 /// Run the check command for diagnostics on save.
128 checkOnSave | checkOnSave_enable: bool = "true",
130 /// Check all targets and tests (`--all-targets`).
131 check_allTargets | checkOnSave_allTargets: bool = "true",
132 /// Cargo command to use for `cargo check`.
133 check_command | checkOnSave_command: String = "\"check\"",
134 /// Extra arguments for `cargo check`.
135 check_extraArgs | checkOnSave_extraArgs: Vec<String> = "[]",
136 /// Extra environment variables that will be set when running `cargo check`.
137 /// Extends `#rust-analyzer.cargo.extraEnv#`.
138 check_extraEnv | checkOnSave_extraEnv: FxHashMap<String, String> = "{}",
139 /// List of features to activate. Defaults to
140 /// `#rust-analyzer.cargo.features#`.
142 /// Set to `"all"` to pass `--all-features` to Cargo.
143 check_features | checkOnSave_features: Option<CargoFeaturesDef> = "null",
144 /// Specifies the working directory for running checks.
145 /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories.
146 // FIXME: Ideally we would support this in some way
147 /// This falls back to "root" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`.
148 /// - "root": run checks in the project's root directory.
149 /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
151 check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"",
152 /// Specifies the invocation strategy to use when running the checkOnSave command.
153 /// If `per_workspace` is set, the command will be executed for each workspace.
154 /// If `once` is set, the command will be executed once.
155 /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
157 check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"",
158 /// Whether to pass `--no-default-features` to Cargo. Defaults to
159 /// `#rust-analyzer.cargo.noDefaultFeatures#`.
160 check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool> = "null",
161 /// Override the command rust-analyzer uses instead of `cargo check` for
162 /// diagnostics on save. The command is required to output json and
163 /// should therefore include `--message-format=json` or a similar option
164 /// (if your client supports the `colorDiagnosticOutput` experimental
165 /// capability, you can use `--message-format=json-diagnostic-rendered-ansi`).
167 /// If you're changing this because you're using some tool wrapping
168 /// Cargo, you might also want to change
169 /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
171 /// If there are multiple linked projects, this command is invoked for
172 /// each of them, with the working directory being the project root
173 /// (i.e., the folder containing the `Cargo.toml`).
175 /// An example command would be:
178 /// cargo check --workspace --message-format=json --all-targets
181 check_overrideCommand | checkOnSave_overrideCommand: Option<Vec<String>> = "null",
182 /// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.
184 /// Can be a single target, e.g. `"x86_64-unknown-linux-gnu"` or a list of targets, e.g.
185 /// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`.
187 /// Aliased as `"checkOnSave.targets"`.
188 check_targets | checkOnSave_targets | checkOnSave_target: Option<CheckOnSaveTargets> = "null",
190 /// Toggles the additional completions that automatically add imports when completed.
191 /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
192 completion_autoimport_enable: bool = "true",
193 /// Toggles the additional completions that automatically show method calls and field accesses
194 /// with `self` prefixed to them when inside a method.
195 completion_autoself_enable: bool = "true",
196 /// Whether to add parenthesis and argument snippets when completing function.
197 completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
198 /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
199 completion_postfix_enable: bool = "true",
200 /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
201 completion_privateEditable_enable: bool = "false",
202 /// Custom completion snippets.
203 // NOTE: Keep this list in sync with the feature docs of user snippets.
204 completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
207 "body": "Arc::new(${receiver})",
208 "requires": "std::sync::Arc",
209 "description": "Put the expression into an `Arc`",
214 "body": "Rc::new(${receiver})",
215 "requires": "std::rc::Rc",
216 "description": "Put the expression into an `Rc`",
221 "body": "Box::pin(${receiver})",
222 "requires": "std::boxed::Box",
223 "description": "Put the expression into a pinned `Box`",
228 "body": "Ok(${receiver})",
229 "description": "Wrap the expression in a `Result::Ok`",
234 "body": "Err(${receiver})",
235 "description": "Wrap the expression in a `Result::Err`",
240 "body": "Some(${receiver})",
241 "description": "Wrap the expression in an `Option::Some`",
246 /// List of rust-analyzer diagnostics to disable.
247 diagnostics_disabled: FxHashSet<String> = "[]",
248 /// Whether to show native rust-analyzer diagnostics.
249 diagnostics_enable: bool = "true",
250 /// Whether to show experimental rust-analyzer diagnostics that might
251 /// have more false positives than usual.
252 diagnostics_experimental_enable: bool = "false",
253 /// Map of prefixes to be substituted when parsing diagnostic file paths.
254 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
255 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
256 /// List of warnings that should be displayed with hint severity.
258 /// The warnings will be indicated by faded text or three dots in code
259 /// and will not show up in the `Problems Panel`.
260 diagnostics_warningsAsHint: Vec<String> = "[]",
261 /// List of warnings that should be displayed with info severity.
263 /// The warnings will be indicated by a blue squiggly underline in code
264 /// and a blue icon in the `Problems Panel`.
265 diagnostics_warningsAsInfo: Vec<String> = "[]",
267 /// These directories will be ignored by rust-analyzer. They are
268 /// relative to the workspace root, and globs are not supported. You may
269 /// also need to add the folders to Code's `files.watcherExclude`.
270 files_excludeDirs: Vec<PathBuf> = "[]",
271 /// Controls file watching implementation.
272 files_watcher: FilesWatcherDef = "\"client\"",
274 /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
275 highlightRelated_breakPoints_enable: bool = "true",
276 /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
277 highlightRelated_exitPoints_enable: bool = "true",
278 /// Enables highlighting of related references while the cursor is on any identifier.
279 highlightRelated_references_enable: bool = "true",
280 /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
281 highlightRelated_yieldPoints_enable: bool = "true",
283 /// Whether to show `Debug` action. Only applies when
284 /// `#rust-analyzer.hover.actions.enable#` is set.
285 hover_actions_debug_enable: bool = "true",
286 /// Whether to show HoverActions in Rust files.
287 hover_actions_enable: bool = "true",
288 /// Whether to show `Go to Type Definition` action. Only applies when
289 /// `#rust-analyzer.hover.actions.enable#` is set.
290 hover_actions_gotoTypeDef_enable: bool = "true",
291 /// Whether to show `Implementations` action. Only applies when
292 /// `#rust-analyzer.hover.actions.enable#` is set.
293 hover_actions_implementations_enable: bool = "true",
294 /// Whether to show `References` action. Only applies when
295 /// `#rust-analyzer.hover.actions.enable#` is set.
296 hover_actions_references_enable: bool = "false",
297 /// Whether to show `Run` action. Only applies when
298 /// `#rust-analyzer.hover.actions.enable#` is set.
299 hover_actions_run_enable: bool = "true",
301 /// Whether to show documentation on hover.
302 hover_documentation_enable: bool = "true",
303 /// Whether to show keyword hover popups. Only applies when
304 /// `#rust-analyzer.hover.documentation.enable#` is set.
305 hover_documentation_keywords_enable: bool = "true",
306 /// Use markdown syntax for links in hover.
307 hover_links_enable: bool = "true",
309 /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.
310 imports_granularity_enforce: bool = "false",
311 /// How imports should be grouped into use statements.
312 imports_granularity_group: ImportGranularityDef = "\"crate\"",
313 /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
314 imports_group_enable: bool = "true",
315 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
316 imports_merge_glob: bool = "true",
317 /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate.
318 imports_prefer_no_std: bool = "false",
319 /// The path structure for newly inserted paths to use.
320 imports_prefix: ImportPrefixDef = "\"plain\"",
322 /// Whether to show inlay type hints for binding modes.
323 inlayHints_bindingModeHints_enable: bool = "false",
324 /// Whether to show inlay type hints for method chains.
325 inlayHints_chainingHints_enable: bool = "true",
326 /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
327 inlayHints_closingBraceHints_enable: bool = "true",
328 /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
329 /// to always show them).
330 inlayHints_closingBraceHints_minLines: usize = "25",
331 /// Whether to show inlay type hints for return types of closures.
332 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
333 /// Whether to show enum variant discriminant hints.
334 inlayHints_discriminantHints_enable: DiscriminantHintsDef = "\"never\"",
335 /// Whether to show inlay hints for type adjustments.
336 inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"",
337 /// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks.
338 inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false",
339 /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc).
340 inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"",
341 /// Whether to show inlay type hints for elided lifetimes in function signatures.
342 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
343 /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
344 inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
345 /// Whether to use location links for parts of type mentioned in inlay hints.
346 inlayHints_locationLinks: bool = "true",
347 /// Maximum length for inlay hints. Set to null to have an unlimited length.
348 inlayHints_maxLength: Option<usize> = "25",
349 /// Whether to show function parameter name inlay hints at the call
351 inlayHints_parameterHints_enable: bool = "true",
352 /// Whether to show inlay hints for compiler inserted reborrows.
353 /// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#.
354 inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
355 /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
356 inlayHints_renderColons: bool = "true",
357 /// Whether to show inlay type hints for variables.
358 inlayHints_typeHints_enable: bool = "true",
359 /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
360 /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
361 inlayHints_typeHints_hideClosureInitialization: bool = "false",
362 /// Whether to hide inlay type hints for constructors.
363 inlayHints_typeHints_hideNamedConstructor: bool = "false",
365 /// Join lines merges consecutive declaration and initialization of an assignment.
366 joinLines_joinAssignments: bool = "true",
367 /// Join lines inserts else between consecutive ifs.
368 joinLines_joinElseIf: bool = "true",
369 /// Join lines removes trailing commas.
370 joinLines_removeTrailingComma: bool = "true",
371 /// Join lines unwraps trivial blocks.
372 joinLines_unwrapTrivialBlock: bool = "true",
375 /// Whether to show `Debug` lens. Only applies when
376 /// `#rust-analyzer.lens.enable#` is set.
377 lens_debug_enable: bool = "true",
378 /// Whether to show CodeLens in Rust files.
379 lens_enable: bool = "true",
380 /// Internal config: use custom client-side commands even when the
381 /// client doesn't set the corresponding capability.
382 lens_forceCustomCommands: bool = "true",
383 /// Whether to show `Implementations` lens. Only applies when
384 /// `#rust-analyzer.lens.enable#` is set.
385 lens_implementations_enable: bool = "true",
386 /// Where to render annotations.
387 lens_location: AnnotationLocation = "\"above_name\"",
388 /// Whether to show `References` lens for Struct, Enum, and Union.
389 /// Only applies when `#rust-analyzer.lens.enable#` is set.
390 lens_references_adt_enable: bool = "false",
391 /// Whether to show `References` lens for Enum Variants.
392 /// Only applies when `#rust-analyzer.lens.enable#` is set.
393 lens_references_enumVariant_enable: bool = "false",
394 /// Whether to show `Method References` lens. Only applies when
395 /// `#rust-analyzer.lens.enable#` is set.
396 lens_references_method_enable: bool = "false",
397 /// Whether to show `References` lens for Trait.
398 /// Only applies when `#rust-analyzer.lens.enable#` is set.
399 lens_references_trait_enable: bool = "false",
400 /// Whether to show `Run` lens. Only applies when
401 /// `#rust-analyzer.lens.enable#` is set.
402 lens_run_enable: bool = "true",
404 /// Disable project auto-discovery in favor of explicitly specified set
407 /// Elements must be paths pointing to `Cargo.toml`,
408 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
409 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
411 /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
412 lru_capacity: Option<usize> = "null",
414 /// Whether to show `can't find Cargo.toml` error message.
415 notifications_cargoTomlNotFound: bool = "true",
417 /// How many worker threads in the main loop. The default `null` means to pick automatically.
418 numThreads: Option<usize> = "null",
420 /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
421 procMacro_attributes_enable: bool = "true",
422 /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
423 procMacro_enable: bool = "true",
424 /// These proc-macros will be ignored when trying to expand them.
426 /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
427 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
428 /// Internal config, path to proc-macro server executable (typically,
429 /// this is rust-analyzer itself, but we override this in tests).
430 procMacro_server: Option<PathBuf> = "null",
432 /// Exclude imports from find-all-references.
433 references_excludeImports: bool = "false",
435 /// Command to be executed instead of 'cargo' for runnables.
436 runnables_command: Option<String> = "null",
437 /// Additional arguments to be passed to cargo for runnables such as
438 /// tests or binaries. For example, it may be `--release`.
439 runnables_extraArgs: Vec<String> = "[]",
441 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
442 /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
445 /// Any project which uses rust-analyzer with the rustcPrivate
446 /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
448 /// This option does not take effect until rust-analyzer is restarted.
449 rustc_source: Option<String> = "null",
451 /// Additional arguments to `rustfmt`.
452 rustfmt_extraArgs: Vec<String> = "[]",
453 /// Advanced option, fully override the command rust-analyzer uses for
455 rustfmt_overrideCommand: Option<Vec<String>> = "null",
456 /// Enables the use of rustfmt's unstable range formatting command for the
457 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
458 /// available on a nightly build.
459 rustfmt_rangeFormatting_enable: bool = "false",
461 /// Inject additional highlighting into doc comments.
463 /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
465 semanticHighlighting_doc_comment_inject_enable: bool = "true",
466 /// Use semantic tokens for operators.
468 /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
469 /// they are tagged with modifiers.
470 semanticHighlighting_operator_enable: bool = "true",
471 /// Use specialized semantic tokens for operators.
473 /// When enabled, rust-analyzer will emit special token types for operator tokens instead
474 /// of the generic `operator` token type.
475 semanticHighlighting_operator_specialization_enable: bool = "false",
476 /// Use semantic tokens for punctuations.
478 /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
479 /// they are tagged with modifiers or have a special role.
480 semanticHighlighting_punctuation_enable: bool = "false",
481 /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
483 semanticHighlighting_punctuation_separate_macro_bang: bool = "false",
484 /// Use specialized semantic tokens for punctuations.
486 /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
487 /// of the generic `punctuation` token type.
488 semanticHighlighting_punctuation_specialization_enable: bool = "false",
489 /// Use semantic tokens for strings.
491 /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
492 /// By disabling semantic tokens for strings, other grammars can be used to highlight
494 semanticHighlighting_strings_enable: bool = "true",
496 /// Show full signature of the callable. Only shows parameters if disabled.
497 signatureInfo_detail: SignatureDetail = "\"full\"",
498 /// Show documentation.
499 signatureInfo_documentation_enable: bool = "true",
501 /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
502 typing_autoClosingAngleBrackets_enable: bool = "false",
504 /// Workspace symbol search kind.
505 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
506 /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
507 /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
508 /// Other clients requires all results upfront and might require a higher limit.
509 workspace_symbol_search_limit: usize = "128",
510 /// Workspace symbol search scope.
511 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
515 impl Default for ConfigData {
516 fn default() -> Self {
517 ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
521 #[derive(Debug, Clone)]
523 pub discovered_projects: Option<Vec<ProjectManifest>>,
524 caps: lsp_types::ClientCapabilities,
525 root_path: AbsPathBuf,
527 detached_files: Vec<AbsPathBuf>,
528 snippets: Vec<Snippet>,
531 type ParallelCachePrimingNumThreads = u8;
533 #[derive(Debug, Clone, Eq, PartialEq)]
534 pub enum LinkedProject {
535 ProjectManifest(ProjectManifest),
536 InlineJsonProject(ProjectJson),
539 impl From<ProjectManifest> for LinkedProject {
540 fn from(v: ProjectManifest) -> Self {
541 LinkedProject::ProjectManifest(v)
545 impl From<ProjectJson> for LinkedProject {
546 fn from(v: ProjectJson) -> Self {
547 LinkedProject::InlineJsonProject(v)
551 pub struct CallInfoConfig {
552 pub params_only: bool,
556 #[derive(Clone, Debug, PartialEq, Eq)]
557 pub struct LensConfig {
563 pub implementations: bool,
566 pub method_refs: bool,
567 pub refs_adt: bool, // for Struct, Enum, Union and Trait
568 pub refs_trait: bool, // for Struct, Enum, Union and Trait
569 pub enum_variant_refs: bool,
572 pub location: AnnotationLocation,
575 #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
576 #[serde(rename_all = "snake_case")]
577 pub enum AnnotationLocation {
582 impl From<AnnotationLocation> for ide::AnnotationLocation {
583 fn from(location: AnnotationLocation) -> Self {
585 AnnotationLocation::AboveName => ide::AnnotationLocation::AboveName,
586 AnnotationLocation::AboveWholeItem => ide::AnnotationLocation::AboveWholeItem,
592 pub fn any(&self) -> bool {
595 || self.implementations
599 || self.enum_variant_refs
602 pub fn none(&self) -> bool {
606 pub fn runnable(&self) -> bool {
607 self.run || self.debug
610 pub fn references(&self) -> bool {
611 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
615 #[derive(Clone, Debug, PartialEq, Eq)]
616 pub struct HoverActionsConfig {
617 pub implementations: bool,
618 pub references: bool,
621 pub goto_type_def: bool,
624 impl HoverActionsConfig {
625 pub const NO_ACTIONS: Self = Self {
626 implementations: false,
630 goto_type_def: false,
633 pub fn any(&self) -> bool {
634 self.implementations || self.references || self.runnable() || self.goto_type_def
637 pub fn none(&self) -> bool {
641 pub fn runnable(&self) -> bool {
642 self.run || self.debug
646 #[derive(Debug, Clone)]
647 pub struct FilesConfig {
648 pub watcher: FilesWatcher,
649 pub exclude: Vec<AbsPathBuf>,
652 #[derive(Debug, Clone)]
653 pub enum FilesWatcher {
658 #[derive(Debug, Clone)]
659 pub struct NotificationsConfig {
660 pub cargo_toml_not_found: bool,
663 #[derive(Debug, Clone)]
664 pub enum RustfmtConfig {
665 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
666 CustomCommand { command: String, args: Vec<String> },
669 /// Configuration for runnable items, such as `main` function or tests.
670 #[derive(Debug, Clone)]
671 pub struct RunnablesConfig {
672 /// Custom command to be executed instead of `cargo` for runnables.
673 pub override_cargo: Option<String>,
674 /// Additional arguments for the `cargo`, e.g. `--release`.
675 pub cargo_extra_args: Vec<String>,
678 /// Configuration for workspace symbol search requests.
679 #[derive(Debug, Clone)]
680 pub struct WorkspaceSymbolConfig {
681 /// In what scope should the symbol be searched in.
682 pub search_scope: WorkspaceSymbolSearchScope,
683 /// What kind of symbol is being searched for.
684 pub search_kind: WorkspaceSymbolSearchKind,
685 /// How many items are returned at most.
686 pub search_limit: usize,
689 pub struct ClientCommandsConfig {
690 pub run_single: bool,
691 pub debug_single: bool,
692 pub show_reference: bool,
693 pub goto_location: bool,
694 pub trigger_parameter_hints: bool,
698 pub struct ConfigUpdateError {
699 errors: Vec<(String, serde_json::Error)>,
702 impl fmt::Display for ConfigUpdateError {
703 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704 let errors = self.errors.iter().format_with("\n", |(key, e), f| {
711 "rust-analyzer found {} invalid config value{}:\n{}",
713 if self.errors.len() == 1 { "" } else { "s" },
720 pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
723 data: ConfigData::default(),
724 detached_files: Vec::new(),
725 discovered_projects: None,
727 snippets: Default::default(),
731 pub fn client_specific_adjustments(&mut self, client_info: &Option<ClientInfo>) {
732 // FIXME: remove this when we drop support for vscode 1.65 and below
733 if let Some(client) = client_info {
734 if client.name.contains("Code") || client.name.contains("Codium") {
735 if let Some(version) = &client.version {
736 if version.as_str() < "1.76" {
737 self.data.inlayHints_locationLinks = false;
744 pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
745 tracing::info!("updating config from JSON: {:#}", json);
746 if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
749 let mut errors = Vec::new();
750 self.detached_files =
751 get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
753 .map(AbsPathBuf::assert)
755 patch_old_style::patch_json_for_outdated_configs(&mut json);
756 self.data = ConfigData::from_json(json, &mut errors);
757 tracing::debug!("deserialized config data: {:#?}", self.data);
758 self.snippets.clear();
759 for (name, def) in self.data.completion_snippets_custom.iter() {
760 if def.prefix.is_empty() && def.postfix.is_empty() {
763 let scope = match def.scope {
764 SnippetScopeDef::Expr => SnippetScope::Expr,
765 SnippetScopeDef::Type => SnippetScope::Type,
766 SnippetScopeDef::Item => SnippetScope::Item,
772 def.description.as_ref().unwrap_or(name),
776 Some(snippet) => self.snippets.push(snippet),
777 None => errors.push((
778 format!("snippet {name} is invalid"),
779 <serde_json::Error as serde::de::Error>::custom(
780 "snippet path is invalid or triggers are missing",
786 self.validate(&mut errors);
788 if errors.is_empty() {
791 Err(ConfigUpdateError { errors })
795 fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
796 use serde::de::Error;
797 if self.data.check_command.is_empty() {
799 "/check/command".to_string(),
800 serde_json::Error::custom("expected a non-empty string"),
805 pub fn json_schema() -> serde_json::Value {
806 ConfigData::json_schema()
809 pub fn root_path(&self) -> &AbsPathBuf {
813 pub fn caps(&self) -> &lsp_types::ClientCapabilities {
817 pub fn detached_files(&self) -> &[AbsPathBuf] {
824 || -> _ { Some($expr) }()
827 macro_rules! try_or {
828 ($expr:expr, $or:expr) => {
829 try_!($expr).unwrap_or($or)
833 macro_rules! try_or_def {
835 try_!($expr).unwrap_or_default()
840 pub fn linked_projects(&self) -> Vec<LinkedProject> {
841 match self.data.linkedProjects.as_slice() {
842 [] => match self.discovered_projects.as_ref() {
843 Some(discovered_projects) => {
844 let exclude_dirs: Vec<_> = self
848 .map(|p| self.root_path.join(p))
853 let (ProjectManifest::ProjectJson(path)
854 | ProjectManifest::CargoToml(path)) = p;
855 !exclude_dirs.iter().any(|p| path.starts_with(p))
858 .map(LinkedProject::from)
863 linked_projects => linked_projects
865 .filter_map(|linked_project| match linked_project {
866 ManifestOrProjectJson::Manifest(it) => {
867 let path = self.root_path.join(it);
868 ProjectManifest::from_manifest_file(path)
869 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
873 ManifestOrProjectJson::ProjectJson(it) => {
874 Some(ProjectJson::new(&self.root_path, it.clone()).into())
881 pub fn did_save_text_document_dynamic_registration(&self) -> bool {
882 let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
883 caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
886 pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
888 self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
892 pub fn prefill_caches(&self) -> bool {
893 self.data.cachePriming_enable
896 pub fn location_link(&self) -> bool {
897 try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
900 pub fn line_folding_only(&self) -> bool {
901 try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
904 pub fn hierarchical_symbols(&self) -> bool {
911 .hierarchical_document_symbol_support?
915 pub fn code_action_literals(&self) -> bool {
922 .code_action_literal_support
927 pub fn work_done_progress(&self) -> bool {
928 try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
931 pub fn will_rename(&self) -> bool {
932 try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
935 pub fn change_annotation_support(&self) -> bool {
942 .change_annotation_support
947 pub fn code_action_resolve(&self) -> bool {
959 .any(|it| it == "edit")
962 pub fn signature_help_label_offsets(&self) -> bool {
969 .signature_information
971 .parameter_information
973 .label_offset_support?
977 pub fn completion_label_details_support(&self) -> bool {
986 .label_details_support
991 pub fn position_encoding(&self) -> PositionEncoding {
992 if supports_utf8(&self.caps) {
993 PositionEncoding::Utf8
995 PositionEncoding::Utf16
999 fn experimental(&self, index: &'static str) -> bool {
1000 try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
1003 pub fn code_action_group(&self) -> bool {
1004 self.experimental("codeActionGroup")
1007 pub fn server_status_notification(&self) -> bool {
1008 self.experimental("serverStatusNotification")
1011 /// Whether the client supports colored output for full diagnostics from `checkOnSave`.
1012 pub fn color_diagnostic_output(&self) -> bool {
1013 self.experimental("colorDiagnosticOutput")
1016 pub fn publish_diagnostics(&self) -> bool {
1017 self.data.diagnostics_enable
1020 pub fn diagnostics(&self) -> DiagnosticsConfig {
1022 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
1023 proc_macros_enabled: self.data.procMacro_enable,
1024 disable_experimental: !self.data.diagnostics_experimental_enable,
1025 disabled: self.data.diagnostics_disabled.clone(),
1026 expr_fill_default: match self.data.assist_expressionFillDefault {
1027 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
1028 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
1030 insert_use: self.insert_use_config(),
1031 prefer_no_std: self.data.imports_prefer_no_std,
1035 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
1036 DiagnosticsMapConfig {
1037 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
1038 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
1039 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
1043 pub fn extra_env(&self) -> &FxHashMap<String, String> {
1044 &self.data.cargo_extraEnv
1047 pub fn check_on_save_extra_env(&self) -> FxHashMap<String, String> {
1048 let mut extra_env = self.data.cargo_extraEnv.clone();
1049 extra_env.extend(self.data.check_extraEnv.clone());
1053 pub fn lru_capacity(&self) -> Option<usize> {
1054 self.data.lru_capacity
1057 pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, /* is path explicitly set */ bool)> {
1058 if !self.data.procMacro_enable {
1061 Some(match &self.data.procMacro_server {
1063 AbsPathBuf::try_from(it.clone()).unwrap_or_else(|path| self.root_path.join(path)),
1066 None => (AbsPathBuf::assert(std::env::current_exe().ok()?), false),
1070 pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
1071 &self.data.procMacro_ignored
1074 pub fn expand_proc_attr_macros(&self) -> bool {
1075 self.data.procMacro_enable && self.data.procMacro_attributes_enable
1078 pub fn files(&self) -> FilesConfig {
1080 watcher: match self.data.files_watcher {
1081 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
1082 FilesWatcher::Client
1084 _ => FilesWatcher::Server,
1086 exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
1090 pub fn notifications(&self) -> NotificationsConfig {
1091 NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
1094 pub fn cargo_autoreload(&self) -> bool {
1095 self.data.cargo_autoreload
1098 pub fn run_build_scripts(&self) -> bool {
1099 self.data.cargo_buildScripts_enable || self.data.procMacro_enable
1102 pub fn cargo(&self) -> CargoConfig {
1103 let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
1104 if rustc_src == "discover" {
1105 RustcSource::Discover
1107 RustcSource::Path(self.root_path.join(rustc_src))
1110 let sysroot = self.data.cargo_sysroot.as_ref().map(|sysroot| {
1111 if sysroot == "discover" {
1112 RustcSource::Discover
1114 RustcSource::Path(self.root_path.join(sysroot))
1119 features: match &self.data.cargo_features {
1120 CargoFeaturesDef::All => CargoFeatures::All,
1121 CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
1122 features: features.clone(),
1123 no_default_features: self.data.cargo_noDefaultFeatures,
1126 target: self.data.cargo_target.clone(),
1129 unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
1130 wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
1131 invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy {
1132 InvocationStrategy::Once => project_model::InvocationStrategy::Once,
1133 InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
1135 invocation_location: match self.data.cargo_buildScripts_invocationLocation {
1136 InvocationLocation::Root => {
1137 project_model::InvocationLocation::Root(self.root_path.clone())
1139 InvocationLocation::Workspace => project_model::InvocationLocation::Workspace,
1141 run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
1142 extra_env: self.data.cargo_extraEnv.clone(),
1146 pub fn rustfmt(&self) -> RustfmtConfig {
1147 match &self.data.rustfmt_overrideCommand {
1148 Some(args) if !args.is_empty() => {
1149 let mut args = args.clone();
1150 let command = args.remove(0);
1151 RustfmtConfig::CustomCommand { command, args }
1153 Some(_) | None => RustfmtConfig::Rustfmt {
1154 extra_args: self.data.rustfmt_extraArgs.clone(),
1155 enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
1160 pub fn flycheck(&self) -> FlycheckConfig {
1161 match &self.data.check_overrideCommand {
1162 Some(args) if !args.is_empty() => {
1163 let mut args = args.clone();
1164 let command = args.remove(0);
1165 FlycheckConfig::CustomCommand {
1168 extra_env: self.check_on_save_extra_env(),
1169 invocation_strategy: match self.data.check_invocationStrategy {
1170 InvocationStrategy::Once => flycheck::InvocationStrategy::Once,
1171 InvocationStrategy::PerWorkspace => {
1172 flycheck::InvocationStrategy::PerWorkspace
1175 invocation_location: match self.data.check_invocationLocation {
1176 InvocationLocation::Root => {
1177 flycheck::InvocationLocation::Root(self.root_path.clone())
1179 InvocationLocation::Workspace => flycheck::InvocationLocation::Workspace,
1183 Some(_) | None => FlycheckConfig::CargoCommand {
1184 command: self.data.check_command.clone(),
1185 target_triples: self
1189 .and_then(|targets| match &targets.0[..] {
1191 targets => Some(targets.into()),
1193 .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
1194 all_targets: self.data.check_allTargets,
1195 no_default_features: self
1197 .check_noDefaultFeatures
1198 .unwrap_or(self.data.cargo_noDefaultFeatures),
1199 all_features: matches!(
1200 self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
1201 CargoFeaturesDef::All
1203 features: match self
1207 .unwrap_or_else(|| self.data.cargo_features.clone())
1209 CargoFeaturesDef::All => vec![],
1210 CargoFeaturesDef::Selected(it) => it,
1212 extra_args: self.data.check_extraArgs.clone(),
1213 extra_env: self.check_on_save_extra_env(),
1214 ansi_color_output: self.color_diagnostic_output(),
1219 pub fn check_on_save(&self) -> bool {
1220 self.data.checkOnSave
1223 pub fn runnables(&self) -> RunnablesConfig {
1225 override_cargo: self.data.runnables_command.clone(),
1226 cargo_extra_args: self.data.runnables_extraArgs.clone(),
1230 pub fn inlay_hints(&self) -> InlayHintsConfig {
1232 location_links: self.data.inlayHints_locationLinks,
1233 render_colons: self.data.inlayHints_renderColons,
1234 type_hints: self.data.inlayHints_typeHints_enable,
1235 parameter_hints: self.data.inlayHints_parameterHints_enable,
1236 chaining_hints: self.data.inlayHints_chainingHints_enable,
1237 discriminant_hints: match self.data.inlayHints_discriminantHints_enable {
1238 DiscriminantHintsDef::Always => ide::DiscriminantHints::Always,
1239 DiscriminantHintsDef::Never => ide::DiscriminantHints::Never,
1240 DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless,
1242 closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
1243 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
1244 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
1245 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
1247 lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
1248 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
1249 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
1250 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
1252 hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
1253 hide_closure_initialization_hints: self
1255 .inlayHints_typeHints_hideClosureInitialization,
1256 adjustment_hints: match self.data.inlayHints_expressionAdjustmentHints_enable {
1257 AdjustmentHintsDef::Always => ide::AdjustmentHints::Always,
1258 AdjustmentHintsDef::Never => match self.data.inlayHints_reborrowHints_enable {
1259 ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => {
1260 ide::AdjustmentHints::ReborrowOnly
1262 ReborrowHintsDef::Never => ide::AdjustmentHints::Never,
1264 AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly,
1266 adjustment_hints_mode: match self.data.inlayHints_expressionAdjustmentHints_mode {
1267 AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix,
1268 AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix,
1269 AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix,
1270 AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix,
1272 adjustment_hints_hide_outside_unsafe: self
1274 .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe,
1275 binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
1276 param_names_for_lifetime_elision_hints: self
1278 .inlayHints_lifetimeElisionHints_useParameterNames,
1279 max_length: self.data.inlayHints_maxLength,
1280 closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1281 Some(self.data.inlayHints_closingBraceHints_minLines)
1288 fn insert_use_config(&self) -> InsertUseConfig {
1290 granularity: match self.data.imports_granularity_group {
1291 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
1292 ImportGranularityDef::Item => ImportGranularity::Item,
1293 ImportGranularityDef::Crate => ImportGranularity::Crate,
1294 ImportGranularityDef::Module => ImportGranularity::Module,
1296 enforce_granularity: self.data.imports_granularity_enforce,
1297 prefix_kind: match self.data.imports_prefix {
1298 ImportPrefixDef::Plain => PrefixKind::Plain,
1299 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
1300 ImportPrefixDef::BySelf => PrefixKind::BySelf,
1302 group: self.data.imports_group_enable,
1303 skip_glob_imports: !self.data.imports_merge_glob,
1307 pub fn completion(&self) -> CompletionConfig {
1309 enable_postfix_completions: self.data.completion_postfix_enable,
1310 enable_imports_on_the_fly: self.data.completion_autoimport_enable
1311 && completion_item_edit_resolve(&self.caps),
1312 enable_self_on_the_fly: self.data.completion_autoself_enable,
1313 enable_private_editable: self.data.completion_privateEditable_enable,
1314 callable: match self.data.completion_callable_snippets {
1315 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1316 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1317 CallableCompletionDef::None => None,
1319 insert_use: self.insert_use_config(),
1320 prefer_no_std: self.data.imports_prefer_no_std,
1321 snippet_cap: SnippetCap::new(try_or_def!(
1331 snippets: self.snippets.clone(),
1335 pub fn find_all_refs_exclude_imports(&self) -> bool {
1336 self.data.references_excludeImports
1339 pub fn snippet_cap(&self) -> bool {
1340 self.experimental("snippetTextEdit")
1343 pub fn assist(&self) -> AssistConfig {
1345 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
1347 insert_use: self.insert_use_config(),
1348 prefer_no_std: self.data.imports_prefer_no_std,
1349 assist_emit_must_use: self.data.assist_emitMustUse,
1353 pub fn join_lines(&self) -> JoinLinesConfig {
1355 join_else_if: self.data.joinLines_joinElseIf,
1356 remove_trailing_comma: self.data.joinLines_removeTrailingComma,
1357 unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
1358 join_assignments: self.data.joinLines_joinAssignments,
1362 pub fn call_info(&self) -> CallInfoConfig {
1364 params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
1365 docs: self.data.signatureInfo_documentation_enable,
1369 pub fn lens(&self) -> LensConfig {
1371 run: self.data.lens_enable && self.data.lens_run_enable,
1372 debug: self.data.lens_enable && self.data.lens_debug_enable,
1373 implementations: self.data.lens_enable && self.data.lens_implementations_enable,
1374 method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
1375 refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
1376 refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
1377 enum_variant_refs: self.data.lens_enable
1378 && self.data.lens_references_enumVariant_enable,
1379 location: self.data.lens_location,
1383 pub fn hover_actions(&self) -> HoverActionsConfig {
1384 let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
1385 HoverActionsConfig {
1386 implementations: enable && self.data.hover_actions_implementations_enable,
1387 references: enable && self.data.hover_actions_references_enable,
1388 run: enable && self.data.hover_actions_run_enable,
1389 debug: enable && self.data.hover_actions_debug_enable,
1390 goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
1394 pub fn highlighting_config(&self) -> HighlightConfig {
1396 strings: self.data.semanticHighlighting_strings_enable,
1397 punctuation: self.data.semanticHighlighting_punctuation_enable,
1398 specialize_punctuation: self
1400 .semanticHighlighting_punctuation_specialization_enable,
1401 macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang,
1402 operator: self.data.semanticHighlighting_operator_enable,
1403 specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
1404 inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable,
1405 syntactic_name_ref_highlighting: false,
1409 pub fn hover(&self) -> HoverConfig {
1411 links_in_hover: self.data.hover_links_enable,
1412 documentation: self.data.hover_documentation_enable.then(|| {
1413 let is_markdown = try_or_def!(self
1422 .contains(&MarkupKind::Markdown);
1424 HoverDocFormat::Markdown
1426 HoverDocFormat::PlainText
1429 keywords: self.data.hover_documentation_keywords_enable,
1433 pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
1434 WorkspaceSymbolConfig {
1435 search_scope: match self.data.workspace_symbol_search_scope {
1436 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
1437 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
1438 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
1441 search_kind: match self.data.workspace_symbol_search_kind {
1442 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
1443 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
1445 search_limit: self.data.workspace_symbol_search_limit,
1449 pub fn semantic_tokens_refresh(&self) -> bool {
1450 try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
1453 pub fn code_lens_refresh(&self) -> bool {
1454 try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
1457 pub fn insert_replace_support(&self) -> bool {
1466 .insert_replace_support?
1470 pub fn client_commands(&self) -> ClientCommandsConfig {
1472 try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
1473 let commands: Option<lsp_ext::ClientCommandOptions> =
1474 serde_json::from_value(commands.clone()).ok();
1475 let force = commands.is_none() && self.data.lens_forceCustomCommands;
1476 let commands = commands.map(|it| it.commands).unwrap_or_default();
1478 let get = |name: &str| commands.iter().any(|it| it == name) || force;
1480 ClientCommandsConfig {
1481 run_single: get("rust-analyzer.runSingle"),
1482 debug_single: get("rust-analyzer.debugSingle"),
1483 show_reference: get("rust-analyzer.showReferences"),
1484 goto_location: get("rust-analyzer.gotoLocation"),
1485 trigger_parameter_hints: get("editor.action.triggerParameterHints"),
1489 pub fn highlight_related(&self) -> HighlightRelatedConfig {
1490 HighlightRelatedConfig {
1491 references: self.data.highlightRelated_references_enable,
1492 break_points: self.data.highlightRelated_breakPoints_enable,
1493 exit_points: self.data.highlightRelated_exitPoints_enable,
1494 yield_points: self.data.highlightRelated_yieldPoints_enable,
1498 pub fn prime_caches_num_threads(&self) -> u8 {
1499 match self.data.cachePriming_numThreads {
1500 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
1505 pub fn main_loop_num_threads(&self) -> usize {
1506 self.data.numThreads.unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1))
1509 pub fn typing_autoclose_angle(&self) -> bool {
1510 self.data.typing_autoClosingAngleBrackets_enable
1513 // Deserialization definitions
1515 macro_rules! create_bool_or_string_de {
1516 ($ident:ident<$bool:literal, $string:literal>) => {
1517 fn $ident<'de, D>(d: D) -> Result<(), D::Error>
1519 D: serde::Deserializer<'de>,
1522 impl<'de> serde::de::Visitor<'de> for V {
1525 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1526 formatter.write_str(concat!(
1529 stringify!($string),
1534 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1536 E: serde::de::Error,
1540 _ => Err(serde::de::Error::invalid_value(
1541 serde::de::Unexpected::Bool(v),
1547 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1549 E: serde::de::Error,
1553 _ => Err(serde::de::Error::invalid_value(
1554 serde::de::Unexpected::Str(v),
1560 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
1562 A: serde::de::EnumAccess<'de>,
1564 use serde::de::VariantAccess;
1565 let (variant, va) = a.variant::<&'de str>()?;
1569 _ => Err(serde::de::Error::invalid_value(
1570 serde::de::Unexpected::Str(variant),
1576 d.deserialize_any(V)
1580 create_bool_or_string_de!(true_or_always<true, "always">);
1581 create_bool_or_string_de!(false_or_never<false, "never">);
1583 macro_rules! named_unit_variant {
1584 ($variant:ident) => {
1585 pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
1587 D: serde::Deserializer<'de>,
1590 impl<'de> serde::de::Visitor<'de> for V {
1592 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1593 f.write_str(concat!("\"", stringify!($variant), "\""))
1595 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
1596 if value == stringify!($variant) {
1599 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
1603 deserializer.deserialize_str(V)
1609 named_unit_variant!(all);
1610 named_unit_variant!(skip_trivial);
1611 named_unit_variant!(mutable);
1612 named_unit_variant!(reborrow);
1613 named_unit_variant!(fieldless);
1614 named_unit_variant!(with_block);
1617 #[derive(Deserialize, Debug, Clone, Copy)]
1618 #[serde(rename_all = "snake_case")]
1619 enum SnippetScopeDef {
1625 impl Default for SnippetScopeDef {
1626 fn default() -> Self {
1627 SnippetScopeDef::Expr
1631 #[derive(Deserialize, Debug, Clone, Default)]
1634 #[serde(deserialize_with = "single_or_array")]
1635 prefix: Vec<String>,
1636 #[serde(deserialize_with = "single_or_array")]
1637 postfix: Vec<String>,
1638 description: Option<String>,
1639 #[serde(deserialize_with = "single_or_array")]
1641 #[serde(deserialize_with = "single_or_array")]
1642 requires: Vec<String>,
1643 scope: SnippetScopeDef,
1646 fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
1648 D: serde::Deserializer<'de>,
1652 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
1653 type Value = Vec<String>;
1655 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1656 formatter.write_str("string or array of strings")
1659 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1661 E: serde::de::Error,
1663 Ok(vec![value.to_owned()])
1666 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
1668 A: serde::de::SeqAccess<'de>,
1670 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
1674 deserializer.deserialize_any(SingleOrVec)
1677 #[derive(Deserialize, Debug, Clone)]
1679 enum ManifestOrProjectJson {
1681 ProjectJson(ProjectJsonData),
1684 #[derive(Deserialize, Debug, Clone)]
1685 #[serde(rename_all = "snake_case")]
1686 enum ExprFillDefaultDef {
1691 #[derive(Deserialize, Debug, Clone)]
1692 #[serde(rename_all = "snake_case")]
1693 enum ImportGranularityDef {
1700 #[derive(Deserialize, Debug, Copy, Clone)]
1701 #[serde(rename_all = "snake_case")]
1702 enum CallableCompletionDef {
1708 #[derive(Deserialize, Debug, Clone)]
1710 enum CargoFeaturesDef {
1711 #[serde(deserialize_with = "de_unit_v::all")]
1713 Selected(Vec<String>),
1716 #[derive(Deserialize, Debug, Clone)]
1717 #[serde(rename_all = "snake_case")]
1718 enum InvocationStrategy {
1723 #[derive(Deserialize, Debug, Clone)]
1724 struct CheckOnSaveTargets(#[serde(deserialize_with = "single_or_array")] Vec<String>);
1726 #[derive(Deserialize, Debug, Clone)]
1727 #[serde(rename_all = "snake_case")]
1728 enum InvocationLocation {
1733 #[derive(Deserialize, Debug, Clone)]
1735 enum LifetimeElisionDef {
1736 #[serde(deserialize_with = "true_or_always")]
1738 #[serde(deserialize_with = "false_or_never")]
1740 #[serde(deserialize_with = "de_unit_v::skip_trivial")]
1744 #[derive(Deserialize, Debug, Clone)]
1746 enum ClosureReturnTypeHintsDef {
1747 #[serde(deserialize_with = "true_or_always")]
1749 #[serde(deserialize_with = "false_or_never")]
1751 #[serde(deserialize_with = "de_unit_v::with_block")]
1755 #[derive(Deserialize, Debug, Clone)]
1757 enum ReborrowHintsDef {
1758 #[serde(deserialize_with = "true_or_always")]
1760 #[serde(deserialize_with = "false_or_never")]
1762 #[serde(deserialize_with = "de_unit_v::mutable")]
1766 #[derive(Deserialize, Debug, Clone)]
1768 enum AdjustmentHintsDef {
1769 #[serde(deserialize_with = "true_or_always")]
1771 #[serde(deserialize_with = "false_or_never")]
1773 #[serde(deserialize_with = "de_unit_v::reborrow")]
1777 #[derive(Deserialize, Debug, Clone)]
1779 enum DiscriminantHintsDef {
1780 #[serde(deserialize_with = "true_or_always")]
1782 #[serde(deserialize_with = "false_or_never")]
1784 #[serde(deserialize_with = "de_unit_v::fieldless")]
1788 #[derive(Deserialize, Debug, Clone)]
1789 #[serde(rename_all = "snake_case")]
1790 enum AdjustmentHintsModeDef {
1797 #[derive(Deserialize, Debug, Clone)]
1798 #[serde(rename_all = "snake_case")]
1799 enum FilesWatcherDef {
1805 #[derive(Deserialize, Debug, Clone)]
1806 #[serde(rename_all = "snake_case")]
1807 enum ImportPrefixDef {
1809 #[serde(alias = "self")]
1811 #[serde(alias = "crate")]
1815 #[derive(Deserialize, Debug, Clone)]
1816 #[serde(rename_all = "snake_case")]
1817 enum WorkspaceSymbolSearchScopeDef {
1819 WorkspaceAndDependencies,
1822 #[derive(Deserialize, Debug, Clone)]
1823 #[serde(rename_all = "snake_case")]
1824 enum SignatureDetail {
1829 #[derive(Deserialize, Debug, Clone)]
1830 #[serde(rename_all = "snake_case")]
1831 enum WorkspaceSymbolSearchKindDef {
1836 macro_rules! _config_data {
1837 (struct $name:ident {
1839 $(#[doc=$doc:literal])*
1840 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
1843 #[allow(non_snake_case)]
1844 #[derive(Debug, Clone)]
1845 struct $name { $($field: $ty,)* }
1847 fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
1853 None$(.or(Some(stringify!($alias))))*,
1859 fn json_schema() -> serde_json::Value {
1862 let field = stringify!($field);
1863 let ty = stringify!($ty);
1865 (field, ty, &[$($doc),*], $default)
1871 fn manual() -> String {
1874 let field = stringify!($field);
1875 let ty = stringify!($ty);
1877 (field, ty, &[$($doc),*], $default)
1884 fn fields_are_sorted() {
1885 [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
1889 use _config_data as config_data;
1891 fn get_field<T: DeserializeOwned>(
1892 json: &mut serde_json::Value,
1893 error_sink: &mut Vec<(String, serde_json::Error)>,
1894 field: &'static str,
1895 alias: Option<&'static str>,
1898 // XXX: check alias first, to work-around the VS Code where it pre-fills the
1899 // defaults instead of sending an empty object.
1902 .chain(iter::once(field))
1903 .filter_map(move |field| {
1904 let mut pointer = field.replace('_', "/");
1905 pointer.insert(0, '/');
1906 json.pointer_mut(&pointer)
1907 .map(|it| serde_json::from_value(it.take()).map_err(|e| (e, pointer)))
1909 .find(Result::is_ok)
1910 .and_then(|res| match res {
1912 Err((e, pointer)) => {
1913 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
1914 error_sink.push((pointer, e));
1918 .unwrap_or_else(|| serde_json::from_str(default).unwrap())
1921 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
1924 .map(|(field, ty, doc, default)| {
1925 let name = field.replace('_', ".");
1926 let name = format!("rust-analyzer.{name}");
1927 let props = field_props(field, ty, doc, default);
1930 .collect::<serde_json::Map<_, _>>();
1934 fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
1935 let doc = doc_comment_to_string(doc);
1936 let doc = doc.trim_end_matches('\n');
1938 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
1939 "bad docs for {field}: {doc:?}"
1941 let default = default.parse::<serde_json::Value>().unwrap();
1943 let mut map = serde_json::Map::default();
1945 ($($key:literal: $value:tt),*$(,)?) => {{$(
1946 map.insert($key.into(), serde_json::json!($value));
1949 set!("markdownDescription": doc);
1950 set!("default": default);
1953 "bool" => set!("type": "boolean"),
1954 "usize" => set!("type": "integer", "minimum": 0),
1955 "String" => set!("type": "string"),
1956 "Vec<String>" => set! {
1958 "items": { "type": "string" },
1960 "Vec<PathBuf>" => set! {
1962 "items": { "type": "string" },
1964 "FxHashSet<String>" => set! {
1966 "items": { "type": "string" },
1967 "uniqueItems": true,
1969 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
1972 "FxHashMap<String, SnippetDef>" => set! {
1975 "FxHashMap<String, String>" => set! {
1978 "Option<usize>" => set! {
1979 "type": ["null", "integer"],
1982 "Option<String>" => set! {
1983 "type": ["null", "string"],
1985 "Option<PathBuf>" => set! {
1986 "type": ["null", "string"],
1988 "Option<bool>" => set! {
1989 "type": ["null", "boolean"],
1991 "Option<Vec<String>>" => set! {
1992 "type": ["null", "array"],
1993 "items": { "type": "string" },
1995 "ExprFillDefaultDef" => set! {
1997 "enum": ["todo", "default"],
1998 "enumDescriptions": [
1999 "Fill missing expressions with the `todo` macro",
2000 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
2003 "ImportGranularityDef" => set! {
2005 "enum": ["preserve", "crate", "module", "item"],
2006 "enumDescriptions": [
2007 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
2008 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
2009 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
2010 "Flatten imports so that each has its own use statement."
2013 "ImportPrefixDef" => set! {
2020 "enumDescriptions": [
2021 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
2022 "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.",
2023 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
2026 "Vec<ManifestOrProjectJson>" => set! {
2028 "items": { "type": ["string", "object"] },
2030 "WorkspaceSymbolSearchScopeDef" => set! {
2032 "enum": ["workspace", "workspace_and_dependencies"],
2033 "enumDescriptions": [
2034 "Search in current workspace only.",
2035 "Search in current workspace and dependencies."
2038 "WorkspaceSymbolSearchKindDef" => set! {
2040 "enum": ["only_types", "all_symbols"],
2041 "enumDescriptions": [
2042 "Search for types only.",
2043 "Search for all symbols kinds."
2046 "ParallelCachePrimingNumThreads" => set! {
2051 "LifetimeElisionDef" => set! {
2058 "enumDescriptions": [
2059 "Always show lifetime elision hints.",
2060 "Never show lifetime elision hints.",
2061 "Only show lifetime elision hints if a return type is involved."
2064 "ClosureReturnTypeHintsDef" => set! {
2071 "enumDescriptions": [
2072 "Always show type hints for return types of closures.",
2073 "Never show type hints for return types of closures.",
2074 "Only show type hints for return types of closures with blocks."
2077 "ReborrowHintsDef" => set! {
2084 "enumDescriptions": [
2085 "Always show reborrow hints.",
2086 "Never show reborrow hints.",
2087 "Only show mutable reborrow hints."
2090 "AdjustmentHintsDef" => set! {
2097 "enumDescriptions": [
2098 "Always show all adjustment hints.",
2099 "Never show adjustment hints.",
2100 "Only show auto borrow and dereference adjustment hints."
2103 "DiscriminantHintsDef" => set! {
2110 "enumDescriptions": [
2111 "Always show all discriminant hints.",
2112 "Never show discriminant hints.",
2113 "Only show discriminant hints on fieldless enum variants."
2116 "AdjustmentHintsModeDef" => set! {
2124 "enumDescriptions": [
2125 "Always show adjustment hints as prefix (`*expr`).",
2126 "Always show adjustment hints as postfix (`expr.*`).",
2127 "Show prefix or postfix depending on which uses less parenthesis, prefering prefix.",
2128 "Show prefix or postfix depending on which uses less parenthesis, prefering postfix.",
2131 "CargoFeaturesDef" => set! {
2138 "enumDescriptions": [
2139 "Pass `--all-features` to cargo",
2144 "items": { "type": "string" }
2148 "Option<CargoFeaturesDef>" => set! {
2155 "enumDescriptions": [
2156 "Pass `--all-features` to cargo",
2161 "items": { "type": "string" }
2166 "CallableCompletionDef" => set! {
2173 "enumDescriptions": [
2174 "Add call parentheses and pre-fill arguments.",
2175 "Add call parentheses.",
2176 "Do no snippet completions for callables."
2179 "SignatureDetail" => set! {
2181 "enum": ["full", "parameters"],
2182 "enumDescriptions": [
2183 "Show the entire signature.",
2184 "Show only the parameters."
2187 "FilesWatcherDef" => set! {
2189 "enum": ["client", "server"],
2190 "enumDescriptions": [
2191 "Use the client (editor) to watch files for changes",
2192 "Use server-side file watching",
2195 "AnnotationLocation" => set! {
2197 "enum": ["above_name", "above_whole_item"],
2198 "enumDescriptions": [
2199 "Render annotations above the name of the item.",
2200 "Render annotations above the whole item, including documentation comments and attributes."
2203 "InvocationStrategy" => set! {
2205 "enum": ["per_workspace", "once"],
2206 "enumDescriptions": [
2207 "The command will be executed for each workspace.",
2208 "The command will be executed once."
2211 "InvocationLocation" => set! {
2213 "enum": ["workspace", "root"],
2214 "enumDescriptions": [
2215 "The command will be executed in the corresponding workspace root.",
2216 "The command will be executed in the project root."
2219 "Option<CheckOnSaveTargets>" => set! {
2229 "items": { "type": "string" }
2233 _ => panic!("missing entry for {ty}: {default}"),
2240 fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
2243 .map(|(field, _ty, doc, default)| {
2244 let name = format!("rust-analyzer.{}", field.replace('_', "."));
2245 let doc = doc_comment_to_string(doc);
2246 if default.contains('\n') {
2248 r#"[[{name}]]{name}::
2260 format!("[[{name}]]{name} (default: `{default}`)::\n+\n--\n{doc}--\n")
2263 .collect::<String>()
2266 fn doc_comment_to_string(doc: &[&str]) -> String {
2267 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{it}\n")).collect()
2274 use test_utils::{ensure_file_contents, project_root};
2279 fn generate_package_json_config() {
2280 let s = Config::json_schema();
2281 let schema = format!("{s:#}");
2282 let mut schema = schema
2283 .trim_start_matches('{')
2284 .trim_end_matches('}')
2286 .replace('\n', "\n ")
2287 .trim_start_matches('\n')
2290 schema.push_str(",\n");
2292 // Transform the asciidoc form link to markdown style.
2294 // https://link[text] => [text](https://link)
2295 let url_matches = schema.match_indices("https://");
2296 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
2297 url_offsets.reverse();
2298 for idx in url_offsets {
2299 let link = &schema[idx..];
2300 // matching on whitespace to ignore normal links
2301 if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
2302 if link.chars().nth(link_end) == Some('[') {
2303 if let Some(link_text_end) = link.find(']') {
2304 let link_text = link[link_end..(link_text_end + 1)].to_string();
2306 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
2307 schema.insert(idx, '(');
2308 schema.insert(idx + link_end + 1, ')');
2309 schema.insert_str(idx, &link_text);
2315 let package_json_path = project_root().join("editors/code/package.json");
2316 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
2318 let start_marker = " \"$generated-start\": {},\n";
2319 let end_marker = " \"$generated-end\": {}\n";
2321 let start = package_json.find(start_marker).unwrap() + start_marker.len();
2322 let end = package_json.find(end_marker).unwrap();
2324 let p = remove_ws(&package_json[start..end]);
2325 let s = remove_ws(&schema);
2326 if !p.contains(&s) {
2327 package_json.replace_range(start..end, &schema);
2328 ensure_file_contents(&package_json_path, &package_json)
2333 fn generate_config_documentation() {
2334 let docs_path = project_root().join("docs/user/generated_config.adoc");
2335 let expected = ConfigData::manual();
2336 ensure_file_contents(&docs_path, &expected);
2339 fn remove_ws(text: &str) -> String {
2340 text.replace(char::is_whitespace, "")