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