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