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