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