1use std::{env, fmt, iter, ops::Not, sync::OnceLock};
7
8use cfg::{CfgAtom, CfgDiff};
9use hir::Symbol;
10use ide::{
11 AnnotationConfig, AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig,
12 CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, GotoDefinitionConfig,
13 GotoImplementationConfig, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat,
14 InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig,
15 MemoryLayoutHoverRenderKind, RenameConfig, Snippet, SnippetScope, SourceRootId,
16};
17use ide_db::{
18 MiniCore, SnippetCap,
19 assists::ExprFillDefaultMode,
20 imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
21};
22use itertools::{Either, Itertools};
23use paths::{Utf8Path, Utf8PathBuf};
24use project_model::{
25 CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectJsonFromCommand,
26 ProjectManifest, RustLibSource, TargetDirectoryConfig,
27};
28use rustc_hash::{FxHashMap, FxHashSet};
29use semver::Version;
30use serde::{
31 Deserialize, Serialize,
32 de::{DeserializeOwned, Error},
33};
34use stdx::format_to_acc;
35use triomphe::Arc;
36use vfs::{AbsPath, AbsPathBuf, VfsPath};
37
38use crate::{
39 diagnostics::DiagnosticsMapConfig,
40 flycheck::{CargoOptions, FlycheckConfig},
41 lsp::capabilities::ClientCapabilities,
42 lsp_ext::{WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
43};
44
45type FxIndexMap<K, V> = indexmap::IndexMap<K, V, rustc_hash::FxBuildHasher>;
46
47mod patch_old_style;
48
49#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub enum MaxSubstitutionLength {
59 Hide,
60 #[serde(untagged)]
61 Limit(usize),
62}
63
64config_data! {
73 global: struct GlobalDefaultConfigData <- GlobalConfigInput -> {
83 cachePriming_enable: bool = true,
85
86 cachePriming_numThreads: NumThreads = NumThreads::Physical,
89
90 completion_snippets_custom: FxIndexMap<String, SnippetDef> =
92 Config::completion_snippets_default(),
93
94 files_exclude | files_excludeDirs: Vec<Utf8PathBuf> = vec![],
100
101 gotoImplementations_filterAdjacentDerives: bool = false,
103
104 highlightRelated_branchExitPoints_enable: bool = true,
107
108 highlightRelated_breakPoints_enable: bool = true,
111
112 highlightRelated_closureCaptures_enable: bool = true,
114
115 highlightRelated_exitPoints_enable: bool = true,
118
119 highlightRelated_references_enable: bool = true,
121
122 highlightRelated_yieldPoints_enable: bool = true,
125
126 hover_actions_debug_enable: bool = true,
128
129 hover_actions_enable: bool = true,
131
132 hover_actions_gotoTypeDef_enable: bool = true,
135
136 hover_actions_implementations_enable: bool = true,
139
140 hover_actions_references_enable: bool = false,
143
144 hover_actions_run_enable: bool = true,
146
147 hover_actions_updateTest_enable: bool = true,
150
151 hover_documentation_enable: bool = true,
153
154 hover_documentation_keywords_enable: bool = true,
157
158 hover_dropGlue_enable: bool = true,
160
161 hover_links_enable: bool = true,
163
164 hover_maxSubstitutionLength: Option<MaxSubstitutionLength> =
172 Some(MaxSubstitutionLength::Limit(20)),
173
174 hover_memoryLayout_alignment: Option<MemoryLayoutHoverRenderKindDef> =
176 Some(MemoryLayoutHoverRenderKindDef::Hexadecimal),
177
178 hover_memoryLayout_enable: bool = true,
180
181 hover_memoryLayout_niches: Option<bool> = Some(false),
183
184 hover_memoryLayout_offset: Option<MemoryLayoutHoverRenderKindDef> =
186 Some(MemoryLayoutHoverRenderKindDef::Hexadecimal),
187
188 hover_memoryLayout_padding: Option<MemoryLayoutHoverRenderKindDef> = None,
190
191 hover_memoryLayout_size: Option<MemoryLayoutHoverRenderKindDef> =
193 Some(MemoryLayoutHoverRenderKindDef::Both),
194
195 hover_show_enumVariants: Option<usize> = Some(5),
197
198 hover_show_fields: Option<usize> = Some(5),
201
202 hover_show_traitAssocItems: Option<usize> = None,
204
205 inlayHints_bindingModeHints_enable: bool = false,
207
208 inlayHints_chainingHints_enable: bool = true,
210
211 inlayHints_closingBraceHints_enable: bool = true,
213
214 inlayHints_closingBraceHints_minLines: usize = 25,
217
218 inlayHints_closureCaptureHints_enable: bool = false,
220
221 inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef =
223 ClosureReturnTypeHintsDef::Never,
224
225 inlayHints_closureStyle: ClosureStyle = ClosureStyle::ImplFn,
227
228 inlayHints_discriminantHints_enable: DiscriminantHintsDef =
230 DiscriminantHintsDef::Never,
231
232 inlayHints_expressionAdjustmentHints_disableReborrows: bool =
238 true,
239
240 inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef =
242 AdjustmentHintsDef::Never,
243
244 inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = false,
246
247 inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef =
249 AdjustmentHintsModeDef::Prefix,
250
251 inlayHints_genericParameterHints_const_enable: bool = true,
253
254 inlayHints_genericParameterHints_lifetime_enable: bool = false,
256
257 inlayHints_genericParameterHints_type_enable: bool = false,
259
260 inlayHints_implicitDrops_enable: bool = false,
262
263 inlayHints_implicitSizedBoundHints_enable: bool = false,
265
266 inlayHints_impliedDynTraitHints_enable: bool = true,
268
269 inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = LifetimeElisionDef::Never,
271
272 inlayHints_lifetimeElisionHints_useParameterNames: bool = false,
274
275 inlayHints_maxLength: Option<usize> = Some(25),
279
280 inlayHints_parameterHints_enable: bool = true,
282
283 inlayHints_parameterHints_missingArguments_enable: bool = false,
285
286 inlayHints_rangeExclusiveHints_enable: bool = false,
288
289 inlayHints_reborrowHints_enable: ReborrowHintsDef = ReborrowHintsDef::Never,
294
295 inlayHints_renderColons: bool = true,
297
298 inlayHints_typeHints_enable: bool = true,
300
301 inlayHints_typeHints_hideClosureInitialization: bool = false,
306
307 inlayHints_typeHints_hideClosureParameter: bool = false,
309
310 inlayHints_typeHints_hideInferredTypes: bool = false,
312
313 inlayHints_typeHints_hideNamedConstructor: bool = false,
315
316 interpret_tests: bool = false,
318
319 joinLines_joinAssignments: bool = true,
321
322 joinLines_joinElseIf: bool = true,
324
325 joinLines_removeTrailingComma: bool = true,
327
328 joinLines_unwrapTrivialBlock: bool = true,
330
331 lens_debug_enable: bool = true,
333
334 lens_enable: bool = true,
336
337 lens_implementations_enable: bool = true,
339
340 lens_location: AnnotationLocation = AnnotationLocation::AboveName,
342
343 lens_references_adt_enable: bool = false,
346
347 lens_references_enumVariant_enable: bool = false,
350
351 lens_references_method_enable: bool = false,
353
354 lens_references_trait_enable: bool = false,
357
358 lens_run_enable: bool = true,
360
361 lens_updateTest_enable: bool = true,
364
365 linkedProjects: Vec<ManifestOrProjectJson> = vec![],
370
371 lru_capacity: Option<u16> = None,
373
374 lru_query_capacities: FxHashMap<Box<str>, u16> = FxHashMap::default(),
376
377 notifications_cargoTomlNotFound: bool = true,
379
380 numThreads: Option<NumThreads> = None,
383
384 procMacro_attributes_enable: bool = true,
386
387 procMacro_enable: bool = true,
389
390 procMacro_processes: NumProcesses = NumProcesses::Concrete(1),
395
396 procMacro_server: Option<Utf8PathBuf> = None,
398
399 profiling_memoryProfile: Option<Utf8PathBuf> = None,
404
405 references_excludeImports: bool = false,
407
408 references_excludeTests: bool = false,
410
411 semanticHighlighting_comments_enable: bool = true,
417
418 semanticHighlighting_doc_comment_inject_enable: bool = true,
423
424 semanticHighlighting_nonStandardTokens: bool = true,
429
430 semanticHighlighting_operator_enable: bool = true,
435
436 semanticHighlighting_operator_specialization_enable: bool = false,
441
442 semanticHighlighting_punctuation_enable: bool = false,
447
448 semanticHighlighting_punctuation_separate_macro_bang: bool = false,
451
452 semanticHighlighting_punctuation_specialization_enable: bool = false,
457
458 semanticHighlighting_strings_enable: bool = true,
464
465 signatureInfo_detail: SignatureDetail = SignatureDetail::Full,
467
468 signatureInfo_documentation_enable: bool = true,
470
471 typing_triggerChars: Option<String> = Some("=.".to_owned()),
485
486
487 workspace_discoverConfig: Option<DiscoverWorkspaceConfig> = None,
604 }
605}
606
607config_data! {
608 local: struct LocalDefaultConfigData <- LocalConfigInput -> {
610 assist_emitMustUse: bool = false,
612
613 assist_expressionFillDefault: ExprFillDefaultDef = ExprFillDefaultDef::Todo,
615
616 assist_preferSelf: bool = false,
618
619 assist_termSearch_borrowcheck: bool = true,
622
623 assist_termSearch_fuel: usize = 1800,
625
626 completion_addSemicolonToUnit: bool = true,
630
631 completion_autoAwait_enable: bool = true,
634
635 completion_autoIter_enable: bool = true,
638
639 completion_autoimport_enable: bool = true,
644
645 completion_autoimport_exclude: Vec<AutoImportExclusion> = vec![
658 AutoImportExclusion::Verbose { path: "core::borrow::Borrow".to_owned(), r#type: AutoImportExclusionType::Methods },
659 AutoImportExclusion::Verbose { path: "core::borrow::BorrowMut".to_owned(), r#type: AutoImportExclusionType::Methods },
660 ],
661
662 completion_autoself_enable: bool = true,
665
666 completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments,
668
669 completion_excludeTraits: Vec<String> = Vec::new(),
677
678 completion_fullFunctionSignatures_enable: bool = false,
680
681 completion_hideDeprecated: bool = false,
684
685 completion_limit: Option<usize> = None,
687
688 completion_postfix_enable: bool = true,
690
691 completion_privateEditable_enable: bool = false,
694
695 completion_termSearch_enable: bool = false,
697
698 completion_termSearch_fuel: usize = 1000,
700
701 diagnostics_disabled: FxHashSet<String> = FxHashSet::default(),
703
704 diagnostics_enable: bool = true,
706
707 diagnostics_experimental_enable: bool = false,
710
711 diagnostics_remapPrefix: FxHashMap<String, String> = FxHashMap::default(),
714
715 diagnostics_styleLints_enable: bool = false,
717
718 diagnostics_warningsAsHint: Vec<String> = vec![],
723
724 diagnostics_warningsAsInfo: Vec<String> = vec![],
729
730 imports_granularity_enforce: bool = false,
733
734 imports_granularity_group: ImportGranularityDef = ImportGranularityDef::Crate,
736
737 imports_group_enable: bool = true,
741
742 imports_merge_glob: bool = true,
745
746 imports_preferNoStd | imports_prefer_no_std: bool = false,
748
749 imports_preferPrelude: bool = false,
751
752 imports_prefix: ImportPrefixDef = ImportPrefixDef::ByCrate,
754
755 imports_prefixExternPrelude: bool = false,
759
760 rename_showConflicts: bool = true,
762 }
763}
764
765config_data! {
766 workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> {
767 cargo_allTargets: bool = true,
769 cargo_autoreload: bool = true,
772 cargo_buildScripts_enable: bool = true,
774 cargo_buildScripts_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace,
782 cargo_buildScripts_overrideCommand: Option<Vec<String>> = None,
802 cargo_buildScripts_rebuildOnSave: bool = true,
805 cargo_buildScripts_useRustcWrapper: bool = true,
808 cargo_cfgs: Vec<String> = {
814 vec!["debug_assertions".into(), "miri".into()]
815 },
816 cargo_extraArgs: Vec<String> = vec![],
818 cargo_extraEnv: FxHashMap<String, Option<String>> = FxHashMap::default(),
821 cargo_features: CargoFeaturesDef = CargoFeaturesDef::Selected(vec![]),
825 cargo_noDefaultFeatures: bool = false,
827 cargo_noDeps: bool = false,
830 cargo_sysroot: Option<String> = Some("discover".to_owned()),
837 cargo_sysrootSrc: Option<String> = None,
842 cargo_target: Option<String> = None,
846 cargo_targetDir | ra_ap_rust_analyzerTargetDir: Option<TargetDirectory> = None,
853
854 cfg_setTest: bool = true,
856
857 checkOnSave | checkOnSave_enable: bool = true,
859
860
861 check_allTargets | checkOnSave_allTargets: Option<bool> = None,
864 check_command | checkOnSave_command: String = "check".to_owned(),
866 check_extraArgs | checkOnSave_extraArgs: Vec<String> = vec![],
868 check_extraEnv | checkOnSave_extraEnv: FxHashMap<String, Option<String>> = FxHashMap::default(),
871 check_features | checkOnSave_features: Option<CargoFeaturesDef> = None,
876 check_ignore: FxHashSet<String> = FxHashSet::default(),
880 check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace,
886 check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option<bool> = None,
889 check_overrideCommand | checkOnSave_overrideCommand: Option<Vec<String>> = None,
926 check_targets | checkOnSave_targets | checkOnSave_target: Option<CheckOnSaveTargets> = None,
933 check_workspace: bool = true,
937
938 document_symbol_search_excludeLocals: bool = true,
940
941 procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = FxHashMap::default(),
945
946 runnables_bench_command: String = "bench".to_owned(),
948 runnables_bench_overrideCommand: Option<Vec<String>> = None,
955 runnables_command: Option<String> = None,
957 runnables_doctest_overrideCommand: Option<Vec<String>> = None,
964 runnables_extraArgs: Vec<String> = vec![],
967 runnables_extraTestBinaryArgs: Vec<String> = vec!["--nocapture".to_owned()],
975 runnables_test_command: String = "test".to_owned(),
977 runnables_test_overrideCommand: Option<Vec<String>> = None,
984
985 rustc_source: Option<String> = None,
994
995 rustfmt_extraArgs: Vec<String> = vec![],
997 rustfmt_overrideCommand: Option<Vec<String>> = None,
1006 rustfmt_rangeFormatting_enable: bool = false,
1010
1011 vfs_extraIncludes: Vec<String> = vec![],
1015
1016 workspace_symbol_search_excludeImports: bool = false,
1022 workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = WorkspaceSymbolSearchKindDef::OnlyTypes,
1024 workspace_symbol_search_limit: usize = 128,
1028 workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = WorkspaceSymbolSearchScopeDef::Workspace,
1030 }
1031}
1032
1033config_data! {
1034 client: struct ClientDefaultConfigData <- ClientConfigInput -> {
1037
1038 files_watcher: FilesWatcherDef = FilesWatcherDef::Client,
1040
1041
1042 }
1043}
1044
1045#[derive(Debug)]
1046pub enum RatomlFileKind {
1047 Workspace,
1048 Crate,
1049}
1050
1051#[derive(Debug, Clone)]
1052#[allow(clippy::large_enum_variant)]
1053enum RatomlFile {
1054 Workspace(WorkspaceLocalConfigInput),
1055 Crate(LocalConfigInput),
1056}
1057
1058#[derive(Clone, Debug)]
1059struct ClientInfo {
1060 name: String,
1061 version: Option<Version>,
1062}
1063
1064#[derive(Clone)]
1065pub struct Config {
1066 discovered_projects_from_filesystem: Vec<ProjectManifest>,
1070 discovered_projects_from_command: Vec<ProjectJsonFromCommand>,
1073 workspace_roots: Vec<AbsPathBuf>,
1075 caps: ClientCapabilities,
1076 root_path: AbsPathBuf,
1078 snippets: Vec<Snippet>,
1079 client_info: Option<ClientInfo>,
1080
1081 default_config: &'static DefaultConfigData,
1082 client_config: (FullConfigInput, ConfigErrors),
1085
1086 user_config: Option<(GlobalWorkspaceLocalConfigInput, ConfigErrors)>,
1088
1089 ratoml_file: FxHashMap<SourceRootId, (RatomlFile, ConfigErrors)>,
1090
1091 source_root_parent_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
1093
1094 validation_errors: ConfigErrors,
1099
1100 detached_files: Vec<AbsPathBuf>,
1101}
1102
1103impl fmt::Debug for Config {
1104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1105 f.debug_struct("Config")
1106 .field("discovered_projects_from_filesystem", &self.discovered_projects_from_filesystem)
1107 .field("discovered_projects_from_command", &self.discovered_projects_from_command)
1108 .field("workspace_roots", &self.workspace_roots)
1109 .field("caps", &self.caps)
1110 .field("root_path", &self.root_path)
1111 .field("snippets", &self.snippets)
1112 .field("client_info", &self.client_info)
1113 .field("client_config", &self.client_config)
1114 .field("user_config", &self.user_config)
1115 .field("ratoml_file", &self.ratoml_file)
1116 .field("source_root_parent_map", &self.source_root_parent_map)
1117 .field("validation_errors", &self.validation_errors)
1118 .field("detached_files", &self.detached_files)
1119 .finish()
1120 }
1121}
1122
1123impl std::ops::Deref for Config {
1125 type Target = ClientCapabilities;
1126
1127 fn deref(&self) -> &Self::Target {
1128 &self.caps
1129 }
1130}
1131
1132impl Config {
1133 pub fn user_config_dir_path() -> Option<AbsPathBuf> {
1135 let user_config_path = if let Some(path) = env::var_os("__TEST_RA_USER_CONFIG_DIR") {
1136 std::path::PathBuf::from(path)
1137 } else {
1138 dirs::config_dir()?.join("rust-analyzer")
1139 };
1140 Some(AbsPathBuf::assert_utf8(user_config_path))
1141 }
1142
1143 pub fn same_source_root_parent_map(
1144 &self,
1145 other: &Arc<FxHashMap<SourceRootId, SourceRootId>>,
1146 ) -> bool {
1147 Arc::ptr_eq(&self.source_root_parent_map, other)
1148 }
1149
1150 fn apply_change_with_sink(&self, change: ConfigChange) -> (Config, bool) {
1154 let mut config = self.clone();
1155 config.validation_errors = ConfigErrors::default();
1156
1157 let mut should_update = false;
1158
1159 if let Some(change) = change.user_config_change {
1160 tracing::info!("updating config from user config toml: {:#}", change);
1161 if let Ok(table) = toml::from_str(&change) {
1162 let mut toml_errors = vec![];
1163 validate_toml_table(
1164 GlobalWorkspaceLocalConfigInput::FIELDS,
1165 &table,
1166 &mut String::new(),
1167 &mut toml_errors,
1168 );
1169 config.user_config = Some((
1170 GlobalWorkspaceLocalConfigInput::from_toml(table, &mut toml_errors),
1171 ConfigErrors(
1172 toml_errors
1173 .into_iter()
1174 .map(|(a, b)| ConfigErrorInner::Toml { config_key: a, error: b })
1175 .map(Arc::new)
1176 .collect(),
1177 ),
1178 ));
1179 should_update = true;
1180 }
1181 }
1182
1183 if let Some(mut json) = change.client_config_change {
1184 tracing::info!("updating config from JSON: {:#}", json);
1185
1186 if !(json.is_null() || json.as_object().is_some_and(|it| it.is_empty())) {
1187 let detached_files = get_field_json::<Vec<Utf8PathBuf>>(
1188 &mut json,
1189 &mut Vec::new(),
1190 "detachedFiles",
1191 None,
1192 )
1193 .unwrap_or_default()
1194 .into_iter()
1195 .map(AbsPathBuf::assert)
1196 .collect();
1197
1198 patch_old_style::patch_json_for_outdated_configs(&mut json);
1199
1200 let mut json_errors = vec![];
1201
1202 let input = FullConfigInput::from_json(json, &mut json_errors);
1203
1204 config.snippets.clear();
1206
1207 let snips = input
1208 .global
1209 .completion_snippets_custom
1210 .as_ref()
1211 .unwrap_or(&self.default_config.global.completion_snippets_custom);
1212 #[allow(dead_code)]
1213 let _ = Self::completion_snippets_custom;
1214 for (name, def) in snips.iter() {
1215 if def.prefix.is_empty() && def.postfix.is_empty() {
1216 continue;
1217 }
1218 let scope = match def.scope {
1219 SnippetScopeDef::Expr => SnippetScope::Expr,
1220 SnippetScopeDef::Type => SnippetScope::Type,
1221 SnippetScopeDef::Item => SnippetScope::Item,
1222 };
1223 match Snippet::new(
1224 &def.prefix,
1225 &def.postfix,
1226 &def.body,
1227 def.description.as_ref().unwrap_or(name),
1228 &def.requires,
1229 scope,
1230 ) {
1231 Some(snippet) => config.snippets.push(snippet),
1232 None => json_errors.push((
1233 name.to_owned(),
1234 <serde_json::Error as serde::de::Error>::custom(format!(
1235 "snippet {name} is invalid or triggers are missing",
1236 )),
1237 )),
1238 }
1239 }
1240
1241 config.client_config = (
1242 input,
1243 ConfigErrors(
1244 json_errors
1245 .into_iter()
1246 .map(|(a, b)| ConfigErrorInner::Json { config_key: a, error: b })
1247 .map(Arc::new)
1248 .collect(),
1249 ),
1250 );
1251 config.detached_files = detached_files;
1252 }
1253 should_update = true;
1254 }
1255
1256 if let Some(change) = change.ratoml_file_change {
1257 for (source_root_id, (kind, _, text)) in change {
1258 match kind {
1259 RatomlFileKind::Crate => {
1260 if let Some(text) = text {
1261 let mut toml_errors = vec![];
1262 tracing::info!("updating ra-toml crate config: {:#}", text);
1263 match toml::from_str(&text) {
1264 Ok(table) => {
1265 validate_toml_table(
1266 &[LocalConfigInput::FIELDS],
1267 &table,
1268 &mut String::new(),
1269 &mut toml_errors,
1270 );
1271 config.ratoml_file.insert(
1272 source_root_id,
1273 (
1274 RatomlFile::Crate(LocalConfigInput::from_toml(
1275 &table,
1276 &mut toml_errors,
1277 )),
1278 ConfigErrors(
1279 toml_errors
1280 .into_iter()
1281 .map(|(a, b)| ConfigErrorInner::Toml {
1282 config_key: a,
1283 error: b,
1284 })
1285 .map(Arc::new)
1286 .collect(),
1287 ),
1288 ),
1289 );
1290 }
1291 Err(e) => {
1292 config.validation_errors.0.push(
1293 ConfigErrorInner::ParseError {
1294 reason: e.message().to_owned(),
1295 }
1296 .into(),
1297 );
1298 }
1299 }
1300 }
1301 }
1302 RatomlFileKind::Workspace => {
1303 if let Some(text) = text {
1304 tracing::info!("updating ra-toml workspace config: {:#}", text);
1305 let mut toml_errors = vec![];
1306 match toml::from_str(&text) {
1307 Ok(table) => {
1308 validate_toml_table(
1309 WorkspaceLocalConfigInput::FIELDS,
1310 &table,
1311 &mut String::new(),
1312 &mut toml_errors,
1313 );
1314 config.ratoml_file.insert(
1315 source_root_id,
1316 (
1317 RatomlFile::Workspace(
1318 WorkspaceLocalConfigInput::from_toml(
1319 table,
1320 &mut toml_errors,
1321 ),
1322 ),
1323 ConfigErrors(
1324 toml_errors
1325 .into_iter()
1326 .map(|(a, b)| ConfigErrorInner::Toml {
1327 config_key: a,
1328 error: b,
1329 })
1330 .map(Arc::new)
1331 .collect(),
1332 ),
1333 ),
1334 );
1335 should_update = true;
1336 }
1337 Err(e) => {
1338 config.validation_errors.0.push(
1339 ConfigErrorInner::ParseError {
1340 reason: e.message().to_owned(),
1341 }
1342 .into(),
1343 );
1344 }
1345 }
1346 }
1347 }
1348 }
1349 }
1350 }
1351
1352 if let Some(source_root_map) = change.source_map_change {
1353 config.source_root_parent_map = source_root_map;
1354 }
1355
1356 if config.check_command(None).is_empty() {
1357 config.validation_errors.0.push(Arc::new(ConfigErrorInner::Json {
1358 config_key: "/check/command".to_owned(),
1359 error: serde_json::Error::custom("expected a non-empty string"),
1360 }));
1361 }
1362
1363 (config, should_update)
1364 }
1365
1366 pub fn apply_change(&self, change: ConfigChange) -> (Config, ConfigErrors, bool) {
1370 let (config, should_update) = self.apply_change_with_sink(change);
1371 let e = ConfigErrors(
1372 config
1373 .client_config
1374 .1
1375 .0
1376 .iter()
1377 .chain(config.user_config.as_ref().into_iter().flat_map(|it| it.1.0.iter()))
1378 .chain(config.ratoml_file.values().flat_map(|it| it.1.0.iter()))
1379 .chain(config.validation_errors.0.iter())
1380 .cloned()
1381 .collect(),
1382 );
1383 (config, e, should_update)
1384 }
1385
1386 pub fn add_discovered_project_from_command(
1387 &mut self,
1388 data: ProjectJsonData,
1389 buildfile: AbsPathBuf,
1390 ) {
1391 for proj in self.discovered_projects_from_command.iter_mut() {
1392 if proj.buildfile == buildfile {
1393 proj.data = data;
1394 return;
1395 }
1396 }
1397
1398 self.discovered_projects_from_command.push(ProjectJsonFromCommand { data, buildfile });
1399 }
1400
1401 pub fn workspace_roots(&self) -> &[AbsPathBuf] {
1402 &self.workspace_roots
1403 }
1404}
1405
1406#[derive(Default, Debug)]
1407pub struct ConfigChange {
1408 user_config_change: Option<Arc<str>>,
1409 client_config_change: Option<serde_json::Value>,
1410 ratoml_file_change:
1411 Option<FxHashMap<SourceRootId, (RatomlFileKind, VfsPath, Option<Arc<str>>)>>,
1412 source_map_change: Option<Arc<FxHashMap<SourceRootId, SourceRootId>>>,
1413}
1414
1415impl ConfigChange {
1416 pub fn change_ratoml(
1417 &mut self,
1418 source_root: SourceRootId,
1419 vfs_path: VfsPath,
1420 content: Option<Arc<str>>,
1421 ) -> Option<(RatomlFileKind, VfsPath, Option<Arc<str>>)> {
1422 self.ratoml_file_change
1423 .get_or_insert_with(Default::default)
1424 .insert(source_root, (RatomlFileKind::Crate, vfs_path, content))
1425 }
1426
1427 pub fn change_user_config(&mut self, content: Option<Arc<str>>) {
1428 assert!(self.user_config_change.is_none()); self.user_config_change = content;
1430 }
1431
1432 pub fn change_workspace_ratoml(
1433 &mut self,
1434 source_root: SourceRootId,
1435 vfs_path: VfsPath,
1436 content: Option<Arc<str>>,
1437 ) -> Option<(RatomlFileKind, VfsPath, Option<Arc<str>>)> {
1438 self.ratoml_file_change
1439 .get_or_insert_with(Default::default)
1440 .insert(source_root, (RatomlFileKind::Workspace, vfs_path, content))
1441 }
1442
1443 pub fn change_client_config(&mut self, change: serde_json::Value) {
1444 self.client_config_change = Some(change);
1445 }
1446
1447 pub fn change_source_root_parent_map(
1448 &mut self,
1449 source_root_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
1450 ) {
1451 assert!(self.source_map_change.is_none());
1452 self.source_map_change = Some(source_root_map);
1453 }
1454}
1455
1456#[derive(Debug, Clone, Eq, PartialEq)]
1457pub enum LinkedProject {
1458 ProjectManifest(ProjectManifest),
1459 InlineProjectJson(ProjectJson),
1460}
1461
1462impl From<ProjectManifest> for LinkedProject {
1463 fn from(v: ProjectManifest) -> Self {
1464 LinkedProject::ProjectManifest(v)
1465 }
1466}
1467
1468impl From<ProjectJson> for LinkedProject {
1469 fn from(v: ProjectJson) -> Self {
1470 LinkedProject::InlineProjectJson(v)
1471 }
1472}
1473
1474#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1475#[serde(rename_all = "camelCase")]
1476pub struct DiscoverWorkspaceConfig {
1477 pub command: Vec<String>,
1478 pub progress_label: String,
1479 pub files_to_watch: Vec<String>,
1480}
1481
1482pub struct CallInfoConfig {
1483 pub params_only: bool,
1484 pub docs: bool,
1485}
1486
1487#[derive(Clone, Debug, PartialEq, Eq)]
1488pub struct LensConfig {
1489 pub run: bool,
1491 pub debug: bool,
1492 pub update_test: bool,
1493 pub interpret: bool,
1494
1495 pub implementations: bool,
1497
1498 pub method_refs: bool,
1500 pub refs_adt: bool, pub refs_trait: bool, pub enum_variant_refs: bool,
1503
1504 pub location: AnnotationLocation,
1506 pub filter_adjacent_derive_implementations: bool,
1507}
1508
1509#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1510#[serde(rename_all = "snake_case")]
1511pub enum AnnotationLocation {
1512 AboveName,
1513 AboveWholeItem,
1514}
1515
1516impl From<AnnotationLocation> for ide::AnnotationLocation {
1517 fn from(location: AnnotationLocation) -> Self {
1518 match location {
1519 AnnotationLocation::AboveName => ide::AnnotationLocation::AboveName,
1520 AnnotationLocation::AboveWholeItem => ide::AnnotationLocation::AboveWholeItem,
1521 }
1522 }
1523}
1524
1525impl LensConfig {
1526 pub fn any(&self) -> bool {
1527 self.run
1528 || self.debug
1529 || self.update_test
1530 || self.implementations
1531 || self.method_refs
1532 || self.refs_adt
1533 || self.refs_trait
1534 || self.enum_variant_refs
1535 }
1536
1537 pub fn none(&self) -> bool {
1538 !self.any()
1539 }
1540
1541 pub fn runnable(&self) -> bool {
1542 self.run || self.debug || self.update_test
1543 }
1544
1545 pub fn references(&self) -> bool {
1546 self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
1547 }
1548
1549 pub fn into_annotation_config<'a>(
1550 self,
1551 binary_target: bool,
1552 minicore: MiniCore<'a>,
1553 ) -> AnnotationConfig<'a> {
1554 AnnotationConfig {
1555 binary_target,
1556 annotate_runnables: self.runnable(),
1557 annotate_impls: self.implementations,
1558 annotate_references: self.refs_adt,
1559 annotate_method_references: self.method_refs,
1560 annotate_enum_variant_references: self.enum_variant_refs,
1561 location: self.location.into(),
1562 minicore,
1563 filter_adjacent_derive_implementations: self.filter_adjacent_derive_implementations,
1564 }
1565 }
1566}
1567
1568#[derive(Clone, Debug, PartialEq, Eq)]
1569pub struct HoverActionsConfig {
1570 pub implementations: bool,
1571 pub references: bool,
1572 pub run: bool,
1573 pub debug: bool,
1574 pub update_test: bool,
1575 pub goto_type_def: bool,
1576}
1577
1578impl HoverActionsConfig {
1579 pub const NO_ACTIONS: Self = Self {
1580 implementations: false,
1581 references: false,
1582 run: false,
1583 debug: false,
1584 update_test: false,
1585 goto_type_def: false,
1586 };
1587
1588 pub fn any(&self) -> bool {
1589 self.implementations || self.references || self.runnable() || self.goto_type_def
1590 }
1591
1592 pub fn none(&self) -> bool {
1593 !self.any()
1594 }
1595
1596 pub fn runnable(&self) -> bool {
1597 self.run || self.debug || self.update_test
1598 }
1599}
1600
1601#[derive(Debug, Clone)]
1602pub struct FilesConfig {
1603 pub watcher: FilesWatcher,
1604 pub exclude: Vec<AbsPathBuf>,
1605}
1606
1607#[derive(Debug, Clone)]
1608pub enum FilesWatcher {
1609 Client,
1610 Server,
1611}
1612
1613#[derive(Debug, Clone)]
1615pub struct DocumentSymbolConfig {
1616 pub search_exclude_locals: bool,
1618}
1619
1620#[derive(Debug, Clone)]
1621pub struct NotificationsConfig {
1622 pub cargo_toml_not_found: bool,
1623}
1624
1625#[derive(Debug, Clone)]
1626pub enum RustfmtConfig {
1627 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
1628 CustomCommand { command: String, args: Vec<String> },
1629}
1630
1631#[derive(Debug, Clone)]
1633pub struct RunnablesConfig {
1634 pub override_cargo: Option<String>,
1636 pub cargo_extra_args: Vec<String>,
1638 pub extra_test_binary_args: Vec<String>,
1640 pub test_command: String,
1642 pub test_override_command: Option<Vec<String>>,
1644 pub bench_command: String,
1646 pub bench_override_command: Option<Vec<String>>,
1648 pub doc_test_override_command: Option<Vec<String>>,
1650}
1651
1652#[derive(Debug, Clone)]
1654pub struct WorkspaceSymbolConfig {
1655 pub search_exclude_imports: bool,
1657 pub search_scope: WorkspaceSymbolSearchScope,
1659 pub search_kind: WorkspaceSymbolSearchKind,
1661 pub search_limit: usize,
1663}
1664#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1665pub struct ClientCommandsConfig {
1666 pub run_single: bool,
1667 pub debug_single: bool,
1668 pub show_reference: bool,
1669 pub goto_location: bool,
1670 pub trigger_parameter_hints: bool,
1671 pub rename: bool,
1672}
1673
1674#[derive(Debug)]
1675pub enum ConfigErrorInner {
1676 Json { config_key: String, error: serde_json::Error },
1677 Toml { config_key: String, error: toml::de::Error },
1678 ParseError { reason: String },
1679}
1680
1681#[derive(Clone, Debug, Default)]
1682pub struct ConfigErrors(Vec<Arc<ConfigErrorInner>>);
1683
1684impl ConfigErrors {
1685 pub fn is_empty(&self) -> bool {
1686 self.0.is_empty()
1687 }
1688}
1689
1690impl fmt::Display for ConfigErrors {
1691 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1692 let errors = self.0.iter().format_with("\n", |inner, f| {
1693 match &**inner {
1694 ConfigErrorInner::Json { config_key: key, error: e } => {
1695 f(key)?;
1696 f(&": ")?;
1697 f(e)
1698 }
1699 ConfigErrorInner::Toml { config_key: key, error: e } => {
1700 f(key)?;
1701 f(&": ")?;
1702 f(e)
1703 }
1704 ConfigErrorInner::ParseError { reason } => f(reason),
1705 }?;
1706 f(&";")
1707 });
1708 write!(f, "invalid config value{}:\n{}", if self.0.len() == 1 { "" } else { "s" }, errors)
1709 }
1710}
1711
1712impl std::error::Error for ConfigErrors {}
1713
1714impl Config {
1715 pub fn new(
1716 root_path: AbsPathBuf,
1717 caps: lsp_types::ClientCapabilities,
1718 workspace_roots: Vec<AbsPathBuf>,
1719 client_info: Option<lsp_types::ClientInfo>,
1720 ) -> Self {
1721 static DEFAULT_CONFIG_DATA: OnceLock<&'static DefaultConfigData> = OnceLock::new();
1722
1723 Config {
1724 caps: ClientCapabilities::new(caps),
1725 discovered_projects_from_filesystem: Vec::new(),
1726 discovered_projects_from_command: Vec::new(),
1727 root_path,
1728 snippets: Default::default(),
1729 workspace_roots,
1730 client_info: client_info.map(|it| ClientInfo {
1731 name: it.name,
1732 version: it.version.as_deref().map(Version::parse).and_then(Result::ok),
1733 }),
1734 client_config: (FullConfigInput::default(), ConfigErrors(vec![])),
1735 default_config: DEFAULT_CONFIG_DATA.get_or_init(|| Box::leak(Box::default())),
1736 source_root_parent_map: Arc::new(FxHashMap::default()),
1737 user_config: None,
1738 detached_files: Default::default(),
1739 validation_errors: Default::default(),
1740 ratoml_file: Default::default(),
1741 }
1742 }
1743
1744 pub fn rediscover_workspaces(&mut self) {
1745 let discovered = ProjectManifest::discover_all(&self.workspace_roots);
1746 tracing::info!("discovered projects: {:?}", discovered);
1747 if discovered.is_empty() {
1748 tracing::error!("failed to find any projects in {:?}", &self.workspace_roots);
1749 }
1750 self.discovered_projects_from_filesystem = discovered;
1751 }
1752
1753 pub fn remove_workspace(&mut self, path: &AbsPath) {
1754 if let Some(position) = self.workspace_roots.iter().position(|it| it == path) {
1755 self.workspace_roots.remove(position);
1756 }
1757 }
1758
1759 pub fn add_workspaces(&mut self, paths: impl Iterator<Item = AbsPathBuf>) {
1760 self.workspace_roots.extend(paths);
1761 }
1762
1763 pub fn json_schema() -> serde_json::Value {
1764 let mut s = FullConfigInput::json_schema();
1765
1766 fn sort_objects_by_field(json: &mut serde_json::Value) {
1767 if let serde_json::Value::Object(object) = json {
1768 let old = std::mem::take(object);
1769 old.into_iter().sorted_by(|(k, _), (k2, _)| k.cmp(k2)).for_each(|(k, mut v)| {
1770 sort_objects_by_field(&mut v);
1771 object.insert(k, v);
1772 });
1773 }
1774 }
1775 sort_objects_by_field(&mut s);
1776 s
1777 }
1778
1779 pub fn root_path(&self) -> &AbsPathBuf {
1780 &self.root_path
1782 }
1783
1784 pub fn caps(&self) -> &ClientCapabilities {
1785 &self.caps
1786 }
1787
1788 pub fn assist(&self, source_root: Option<SourceRootId>) -> AssistConfig {
1789 AssistConfig {
1790 snippet_cap: self.snippet_cap(),
1791 allowed: None,
1792 insert_use: self.insert_use_config(source_root),
1793 prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
1794 assist_emit_must_use: self.assist_emitMustUse(source_root).to_owned(),
1795 prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
1796 prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
1797 term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
1798 term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(),
1799 code_action_grouping: self.code_action_group(),
1800 expr_fill_default: match self.assist_expressionFillDefault(source_root) {
1801 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
1802 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
1803 ExprFillDefaultDef::Underscore => ExprFillDefaultMode::Underscore,
1804 },
1805 prefer_self_ty: *self.assist_preferSelf(source_root),
1806 show_rename_conflicts: *self.rename_showConflicts(source_root),
1807 }
1808 }
1809
1810 pub fn rename(&self, source_root: Option<SourceRootId>) -> RenameConfig {
1811 RenameConfig {
1812 prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
1813 prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
1814 prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
1815 show_conflicts: *self.rename_showConflicts(source_root),
1816 }
1817 }
1818
1819 pub fn call_hierarchy<'a>(&self, minicore: MiniCore<'a>) -> CallHierarchyConfig<'a> {
1820 CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned(), minicore }
1821 }
1822
1823 pub fn completion<'a>(
1824 &'a self,
1825 source_root: Option<SourceRootId>,
1826 minicore: MiniCore<'a>,
1827 ) -> CompletionConfig<'a> {
1828 let client_capability_fields = self.completion_resolve_support_properties();
1829 CompletionConfig {
1830 enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(),
1831 enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned()
1832 && self.caps.has_completion_item_resolve_additionalTextEdits(),
1833 enable_self_on_the_fly: self.completion_autoself_enable(source_root).to_owned(),
1834 enable_auto_iter: *self.completion_autoIter_enable(source_root),
1835 enable_auto_await: *self.completion_autoAwait_enable(source_root),
1836 enable_private_editable: self.completion_privateEditable_enable(source_root).to_owned(),
1837 full_function_signatures: self
1838 .completion_fullFunctionSignatures_enable(source_root)
1839 .to_owned(),
1840 callable: match self.completion_callable_snippets(source_root) {
1841 CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
1842 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
1843 CallableCompletionDef::None => None,
1844 },
1845 add_semicolon_to_unit: *self.completion_addSemicolonToUnit(source_root),
1846 snippet_cap: SnippetCap::new(self.completion_snippet()),
1847 insert_use: self.insert_use_config(source_root),
1848 prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
1849 prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
1850 prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
1851 snippets: self.snippets.clone().to_vec(),
1852 limit: self.completion_limit(source_root).to_owned(),
1853 enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
1854 term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
1855 fields_to_resolve: if self.client_is_neovim() {
1856 CompletionFieldsToResolve::empty()
1857 } else {
1858 CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields)
1859 },
1860 exclude_flyimport: self
1861 .completion_autoimport_exclude(source_root)
1862 .iter()
1863 .map(|it| match it {
1864 AutoImportExclusion::Path(path) => {
1865 (path.clone(), ide_completion::AutoImportExclusionType::Always)
1866 }
1867 AutoImportExclusion::Verbose { path, r#type } => (
1868 path.clone(),
1869 match r#type {
1870 AutoImportExclusionType::Always => {
1871 ide_completion::AutoImportExclusionType::Always
1872 }
1873 AutoImportExclusionType::Methods => {
1874 ide_completion::AutoImportExclusionType::Methods
1875 }
1876 },
1877 ),
1878 })
1879 .collect(),
1880 exclude_traits: self.completion_excludeTraits(source_root),
1881 minicore,
1882 }
1883 }
1884
1885 pub fn completion_hide_deprecated(&self) -> bool {
1886 *self.completion_hideDeprecated(None)
1887 }
1888
1889 pub fn detached_files(&self) -> &Vec<AbsPathBuf> {
1890 &self.detached_files
1893 }
1894
1895 pub fn diagnostics(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig {
1896 DiagnosticsConfig {
1897 enabled: *self.diagnostics_enable(source_root),
1898 proc_attr_macros_enabled: self.expand_proc_attr_macros(),
1899 proc_macros_enabled: *self.procMacro_enable(),
1900 disable_experimental: !self.diagnostics_experimental_enable(source_root),
1901 disabled: self.diagnostics_disabled(source_root).clone(),
1902 expr_fill_default: match self.assist_expressionFillDefault(source_root) {
1903 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
1904 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
1905 ExprFillDefaultDef::Underscore => ExprFillDefaultMode::Underscore,
1906 },
1907 snippet_cap: self.snippet_cap(),
1908 insert_use: self.insert_use_config(source_root),
1909 prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
1910 prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
1911 prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
1912 style_lints: self.diagnostics_styleLints_enable(source_root).to_owned(),
1913 term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
1914 term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(),
1915 show_rename_conflicts: *self.rename_showConflicts(source_root),
1916 }
1917 }
1918
1919 pub fn diagnostic_fixes(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig {
1920 DiagnosticsConfig {
1922 enabled: true,
1923 disable_experimental: false,
1924 ..self.diagnostics(source_root)
1925 }
1926 }
1927
1928 pub fn expand_proc_attr_macros(&self) -> bool {
1929 self.procMacro_enable().to_owned() && self.procMacro_attributes_enable().to_owned()
1930 }
1931
1932 pub fn highlight_related(&self, _source_root: Option<SourceRootId>) -> HighlightRelatedConfig {
1933 HighlightRelatedConfig {
1934 references: self.highlightRelated_references_enable().to_owned(),
1935 break_points: self.highlightRelated_breakPoints_enable().to_owned(),
1936 exit_points: self.highlightRelated_exitPoints_enable().to_owned(),
1937 yield_points: self.highlightRelated_yieldPoints_enable().to_owned(),
1938 closure_captures: self.highlightRelated_closureCaptures_enable().to_owned(),
1939 branch_exit_points: self.highlightRelated_branchExitPoints_enable().to_owned(),
1940 }
1941 }
1942
1943 pub fn hover_actions(&self) -> HoverActionsConfig {
1944 let enable = self.caps.hover_actions() && self.hover_actions_enable().to_owned();
1945 HoverActionsConfig {
1946 implementations: enable && self.hover_actions_implementations_enable().to_owned(),
1947 references: enable && self.hover_actions_references_enable().to_owned(),
1948 run: enable && self.hover_actions_run_enable().to_owned(),
1949 debug: enable && self.hover_actions_debug_enable().to_owned(),
1950 update_test: enable
1951 && self.hover_actions_run_enable().to_owned()
1952 && self.hover_actions_updateTest_enable().to_owned(),
1953 goto_type_def: enable && self.hover_actions_gotoTypeDef_enable().to_owned(),
1954 }
1955 }
1956
1957 pub fn hover<'a>(&self, minicore: MiniCore<'a>) -> HoverConfig<'a> {
1958 let mem_kind = |kind| match kind {
1959 MemoryLayoutHoverRenderKindDef::Both => MemoryLayoutHoverRenderKind::Both,
1960 MemoryLayoutHoverRenderKindDef::Decimal => MemoryLayoutHoverRenderKind::Decimal,
1961 MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal,
1962 };
1963 HoverConfig {
1964 links_in_hover: self.hover_links_enable().to_owned(),
1965 memory_layout: self.hover_memoryLayout_enable().then_some(MemoryLayoutHoverConfig {
1966 size: self.hover_memoryLayout_size().map(mem_kind),
1967 offset: self.hover_memoryLayout_offset().map(mem_kind),
1968 alignment: self.hover_memoryLayout_alignment().map(mem_kind),
1969 padding: self.hover_memoryLayout_padding().map(mem_kind),
1970 niches: self.hover_memoryLayout_niches().unwrap_or_default(),
1971 }),
1972 documentation: self.hover_documentation_enable().to_owned(),
1973 format: {
1974 if self.caps.hover_markdown_support() {
1975 HoverDocFormat::Markdown
1976 } else {
1977 HoverDocFormat::PlainText
1978 }
1979 },
1980 keywords: self.hover_documentation_keywords_enable().to_owned(),
1981 max_trait_assoc_items_count: self.hover_show_traitAssocItems().to_owned(),
1982 max_fields_count: self.hover_show_fields().to_owned(),
1983 max_enum_variants_count: self.hover_show_enumVariants().to_owned(),
1984 max_subst_ty_len: match self.hover_maxSubstitutionLength() {
1985 Some(MaxSubstitutionLength::Hide) => ide::SubstTyLen::Hide,
1986 Some(MaxSubstitutionLength::Limit(limit)) => ide::SubstTyLen::LimitTo(*limit),
1987 None => ide::SubstTyLen::Unlimited,
1988 },
1989 show_drop_glue: *self.hover_dropGlue_enable(),
1990 minicore,
1991 }
1992 }
1993
1994 pub fn goto_definition<'a>(&self, minicore: MiniCore<'a>) -> GotoDefinitionConfig<'a> {
1995 GotoDefinitionConfig { minicore }
1996 }
1997
1998 pub fn inlay_hints<'a>(&self, minicore: MiniCore<'a>) -> InlayHintsConfig<'a> {
1999 let client_capability_fields = self.inlay_hint_resolve_support_properties();
2000
2001 InlayHintsConfig {
2002 render_colons: self.inlayHints_renderColons().to_owned(),
2003 type_hints: self.inlayHints_typeHints_enable().to_owned(),
2004 sized_bound: self.inlayHints_implicitSizedBoundHints_enable().to_owned(),
2005 parameter_hints: self.inlayHints_parameterHints_enable().to_owned(),
2006 parameter_hints_for_missing_arguments: self
2007 .inlayHints_parameterHints_missingArguments_enable()
2008 .to_owned(),
2009 generic_parameter_hints: GenericParameterHints {
2010 type_hints: self.inlayHints_genericParameterHints_type_enable().to_owned(),
2011 lifetime_hints: self.inlayHints_genericParameterHints_lifetime_enable().to_owned(),
2012 const_hints: self.inlayHints_genericParameterHints_const_enable().to_owned(),
2013 },
2014 chaining_hints: self.inlayHints_chainingHints_enable().to_owned(),
2015 discriminant_hints: match self.inlayHints_discriminantHints_enable() {
2016 DiscriminantHintsDef::Always => ide::DiscriminantHints::Always,
2017 DiscriminantHintsDef::Never => ide::DiscriminantHints::Never,
2018 DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless,
2019 },
2020 closure_return_type_hints: match self.inlayHints_closureReturnTypeHints_enable() {
2021 ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
2022 ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
2023 ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
2024 },
2025 lifetime_elision_hints: match self.inlayHints_lifetimeElisionHints_enable() {
2026 LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
2027 LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
2028 LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
2029 },
2030 hide_named_constructor_hints: self
2031 .inlayHints_typeHints_hideNamedConstructor()
2032 .to_owned(),
2033 hide_inferred_type_hints: self.inlayHints_typeHints_hideInferredTypes().to_owned(),
2034 hide_closure_initialization_hints: self
2035 .inlayHints_typeHints_hideClosureInitialization()
2036 .to_owned(),
2037 hide_closure_parameter_hints: self
2038 .inlayHints_typeHints_hideClosureParameter()
2039 .to_owned(),
2040 closure_style: match self.inlayHints_closureStyle() {
2041 ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn,
2042 ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation,
2043 ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId,
2044 ClosureStyle::Hide => hir::ClosureStyle::Hide,
2045 },
2046 closure_capture_hints: self.inlayHints_closureCaptureHints_enable().to_owned(),
2047 adjustment_hints: match self.inlayHints_expressionAdjustmentHints_enable() {
2048 AdjustmentHintsDef::Always => ide::AdjustmentHints::Always,
2049 AdjustmentHintsDef::Never => match self.inlayHints_reborrowHints_enable() {
2050 ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => {
2051 ide::AdjustmentHints::BorrowsOnly
2052 }
2053 ReborrowHintsDef::Never => ide::AdjustmentHints::Never,
2054 },
2055 AdjustmentHintsDef::Borrows => ide::AdjustmentHints::BorrowsOnly,
2056 },
2057 adjustment_hints_disable_reborrows: *self
2058 .inlayHints_expressionAdjustmentHints_disableReborrows(),
2059 adjustment_hints_mode: match self.inlayHints_expressionAdjustmentHints_mode() {
2060 AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix,
2061 AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix,
2062 AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix,
2063 AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix,
2064 },
2065 adjustment_hints_hide_outside_unsafe: self
2066 .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe()
2067 .to_owned(),
2068 binding_mode_hints: self.inlayHints_bindingModeHints_enable().to_owned(),
2069 param_names_for_lifetime_elision_hints: self
2070 .inlayHints_lifetimeElisionHints_useParameterNames()
2071 .to_owned(),
2072 max_length: self.inlayHints_maxLength().to_owned(),
2073 closing_brace_hints_min_lines: if self.inlayHints_closingBraceHints_enable().to_owned()
2074 {
2075 Some(self.inlayHints_closingBraceHints_minLines().to_owned())
2076 } else {
2077 None
2078 },
2079 fields_to_resolve: InlayFieldsToResolve::from_client_capabilities(
2080 &client_capability_fields,
2081 ),
2082 implicit_drop_hints: self.inlayHints_implicitDrops_enable().to_owned(),
2083 implied_dyn_trait_hints: self.inlayHints_impliedDynTraitHints_enable().to_owned(),
2084 range_exclusive_hints: self.inlayHints_rangeExclusiveHints_enable().to_owned(),
2085 minicore,
2086 }
2087 }
2088
2089 fn insert_use_config(&self, source_root: Option<SourceRootId>) -> InsertUseConfig {
2090 InsertUseConfig {
2091 granularity: match self.imports_granularity_group(source_root) {
2092 ImportGranularityDef::Item | ImportGranularityDef::Preserve => {
2093 ImportGranularity::Item
2094 }
2095 ImportGranularityDef::Crate => ImportGranularity::Crate,
2096 ImportGranularityDef::Module => ImportGranularity::Module,
2097 ImportGranularityDef::One => ImportGranularity::One,
2098 },
2099 enforce_granularity: self.imports_granularity_enforce(source_root).to_owned(),
2100 prefix_kind: match self.imports_prefix(source_root) {
2101 ImportPrefixDef::Plain => PrefixKind::Plain,
2102 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
2103 ImportPrefixDef::BySelf => PrefixKind::BySelf,
2104 },
2105 group: self.imports_group_enable(source_root).to_owned(),
2106 skip_glob_imports: !self.imports_merge_glob(source_root),
2107 }
2108 }
2109
2110 pub fn join_lines(&self) -> JoinLinesConfig {
2111 JoinLinesConfig {
2112 join_else_if: self.joinLines_joinElseIf().to_owned(),
2113 remove_trailing_comma: self.joinLines_removeTrailingComma().to_owned(),
2114 unwrap_trivial_blocks: self.joinLines_unwrapTrivialBlock().to_owned(),
2115 join_assignments: self.joinLines_joinAssignments().to_owned(),
2116 }
2117 }
2118
2119 pub fn highlighting_non_standard_tokens(&self) -> bool {
2120 self.semanticHighlighting_nonStandardTokens().to_owned()
2121 }
2122
2123 pub fn highlighting_config<'a>(&self, minicore: MiniCore<'a>) -> HighlightConfig<'a> {
2124 HighlightConfig {
2125 strings: self.semanticHighlighting_strings_enable().to_owned(),
2126 comments: self.semanticHighlighting_comments_enable().to_owned(),
2127 punctuation: self.semanticHighlighting_punctuation_enable().to_owned(),
2128 specialize_punctuation: self
2129 .semanticHighlighting_punctuation_specialization_enable()
2130 .to_owned(),
2131 macro_bang: self.semanticHighlighting_punctuation_separate_macro_bang().to_owned(),
2132 operator: self.semanticHighlighting_operator_enable().to_owned(),
2133 specialize_operator: self
2134 .semanticHighlighting_operator_specialization_enable()
2135 .to_owned(),
2136 inject_doc_comment: self.semanticHighlighting_doc_comment_inject_enable().to_owned(),
2137 syntactic_name_ref_highlighting: false,
2138 minicore,
2139 }
2140 }
2141
2142 pub fn has_linked_projects(&self) -> bool {
2143 !self.linkedProjects().is_empty()
2144 }
2145
2146 pub fn linked_manifests(&self) -> impl Iterator<Item = &Utf8Path> + '_ {
2147 self.linkedProjects().iter().filter_map(|it| match it {
2148 ManifestOrProjectJson::Manifest(p) => Some(&**p),
2149 ManifestOrProjectJson::DiscoveredProjectJson { .. } => None,
2152 ManifestOrProjectJson::ProjectJson { .. } => None,
2153 })
2154 }
2155
2156 pub fn has_linked_project_jsons(&self) -> bool {
2157 self.linkedProjects()
2158 .iter()
2159 .any(|it| matches!(it, ManifestOrProjectJson::ProjectJson { .. }))
2160 }
2161
2162 pub fn discover_workspace_config(&self) -> Option<&DiscoverWorkspaceConfig> {
2163 self.workspace_discoverConfig().as_ref()
2164 }
2165
2166 fn discovered_projects(&self) -> Vec<ManifestOrProjectJson> {
2167 let exclude_dirs: Vec<_> =
2168 self.files_exclude().iter().map(|p| self.root_path.join(p)).collect();
2169
2170 let mut projects = vec![];
2171 for fs_proj in &self.discovered_projects_from_filesystem {
2172 let manifest_path = fs_proj.manifest_path();
2173 if exclude_dirs.iter().any(|p| manifest_path.starts_with(p)) {
2174 continue;
2175 }
2176
2177 let buf: Utf8PathBuf = manifest_path.to_path_buf().into();
2178 projects.push(ManifestOrProjectJson::Manifest(buf));
2179 }
2180
2181 for dis_proj in &self.discovered_projects_from_command {
2182 projects.push(ManifestOrProjectJson::DiscoveredProjectJson {
2183 data: dis_proj.data.clone(),
2184 buildfile: dis_proj.buildfile.clone(),
2185 });
2186 }
2187
2188 projects
2189 }
2190
2191 pub fn linked_or_discovered_projects(&self) -> Vec<LinkedProject> {
2192 let linked_projects = self.linkedProjects();
2193 let projects = if linked_projects.is_empty() {
2194 self.discovered_projects()
2195 } else {
2196 linked_projects.clone()
2197 };
2198
2199 projects
2200 .iter()
2201 .filter_map(|linked_project| match linked_project {
2202 ManifestOrProjectJson::Manifest(it) => {
2203 let path = self.root_path.join(it);
2204 ProjectManifest::from_manifest_file(path)
2205 .map_err(|e| tracing::error!("failed to load linked project: {}", e))
2206 .ok()
2207 .map(Into::into)
2208 }
2209 ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile } => {
2210 let root_path = buildfile.parent().expect("Unable to get parent of buildfile");
2211
2212 Some(ProjectJson::new(None, root_path, data.clone()).into())
2213 }
2214 ManifestOrProjectJson::ProjectJson(it) => {
2215 Some(ProjectJson::new(None, &self.root_path, it.clone()).into())
2216 }
2217 })
2218 .collect()
2219 }
2220
2221 pub fn prefill_caches(&self) -> bool {
2222 self.cachePriming_enable().to_owned()
2223 }
2224
2225 pub fn publish_diagnostics(&self, source_root: Option<SourceRootId>) -> bool {
2226 self.diagnostics_enable(source_root).to_owned()
2227 }
2228
2229 pub fn diagnostics_map(&self, source_root: Option<SourceRootId>) -> DiagnosticsMapConfig {
2230 DiagnosticsMapConfig {
2231 remap_prefix: self.diagnostics_remapPrefix(source_root).clone(),
2232 warnings_as_info: self.diagnostics_warningsAsInfo(source_root).clone(),
2233 warnings_as_hint: self.diagnostics_warningsAsHint(source_root).clone(),
2234 check_ignore: self.check_ignore(source_root).clone(),
2235 }
2236 }
2237
2238 pub fn extra_args(&self, source_root: Option<SourceRootId>) -> &Vec<String> {
2239 self.cargo_extraArgs(source_root)
2240 }
2241
2242 pub fn extra_env(
2243 &self,
2244 source_root: Option<SourceRootId>,
2245 ) -> &FxHashMap<String, Option<String>> {
2246 self.cargo_extraEnv(source_root)
2247 }
2248
2249 pub fn check_extra_args(&self, source_root: Option<SourceRootId>) -> Vec<String> {
2250 let mut extra_args = self.extra_args(source_root).clone();
2251 extra_args.extend_from_slice(self.check_extraArgs(source_root));
2252 extra_args
2253 }
2254
2255 pub fn check_extra_env(
2256 &self,
2257 source_root: Option<SourceRootId>,
2258 ) -> FxHashMap<String, Option<String>> {
2259 let mut extra_env = self.cargo_extraEnv(source_root).clone();
2260 extra_env.extend(self.check_extraEnv(source_root).clone());
2261 extra_env
2262 }
2263
2264 pub fn lru_parse_query_capacity(&self) -> Option<u16> {
2265 self.lru_capacity().to_owned()
2266 }
2267
2268 pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap<Box<str>, u16>> {
2269 self.lru_query_capacities().is_empty().not().then(|| self.lru_query_capacities())
2270 }
2271
2272 pub fn proc_macro_srv(&self) -> Option<AbsPathBuf> {
2273 let path = self.procMacro_server().clone()?;
2274 Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path)))
2275 }
2276
2277 pub fn dhat_output_file(&self) -> Option<AbsPathBuf> {
2278 let path = self.profiling_memoryProfile().clone()?;
2279 Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path)))
2280 }
2281
2282 pub fn ignored_proc_macros(
2283 &self,
2284 source_root: Option<SourceRootId>,
2285 ) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
2286 self.procMacro_ignored(source_root)
2287 }
2288
2289 pub fn expand_proc_macros(&self) -> bool {
2290 self.procMacro_enable().to_owned()
2291 }
2292
2293 pub fn files(&self) -> FilesConfig {
2294 FilesConfig {
2295 watcher: match self.files_watcher() {
2296 FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
2297 FilesWatcher::Client
2298 }
2299 _ => FilesWatcher::Server,
2300 },
2301 exclude: self.excluded().collect(),
2302 }
2303 }
2304
2305 pub fn excluded(&self) -> impl Iterator<Item = AbsPathBuf> + use<'_> {
2306 self.files_exclude().iter().map(|it| self.root_path.join(it))
2307 }
2308
2309 pub fn notifications(&self) -> NotificationsConfig {
2310 NotificationsConfig {
2311 cargo_toml_not_found: self.notifications_cargoTomlNotFound().to_owned(),
2312 }
2313 }
2314
2315 pub fn cargo_autoreload_config(&self, source_root: Option<SourceRootId>) -> bool {
2316 self.cargo_autoreload(source_root).to_owned()
2317 }
2318
2319 pub fn run_build_scripts(&self, source_root: Option<SourceRootId>) -> bool {
2320 self.cargo_buildScripts_enable(source_root).to_owned() || self.procMacro_enable().to_owned()
2321 }
2322
2323 pub fn cargo(&self, source_root: Option<SourceRootId>) -> CargoConfig {
2324 let rustc_source = self.rustc_source(source_root).as_ref().map(|rustc_src| {
2325 if rustc_src == "discover" {
2326 RustLibSource::Discover
2327 } else {
2328 RustLibSource::Path(self.root_path.join(rustc_src))
2329 }
2330 });
2331 let sysroot = self.cargo_sysroot(source_root).as_ref().map(|sysroot| {
2332 if sysroot == "discover" {
2333 RustLibSource::Discover
2334 } else {
2335 RustLibSource::Path(self.root_path.join(sysroot))
2336 }
2337 });
2338 let sysroot_src =
2339 self.cargo_sysrootSrc(source_root).as_ref().map(|sysroot| self.root_path.join(sysroot));
2340 let extra_includes = self
2341 .vfs_extraIncludes(source_root)
2342 .iter()
2343 .map(String::as_str)
2344 .map(AbsPathBuf::try_from)
2345 .filter_map(Result::ok)
2346 .collect();
2347
2348 CargoConfig {
2349 all_targets: *self.cargo_allTargets(source_root),
2350 features: match &self.cargo_features(source_root) {
2351 CargoFeaturesDef::All => CargoFeatures::All,
2352 CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
2353 features: features.clone(),
2354 no_default_features: self.cargo_noDefaultFeatures(source_root).to_owned(),
2355 },
2356 },
2357 target: self.cargo_target(source_root).clone(),
2358 sysroot,
2359 sysroot_src,
2360 rustc_source,
2361 extra_includes,
2362 cfg_overrides: project_model::CfgOverrides {
2363 global: {
2364 let (enabled, disabled): (Vec<_>, Vec<_>) =
2365 self.cargo_cfgs(source_root).iter().partition_map(|s| {
2366 s.strip_prefix("!").map_or(Either::Left(s), Either::Right)
2367 });
2368 CfgDiff::new(
2369 enabled
2370 .into_iter()
2371 .map(|s| match s.split_once("=") {
2373 Some((key, val)) => CfgAtom::KeyValue {
2374 key: Symbol::intern(key),
2375 value: Symbol::intern(val),
2376 },
2377 None => CfgAtom::Flag(Symbol::intern(s)),
2378 })
2379 .collect(),
2380 disabled
2381 .into_iter()
2382 .map(|s| match s.split_once("=") {
2383 Some((key, val)) => CfgAtom::KeyValue {
2384 key: Symbol::intern(key),
2385 value: Symbol::intern(val),
2386 },
2387 None => CfgAtom::Flag(Symbol::intern(s)),
2388 })
2389 .collect(),
2390 )
2391 },
2392 selective: Default::default(),
2393 },
2394 wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(source_root),
2395 invocation_strategy: match self.cargo_buildScripts_invocationStrategy(source_root) {
2396 InvocationStrategy::Once => project_model::InvocationStrategy::Once,
2397 InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
2398 },
2399 run_build_script_command: self.cargo_buildScripts_overrideCommand(source_root).clone(),
2400 extra_args: self.cargo_extraArgs(source_root).clone(),
2401 extra_env: self.cargo_extraEnv(source_root).clone(),
2402 target_dir_config: self.target_dir_from_config(source_root),
2403 set_test: *self.cfg_setTest(source_root),
2404 no_deps: *self.cargo_noDeps(source_root),
2405 }
2406 }
2407
2408 pub fn cfg_set_test(&self, source_root: Option<SourceRootId>) -> bool {
2409 *self.cfg_setTest(source_root)
2410 }
2411
2412 pub(crate) fn completion_snippets_default() -> FxIndexMap<String, SnippetDef> {
2413 serde_json::from_str(
2414 r#"{
2415 "Ok": {
2416 "postfix": "ok",
2417 "body": "Ok(${receiver})",
2418 "description": "Wrap the expression in a `Result::Ok`",
2419 "scope": "expr"
2420 },
2421 "Box::pin": {
2422 "postfix": "pinbox",
2423 "body": "Box::pin(${receiver})",
2424 "requires": "std::boxed::Box",
2425 "description": "Put the expression into a pinned `Box`",
2426 "scope": "expr"
2427 },
2428 "Arc::new": {
2429 "postfix": "arc",
2430 "body": "Arc::new(${receiver})",
2431 "requires": "std::sync::Arc",
2432 "description": "Put the expression into an `Arc`",
2433 "scope": "expr"
2434 },
2435 "Some": {
2436 "postfix": "some",
2437 "body": "Some(${receiver})",
2438 "description": "Wrap the expression in an `Option::Some`",
2439 "scope": "expr"
2440 },
2441 "Err": {
2442 "postfix": "err",
2443 "body": "Err(${receiver})",
2444 "description": "Wrap the expression in a `Result::Err`",
2445 "scope": "expr"
2446 },
2447 "Rc::new": {
2448 "postfix": "rc",
2449 "body": "Rc::new(${receiver})",
2450 "requires": "std::rc::Rc",
2451 "description": "Put the expression into an `Rc`",
2452 "scope": "expr"
2453 }
2454 }"#,
2455 )
2456 .unwrap()
2457 }
2458
2459 pub fn rustfmt(&self, source_root_id: Option<SourceRootId>) -> RustfmtConfig {
2460 match &self.rustfmt_overrideCommand(source_root_id) {
2461 Some(args) if !args.is_empty() => {
2462 let mut args = args.clone();
2463 let command = args.remove(0);
2464 RustfmtConfig::CustomCommand { command, args }
2465 }
2466 Some(_) | None => RustfmtConfig::Rustfmt {
2467 extra_args: self.rustfmt_extraArgs(source_root_id).clone(),
2468 enable_range_formatting: *self.rustfmt_rangeFormatting_enable(source_root_id),
2469 },
2470 }
2471 }
2472
2473 pub fn flycheck_workspace(&self, source_root: Option<SourceRootId>) -> bool {
2474 *self.check_workspace(source_root)
2475 }
2476
2477 pub(crate) fn cargo_test_options(&self, source_root: Option<SourceRootId>) -> CargoOptions {
2478 CargoOptions {
2479 subcommand: "test".into(),
2481 target_tuples: self.cargo_target(source_root).clone().into_iter().collect(),
2482 all_targets: false,
2483 no_default_features: *self.cargo_noDefaultFeatures(source_root),
2484 all_features: matches!(self.cargo_features(source_root), CargoFeaturesDef::All),
2485 features: match self.cargo_features(source_root).clone() {
2486 CargoFeaturesDef::All => vec![],
2487 CargoFeaturesDef::Selected(it) => it,
2488 },
2489 extra_args: self.extra_args(source_root).clone(),
2490 extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
2491 extra_env: self.extra_env(source_root).clone(),
2492 target_dir_config: self.target_dir_from_config(source_root),
2493 set_test: true,
2494 }
2495 }
2496
2497 pub(crate) fn flycheck(&self, source_root: Option<SourceRootId>) -> FlycheckConfig {
2498 match &self.check_overrideCommand(source_root) {
2499 Some(args) if !args.is_empty() => {
2500 let mut args = args.clone();
2501 let command = args.remove(0);
2502 FlycheckConfig::CustomCommand {
2503 command,
2504 args,
2505 extra_env: self.check_extra_env(source_root),
2506 invocation_strategy: match self.check_invocationStrategy(source_root) {
2507 InvocationStrategy::Once => crate::flycheck::InvocationStrategy::Once,
2508 InvocationStrategy::PerWorkspace => {
2509 crate::flycheck::InvocationStrategy::PerWorkspace
2510 }
2511 },
2512 }
2513 }
2514 Some(_) | None => FlycheckConfig::Automatic {
2515 cargo_options: CargoOptions {
2516 subcommand: self.check_command(source_root).clone(),
2517 target_tuples: self
2518 .check_targets(source_root)
2519 .clone()
2520 .and_then(|targets| match &targets.0[..] {
2521 [] => None,
2522 targets => Some(targets.into()),
2523 })
2524 .unwrap_or_else(|| {
2525 self.cargo_target(source_root).clone().into_iter().collect()
2526 }),
2527 all_targets: self
2528 .check_allTargets(source_root)
2529 .unwrap_or(*self.cargo_allTargets(source_root)),
2530 no_default_features: self
2531 .check_noDefaultFeatures(source_root)
2532 .unwrap_or(*self.cargo_noDefaultFeatures(source_root)),
2533 all_features: matches!(
2534 self.check_features(source_root)
2535 .as_ref()
2536 .unwrap_or(self.cargo_features(source_root)),
2537 CargoFeaturesDef::All
2538 ),
2539 features: match self
2540 .check_features(source_root)
2541 .clone()
2542 .unwrap_or_else(|| self.cargo_features(source_root).clone())
2543 {
2544 CargoFeaturesDef::All => vec![],
2545 CargoFeaturesDef::Selected(it) => it,
2546 },
2547 extra_args: self.check_extra_args(source_root),
2548 extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
2549 extra_env: self.check_extra_env(source_root),
2550 target_dir_config: self.target_dir_from_config(source_root),
2551 set_test: *self.cfg_setTest(source_root),
2552 },
2553 ansi_color_output: self.color_diagnostic_output(),
2554 },
2555 }
2556 }
2557
2558 fn target_dir_from_config(&self, source_root: Option<SourceRootId>) -> TargetDirectoryConfig {
2559 match &self.cargo_targetDir(source_root) {
2560 Some(TargetDirectory::UseSubdirectory(true)) => TargetDirectoryConfig::UseSubdirectory,
2561 Some(TargetDirectory::UseSubdirectory(false)) | None => TargetDirectoryConfig::None,
2562 Some(TargetDirectory::Directory(dir)) => TargetDirectoryConfig::Directory(dir.clone()),
2563 }
2564 }
2565
2566 pub fn check_on_save(&self, source_root: Option<SourceRootId>) -> bool {
2567 *self.checkOnSave(source_root)
2568 }
2569
2570 pub fn script_rebuild_on_save(&self, source_root: Option<SourceRootId>) -> bool {
2571 *self.cargo_buildScripts_rebuildOnSave(source_root)
2572 }
2573
2574 pub fn runnables(&self, source_root: Option<SourceRootId>) -> RunnablesConfig {
2575 RunnablesConfig {
2576 override_cargo: self.runnables_command(source_root).clone(),
2577 cargo_extra_args: self.runnables_extraArgs(source_root).clone(),
2578 extra_test_binary_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
2579 test_command: self.runnables_test_command(source_root).clone(),
2580 test_override_command: self.runnables_test_overrideCommand(source_root).clone(),
2581 bench_command: self.runnables_bench_command(source_root).clone(),
2582 bench_override_command: self.runnables_bench_overrideCommand(source_root).clone(),
2583 doc_test_override_command: self.runnables_doctest_overrideCommand(source_root).clone(),
2584 }
2585 }
2586
2587 pub fn find_all_refs_exclude_imports(&self) -> bool {
2588 *self.references_excludeImports()
2589 }
2590
2591 pub fn find_all_refs_exclude_tests(&self) -> bool {
2592 *self.references_excludeTests()
2593 }
2594
2595 pub fn snippet_cap(&self) -> Option<SnippetCap> {
2596 SnippetCap::new(self.snippet_text_edit())
2599 }
2600
2601 pub fn call_info(&self) -> CallInfoConfig {
2602 CallInfoConfig {
2603 params_only: matches!(self.signatureInfo_detail(), SignatureDetail::Parameters),
2604 docs: *self.signatureInfo_documentation_enable(),
2605 }
2606 }
2607
2608 pub fn lens(&self) -> LensConfig {
2609 LensConfig {
2610 run: *self.lens_enable() && *self.lens_run_enable(),
2611 debug: *self.lens_enable() && *self.lens_debug_enable(),
2612 update_test: *self.lens_enable()
2613 && *self.lens_updateTest_enable()
2614 && *self.lens_run_enable(),
2615 interpret: *self.lens_enable() && *self.lens_run_enable() && *self.interpret_tests(),
2616 implementations: *self.lens_enable() && *self.lens_implementations_enable(),
2617 method_refs: *self.lens_enable() && *self.lens_references_method_enable(),
2618 refs_adt: *self.lens_enable() && *self.lens_references_adt_enable(),
2619 refs_trait: *self.lens_enable() && *self.lens_references_trait_enable(),
2620 enum_variant_refs: *self.lens_enable() && *self.lens_references_enumVariant_enable(),
2621 location: *self.lens_location(),
2622 filter_adjacent_derive_implementations: *self
2623 .gotoImplementations_filterAdjacentDerives(),
2624 }
2625 }
2626
2627 pub fn goto_implementation(&self) -> GotoImplementationConfig {
2628 GotoImplementationConfig {
2629 filter_adjacent_derive_implementations: *self
2630 .gotoImplementations_filterAdjacentDerives(),
2631 }
2632 }
2633
2634 pub fn document_symbol(&self, source_root: Option<SourceRootId>) -> DocumentSymbolConfig {
2635 DocumentSymbolConfig {
2636 search_exclude_locals: *self.document_symbol_search_excludeLocals(source_root),
2637 }
2638 }
2639
2640 pub fn workspace_symbol(&self, source_root: Option<SourceRootId>) -> WorkspaceSymbolConfig {
2641 WorkspaceSymbolConfig {
2642 search_exclude_imports: *self.workspace_symbol_search_excludeImports(source_root),
2643 search_scope: match self.workspace_symbol_search_scope(source_root) {
2644 WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
2645 WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
2646 WorkspaceSymbolSearchScope::WorkspaceAndDependencies
2647 }
2648 },
2649 search_kind: match self.workspace_symbol_search_kind(source_root) {
2650 WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
2651 WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
2652 },
2653 search_limit: *self.workspace_symbol_search_limit(source_root),
2654 }
2655 }
2656
2657 pub fn client_commands(&self) -> ClientCommandsConfig {
2658 let commands = self.commands().map(|it| it.commands).unwrap_or_default();
2659
2660 let get = |name: &str| commands.iter().any(|it| it == name);
2661
2662 ClientCommandsConfig {
2663 run_single: get("rust-analyzer.runSingle"),
2664 debug_single: get("rust-analyzer.debugSingle"),
2665 show_reference: get("rust-analyzer.showReferences"),
2666 goto_location: get("rust-analyzer.gotoLocation"),
2667 trigger_parameter_hints: get("rust-analyzer.triggerParameterHints"),
2668 rename: get("rust-analyzer.rename"),
2669 }
2670 }
2671
2672 pub fn prime_caches_num_threads(&self) -> usize {
2673 match self.cachePriming_numThreads() {
2674 NumThreads::Concrete(0) | NumThreads::Physical => num_cpus::get_physical(),
2675 &NumThreads::Concrete(n) => n,
2676 NumThreads::Logical => num_cpus::get(),
2677 }
2678 }
2679
2680 pub fn proc_macro_num_processes(&self) -> usize {
2681 match self.procMacro_processes() {
2682 NumProcesses::Concrete(0) | NumProcesses::Physical => num_cpus::get_physical(),
2683 &NumProcesses::Concrete(n) => n,
2684 }
2685 }
2686
2687 pub fn main_loop_num_threads(&self) -> usize {
2688 match self.numThreads() {
2689 Some(NumThreads::Concrete(0)) | None | Some(NumThreads::Physical) => {
2690 num_cpus::get_physical()
2691 }
2692 &Some(NumThreads::Concrete(n)) => n,
2693 Some(NumThreads::Logical) => num_cpus::get(),
2694 }
2695 }
2696
2697 pub fn typing_trigger_chars(&self) -> &str {
2698 self.typing_triggerChars().as_deref().unwrap_or_default()
2699 }
2700
2701 pub fn visual_studio_code_version(&self) -> Option<&Version> {
2704 self.client_info
2705 .as_ref()
2706 .filter(|it| it.name.starts_with("Visual Studio Code"))
2707 .and_then(|it| it.version.as_ref())
2708 }
2709
2710 pub fn client_is_neovim(&self) -> bool {
2711 self.client_info.as_ref().map(|it| it.name == "Neovim").unwrap_or_default()
2712 }
2713}
2714macro_rules! create_bool_or_string_serde {
2717 ($ident:ident<$bool:literal, $string:literal>) => {
2718 mod $ident {
2719 pub(super) fn deserialize<'de, D>(d: D) -> Result<(), D::Error>
2720 where
2721 D: serde::Deserializer<'de>,
2722 {
2723 struct V;
2724 impl<'de> serde::de::Visitor<'de> for V {
2725 type Value = ();
2726
2727 fn expecting(
2728 &self,
2729 formatter: &mut std::fmt::Formatter<'_>,
2730 ) -> std::fmt::Result {
2731 formatter.write_str(concat!(
2732 stringify!($bool),
2733 " or \"",
2734 stringify!($string),
2735 "\""
2736 ))
2737 }
2738
2739 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
2740 where
2741 E: serde::de::Error,
2742 {
2743 match v {
2744 $bool => Ok(()),
2745 _ => Err(serde::de::Error::invalid_value(
2746 serde::de::Unexpected::Bool(v),
2747 &self,
2748 )),
2749 }
2750 }
2751
2752 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
2753 where
2754 E: serde::de::Error,
2755 {
2756 match v {
2757 $string => Ok(()),
2758 _ => Err(serde::de::Error::invalid_value(
2759 serde::de::Unexpected::Str(v),
2760 &self,
2761 )),
2762 }
2763 }
2764
2765 fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
2766 where
2767 A: serde::de::EnumAccess<'de>,
2768 {
2769 use serde::de::VariantAccess;
2770 let (variant, va) = a.variant::<&'de str>()?;
2771 va.unit_variant()?;
2772 match variant {
2773 $string => Ok(()),
2774 _ => Err(serde::de::Error::invalid_value(
2775 serde::de::Unexpected::Str(variant),
2776 &self,
2777 )),
2778 }
2779 }
2780 }
2781 d.deserialize_any(V)
2782 }
2783
2784 pub(super) fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
2785 where
2786 S: serde::Serializer,
2787 {
2788 serializer.serialize_str($string)
2789 }
2790 }
2791 };
2792}
2793create_bool_or_string_serde!(true_or_always<true, "always">);
2794create_bool_or_string_serde!(false_or_never<false, "never">);
2795
2796#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
2797#[serde(rename_all = "snake_case")]
2798#[derive(Default)]
2799enum SnippetScopeDef {
2800 #[default]
2801 Expr,
2802 Item,
2803 Type,
2804}
2805
2806#[derive(Serialize, Deserialize, Debug, Clone, Default)]
2807#[serde(default)]
2808pub(crate) struct SnippetDef {
2809 #[serde(with = "single_or_array")]
2810 #[serde(skip_serializing_if = "Vec::is_empty")]
2811 prefix: Vec<String>,
2812
2813 #[serde(with = "single_or_array")]
2814 #[serde(skip_serializing_if = "Vec::is_empty")]
2815 postfix: Vec<String>,
2816
2817 #[serde(with = "single_or_array")]
2818 #[serde(skip_serializing_if = "Vec::is_empty")]
2819 body: Vec<String>,
2820
2821 #[serde(with = "single_or_array")]
2822 #[serde(skip_serializing_if = "Vec::is_empty")]
2823 requires: Vec<String>,
2824
2825 #[serde(skip_serializing_if = "Option::is_none")]
2826 description: Option<String>,
2827
2828 scope: SnippetScopeDef,
2829}
2830
2831mod single_or_array {
2832 use serde::{Deserialize, Serialize};
2833
2834 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
2835 where
2836 D: serde::Deserializer<'de>,
2837 {
2838 struct SingleOrVec;
2839
2840 impl<'de> serde::de::Visitor<'de> for SingleOrVec {
2841 type Value = Vec<String>;
2842
2843 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2844 formatter.write_str("string or array of strings")
2845 }
2846
2847 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
2848 where
2849 E: serde::de::Error,
2850 {
2851 Ok(vec![value.to_owned()])
2852 }
2853
2854 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
2855 where
2856 A: serde::de::SeqAccess<'de>,
2857 {
2858 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
2859 }
2860 }
2861
2862 deserializer.deserialize_any(SingleOrVec)
2863 }
2864
2865 pub(super) fn serialize<S>(vec: &[String], serializer: S) -> Result<S::Ok, S::Error>
2866 where
2867 S: serde::Serializer,
2868 {
2869 match vec {
2870 [single] => serializer.serialize_str(single),
2872 slice => slice.serialize(serializer),
2873 }
2874 }
2875}
2876
2877#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
2878#[serde(untagged)]
2879enum ManifestOrProjectJson {
2880 Manifest(Utf8PathBuf),
2881 ProjectJson(ProjectJsonData),
2882 DiscoveredProjectJson {
2883 data: ProjectJsonData,
2884 #[serde(serialize_with = "serialize_abs_pathbuf")]
2885 #[serde(deserialize_with = "deserialize_abs_pathbuf")]
2886 buildfile: AbsPathBuf,
2887 },
2888}
2889
2890fn deserialize_abs_pathbuf<'de, D>(de: D) -> std::result::Result<AbsPathBuf, D::Error>
2891where
2892 D: serde::de::Deserializer<'de>,
2893{
2894 let path = String::deserialize(de)?;
2895
2896 AbsPathBuf::try_from(path.as_ref())
2897 .map_err(|err| serde::de::Error::custom(format!("invalid path name: {err:?}")))
2898}
2899
2900fn serialize_abs_pathbuf<S>(path: &AbsPathBuf, se: S) -> Result<S::Ok, S::Error>
2901where
2902 S: serde::Serializer,
2903{
2904 let path: &Utf8Path = path.as_ref();
2905 se.serialize_str(path.as_str())
2906}
2907
2908#[derive(Serialize, Deserialize, Debug, Clone)]
2909#[serde(rename_all = "snake_case")]
2910enum ExprFillDefaultDef {
2911 Todo,
2912 Default,
2913 Underscore,
2914}
2915
2916#[derive(Serialize, Deserialize, Debug, Clone)]
2917#[serde(untagged)]
2918#[serde(rename_all = "snake_case")]
2919pub enum AutoImportExclusion {
2920 Path(String),
2921 Verbose { path: String, r#type: AutoImportExclusionType },
2922}
2923
2924#[derive(Serialize, Deserialize, Debug, Clone)]
2925#[serde(rename_all = "snake_case")]
2926pub enum AutoImportExclusionType {
2927 Always,
2928 Methods,
2929}
2930
2931#[derive(Serialize, Deserialize, Debug, Clone)]
2932#[serde(rename_all = "snake_case")]
2933enum ImportGranularityDef {
2934 Preserve,
2935 Item,
2936 Crate,
2937 Module,
2938 One,
2939}
2940
2941#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
2942#[serde(rename_all = "snake_case")]
2943pub(crate) enum CallableCompletionDef {
2944 FillArguments,
2945 AddParentheses,
2946 None,
2947}
2948
2949#[derive(Serialize, Deserialize, Debug, Clone)]
2950#[serde(rename_all = "snake_case")]
2951enum CargoFeaturesDef {
2952 All,
2953 #[serde(untagged)]
2954 Selected(Vec<String>),
2955}
2956
2957#[derive(Serialize, Deserialize, Debug, Clone)]
2958#[serde(rename_all = "snake_case")]
2959pub(crate) enum InvocationStrategy {
2960 Once,
2961 PerWorkspace,
2962}
2963
2964#[derive(Serialize, Deserialize, Debug, Clone)]
2965struct CheckOnSaveTargets(#[serde(with = "single_or_array")] Vec<String>);
2966
2967#[derive(Serialize, Deserialize, Debug, Clone)]
2968#[serde(rename_all = "snake_case")]
2969enum LifetimeElisionDef {
2970 SkipTrivial,
2971 #[serde(with = "true_or_always")]
2972 #[serde(untagged)]
2973 Always,
2974 #[serde(with = "false_or_never")]
2975 #[serde(untagged)]
2976 Never,
2977}
2978
2979#[derive(Serialize, Deserialize, Debug, Clone)]
2980#[serde(rename_all = "snake_case")]
2981enum ClosureReturnTypeHintsDef {
2982 WithBlock,
2983 #[serde(with = "true_or_always")]
2984 #[serde(untagged)]
2985 Always,
2986 #[serde(with = "false_or_never")]
2987 #[serde(untagged)]
2988 Never,
2989}
2990
2991#[derive(Serialize, Deserialize, Debug, Clone)]
2992#[serde(rename_all = "snake_case")]
2993enum ClosureStyle {
2994 ImplFn,
2995 RustAnalyzer,
2996 WithId,
2997 Hide,
2998}
2999
3000#[derive(Serialize, Deserialize, Debug, Clone)]
3001#[serde(rename_all = "snake_case")]
3002enum ReborrowHintsDef {
3003 Mutable,
3004 #[serde(with = "true_or_always")]
3005 #[serde(untagged)]
3006 Always,
3007 #[serde(with = "false_or_never")]
3008 #[serde(untagged)]
3009 Never,
3010}
3011
3012#[derive(Serialize, Deserialize, Debug, Clone)]
3013#[serde(rename_all = "snake_case")]
3014enum AdjustmentHintsDef {
3015 #[serde(alias = "reborrow")]
3016 Borrows,
3017 #[serde(with = "true_or_always")]
3018 #[serde(untagged)]
3019 Always,
3020 #[serde(with = "false_or_never")]
3021 #[serde(untagged)]
3022 Never,
3023}
3024
3025#[derive(Serialize, Deserialize, Debug, Clone)]
3026#[serde(rename_all = "snake_case")]
3027enum DiscriminantHintsDef {
3028 Fieldless,
3029 #[serde(with = "true_or_always")]
3030 #[serde(untagged)]
3031 Always,
3032 #[serde(with = "false_or_never")]
3033 #[serde(untagged)]
3034 Never,
3035}
3036
3037#[derive(Serialize, Deserialize, Debug, Clone)]
3038#[serde(rename_all = "snake_case")]
3039enum AdjustmentHintsModeDef {
3040 Prefix,
3041 Postfix,
3042 PreferPrefix,
3043 PreferPostfix,
3044}
3045
3046#[derive(Serialize, Deserialize, Debug, Clone)]
3047#[serde(rename_all = "snake_case")]
3048enum FilesWatcherDef {
3049 Client,
3050 Notify,
3051 Server,
3052}
3053
3054#[derive(Serialize, Deserialize, Debug, Clone)]
3055#[serde(rename_all = "snake_case")]
3056enum ImportPrefixDef {
3057 Plain,
3058 #[serde(rename = "self")]
3059 #[serde(alias = "by_self")]
3060 BySelf,
3061 #[serde(rename = "crate")]
3062 #[serde(alias = "by_crate")]
3063 ByCrate,
3064}
3065
3066#[derive(Serialize, Deserialize, Debug, Clone)]
3067#[serde(rename_all = "snake_case")]
3068enum WorkspaceSymbolSearchScopeDef {
3069 Workspace,
3070 WorkspaceAndDependencies,
3071}
3072
3073#[derive(Serialize, Deserialize, Debug, Clone)]
3074#[serde(rename_all = "snake_case")]
3075enum SignatureDetail {
3076 Full,
3077 Parameters,
3078}
3079
3080#[derive(Serialize, Deserialize, Debug, Clone)]
3081#[serde(rename_all = "snake_case")]
3082enum WorkspaceSymbolSearchKindDef {
3083 OnlyTypes,
3084 AllSymbols,
3085}
3086
3087#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
3088#[serde(rename_all = "snake_case")]
3089enum MemoryLayoutHoverRenderKindDef {
3090 Decimal,
3091 Hexadecimal,
3092 Both,
3093}
3094
3095#[test]
3096fn untagged_option_hover_render_kind() {
3097 let hex = MemoryLayoutHoverRenderKindDef::Hexadecimal;
3098
3099 let ser = serde_json::to_string(&Some(hex)).unwrap();
3100 assert_eq!(&ser, "\"hexadecimal\"");
3101
3102 let opt: Option<_> = serde_json::from_str("\"hexadecimal\"").unwrap();
3103 assert_eq!(opt, Some(hex));
3104}
3105
3106#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
3107#[serde(rename_all = "snake_case")]
3108#[serde(untagged)]
3109pub enum TargetDirectory {
3110 UseSubdirectory(bool),
3111 Directory(Utf8PathBuf),
3112}
3113
3114#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
3115#[serde(rename_all = "snake_case")]
3116pub enum NumThreads {
3117 Physical,
3118 Logical,
3119 #[serde(untagged)]
3120 Concrete(usize),
3121}
3122
3123#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
3124#[serde(rename_all = "snake_case")]
3125pub enum NumProcesses {
3126 Physical,
3127 #[serde(untagged)]
3128 Concrete(usize),
3129}
3130
3131macro_rules! _default_val {
3132 ($default:expr, $ty:ty) => {{
3133 let default_: $ty = $default;
3134 default_
3135 }};
3136}
3137use _default_val as default_val;
3138
3139macro_rules! _default_str {
3140 ($default:expr, $ty:ty) => {{
3141 let val = default_val!($default, $ty);
3142 serde_json::to_string_pretty(&val).unwrap()
3143 }};
3144}
3145use _default_str as default_str;
3146
3147macro_rules! _impl_for_config_data {
3148 (local, $(
3149 $(#[doc=$doc:literal])*
3150 $vis:vis $field:ident : $ty:ty = $default:expr,
3151 )*
3152 ) => {
3153 impl Config {
3154 $(
3155 $($doc)*
3156 #[allow(non_snake_case)]
3157 $vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
3158 let mut source_root = source_root.as_ref();
3159 while let Some(sr) = source_root {
3160 if let Some((file, _)) = self.ratoml_file.get(&sr) {
3161 match file {
3162 RatomlFile::Workspace(config) => {
3163 if let Some(v) = config.local.$field.as_ref() {
3164 return &v;
3165 }
3166 },
3167 RatomlFile::Crate(config) => {
3168 if let Some(value) = config.$field.as_ref() {
3169 return value;
3170 }
3171 }
3172 }
3173 }
3174 source_root = self.source_root_parent_map.get(&sr);
3175 }
3176
3177 if let Some(v) = self.client_config.0.local.$field.as_ref() {
3178 return &v;
3179 }
3180
3181 if let Some((user_config, _)) = self.user_config.as_ref() {
3182 if let Some(v) = user_config.local.$field.as_ref() {
3183 return &v;
3184 }
3185 }
3186
3187 &self.default_config.local.$field
3188 }
3189 )*
3190 }
3191 };
3192 (workspace, $(
3193 $(#[doc=$doc:literal])*
3194 $vis:vis $field:ident : $ty:ty = $default:expr,
3195 )*
3196 ) => {
3197 impl Config {
3198 $(
3199 $($doc)*
3200 #[allow(non_snake_case)]
3201 $vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
3202 let mut source_root = source_root.as_ref();
3203 while let Some(sr) = source_root {
3204 if let Some((RatomlFile::Workspace(config), _)) = self.ratoml_file.get(&sr) {
3205 if let Some(v) = config.workspace.$field.as_ref() {
3206 return &v;
3207 }
3208 }
3209 source_root = self.source_root_parent_map.get(&sr);
3210 }
3211
3212 if let Some(v) = self.client_config.0.workspace.$field.as_ref() {
3213 return &v;
3214 }
3215
3216 if let Some((user_config, _)) = self.user_config.as_ref() {
3217 if let Some(v) = user_config.workspace.$field.as_ref() {
3218 return &v;
3219 }
3220 }
3221
3222 &self.default_config.workspace.$field
3223 }
3224 )*
3225 }
3226 };
3227 (global, $(
3228 $(#[doc=$doc:literal])*
3229 $vis:vis $field:ident : $ty:ty = $default:expr,
3230 )*
3231 ) => {
3232 impl Config {
3233 $(
3234 $($doc)*
3235 #[allow(non_snake_case)]
3236 $vis fn $field(&self) -> &$ty {
3237 if let Some(v) = self.client_config.0.global.$field.as_ref() {
3238 return &v;
3239 }
3240
3241 if let Some((user_config, _)) = self.user_config.as_ref() {
3242 if let Some(v) = user_config.global.$field.as_ref() {
3243 return &v;
3244 }
3245 }
3246
3247
3248 &self.default_config.global.$field
3249 }
3250 )*
3251 }
3252 };
3253 (client, $(
3254 $(#[doc=$doc:literal])*
3255 $vis:vis $field:ident : $ty:ty = $default:expr,
3256 )*
3257 ) => {
3258 impl Config {
3259 $(
3260 $($doc)*
3261 #[allow(non_snake_case)]
3262 $vis fn $field(&self) -> &$ty {
3263 if let Some(v) = self.client_config.0.client.$field.as_ref() {
3264 return &v;
3265 }
3266
3267 &self.default_config.client.$field
3268 }
3269 )*
3270 }
3271 };
3272}
3273use _impl_for_config_data as impl_for_config_data;
3274
3275macro_rules! _config_data {
3276 ($(#[doc=$dox:literal])* $modname:ident: struct $name:ident <- $input:ident -> {
3278 $(
3279 $(#[doc=$doc:literal])*
3280 $vis:vis $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
3281 )*
3282 }) => {
3283 #[allow(non_snake_case)]
3285 #[derive(Debug, Clone)]
3286 struct $name { $($field: $ty,)* }
3287
3288 impl_for_config_data!{
3289 $modname,
3290 $(
3291 $vis $field : $ty = $default,
3292 )*
3293 }
3294
3295 #[allow(non_snake_case)]
3297 #[derive(Clone, Default)]
3298 struct $input { $(
3299 $field: Option<$ty>,
3300 )* }
3301
3302 impl std::fmt::Debug for $input {
3303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3304 let mut s = f.debug_struct(stringify!($input));
3305 $(
3306 if let Some(val) = self.$field.as_ref() {
3307 s.field(stringify!($field), val);
3308 }
3309 )*
3310 s.finish()
3311 }
3312 }
3313
3314 impl Default for $name {
3315 fn default() -> Self {
3316 $name {$(
3317 $field: default_val!($default, $ty),
3318 )*}
3319 }
3320 }
3321
3322 #[allow(unused, clippy::ptr_arg)]
3323 impl $input {
3324 const FIELDS: &'static [&'static str] = &[$(stringify!($field)),*];
3325
3326 fn from_json(json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> Self {
3327 Self {$(
3328 $field: get_field_json(
3329 json,
3330 error_sink,
3331 stringify!($field),
3332 None$(.or(Some(stringify!($alias))))*,
3333 ),
3334 )*}
3335 }
3336
3337 fn from_toml(toml: &toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>) -> Self {
3338 Self {$(
3339 $field: get_field_toml::<$ty>(
3340 toml,
3341 error_sink,
3342 stringify!($field),
3343 None$(.or(Some(stringify!($alias))))*,
3344 ),
3345 )*}
3346 }
3347
3348 fn schema_fields(sink: &mut Vec<SchemaField>) {
3349 sink.extend_from_slice(&[
3350 $({
3351 let field = stringify!($field);
3352 let ty = stringify!($ty);
3353 let default = default_str!($default, $ty);
3354
3355 (field, ty, &[$($doc),*], default)
3356 },)*
3357 ])
3358 }
3359 }
3360
3361 mod $modname {
3362 #[test]
3363 fn fields_are_sorted() {
3364 super::$input::FIELDS.windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
3365 }
3366 }
3367 };
3368}
3369use _config_data as config_data;
3370
3371#[derive(Default, Debug, Clone)]
3372struct DefaultConfigData {
3373 global: GlobalDefaultConfigData,
3374 workspace: WorkspaceDefaultConfigData,
3375 local: LocalDefaultConfigData,
3376 client: ClientDefaultConfigData,
3377}
3378
3379#[derive(Debug, Clone, Default)]
3383struct FullConfigInput {
3384 global: GlobalConfigInput,
3385 workspace: WorkspaceConfigInput,
3386 local: LocalConfigInput,
3387 client: ClientConfigInput,
3388}
3389
3390impl FullConfigInput {
3391 fn from_json(
3392 mut json: serde_json::Value,
3393 error_sink: &mut Vec<(String, serde_json::Error)>,
3394 ) -> FullConfigInput {
3395 FullConfigInput {
3396 global: GlobalConfigInput::from_json(&mut json, error_sink),
3397 local: LocalConfigInput::from_json(&mut json, error_sink),
3398 client: ClientConfigInput::from_json(&mut json, error_sink),
3399 workspace: WorkspaceConfigInput::from_json(&mut json, error_sink),
3400 }
3401 }
3402
3403 fn schema_fields() -> Vec<SchemaField> {
3404 let mut fields = Vec::new();
3405 GlobalConfigInput::schema_fields(&mut fields);
3406 LocalConfigInput::schema_fields(&mut fields);
3407 ClientConfigInput::schema_fields(&mut fields);
3408 WorkspaceConfigInput::schema_fields(&mut fields);
3409 fields.sort_by_key(|&(x, ..)| x);
3410 fields
3411 .iter()
3412 .tuple_windows()
3413 .for_each(|(a, b)| assert!(a.0 != b.0, "{a:?} duplicate field"));
3414 fields
3415 }
3416
3417 fn json_schema() -> serde_json::Value {
3418 schema(&Self::schema_fields())
3419 }
3420
3421 #[cfg(test)]
3422 fn manual() -> String {
3423 manual(&Self::schema_fields())
3424 }
3425}
3426
3427#[derive(Debug, Clone, Default)]
3431struct GlobalWorkspaceLocalConfigInput {
3432 global: GlobalConfigInput,
3433 local: LocalConfigInput,
3434 workspace: WorkspaceConfigInput,
3435}
3436
3437impl GlobalWorkspaceLocalConfigInput {
3438 const FIELDS: &'static [&'static [&'static str]] =
3439 &[GlobalConfigInput::FIELDS, LocalConfigInput::FIELDS];
3440 fn from_toml(
3441 toml: toml::Table,
3442 error_sink: &mut Vec<(String, toml::de::Error)>,
3443 ) -> GlobalWorkspaceLocalConfigInput {
3444 GlobalWorkspaceLocalConfigInput {
3445 global: GlobalConfigInput::from_toml(&toml, error_sink),
3446 local: LocalConfigInput::from_toml(&toml, error_sink),
3447 workspace: WorkspaceConfigInput::from_toml(&toml, error_sink),
3448 }
3449 }
3450}
3451
3452#[derive(Debug, Clone, Default)]
3456#[allow(dead_code)]
3457struct WorkspaceLocalConfigInput {
3458 workspace: WorkspaceConfigInput,
3459 local: LocalConfigInput,
3460}
3461
3462impl WorkspaceLocalConfigInput {
3463 #[allow(dead_code)]
3464 const FIELDS: &'static [&'static [&'static str]] =
3465 &[WorkspaceConfigInput::FIELDS, LocalConfigInput::FIELDS];
3466 fn from_toml(toml: toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>) -> Self {
3467 Self {
3468 workspace: WorkspaceConfigInput::from_toml(&toml, error_sink),
3469 local: LocalConfigInput::from_toml(&toml, error_sink),
3470 }
3471 }
3472}
3473
3474fn get_field_json<T: DeserializeOwned>(
3475 json: &mut serde_json::Value,
3476 error_sink: &mut Vec<(String, serde_json::Error)>,
3477 field: &'static str,
3478 alias: Option<&'static str>,
3479) -> Option<T> {
3480 alias
3483 .into_iter()
3484 .chain(iter::once(field))
3485 .filter_map(move |field| {
3486 let mut pointer = field.replace('_', "/");
3487 pointer.insert(0, '/');
3488 json.pointer_mut(&pointer)
3489 .map(|it| serde_json::from_value(it.take()).map_err(|e| (e, pointer)))
3490 })
3491 .flat_map(|res| match res {
3492 Ok(it) => Some(it),
3493 Err((e, pointer)) => {
3494 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
3495 error_sink.push((pointer, e));
3496 None
3497 }
3498 })
3499 .next()
3500}
3501
3502fn get_field_toml<T: DeserializeOwned>(
3503 toml: &toml::Table,
3504 error_sink: &mut Vec<(String, toml::de::Error)>,
3505 field: &'static str,
3506 alias: Option<&'static str>,
3507) -> Option<T> {
3508 alias
3511 .into_iter()
3512 .chain(iter::once(field))
3513 .filter_map(move |field| {
3514 let mut pointer = field.replace('_', "/");
3515 pointer.insert(0, '/');
3516 toml_pointer(toml, &pointer)
3517 .map(|it| <_>::deserialize(it.clone()).map_err(|e| (e, pointer)))
3518 })
3519 .find(Result::is_ok)
3520 .and_then(|res| match res {
3521 Ok(it) => Some(it),
3522 Err((e, pointer)) => {
3523 tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
3524 error_sink.push((pointer, e));
3525 None
3526 }
3527 })
3528}
3529
3530fn toml_pointer<'a>(toml: &'a toml::Table, pointer: &str) -> Option<&'a toml::Value> {
3531 fn parse_index(s: &str) -> Option<usize> {
3532 if s.starts_with('+') || (s.starts_with('0') && s.len() != 1) {
3533 return None;
3534 }
3535 s.parse().ok()
3536 }
3537
3538 if pointer.is_empty() {
3539 return None;
3540 }
3541 if !pointer.starts_with('/') {
3542 return None;
3543 }
3544 let mut parts = pointer.split('/').skip(1);
3545 let first = parts.next()?;
3546 let init = toml.get(first)?;
3547 parts.map(|x| x.replace("~1", "/").replace("~0", "~")).try_fold(init, |target, token| {
3548 match target {
3549 toml::Value::Table(table) => table.get(&token),
3550 toml::Value::Array(list) => parse_index(&token).and_then(move |x| list.get(x)),
3551 _ => None,
3552 }
3553 })
3554}
3555
3556type SchemaField = (&'static str, &'static str, &'static [&'static str], String);
3557
3558fn schema(fields: &[SchemaField]) -> serde_json::Value {
3559 let map = fields
3560 .iter()
3561 .map(|(field, ty, doc, default)| {
3562 let name = field.replace('_', ".");
3563 let category = name
3564 .split_once(".")
3565 .map(|(category, _name)| to_title_case(category))
3566 .unwrap_or("rust-analyzer".into());
3567 let name = format!("rust-analyzer.{name}");
3568 let props = field_props(field, ty, doc, default);
3569 serde_json::json!({
3570 "title": category,
3571 "properties": {
3572 name: props
3573 }
3574 })
3575 })
3576 .collect::<Vec<_>>();
3577 map.into()
3578}
3579
3580fn to_title_case(s: &str) -> String {
3589 let mut result = String::with_capacity(s.len());
3590 let mut chars = s.chars();
3591 if let Some(first) = chars.next() {
3592 result.push(first.to_ascii_uppercase());
3593 for c in chars {
3594 if c.is_uppercase() {
3595 result.push(' ');
3596 }
3597 result.push(c);
3598 }
3599 }
3600 result
3601}
3602
3603fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
3604 let doc = doc_comment_to_string(doc);
3605 let doc = doc.trim_end_matches('\n');
3606 assert!(
3607 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
3608 "bad docs for {field}: {doc:?}"
3609 );
3610 let default = default.parse::<serde_json::Value>().unwrap();
3611
3612 let mut map = serde_json::Map::default();
3613 macro_rules! set {
3614 ($($key:literal: $value:tt),*$(,)?) => {{$(
3615 map.insert($key.into(), serde_json::json!($value));
3616 )*}};
3617 }
3618 set!("markdownDescription": doc);
3619 set!("default": default);
3620
3621 match ty {
3622 "bool" => set!("type": "boolean"),
3623 "usize" => set!("type": "integer", "minimum": 0),
3624 "String" => set!("type": "string"),
3625 "Vec<String>" => set! {
3626 "type": "array",
3627 "items": { "type": "string" },
3628 },
3629 "Vec<Utf8PathBuf>" => set! {
3630 "type": "array",
3631 "items": { "type": "string" },
3632 },
3633 "FxHashSet<String>" => set! {
3634 "type": "array",
3635 "items": { "type": "string" },
3636 "uniqueItems": true,
3637 },
3638 "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
3639 "type": "object",
3640 },
3641 "FxIndexMap<String, SnippetDef>" => set! {
3642 "type": "object",
3643 },
3644 "FxHashMap<String, String>" => set! {
3645 "type": "object",
3646 },
3647 "FxHashMap<Box<str>, u16>" => set! {
3648 "type": "object",
3649 },
3650 "FxHashMap<String, Option<String>>" => set! {
3651 "type": "object",
3652 },
3653 "Option<usize>" => set! {
3654 "type": ["null", "integer"],
3655 "minimum": 0,
3656 },
3657 "Option<u16>" => set! {
3658 "type": ["null", "integer"],
3659 "minimum": 0,
3660 "maximum": 65535,
3661 },
3662 "Option<String>" => set! {
3663 "type": ["null", "string"],
3664 },
3665 "Option<Utf8PathBuf>" => set! {
3666 "type": ["null", "string"],
3667 },
3668 "Option<bool>" => set! {
3669 "type": ["null", "boolean"],
3670 },
3671 "Option<Vec<String>>" => set! {
3672 "type": ["null", "array"],
3673 "items": { "type": "string" },
3674 },
3675 "ExprFillDefaultDef" => set! {
3676 "type": "string",
3677 "enum": ["todo", "default"],
3678 "enumDescriptions": [
3679 "Fill missing expressions with the `todo` macro",
3680 "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
3681 ],
3682 },
3683 "ImportGranularityDef" => set! {
3684 "type": "string",
3685 "enum": ["crate", "module", "item", "one", "preserve"],
3686 "enumDescriptions": [
3687 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
3688 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
3689 "Flatten imports so that each has its own use statement.",
3690 "Merge all imports into a single use statement as long as they have the same visibility and attributes.",
3691 "Deprecated - unless `enforceGranularity` is `true`, the style of the current file is preferred over this setting. Behaves like `item`."
3692 ],
3693 },
3694 "ImportPrefixDef" => set! {
3695 "type": "string",
3696 "enum": [
3697 "plain",
3698 "self",
3699 "crate"
3700 ],
3701 "enumDescriptions": [
3702 "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
3703 "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.",
3704 "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
3705 ],
3706 },
3707 "Vec<ManifestOrProjectJson>" => set! {
3708 "type": "array",
3709 "items": { "type": ["string", "object"] },
3710 },
3711 "WorkspaceSymbolSearchScopeDef" => set! {
3712 "type": "string",
3713 "enum": ["workspace", "workspace_and_dependencies"],
3714 "enumDescriptions": [
3715 "Search in current workspace only.",
3716 "Search in current workspace and dependencies."
3717 ],
3718 },
3719 "WorkspaceSymbolSearchKindDef" => set! {
3720 "type": "string",
3721 "enum": ["only_types", "all_symbols"],
3722 "enumDescriptions": [
3723 "Search for types only.",
3724 "Search for all symbols kinds."
3725 ],
3726 },
3727 "LifetimeElisionDef" => set! {
3728 "type": "string",
3729 "enum": [
3730 "always",
3731 "never",
3732 "skip_trivial"
3733 ],
3734 "enumDescriptions": [
3735 "Always show lifetime elision hints.",
3736 "Never show lifetime elision hints.",
3737 "Only show lifetime elision hints if a return type is involved."
3738 ]
3739 },
3740 "ClosureReturnTypeHintsDef" => set! {
3741 "type": "string",
3742 "enum": [
3743 "always",
3744 "never",
3745 "with_block"
3746 ],
3747 "enumDescriptions": [
3748 "Always show type hints for return types of closures.",
3749 "Never show type hints for return types of closures.",
3750 "Only show type hints for return types of closures with blocks."
3751 ]
3752 },
3753 "ReborrowHintsDef" => set! {
3754 "type": "string",
3755 "enum": [
3756 "always",
3757 "never",
3758 "mutable"
3759 ],
3760 "enumDescriptions": [
3761 "Always show reborrow hints.",
3762 "Never show reborrow hints.",
3763 "Only show mutable reborrow hints."
3764 ]
3765 },
3766 "AdjustmentHintsDef" => set! {
3767 "type": "string",
3768 "enum": [
3769 "always",
3770 "never",
3771 "reborrow"
3772 ],
3773 "enumDescriptions": [
3774 "Always show all adjustment hints.",
3775 "Never show adjustment hints.",
3776 "Only show auto borrow and dereference adjustment hints."
3777 ]
3778 },
3779 "DiscriminantHintsDef" => set! {
3780 "type": "string",
3781 "enum": [
3782 "always",
3783 "never",
3784 "fieldless"
3785 ],
3786 "enumDescriptions": [
3787 "Always show all discriminant hints.",
3788 "Never show discriminant hints.",
3789 "Only show discriminant hints on fieldless enum variants."
3790 ]
3791 },
3792 "AdjustmentHintsModeDef" => set! {
3793 "type": "string",
3794 "enum": [
3795 "prefix",
3796 "postfix",
3797 "prefer_prefix",
3798 "prefer_postfix",
3799 ],
3800 "enumDescriptions": [
3801 "Always show adjustment hints as prefix (`*expr`).",
3802 "Always show adjustment hints as postfix (`expr.*`).",
3803 "Show prefix or postfix depending on which uses less parenthesis, preferring prefix.",
3804 "Show prefix or postfix depending on which uses less parenthesis, preferring postfix.",
3805 ]
3806 },
3807 "CargoFeaturesDef" => set! {
3808 "anyOf": [
3809 {
3810 "type": "string",
3811 "enum": [
3812 "all"
3813 ],
3814 "enumDescriptions": [
3815 "Pass `--all-features` to cargo",
3816 ]
3817 },
3818 {
3819 "type": "array",
3820 "items": { "type": "string" }
3821 }
3822 ],
3823 },
3824 "Option<CargoFeaturesDef>" => set! {
3825 "anyOf": [
3826 {
3827 "type": "string",
3828 "enum": [
3829 "all"
3830 ],
3831 "enumDescriptions": [
3832 "Pass `--all-features` to cargo",
3833 ]
3834 },
3835 {
3836 "type": "array",
3837 "items": { "type": "string" }
3838 },
3839 { "type": "null" }
3840 ],
3841 },
3842 "CallableCompletionDef" => set! {
3843 "type": "string",
3844 "enum": [
3845 "fill_arguments",
3846 "add_parentheses",
3847 "none",
3848 ],
3849 "enumDescriptions": [
3850 "Add call parentheses and pre-fill arguments.",
3851 "Add call parentheses.",
3852 "Do no snippet completions for callables."
3853 ]
3854 },
3855 "SignatureDetail" => set! {
3856 "type": "string",
3857 "enum": ["full", "parameters"],
3858 "enumDescriptions": [
3859 "Show the entire signature.",
3860 "Show only the parameters."
3861 ],
3862 },
3863 "FilesWatcherDef" => set! {
3864 "type": "string",
3865 "enum": ["client", "server"],
3866 "enumDescriptions": [
3867 "Use the client (editor) to watch files for changes",
3868 "Use server-side file watching",
3869 ],
3870 },
3871 "AnnotationLocation" => set! {
3872 "type": "string",
3873 "enum": ["above_name", "above_whole_item"],
3874 "enumDescriptions": [
3875 "Render annotations above the name of the item.",
3876 "Render annotations above the whole item, including documentation comments and attributes."
3877 ],
3878 },
3879 "InvocationStrategy" => set! {
3880 "type": "string",
3881 "enum": ["per_workspace", "once"],
3882 "enumDescriptions": [
3883 "The command will be executed for each Rust workspace with the workspace as the working directory.",
3884 "The command will be executed once with the opened project as the working directory."
3885 ],
3886 },
3887 "Option<CheckOnSaveTargets>" => set! {
3888 "anyOf": [
3889 {
3890 "type": "null"
3891 },
3892 {
3893 "type": "string",
3894 },
3895 {
3896 "type": "array",
3897 "items": { "type": "string" }
3898 },
3899 ],
3900 },
3901 "ClosureStyle" => set! {
3902 "type": "string",
3903 "enum": ["impl_fn", "ra_ap_rust_analyzer", "with_id", "hide"],
3904 "enumDescriptions": [
3905 "`impl_fn`: `impl FnMut(i32, u64) -> i8`",
3906 "`ra_ap_rust_analyzer`: `|i32, u64| -> i8`",
3907 "`with_id`: `{closure#14352}`, where that id is the unique number of the closure in r-a internals",
3908 "`hide`: Shows `...` for every closure type",
3909 ],
3910 },
3911 "Option<MemoryLayoutHoverRenderKindDef>" => set! {
3912 "anyOf": [
3913 {
3914 "type": "null"
3915 },
3916 {
3917 "type": "string",
3918 "enum": ["both", "decimal", "hexadecimal", ],
3919 "enumDescriptions": [
3920 "Render as 12 (0xC)",
3921 "Render as 12",
3922 "Render as 0xC"
3923 ],
3924 },
3925 ],
3926 },
3927 "Option<TargetDirectory>" => set! {
3928 "anyOf": [
3929 {
3930 "type": "null"
3931 },
3932 {
3933 "type": "boolean"
3934 },
3935 {
3936 "type": "string"
3937 },
3938 ],
3939 },
3940 "NumThreads" => set! {
3941 "anyOf": [
3942 {
3943 "type": "number",
3944 "minimum": 0,
3945 "maximum": 255
3946 },
3947 {
3948 "type": "string",
3949 "enum": ["physical", "logical", ],
3950 "enumDescriptions": [
3951 "Use the number of physical cores",
3952 "Use the number of logical cores",
3953 ],
3954 },
3955 ],
3956 },
3957 "NumProcesses" => set! {
3958 "anyOf": [
3959 {
3960 "type": "number",
3961 "minimum": 0,
3962 "maximum": 255
3963 },
3964 {
3965 "type": "string",
3966 "enum": ["physical"],
3967 "enumDescriptions": [
3968 "Use the number of physical cores",
3969 ],
3970 },
3971 ],
3972 },
3973 "Option<NumThreads>" => set! {
3974 "anyOf": [
3975 {
3976 "type": "null"
3977 },
3978 {
3979 "type": "number",
3980 "minimum": 0,
3981 "maximum": 255
3982 },
3983 {
3984 "type": "string",
3985 "enum": ["physical", "logical", ],
3986 "enumDescriptions": [
3987 "Use the number of physical cores",
3988 "Use the number of logical cores",
3989 ],
3990 },
3991 ],
3992 },
3993 "Option<DiscoverWorkspaceConfig>" => set! {
3994 "anyOf": [
3995 {
3996 "type": "null"
3997 },
3998 {
3999 "type": "object",
4000 "properties": {
4001 "command": {
4002 "type": "array",
4003 "items": { "type": "string" }
4004 },
4005 "progressLabel": {
4006 "type": "string"
4007 },
4008 "filesToWatch": {
4009 "type": "array",
4010 "items": { "type": "string" }
4011 },
4012 }
4013 }
4014 ]
4015 },
4016 "Option<MaxSubstitutionLength>" => set! {
4017 "anyOf": [
4018 {
4019 "type": "null"
4020 },
4021 {
4022 "type": "string",
4023 "enum": ["hide"]
4024 },
4025 {
4026 "type": "integer"
4027 }
4028 ]
4029 },
4030 "Vec<AutoImportExclusion>" => set! {
4031 "type": "array",
4032 "items": {
4033 "anyOf": [
4034 {
4035 "type": "string",
4036 },
4037 {
4038 "type": "object",
4039 "properties": {
4040 "path": {
4041 "type": "string",
4042 },
4043 "type": {
4044 "type": "string",
4045 "enum": ["always", "methods"],
4046 "enumDescriptions": [
4047 "Do not show this item or its methods (if it is a trait) in auto-import completions.",
4048 "Do not show this traits methods in auto-import completions."
4049 ],
4050 },
4051 }
4052 }
4053 ]
4054 }
4055 },
4056 _ => panic!("missing entry for {ty}: {default} (field {field})"),
4057 }
4058
4059 map.into()
4060}
4061
4062fn validate_toml_table(
4063 known_ptrs: &[&[&'static str]],
4064 toml: &toml::Table,
4065 ptr: &mut String,
4066 error_sink: &mut Vec<(String, toml::de::Error)>,
4067) {
4068 let verify = |ptr: &String| known_ptrs.iter().any(|ptrs| ptrs.contains(&ptr.as_str()));
4069
4070 let l = ptr.len();
4071 for (k, v) in toml {
4072 if !ptr.is_empty() {
4073 ptr.push('_');
4074 }
4075 ptr.push_str(k);
4076
4077 match v {
4078 toml::Value::Table(_) if verify(ptr) => (),
4080 toml::Value::Table(table) => validate_toml_table(known_ptrs, table, ptr, error_sink),
4081 _ if !verify(ptr) => error_sink
4082 .push((ptr.replace('_', "/"), toml::de::Error::custom("unexpected field"))),
4083 _ => (),
4084 }
4085
4086 ptr.truncate(l);
4087 }
4088}
4089
4090#[cfg(test)]
4091fn manual(fields: &[SchemaField]) -> String {
4092 fields.iter().fold(String::new(), |mut acc, (field, _ty, doc, default)| {
4093 let id = field.replace('_', ".");
4094 let name = format!("rust-analyzer.{id}");
4095 let doc = doc_comment_to_string(doc);
4096 if default.contains('\n') {
4097 format_to_acc!(
4098 acc,
4099 "## {name} {{#{id}}}\n\nDefault:\n```json\n{default}\n```\n\n{doc}\n\n"
4100 )
4101 } else {
4102 format_to_acc!(acc, "## {name} {{#{id}}}\n\nDefault: `{default}`\n\n{doc}\n\n")
4103 }
4104 })
4105}
4106
4107fn doc_comment_to_string(doc: &[&str]) -> String {
4108 doc.iter()
4109 .map(|it| it.strip_prefix(' ').unwrap_or(it))
4110 .fold(String::new(), |mut acc, it| format_to_acc!(acc, "{it}\n"))
4111}
4112
4113#[cfg(test)]
4114mod tests {
4115 use std::{borrow::Cow, fs};
4116
4117 use test_utils::{ensure_file_contents, project_root};
4118
4119 use super::*;
4120
4121 #[test]
4122 fn generate_package_json_config() {
4123 let s = Config::json_schema();
4124
4125 let schema = format!("{s:#}");
4126 let mut schema = schema
4127 .trim_start_matches('[')
4128 .trim_end_matches(']')
4129 .replace(" ", " ")
4130 .replace('\n', "\n ")
4131 .trim_start_matches('\n')
4132 .trim_end()
4133 .to_owned();
4134 schema.push_str(",\n");
4135
4136 let url_matches = schema.match_indices("https://");
4140 let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
4141 url_offsets.reverse();
4142 for idx in url_offsets {
4143 let link = &schema[idx..];
4144 if let Some(link_end) = link.find([' ', '['])
4146 && link.chars().nth(link_end) == Some('[')
4147 && let Some(link_text_end) = link.find(']')
4148 {
4149 let link_text = link[link_end..(link_text_end + 1)].to_string();
4150
4151 schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
4152 schema.insert(idx, '(');
4153 schema.insert(idx + link_end + 1, ')');
4154 schema.insert_str(idx, &link_text);
4155 }
4156 }
4157
4158 let package_json_path = project_root().join("editors/code/package.json");
4159 let mut package_json = fs::read_to_string(&package_json_path).unwrap();
4160
4161 let start_marker =
4162 " {\n \"title\": \"$generated-start\"\n },\n";
4163 let end_marker =
4164 " {\n \"title\": \"$generated-end\"\n }\n";
4165
4166 let start = package_json.find(start_marker).unwrap() + start_marker.len();
4167 let end = package_json.find(end_marker).unwrap();
4168
4169 let p = remove_ws(&package_json[start..end]);
4170 let s = remove_ws(&schema);
4171 if !p.contains(&s) {
4172 package_json.replace_range(start..end, &schema);
4173 ensure_file_contents(package_json_path.as_std_path(), &package_json)
4174 }
4175 }
4176
4177 #[test]
4178 fn generate_config_documentation() {
4179 let docs_path = project_root().join("docs/book/src/configuration_generated.md");
4180 let expected = FullConfigInput::manual();
4181 ensure_file_contents(docs_path.as_std_path(), &expected);
4182 }
4183
4184 fn remove_ws(text: &str) -> String {
4185 text.replace(char::is_whitespace, "")
4186 }
4187
4188 #[test]
4189 fn proc_macro_srv_null() {
4190 let mut config =
4191 Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4192
4193 let mut change = ConfigChange::default();
4194 change.change_client_config(serde_json::json!({
4195 "procMacro" : {
4196 "server": null,
4197 }}));
4198
4199 (config, _, _) = config.apply_change(change);
4200 assert_eq!(config.proc_macro_srv(), None);
4201 }
4202
4203 #[test]
4204 fn proc_macro_srv_abs() {
4205 let mut config =
4206 Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4207 let mut change = ConfigChange::default();
4208 change.change_client_config(serde_json::json!({
4209 "procMacro" : {
4210 "server": project_root().to_string(),
4211 }}));
4212
4213 (config, _, _) = config.apply_change(change);
4214 assert_eq!(config.proc_macro_srv(), Some(AbsPathBuf::assert(project_root())));
4215 }
4216
4217 #[test]
4218 fn proc_macro_srv_rel() {
4219 let mut config =
4220 Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4221
4222 let mut change = ConfigChange::default();
4223
4224 change.change_client_config(serde_json::json!({
4225 "procMacro" : {
4226 "server": "./server"
4227 }}));
4228
4229 (config, _, _) = config.apply_change(change);
4230
4231 assert_eq!(
4232 config.proc_macro_srv(),
4233 Some(AbsPathBuf::try_from(project_root().join("./server")).unwrap())
4234 );
4235 }
4236
4237 #[test]
4238 fn cargo_target_dir_unset() {
4239 let mut config =
4240 Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4241
4242 let mut change = ConfigChange::default();
4243
4244 change.change_client_config(serde_json::json!({
4245 "rust" : { "analyzerTargetDir" : null }
4246 }));
4247
4248 (config, _, _) = config.apply_change(change);
4249 assert_eq!(config.cargo_targetDir(None), &None);
4250 assert!(matches!(
4251 config.flycheck(None),
4252 FlycheckConfig::Automatic {
4253 cargo_options: CargoOptions { target_dir_config: TargetDirectoryConfig::None, .. },
4254 ..
4255 }
4256 ));
4257 }
4258
4259 #[test]
4260 fn cargo_target_dir_subdir() {
4261 let mut config =
4262 Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4263
4264 let mut change = ConfigChange::default();
4265 change.change_client_config(serde_json::json!({
4266 "rust" : { "analyzerTargetDir" : true }
4267 }));
4268
4269 (config, _, _) = config.apply_change(change);
4270
4271 assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true)));
4272 let ws_target_dir =
4273 Utf8PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap_or("target".to_owned()));
4274 assert!(matches!(
4275 config.flycheck(None),
4276 FlycheckConfig::Automatic {
4277 cargo_options: CargoOptions { target_dir_config, .. },
4278 ..
4279 } if target_dir_config.target_dir(Some(&ws_target_dir)).map(Cow::into_owned)
4280 == Some(ws_target_dir.join("rust-analyzer"))
4281 ));
4282 }
4283
4284 #[test]
4285 fn cargo_target_dir_relative_dir() {
4286 let mut config =
4287 Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
4288
4289 let mut change = ConfigChange::default();
4290 change.change_client_config(serde_json::json!({
4291 "rust" : { "analyzerTargetDir" : "other_folder" }
4292 }));
4293
4294 (config, _, _) = config.apply_change(change);
4295
4296 assert_eq!(
4297 config.cargo_targetDir(None),
4298 &Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder")))
4299 );
4300 assert!(matches!(
4301 config.flycheck(None),
4302 FlycheckConfig::Automatic {
4303 cargo_options: CargoOptions { target_dir_config, .. },
4304 ..
4305 } if target_dir_config.target_dir(None).map(Cow::into_owned)
4306 == Some(Utf8PathBuf::from("other_folder"))
4307 ));
4308 }
4309}