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