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