From b09dbcb520e37606fb8f7fbf5c65040a0566db01 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Sun, 22 Oct 2023 14:52:43 +0200 Subject: [PATCH 01/52] Assume everything is global for now --- crates/rust-analyzer/src/config.rs | 391 +++++++++++++++++------------ 1 file changed, 229 insertions(+), 162 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index c8df4255d96b..997e0b63f480 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -56,8 +56,7 @@ mod patch_old_style; // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep // parsing the old name. config_data! { - struct ConfigData { - /// Whether to insert #[must_use] when generating `as_` methods + struct GlobalConfigData { /// Whether to insert #[must_use] when generating `as_` methods /// for enum variants. assist_emitMustUse: bool = "false", /// Placeholder expression to use for missing expressions in assists. @@ -565,11 +564,26 @@ config_data! { /// Workspace symbol search scope. workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"", } + +} + +config_data! {struct LocalConfigData {}} +config_data! {struct ClientConfigData {}} + +#[derive(Debug, Clone)] +struct ConfigData { + local: LocalConfigData, + global: GlobalConfigData, + client: ClientConfigData, } impl Default for ConfigData { fn default() -> Self { - ConfigData::from_json(serde_json::Value::Null, &mut Vec::new()) + ConfigData { + local: LocalConfigData::from_json(serde_json::Value::Null, &mut Vec::new()), + global: GlobalConfigData::from_json(serde_json::Value::Null, &mut Vec::new()), + client: ClientConfigData::from_json(serde_json::Value::Null, &mut Vec::new()), + } } } @@ -826,10 +840,10 @@ impl Config { .map(AbsPathBuf::assert) .collect(); patch_old_style::patch_json_for_outdated_configs(&mut json); - self.data = ConfigData::from_json(json, &mut errors); - tracing::debug!("deserialized config data: {:#?}", self.data); + self.data.global = GlobalConfigData::from_json(json, &mut errors); + tracing::debug!("deserialized config data: {:#?}", self.data.global); self.snippets.clear(); - for (name, def) in self.data.completion_snippets_custom.iter() { + for (name, def) in self.data.global.completion_snippets_custom.iter() { if def.prefix.is_empty() && def.postfix.is_empty() { continue; } @@ -867,7 +881,7 @@ impl Config { fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) { use serde::de::Error; - if self.data.check_command.is_empty() { + if self.data.global.check_command.is_empty() { error_sink.push(( "/check/command".to_string(), serde_json::Error::custom("expected a non-empty string"), @@ -876,7 +890,7 @@ impl Config { } pub fn json_schema() -> serde_json::Value { - ConfigData::json_schema() + GlobalConfigData::json_schema() } pub fn root_path(&self) -> &AbsPathBuf { @@ -911,13 +925,18 @@ macro_rules! try_or_def { impl Config { pub fn has_linked_projects(&self) -> bool { - !self.data.linkedProjects.is_empty() + !self.data.global.linkedProjects.is_empty() } pub fn linked_projects(&self) -> Vec { - match self.data.linkedProjects.as_slice() { + match self.data.global.linkedProjects.as_slice() { [] => { - let exclude_dirs: Vec<_> = - self.data.files_excludeDirs.iter().map(|p| self.root_path.join(p)).collect(); + let exclude_dirs: Vec<_> = self + .data + .global + .files_excludeDirs + .iter() + .map(|p| self.root_path.join(p)) + .collect(); self.discovered_projects .iter() .filter( @@ -954,7 +973,7 @@ impl Config { .map(ManifestOrProjectJson::ProjectJson) .collect::>(); - self.data.linkedProjects.append(&mut linked_projects); + self.data.global.linkedProjects.append(&mut linked_projects); } pub fn did_save_text_document_dynamic_registration(&self) -> bool { @@ -969,7 +988,7 @@ impl Config { } pub fn prefill_caches(&self) -> bool { - self.data.cachePriming_enable + self.data.global.cachePriming_enable } pub fn location_link(&self) -> bool { @@ -1102,112 +1121,125 @@ impl Config { } pub fn publish_diagnostics(&self) -> bool { - self.data.diagnostics_enable + self.data.global.diagnostics_enable } pub fn diagnostics(&self) -> DiagnosticsConfig { DiagnosticsConfig { - enabled: self.data.diagnostics_enable, + enabled: self.data.global.diagnostics_enable, proc_attr_macros_enabled: self.expand_proc_attr_macros(), - proc_macros_enabled: self.data.procMacro_enable, - disable_experimental: !self.data.diagnostics_experimental_enable, - disabled: self.data.diagnostics_disabled.clone(), - expr_fill_default: match self.data.assist_expressionFillDefault { + proc_macros_enabled: self.data.global.procMacro_enable, + disable_experimental: !self.data.global.diagnostics_experimental_enable, + disabled: self.data.global.diagnostics_disabled.clone(), + expr_fill_default: match self.data.global.assist_expressionFillDefault { ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, }, insert_use: self.insert_use_config(), - prefer_no_std: self.data.imports_prefer_no_std, + prefer_no_std: self.data.global.imports_prefer_no_std, } } pub fn diagnostics_map(&self) -> DiagnosticsMapConfig { DiagnosticsMapConfig { - remap_prefix: self.data.diagnostics_remapPrefix.clone(), - warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(), - warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(), - check_ignore: self.data.check_ignore.clone(), + remap_prefix: self.data.global.diagnostics_remapPrefix.clone(), + warnings_as_info: self.data.global.diagnostics_warningsAsInfo.clone(), + warnings_as_hint: self.data.global.diagnostics_warningsAsHint.clone(), + check_ignore: self.data.global.check_ignore.clone(), } } pub fn extra_args(&self) -> &Vec { - &self.data.cargo_extraArgs + &self.data.global.cargo_extraArgs } pub fn extra_env(&self) -> &FxHashMap { - &self.data.cargo_extraEnv + &self.data.global.cargo_extraEnv } pub fn check_extra_args(&self) -> Vec { let mut extra_args = self.extra_args().clone(); - extra_args.extend_from_slice(&self.data.check_extraArgs); + extra_args.extend_from_slice(&self.data.global.check_extraArgs); extra_args } pub fn check_extra_env(&self) -> FxHashMap { - let mut extra_env = self.data.cargo_extraEnv.clone(); - extra_env.extend(self.data.check_extraEnv.clone()); + let mut extra_env = self.data.global.cargo_extraEnv.clone(); + extra_env.extend(self.data.global.check_extraEnv.clone()); extra_env } pub fn lru_parse_query_capacity(&self) -> Option { - self.data.lru_capacity + self.data.global.lru_capacity } pub fn lru_query_capacities(&self) -> Option<&FxHashMap, usize>> { - self.data.lru_query_capacities.is_empty().not().then(|| &self.data.lru_query_capacities) + self.data + .global + .lru_query_capacities + .is_empty() + .not() + .then(|| &self.data.global.lru_query_capacities) } pub fn proc_macro_srv(&self) -> Option { - let path = self.data.procMacro_server.clone()?; + let path = self.data.global.procMacro_server.clone()?; Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(&path))) } pub fn dummy_replacements(&self) -> &FxHashMap, Box<[Box]>> { - &self.data.procMacro_ignored + &self.data.global.procMacro_ignored } pub fn expand_proc_macros(&self) -> bool { - self.data.procMacro_enable + self.data.global.procMacro_enable } pub fn expand_proc_attr_macros(&self) -> bool { - self.data.procMacro_enable && self.data.procMacro_attributes_enable + self.data.global.procMacro_enable && self.data.global.procMacro_attributes_enable } pub fn files(&self) -> FilesConfig { FilesConfig { - watcher: match self.data.files_watcher { + watcher: match self.data.global.files_watcher { FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => { FilesWatcher::Client } _ => FilesWatcher::Server, }, - exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(), + exclude: self + .data + .global + .files_excludeDirs + .iter() + .map(|it| self.root_path.join(it)) + .collect(), } } pub fn notifications(&self) -> NotificationsConfig { - NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound } + NotificationsConfig { + cargo_toml_not_found: self.data.global.notifications_cargoTomlNotFound, + } } pub fn cargo_autoreload(&self) -> bool { - self.data.cargo_autoreload + self.data.global.cargo_autoreload } pub fn run_build_scripts(&self) -> bool { - self.data.cargo_buildScripts_enable || self.data.procMacro_enable + self.data.global.cargo_buildScripts_enable || self.data.global.procMacro_enable } pub fn cargo(&self) -> CargoConfig { - let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| { + let rustc_source = self.data.global.rustc_source.as_ref().map(|rustc_src| { if rustc_src == "discover" { RustLibSource::Discover } else { RustLibSource::Path(self.root_path.join(rustc_src)) } }); - let sysroot = self.data.cargo_sysroot.as_ref().map(|sysroot| { + let sysroot = self.data.global.cargo_sysroot.as_ref().map(|sysroot| { if sysroot == "discover" { RustLibSource::Discover } else { @@ -1215,23 +1247,24 @@ impl Config { } }); let sysroot_src = - self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot)); + self.data.global.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot)); CargoConfig { - features: match &self.data.cargo_features { + features: match &self.data.global.cargo_features { CargoFeaturesDef::All => CargoFeatures::All, CargoFeaturesDef::Selected(features) => CargoFeatures::Selected { features: features.clone(), - no_default_features: self.data.cargo_noDefaultFeatures, + no_default_features: self.data.global.cargo_noDefaultFeatures, }, }, - target: self.data.cargo_target.clone(), + target: self.data.global.cargo_target.clone(), sysroot, sysroot_src, rustc_source, cfg_overrides: project_model::CfgOverrides { global: CfgDiff::new( self.data + .global .cargo_cfgs .iter() .map(|(key, val)| { @@ -1247,6 +1280,7 @@ impl Config { .unwrap(), selective: self .data + .global .cargo_unsetTest .iter() .map(|it| { @@ -1257,40 +1291,40 @@ impl Config { }) .collect(), }, - wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper, - invocation_strategy: match self.data.cargo_buildScripts_invocationStrategy { + wrap_rustc_in_build_scripts: self.data.global.cargo_buildScripts_useRustcWrapper, + invocation_strategy: match self.data.global.cargo_buildScripts_invocationStrategy { InvocationStrategy::Once => project_model::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace, }, - invocation_location: match self.data.cargo_buildScripts_invocationLocation { + invocation_location: match self.data.global.cargo_buildScripts_invocationLocation { InvocationLocation::Root => { project_model::InvocationLocation::Root(self.root_path.clone()) } InvocationLocation::Workspace => project_model::InvocationLocation::Workspace, }, - run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(), - extra_args: self.data.cargo_extraArgs.clone(), - extra_env: self.data.cargo_extraEnv.clone(), + run_build_script_command: self.data.global.cargo_buildScripts_overrideCommand.clone(), + extra_args: self.data.global.cargo_extraArgs.clone(), + extra_env: self.data.global.cargo_extraEnv.clone(), target_dir: self.target_dir_from_config(), } } pub fn rustfmt(&self) -> RustfmtConfig { - match &self.data.rustfmt_overrideCommand { + match &self.data.global.rustfmt_overrideCommand { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); RustfmtConfig::CustomCommand { command, args } } Some(_) | None => RustfmtConfig::Rustfmt { - extra_args: self.data.rustfmt_extraArgs.clone(), - enable_range_formatting: self.data.rustfmt_rangeFormatting_enable, + extra_args: self.data.global.rustfmt_extraArgs.clone(), + enable_range_formatting: self.data.global.rustfmt_rangeFormatting_enable, }, } } pub fn flycheck(&self) -> FlycheckConfig { - match &self.data.check_overrideCommand { + match &self.data.global.check_overrideCommand { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); @@ -1298,13 +1332,13 @@ impl Config { command, args, extra_env: self.check_extra_env(), - invocation_strategy: match self.data.check_invocationStrategy { + invocation_strategy: match self.data.global.check_invocationStrategy { InvocationStrategy::Once => flycheck::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => { flycheck::InvocationStrategy::PerWorkspace } }, - invocation_location: match self.data.check_invocationLocation { + invocation_location: match self.data.global.check_invocationLocation { InvocationLocation::Root => { flycheck::InvocationLocation::Root(self.root_path.clone()) } @@ -1313,30 +1347,37 @@ impl Config { } } Some(_) | None => FlycheckConfig::CargoCommand { - command: self.data.check_command.clone(), + command: self.data.global.check_command.clone(), target_triples: self .data + .global .check_targets .clone() .and_then(|targets| match &targets.0[..] { [] => None, targets => Some(targets.into()), }) - .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()), - all_targets: self.data.check_allTargets, + .unwrap_or_else(|| self.data.global.cargo_target.clone().into_iter().collect()), + all_targets: self.data.global.check_allTargets, no_default_features: self .data + .global .check_noDefaultFeatures - .unwrap_or(self.data.cargo_noDefaultFeatures), + .unwrap_or(self.data.global.cargo_noDefaultFeatures), all_features: matches!( - self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features), + self.data + .global + .check_features + .as_ref() + .unwrap_or(&self.data.global.cargo_features), CargoFeaturesDef::All ), features: match self .data + .global .check_features .clone() - .unwrap_or_else(|| self.data.cargo_features.clone()) + .unwrap_or_else(|| self.data.global.cargo_features.clone()) { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, @@ -1350,7 +1391,7 @@ impl Config { } fn target_dir_from_config(&self) -> Option { - self.data.rust_analyzerTargetDir.as_ref().and_then(|target_dir| match target_dir { + self.data.global.rust_analyzerTargetDir.as_ref().and_then(|target_dir| match target_dir { TargetDirectory::UseSubdirectory(yes) if *yes => { Some(PathBuf::from("target/rust-analyzer")) } @@ -1360,13 +1401,13 @@ impl Config { } pub fn check_on_save(&self) -> bool { - self.data.checkOnSave + self.data.global.checkOnSave } pub fn runnables(&self) -> RunnablesConfig { RunnablesConfig { - override_cargo: self.data.runnables_command.clone(), - cargo_extra_args: self.data.runnables_extraArgs.clone(), + override_cargo: self.data.global.runnables_command.clone(), + cargo_extra_args: self.data.global.runnables_extraArgs.clone(), } } @@ -1384,39 +1425,48 @@ impl Config { .collect::>(); InlayHintsConfig { - render_colons: self.data.inlayHints_renderColons, - type_hints: self.data.inlayHints_typeHints_enable, - parameter_hints: self.data.inlayHints_parameterHints_enable, - chaining_hints: self.data.inlayHints_chainingHints_enable, - discriminant_hints: match self.data.inlayHints_discriminantHints_enable { + render_colons: self.data.global.inlayHints_renderColons, + type_hints: self.data.global.inlayHints_typeHints_enable, + parameter_hints: self.data.global.inlayHints_parameterHints_enable, + chaining_hints: self.data.global.inlayHints_chainingHints_enable, + discriminant_hints: match self.data.global.inlayHints_discriminantHints_enable { DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless, }, - closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable { + closure_return_type_hints: match self + .data + .global + .inlayHints_closureReturnTypeHints_enable + { ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always, ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never, ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock, }, - lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable { + lifetime_elision_hints: match self.data.global.inlayHints_lifetimeElisionHints_enable { LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always, LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never, LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial, }, - hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor, + hide_named_constructor_hints: self + .data + .global + .inlayHints_typeHints_hideNamedConstructor, hide_closure_initialization_hints: self .data + .global .inlayHints_typeHints_hideClosureInitialization, - closure_style: match self.data.inlayHints_closureStyle { + closure_style: match self.data.global.inlayHints_closureStyle { ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn, ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation, ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId, ClosureStyle::Hide => hir::ClosureStyle::Hide, }, - closure_capture_hints: self.data.inlayHints_closureCaptureHints_enable, - adjustment_hints: match self.data.inlayHints_expressionAdjustmentHints_enable { + closure_capture_hints: self.data.global.inlayHints_closureCaptureHints_enable, + adjustment_hints: match self.data.global.inlayHints_expressionAdjustmentHints_enable { AdjustmentHintsDef::Always => ide::AdjustmentHints::Always, - AdjustmentHintsDef::Never => match self.data.inlayHints_reborrowHints_enable { + AdjustmentHintsDef::Never => match self.data.global.inlayHints_reborrowHints_enable + { ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { ide::AdjustmentHints::ReborrowOnly } @@ -1424,7 +1474,8 @@ impl Config { }, AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly, }, - adjustment_hints_mode: match self.data.inlayHints_expressionAdjustmentHints_mode { + adjustment_hints_mode: match self.data.global.inlayHints_expressionAdjustmentHints_mode + { AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix, AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix, AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix, @@ -1432,14 +1483,16 @@ impl Config { }, adjustment_hints_hide_outside_unsafe: self .data + .global .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe, - binding_mode_hints: self.data.inlayHints_bindingModeHints_enable, + binding_mode_hints: self.data.global.inlayHints_bindingModeHints_enable, param_names_for_lifetime_elision_hints: self .data + .global .inlayHints_lifetimeElisionHints_useParameterNames, - max_length: self.data.inlayHints_maxLength, - closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable { - Some(self.data.inlayHints_closingBraceHints_minLines) + max_length: self.data.global.inlayHints_maxLength, + closing_brace_hints_min_lines: if self.data.global.inlayHints_closingBraceHints_enable { + Some(self.data.global.inlayHints_closingBraceHints_minLines) } else { None }, @@ -1455,38 +1508,38 @@ impl Config { fn insert_use_config(&self) -> InsertUseConfig { InsertUseConfig { - granularity: match self.data.imports_granularity_group { + granularity: match self.data.global.imports_granularity_group { ImportGranularityDef::Preserve => ImportGranularity::Preserve, ImportGranularityDef::Item => ImportGranularity::Item, ImportGranularityDef::Crate => ImportGranularity::Crate, ImportGranularityDef::Module => ImportGranularity::Module, }, - enforce_granularity: self.data.imports_granularity_enforce, - prefix_kind: match self.data.imports_prefix { + enforce_granularity: self.data.global.imports_granularity_enforce, + prefix_kind: match self.data.global.imports_prefix { ImportPrefixDef::Plain => PrefixKind::Plain, ImportPrefixDef::ByCrate => PrefixKind::ByCrate, ImportPrefixDef::BySelf => PrefixKind::BySelf, }, - group: self.data.imports_group_enable, - skip_glob_imports: !self.data.imports_merge_glob, + group: self.data.global.imports_group_enable, + skip_glob_imports: !self.data.global.imports_merge_glob, } } pub fn completion(&self) -> CompletionConfig { CompletionConfig { - enable_postfix_completions: self.data.completion_postfix_enable, - enable_imports_on_the_fly: self.data.completion_autoimport_enable + enable_postfix_completions: self.data.global.completion_postfix_enable, + enable_imports_on_the_fly: self.data.global.completion_autoimport_enable && completion_item_edit_resolve(&self.caps), - enable_self_on_the_fly: self.data.completion_autoself_enable, - enable_private_editable: self.data.completion_privateEditable_enable, - full_function_signatures: self.data.completion_fullFunctionSignatures_enable, - callable: match self.data.completion_callable_snippets { + enable_self_on_the_fly: self.data.global.completion_autoself_enable, + enable_private_editable: self.data.global.completion_privateEditable_enable, + full_function_signatures: self.data.global.completion_fullFunctionSignatures_enable, + callable: match self.data.global.completion_callable_snippets { CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), CallableCompletionDef::None => None, }, insert_use: self.insert_use_config(), - prefer_no_std: self.data.imports_prefer_no_std, + prefer_no_std: self.data.global.imports_prefer_no_std, snippet_cap: SnippetCap::new(try_or_def!( self.caps .text_document @@ -1498,12 +1551,12 @@ impl Config { .snippet_support? )), snippets: self.snippets.clone(), - limit: self.data.completion_limit, + limit: self.data.global.completion_limit, } } pub fn find_all_refs_exclude_imports(&self) -> bool { - self.data.references_excludeImports + self.data.global.references_excludeImports } pub fn snippet_cap(&self) -> bool { @@ -1515,70 +1568,80 @@ impl Config { snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), allowed: None, insert_use: self.insert_use_config(), - prefer_no_std: self.data.imports_prefer_no_std, - assist_emit_must_use: self.data.assist_emitMustUse, + prefer_no_std: self.data.global.imports_prefer_no_std, + assist_emit_must_use: self.data.global.assist_emitMustUse, } } pub fn join_lines(&self) -> JoinLinesConfig { JoinLinesConfig { - join_else_if: self.data.joinLines_joinElseIf, - remove_trailing_comma: self.data.joinLines_removeTrailingComma, - unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock, - join_assignments: self.data.joinLines_joinAssignments, + join_else_if: self.data.global.joinLines_joinElseIf, + remove_trailing_comma: self.data.global.joinLines_removeTrailingComma, + unwrap_trivial_blocks: self.data.global.joinLines_unwrapTrivialBlock, + join_assignments: self.data.global.joinLines_joinAssignments, } } pub fn call_info(&self) -> CallInfoConfig { CallInfoConfig { - params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters), - docs: self.data.signatureInfo_documentation_enable, + params_only: matches!( + self.data.global.signatureInfo_detail, + SignatureDetail::Parameters + ), + docs: self.data.global.signatureInfo_documentation_enable, } } pub fn lens(&self) -> LensConfig { LensConfig { - run: self.data.lens_enable && self.data.lens_run_enable, - debug: self.data.lens_enable && self.data.lens_debug_enable, - interpret: self.data.lens_enable - && self.data.lens_run_enable - && self.data.interpret_tests, - implementations: self.data.lens_enable && self.data.lens_implementations_enable, - method_refs: self.data.lens_enable && self.data.lens_references_method_enable, - refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable, - refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable, - enum_variant_refs: self.data.lens_enable - && self.data.lens_references_enumVariant_enable, - location: self.data.lens_location, + run: self.data.global.lens_enable && self.data.global.lens_run_enable, + debug: self.data.global.lens_enable && self.data.global.lens_debug_enable, + interpret: self.data.global.lens_enable + && self.data.global.lens_run_enable + && self.data.global.interpret_tests, + implementations: self.data.global.lens_enable + && self.data.global.lens_implementations_enable, + method_refs: self.data.global.lens_enable + && self.data.global.lens_references_method_enable, + refs_adt: self.data.global.lens_enable && self.data.global.lens_references_adt_enable, + refs_trait: self.data.global.lens_enable + && self.data.global.lens_references_trait_enable, + enum_variant_refs: self.data.global.lens_enable + && self.data.global.lens_references_enumVariant_enable, + location: self.data.global.lens_location, } } pub fn hover_actions(&self) -> HoverActionsConfig { - let enable = self.experimental("hoverActions") && self.data.hover_actions_enable; + let enable = self.experimental("hoverActions") && self.data.global.hover_actions_enable; HoverActionsConfig { - implementations: enable && self.data.hover_actions_implementations_enable, - references: enable && self.data.hover_actions_references_enable, - run: enable && self.data.hover_actions_run_enable, - debug: enable && self.data.hover_actions_debug_enable, - goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable, + implementations: enable && self.data.global.hover_actions_implementations_enable, + references: enable && self.data.global.hover_actions_references_enable, + run: enable && self.data.global.hover_actions_run_enable, + debug: enable && self.data.global.hover_actions_debug_enable, + goto_type_def: enable && self.data.global.hover_actions_gotoTypeDef_enable, } } pub fn highlighting_non_standard_tokens(&self) -> bool { - self.data.semanticHighlighting_nonStandardTokens + self.data.global.semanticHighlighting_nonStandardTokens } pub fn highlighting_config(&self) -> HighlightConfig { HighlightConfig { - strings: self.data.semanticHighlighting_strings_enable, - punctuation: self.data.semanticHighlighting_punctuation_enable, + strings: self.data.global.semanticHighlighting_strings_enable, + punctuation: self.data.global.semanticHighlighting_punctuation_enable, specialize_punctuation: self .data + .global .semanticHighlighting_punctuation_specialization_enable, - macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang, - operator: self.data.semanticHighlighting_operator_enable, - specialize_operator: self.data.semanticHighlighting_operator_specialization_enable, - inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable, + macro_bang: self.data.global.semanticHighlighting_punctuation_separate_macro_bang, + operator: self.data.global.semanticHighlighting_operator_enable, + specialize_operator: self + .data + .global + .semanticHighlighting_operator_specialization_enable, + inject_doc_comment: self.data.global.semanticHighlighting_doc_comment_inject_enable, syntactic_name_ref_highlighting: false, } } @@ -1590,14 +1653,16 @@ impl Config { MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal, }; HoverConfig { - links_in_hover: self.data.hover_links_enable, - memory_layout: self.data.hover_memoryLayout_enable.then_some(MemoryLayoutHoverConfig { - size: self.data.hover_memoryLayout_size.map(mem_kind), - offset: self.data.hover_memoryLayout_offset.map(mem_kind), - alignment: self.data.hover_memoryLayout_alignment.map(mem_kind), - niches: self.data.hover_memoryLayout_niches.unwrap_or_default(), - }), - documentation: self.data.hover_documentation_enable, + links_in_hover: self.data.global.hover_links_enable, + memory_layout: self.data.global.hover_memoryLayout_enable.then_some( + MemoryLayoutHoverConfig { + size: self.data.global.hover_memoryLayout_size.map(mem_kind), + offset: self.data.global.hover_memoryLayout_offset.map(mem_kind), + alignment: self.data.global.hover_memoryLayout_alignment.map(mem_kind), + niches: self.data.global.hover_memoryLayout_niches.unwrap_or_default(), + }, + ), + documentation: self.data.global.hover_documentation_enable, format: { let is_markdown = try_or_def!(self .caps @@ -1615,23 +1680,23 @@ impl Config { HoverDocFormat::PlainText } }, - keywords: self.data.hover_documentation_keywords_enable, + keywords: self.data.global.hover_documentation_keywords_enable, } } pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig { WorkspaceSymbolConfig { - search_scope: match self.data.workspace_symbol_search_scope { + search_scope: match self.data.global.workspace_symbol_search_scope { WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace, WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => { WorkspaceSymbolSearchScope::WorkspaceAndDependencies } }, - search_kind: match self.data.workspace_symbol_search_kind { + search_kind: match self.data.global.workspace_symbol_search_kind { WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes, WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols, }, - search_limit: self.data.workspace_symbol_search_limit, + search_limit: self.data.global.workspace_symbol_search_limit, } } @@ -1665,7 +1730,7 @@ impl Config { try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null); let commands: Option = serde_json::from_value(commands.clone()).ok(); - let force = commands.is_none() && self.data.lens_forceCustomCommands; + let force = commands.is_none() && self.data.global.lens_forceCustomCommands; let commands = commands.map(|it| it.commands).unwrap_or_default(); let get = |name: &str| commands.iter().any(|it| it == name) || force; @@ -1681,27 +1746,27 @@ impl Config { pub fn highlight_related(&self) -> HighlightRelatedConfig { HighlightRelatedConfig { - references: self.data.highlightRelated_references_enable, - break_points: self.data.highlightRelated_breakPoints_enable, - exit_points: self.data.highlightRelated_exitPoints_enable, - yield_points: self.data.highlightRelated_yieldPoints_enable, - closure_captures: self.data.highlightRelated_closureCaptures_enable, + references: self.data.global.highlightRelated_references_enable, + break_points: self.data.global.highlightRelated_breakPoints_enable, + exit_points: self.data.global.highlightRelated_exitPoints_enable, + yield_points: self.data.global.highlightRelated_yieldPoints_enable, + closure_captures: self.data.global.highlightRelated_closureCaptures_enable, } } pub fn prime_caches_num_threads(&self) -> u8 { - match self.data.cachePriming_numThreads { + match self.data.global.cachePriming_numThreads { 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX), n => n, } } pub fn main_loop_num_threads(&self) -> usize { - self.data.numThreads.unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1)) + self.data.global.numThreads.unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1)) } pub fn typing_autoclose_angle(&self) -> bool { - self.data.typing_autoClosingAngleBrackets_enable + self.data.global.typing_autoClosingAngleBrackets_enable } // FIXME: VSCode seems to work wrong sometimes, see https://github.com/microsoft/vscode/issues/193124 @@ -2112,10 +2177,11 @@ macro_rules! _config_data { } } - #[test] - fn fields_are_sorted() { - [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); - } + // TODO + // #[test] + // fn fields_are_sorted() { + // [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); + // } }; } use _config_data as config_data; @@ -2608,7 +2674,8 @@ mod tests { #[test] fn generate_config_documentation() { let docs_path = project_root().join("docs/user/generated_config.adoc"); - let expected = ConfigData::manual(); + // TODO Merge configs , lexicographically order and spit them out. + let expected = GlobalConfigData::manual(); ensure_file_contents(&docs_path, &expected); } @@ -2680,7 +2747,7 @@ mod tests { "rust": { "analyzerTargetDir": null } })) .unwrap(); - assert_eq!(config.data.rust_analyzerTargetDir, None); + assert_eq!(config.data.global.rust_analyzerTargetDir, None); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == None) ); @@ -2700,7 +2767,7 @@ mod tests { })) .unwrap(); assert_eq!( - config.data.rust_analyzerTargetDir, + config.data.global.rust_analyzerTargetDir, Some(TargetDirectory::UseSubdirectory(true)) ); assert!( @@ -2722,7 +2789,7 @@ mod tests { })) .unwrap(); assert_eq!( - config.data.rust_analyzerTargetDir, + config.data.global.rust_analyzerTargetDir, Some(TargetDirectory::Directory(PathBuf::from("other_folder"))) ); assert!( From 451a406cbf7e5c7379738835151751a813dd4949 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 01:39:13 +0200 Subject: [PATCH 02/52] Declare imports_* configs as local --- crates/rust-analyzer/Cargo.toml | 1 + crates/rust-analyzer/src/config.rs | 636 +++++++++++-------- crates/rust-analyzer/src/diagnostics.rs | 2 +- crates/rust-analyzer/src/handlers/request.rs | 19 +- crates/rust-analyzer/src/lsp/to_proto.rs | 4 +- 5 files changed, 382 insertions(+), 280 deletions(-) diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index ee5df984b68e..87e85f7e5950 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -72,6 +72,7 @@ parser.workspace = true toolchain.workspace = true vfs-notify.workspace = true vfs.workspace = true +la-arena.workspace = true [target.'cfg(windows)'.dependencies] winapi = "0.3.9" diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 997e0b63f480..9f9f2a2fde48 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -22,13 +22,14 @@ use ide_db::{ SnippetCap, }; use itertools::Itertools; +use la_arena::Arena; use lsp_types::{ClientCapabilities, MarkupKind}; use project_model::{ CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource, }; use rustc_hash::{FxHashMap, FxHashSet}; use serde::{de::DeserializeOwned, Deserialize}; -use vfs::{AbsPath, AbsPathBuf}; +use vfs::{AbsPath, AbsPathBuf, FileId}; use crate::{ caps::completion_item_edit_resolve, @@ -56,7 +57,8 @@ mod patch_old_style; // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep // parsing the old name. config_data! { - struct GlobalConfigData { /// Whether to insert #[must_use] when generating `as_` methods + struct GlobalConfigData { + /// Whether to insert #[must_use] when generating `as_` methods /// for enum variants. assist_emitMustUse: bool = "false", /// Placeholder expression to use for missing expressions in assists. @@ -342,19 +344,6 @@ config_data! { /// How to render the size information in a memory layout hover. hover_memoryLayout_size: Option = "\"both\"", - /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file. - imports_granularity_enforce: bool = "false", - /// How imports should be grouped into use statements. - imports_granularity_group: ImportGranularityDef = "\"crate\"", - /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines. - imports_group_enable: bool = "true", - /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`. - imports_merge_glob: bool = "true", - /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate. - imports_prefer_no_std: bool = "false", - /// The path structure for newly inserted paths to use. - imports_prefix: ImportPrefixDef = "\"plain\"", - /// Whether to show inlay type hints for binding modes. inlayHints_bindingModeHints_enable: bool = "false", /// Whether to show inlay type hints for method chains. @@ -567,7 +556,24 @@ config_data! { } -config_data! {struct LocalConfigData {}} +config_data! { + struct LocalConfigData { + + /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file. + imports_granularity_enforce: bool = "false", + /// How imports should be grouped into use statements. + imports_granularity_group: ImportGranularityDef = "\"crate\"", + /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines. + imports_group_enable: bool = "true", + /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`. + imports_merge_glob: bool = "true", + /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate. + imports_prefer_no_std: bool = "false", + /// The path structure for newly inserted paths to use. + imports_prefix: ImportPrefixDef = "\"plain\"", + } +} + config_data! {struct ClientConfigData {}} #[derive(Debug, Clone)] @@ -594,12 +600,127 @@ pub struct Config { workspace_roots: Vec, caps: lsp_types::ClientCapabilities, root_path: AbsPathBuf, - data: ConfigData, + root_config: ConfigData, + config_arena: Arena, detached_files: Vec, snippets: Vec, is_visual_studio_code: bool, } +macro_rules! try_ { + ($expr:expr) => { + || -> _ { Some($expr) }() + }; +} +macro_rules! try_or { + ($expr:expr, $or:expr) => { + try_!($expr).unwrap_or($or) + }; +} + +macro_rules! try_or_def { + ($expr:expr) => { + try_!($expr).unwrap_or_default() + }; +} + +#[derive(Debug, Clone)] +pub struct LocalConfigView<'a> { + local: &'a LocalConfigData, + root_config: &'a ConfigData, + caps: &'a lsp_types::ClientCapabilities, + snippets: &'a Vec, +} + +impl<'a> LocalConfigView<'a> { + pub fn diagnostics(&self) -> DiagnosticsConfig { + DiagnosticsConfig { + enabled: self.root_config.global.diagnostics_enable, + proc_attr_macros_enabled: self.expand_proc_attr_macros(), + proc_macros_enabled: self.root_config.global.procMacro_enable, + disable_experimental: !self.root_config.global.diagnostics_experimental_enable, + disabled: self.root_config.global.diagnostics_disabled.clone(), + expr_fill_default: match self.root_config.global.assist_expressionFillDefault { + ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, + ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, + }, + insert_use: self.insert_use_config(), + prefer_no_std: self.local.imports_prefer_no_std, + } + } + + pub fn assist(&self) -> AssistConfig { + AssistConfig { + snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), + allowed: None, + insert_use: self.insert_use_config(), + prefer_no_std: self.local.imports_prefer_no_std, + assist_emit_must_use: self.root_config.global.assist_emitMustUse, + } + } + + fn insert_use_config(&self) -> InsertUseConfig { + InsertUseConfig { + granularity: match self.local.imports_granularity_group { + ImportGranularityDef::Preserve => ImportGranularity::Preserve, + ImportGranularityDef::Item => ImportGranularity::Item, + ImportGranularityDef::Crate => ImportGranularity::Crate, + ImportGranularityDef::Module => ImportGranularity::Module, + }, + enforce_granularity: self.local.imports_granularity_enforce, + prefix_kind: match self.local.imports_prefix { + ImportPrefixDef::Plain => PrefixKind::Plain, + ImportPrefixDef::ByCrate => PrefixKind::ByCrate, + ImportPrefixDef::BySelf => PrefixKind::BySelf, + }, + group: self.local.imports_group_enable, + skip_glob_imports: !self.local.imports_merge_glob, + } + } + + pub fn expand_proc_attr_macros(&self) -> bool { + self.root_config.global.procMacro_enable + && self.root_config.global.procMacro_attributes_enable + } + + pub fn completion(&self) -> CompletionConfig { + CompletionConfig { + enable_postfix_completions: self.root_config.global.completion_postfix_enable, + enable_imports_on_the_fly: self.root_config.global.completion_autoimport_enable + && completion_item_edit_resolve(&self.caps), + enable_self_on_the_fly: self.root_config.global.completion_autoself_enable, + enable_private_editable: self.root_config.global.completion_privateEditable_enable, + full_function_signatures: self + .root_config + .global + .completion_fullFunctionSignatures_enable, + callable: match self.root_config.global.completion_callable_snippets { + CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), + CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), + CallableCompletionDef::None => None, + }, + insert_use: self.insert_use_config(), + prefer_no_std: self.local.imports_prefer_no_std, + snippet_cap: SnippetCap::new(try_or_def!( + self.caps + .text_document + .as_ref()? + .completion + .as_ref()? + .completion_item + .as_ref()? + .snippet_support? + )), + snippets: self.snippets.clone().to_vec(), + limit: self.root_config.global.completion_limit, + } + } + + fn experimental(&self, index: &'static str) -> bool { + try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?) + } +} + type ParallelCachePrimingNumThreads = u8; #[derive(Debug, Clone, Eq, PartialEq)] @@ -797,18 +918,50 @@ impl Config { workspace_roots: Vec, is_visual_studio_code: bool, ) -> Self { + let root_config = ConfigData::default(); + let config_arena = Arena::new(); + Config { caps, - data: ConfigData::default(), + root_config, detached_files: Vec::new(), discovered_projects: Vec::new(), root_path, snippets: Default::default(), workspace_roots, is_visual_studio_code, + config_arena, + } + } + + /// Returns a `LocalConfigView` that points to the root config. + /// This is a compromise for cases where we do not have enough data + /// to point to a specific local view and we still need to have one + /// because we need to query fields that are essentially global. + pub fn localize_to_root_view(&self) -> LocalConfigView<'_> { + LocalConfigView { + local: &self.root_config.local, + root_config: &self.root_config, + caps: &self.caps, + snippets: &self.snippets, + } + } + + pub fn localize_by_file_id(&self, file_id: FileId) -> LocalConfigView<'_> { + // FIXME : Plain wrong + LocalConfigView { + local: &self.root_config.local, + root_config: &self.root_config, + caps: &self.caps, + snippets: &self.snippets, } } + pub fn expand_proc_attr_macros(&self) -> bool { + self.root_config.global.procMacro_enable + && self.root_config.global.procMacro_attributes_enable + } + pub fn rediscover_workspaces(&mut self) { let discovered = ProjectManifest::discover_all(&self.workspace_roots); tracing::info!("discovered projects: {:?}", discovered); @@ -840,10 +993,10 @@ impl Config { .map(AbsPathBuf::assert) .collect(); patch_old_style::patch_json_for_outdated_configs(&mut json); - self.data.global = GlobalConfigData::from_json(json, &mut errors); - tracing::debug!("deserialized config data: {:#?}", self.data.global); + self.root_config.global = GlobalConfigData::from_json(json, &mut errors); + tracing::debug!("deserialized config data: {:#?}", self.root_config.global); self.snippets.clear(); - for (name, def) in self.data.global.completion_snippets_custom.iter() { + for (name, def) in self.root_config.global.completion_snippets_custom.iter() { if def.prefix.is_empty() && def.postfix.is_empty() { continue; } @@ -881,7 +1034,7 @@ impl Config { fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) { use serde::de::Error; - if self.data.global.check_command.is_empty() { + if self.root_config.global.check_command.is_empty() { error_sink.push(( "/check/command".to_string(), serde_json::Error::custom("expected a non-empty string"), @@ -906,32 +1059,15 @@ impl Config { } } -macro_rules! try_ { - ($expr:expr) => { - || -> _ { Some($expr) }() - }; -} -macro_rules! try_or { - ($expr:expr, $or:expr) => { - try_!($expr).unwrap_or($or) - }; -} - -macro_rules! try_or_def { - ($expr:expr) => { - try_!($expr).unwrap_or_default() - }; -} - impl Config { pub fn has_linked_projects(&self) -> bool { - !self.data.global.linkedProjects.is_empty() + !self.root_config.global.linkedProjects.is_empty() } pub fn linked_projects(&self) -> Vec { - match self.data.global.linkedProjects.as_slice() { + match self.root_config.global.linkedProjects.as_slice() { [] => { let exclude_dirs: Vec<_> = self - .data + .root_config .global .files_excludeDirs .iter() @@ -973,7 +1109,7 @@ impl Config { .map(ManifestOrProjectJson::ProjectJson) .collect::>(); - self.data.global.linkedProjects.append(&mut linked_projects); + self.root_config.global.linkedProjects.append(&mut linked_projects); } pub fn did_save_text_document_dynamic_registration(&self) -> bool { @@ -988,7 +1124,7 @@ impl Config { } pub fn prefill_caches(&self) -> bool { - self.data.global.cachePriming_enable + self.root_config.global.cachePriming_enable } pub fn location_link(&self) -> bool { @@ -1121,94 +1257,74 @@ impl Config { } pub fn publish_diagnostics(&self) -> bool { - self.data.global.diagnostics_enable - } - - pub fn diagnostics(&self) -> DiagnosticsConfig { - DiagnosticsConfig { - enabled: self.data.global.diagnostics_enable, - proc_attr_macros_enabled: self.expand_proc_attr_macros(), - proc_macros_enabled: self.data.global.procMacro_enable, - disable_experimental: !self.data.global.diagnostics_experimental_enable, - disabled: self.data.global.diagnostics_disabled.clone(), - expr_fill_default: match self.data.global.assist_expressionFillDefault { - ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, - ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, - }, - insert_use: self.insert_use_config(), - prefer_no_std: self.data.global.imports_prefer_no_std, - } + self.root_config.global.diagnostics_enable } pub fn diagnostics_map(&self) -> DiagnosticsMapConfig { DiagnosticsMapConfig { - remap_prefix: self.data.global.diagnostics_remapPrefix.clone(), - warnings_as_info: self.data.global.diagnostics_warningsAsInfo.clone(), - warnings_as_hint: self.data.global.diagnostics_warningsAsHint.clone(), - check_ignore: self.data.global.check_ignore.clone(), + remap_prefix: self.root_config.global.diagnostics_remapPrefix.clone(), + warnings_as_info: self.root_config.global.diagnostics_warningsAsInfo.clone(), + warnings_as_hint: self.root_config.global.diagnostics_warningsAsHint.clone(), + check_ignore: self.root_config.global.check_ignore.clone(), } } pub fn extra_args(&self) -> &Vec { - &self.data.global.cargo_extraArgs + &self.root_config.global.cargo_extraArgs } pub fn extra_env(&self) -> &FxHashMap { - &self.data.global.cargo_extraEnv + &self.root_config.global.cargo_extraEnv } pub fn check_extra_args(&self) -> Vec { let mut extra_args = self.extra_args().clone(); - extra_args.extend_from_slice(&self.data.global.check_extraArgs); + extra_args.extend_from_slice(&self.root_config.global.check_extraArgs); extra_args } pub fn check_extra_env(&self) -> FxHashMap { - let mut extra_env = self.data.global.cargo_extraEnv.clone(); - extra_env.extend(self.data.global.check_extraEnv.clone()); + let mut extra_env = self.root_config.global.cargo_extraEnv.clone(); + extra_env.extend(self.root_config.global.check_extraEnv.clone()); extra_env } pub fn lru_parse_query_capacity(&self) -> Option { - self.data.global.lru_capacity + self.root_config.global.lru_capacity } pub fn lru_query_capacities(&self) -> Option<&FxHashMap, usize>> { - self.data + self.root_config .global .lru_query_capacities .is_empty() .not() - .then(|| &self.data.global.lru_query_capacities) + .then(|| &self.root_config.global.lru_query_capacities) } pub fn proc_macro_srv(&self) -> Option { - let path = self.data.global.procMacro_server.clone()?; + let path = self.root_config.global.procMacro_server.clone()?; Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(&path))) } pub fn dummy_replacements(&self) -> &FxHashMap, Box<[Box]>> { - &self.data.global.procMacro_ignored + &self.root_config.global.procMacro_ignored } pub fn expand_proc_macros(&self) -> bool { - self.data.global.procMacro_enable - } - - pub fn expand_proc_attr_macros(&self) -> bool { - self.data.global.procMacro_enable && self.data.global.procMacro_attributes_enable + self.root_config.global.procMacro_enable } pub fn files(&self) -> FilesConfig { FilesConfig { - watcher: match self.data.global.files_watcher { + watcher: match self.root_config.global.files_watcher { FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => { FilesWatcher::Client } _ => FilesWatcher::Server, }, exclude: self - .data + .root_config .global .files_excludeDirs .iter() @@ -1219,51 +1335,56 @@ impl Config { pub fn notifications(&self) -> NotificationsConfig { NotificationsConfig { - cargo_toml_not_found: self.data.global.notifications_cargoTomlNotFound, + cargo_toml_not_found: self.root_config.global.notifications_cargoTomlNotFound, } } pub fn cargo_autoreload(&self) -> bool { - self.data.global.cargo_autoreload + self.root_config.global.cargo_autoreload } pub fn run_build_scripts(&self) -> bool { - self.data.global.cargo_buildScripts_enable || self.data.global.procMacro_enable + self.root_config.global.cargo_buildScripts_enable + || self.root_config.global.procMacro_enable } pub fn cargo(&self) -> CargoConfig { - let rustc_source = self.data.global.rustc_source.as_ref().map(|rustc_src| { + let rustc_source = self.root_config.global.rustc_source.as_ref().map(|rustc_src| { if rustc_src == "discover" { RustLibSource::Discover } else { RustLibSource::Path(self.root_path.join(rustc_src)) } }); - let sysroot = self.data.global.cargo_sysroot.as_ref().map(|sysroot| { + let sysroot = self.root_config.global.cargo_sysroot.as_ref().map(|sysroot| { if sysroot == "discover" { RustLibSource::Discover } else { RustLibSource::Path(self.root_path.join(sysroot)) } }); - let sysroot_src = - self.data.global.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot)); + let sysroot_src = self + .root_config + .global + .cargo_sysrootSrc + .as_ref() + .map(|sysroot| self.root_path.join(sysroot)); CargoConfig { - features: match &self.data.global.cargo_features { + features: match &self.root_config.global.cargo_features { CargoFeaturesDef::All => CargoFeatures::All, CargoFeaturesDef::Selected(features) => CargoFeatures::Selected { features: features.clone(), - no_default_features: self.data.global.cargo_noDefaultFeatures, + no_default_features: self.root_config.global.cargo_noDefaultFeatures, }, }, - target: self.data.global.cargo_target.clone(), + target: self.root_config.global.cargo_target.clone(), sysroot, sysroot_src, rustc_source, cfg_overrides: project_model::CfgOverrides { global: CfgDiff::new( - self.data + self.root_config .global .cargo_cfgs .iter() @@ -1279,7 +1400,7 @@ impl Config { ) .unwrap(), selective: self - .data + .root_config .global .cargo_unsetTest .iter() @@ -1291,40 +1412,46 @@ impl Config { }) .collect(), }, - wrap_rustc_in_build_scripts: self.data.global.cargo_buildScripts_useRustcWrapper, - invocation_strategy: match self.data.global.cargo_buildScripts_invocationStrategy { + wrap_rustc_in_build_scripts: self.root_config.global.cargo_buildScripts_useRustcWrapper, + invocation_strategy: match self.root_config.global.cargo_buildScripts_invocationStrategy + { InvocationStrategy::Once => project_model::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace, }, - invocation_location: match self.data.global.cargo_buildScripts_invocationLocation { + invocation_location: match self.root_config.global.cargo_buildScripts_invocationLocation + { InvocationLocation::Root => { project_model::InvocationLocation::Root(self.root_path.clone()) } InvocationLocation::Workspace => project_model::InvocationLocation::Workspace, }, - run_build_script_command: self.data.global.cargo_buildScripts_overrideCommand.clone(), - extra_args: self.data.global.cargo_extraArgs.clone(), - extra_env: self.data.global.cargo_extraEnv.clone(), + run_build_script_command: self + .root_config + .global + .cargo_buildScripts_overrideCommand + .clone(), + extra_args: self.root_config.global.cargo_extraArgs.clone(), + extra_env: self.root_config.global.cargo_extraEnv.clone(), target_dir: self.target_dir_from_config(), } } pub fn rustfmt(&self) -> RustfmtConfig { - match &self.data.global.rustfmt_overrideCommand { + match &self.root_config.global.rustfmt_overrideCommand { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); RustfmtConfig::CustomCommand { command, args } } Some(_) | None => RustfmtConfig::Rustfmt { - extra_args: self.data.global.rustfmt_extraArgs.clone(), - enable_range_formatting: self.data.global.rustfmt_rangeFormatting_enable, + extra_args: self.root_config.global.rustfmt_extraArgs.clone(), + enable_range_formatting: self.root_config.global.rustfmt_rangeFormatting_enable, }, } } pub fn flycheck(&self) -> FlycheckConfig { - match &self.data.global.check_overrideCommand { + match &self.root_config.global.check_overrideCommand { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); @@ -1332,13 +1459,13 @@ impl Config { command, args, extra_env: self.check_extra_env(), - invocation_strategy: match self.data.global.check_invocationStrategy { + invocation_strategy: match self.root_config.global.check_invocationStrategy { InvocationStrategy::Once => flycheck::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => { flycheck::InvocationStrategy::PerWorkspace } }, - invocation_location: match self.data.global.check_invocationLocation { + invocation_location: match self.root_config.global.check_invocationLocation { InvocationLocation::Root => { flycheck::InvocationLocation::Root(self.root_path.clone()) } @@ -1347,9 +1474,9 @@ impl Config { } } Some(_) | None => FlycheckConfig::CargoCommand { - command: self.data.global.check_command.clone(), + command: self.root_config.global.check_command.clone(), target_triples: self - .data + .root_config .global .check_targets .clone() @@ -1357,27 +1484,29 @@ impl Config { [] => None, targets => Some(targets.into()), }) - .unwrap_or_else(|| self.data.global.cargo_target.clone().into_iter().collect()), - all_targets: self.data.global.check_allTargets, + .unwrap_or_else(|| { + self.root_config.global.cargo_target.clone().into_iter().collect() + }), + all_targets: self.root_config.global.check_allTargets, no_default_features: self - .data + .root_config .global .check_noDefaultFeatures - .unwrap_or(self.data.global.cargo_noDefaultFeatures), + .unwrap_or(self.root_config.global.cargo_noDefaultFeatures), all_features: matches!( - self.data + self.root_config .global .check_features .as_ref() - .unwrap_or(&self.data.global.cargo_features), + .unwrap_or(&self.root_config.global.cargo_features), CargoFeaturesDef::All ), features: match self - .data + .root_config .global .check_features .clone() - .unwrap_or_else(|| self.data.global.cargo_features.clone()) + .unwrap_or_else(|| self.root_config.global.cargo_features.clone()) { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, @@ -1391,23 +1520,25 @@ impl Config { } fn target_dir_from_config(&self) -> Option { - self.data.global.rust_analyzerTargetDir.as_ref().and_then(|target_dir| match target_dir { - TargetDirectory::UseSubdirectory(yes) if *yes => { - Some(PathBuf::from("target/rust-analyzer")) + self.root_config.global.rust_analyzerTargetDir.as_ref().and_then(|target_dir| { + match target_dir { + TargetDirectory::UseSubdirectory(yes) if *yes => { + Some(PathBuf::from("target/rust-analyzer")) + } + TargetDirectory::UseSubdirectory(_) => None, + TargetDirectory::Directory(dir) => Some(dir.clone()), } - TargetDirectory::UseSubdirectory(_) => None, - TargetDirectory::Directory(dir) => Some(dir.clone()), }) } pub fn check_on_save(&self) -> bool { - self.data.global.checkOnSave + self.root_config.global.checkOnSave } pub fn runnables(&self) -> RunnablesConfig { RunnablesConfig { - override_cargo: self.data.global.runnables_command.clone(), - cargo_extra_args: self.data.global.runnables_extraArgs.clone(), + override_cargo: self.root_config.global.runnables_command.clone(), + cargo_extra_args: self.root_config.global.runnables_extraArgs.clone(), } } @@ -1425,17 +1556,17 @@ impl Config { .collect::>(); InlayHintsConfig { - render_colons: self.data.global.inlayHints_renderColons, - type_hints: self.data.global.inlayHints_typeHints_enable, - parameter_hints: self.data.global.inlayHints_parameterHints_enable, - chaining_hints: self.data.global.inlayHints_chainingHints_enable, - discriminant_hints: match self.data.global.inlayHints_discriminantHints_enable { + render_colons: self.root_config.global.inlayHints_renderColons, + type_hints: self.root_config.global.inlayHints_typeHints_enable, + parameter_hints: self.root_config.global.inlayHints_parameterHints_enable, + chaining_hints: self.root_config.global.inlayHints_chainingHints_enable, + discriminant_hints: match self.root_config.global.inlayHints_discriminantHints_enable { DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless, }, closure_return_type_hints: match self - .data + .root_config .global .inlayHints_closureReturnTypeHints_enable { @@ -1443,38 +1574,50 @@ impl Config { ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never, ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock, }, - lifetime_elision_hints: match self.data.global.inlayHints_lifetimeElisionHints_enable { + lifetime_elision_hints: match self + .root_config + .global + .inlayHints_lifetimeElisionHints_enable + { LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always, LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never, LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial, }, hide_named_constructor_hints: self - .data + .root_config .global .inlayHints_typeHints_hideNamedConstructor, hide_closure_initialization_hints: self - .data + .root_config .global .inlayHints_typeHints_hideClosureInitialization, - closure_style: match self.data.global.inlayHints_closureStyle { + closure_style: match self.root_config.global.inlayHints_closureStyle { ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn, ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation, ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId, ClosureStyle::Hide => hir::ClosureStyle::Hide, }, - closure_capture_hints: self.data.global.inlayHints_closureCaptureHints_enable, - adjustment_hints: match self.data.global.inlayHints_expressionAdjustmentHints_enable { + closure_capture_hints: self.root_config.global.inlayHints_closureCaptureHints_enable, + adjustment_hints: match self + .root_config + .global + .inlayHints_expressionAdjustmentHints_enable + { AdjustmentHintsDef::Always => ide::AdjustmentHints::Always, - AdjustmentHintsDef::Never => match self.data.global.inlayHints_reborrowHints_enable - { - ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { - ide::AdjustmentHints::ReborrowOnly + AdjustmentHintsDef::Never => { + match self.root_config.global.inlayHints_reborrowHints_enable { + ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { + ide::AdjustmentHints::ReborrowOnly + } + ReborrowHintsDef::Never => ide::AdjustmentHints::Never, } - ReborrowHintsDef::Never => ide::AdjustmentHints::Never, - }, + } AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly, }, - adjustment_hints_mode: match self.data.global.inlayHints_expressionAdjustmentHints_mode + adjustment_hints_mode: match self + .root_config + .global + .inlayHints_expressionAdjustmentHints_mode { AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix, AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix, @@ -1482,17 +1625,21 @@ impl Config { AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix, }, adjustment_hints_hide_outside_unsafe: self - .data + .root_config .global .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe, - binding_mode_hints: self.data.global.inlayHints_bindingModeHints_enable, + binding_mode_hints: self.root_config.global.inlayHints_bindingModeHints_enable, param_names_for_lifetime_elision_hints: self - .data + .root_config .global .inlayHints_lifetimeElisionHints_useParameterNames, - max_length: self.data.global.inlayHints_maxLength, - closing_brace_hints_min_lines: if self.data.global.inlayHints_closingBraceHints_enable { - Some(self.data.global.inlayHints_closingBraceHints_minLines) + max_length: self.root_config.global.inlayHints_maxLength, + closing_brace_hints_min_lines: if self + .root_config + .global + .inlayHints_closingBraceHints_enable + { + Some(self.root_config.global.inlayHints_closingBraceHints_minLines) } else { None }, @@ -1506,142 +1653,91 @@ impl Config { } } - fn insert_use_config(&self) -> InsertUseConfig { - InsertUseConfig { - granularity: match self.data.global.imports_granularity_group { - ImportGranularityDef::Preserve => ImportGranularity::Preserve, - ImportGranularityDef::Item => ImportGranularity::Item, - ImportGranularityDef::Crate => ImportGranularity::Crate, - ImportGranularityDef::Module => ImportGranularity::Module, - }, - enforce_granularity: self.data.global.imports_granularity_enforce, - prefix_kind: match self.data.global.imports_prefix { - ImportPrefixDef::Plain => PrefixKind::Plain, - ImportPrefixDef::ByCrate => PrefixKind::ByCrate, - ImportPrefixDef::BySelf => PrefixKind::BySelf, - }, - group: self.data.global.imports_group_enable, - skip_glob_imports: !self.data.global.imports_merge_glob, - } - } - - pub fn completion(&self) -> CompletionConfig { - CompletionConfig { - enable_postfix_completions: self.data.global.completion_postfix_enable, - enable_imports_on_the_fly: self.data.global.completion_autoimport_enable - && completion_item_edit_resolve(&self.caps), - enable_self_on_the_fly: self.data.global.completion_autoself_enable, - enable_private_editable: self.data.global.completion_privateEditable_enable, - full_function_signatures: self.data.global.completion_fullFunctionSignatures_enable, - callable: match self.data.global.completion_callable_snippets { - CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), - CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), - CallableCompletionDef::None => None, - }, - insert_use: self.insert_use_config(), - prefer_no_std: self.data.global.imports_prefer_no_std, - snippet_cap: SnippetCap::new(try_or_def!( - self.caps - .text_document - .as_ref()? - .completion - .as_ref()? - .completion_item - .as_ref()? - .snippet_support? - )), - snippets: self.snippets.clone(), - limit: self.data.global.completion_limit, - } - } - pub fn find_all_refs_exclude_imports(&self) -> bool { - self.data.global.references_excludeImports + self.root_config.global.references_excludeImports } pub fn snippet_cap(&self) -> bool { self.experimental("snippetTextEdit") } - pub fn assist(&self) -> AssistConfig { - AssistConfig { - snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), - allowed: None, - insert_use: self.insert_use_config(), - prefer_no_std: self.data.global.imports_prefer_no_std, - assist_emit_must_use: self.data.global.assist_emitMustUse, - } - } - pub fn join_lines(&self) -> JoinLinesConfig { JoinLinesConfig { - join_else_if: self.data.global.joinLines_joinElseIf, - remove_trailing_comma: self.data.global.joinLines_removeTrailingComma, - unwrap_trivial_blocks: self.data.global.joinLines_unwrapTrivialBlock, - join_assignments: self.data.global.joinLines_joinAssignments, + join_else_if: self.root_config.global.joinLines_joinElseIf, + remove_trailing_comma: self.root_config.global.joinLines_removeTrailingComma, + unwrap_trivial_blocks: self.root_config.global.joinLines_unwrapTrivialBlock, + join_assignments: self.root_config.global.joinLines_joinAssignments, } } pub fn call_info(&self) -> CallInfoConfig { CallInfoConfig { params_only: matches!( - self.data.global.signatureInfo_detail, + self.root_config.global.signatureInfo_detail, SignatureDetail::Parameters ), - docs: self.data.global.signatureInfo_documentation_enable, + docs: self.root_config.global.signatureInfo_documentation_enable, } } pub fn lens(&self) -> LensConfig { LensConfig { - run: self.data.global.lens_enable && self.data.global.lens_run_enable, - debug: self.data.global.lens_enable && self.data.global.lens_debug_enable, - interpret: self.data.global.lens_enable - && self.data.global.lens_run_enable - && self.data.global.interpret_tests, - implementations: self.data.global.lens_enable - && self.data.global.lens_implementations_enable, - method_refs: self.data.global.lens_enable - && self.data.global.lens_references_method_enable, - refs_adt: self.data.global.lens_enable && self.data.global.lens_references_adt_enable, - refs_trait: self.data.global.lens_enable - && self.data.global.lens_references_trait_enable, - enum_variant_refs: self.data.global.lens_enable - && self.data.global.lens_references_enumVariant_enable, - location: self.data.global.lens_location, + run: self.root_config.global.lens_enable && self.root_config.global.lens_run_enable, + debug: self.root_config.global.lens_enable && self.root_config.global.lens_debug_enable, + interpret: self.root_config.global.lens_enable + && self.root_config.global.lens_run_enable + && self.root_config.global.interpret_tests, + implementations: self.root_config.global.lens_enable + && self.root_config.global.lens_implementations_enable, + method_refs: self.root_config.global.lens_enable + && self.root_config.global.lens_references_method_enable, + refs_adt: self.root_config.global.lens_enable + && self.root_config.global.lens_references_adt_enable, + refs_trait: self.root_config.global.lens_enable + && self.root_config.global.lens_references_trait_enable, + enum_variant_refs: self.root_config.global.lens_enable + && self.root_config.global.lens_references_enumVariant_enable, + location: self.root_config.global.lens_location, } } pub fn hover_actions(&self) -> HoverActionsConfig { - let enable = self.experimental("hoverActions") && self.data.global.hover_actions_enable; + let enable = + self.experimental("hoverActions") && self.root_config.global.hover_actions_enable; HoverActionsConfig { - implementations: enable && self.data.global.hover_actions_implementations_enable, - references: enable && self.data.global.hover_actions_references_enable, - run: enable && self.data.global.hover_actions_run_enable, - debug: enable && self.data.global.hover_actions_debug_enable, - goto_type_def: enable && self.data.global.hover_actions_gotoTypeDef_enable, + implementations: enable && self.root_config.global.hover_actions_implementations_enable, + references: enable && self.root_config.global.hover_actions_references_enable, + run: enable && self.root_config.global.hover_actions_run_enable, + debug: enable && self.root_config.global.hover_actions_debug_enable, + goto_type_def: enable && self.root_config.global.hover_actions_gotoTypeDef_enable, } } pub fn highlighting_non_standard_tokens(&self) -> bool { - self.data.global.semanticHighlighting_nonStandardTokens + self.root_config.global.semanticHighlighting_nonStandardTokens } pub fn highlighting_config(&self) -> HighlightConfig { HighlightConfig { - strings: self.data.global.semanticHighlighting_strings_enable, - punctuation: self.data.global.semanticHighlighting_punctuation_enable, + strings: self.root_config.global.semanticHighlighting_strings_enable, + punctuation: self.root_config.global.semanticHighlighting_punctuation_enable, specialize_punctuation: self - .data + .root_config .global .semanticHighlighting_punctuation_specialization_enable, - macro_bang: self.data.global.semanticHighlighting_punctuation_separate_macro_bang, - operator: self.data.global.semanticHighlighting_operator_enable, + macro_bang: self + .root_config + .global + .semanticHighlighting_punctuation_separate_macro_bang, + operator: self.root_config.global.semanticHighlighting_operator_enable, specialize_operator: self - .data + .root_config .global .semanticHighlighting_operator_specialization_enable, - inject_doc_comment: self.data.global.semanticHighlighting_doc_comment_inject_enable, + inject_doc_comment: self + .root_config + .global + .semanticHighlighting_doc_comment_inject_enable, syntactic_name_ref_highlighting: false, } } @@ -1653,16 +1749,16 @@ impl Config { MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal, }; HoverConfig { - links_in_hover: self.data.global.hover_links_enable, - memory_layout: self.data.global.hover_memoryLayout_enable.then_some( + links_in_hover: self.root_config.global.hover_links_enable, + memory_layout: self.root_config.global.hover_memoryLayout_enable.then_some( MemoryLayoutHoverConfig { - size: self.data.global.hover_memoryLayout_size.map(mem_kind), - offset: self.data.global.hover_memoryLayout_offset.map(mem_kind), - alignment: self.data.global.hover_memoryLayout_alignment.map(mem_kind), - niches: self.data.global.hover_memoryLayout_niches.unwrap_or_default(), + size: self.root_config.global.hover_memoryLayout_size.map(mem_kind), + offset: self.root_config.global.hover_memoryLayout_offset.map(mem_kind), + alignment: self.root_config.global.hover_memoryLayout_alignment.map(mem_kind), + niches: self.root_config.global.hover_memoryLayout_niches.unwrap_or_default(), }, ), - documentation: self.data.global.hover_documentation_enable, + documentation: self.root_config.global.hover_documentation_enable, format: { let is_markdown = try_or_def!(self .caps @@ -1680,23 +1776,23 @@ impl Config { HoverDocFormat::PlainText } }, - keywords: self.data.global.hover_documentation_keywords_enable, + keywords: self.root_config.global.hover_documentation_keywords_enable, } } pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig { WorkspaceSymbolConfig { - search_scope: match self.data.global.workspace_symbol_search_scope { + search_scope: match self.root_config.global.workspace_symbol_search_scope { WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace, WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => { WorkspaceSymbolSearchScope::WorkspaceAndDependencies } }, - search_kind: match self.data.global.workspace_symbol_search_kind { + search_kind: match self.root_config.global.workspace_symbol_search_kind { WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes, WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols, }, - search_limit: self.data.global.workspace_symbol_search_limit, + search_limit: self.root_config.global.workspace_symbol_search_limit, } } @@ -1730,7 +1826,7 @@ impl Config { try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null); let commands: Option = serde_json::from_value(commands.clone()).ok(); - let force = commands.is_none() && self.data.global.lens_forceCustomCommands; + let force = commands.is_none() && self.root_config.global.lens_forceCustomCommands; let commands = commands.map(|it| it.commands).unwrap_or_default(); let get = |name: &str| commands.iter().any(|it| it == name) || force; @@ -1746,27 +1842,30 @@ impl Config { pub fn highlight_related(&self) -> HighlightRelatedConfig { HighlightRelatedConfig { - references: self.data.global.highlightRelated_references_enable, - break_points: self.data.global.highlightRelated_breakPoints_enable, - exit_points: self.data.global.highlightRelated_exitPoints_enable, - yield_points: self.data.global.highlightRelated_yieldPoints_enable, - closure_captures: self.data.global.highlightRelated_closureCaptures_enable, + references: self.root_config.global.highlightRelated_references_enable, + break_points: self.root_config.global.highlightRelated_breakPoints_enable, + exit_points: self.root_config.global.highlightRelated_exitPoints_enable, + yield_points: self.root_config.global.highlightRelated_yieldPoints_enable, + closure_captures: self.root_config.global.highlightRelated_closureCaptures_enable, } } pub fn prime_caches_num_threads(&self) -> u8 { - match self.data.global.cachePriming_numThreads { + match self.root_config.global.cachePriming_numThreads { 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX), n => n, } } pub fn main_loop_num_threads(&self) -> usize { - self.data.global.numThreads.unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1)) + self.root_config + .global + .numThreads + .unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1)) } pub fn typing_autoclose_angle(&self) -> bool { - self.data.global.typing_autoClosingAngleBrackets_enable + self.root_config.global.typing_autoClosingAngleBrackets_enable } // FIXME: VSCode seems to work wrong sometimes, see https://github.com/microsoft/vscode/issues/193124 @@ -2177,7 +2276,6 @@ macro_rules! _config_data { } } - // TODO // #[test] // fn fields_are_sorted() { // [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); @@ -2674,7 +2772,7 @@ mod tests { #[test] fn generate_config_documentation() { let docs_path = project_root().join("docs/user/generated_config.adoc"); - // TODO Merge configs , lexicographically order and spit them out. + // ATTENTION let expected = GlobalConfigData::manual(); ensure_file_contents(&docs_path, &expected); } @@ -2747,7 +2845,7 @@ mod tests { "rust": { "analyzerTargetDir": null } })) .unwrap(); - assert_eq!(config.data.global.rust_analyzerTargetDir, None); + assert_eq!(config.root_config.global.rust_analyzerTargetDir, None); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == None) ); @@ -2767,7 +2865,7 @@ mod tests { })) .unwrap(); assert_eq!( - config.data.global.rust_analyzerTargetDir, + config.root_config.global.rust_analyzerTargetDir, Some(TargetDirectory::UseSubdirectory(true)) ); assert!( @@ -2789,7 +2887,7 @@ mod tests { })) .unwrap(); assert_eq!( - config.data.global.rust_analyzerTargetDir, + config.root_config.global.rust_analyzerTargetDir, Some(TargetDirectory::Directory(PathBuf::from("other_folder"))) ); assert!( diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index 71701ef16179..b841762d21b7 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -136,7 +136,7 @@ pub(crate) fn fetch_native_diagnostics( let diagnostics = snapshot .analysis .diagnostics( - &snapshot.config.diagnostics(), + &snapshot.config.localize_by_file_id(file_id).diagnostics(), ide::AssistResolveStrategy::None, file_id, ) diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 06c27332d440..0a14e6893d57 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -859,7 +859,7 @@ pub(crate) fn handle_completion( let completion_trigger_character = params.context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next()); - let completion_config = &snap.config.completion(); + let completion_config = &snap.config.localize_by_file_id(position.file_id).completion(); let items = match snap.analysis.completions( completion_config, position, @@ -904,7 +904,7 @@ pub(crate) fn handle_completion_resolve( let additional_edits = snap .analysis .resolve_completion_edits( - &snap.config.completion(), + &snap.config.localize_by_file_id(file_id).completion(), FilePosition { file_id, offset }, resolve_data .imports @@ -1129,11 +1129,12 @@ pub(crate) fn handle_code_action( return Ok(None); } - let line_index = - snap.file_line_index(from_proto::file_id(&snap, ¶ms.text_document.uri)?)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.file_line_index(file_id)?; let frange = from_proto::file_range(&snap, ¶ms.text_document, params.range)?; - let mut assists_config = snap.config.assist(); + let local_config = snap.config.localize_by_file_id(file_id); + let mut assists_config = local_config.assist(); assists_config.allowed = params .context .only @@ -1150,7 +1151,7 @@ pub(crate) fn handle_code_action( }; let assists = snap.analysis.assists_with_fixes( &assists_config, - &snap.config.diagnostics(), + &local_config.diagnostics(), resolve, frange, )?; @@ -1209,7 +1210,9 @@ pub(crate) fn handle_code_action_resolve( let range = from_proto::text_range(&line_index, params.code_action_params.range)?; let frange = FileRange { file_id, range }; - let mut assists_config = snap.config.assist(); + let local_config = snap.config.localize_by_file_id(file_id); + + let mut assists_config = local_config.assist(); assists_config.allowed = params .code_action_params .context @@ -1232,7 +1235,7 @@ pub(crate) fn handle_code_action_resolve( let assists = snap.analysis.assists_with_fixes( &assists_config, - &snap.config.diagnostics(), + &local_config.diagnostics(), AssistResolveStrategy::Single(assist_resolve), frange, )?; diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index aca91570f7c2..5da70526b662 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -226,7 +226,7 @@ pub(crate) fn completion_items( completion_item(&mut res, config, line_index, &tdpp, max_relevance, item); } - if let Some(limit) = config.completion().limit { + if let Some(limit) = config.localize_to_root_view().completion().limit { res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text)); res.truncate(limit); } @@ -308,7 +308,7 @@ fn completion_item( set_score(&mut lsp_item, max_relevance, item.relevance); - if config.completion().enable_imports_on_the_fly { + if config.localize_to_root_view().completion().enable_imports_on_the_fly { if !item.import_to_add.is_empty() { let imports: Vec<_> = item .import_to_add From a7b61751138eb1b8361099da8c229f352e9ed363 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 01:48:08 +0200 Subject: [PATCH 03/52] Declare joinLines_* as local --- crates/rust-analyzer/src/config.rs | 36 +++++++++++--------- crates/rust-analyzer/src/handlers/request.rs | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 9f9f2a2fde48..e72466d101e8 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -391,14 +391,6 @@ config_data! { /// Enables the experimental support for interpreting tests. interpret_tests: bool = "false", - /// Join lines merges consecutive declaration and initialization of an assignment. - joinLines_joinAssignments: bool = "true", - /// Join lines inserts else between consecutive ifs. - joinLines_joinElseIf: bool = "true", - /// Join lines removes trailing commas. - joinLines_removeTrailingComma: bool = "true", - /// Join lines unwraps trivial blocks. - joinLines_unwrapTrivialBlock: bool = "true", /// Whether to show `Debug` lens. Only applies when @@ -571,6 +563,16 @@ config_data! { imports_prefer_no_std: bool = "false", /// The path structure for newly inserted paths to use. imports_prefix: ImportPrefixDef = "\"plain\"", + + + /// Join lines merges consecutive declaration and initialization of an assignment. + joinLines_joinAssignments: bool = "true", + /// Join lines inserts else between consecutive ifs. + joinLines_joinElseIf: bool = "true", + /// Join lines removes trailing commas. + joinLines_removeTrailingComma: bool = "true", + /// Join lines unwraps trivial blocks. + joinLines_unwrapTrivialBlock: bool = "true", } } @@ -719,6 +721,15 @@ impl<'a> LocalConfigView<'a> { fn experimental(&self, index: &'static str) -> bool { try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?) } + + pub fn join_lines(&self) -> JoinLinesConfig { + JoinLinesConfig { + join_else_if: self.local.joinLines_joinElseIf, + remove_trailing_comma: self.local.joinLines_removeTrailingComma, + unwrap_trivial_blocks: self.local.joinLines_unwrapTrivialBlock, + join_assignments: self.local.joinLines_joinAssignments, + } + } } type ParallelCachePrimingNumThreads = u8; @@ -1661,15 +1672,6 @@ impl Config { self.experimental("snippetTextEdit") } - pub fn join_lines(&self) -> JoinLinesConfig { - JoinLinesConfig { - join_else_if: self.root_config.global.joinLines_joinElseIf, - remove_trailing_comma: self.root_config.global.joinLines_removeTrailingComma, - unwrap_trivial_blocks: self.root_config.global.joinLines_unwrapTrivialBlock, - join_assignments: self.root_config.global.joinLines_joinAssignments, - } - } - pub fn call_info(&self) -> CallInfoConfig { CallInfoConfig { params_only: matches!( diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 0a14e6893d57..51fd16427e00 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -288,8 +288,8 @@ pub(crate) fn handle_join_lines( ) -> anyhow::Result> { let _p = profile::span("handle_join_lines"); - let config = snap.config.join_lines(); let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let config = snap.config.localize_by_file_id(file_id).join_lines(); let line_index = snap.file_line_index(file_id)?; let mut res = TextEdit::default(); From 34a255cd82b8625522c1fd9867509e5680a0ab07 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 02:13:53 +0200 Subject: [PATCH 04/52] Compile time measures. Make a distinction between a RootConfig and a NonRootConfig --- crates/rust-analyzer/src/config.rs | 357 +++++++++++++++++------------ 1 file changed, 215 insertions(+), 142 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e72466d101e8..e749645b7c45 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -595,6 +595,39 @@ impl Default for ConfigData { } } +#[derive(Debug, Clone)] +struct RootLocalConfigData(LocalConfigData); +#[derive(Debug, Clone)] +struct RootGlobalConfigData(GlobalConfigData); +#[derive(Debug, Clone)] +struct RootClientConfigData(ClientConfigData); + +#[derive(Debug, Clone)] +struct RootConfigData { + local: RootLocalConfigData, + global: RootGlobalConfigData, + client: RootClientConfigData, +} + +impl Default for RootConfigData { + fn default() -> Self { + RootConfigData { + local: RootLocalConfigData(LocalConfigData::from_json( + serde_json::Value::Null, + &mut Vec::new(), + )), + global: RootGlobalConfigData(GlobalConfigData::from_json( + serde_json::Value::Null, + &mut Vec::new(), + )), + client: RootClientConfigData(ClientConfigData::from_json( + serde_json::Value::Null, + &mut Vec::new(), + )), + } + } +} + #[derive(Debug, Clone)] pub struct Config { discovered_projects: Vec, @@ -602,7 +635,7 @@ pub struct Config { workspace_roots: Vec, caps: lsp_types::ClientCapabilities, root_path: AbsPathBuf, - root_config: ConfigData, + root_config: RootConfigData, config_arena: Arena, detached_files: Vec, snippets: Vec, @@ -629,7 +662,8 @@ macro_rules! try_or_def { #[derive(Debug, Clone)] pub struct LocalConfigView<'a> { local: &'a LocalConfigData, - root_config: &'a ConfigData, + global: &'a RootGlobalConfigData, + client: &'a RootClientConfigData, caps: &'a lsp_types::ClientCapabilities, snippets: &'a Vec, } @@ -637,12 +671,12 @@ pub struct LocalConfigView<'a> { impl<'a> LocalConfigView<'a> { pub fn diagnostics(&self) -> DiagnosticsConfig { DiagnosticsConfig { - enabled: self.root_config.global.diagnostics_enable, + enabled: self.global.0.diagnostics_enable, proc_attr_macros_enabled: self.expand_proc_attr_macros(), - proc_macros_enabled: self.root_config.global.procMacro_enable, - disable_experimental: !self.root_config.global.diagnostics_experimental_enable, - disabled: self.root_config.global.diagnostics_disabled.clone(), - expr_fill_default: match self.root_config.global.assist_expressionFillDefault { + proc_macros_enabled: self.global.0.procMacro_enable, + disable_experimental: !self.global.0.diagnostics_experimental_enable, + disabled: self.global.0.diagnostics_disabled.clone(), + expr_fill_default: match self.global.0.assist_expressionFillDefault { ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo, ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, }, @@ -657,7 +691,7 @@ impl<'a> LocalConfigView<'a> { allowed: None, insert_use: self.insert_use_config(), prefer_no_std: self.local.imports_prefer_no_std, - assist_emit_must_use: self.root_config.global.assist_emitMustUse, + assist_emit_must_use: self.global.0.assist_emitMustUse, } } @@ -681,22 +715,18 @@ impl<'a> LocalConfigView<'a> { } pub fn expand_proc_attr_macros(&self) -> bool { - self.root_config.global.procMacro_enable - && self.root_config.global.procMacro_attributes_enable + self.global.0.procMacro_enable && self.global.0.procMacro_attributes_enable } pub fn completion(&self) -> CompletionConfig { CompletionConfig { - enable_postfix_completions: self.root_config.global.completion_postfix_enable, - enable_imports_on_the_fly: self.root_config.global.completion_autoimport_enable + enable_postfix_completions: self.global.0.completion_postfix_enable, + enable_imports_on_the_fly: self.global.0.completion_autoimport_enable && completion_item_edit_resolve(&self.caps), - enable_self_on_the_fly: self.root_config.global.completion_autoself_enable, - enable_private_editable: self.root_config.global.completion_privateEditable_enable, - full_function_signatures: self - .root_config - .global - .completion_fullFunctionSignatures_enable, - callable: match self.root_config.global.completion_callable_snippets { + enable_self_on_the_fly: self.global.0.completion_autoself_enable, + enable_private_editable: self.global.0.completion_privateEditable_enable, + full_function_signatures: self.global.0.completion_fullFunctionSignatures_enable, + callable: match self.global.0.completion_callable_snippets { CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), CallableCompletionDef::None => None, @@ -714,7 +744,7 @@ impl<'a> LocalConfigView<'a> { .snippet_support? )), snippets: self.snippets.clone().to_vec(), - limit: self.root_config.global.completion_limit, + limit: self.global.0.completion_limit, } } @@ -929,7 +959,7 @@ impl Config { workspace_roots: Vec, is_visual_studio_code: bool, ) -> Self { - let root_config = ConfigData::default(); + let root_config = RootConfigData::default(); let config_arena = Arena::new(); Config { @@ -951,8 +981,9 @@ impl Config { /// because we need to query fields that are essentially global. pub fn localize_to_root_view(&self) -> LocalConfigView<'_> { LocalConfigView { - local: &self.root_config.local, - root_config: &self.root_config, + local: &self.root_config.local.0, + global: &self.root_config.global, + client: &self.root_config.client, caps: &self.caps, snippets: &self.snippets, } @@ -961,16 +992,17 @@ impl Config { pub fn localize_by_file_id(&self, file_id: FileId) -> LocalConfigView<'_> { // FIXME : Plain wrong LocalConfigView { - local: &self.root_config.local, - root_config: &self.root_config, + local: &self.root_config.local.0, + global: &self.root_config.global, + client: &self.root_config.client, caps: &self.caps, snippets: &self.snippets, } } pub fn expand_proc_attr_macros(&self) -> bool { - self.root_config.global.procMacro_enable - && self.root_config.global.procMacro_attributes_enable + self.root_config.global.0.procMacro_enable + && self.root_config.global.0.procMacro_attributes_enable } pub fn rediscover_workspaces(&mut self) { @@ -1004,10 +1036,11 @@ impl Config { .map(AbsPathBuf::assert) .collect(); patch_old_style::patch_json_for_outdated_configs(&mut json); - self.root_config.global = GlobalConfigData::from_json(json, &mut errors); + self.root_config.global = + RootGlobalConfigData(GlobalConfigData::from_json(json, &mut errors)); tracing::debug!("deserialized config data: {:#?}", self.root_config.global); self.snippets.clear(); - for (name, def) in self.root_config.global.completion_snippets_custom.iter() { + for (name, def) in self.root_config.global.0.completion_snippets_custom.iter() { if def.prefix.is_empty() && def.postfix.is_empty() { continue; } @@ -1045,7 +1078,7 @@ impl Config { fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) { use serde::de::Error; - if self.root_config.global.check_command.is_empty() { + if self.root_config.global.0.check_command.is_empty() { error_sink.push(( "/check/command".to_string(), serde_json::Error::custom("expected a non-empty string"), @@ -1072,14 +1105,15 @@ impl Config { impl Config { pub fn has_linked_projects(&self) -> bool { - !self.root_config.global.linkedProjects.is_empty() + !self.root_config.global.0.linkedProjects.is_empty() } pub fn linked_projects(&self) -> Vec { - match self.root_config.global.linkedProjects.as_slice() { + match self.root_config.global.0.linkedProjects.as_slice() { [] => { let exclude_dirs: Vec<_> = self .root_config .global + .0 .files_excludeDirs .iter() .map(|p| self.root_path.join(p)) @@ -1120,7 +1154,7 @@ impl Config { .map(ManifestOrProjectJson::ProjectJson) .collect::>(); - self.root_config.global.linkedProjects.append(&mut linked_projects); + self.root_config.global.0.linkedProjects.append(&mut linked_projects); } pub fn did_save_text_document_dynamic_registration(&self) -> bool { @@ -1135,7 +1169,7 @@ impl Config { } pub fn prefill_caches(&self) -> bool { - self.root_config.global.cachePriming_enable + self.root_config.global.0.cachePriming_enable } pub fn location_link(&self) -> bool { @@ -1268,67 +1302,68 @@ impl Config { } pub fn publish_diagnostics(&self) -> bool { - self.root_config.global.diagnostics_enable + self.root_config.global.0.diagnostics_enable } pub fn diagnostics_map(&self) -> DiagnosticsMapConfig { DiagnosticsMapConfig { - remap_prefix: self.root_config.global.diagnostics_remapPrefix.clone(), - warnings_as_info: self.root_config.global.diagnostics_warningsAsInfo.clone(), - warnings_as_hint: self.root_config.global.diagnostics_warningsAsHint.clone(), - check_ignore: self.root_config.global.check_ignore.clone(), + remap_prefix: self.root_config.global.0.diagnostics_remapPrefix.clone(), + warnings_as_info: self.root_config.global.0.diagnostics_warningsAsInfo.clone(), + warnings_as_hint: self.root_config.global.0.diagnostics_warningsAsHint.clone(), + check_ignore: self.root_config.global.0.check_ignore.clone(), } } pub fn extra_args(&self) -> &Vec { - &self.root_config.global.cargo_extraArgs + &self.root_config.global.0.cargo_extraArgs } pub fn extra_env(&self) -> &FxHashMap { - &self.root_config.global.cargo_extraEnv + &self.root_config.global.0.cargo_extraEnv } pub fn check_extra_args(&self) -> Vec { let mut extra_args = self.extra_args().clone(); - extra_args.extend_from_slice(&self.root_config.global.check_extraArgs); + extra_args.extend_from_slice(&self.root_config.global.0.check_extraArgs); extra_args } pub fn check_extra_env(&self) -> FxHashMap { - let mut extra_env = self.root_config.global.cargo_extraEnv.clone(); - extra_env.extend(self.root_config.global.check_extraEnv.clone()); + let mut extra_env = self.root_config.global.0.cargo_extraEnv.clone(); + extra_env.extend(self.root_config.global.0.check_extraEnv.clone()); extra_env } pub fn lru_parse_query_capacity(&self) -> Option { - self.root_config.global.lru_capacity + self.root_config.global.0.lru_capacity } pub fn lru_query_capacities(&self) -> Option<&FxHashMap, usize>> { self.root_config .global + .0 .lru_query_capacities .is_empty() .not() - .then(|| &self.root_config.global.lru_query_capacities) + .then(|| &self.root_config.global.0.lru_query_capacities) } pub fn proc_macro_srv(&self) -> Option { - let path = self.root_config.global.procMacro_server.clone()?; + let path = self.root_config.global.0.procMacro_server.clone()?; Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(&path))) } pub fn dummy_replacements(&self) -> &FxHashMap, Box<[Box]>> { - &self.root_config.global.procMacro_ignored + &self.root_config.global.0.procMacro_ignored } pub fn expand_proc_macros(&self) -> bool { - self.root_config.global.procMacro_enable + self.root_config.global.0.procMacro_enable } pub fn files(&self) -> FilesConfig { FilesConfig { - watcher: match self.root_config.global.files_watcher { + watcher: match self.root_config.global.0.files_watcher { FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => { FilesWatcher::Client } @@ -1337,6 +1372,7 @@ impl Config { exclude: self .root_config .global + .0 .files_excludeDirs .iter() .map(|it| self.root_path.join(it)) @@ -1346,28 +1382,28 @@ impl Config { pub fn notifications(&self) -> NotificationsConfig { NotificationsConfig { - cargo_toml_not_found: self.root_config.global.notifications_cargoTomlNotFound, + cargo_toml_not_found: self.root_config.global.0.notifications_cargoTomlNotFound, } } pub fn cargo_autoreload(&self) -> bool { - self.root_config.global.cargo_autoreload + self.root_config.global.0.cargo_autoreload } pub fn run_build_scripts(&self) -> bool { - self.root_config.global.cargo_buildScripts_enable - || self.root_config.global.procMacro_enable + self.root_config.global.0.cargo_buildScripts_enable + || self.root_config.global.0.procMacro_enable } pub fn cargo(&self) -> CargoConfig { - let rustc_source = self.root_config.global.rustc_source.as_ref().map(|rustc_src| { + let rustc_source = self.root_config.global.0.rustc_source.as_ref().map(|rustc_src| { if rustc_src == "discover" { RustLibSource::Discover } else { RustLibSource::Path(self.root_path.join(rustc_src)) } }); - let sysroot = self.root_config.global.cargo_sysroot.as_ref().map(|sysroot| { + let sysroot = self.root_config.global.0.cargo_sysroot.as_ref().map(|sysroot| { if sysroot == "discover" { RustLibSource::Discover } else { @@ -1377,19 +1413,20 @@ impl Config { let sysroot_src = self .root_config .global + .0 .cargo_sysrootSrc .as_ref() .map(|sysroot| self.root_path.join(sysroot)); CargoConfig { - features: match &self.root_config.global.cargo_features { + features: match &self.root_config.global.0.cargo_features { CargoFeaturesDef::All => CargoFeatures::All, CargoFeaturesDef::Selected(features) => CargoFeatures::Selected { features: features.clone(), - no_default_features: self.root_config.global.cargo_noDefaultFeatures, + no_default_features: self.root_config.global.0.cargo_noDefaultFeatures, }, }, - target: self.root_config.global.cargo_target.clone(), + target: self.root_config.global.0.cargo_target.clone(), sysroot, sysroot_src, rustc_source, @@ -1397,6 +1434,7 @@ impl Config { global: CfgDiff::new( self.root_config .global + .0 .cargo_cfgs .iter() .map(|(key, val)| { @@ -1413,6 +1451,7 @@ impl Config { selective: self .root_config .global + .0 .cargo_unsetTest .iter() .map(|it| { @@ -1423,13 +1462,25 @@ impl Config { }) .collect(), }, - wrap_rustc_in_build_scripts: self.root_config.global.cargo_buildScripts_useRustcWrapper, - invocation_strategy: match self.root_config.global.cargo_buildScripts_invocationStrategy + wrap_rustc_in_build_scripts: self + .root_config + .global + .0 + .cargo_buildScripts_useRustcWrapper, + invocation_strategy: match self + .root_config + .global + .0 + .cargo_buildScripts_invocationStrategy { InvocationStrategy::Once => project_model::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace, }, - invocation_location: match self.root_config.global.cargo_buildScripts_invocationLocation + invocation_location: match self + .root_config + .global + .0 + .cargo_buildScripts_invocationLocation { InvocationLocation::Root => { project_model::InvocationLocation::Root(self.root_path.clone()) @@ -1439,30 +1490,31 @@ impl Config { run_build_script_command: self .root_config .global + .0 .cargo_buildScripts_overrideCommand .clone(), - extra_args: self.root_config.global.cargo_extraArgs.clone(), - extra_env: self.root_config.global.cargo_extraEnv.clone(), + extra_args: self.root_config.global.0.cargo_extraArgs.clone(), + extra_env: self.root_config.global.0.cargo_extraEnv.clone(), target_dir: self.target_dir_from_config(), } } pub fn rustfmt(&self) -> RustfmtConfig { - match &self.root_config.global.rustfmt_overrideCommand { + match &self.root_config.global.0.rustfmt_overrideCommand { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); RustfmtConfig::CustomCommand { command, args } } Some(_) | None => RustfmtConfig::Rustfmt { - extra_args: self.root_config.global.rustfmt_extraArgs.clone(), - enable_range_formatting: self.root_config.global.rustfmt_rangeFormatting_enable, + extra_args: self.root_config.global.0.rustfmt_extraArgs.clone(), + enable_range_formatting: self.root_config.global.0.rustfmt_rangeFormatting_enable, }, } } pub fn flycheck(&self) -> FlycheckConfig { - match &self.root_config.global.check_overrideCommand { + match &self.root_config.global.0.check_overrideCommand { Some(args) if !args.is_empty() => { let mut args = args.clone(); let command = args.remove(0); @@ -1470,13 +1522,13 @@ impl Config { command, args, extra_env: self.check_extra_env(), - invocation_strategy: match self.root_config.global.check_invocationStrategy { + invocation_strategy: match self.root_config.global.0.check_invocationStrategy { InvocationStrategy::Once => flycheck::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => { flycheck::InvocationStrategy::PerWorkspace } }, - invocation_location: match self.root_config.global.check_invocationLocation { + invocation_location: match self.root_config.global.0.check_invocationLocation { InvocationLocation::Root => { flycheck::InvocationLocation::Root(self.root_path.clone()) } @@ -1485,10 +1537,11 @@ impl Config { } } Some(_) | None => FlycheckConfig::CargoCommand { - command: self.root_config.global.check_command.clone(), + command: self.root_config.global.0.check_command.clone(), target_triples: self .root_config .global + .0 .check_targets .clone() .and_then(|targets| match &targets.0[..] { @@ -1496,28 +1549,31 @@ impl Config { targets => Some(targets.into()), }) .unwrap_or_else(|| { - self.root_config.global.cargo_target.clone().into_iter().collect() + self.root_config.global.0.cargo_target.clone().into_iter().collect() }), - all_targets: self.root_config.global.check_allTargets, + all_targets: self.root_config.global.0.check_allTargets, no_default_features: self .root_config .global + .0 .check_noDefaultFeatures - .unwrap_or(self.root_config.global.cargo_noDefaultFeatures), + .unwrap_or(self.root_config.global.0.cargo_noDefaultFeatures), all_features: matches!( self.root_config .global + .0 .check_features .as_ref() - .unwrap_or(&self.root_config.global.cargo_features), + .unwrap_or(&self.root_config.global.0.cargo_features), CargoFeaturesDef::All ), features: match self .root_config .global + .0 .check_features .clone() - .unwrap_or_else(|| self.root_config.global.cargo_features.clone()) + .unwrap_or_else(|| self.root_config.global.0.cargo_features.clone()) { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, @@ -1531,7 +1587,7 @@ impl Config { } fn target_dir_from_config(&self) -> Option { - self.root_config.global.rust_analyzerTargetDir.as_ref().and_then(|target_dir| { + self.root_config.global.0.rust_analyzerTargetDir.as_ref().and_then(|target_dir| { match target_dir { TargetDirectory::UseSubdirectory(yes) if *yes => { Some(PathBuf::from("target/rust-analyzer")) @@ -1543,13 +1599,13 @@ impl Config { } pub fn check_on_save(&self) -> bool { - self.root_config.global.checkOnSave + self.root_config.global.0.checkOnSave } pub fn runnables(&self) -> RunnablesConfig { RunnablesConfig { - override_cargo: self.root_config.global.runnables_command.clone(), - cargo_extra_args: self.root_config.global.runnables_extraArgs.clone(), + override_cargo: self.root_config.global.0.runnables_command.clone(), + cargo_extra_args: self.root_config.global.0.runnables_extraArgs.clone(), } } @@ -1567,11 +1623,12 @@ impl Config { .collect::>(); InlayHintsConfig { - render_colons: self.root_config.global.inlayHints_renderColons, - type_hints: self.root_config.global.inlayHints_typeHints_enable, - parameter_hints: self.root_config.global.inlayHints_parameterHints_enable, - chaining_hints: self.root_config.global.inlayHints_chainingHints_enable, - discriminant_hints: match self.root_config.global.inlayHints_discriminantHints_enable { + render_colons: self.root_config.global.0.inlayHints_renderColons, + type_hints: self.root_config.global.0.inlayHints_typeHints_enable, + parameter_hints: self.root_config.global.0.inlayHints_parameterHints_enable, + chaining_hints: self.root_config.global.0.inlayHints_chainingHints_enable, + discriminant_hints: match self.root_config.global.0.inlayHints_discriminantHints_enable + { DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless, @@ -1579,6 +1636,7 @@ impl Config { closure_return_type_hints: match self .root_config .global + .0 .inlayHints_closureReturnTypeHints_enable { ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always, @@ -1588,6 +1646,7 @@ impl Config { lifetime_elision_hints: match self .root_config .global + .0 .inlayHints_lifetimeElisionHints_enable { LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always, @@ -1597,26 +1656,29 @@ impl Config { hide_named_constructor_hints: self .root_config .global + .0 .inlayHints_typeHints_hideNamedConstructor, hide_closure_initialization_hints: self .root_config .global + .0 .inlayHints_typeHints_hideClosureInitialization, - closure_style: match self.root_config.global.inlayHints_closureStyle { + closure_style: match self.root_config.global.0.inlayHints_closureStyle { ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn, ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation, ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId, ClosureStyle::Hide => hir::ClosureStyle::Hide, }, - closure_capture_hints: self.root_config.global.inlayHints_closureCaptureHints_enable, + closure_capture_hints: self.root_config.global.0.inlayHints_closureCaptureHints_enable, adjustment_hints: match self .root_config .global + .0 .inlayHints_expressionAdjustmentHints_enable { AdjustmentHintsDef::Always => ide::AdjustmentHints::Always, AdjustmentHintsDef::Never => { - match self.root_config.global.inlayHints_reborrowHints_enable { + match self.root_config.global.0.inlayHints_reborrowHints_enable { ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { ide::AdjustmentHints::ReborrowOnly } @@ -1628,6 +1690,7 @@ impl Config { adjustment_hints_mode: match self .root_config .global + .0 .inlayHints_expressionAdjustmentHints_mode { AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix, @@ -1638,19 +1701,22 @@ impl Config { adjustment_hints_hide_outside_unsafe: self .root_config .global + .0 .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe, - binding_mode_hints: self.root_config.global.inlayHints_bindingModeHints_enable, + binding_mode_hints: self.root_config.global.0.inlayHints_bindingModeHints_enable, param_names_for_lifetime_elision_hints: self .root_config .global + .0 .inlayHints_lifetimeElisionHints_useParameterNames, - max_length: self.root_config.global.inlayHints_maxLength, + max_length: self.root_config.global.0.inlayHints_maxLength, closing_brace_hints_min_lines: if self .root_config .global + .0 .inlayHints_closingBraceHints_enable { - Some(self.root_config.global.inlayHints_closingBraceHints_minLines) + Some(self.root_config.global.0.inlayHints_closingBraceHints_minLines) } else { None }, @@ -1665,7 +1731,7 @@ impl Config { } pub fn find_all_refs_exclude_imports(&self) -> bool { - self.root_config.global.references_excludeImports + self.root_config.global.0.references_excludeImports } pub fn snippet_cap(&self) -> bool { @@ -1675,70 +1741,76 @@ impl Config { pub fn call_info(&self) -> CallInfoConfig { CallInfoConfig { params_only: matches!( - self.root_config.global.signatureInfo_detail, + self.root_config.global.0.signatureInfo_detail, SignatureDetail::Parameters ), - docs: self.root_config.global.signatureInfo_documentation_enable, + docs: self.root_config.global.0.signatureInfo_documentation_enable, } } pub fn lens(&self) -> LensConfig { LensConfig { - run: self.root_config.global.lens_enable && self.root_config.global.lens_run_enable, - debug: self.root_config.global.lens_enable && self.root_config.global.lens_debug_enable, - interpret: self.root_config.global.lens_enable - && self.root_config.global.lens_run_enable - && self.root_config.global.interpret_tests, - implementations: self.root_config.global.lens_enable - && self.root_config.global.lens_implementations_enable, - method_refs: self.root_config.global.lens_enable - && self.root_config.global.lens_references_method_enable, - refs_adt: self.root_config.global.lens_enable - && self.root_config.global.lens_references_adt_enable, - refs_trait: self.root_config.global.lens_enable - && self.root_config.global.lens_references_trait_enable, - enum_variant_refs: self.root_config.global.lens_enable - && self.root_config.global.lens_references_enumVariant_enable, - location: self.root_config.global.lens_location, + run: self.root_config.global.0.lens_enable && self.root_config.global.0.lens_run_enable, + debug: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_debug_enable, + interpret: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_run_enable + && self.root_config.global.0.interpret_tests, + implementations: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_implementations_enable, + method_refs: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_references_method_enable, + refs_adt: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_references_adt_enable, + refs_trait: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_references_trait_enable, + enum_variant_refs: self.root_config.global.0.lens_enable + && self.root_config.global.0.lens_references_enumVariant_enable, + location: self.root_config.global.0.lens_location, } } pub fn hover_actions(&self) -> HoverActionsConfig { let enable = - self.experimental("hoverActions") && self.root_config.global.hover_actions_enable; + self.experimental("hoverActions") && self.root_config.global.0.hover_actions_enable; HoverActionsConfig { - implementations: enable && self.root_config.global.hover_actions_implementations_enable, - references: enable && self.root_config.global.hover_actions_references_enable, - run: enable && self.root_config.global.hover_actions_run_enable, - debug: enable && self.root_config.global.hover_actions_debug_enable, - goto_type_def: enable && self.root_config.global.hover_actions_gotoTypeDef_enable, + implementations: enable + && self.root_config.global.0.hover_actions_implementations_enable, + references: enable && self.root_config.global.0.hover_actions_references_enable, + run: enable && self.root_config.global.0.hover_actions_run_enable, + debug: enable && self.root_config.global.0.hover_actions_debug_enable, + goto_type_def: enable && self.root_config.global.0.hover_actions_gotoTypeDef_enable, } } pub fn highlighting_non_standard_tokens(&self) -> bool { - self.root_config.global.semanticHighlighting_nonStandardTokens + self.root_config.global.0.semanticHighlighting_nonStandardTokens } pub fn highlighting_config(&self) -> HighlightConfig { HighlightConfig { - strings: self.root_config.global.semanticHighlighting_strings_enable, - punctuation: self.root_config.global.semanticHighlighting_punctuation_enable, + strings: self.root_config.global.0.semanticHighlighting_strings_enable, + punctuation: self.root_config.global.0.semanticHighlighting_punctuation_enable, specialize_punctuation: self .root_config .global + .0 .semanticHighlighting_punctuation_specialization_enable, macro_bang: self .root_config .global + .0 .semanticHighlighting_punctuation_separate_macro_bang, - operator: self.root_config.global.semanticHighlighting_operator_enable, + operator: self.root_config.global.0.semanticHighlighting_operator_enable, specialize_operator: self .root_config .global + .0 .semanticHighlighting_operator_specialization_enable, inject_doc_comment: self .root_config .global + .0 .semanticHighlighting_doc_comment_inject_enable, syntactic_name_ref_highlighting: false, } @@ -1751,16 +1823,16 @@ impl Config { MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal, }; HoverConfig { - links_in_hover: self.root_config.global.hover_links_enable, - memory_layout: self.root_config.global.hover_memoryLayout_enable.then_some( + links_in_hover: self.root_config.global.0.hover_links_enable, + memory_layout: self.root_config.global.0.hover_memoryLayout_enable.then_some( MemoryLayoutHoverConfig { - size: self.root_config.global.hover_memoryLayout_size.map(mem_kind), - offset: self.root_config.global.hover_memoryLayout_offset.map(mem_kind), - alignment: self.root_config.global.hover_memoryLayout_alignment.map(mem_kind), - niches: self.root_config.global.hover_memoryLayout_niches.unwrap_or_default(), + size: self.root_config.global.0.hover_memoryLayout_size.map(mem_kind), + offset: self.root_config.global.0.hover_memoryLayout_offset.map(mem_kind), + alignment: self.root_config.global.0.hover_memoryLayout_alignment.map(mem_kind), + niches: self.root_config.global.0.hover_memoryLayout_niches.unwrap_or_default(), }, ), - documentation: self.root_config.global.hover_documentation_enable, + documentation: self.root_config.global.0.hover_documentation_enable, format: { let is_markdown = try_or_def!(self .caps @@ -1778,23 +1850,23 @@ impl Config { HoverDocFormat::PlainText } }, - keywords: self.root_config.global.hover_documentation_keywords_enable, + keywords: self.root_config.global.0.hover_documentation_keywords_enable, } } pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig { WorkspaceSymbolConfig { - search_scope: match self.root_config.global.workspace_symbol_search_scope { + search_scope: match self.root_config.global.0.workspace_symbol_search_scope { WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace, WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => { WorkspaceSymbolSearchScope::WorkspaceAndDependencies } }, - search_kind: match self.root_config.global.workspace_symbol_search_kind { + search_kind: match self.root_config.global.0.workspace_symbol_search_kind { WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes, WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols, }, - search_limit: self.root_config.global.workspace_symbol_search_limit, + search_limit: self.root_config.global.0.workspace_symbol_search_limit, } } @@ -1828,7 +1900,7 @@ impl Config { try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null); let commands: Option = serde_json::from_value(commands.clone()).ok(); - let force = commands.is_none() && self.root_config.global.lens_forceCustomCommands; + let force = commands.is_none() && self.root_config.global.0.lens_forceCustomCommands; let commands = commands.map(|it| it.commands).unwrap_or_default(); let get = |name: &str| commands.iter().any(|it| it == name) || force; @@ -1844,16 +1916,16 @@ impl Config { pub fn highlight_related(&self) -> HighlightRelatedConfig { HighlightRelatedConfig { - references: self.root_config.global.highlightRelated_references_enable, - break_points: self.root_config.global.highlightRelated_breakPoints_enable, - exit_points: self.root_config.global.highlightRelated_exitPoints_enable, - yield_points: self.root_config.global.highlightRelated_yieldPoints_enable, - closure_captures: self.root_config.global.highlightRelated_closureCaptures_enable, + references: self.root_config.global.0.highlightRelated_references_enable, + break_points: self.root_config.global.0.highlightRelated_breakPoints_enable, + exit_points: self.root_config.global.0.highlightRelated_exitPoints_enable, + yield_points: self.root_config.global.0.highlightRelated_yieldPoints_enable, + closure_captures: self.root_config.global.0.highlightRelated_closureCaptures_enable, } } pub fn prime_caches_num_threads(&self) -> u8 { - match self.root_config.global.cachePriming_numThreads { + match self.root_config.global.0.cachePriming_numThreads { 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX), n => n, } @@ -1862,12 +1934,13 @@ impl Config { pub fn main_loop_num_threads(&self) -> usize { self.root_config .global + .0 .numThreads .unwrap_or(num_cpus::get_physical().try_into().unwrap_or(1)) } pub fn typing_autoclose_angle(&self) -> bool { - self.root_config.global.typing_autoClosingAngleBrackets_enable + self.root_config.global.0.typing_autoClosingAngleBrackets_enable } // FIXME: VSCode seems to work wrong sometimes, see https://github.com/microsoft/vscode/issues/193124 @@ -2847,7 +2920,7 @@ mod tests { "rust": { "analyzerTargetDir": null } })) .unwrap(); - assert_eq!(config.root_config.global.rust_analyzerTargetDir, None); + assert_eq!(config.root_config.global.0.rust_analyzerTargetDir, None); assert!( matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == None) ); @@ -2867,7 +2940,7 @@ mod tests { })) .unwrap(); assert_eq!( - config.root_config.global.rust_analyzerTargetDir, + config.root_config.global.0.rust_analyzerTargetDir, Some(TargetDirectory::UseSubdirectory(true)) ); assert!( @@ -2889,7 +2962,7 @@ mod tests { })) .unwrap(); assert_eq!( - config.root_config.global.rust_analyzerTargetDir, + config.root_config.global.0.rust_analyzerTargetDir, Some(TargetDirectory::Directory(PathBuf::from("other_folder"))) ); assert!( From 259e233ab957e1f7e16db77668007610bc3eb90d Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 02:23:32 +0200 Subject: [PATCH 05/52] Declare inlayHints_* as local --- crates/rust-analyzer/src/config.rs | 292 ++++++++----------- crates/rust-analyzer/src/handlers/request.rs | 5 +- 2 files changed, 130 insertions(+), 167 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e749645b7c45..2d0cfc8936b7 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -344,50 +344,6 @@ config_data! { /// How to render the size information in a memory layout hover. hover_memoryLayout_size: Option = "\"both\"", - /// Whether to show inlay type hints for binding modes. - inlayHints_bindingModeHints_enable: bool = "false", - /// Whether to show inlay type hints for method chains. - inlayHints_chainingHints_enable: bool = "true", - /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to. - inlayHints_closingBraceHints_enable: bool = "true", - /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1 - /// to always show them). - inlayHints_closingBraceHints_minLines: usize = "25", - /// Whether to show inlay hints for closure captures. - inlayHints_closureCaptureHints_enable: bool = "false", - /// Whether to show inlay type hints for return types of closures. - inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"", - /// Closure notation in type and chaining inlay hints. - inlayHints_closureStyle: ClosureStyle = "\"impl_fn\"", - /// Whether to show enum variant discriminant hints. - inlayHints_discriminantHints_enable: DiscriminantHintsDef = "\"never\"", - /// Whether to show inlay hints for type adjustments. - inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"", - /// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks. - inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false", - /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc). - inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"", - /// Whether to show inlay type hints for elided lifetimes in function signatures. - inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"", - /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. - inlayHints_lifetimeElisionHints_useParameterNames: bool = "false", - /// Maximum length for inlay hints. Set to null to have an unlimited length. - inlayHints_maxLength: Option = "25", - /// Whether to show function parameter name inlay hints at the call - /// site. - inlayHints_parameterHints_enable: bool = "true", - /// Whether to show inlay hints for compiler inserted reborrows. - /// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#. - inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"", - /// Whether to render leading colons for type hints, and trailing colons for parameter hints. - inlayHints_renderColons: bool = "true", - /// Whether to show inlay type hints for variables. - inlayHints_typeHints_enable: bool = "true", - /// Whether to hide inlay type hints for `let` statements that initialize to a closure. - /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`. - inlayHints_typeHints_hideClosureInitialization: bool = "false", - /// Whether to hide inlay type hints for constructors. - inlayHints_typeHints_hideNamedConstructor: bool = "false", /// Enables the experimental support for interpreting tests. interpret_tests: bool = "false", @@ -551,6 +507,51 @@ config_data! { config_data! { struct LocalConfigData { + /// Whether to show inlay type hints for binding modes. + inlayHints_bindingModeHints_enable: bool = "false", + /// Whether to show inlay type hints for method chains. + inlayHints_chainingHints_enable: bool = "true", + /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to. + inlayHints_closingBraceHints_enable: bool = "true", + /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1 + /// to always show them). + inlayHints_closingBraceHints_minLines: usize = "25", + /// Whether to show inlay hints for closure captures. + inlayHints_closureCaptureHints_enable: bool = "false", + /// Whether to show inlay type hints for return types of closures. + inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"", + /// Closure notation in type and chaining inlay hints. + inlayHints_closureStyle: ClosureStyle = "\"impl_fn\"", + /// Whether to show enum variant discriminant hints. + inlayHints_discriminantHints_enable: DiscriminantHintsDef = "\"never\"", + /// Whether to show inlay hints for type adjustments. + inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"", + /// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks. + inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false", + /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc). + inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"", + /// Whether to show inlay type hints for elided lifetimes in function signatures. + inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"", + /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. + inlayHints_lifetimeElisionHints_useParameterNames: bool = "false", + /// Maximum length for inlay hints. Set to null to have an unlimited length. + inlayHints_maxLength: Option = "25", + /// Whether to show function parameter name inlay hints at the call + /// site. + inlayHints_parameterHints_enable: bool = "true", + /// Whether to show inlay hints for compiler inserted reborrows. + /// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#. + inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"", + /// Whether to render leading colons for type hints, and trailing colons for parameter hints. + inlayHints_renderColons: bool = "true", + /// Whether to show inlay type hints for variables. + inlayHints_typeHints_enable: bool = "true", + /// Whether to hide inlay type hints for `let` statements that initialize to a closure. + /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`. + inlayHints_typeHints_hideClosureInitialization: bool = "false", + /// Whether to hide inlay type hints for constructors. + inlayHints_typeHints_hideNamedConstructor: bool = "false", + /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file. imports_granularity_enforce: bool = "false", /// How imports should be grouped into use statements. @@ -713,6 +714,88 @@ impl<'a> LocalConfigView<'a> { skip_glob_imports: !self.local.imports_merge_glob, } } + pub fn inlay_hints(&self) -> InlayHintsConfig { + let client_capability_fields = self + .caps + .text_document + .as_ref() + .and_then(|text| text.inlay_hint.as_ref()) + .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref()) + .map(|inlay_resolve| inlay_resolve.properties.iter()) + .into_iter() + .flatten() + .cloned() + .collect::>(); + + InlayHintsConfig { + render_colons: self.local.inlayHints_renderColons, + type_hints: self.local.inlayHints_typeHints_enable, + parameter_hints: self.local.inlayHints_parameterHints_enable, + chaining_hints: self.local.inlayHints_chainingHints_enable, + discriminant_hints: match self.local.inlayHints_discriminantHints_enable { + DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, + DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, + DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless, + }, + closure_return_type_hints: match self.local.inlayHints_closureReturnTypeHints_enable { + ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always, + ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never, + ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock, + }, + lifetime_elision_hints: match self.local.inlayHints_lifetimeElisionHints_enable { + LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always, + LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never, + LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial, + }, + hide_named_constructor_hints: self.local.inlayHints_typeHints_hideNamedConstructor, + hide_closure_initialization_hints: self + .local + .inlayHints_typeHints_hideClosureInitialization, + closure_style: match self.local.inlayHints_closureStyle { + ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn, + ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation, + ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId, + ClosureStyle::Hide => hir::ClosureStyle::Hide, + }, + closure_capture_hints: self.local.inlayHints_closureCaptureHints_enable, + adjustment_hints: match self.local.inlayHints_expressionAdjustmentHints_enable { + AdjustmentHintsDef::Always => ide::AdjustmentHints::Always, + AdjustmentHintsDef::Never => match self.local.inlayHints_reborrowHints_enable { + ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { + ide::AdjustmentHints::ReborrowOnly + } + ReborrowHintsDef::Never => ide::AdjustmentHints::Never, + }, + AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly, + }, + adjustment_hints_mode: match self.local.inlayHints_expressionAdjustmentHints_mode { + AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix, + AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix, + AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix, + AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix, + }, + adjustment_hints_hide_outside_unsafe: self + .local + .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe, + binding_mode_hints: self.local.inlayHints_bindingModeHints_enable, + param_names_for_lifetime_elision_hints: self + .local + .inlayHints_lifetimeElisionHints_useParameterNames, + max_length: self.local.inlayHints_maxLength, + closing_brace_hints_min_lines: if self.local.inlayHints_closingBraceHints_enable { + Some(self.local.inlayHints_closingBraceHints_minLines) + } else { + None + }, + fields_to_resolve: InlayFieldsToResolve { + resolve_text_edits: client_capability_fields.contains("textEdits"), + resolve_hint_tooltip: client_capability_fields.contains("tooltip"), + resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), + resolve_label_location: client_capability_fields.contains("label.location"), + resolve_label_command: client_capability_fields.contains("label.command"), + }, + } + } pub fn expand_proc_attr_macros(&self) -> bool { self.global.0.procMacro_enable && self.global.0.procMacro_attributes_enable @@ -1609,127 +1692,6 @@ impl Config { } } - pub fn inlay_hints(&self) -> InlayHintsConfig { - let client_capability_fields = self - .caps - .text_document - .as_ref() - .and_then(|text| text.inlay_hint.as_ref()) - .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref()) - .map(|inlay_resolve| inlay_resolve.properties.iter()) - .into_iter() - .flatten() - .cloned() - .collect::>(); - - InlayHintsConfig { - render_colons: self.root_config.global.0.inlayHints_renderColons, - type_hints: self.root_config.global.0.inlayHints_typeHints_enable, - parameter_hints: self.root_config.global.0.inlayHints_parameterHints_enable, - chaining_hints: self.root_config.global.0.inlayHints_chainingHints_enable, - discriminant_hints: match self.root_config.global.0.inlayHints_discriminantHints_enable - { - DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, - DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, - DiscriminantHintsDef::Fieldless => ide::DiscriminantHints::Fieldless, - }, - closure_return_type_hints: match self - .root_config - .global - .0 - .inlayHints_closureReturnTypeHints_enable - { - ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always, - ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never, - ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock, - }, - lifetime_elision_hints: match self - .root_config - .global - .0 - .inlayHints_lifetimeElisionHints_enable - { - LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always, - LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never, - LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial, - }, - hide_named_constructor_hints: self - .root_config - .global - .0 - .inlayHints_typeHints_hideNamedConstructor, - hide_closure_initialization_hints: self - .root_config - .global - .0 - .inlayHints_typeHints_hideClosureInitialization, - closure_style: match self.root_config.global.0.inlayHints_closureStyle { - ClosureStyle::ImplFn => hir::ClosureStyle::ImplFn, - ClosureStyle::RustAnalyzer => hir::ClosureStyle::RANotation, - ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId, - ClosureStyle::Hide => hir::ClosureStyle::Hide, - }, - closure_capture_hints: self.root_config.global.0.inlayHints_closureCaptureHints_enable, - adjustment_hints: match self - .root_config - .global - .0 - .inlayHints_expressionAdjustmentHints_enable - { - AdjustmentHintsDef::Always => ide::AdjustmentHints::Always, - AdjustmentHintsDef::Never => { - match self.root_config.global.0.inlayHints_reborrowHints_enable { - ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => { - ide::AdjustmentHints::ReborrowOnly - } - ReborrowHintsDef::Never => ide::AdjustmentHints::Never, - } - } - AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly, - }, - adjustment_hints_mode: match self - .root_config - .global - .0 - .inlayHints_expressionAdjustmentHints_mode - { - AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix, - AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix, - AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix, - AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix, - }, - adjustment_hints_hide_outside_unsafe: self - .root_config - .global - .0 - .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe, - binding_mode_hints: self.root_config.global.0.inlayHints_bindingModeHints_enable, - param_names_for_lifetime_elision_hints: self - .root_config - .global - .0 - .inlayHints_lifetimeElisionHints_useParameterNames, - max_length: self.root_config.global.0.inlayHints_maxLength, - closing_brace_hints_min_lines: if self - .root_config - .global - .0 - .inlayHints_closingBraceHints_enable - { - Some(self.root_config.global.0.inlayHints_closingBraceHints_minLines) - } else { - None - }, - fields_to_resolve: InlayFieldsToResolve { - resolve_text_edits: client_capability_fields.contains("textEdits"), - resolve_hint_tooltip: client_capability_fields.contains("tooltip"), - resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), - resolve_label_location: client_capability_fields.contains("label.location"), - resolve_label_command: client_capability_fields.contains("label.command"), - }, - } - } - pub fn find_all_refs_exclude_imports(&self) -> bool { self.root_config.global.0.references_excludeImports } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 51fd16427e00..09cab755710c 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -1411,7 +1411,7 @@ pub(crate) fn handle_inlay_hints( params.range, )?; let line_index = snap.file_line_index(file_id)?; - let inlay_hints_config = snap.config.inlay_hints(); + let inlay_hints_config = snap.config.localize_by_file_id(file_id).inlay_hints(); Ok(Some( snap.analysis .inlay_hints(&inlay_hints_config, file_id, Some(range))? @@ -1455,7 +1455,8 @@ pub(crate) fn handle_inlay_hints_resolve( range_start.checked_sub(1.into()).unwrap_or(range_start), range_end.checked_add(1.into()).unwrap_or(range_end), ); - let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(); + let mut forced_resolve_inlay_hints_config = + snap.config.localize_by_file_id(file_id).inlay_hints(); forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty(); let resolve_hints = snap.analysis.inlay_hints( &forced_resolve_inlay_hints_config, From 3e17039fe377aa1d9ed199632d18fab3b4dc4fb7 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 02:45:30 +0200 Subject: [PATCH 06/52] Declare highlightRelated_* as local --- crates/rust-analyzer/src/config.rs | 156 +++++++++---------- crates/rust-analyzer/src/handlers/request.rs | 5 +- 2 files changed, 82 insertions(+), 79 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 2d0cfc8936b7..b7c6a44454a3 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -297,16 +297,6 @@ config_data! { /// Controls file watching implementation. files_watcher: FilesWatcherDef = "\"client\"", - /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. - highlightRelated_breakPoints_enable: bool = "true", - /// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure. - highlightRelated_closureCaptures_enable: bool = "true", - /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`). - highlightRelated_exitPoints_enable: bool = "true", - /// Enables highlighting of related references while the cursor is on any identifier. - highlightRelated_references_enable: bool = "true", - /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords. - highlightRelated_yieldPoints_enable: bool = "true", /// Whether to show `Debug` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. @@ -506,6 +496,16 @@ config_data! { config_data! { struct LocalConfigData { + /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. + highlightRelated_breakPoints_enable: bool = "true", + /// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure. + highlightRelated_closureCaptures_enable: bool = "true", + /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`). + highlightRelated_exitPoints_enable: bool = "true", + /// Enables highlighting of related references while the cursor is on any identifier. + highlightRelated_references_enable: bool = "true", + /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords. + highlightRelated_yieldPoints_enable: bool = "true", /// Whether to show inlay type hints for binding modes. inlayHints_bindingModeHints_enable: bool = "false", @@ -670,6 +670,46 @@ pub struct LocalConfigView<'a> { } impl<'a> LocalConfigView<'a> { + pub fn assist(&self) -> AssistConfig { + AssistConfig { + snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), + allowed: None, + insert_use: self.insert_use_config(), + prefer_no_std: self.local.imports_prefer_no_std, + assist_emit_must_use: self.global.0.assist_emitMustUse, + } + } + + pub fn completion(&self) -> CompletionConfig { + CompletionConfig { + enable_postfix_completions: self.global.0.completion_postfix_enable, + enable_imports_on_the_fly: self.global.0.completion_autoimport_enable + && completion_item_edit_resolve(&self.caps), + enable_self_on_the_fly: self.global.0.completion_autoself_enable, + enable_private_editable: self.global.0.completion_privateEditable_enable, + full_function_signatures: self.global.0.completion_fullFunctionSignatures_enable, + callable: match self.global.0.completion_callable_snippets { + CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), + CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), + CallableCompletionDef::None => None, + }, + insert_use: self.insert_use_config(), + prefer_no_std: self.local.imports_prefer_no_std, + snippet_cap: SnippetCap::new(try_or_def!( + self.caps + .text_document + .as_ref()? + .completion + .as_ref()? + .completion_item + .as_ref()? + .snippet_support? + )), + snippets: self.snippets.clone().to_vec(), + limit: self.global.0.completion_limit, + } + } + pub fn diagnostics(&self) -> DiagnosticsConfig { DiagnosticsConfig { enabled: self.global.0.diagnostics_enable, @@ -685,35 +725,24 @@ impl<'a> LocalConfigView<'a> { prefer_no_std: self.local.imports_prefer_no_std, } } + pub fn expand_proc_attr_macros(&self) -> bool { + self.global.0.procMacro_enable && self.global.0.procMacro_attributes_enable + } - pub fn assist(&self) -> AssistConfig { - AssistConfig { - snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), - allowed: None, - insert_use: self.insert_use_config(), - prefer_no_std: self.local.imports_prefer_no_std, - assist_emit_must_use: self.global.0.assist_emitMustUse, - } + fn experimental(&self, index: &'static str) -> bool { + try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?) } - fn insert_use_config(&self) -> InsertUseConfig { - InsertUseConfig { - granularity: match self.local.imports_granularity_group { - ImportGranularityDef::Preserve => ImportGranularity::Preserve, - ImportGranularityDef::Item => ImportGranularity::Item, - ImportGranularityDef::Crate => ImportGranularity::Crate, - ImportGranularityDef::Module => ImportGranularity::Module, - }, - enforce_granularity: self.local.imports_granularity_enforce, - prefix_kind: match self.local.imports_prefix { - ImportPrefixDef::Plain => PrefixKind::Plain, - ImportPrefixDef::ByCrate => PrefixKind::ByCrate, - ImportPrefixDef::BySelf => PrefixKind::BySelf, - }, - group: self.local.imports_group_enable, - skip_glob_imports: !self.local.imports_merge_glob, + pub fn highlight_related(&self) -> HighlightRelatedConfig { + HighlightRelatedConfig { + references: self.local.highlightRelated_references_enable, + break_points: self.local.highlightRelated_breakPoints_enable, + exit_points: self.local.highlightRelated_exitPoints_enable, + yield_points: self.local.highlightRelated_yieldPoints_enable, + closure_captures: self.local.highlightRelated_closureCaptures_enable, } } + pub fn inlay_hints(&self) -> InlayHintsConfig { let client_capability_fields = self .caps @@ -797,44 +826,25 @@ impl<'a> LocalConfigView<'a> { } } - pub fn expand_proc_attr_macros(&self) -> bool { - self.global.0.procMacro_enable && self.global.0.procMacro_attributes_enable - } - - pub fn completion(&self) -> CompletionConfig { - CompletionConfig { - enable_postfix_completions: self.global.0.completion_postfix_enable, - enable_imports_on_the_fly: self.global.0.completion_autoimport_enable - && completion_item_edit_resolve(&self.caps), - enable_self_on_the_fly: self.global.0.completion_autoself_enable, - enable_private_editable: self.global.0.completion_privateEditable_enable, - full_function_signatures: self.global.0.completion_fullFunctionSignatures_enable, - callable: match self.global.0.completion_callable_snippets { - CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), - CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), - CallableCompletionDef::None => None, + fn insert_use_config(&self) -> InsertUseConfig { + InsertUseConfig { + granularity: match self.local.imports_granularity_group { + ImportGranularityDef::Preserve => ImportGranularity::Preserve, + ImportGranularityDef::Item => ImportGranularity::Item, + ImportGranularityDef::Crate => ImportGranularity::Crate, + ImportGranularityDef::Module => ImportGranularity::Module, }, - insert_use: self.insert_use_config(), - prefer_no_std: self.local.imports_prefer_no_std, - snippet_cap: SnippetCap::new(try_or_def!( - self.caps - .text_document - .as_ref()? - .completion - .as_ref()? - .completion_item - .as_ref()? - .snippet_support? - )), - snippets: self.snippets.clone().to_vec(), - limit: self.global.0.completion_limit, + enforce_granularity: self.local.imports_granularity_enforce, + prefix_kind: match self.local.imports_prefix { + ImportPrefixDef::Plain => PrefixKind::Plain, + ImportPrefixDef::ByCrate => PrefixKind::ByCrate, + ImportPrefixDef::BySelf => PrefixKind::BySelf, + }, + group: self.local.imports_group_enable, + skip_glob_imports: !self.local.imports_merge_glob, } } - fn experimental(&self, index: &'static str) -> bool { - try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?) - } - pub fn join_lines(&self) -> JoinLinesConfig { JoinLinesConfig { join_else_if: self.local.joinLines_joinElseIf, @@ -1876,16 +1886,6 @@ impl Config { } } - pub fn highlight_related(&self) -> HighlightRelatedConfig { - HighlightRelatedConfig { - references: self.root_config.global.0.highlightRelated_references_enable, - break_points: self.root_config.global.0.highlightRelated_breakPoints_enable, - exit_points: self.root_config.global.0.highlightRelated_exitPoints_enable, - yield_points: self.root_config.global.0.highlightRelated_yieldPoints_enable, - closure_captures: self.root_config.global.0.highlightRelated_closureCaptures_enable, - } - } - pub fn prime_caches_num_threads(&self) -> u8 { match self.root_config.global.0.cachePriming_numThreads { 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX), diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 09cab755710c..0956d131f3e8 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -1365,7 +1365,10 @@ pub(crate) fn handle_document_highlight( let position = from_proto::file_position(&snap, params.text_document_position_params)?; let line_index = snap.file_line_index(position.file_id)?; - let refs = match snap.analysis.highlight_related(snap.config.highlight_related(), position)? { + let refs = match snap.analysis.highlight_related( + snap.config.localize_by_file_id(position.file_id).highlight_related(), + position, + )? { None => return Ok(None), Some(refs) => refs, }; From f621722bb62129ed2d81c034b9dc0eb401d5337a Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 02:59:34 +0200 Subject: [PATCH 07/52] Declare hover_actions_* as local --- crates/rust-analyzer/src/config.rs | 172 +++++++++---------- crates/rust-analyzer/src/handlers/request.rs | 31 ++-- 2 files changed, 106 insertions(+), 97 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index b7c6a44454a3..e5b9135ae6bb 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -298,42 +298,6 @@ config_data! { files_watcher: FilesWatcherDef = "\"client\"", - /// Whether to show `Debug` action. Only applies when - /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_debug_enable: bool = "true", - /// Whether to show HoverActions in Rust files. - hover_actions_enable: bool = "true", - /// Whether to show `Go to Type Definition` action. Only applies when - /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_gotoTypeDef_enable: bool = "true", - /// Whether to show `Implementations` action. Only applies when - /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_implementations_enable: bool = "true", - /// Whether to show `References` action. Only applies when - /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_references_enable: bool = "false", - /// Whether to show `Run` action. Only applies when - /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_run_enable: bool = "true", - - /// Whether to show documentation on hover. - hover_documentation_enable: bool = "true", - /// Whether to show keyword hover popups. Only applies when - /// `#rust-analyzer.hover.documentation.enable#` is set. - hover_documentation_keywords_enable: bool = "true", - /// Use markdown syntax for links on hover. - hover_links_enable: bool = "true", - /// How to render the align information in a memory layout hover. - hover_memoryLayout_alignment: Option = "\"hexadecimal\"", - /// Whether to show memory layout data on hover. - hover_memoryLayout_enable: bool = "true", - /// How to render the niche information in a memory layout hover. - hover_memoryLayout_niches: Option = "false", - /// How to render the offset information in a memory layout hover. - hover_memoryLayout_offset: Option = "\"hexadecimal\"", - /// How to render the size information in a memory layout hover. - hover_memoryLayout_size: Option = "\"both\"", - /// Enables the experimental support for interpreting tests. interpret_tests: bool = "false", @@ -507,6 +471,42 @@ config_data! { /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords. highlightRelated_yieldPoints_enable: bool = "true", + /// Whether to show `Debug` action. Only applies when + /// `#rust-analyzer.hover.actions.enable#` is set. + hover_actions_debug_enable: bool = "true", + /// Whether to show HoverActions in Rust files. + hover_actions_enable: bool = "true", + /// Whether to show `Go to Type Definition` action. Only applies when + /// `#rust-analyzer.hover.actions.enable#` is set. + hover_actions_gotoTypeDef_enable: bool = "true", + /// Whether to show `Implementations` action. Only applies when + /// `#rust-analyzer.hover.actions.enable#` is set. + hover_actions_implementations_enable: bool = "true", + /// Whether to show `References` action. Only applies when + /// `#rust-analyzer.hover.actions.enable#` is set. + hover_actions_references_enable: bool = "false", + /// Whether to show `Run` action. Only applies when + /// `#rust-analyzer.hover.actions.enable#` is set. + hover_actions_run_enable: bool = "true", + + /// Whether to show documentation on hover. + hover_documentation_enable: bool = "true", + /// Whether to show keyword hover popups. Only applies when + /// `#rust-analyzer.hover.documentation.enable#` is set. + hover_documentation_keywords_enable: bool = "true", + /// Use markdown syntax for links on hover. + hover_links_enable: bool = "true", + /// How to render the align information in a memory layout hover. + hover_memoryLayout_alignment: Option = "\"hexadecimal\"", + /// Whether to show memory layout data on hover. + hover_memoryLayout_enable: bool = "true", + /// How to render the niche information in a memory layout hover. + hover_memoryLayout_niches: Option = "false", + /// How to render the offset information in a memory layout hover. + hover_memoryLayout_offset: Option = "\"hexadecimal\"", + /// How to render the size information in a memory layout hover. + hover_memoryLayout_size: Option = "\"both\"", + /// Whether to show inlay type hints for binding modes. inlayHints_bindingModeHints_enable: bool = "false", /// Whether to show inlay type hints for method chains. @@ -743,6 +743,55 @@ impl<'a> LocalConfigView<'a> { } } + pub fn hover_actions(&self) -> HoverActionsConfig { + let enable = self.experimental("hoverActions") && self.local.hover_actions_enable; + HoverActionsConfig { + implementations: enable && self.local.hover_actions_implementations_enable, + references: enable && self.local.hover_actions_references_enable, + run: enable && self.local.hover_actions_run_enable, + debug: enable && self.local.hover_actions_debug_enable, + goto_type_def: enable && self.local.hover_actions_gotoTypeDef_enable, + } + } + + pub fn hover(&self) -> HoverConfig { + let mem_kind = |kind| match kind { + MemoryLayoutHoverRenderKindDef::Both => MemoryLayoutHoverRenderKind::Both, + MemoryLayoutHoverRenderKindDef::Decimal => MemoryLayoutHoverRenderKind::Decimal, + MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal, + }; + HoverConfig { + links_in_hover: self.local.hover_links_enable, + memory_layout: self.local.hover_memoryLayout_enable.then_some( + MemoryLayoutHoverConfig { + size: self.local.hover_memoryLayout_size.map(mem_kind), + offset: self.local.hover_memoryLayout_offset.map(mem_kind), + alignment: self.local.hover_memoryLayout_alignment.map(mem_kind), + niches: self.local.hover_memoryLayout_niches.unwrap_or_default(), + }, + ), + documentation: self.local.hover_documentation_enable, + format: { + let is_markdown = try_or_def!(self + .caps + .text_document + .as_ref()? + .hover + .as_ref()? + .content_format + .as_ref()? + .as_slice()) + .contains(&MarkupKind::Markdown); + if is_markdown { + HoverDocFormat::Markdown + } else { + HoverDocFormat::PlainText + } + }, + keywords: self.local.hover_documentation_keywords_enable, + } + } + pub fn inlay_hints(&self) -> InlayHintsConfig { let client_capability_fields = self .caps @@ -1742,19 +1791,6 @@ impl Config { } } - pub fn hover_actions(&self) -> HoverActionsConfig { - let enable = - self.experimental("hoverActions") && self.root_config.global.0.hover_actions_enable; - HoverActionsConfig { - implementations: enable - && self.root_config.global.0.hover_actions_implementations_enable, - references: enable && self.root_config.global.0.hover_actions_references_enable, - run: enable && self.root_config.global.0.hover_actions_run_enable, - debug: enable && self.root_config.global.0.hover_actions_debug_enable, - goto_type_def: enable && self.root_config.global.0.hover_actions_gotoTypeDef_enable, - } - } - pub fn highlighting_non_standard_tokens(&self) -> bool { self.root_config.global.0.semanticHighlighting_nonStandardTokens } @@ -1788,44 +1824,6 @@ impl Config { } } - pub fn hover(&self) -> HoverConfig { - let mem_kind = |kind| match kind { - MemoryLayoutHoverRenderKindDef::Both => MemoryLayoutHoverRenderKind::Both, - MemoryLayoutHoverRenderKindDef::Decimal => MemoryLayoutHoverRenderKind::Decimal, - MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal, - }; - HoverConfig { - links_in_hover: self.root_config.global.0.hover_links_enable, - memory_layout: self.root_config.global.0.hover_memoryLayout_enable.then_some( - MemoryLayoutHoverConfig { - size: self.root_config.global.0.hover_memoryLayout_size.map(mem_kind), - offset: self.root_config.global.0.hover_memoryLayout_offset.map(mem_kind), - alignment: self.root_config.global.0.hover_memoryLayout_alignment.map(mem_kind), - niches: self.root_config.global.0.hover_memoryLayout_niches.unwrap_or_default(), - }, - ), - documentation: self.root_config.global.0.hover_documentation_enable, - format: { - let is_markdown = try_or_def!(self - .caps - .text_document - .as_ref()? - .hover - .as_ref()? - .content_format - .as_ref()? - .as_slice()) - .contains(&MarkupKind::Markdown); - if is_markdown { - HoverDocFormat::Markdown - } else { - HoverDocFormat::PlainText - } - }, - keywords: self.root_config.global.0.hover_documentation_keywords_enable, - } - } - pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig { WorkspaceSymbolConfig { search_scope: match self.root_config.global.0.workspace_symbol_search_scope { diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 0956d131f3e8..f550d8d1f428 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -974,16 +974,17 @@ pub(crate) fn handle_hover( PositionOrRange::Position(position) => Range::new(position, position), PositionOrRange::Range(range) => range, }; - let file_range = from_proto::file_range(&snap, ¶ms.text_document, range)?; - let info = match snap.analysis.hover(&snap.config.hover(), file_range)? { + let local_conf = snap.config.localize_by_file_id(file_range.file_id); + let hover = local_conf.hover(); + let info = match snap.analysis.hover(&hover, file_range)? { None => return Ok(None), Some(info) => info, }; let line_index = snap.file_line_index(file_range.file_id)?; let range = to_proto::range(&line_index, info.range); - let markup_kind = snap.config.hover().format; + let markup_kind = hover.format; let hover = lsp_ext::Hover { hover: lsp_types::Hover { contents: HoverContents::Markup(to_proto::markup_content( @@ -992,7 +993,7 @@ pub(crate) fn handle_hover( )), range: Some(range), }, - actions: if snap.config.hover_actions().none() { + actions: if local_conf.hover_actions().none() { Vec::new() } else { prepare_hover_actions(&snap, &info.info.actions) @@ -1795,7 +1796,9 @@ fn show_impl_command_link( snap: &GlobalStateSnapshot, position: &FilePosition, ) -> Option { - if snap.config.hover_actions().implementations && snap.config.client_commands().show_reference { + if snap.config.localize_by_file_id(position.file_id).hover_actions().implementations + && snap.config.client_commands().show_reference + { if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; @@ -1821,7 +1824,9 @@ fn show_ref_command_link( snap: &GlobalStateSnapshot, position: &FilePosition, ) -> Option { - if snap.config.hover_actions().references && snap.config.client_commands().show_reference { + if snap.config.localize_by_file_id(position.file_id).hover_actions().references + && snap.config.client_commands().show_reference + { if let Some(ref_search_res) = snap.analysis.find_all_refs(*position, None).unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; @@ -1851,12 +1856,14 @@ fn runnable_action_links( snap: &GlobalStateSnapshot, runnable: Runnable, ) -> Option { - let hover_actions_config = snap.config.hover_actions(); + let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?; + + let hover_actions_config = + snap.config.localize_by_file_id(runnable.nav.file_id).hover_actions(); if !hover_actions_config.runnable() { return None; } - let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?; if should_skip_target(&runnable, cargo_spec.as_ref()) { return None; } @@ -1888,8 +1895,12 @@ fn goto_type_action_links( snap: &GlobalStateSnapshot, nav_targets: &[HoverGotoTypeData], ) -> Option { - if !snap.config.hover_actions().goto_type_def - || nav_targets.is_empty() + if nav_targets.is_empty() + || !snap + .config + .localize_by_file_id(nav_targets[0].nav.file_id) + .hover_actions() + .goto_type_def || !snap.config.client_commands().goto_location { return None; From 5c063b913c0fd54dc22606eecce9f154201678fb Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 03:08:55 +0200 Subject: [PATCH 08/52] Declare completion_* as local --- crates/rust-analyzer/src/config.rs | 135 +++++++++++++++-------------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e5b9135ae6bb..f2c7a2961636 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -210,65 +210,6 @@ config_data! { /// Aliased as `"checkOnSave.targets"`. check_targets | checkOnSave_targets | checkOnSave_target: Option = "null", - /// Toggles the additional completions that automatically add imports when completed. - /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. - completion_autoimport_enable: bool = "true", - /// Toggles the additional completions that automatically show method calls and field accesses - /// with `self` prefixed to them when inside a method. - completion_autoself_enable: bool = "true", - /// Whether to add parenthesis and argument snippets when completing function. - completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"", - /// Whether to show full function/method signatures in completion docs. - completion_fullFunctionSignatures_enable: bool = "false", - /// Maximum number of completions to return. If `None`, the limit is infinite. - completion_limit: Option = "null", - /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. - completion_postfix_enable: bool = "true", - /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. - completion_privateEditable_enable: bool = "false", - /// Custom completion snippets. - // NOTE: Keep this list in sync with the feature docs of user snippets. - completion_snippets_custom: FxHashMap = r#"{ - "Arc::new": { - "postfix": "arc", - "body": "Arc::new(${receiver})", - "requires": "std::sync::Arc", - "description": "Put the expression into an `Arc`", - "scope": "expr" - }, - "Rc::new": { - "postfix": "rc", - "body": "Rc::new(${receiver})", - "requires": "std::rc::Rc", - "description": "Put the expression into an `Rc`", - "scope": "expr" - }, - "Box::pin": { - "postfix": "pinbox", - "body": "Box::pin(${receiver})", - "requires": "std::boxed::Box", - "description": "Put the expression into a pinned `Box`", - "scope": "expr" - }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, - "Err": { - "postfix": "err", - "body": "Err(${receiver})", - "description": "Wrap the expression in a `Result::Err`", - "scope": "expr" - }, - "Some": { - "postfix": "some", - "body": "Some(${receiver})", - "description": "Wrap the expression in an `Option::Some`", - "scope": "expr" - } - }"#, /// List of rust-analyzer diagnostics to disable. diagnostics_disabled: FxHashSet = "[]", @@ -460,6 +401,66 @@ config_data! { config_data! { struct LocalConfigData { + /// Toggles the additional completions that automatically add imports when completed. + /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. + completion_autoimport_enable: bool = "true", + /// Toggles the additional completions that automatically show method calls and field accesses + /// with `self` prefixed to them when inside a method. + completion_autoself_enable: bool = "true", + /// Whether to add parenthesis and argument snippets when completing function. + completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"", + /// Whether to show full function/method signatures in completion docs. + completion_fullFunctionSignatures_enable: bool = "false", + /// Maximum number of completions to return. If `None`, the limit is infinite. + completion_limit: Option = "null", + /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. + completion_postfix_enable: bool = "true", + /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. + completion_privateEditable_enable: bool = "false", + /// Custom completion snippets. + // NOTE: Keep this list in sync with the feature docs of user snippets. + completion_snippets_custom: FxHashMap = r#"{ + "Arc::new": { + "postfix": "arc", + "body": "Arc::new(${receiver})", + "requires": "std::sync::Arc", + "description": "Put the expression into an `Arc`", + "scope": "expr" + }, + "Rc::new": { + "postfix": "rc", + "body": "Rc::new(${receiver})", + "requires": "std::rc::Rc", + "description": "Put the expression into an `Rc`", + "scope": "expr" + }, + "Box::pin": { + "postfix": "pinbox", + "body": "Box::pin(${receiver})", + "requires": "std::boxed::Box", + "description": "Put the expression into a pinned `Box`", + "scope": "expr" + }, + "Ok": { + "postfix": "ok", + "body": "Ok(${receiver})", + "description": "Wrap the expression in a `Result::Ok`", + "scope": "expr" + }, + "Err": { + "postfix": "err", + "body": "Err(${receiver})", + "description": "Wrap the expression in a `Result::Err`", + "scope": "expr" + }, + "Some": { + "postfix": "some", + "body": "Some(${receiver})", + "description": "Wrap the expression in an `Option::Some`", + "scope": "expr" + } + }"#, + /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. highlightRelated_breakPoints_enable: bool = "true", /// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure. @@ -682,13 +683,13 @@ impl<'a> LocalConfigView<'a> { pub fn completion(&self) -> CompletionConfig { CompletionConfig { - enable_postfix_completions: self.global.0.completion_postfix_enable, - enable_imports_on_the_fly: self.global.0.completion_autoimport_enable + enable_postfix_completions: self.local.completion_postfix_enable, + enable_imports_on_the_fly: self.local.completion_autoimport_enable && completion_item_edit_resolve(&self.caps), - enable_self_on_the_fly: self.global.0.completion_autoself_enable, - enable_private_editable: self.global.0.completion_privateEditable_enable, - full_function_signatures: self.global.0.completion_fullFunctionSignatures_enable, - callable: match self.global.0.completion_callable_snippets { + enable_self_on_the_fly: self.local.completion_autoself_enable, + enable_private_editable: self.local.completion_privateEditable_enable, + full_function_signatures: self.local.completion_fullFunctionSignatures_enable, + callable: match self.local.completion_callable_snippets { CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), CallableCompletionDef::None => None, @@ -706,7 +707,7 @@ impl<'a> LocalConfigView<'a> { .snippet_support? )), snippets: self.snippets.clone().to_vec(), - limit: self.global.0.completion_limit, + limit: self.local.completion_limit, } } @@ -1182,7 +1183,7 @@ impl Config { RootGlobalConfigData(GlobalConfigData::from_json(json, &mut errors)); tracing::debug!("deserialized config data: {:#?}", self.root_config.global); self.snippets.clear(); - for (name, def) in self.root_config.global.0.completion_snippets_custom.iter() { + for (name, def) in self.root_config.local.0.completion_snippets_custom.iter() { if def.prefix.is_empty() && def.postfix.is_empty() { continue; } From 45c7caaf8ad9d500ff8897e0960dea48eefec2cb Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 03:46:04 +0200 Subject: [PATCH 09/52] Declare semanticHighlighting_* as local --- crates/rust-analyzer/src/config.rs | 125 +++++++++---------- crates/rust-analyzer/src/handlers/request.rs | 13 +- 2 files changed, 63 insertions(+), 75 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index f2c7a2961636..f86c5e6b5f43 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -342,42 +342,6 @@ config_data! { /// available on a nightly build. rustfmt_rangeFormatting_enable: bool = "false", - /// Inject additional highlighting into doc comments. - /// - /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra - /// doc links. - semanticHighlighting_doc_comment_inject_enable: bool = "true", - /// Whether the server is allowed to emit non-standard tokens and modifiers. - semanticHighlighting_nonStandardTokens: bool = "true", - /// Use semantic tokens for operators. - /// - /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when - /// they are tagged with modifiers. - semanticHighlighting_operator_enable: bool = "true", - /// Use specialized semantic tokens for operators. - /// - /// When enabled, rust-analyzer will emit special token types for operator tokens instead - /// of the generic `operator` token type. - semanticHighlighting_operator_specialization_enable: bool = "false", - /// Use semantic tokens for punctuation. - /// - /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when - /// they are tagged with modifiers or have a special role. - semanticHighlighting_punctuation_enable: bool = "false", - /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro - /// calls. - semanticHighlighting_punctuation_separate_macro_bang: bool = "false", - /// Use specialized semantic tokens for punctuation. - /// - /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead - /// of the generic `punctuation` token type. - semanticHighlighting_punctuation_specialization_enable: bool = "false", - /// Use semantic tokens for strings. - /// - /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars. - /// By disabling semantic tokens for strings, other grammars can be used to highlight - /// their contents. - semanticHighlighting_strings_enable: bool = "true", /// Show full signature of the callable. Only shows parameters if disabled. signatureInfo_detail: SignatureDetail = "\"full\"", @@ -575,6 +539,43 @@ config_data! { joinLines_removeTrailingComma: bool = "true", /// Join lines unwraps trivial blocks. joinLines_unwrapTrivialBlock: bool = "true", + + /// Inject additional highlighting into doc comments. + /// + /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra + /// doc links. + semanticHighlighting_doc_comment_inject_enable: bool = "true", + /// Whether the server is allowed to emit non-standard tokens and modifiers. + semanticHighlighting_nonStandardTokens: bool = "true", + /// Use semantic tokens for operators. + /// + /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when + /// they are tagged with modifiers. + semanticHighlighting_operator_enable: bool = "true", + /// Use specialized semantic tokens for operators. + /// + /// When enabled, rust-analyzer will emit special token types for operator tokens instead + /// of the generic `operator` token type. + semanticHighlighting_operator_specialization_enable: bool = "false", + /// Use semantic tokens for punctuation. + /// + /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when + /// they are tagged with modifiers or have a special role. + semanticHighlighting_punctuation_enable: bool = "false", + /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro + /// calls. + semanticHighlighting_punctuation_separate_macro_bang: bool = "false", + /// Use specialized semantic tokens for punctuation. + /// + /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead + /// of the generic `punctuation` token type. + semanticHighlighting_punctuation_specialization_enable: bool = "false", + /// Use semantic tokens for strings. + /// + /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars. + /// By disabling semantic tokens for strings, other grammars can be used to highlight + /// their contents. + semanticHighlighting_strings_enable: bool = "true", } } @@ -903,6 +904,25 @@ impl<'a> LocalConfigView<'a> { join_assignments: self.local.joinLines_joinAssignments, } } + + pub fn highlighting_non_standard_tokens(&self) -> bool { + self.local.semanticHighlighting_nonStandardTokens + } + + pub fn highlighting_config(&self) -> HighlightConfig { + HighlightConfig { + strings: self.local.semanticHighlighting_strings_enable, + punctuation: self.local.semanticHighlighting_punctuation_enable, + specialize_punctuation: self + .local + .semanticHighlighting_punctuation_specialization_enable, + macro_bang: self.local.semanticHighlighting_punctuation_separate_macro_bang, + operator: self.local.semanticHighlighting_operator_enable, + specialize_operator: self.local.semanticHighlighting_operator_specialization_enable, + inject_doc_comment: self.local.semanticHighlighting_doc_comment_inject_enable, + syntactic_name_ref_highlighting: false, + } + } } type ParallelCachePrimingNumThreads = u8; @@ -1792,39 +1812,6 @@ impl Config { } } - pub fn highlighting_non_standard_tokens(&self) -> bool { - self.root_config.global.0.semanticHighlighting_nonStandardTokens - } - - pub fn highlighting_config(&self) -> HighlightConfig { - HighlightConfig { - strings: self.root_config.global.0.semanticHighlighting_strings_enable, - punctuation: self.root_config.global.0.semanticHighlighting_punctuation_enable, - specialize_punctuation: self - .root_config - .global - .0 - .semanticHighlighting_punctuation_specialization_enable, - macro_bang: self - .root_config - .global - .0 - .semanticHighlighting_punctuation_separate_macro_bang, - operator: self.root_config.global.0.semanticHighlighting_operator_enable, - specialize_operator: self - .root_config - .global - .0 - .semanticHighlighting_operator_specialization_enable, - inject_doc_comment: self - .root_config - .global - .0 - .semanticHighlighting_doc_comment_inject_enable, - syntactic_name_ref_highlighting: false, - } - } - pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig { WorkspaceSymbolConfig { search_scope: match self.root_config.global.0.workspace_symbol_search_scope { diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index f550d8d1f428..9491c9ed50f8 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -1592,7 +1592,7 @@ pub(crate) fn handle_semantic_tokens_full( let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.localize_by_file_id(file_id).highlighting_config(); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -1603,7 +1603,7 @@ pub(crate) fn handle_semantic_tokens_full( &line_index, highlights, snap.config.semantics_tokens_augments_syntax_tokens(), - snap.config.highlighting_non_standard_tokens(), + snap.config.localize_by_file_id(file_id).highlighting_non_standard_tokens(), ); // Unconditionally cache the tokens @@ -1622,7 +1622,7 @@ pub(crate) fn handle_semantic_tokens_full_delta( let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.localize_by_file_id(file_id).highlighting_config(); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -1633,7 +1633,7 @@ pub(crate) fn handle_semantic_tokens_full_delta( &line_index, highlights, snap.config.semantics_tokens_augments_syntax_tokens(), - snap.config.highlighting_non_standard_tokens(), + snap.config.localize_by_file_id(file_id).highlighting_non_standard_tokens(), ); let cached_tokens = snap.semantic_tokens_cache.lock().remove(¶ms.text_document.uri); @@ -1665,7 +1665,8 @@ pub(crate) fn handle_semantic_tokens_range( let text = snap.analysis.file_text(frange.file_id)?; let line_index = snap.file_line_index(frange.file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = + snap.config.localize_by_file_id(frange.file_id).highlighting_config(); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -1676,7 +1677,7 @@ pub(crate) fn handle_semantic_tokens_range( &line_index, highlights, snap.config.semantics_tokens_augments_syntax_tokens(), - snap.config.highlighting_non_standard_tokens(), + snap.config.localize_by_file_id(frange.file_id).highlighting_non_standard_tokens(), ); Ok(Some(semantic_tokens.into())) } From 2bad5e7ccdf584bbdfa4bdb9e41e8c67ad4908f9 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 25 Oct 2023 16:57:38 +0200 Subject: [PATCH 10/52] Last commit before issuing a draft --- crates/base-db/src/change.rs | 3 +- crates/base-db/src/input.rs | 23 ++++++++--- crates/load-cargo/src/lib.rs | 1 + crates/rust-analyzer/src/global_state.rs | 1 - crates/rust-analyzer/src/main_loop.rs | 6 +++ crates/rust-analyzer/src/reload.rs | 6 ++- crates/rust-analyzer/tests/slow-tests/main.rs | 39 +++++++++++++++++++ crates/vfs/src/file_set.rs | 12 +++--- 8 files changed, 77 insertions(+), 14 deletions(-) diff --git a/crates/base-db/src/change.rs b/crates/base-db/src/change.rs index 6a3b36b23128..8300326650ab 100644 --- a/crates/base-db/src/change.rs +++ b/crates/base-db/src/change.rs @@ -25,7 +25,8 @@ impl fmt::Debug for Change { d.field("roots", roots); } if !self.files_changed.is_empty() { - d.field("files_changed", &self.files_changed.len()); + d.field("files_changed", &self.files_changed); + // d.field("files_changed", &self.files_changed.len()); } if self.crate_graph.is_some() { d.field("crate_graph", &self.crate_graph); diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 65db5c0fc7d3..defa4f45d3bc 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -21,6 +21,9 @@ use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath}; pub type ProcMacroPaths = FxHashMap, AbsPathBuf), String>>; pub type ProcMacros = FxHashMap; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct SourceRootId(pub u32); + /// Files are grouped into source roots. A source root is a directory on the /// file systems which is watched for changes. Typically it corresponds to a /// Rust crate. Source roots *might* be nested: in this case, a file belongs to @@ -28,9 +31,6 @@ pub type ProcMacros = FxHashMap; /// source root, and the analyzer does not know the root path of the source root at /// all. So, a file from one source root can't refer to a file in another source /// root by path. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct SourceRootId(pub u32); - #[derive(Clone, Debug, PartialEq, Eq)] pub struct SourceRoot { /// Sysroot or crates.io library. @@ -38,16 +38,19 @@ pub struct SourceRoot { /// Libraries are considered mostly immutable, this assumption is used to /// optimize salsa's query structure pub is_library: bool, + cargo_file_id: Option, + ratoml_file_id: Option, file_set: FileSet, } impl SourceRoot { pub fn new_local(file_set: FileSet) -> SourceRoot { - SourceRoot { is_library: false, file_set } + eprintln!("{:#?}", file_set); + SourceRoot { is_library: false, file_set, cargo_file_id: None, ratoml_file_id: None } } pub fn new_library(file_set: FileSet) -> SourceRoot { - SourceRoot { is_library: true, file_set } + SourceRoot { is_library: true, file_set, cargo_file_id: None, ratoml_file_id: None } } pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> { @@ -65,6 +68,16 @@ impl SourceRoot { pub fn iter(&self) -> impl Iterator + '_ { self.file_set.iter() } + + /// Get `FileId` of the `Cargo.toml` if one present in `SourceRoot` + pub fn cargo_toml(&self) -> Option { + self.cargo_file_id + } + + /// Get `FileId` of the `rust-analyzer.toml` if one present in `SourceRoot` + pub fn ratoml(&self) -> Option { + self.ratoml_file_id + } } /// `CrateGraph` is a bit of information which turns a set of text files into a diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index 7a795dd62ab7..68b592ffaa4d 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -208,6 +208,7 @@ impl ProjectFolders { let entry = { let mut dirs = vfs::loader::Directories::default(); dirs.extensions.push("rs".into()); + dirs.extensions.push("toml".into()); dirs.include.extend(root.include); dirs.exclude.extend(root.exclude); for excl in global_excludes { diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index c09f57252ce9..90743251c4a8 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -330,7 +330,6 @@ impl GlobalState { } (change, changed_files, workspace_structure_change) }; - self.analysis_host.apply_change(change); { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index cdf41c955d26..80e7db3667fa 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -131,6 +131,7 @@ impl GlobalState { Event::Lsp(lsp_server::Message::Notification(Notification { method, .. })) if method == lsp_types::notification::Exit::METHOD ) { + dbg!("EXIT"); return Ok(()); } self.handle_event(event)?; @@ -159,6 +160,11 @@ impl GlobalState { scheme: None, pattern: Some("**/Cargo.lock".into()), }, + lsp_types::DocumentFilter { + language: None, + scheme: None, + pattern: Some("**/.rust-analyzer.toml".into()), + }, ]), }, }; diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 3fae08b82e27..c1898729adec 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -172,7 +172,8 @@ impl GlobalState { } } - if let Err(_) = self.fetch_workspace_error() { + if let Err(k) = self.fetch_workspace_error() { + dbg!(&k); status.health = lsp_ext::Health::Error; message.push_str("Failed to load workspaces.\n\n"); } @@ -414,6 +415,8 @@ impl GlobalState { format!("{it}/**/*.rs"), format!("{it}/**/Cargo.toml"), format!("{it}/**/Cargo.lock"), + // FIXME @alibektas : WS may not have to be local for RATOML to be included. + format!("{it}/**/.rust-analyzer.toml"), ] }) }) @@ -526,6 +529,7 @@ impl GlobalState { change.set_crate_graph(crate_graph); self.analysis_host.apply_change(change); self.crate_graph_file_dependencies = crate_graph_file_dependencies; + eprintln!("Recreate crate graph"); self.process_changes(); self.reload_flycheck(); diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs index d59914298991..2053b8342c31 100644 --- a/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/crates/rust-analyzer/tests/slow-tests/main.rs @@ -1131,3 +1131,42 @@ version = "0.0.0" server.request::(Default::default(), json!([])); } + +#[test] +fn test_ratoml_exists() { + if skip_slow_tests() { + return; + } + + let server = Project::with_fixture( + r#" +//- /foo/Cargo.toml +[package] +name = "foo" +version = "0.0.0" + +[dependencies] +bar.path = "./bar" + +//- /foo/src/lib.rs +pub fn foo() {} + +//- /foo/src/abc/mod.rs +pub fn bar() {} + +//- /foo/.rust-analyzer.toml +ABC + +//- /foo/bar/Cargo.toml +[package] +name = "bar" +version = "0.0.0" + +//- /foo/bar/src/lib.rs +pub fn bar() {} +"#, + ) + .root("foo") + .server() + .wait_until_workspace_is_loaded(); +} diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs index 0392ef3cebe9..e8b32a15eeca 100644 --- a/crates/vfs/src/file_set.rs +++ b/crates/vfs/src/file_set.rs @@ -11,7 +11,7 @@ use rustc_hash::FxHashMap; use crate::{AnchoredPath, FileId, Vfs, VfsPath}; /// A set of [`VfsPath`]s identified by [`FileId`]s. -#[derive(Default, Clone, Eq, PartialEq)] +#[derive(Default, Debug, Clone, Eq, PartialEq)] pub struct FileSet { files: FxHashMap, paths: IntMap, @@ -59,11 +59,11 @@ impl FileSet { } } -impl fmt::Debug for FileSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FileSet").field("n_files", &self.files.len()).finish() - } -} +// impl fmt::Debug for FileSet { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// f.debug_struct("FileSet").field("n_files", &self.files.len()).finish() +// } +// } /// This contains path prefixes to partition a [`Vfs`] into [`FileSet`]s. /// From 4196ad52f41c715a770acaf79a42d50fd0bfe50b Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Fri, 5 Jan 2024 16:02:37 +0100 Subject: [PATCH 11/52] Make ConfigData Ser and TOML De In order to achieve this, I had to virtually rewrite ConfigData's default values to their typed versions. It ended up looking not so well. I will think of something to fix this. In addition to this, some default value have been hastily modified but config manual generation will be a stay as a reminder to fix this. --- Cargo.lock | 63 ++- crates/base-db/Cargo.toml | 1 + crates/base-db/src/input.rs | 14 + crates/project-model/src/cfg_flag.rs | 3 +- crates/project-model/src/project_json.rs | 12 +- crates/rust-analyzer/Cargo.toml | 1 + crates/rust-analyzer/src/config.rs | 612 ++++++++++++++--------- crates/rust-analyzer/src/main_loop.rs | 1 - crates/rust-analyzer/src/reload.rs | 1 - 9 files changed, 446 insertions(+), 262 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcb188c0dfab..46a1ebc69666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,7 @@ dependencies = [ "profile", "rustc-hash", "salsa", + "serde", "stdx", "syntax", "test-utils", @@ -888,9 +889,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -1020,9 +1021,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -1584,6 +1585,7 @@ dependencies = [ "ide-db", "ide-ssr", "itertools", + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "load-cargo", "lsp-server 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "lsp-types", @@ -1610,6 +1612,7 @@ dependencies = [ "syntax", "test-utils", "tikv-jemallocator", + "toml", "toolchain", "tracing", "tracing-log", @@ -1762,6 +1765,15 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -1996,6 +2008,40 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "toolchain" version = "0.0.0" @@ -2365,6 +2411,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6" +dependencies = [ + "memchr", +] + [[package]] name = "write-json" version = "0.1.2" diff --git a/crates/base-db/Cargo.toml b/crates/base-db/Cargo.toml index 171c113a950d..8435dba86d22 100644 --- a/crates/base-db/Cargo.toml +++ b/crates/base-db/Cargo.toml @@ -27,3 +27,4 @@ syntax.workspace = true test-utils.workspace = true tt.workspace = true vfs.workspace = true +serde.workspace = true diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index defa4f45d3bc..a43f542ecdc3 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -11,6 +11,7 @@ use std::{fmt, mem, ops, panic::RefUnwindSafe, str::FromStr, sync}; use cfg::CfgOptions; use la_arena::{Arena, Idx}; use rustc_hash::{FxHashMap, FxHashSet}; +use serde::Serialize; use syntax::SmolStr; use triomphe::Arc; use tt::token_id::Subtree; @@ -39,6 +40,10 @@ pub struct SourceRoot { /// optimize salsa's query structure pub is_library: bool, cargo_file_id: Option, + /// FIXME : @alibektas We know that this is wrong. + /// base-db must stay as a level of abstraction + /// that has no knowledge of such specific files + /// so this should be moved somewhere else. ratoml_file_id: Option, file_set: FileSet, } @@ -116,6 +121,15 @@ pub type CrateId = Idx; #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct CrateName(SmolStr); +impl Serialize for CrateName { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self) + } +} + impl CrateName { /// Creates a crate name, checking for dashes in the string provided. /// Dashes are not allowed in the crate names, diff --git a/crates/project-model/src/cfg_flag.rs b/crates/project-model/src/cfg_flag.rs index e366d441c1bd..05d69726e5ef 100644 --- a/crates/project-model/src/cfg_flag.rs +++ b/crates/project-model/src/cfg_flag.rs @@ -4,8 +4,9 @@ use std::{fmt, str::FromStr}; use cfg::CfgOptions; +use serde::Serialize; -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Debug, Serialize)] pub enum CfgFlag { Atom(String), KeyValue { key: String, value: String }, diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index 80897f7478cf..ae9434743ba1 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -53,7 +53,7 @@ use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition}; use la_arena::RawIdx; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; -use serde::{de, Deserialize}; +use serde::{de, Deserialize, Serialize}; use std::path::PathBuf; use crate::cfg_flag::CfgFlag; @@ -174,14 +174,14 @@ impl ProjectJson { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProjectJsonData { sysroot: Option, sysroot_src: Option, crates: Vec, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct CrateData { display_name: Option, root_module: PathBuf, @@ -203,7 +203,7 @@ struct CrateData { repository: Option, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename = "edition")] enum EditionData { #[serde(rename = "2015")] @@ -224,7 +224,7 @@ impl From for Edition { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct DepData { /// Identifies a crate by position in the crates array. #[serde(rename = "crate")] @@ -233,7 +233,7 @@ struct DepData { name: CrateName, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct CrateSource { include_dirs: Vec, exclude_dirs: Vec, diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 87e85f7e5950..1f53e53f7082 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -44,6 +44,7 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features = tracing-log = "0.1.3" tracing-tree = "0.2.1" triomphe.workspace = true +toml = "0.8.8" nohash-hasher.workspace = true always-assert = "0.1.2" diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index f86c5e6b5f43..8192ca96f06f 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] //! Config used by the language server. //! //! We currently get this config from `initialize` LSP request, which is not the @@ -28,7 +29,8 @@ use project_model::{ CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource, }; use rustc_hash::{FxHashMap, FxHashSet}; -use serde::{de::DeserializeOwned, Deserialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use toml; use vfs::{AbsPath, AbsPathBuf, FileId}; use crate::{ @@ -60,33 +62,33 @@ config_data! { struct GlobalConfigData { /// Whether to insert #[must_use] when generating `as_` methods /// for enum variants. - assist_emitMustUse: bool = "false", + assist_emitMustUse: bool = false, /// Placeholder expression to use for missing expressions in assists. - assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"", + assist_expressionFillDefault: ExprFillDefaultDef = ExprFillDefaultDef::Todo, /// Warm up caches on project load. - cachePriming_enable: bool = "true", + cachePriming_enable: bool = true, /// How many worker threads to handle priming caches. The default `0` means to pick automatically. - cachePriming_numThreads: ParallelCachePrimingNumThreads = "0", + cachePriming_numThreads: ParallelCachePrimingNumThreads = 0u8, /// Automatically refresh project info via `cargo metadata` on /// `Cargo.toml` or `.cargo/config.toml` changes. - cargo_autoreload: bool = "true", + cargo_autoreload: bool = true, /// Run build scripts (`build.rs`) for more precise code analysis. - cargo_buildScripts_enable: bool = "true", + cargo_buildScripts_enable: bool = true, /// Specifies the working directory for running build scripts. /// - "workspace": run build scripts for a workspace in the workspace's root directory. /// This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`. /// - "root": run build scripts in the project's root directory. /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` /// is set. - cargo_buildScripts_invocationLocation: InvocationLocation = "\"workspace\"", + cargo_buildScripts_invocationLocation: InvocationLocation = InvocationLocation::Workspace, /// Specifies the invocation strategy to use when running the build scripts command. /// If `per_workspace` is set, the command will be executed for each workspace. /// If `once` is set, the command will be executed once. /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` /// is set. - cargo_buildScripts_invocationStrategy: InvocationStrategy = "\"per_workspace\"", + cargo_buildScripts_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace, /// Override the command rust-analyzer uses to run build scripts and /// build procedural macros. The command is required to output json /// and should therefore include `--message-format=json` or a similar @@ -105,63 +107,63 @@ config_data! { /// cargo check --quiet --workspace --message-format=json --all-targets /// ``` /// . - cargo_buildScripts_overrideCommand: Option> = "null", + cargo_buildScripts_overrideCommand: Option> = Option::>::None, /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to /// avoid checking unnecessary things. - cargo_buildScripts_useRustcWrapper: bool = "true", + cargo_buildScripts_useRustcWrapper: bool = true, /// List of cfg options to enable with the given values. - cargo_cfgs: FxHashMap = "{}", + cargo_cfgs: FxHashMap = FxHashMap::::default(), /// Extra arguments that are passed to every cargo invocation. - cargo_extraArgs: Vec = "[]", + cargo_extraArgs: Vec = Vec::::new(), /// Extra environment variables that will be set when running cargo, rustc /// or other commands within the workspace. Useful for setting RUSTFLAGS. - cargo_extraEnv: FxHashMap = "{}", + cargo_extraEnv: FxHashMap = FxHashMap::::default(), /// List of features to activate. /// /// Set this to `"all"` to pass `--all-features` to cargo. - cargo_features: CargoFeaturesDef = "[]", + cargo_features: CargoFeaturesDef = CargoFeaturesDef::Selected(Vec::::new()), /// Whether to pass `--no-default-features` to cargo. - cargo_noDefaultFeatures: bool = "false", + cargo_noDefaultFeatures: bool = false, /// Relative path to the sysroot, or "discover" to try to automatically find it via /// "rustc --print sysroot". /// /// Unsetting this disables sysroot loading. /// /// This option does not take effect until rust-analyzer is restarted. - cargo_sysroot: Option = "\"discover\"", + cargo_sysroot: Option = Some("discover".to_string()), /// Relative path to the sysroot library sources. If left unset, this will default to /// `{cargo.sysroot}/lib/rustlib/src/rust/library`. /// /// This option does not take effect until rust-analyzer is restarted. - cargo_sysrootSrc: Option = "null", + cargo_sysrootSrc: Option = Option::::None, /// Compilation target override (target triple). // FIXME(@poliorcetics): move to multiple targets here too, but this will need more work // than `checkOnSave_target` - cargo_target: Option = "null", + cargo_target: Option = Option::::None, /// Unsets the implicit `#[cfg(test)]` for the specified crates. - cargo_unsetTest: Vec = "[\"core\"]", + cargo_unsetTest: Vec = vec!["core".to_string()], /// Run the check command for diagnostics on save. - checkOnSave | checkOnSave_enable: bool = "true", + checkOnSave | checkOnSave_enable: bool = true, /// Check all targets and tests (`--all-targets`). - check_allTargets | checkOnSave_allTargets: bool = "true", + check_allTargets | checkOnSave_allTargets: bool = true, /// Cargo command to use for `cargo check`. - check_command | checkOnSave_command: String = "\"check\"", + check_command | checkOnSave_command: String = "check".to_string(), /// Extra arguments for `cargo check`. - check_extraArgs | checkOnSave_extraArgs: Vec = "[]", + check_extraArgs | checkOnSave_extraArgs: Vec = Vec::::new(), /// Extra environment variables that will be set when running `cargo check`. /// Extends `#rust-analyzer.cargo.extraEnv#`. - check_extraEnv | checkOnSave_extraEnv: FxHashMap = "{}", + check_extraEnv | checkOnSave_extraEnv: FxHashMap = FxHashMap::::default(), /// List of features to activate. Defaults to /// `#rust-analyzer.cargo.features#`. /// /// Set to `"all"` to pass `--all-features` to Cargo. - check_features | checkOnSave_features: Option = "null", + check_features | checkOnSave_features: Option = Option::::None, /// List of `cargo check` (or other command specified in `check.command`) diagnostics to ignore. /// /// For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,... - check_ignore: FxHashSet = "[]", + check_ignore: FxHashSet = FxHashSet::::default(), /// Specifies the working directory for running checks. /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories. // FIXME: Ideally we would support this in some way @@ -169,16 +171,16 @@ config_data! { /// - "root": run checks in the project's root directory. /// This config only has an effect when `#rust-analyzer.cargo.check.overrideCommand#` /// is set. - check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"", + check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = InvocationLocation::Workspace, /// Specifies the invocation strategy to use when running the check command. /// If `per_workspace` is set, the command will be executed for each workspace. /// If `once` is set, the command will be executed once. /// This config only has an effect when `#rust-analyzer.cargo.check.overrideCommand#` /// is set. - check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"", + check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace, /// Whether to pass `--no-default-features` to Cargo. Defaults to /// `#rust-analyzer.cargo.noDefaultFeatures#`. - check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option = "null", + check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option = Option::::None, /// Override the command rust-analyzer uses instead of `cargo check` for /// diagnostics on save. The command is required to output json and /// should therefore include `--message-format=json` or a similar option @@ -201,115 +203,115 @@ config_data! { /// cargo check --workspace --message-format=json --all-targets /// ``` /// . - check_overrideCommand | checkOnSave_overrideCommand: Option> = "null", + check_overrideCommand | checkOnSave_overrideCommand: Option> = Option::>::None, /// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty. /// /// Can be a single target, e.g. `"x86_64-unknown-linux-gnu"` or a list of targets, e.g. /// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`. /// /// Aliased as `"checkOnSave.targets"`. - check_targets | checkOnSave_targets | checkOnSave_target: Option = "null", + check_targets | checkOnSave_targets | checkOnSave_target: Option = Option::::None, /// List of rust-analyzer diagnostics to disable. - diagnostics_disabled: FxHashSet = "[]", + diagnostics_disabled: FxHashSet = FxHashSet::::default(), /// Whether to show native rust-analyzer diagnostics. - diagnostics_enable: bool = "true", + diagnostics_enable: bool = true, /// Whether to show experimental rust-analyzer diagnostics that might /// have more false positives than usual. - diagnostics_experimental_enable: bool = "false", + diagnostics_experimental_enable: bool = false, /// Map of prefixes to be substituted when parsing diagnostic file paths. /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`. - diagnostics_remapPrefix: FxHashMap = "{}", + diagnostics_remapPrefix: FxHashMap = FxHashMap::::default(), /// List of warnings that should be displayed with hint severity. /// /// The warnings will be indicated by faded text or three dots in code /// and will not show up in the `Problems Panel`. - diagnostics_warningsAsHint: Vec = "[]", + diagnostics_warningsAsHint: Vec = Vec::::new(), /// List of warnings that should be displayed with info severity. /// /// The warnings will be indicated by a blue squiggly underline in code /// and a blue icon in the `Problems Panel`. - diagnostics_warningsAsInfo: Vec = "[]", + diagnostics_warningsAsInfo: Vec = Vec::::new(), /// These directories will be ignored by rust-analyzer. They are /// relative to the workspace root, and globs are not supported. You may /// also need to add the folders to Code's `files.watcherExclude`. - files_excludeDirs: Vec = "[]", + files_excludeDirs: Vec = Vec::::new(), /// Controls file watching implementation. - files_watcher: FilesWatcherDef = "\"client\"", + files_watcher: FilesWatcherDef = FilesWatcherDef::Client, /// Enables the experimental support for interpreting tests. - interpret_tests: bool = "false", + interpret_tests: bool = false, /// Whether to show `Debug` lens. Only applies when /// `#rust-analyzer.lens.enable#` is set. - lens_debug_enable: bool = "true", - /// Whether to show CodeLens in Rust files. - lens_enable: bool = "true", + lens_debug_enable: bool = true, + /// Whether to show CodeLens in Rust files. + lens_enable: bool = true, /// Internal config: use custom client-side commands even when the /// client doesn't set the corresponding capability. - lens_forceCustomCommands: bool = "true", + lens_forceCustomCommands: bool = true, /// Whether to show `Implementations` lens. Only applies when /// `#rust-analyzer.lens.enable#` is set. - lens_implementations_enable: bool = "true", + lens_implementations_enable: bool = true, /// Where to render annotations. - lens_location: AnnotationLocation = "\"above_name\"", + lens_location: AnnotationLocation = AnnotationLocation::AboveName, /// Whether to show `References` lens for Struct, Enum, and Union. /// Only applies when `#rust-analyzer.lens.enable#` is set. - lens_references_adt_enable: bool = "false", + lens_references_adt_enable: bool = false, /// Whether to show `References` lens for Enum Variants. /// Only applies when `#rust-analyzer.lens.enable#` is set. - lens_references_enumVariant_enable: bool = "false", + lens_references_enumVariant_enable: bool = false, /// Whether to show `Method References` lens. Only applies when /// `#rust-analyzer.lens.enable#` is set. - lens_references_method_enable: bool = "false", + lens_references_method_enable: bool = false, /// Whether to show `References` lens for Trait. /// Only applies when `#rust-analyzer.lens.enable#` is set. - lens_references_trait_enable: bool = "false", + lens_references_trait_enable: bool = false, /// Whether to show `Run` lens. Only applies when /// `#rust-analyzer.lens.enable#` is set. - lens_run_enable: bool = "true", + lens_run_enable: bool = true, /// Disable project auto-discovery in favor of explicitly specified set /// of projects. /// /// Elements must be paths pointing to `Cargo.toml`, /// `rust-project.json`, or JSON objects in `rust-project.json` format. - linkedProjects: Vec = "[]", + linkedProjects: Vec = Vec::::new(), /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128. - lru_capacity: Option = "null", + lru_capacity: Option = Option::::None, /// Sets the LRU capacity of the specified queries. - lru_query_capacities: FxHashMap, usize> = "{}", + lru_query_capacities: FxHashMap, usize> = FxHashMap::, usize>::default(), /// Whether to show `can't find Cargo.toml` error message. - notifications_cargoTomlNotFound: bool = "true", + notifications_cargoTomlNotFound: bool = true, /// How many worker threads in the main loop. The default `null` means to pick automatically. - numThreads: Option = "null", + numThreads: Option = Option::::None, /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set. - procMacro_attributes_enable: bool = "true", + procMacro_attributes_enable: bool = true, /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`. - procMacro_enable: bool = "true", + procMacro_enable: bool = true, /// These proc-macros will be ignored when trying to expand them. /// /// This config takes a map of crate names with the exported proc-macro names to ignore as values. - procMacro_ignored: FxHashMap, Box<[Box]>> = "{}", + procMacro_ignored: FxHashMap, Box<[Box]>> = FxHashMap::, Box<[Box]>>::default(), /// Internal config, path to proc-macro server executable. - procMacro_server: Option = "null", + procMacro_server: Option = Option::::None, /// Exclude imports from find-all-references. - references_excludeImports: bool = "false", + references_excludeImports: bool = false, /// Command to be executed instead of 'cargo' for runnables. - runnables_command: Option = "null", + runnables_command: Option = Option::::None, /// Additional arguments to be passed to cargo for runnables such as /// tests or binaries. For example, it may be `--release`. - runnables_extraArgs: Vec = "[]", + runnables_extraArgs: Vec = Vec::::new(), /// Optional path to a rust-analyzer specific target directory. /// This prevents rust-analyzer's `cargo check` from locking the `Cargo.lock` @@ -317,7 +319,7 @@ config_data! { /// /// Set to `true` to use a subdirectory of the existing target directory or /// set to a path relative to the workspace to use that path. - rust_analyzerTargetDir: Option = "null", + rust_analyzerTargetDir: Option = Option::::None, /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private /// projects, or "discover" to try to automatically find it if the `rustc-dev` component @@ -327,274 +329,219 @@ config_data! { /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it. /// /// This option does not take effect until rust-analyzer is restarted. - rustc_source: Option = "null", + rustc_source: Option = Option::::None, /// Additional arguments to `rustfmt`. - rustfmt_extraArgs: Vec = "[]", + rustfmt_extraArgs: Vec = Vec::::new(), /// Advanced option, fully override the command rust-analyzer uses for /// formatting. This should be the equivalent of `rustfmt` here, and /// not that of `cargo fmt`. The file contents will be passed on the /// standard input and the formatted result will be read from the /// standard output. - rustfmt_overrideCommand: Option> = "null", + rustfmt_overrideCommand: Option> = Option::>::None, /// Enables the use of rustfmt's unstable range formatting command for the /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only /// available on a nightly build. - rustfmt_rangeFormatting_enable: bool = "false", + rustfmt_rangeFormatting_enable: bool = false, /// Show full signature of the callable. Only shows parameters if disabled. - signatureInfo_detail: SignatureDetail = "\"full\"", + signatureInfo_detail: SignatureDetail = SignatureDetail::Full, /// Show documentation. - signatureInfo_documentation_enable: bool = "true", + signatureInfo_documentation_enable: bool = true, /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. - typing_autoClosingAngleBrackets_enable: bool = "false", + typing_autoClosingAngleBrackets_enable: bool = false, /// Workspace symbol search kind. - workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"", + workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = WorkspaceSymbolSearchKindDef::OnlyTypes, /// Limits the number of items returned from a workspace symbol search (Defaults to 128). /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search. /// Other clients requires all results upfront and might require a higher limit. - workspace_symbol_search_limit: usize = "128", + workspace_symbol_search_limit: usize = 128, /// Workspace symbol search scope. - workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"", - } - -} - -config_data! { + workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = WorkspaceSymbolSearchScopeDef::Workspace, + }, struct LocalConfigData { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. - completion_autoimport_enable: bool = "true", + completion_autoimport_enable: bool = true, /// Toggles the additional completions that automatically show method calls and field accesses /// with `self` prefixed to them when inside a method. - completion_autoself_enable: bool = "true", + completion_autoself_enable: bool = true, /// Whether to add parenthesis and argument snippets when completing function. - completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"", + completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments, /// Whether to show full function/method signatures in completion docs. - completion_fullFunctionSignatures_enable: bool = "false", + completion_fullFunctionSignatures_enable: bool = false, /// Maximum number of completions to return. If `None`, the limit is infinite. - completion_limit: Option = "null", + completion_limit: Option = Option::::None, /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. - completion_postfix_enable: bool = "true", + completion_postfix_enable: bool = true, /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. - completion_privateEditable_enable: bool = "false", + completion_privateEditable_enable: bool = false, /// Custom completion snippets. // NOTE: Keep this list in sync with the feature docs of user snippets. - completion_snippets_custom: FxHashMap = r#"{ - "Arc::new": { - "postfix": "arc", - "body": "Arc::new(${receiver})", - "requires": "std::sync::Arc", - "description": "Put the expression into an `Arc`", - "scope": "expr" - }, - "Rc::new": { - "postfix": "rc", - "body": "Rc::new(${receiver})", - "requires": "std::rc::Rc", - "description": "Put the expression into an `Rc`", - "scope": "expr" - }, - "Box::pin": { - "postfix": "pinbox", - "body": "Box::pin(${receiver})", - "requires": "std::boxed::Box", - "description": "Put the expression into a pinned `Box`", - "scope": "expr" - }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, - "Err": { - "postfix": "err", - "body": "Err(${receiver})", - "description": "Wrap the expression in a `Result::Err`", - "scope": "expr" - }, - "Some": { - "postfix": "some", - "body": "Some(${receiver})", - "description": "Wrap the expression in an `Option::Some`", - "scope": "expr" - } - }"#, + completion_snippets_custom: FxHashMap = FxHashMap::::default() , /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. - highlightRelated_breakPoints_enable: bool = "true", + highlightRelated_breakPoints_enable: bool = true, /// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure. - highlightRelated_closureCaptures_enable: bool = "true", + highlightRelated_closureCaptures_enable: bool = true, /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`). - highlightRelated_exitPoints_enable: bool = "true", + highlightRelated_exitPoints_enable: bool = true, /// Enables highlighting of related references while the cursor is on any identifier. - highlightRelated_references_enable: bool = "true", + highlightRelated_references_enable: bool = true, /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords. - highlightRelated_yieldPoints_enable: bool = "true", + highlightRelated_yieldPoints_enable: bool = true, /// Whether to show `Debug` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_debug_enable: bool = "true", + hover_actions_debug_enable: bool = true, /// Whether to show HoverActions in Rust files. - hover_actions_enable: bool = "true", + hover_actions_enable: bool = true, /// Whether to show `Go to Type Definition` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_gotoTypeDef_enable: bool = "true", + hover_actions_gotoTypeDef_enable: bool = true, /// Whether to show `Implementations` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_implementations_enable: bool = "true", + hover_actions_implementations_enable: bool = true, /// Whether to show `References` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_references_enable: bool = "false", + hover_actions_references_enable: bool = false, /// Whether to show `Run` action. Only applies when /// `#rust-analyzer.hover.actions.enable#` is set. - hover_actions_run_enable: bool = "true", + hover_actions_run_enable: bool = true, /// Whether to show documentation on hover. - hover_documentation_enable: bool = "true", + hover_documentation_enable: bool = true, /// Whether to show keyword hover popups. Only applies when /// `#rust-analyzer.hover.documentation.enable#` is set. - hover_documentation_keywords_enable: bool = "true", + hover_documentation_keywords_enable: bool = true, /// Use markdown syntax for links on hover. - hover_links_enable: bool = "true", + hover_links_enable: bool = true, /// How to render the align information in a memory layout hover. - hover_memoryLayout_alignment: Option = "\"hexadecimal\"", + hover_memoryLayout_alignment: Option = Option::::Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), /// Whether to show memory layout data on hover. - hover_memoryLayout_enable: bool = "true", + hover_memoryLayout_enable: bool = true, /// How to render the niche information in a memory layout hover. - hover_memoryLayout_niches: Option = "false", + hover_memoryLayout_niches: Option = Option::::Some(false), /// How to render the offset information in a memory layout hover. - hover_memoryLayout_offset: Option = "\"hexadecimal\"", + hover_memoryLayout_offset: Option = Option::::Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), /// How to render the size information in a memory layout hover. - hover_memoryLayout_size: Option = "\"both\"", + hover_memoryLayout_size: Option =Option::::Some(MemoryLayoutHoverRenderKindDef::Both), + + /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file. + imports_granularity_enforce: bool = false, + /// How imports should be grouped into use statements. + imports_granularity_group: ImportGranularityDef = ImportGranularityDef::Crate, + /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines. + imports_group_enable: bool = true, + /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`. + imports_merge_glob: bool = true, + /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate. + imports_prefer_no_std: bool = false, + /// The path structure for newly inserted paths to use. + imports_prefix: ImportPrefixDef = ImportPrefixDef::Plain, + /// Whether to show inlay type hints for binding modes. - inlayHints_bindingModeHints_enable: bool = "false", + inlayHints_bindingModeHints_enable: bool = false, /// Whether to show inlay type hints for method chains. - inlayHints_chainingHints_enable: bool = "true", + inlayHints_chainingHints_enable: bool = true, /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to. - inlayHints_closingBraceHints_enable: bool = "true", + inlayHints_closingBraceHints_enable: bool = true, /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1 /// to always show them). - inlayHints_closingBraceHints_minLines: usize = "25", + inlayHints_closingBraceHints_minLines: usize = 25, /// Whether to show inlay hints for closure captures. - inlayHints_closureCaptureHints_enable: bool = "false", + inlayHints_closureCaptureHints_enable: bool = false, /// Whether to show inlay type hints for return types of closures. - inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"", + inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = ClosureReturnTypeHintsDef::Never, /// Closure notation in type and chaining inlay hints. - inlayHints_closureStyle: ClosureStyle = "\"impl_fn\"", + inlayHints_closureStyle: ClosureStyle = ClosureStyle::ImplFn, /// Whether to show enum variant discriminant hints. - inlayHints_discriminantHints_enable: DiscriminantHintsDef = "\"never\"", + inlayHints_discriminantHints_enable: DiscriminantHintsDef = DiscriminantHintsDef::Never, /// Whether to show inlay hints for type adjustments. - inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"", + inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = AdjustmentHintsDef::Never, /// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks. - inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false", + inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = false, /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc). - inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"", + inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = AdjustmentHintsModeDef::Prefix, /// Whether to show inlay type hints for elided lifetimes in function signatures. - inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"", + inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = LifetimeElisionDef::Never, /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. - inlayHints_lifetimeElisionHints_useParameterNames: bool = "false", + inlayHints_lifetimeElisionHints_useParameterNames: bool = false, /// Maximum length for inlay hints. Set to null to have an unlimited length. - inlayHints_maxLength: Option = "25", + inlayHints_maxLength: Option = Option::::Some(25), /// Whether to show function parameter name inlay hints at the call /// site. - inlayHints_parameterHints_enable: bool = "true", + inlayHints_parameterHints_enable: bool = true, /// Whether to show inlay hints for compiler inserted reborrows. /// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#. - inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"", + inlayHints_reborrowHints_enable: ReborrowHintsDef = ReborrowHintsDef::Never, /// Whether to render leading colons for type hints, and trailing colons for parameter hints. - inlayHints_renderColons: bool = "true", + inlayHints_renderColons: bool = true, /// Whether to show inlay type hints for variables. - inlayHints_typeHints_enable: bool = "true", + inlayHints_typeHints_enable: bool = true, /// Whether to hide inlay type hints for `let` statements that initialize to a closure. /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`. - inlayHints_typeHints_hideClosureInitialization: bool = "false", + inlayHints_typeHints_hideClosureInitialization: bool = false, /// Whether to hide inlay type hints for constructors. - inlayHints_typeHints_hideNamedConstructor: bool = "false", - - /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file. - imports_granularity_enforce: bool = "false", - /// How imports should be grouped into use statements. - imports_granularity_group: ImportGranularityDef = "\"crate\"", - /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines. - imports_group_enable: bool = "true", - /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`. - imports_merge_glob: bool = "true", - /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate. - imports_prefer_no_std: bool = "false", - /// The path structure for newly inserted paths to use. - imports_prefix: ImportPrefixDef = "\"plain\"", + inlayHints_typeHints_hideNamedConstructor: bool = false, /// Join lines merges consecutive declaration and initialization of an assignment. - joinLines_joinAssignments: bool = "true", + joinLines_joinAssignments: bool = true, /// Join lines inserts else between consecutive ifs. - joinLines_joinElseIf: bool = "true", + joinLines_joinElseIf: bool = true, /// Join lines removes trailing commas. - joinLines_removeTrailingComma: bool = "true", + joinLines_removeTrailingComma: bool = true, /// Join lines unwraps trivial blocks. - joinLines_unwrapTrivialBlock: bool = "true", + joinLines_unwrapTrivialBlock: bool = true, /// Inject additional highlighting into doc comments. /// /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra /// doc links. - semanticHighlighting_doc_comment_inject_enable: bool = "true", + semanticHighlighting_doc_comment_inject_enable: bool = true, /// Whether the server is allowed to emit non-standard tokens and modifiers. - semanticHighlighting_nonStandardTokens: bool = "true", + semanticHighlighting_nonStandardTokens: bool = true, /// Use semantic tokens for operators. /// /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when /// they are tagged with modifiers. - semanticHighlighting_operator_enable: bool = "true", + semanticHighlighting_operator_enable: bool = true, /// Use specialized semantic tokens for operators. /// /// When enabled, rust-analyzer will emit special token types for operator tokens instead /// of the generic `operator` token type. - semanticHighlighting_operator_specialization_enable: bool = "false", + semanticHighlighting_operator_specialization_enable: bool = false, /// Use semantic tokens for punctuation. /// /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when /// they are tagged with modifiers or have a special role. - semanticHighlighting_punctuation_enable: bool = "false", + semanticHighlighting_punctuation_enable: bool = false, /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro /// calls. - semanticHighlighting_punctuation_separate_macro_bang: bool = "false", + semanticHighlighting_punctuation_separate_macro_bang: bool = false, /// Use specialized semantic tokens for punctuation. /// /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead /// of the generic `punctuation` token type. - semanticHighlighting_punctuation_specialization_enable: bool = "false", + semanticHighlighting_punctuation_specialization_enable: bool = false, /// Use semantic tokens for strings. /// /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars. /// By disabling semantic tokens for strings, other grammars can be used to highlight /// their contents. - semanticHighlighting_strings_enable: bool = "true", - } -} - -config_data! {struct ClientConfigData {}} - -#[derive(Debug, Clone)] -struct ConfigData { - local: LocalConfigData, - global: GlobalConfigData, - client: ClientConfigData, + semanticHighlighting_strings_enable: bool = true, + }, + struct ClientConfigData {} } impl Default for ConfigData { fn default() -> Self { - ConfigData { - local: LocalConfigData::from_json(serde_json::Value::Null, &mut Vec::new()), - global: GlobalConfigData::from_json(serde_json::Value::Null, &mut Vec::new()), - client: ClientConfigData::from_json(serde_json::Value::Null, &mut Vec::new()), - } + ConfigData::from_json(serde_json::Value::Null, &mut Vec::new()) } } @@ -970,7 +917,7 @@ pub struct LensConfig { pub location: AnnotationLocation, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AnnotationLocation { AboveName, @@ -1194,7 +1141,7 @@ impl Config { } let mut errors = Vec::new(); self.detached_files = - get_field::>(&mut json, &mut errors, "detachedFiles", None, "[]") + get_field::>(&mut json, &mut errors, "detachedFiles", None, vec![]) .into_iter() .map(AbsPathBuf::assert) .collect(); @@ -1250,7 +1197,7 @@ impl Config { } pub fn json_schema() -> serde_json::Value { - GlobalConfigData::json_schema() + ConfigData::json_schema() } pub fn root_path(&self) -> &AbsPathBuf { @@ -2004,7 +1951,7 @@ mod de_unit_v { named_unit_variant!(both); } -#[derive(Deserialize, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] #[serde(rename_all = "snake_case")] enum SnippetScopeDef { Expr, @@ -2018,7 +1965,7 @@ impl Default for SnippetScopeDef { } } -#[derive(Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[serde(default)] struct SnippetDef { #[serde(deserialize_with = "single_or_array")] @@ -2064,21 +2011,21 @@ where deserializer.deserialize_any(SingleOrVec) } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ManifestOrProjectJson { Manifest(PathBuf), ProjectJson(ProjectJsonData), } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ExprFillDefaultDef { Todo, Default, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportGranularityDef { Preserve, @@ -2087,7 +2034,7 @@ enum ImportGranularityDef { Module, } -#[derive(Deserialize, Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[serde(rename_all = "snake_case")] enum CallableCompletionDef { FillArguments, @@ -2095,7 +2042,7 @@ enum CallableCompletionDef { None, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum CargoFeaturesDef { #[serde(deserialize_with = "de_unit_v::all")] @@ -2103,24 +2050,24 @@ enum CargoFeaturesDef { Selected(Vec), } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum InvocationStrategy { Once, PerWorkspace, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct CheckOnSaveTargets(#[serde(deserialize_with = "single_or_array")] Vec); -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum InvocationLocation { Root, Workspace, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum LifetimeElisionDef { #[serde(deserialize_with = "true_or_always")] @@ -2131,7 +2078,7 @@ enum LifetimeElisionDef { SkipTrivial, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ClosureReturnTypeHintsDef { #[serde(deserialize_with = "true_or_always")] @@ -2142,7 +2089,7 @@ enum ClosureReturnTypeHintsDef { WithBlock, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ClosureStyle { ImplFn, @@ -2151,7 +2098,7 @@ enum ClosureStyle { Hide, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ReborrowHintsDef { #[serde(deserialize_with = "true_or_always")] @@ -2162,7 +2109,7 @@ enum ReborrowHintsDef { Mutable, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum AdjustmentHintsDef { #[serde(deserialize_with = "true_or_always")] @@ -2173,7 +2120,7 @@ enum AdjustmentHintsDef { Reborrow, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum DiscriminantHintsDef { #[serde(deserialize_with = "true_or_always")] @@ -2184,7 +2131,7 @@ enum DiscriminantHintsDef { Fieldless, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum AdjustmentHintsModeDef { Prefix, @@ -2193,7 +2140,7 @@ enum AdjustmentHintsModeDef { PreferPostfix, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum FilesWatcherDef { Client, @@ -2201,7 +2148,7 @@ enum FilesWatcherDef { Server, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportPrefixDef { Plain, @@ -2211,28 +2158,28 @@ enum ImportPrefixDef { ByCrate, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum WorkspaceSymbolSearchScopeDef { Workspace, WorkspaceAndDependencies, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum SignatureDetail { Full, Parameters, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum WorkspaceSymbolSearchKindDef { OnlyTypes, AllSymbols, } -#[derive(Deserialize, Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[serde(rename_all = "snake_case")] #[serde(untagged)] pub enum MemoryLayoutHoverRenderKindDef { @@ -2244,7 +2191,7 @@ pub enum MemoryLayoutHoverRenderKindDef { Both, } -#[derive(Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "snake_case")] #[serde(untagged)] pub enum TargetDirectory { @@ -2258,7 +2205,20 @@ macro_rules! _config_data { $(#[doc=$doc:literal])* $field:ident $(| $alias:ident)*: $ty:ty = $default:expr, )* - }) => { + }, + struct $name2:ident { + $( + $(#[doc=$doc2:literal])* + $field2:ident $(| $alias2:ident)*: $ty2:ty = $default2:expr, + )* + }, + struct $name3:ident { + $( + $(#[doc=$doc3:literal])* + $field3:ident $(| $alias3:ident)*: $ty3:ty = $default3:expr, + )* + } + ) => { #[allow(non_snake_case)] #[derive(Debug, Clone)] struct $name { $($field: $ty,)* } @@ -2275,13 +2235,131 @@ macro_rules! _config_data { )*} } + } + + #[allow(non_snake_case)] + #[derive(Debug, Clone)] + struct $name2 { $($field2: $ty2,)* } + impl $name2 { + fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name2 { + $name2 {$( + $field2: get_field( + &mut json, + error_sink, + stringify!($field2), + None$(.or(Some(stringify!($alias2))))*, + $default2, + ), + )*} + } + } + + #[allow(non_snake_case)] + #[derive(Debug, Clone)] + struct $name3 { $($field3: $ty3,)* } + impl $name3 { + fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name3 { + $name3 {$( + $field3: get_field( + &mut json, + error_sink, + stringify!($field3), + None$(.or(Some(stringify!($alias3))))*, + $default3, + ), + )*} + } + } + + #[allow(non_snake_case)] + #[derive(Debug, Clone, Deserialize, Serialize)] + struct ConfigData { + $($field: $ty,)* + $($field2: $ty2,)* + $($field3: $ty3,)* + } + + impl ConfigData { + + fn from_toml(mut toml: toml::Table , error_sink: &mut Vec<(String, toml::de::Error)>) -> ConfigData { + ConfigData {$( + $field: get_field_toml::<$ty>( + &mut toml, + error_sink, + stringify!($field), + None$(.or(Some(stringify!($alias))))*, + $default, + ), + )* + $( + $field2: get_field_toml::<$ty2>( + &mut toml, + error_sink, + stringify!($field2), + None$(.or(Some(stringify!($alias2))))*, + $default2, + ), + )* + $( + $field3: get_field_toml::<$ty3>( + &mut toml, + error_sink, + stringify!($field3), + None$(.or(Some(stringify!($alias3))))*, + $default3, + ), + )*} + } + + fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> ConfigData { + ConfigData {$( + $field: get_field::<$ty>( + &mut json, + error_sink, + stringify!($field), + None$(.or(Some(stringify!($alias))))*, + $default, + ), + )* + $( + $field2: get_field::<$ty2>( + &mut json, + error_sink, + stringify!($field2), + None$(.or(Some(stringify!($alias2))))*, + $default2, + ), + )* + $( + $field3: get_field::<$ty3>( + &mut json, + error_sink, + stringify!($field3), + None$(.or(Some(stringify!($alias3))))*, + $default3, + ), + )*} + } + fn json_schema() -> serde_json::Value { schema(&[ $({ let field = stringify!($field); let ty = stringify!($ty); - (field, ty, &[$($doc),*], $default) + (field, ty, &[$($doc),*], serde_json::to_string(&$default).unwrap().as_str()) + },)* + $({ + let field = stringify!($field2); + let ty = stringify!($ty2); + + (field, ty, &[$($doc2),*], serde_json::to_string(&$default2).unwrap().as_str()) + },)* + $({ + let field = stringify!($field3); + let ty = stringify!($ty3); + + (field, ty, &[$($doc3),*], serde_json::to_string(&$default3).unwrap().as_str()) },)* ]) } @@ -2290,29 +2368,68 @@ macro_rules! _config_data { fn manual() -> String { manual(&[ $({ - let field = stringify!($field); - let ty = stringify!($ty); + let field = stringify!($field3); + let ty = stringify!($ty3); - (field, ty, &[$($doc),*], $default) + (field, ty, &[$($doc3),*], $default3) },)* ]) } } - // #[test] - // fn fields_are_sorted() { - // [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); - // } + #[test] + fn fields_are_sorted() { + [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); + [$(stringify!($field2)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); + // [$(stringify!($field3)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); + } }; } use _config_data as config_data; +fn get_field_toml( + val: &toml::Table, + error_sink: &mut Vec<(String, toml::de::Error)>, + field: &'static str, + alias: Option<&'static str>, + default: T, +) -> T { + alias + .into_iter() + .chain(iter::once(field)) + .filter_map(move |field| { + let subkeys = field.split('_'); + let mut v = val; + for subkey in subkeys { + if let Some(val) = v.get(subkey) { + if let Some(map) = val.as_table() { + v = map; + } else { + return Some(toml::Value::try_into(val.clone()).map_err(|e| (e, v))); + } + } else { + return None; + } + } + None + }) + .find(Result::is_ok) + .and_then(|res| match res { + Ok(it) => Some(it), + Err((e, pointer)) => { + error_sink.push((pointer.to_string(), e)); + None + } + }) + .unwrap_or(default) +} + fn get_field( json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>, field: &'static str, alias: Option<&'static str>, - default: &str, + default: T, ) -> T { // XXX: check alias first, to work around the VS Code where it pre-fills the // defaults instead of sending an empty object. @@ -2334,9 +2451,7 @@ fn get_field( None } }) - .unwrap_or_else(|| { - serde_json::from_str(default).unwrap_or_else(|e| panic!("{e} on: `{default}`")) - }) + .unwrap_or(default) } fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value { @@ -2795,8 +2910,7 @@ mod tests { #[test] fn generate_config_documentation() { let docs_path = project_root().join("docs/user/generated_config.adoc"); - // ATTENTION - let expected = GlobalConfigData::manual(); + let expected = ConfigData::manual(); ensure_file_contents(&docs_path, &expected); } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 80e7db3667fa..2ab7fcd5a673 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -131,7 +131,6 @@ impl GlobalState { Event::Lsp(lsp_server::Message::Notification(Notification { method, .. })) if method == lsp_types::notification::Exit::METHOD ) { - dbg!("EXIT"); return Ok(()); } self.handle_event(event)?; diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index c1898729adec..5f0edd56566a 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -173,7 +173,6 @@ impl GlobalState { } if let Err(k) = self.fetch_workspace_error() { - dbg!(&k); status.health = lsp_ext::Health::Error; message.push_str("Failed to load workspaces.\n\n"); } From 46110f428fa3bc33e1d0e286e03503657c127997 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 18 Jan 2024 22:33:55 +1100 Subject: [PATCH 12/52] Remove need to specify `Option::::None` etc in `config_data!` --- crates/rust-analyzer/src/config.rs | 102 ++++++++++++++--------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 8192ca96f06f..beef0b3bd6a0 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -107,21 +107,21 @@ config_data! { /// cargo check --quiet --workspace --message-format=json --all-targets /// ``` /// . - cargo_buildScripts_overrideCommand: Option> = Option::>::None, + cargo_buildScripts_overrideCommand: Option> = None, /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to /// avoid checking unnecessary things. cargo_buildScripts_useRustcWrapper: bool = true, /// List of cfg options to enable with the given values. - cargo_cfgs: FxHashMap = FxHashMap::::default(), + cargo_cfgs: FxHashMap = FxHashMap::default(), /// Extra arguments that are passed to every cargo invocation. - cargo_extraArgs: Vec = Vec::::new(), + cargo_extraArgs: Vec = vec![], /// Extra environment variables that will be set when running cargo, rustc /// or other commands within the workspace. Useful for setting RUSTFLAGS. - cargo_extraEnv: FxHashMap = FxHashMap::::default(), + cargo_extraEnv: FxHashMap = FxHashMap::default(), /// List of features to activate. /// /// Set this to `"all"` to pass `--all-features` to cargo. - cargo_features: CargoFeaturesDef = CargoFeaturesDef::Selected(Vec::::new()), + cargo_features: CargoFeaturesDef = CargoFeaturesDef::Selected(vec![]), /// Whether to pass `--no-default-features` to cargo. cargo_noDefaultFeatures: bool = false, /// Relative path to the sysroot, or "discover" to try to automatically find it via @@ -135,11 +135,11 @@ config_data! { /// `{cargo.sysroot}/lib/rustlib/src/rust/library`. /// /// This option does not take effect until rust-analyzer is restarted. - cargo_sysrootSrc: Option = Option::::None, + cargo_sysrootSrc: Option = None, /// Compilation target override (target triple). // FIXME(@poliorcetics): move to multiple targets here too, but this will need more work // than `checkOnSave_target` - cargo_target: Option = Option::::None, + cargo_target: Option = None, /// Unsets the implicit `#[cfg(test)]` for the specified crates. cargo_unsetTest: Vec = vec!["core".to_string()], @@ -151,19 +151,19 @@ config_data! { /// Cargo command to use for `cargo check`. check_command | checkOnSave_command: String = "check".to_string(), /// Extra arguments for `cargo check`. - check_extraArgs | checkOnSave_extraArgs: Vec = Vec::::new(), + check_extraArgs | checkOnSave_extraArgs: Vec = vec![], /// Extra environment variables that will be set when running `cargo check`. /// Extends `#rust-analyzer.cargo.extraEnv#`. - check_extraEnv | checkOnSave_extraEnv: FxHashMap = FxHashMap::::default(), + check_extraEnv | checkOnSave_extraEnv: FxHashMap = FxHashMap::default(), /// List of features to activate. Defaults to /// `#rust-analyzer.cargo.features#`. /// /// Set to `"all"` to pass `--all-features` to Cargo. - check_features | checkOnSave_features: Option = Option::::None, + check_features | checkOnSave_features: Option = None, /// List of `cargo check` (or other command specified in `check.command`) diagnostics to ignore. /// /// For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,... - check_ignore: FxHashSet = FxHashSet::::default(), + check_ignore: FxHashSet = FxHashSet::default(), /// Specifies the working directory for running checks. /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories. // FIXME: Ideally we would support this in some way @@ -180,7 +180,7 @@ config_data! { check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace, /// Whether to pass `--no-default-features` to Cargo. Defaults to /// `#rust-analyzer.cargo.noDefaultFeatures#`. - check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option = Option::::None, + check_noDefaultFeatures | checkOnSave_noDefaultFeatures: Option = None, /// Override the command rust-analyzer uses instead of `cargo check` for /// diagnostics on save. The command is required to output json and /// should therefore include `--message-format=json` or a similar option @@ -203,18 +203,18 @@ config_data! { /// cargo check --workspace --message-format=json --all-targets /// ``` /// . - check_overrideCommand | checkOnSave_overrideCommand: Option> = Option::>::None, + check_overrideCommand | checkOnSave_overrideCommand: Option> = None, /// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty. /// /// Can be a single target, e.g. `"x86_64-unknown-linux-gnu"` or a list of targets, e.g. /// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`. /// /// Aliased as `"checkOnSave.targets"`. - check_targets | checkOnSave_targets | checkOnSave_target: Option = Option::::None, + check_targets | checkOnSave_targets | checkOnSave_target: Option = None, /// List of rust-analyzer diagnostics to disable. - diagnostics_disabled: FxHashSet = FxHashSet::::default(), + diagnostics_disabled: FxHashSet = FxHashSet::default(), /// Whether to show native rust-analyzer diagnostics. diagnostics_enable: bool = true, /// Whether to show experimental rust-analyzer diagnostics that might @@ -222,21 +222,21 @@ config_data! { diagnostics_experimental_enable: bool = false, /// Map of prefixes to be substituted when parsing diagnostic file paths. /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`. - diagnostics_remapPrefix: FxHashMap = FxHashMap::::default(), + diagnostics_remapPrefix: FxHashMap = FxHashMap::default(), /// List of warnings that should be displayed with hint severity. /// /// The warnings will be indicated by faded text or three dots in code /// and will not show up in the `Problems Panel`. - diagnostics_warningsAsHint: Vec = Vec::::new(), + diagnostics_warningsAsHint: Vec = vec![], /// List of warnings that should be displayed with info severity. /// /// The warnings will be indicated by a blue squiggly underline in code /// and a blue icon in the `Problems Panel`. - diagnostics_warningsAsInfo: Vec = Vec::::new(), + diagnostics_warningsAsInfo: Vec = vec![], /// These directories will be ignored by rust-analyzer. They are /// relative to the workspace root, and globs are not supported. You may /// also need to add the folders to Code's `files.watcherExclude`. - files_excludeDirs: Vec = Vec::::new(), + files_excludeDirs: Vec = vec![], /// Controls file watching implementation. files_watcher: FilesWatcherDef = FilesWatcherDef::Client, @@ -280,18 +280,18 @@ config_data! { /// /// Elements must be paths pointing to `Cargo.toml`, /// `rust-project.json`, or JSON objects in `rust-project.json` format. - linkedProjects: Vec = Vec::::new(), + linkedProjects: Vec = vec![], /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128. - lru_capacity: Option = Option::::None, + lru_capacity: Option = None, /// Sets the LRU capacity of the specified queries. - lru_query_capacities: FxHashMap, usize> = FxHashMap::, usize>::default(), + lru_query_capacities: FxHashMap, usize> = FxHashMap::default(), /// Whether to show `can't find Cargo.toml` error message. notifications_cargoTomlNotFound: bool = true, /// How many worker threads in the main loop. The default `null` means to pick automatically. - numThreads: Option = Option::::None, + numThreads: Option = None, /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set. procMacro_attributes_enable: bool = true, @@ -300,18 +300,18 @@ config_data! { /// These proc-macros will be ignored when trying to expand them. /// /// This config takes a map of crate names with the exported proc-macro names to ignore as values. - procMacro_ignored: FxHashMap, Box<[Box]>> = FxHashMap::, Box<[Box]>>::default(), + procMacro_ignored: FxHashMap, Box<[Box]>> = FxHashMap::default(), /// Internal config, path to proc-macro server executable. - procMacro_server: Option = Option::::None, + procMacro_server: Option = None, /// Exclude imports from find-all-references. references_excludeImports: bool = false, /// Command to be executed instead of 'cargo' for runnables. - runnables_command: Option = Option::::None, + runnables_command: Option = None, /// Additional arguments to be passed to cargo for runnables such as /// tests or binaries. For example, it may be `--release`. - runnables_extraArgs: Vec = Vec::::new(), + runnables_extraArgs: Vec = vec![], /// Optional path to a rust-analyzer specific target directory. /// This prevents rust-analyzer's `cargo check` from locking the `Cargo.lock` @@ -319,7 +319,7 @@ config_data! { /// /// Set to `true` to use a subdirectory of the existing target directory or /// set to a path relative to the workspace to use that path. - rust_analyzerTargetDir: Option = Option::::None, + rust_analyzerTargetDir: Option = None, /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private /// projects, or "discover" to try to automatically find it if the `rustc-dev` component @@ -329,16 +329,16 @@ config_data! { /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it. /// /// This option does not take effect until rust-analyzer is restarted. - rustc_source: Option = Option::::None, + rustc_source: Option = None, /// Additional arguments to `rustfmt`. - rustfmt_extraArgs: Vec = Vec::::new(), + rustfmt_extraArgs: Vec = vec![], /// Advanced option, fully override the command rust-analyzer uses for /// formatting. This should be the equivalent of `rustfmt` here, and /// not that of `cargo fmt`. The file contents will be passed on the /// standard input and the formatted result will be read from the /// standard output. - rustfmt_overrideCommand: Option> = Option::>::None, + rustfmt_overrideCommand: Option> = None, /// Enables the use of rustfmt's unstable range formatting command for the /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only /// available on a nightly build. @@ -374,14 +374,14 @@ config_data! { /// Whether to show full function/method signatures in completion docs. completion_fullFunctionSignatures_enable: bool = false, /// Maximum number of completions to return. If `None`, the limit is infinite. - completion_limit: Option = Option::::None, + completion_limit: Option = None, /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. completion_postfix_enable: bool = true, /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. completion_privateEditable_enable: bool = false, /// Custom completion snippets. // NOTE: Keep this list in sync with the feature docs of user snippets. - completion_snippets_custom: FxHashMap = FxHashMap::::default() , + completion_snippets_custom: FxHashMap = FxHashMap::default(), /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. highlightRelated_breakPoints_enable: bool = true, @@ -420,15 +420,15 @@ config_data! { /// Use markdown syntax for links on hover. hover_links_enable: bool = true, /// How to render the align information in a memory layout hover. - hover_memoryLayout_alignment: Option = Option::::Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), + hover_memoryLayout_alignment: Option = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), /// Whether to show memory layout data on hover. hover_memoryLayout_enable: bool = true, /// How to render the niche information in a memory layout hover. - hover_memoryLayout_niches: Option = Option::::Some(false), + hover_memoryLayout_niches: Option = Some(false), /// How to render the offset information in a memory layout hover. - hover_memoryLayout_offset: Option = Option::::Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), + hover_memoryLayout_offset: Option = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), /// How to render the size information in a memory layout hover. - hover_memoryLayout_size: Option =Option::::Some(MemoryLayoutHoverRenderKindDef::Both), + hover_memoryLayout_size: Option = Some(MemoryLayoutHoverRenderKindDef::Both), /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file. imports_granularity_enforce: bool = false, @@ -472,7 +472,7 @@ config_data! { /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. inlayHints_lifetimeElisionHints_useParameterNames: bool = false, /// Maximum length for inlay hints. Set to null to have an unlimited length. - inlayHints_maxLength: Option = Option::::Some(25), + inlayHints_maxLength: Option = Some(25_usize), /// Whether to show function parameter name inlay hints at the call /// site. inlayHints_parameterHints_enable: bool = true, @@ -2230,7 +2230,7 @@ macro_rules! _config_data { error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - $default, + $default as $ty, ), )*} } @@ -2248,7 +2248,7 @@ macro_rules! _config_data { error_sink, stringify!($field2), None$(.or(Some(stringify!($alias2))))*, - $default2, + $default2 as $ty2, ), )*} } @@ -2265,7 +2265,7 @@ macro_rules! _config_data { error_sink, stringify!($field3), None$(.or(Some(stringify!($alias3))))*, - $default3, + $default3 as $ty3, ), )*} } @@ -2288,7 +2288,7 @@ macro_rules! _config_data { error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - $default, + $default as $ty, ), )* $( @@ -2297,7 +2297,7 @@ macro_rules! _config_data { error_sink, stringify!($field2), None$(.or(Some(stringify!($alias2))))*, - $default2, + $default2 as $ty2, ), )* $( @@ -2306,7 +2306,7 @@ macro_rules! _config_data { error_sink, stringify!($field3), None$(.or(Some(stringify!($alias3))))*, - $default3, + $default3 as $ty3, ), )*} } @@ -2318,7 +2318,7 @@ macro_rules! _config_data { error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - $default, + $default as $ty, ), )* $( @@ -2327,7 +2327,7 @@ macro_rules! _config_data { error_sink, stringify!($field2), None$(.or(Some(stringify!($alias2))))*, - $default2, + $default2 as $ty2, ), )* $( @@ -2336,7 +2336,7 @@ macro_rules! _config_data { error_sink, stringify!($field3), None$(.or(Some(stringify!($alias3))))*, - $default3, + $default3 as $ty3, ), )*} } @@ -2347,19 +2347,19 @@ macro_rules! _config_data { let field = stringify!($field); let ty = stringify!($ty); - (field, ty, &[$($doc),*], serde_json::to_string(&$default).unwrap().as_str()) + (field, ty, &[$($doc),*], serde_json::to_string(&($default as $ty)).unwrap().as_str()) },)* $({ let field = stringify!($field2); let ty = stringify!($ty2); - (field, ty, &[$($doc2),*], serde_json::to_string(&$default2).unwrap().as_str()) + (field, ty, &[$($doc2),*], serde_json::to_string(&($default2 as $ty2)).unwrap().as_str()) },)* $({ let field = stringify!($field3); let ty = stringify!($ty3); - (field, ty, &[$($doc3),*], serde_json::to_string(&$default3).unwrap().as_str()) + (field, ty, &[$($doc3),*], serde_json::to_string(&($default3 as $ty3)).unwrap().as_str()) },)* ]) } @@ -2371,7 +2371,7 @@ macro_rules! _config_data { let field = stringify!($field3); let ty = stringify!($ty3); - (field, ty, &[$($doc3),*], $default3) + (field, ty, &[$($doc3),*], $default3 as $ty3) },)* ]) } From fda193597e5a74a7b591e400bdb785e53a97b604 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 18 Jan 2024 22:44:41 +1100 Subject: [PATCH 13/52] Last type annotation in config_data! --- crates/rust-analyzer/src/config.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index beef0b3bd6a0..bd0b3efd7eed 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -472,7 +472,7 @@ config_data! { /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. inlayHints_lifetimeElisionHints_useParameterNames: bool = false, /// Maximum length for inlay hints. Set to null to have an unlimited length. - inlayHints_maxLength: Option = Some(25_usize), + inlayHints_maxLength: Option = Some(25), /// Whether to show function parameter name inlay hints at the call /// site. inlayHints_parameterHints_enable: bool = true, @@ -2230,7 +2230,7 @@ macro_rules! _config_data { error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - $default as $ty, + { let default_: $ty = $default; default_ }, ), )*} } @@ -2248,7 +2248,7 @@ macro_rules! _config_data { error_sink, stringify!($field2), None$(.or(Some(stringify!($alias2))))*, - $default2 as $ty2, + { let default_: $ty2 = $default2; default_ }, ), )*} } @@ -2265,7 +2265,7 @@ macro_rules! _config_data { error_sink, stringify!($field3), None$(.or(Some(stringify!($alias3))))*, - $default3 as $ty3, + { let default_: $ty3 = $default3; default_ }, ), )*} } @@ -2288,7 +2288,7 @@ macro_rules! _config_data { error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - $default as $ty, + { let default_: $ty = $default; default_ }, ), )* $( @@ -2297,7 +2297,7 @@ macro_rules! _config_data { error_sink, stringify!($field2), None$(.or(Some(stringify!($alias2))))*, - $default2 as $ty2, + { let default_: $ty2 = $default2; default_ }, ), )* $( @@ -2306,7 +2306,7 @@ macro_rules! _config_data { error_sink, stringify!($field3), None$(.or(Some(stringify!($alias3))))*, - $default3 as $ty3, + { let default_: $ty3 = $default3; default_ }, ), )*} } @@ -2318,7 +2318,7 @@ macro_rules! _config_data { error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - $default as $ty, + { let default_: $ty = $default; default_ }, ), )* $( @@ -2327,7 +2327,7 @@ macro_rules! _config_data { error_sink, stringify!($field2), None$(.or(Some(stringify!($alias2))))*, - $default2 as $ty2, + { let default_: $ty2 = $default2; default_ }, ), )* $( @@ -2336,7 +2336,7 @@ macro_rules! _config_data { error_sink, stringify!($field3), None$(.or(Some(stringify!($alias3))))*, - $default3 as $ty3, + { let default_: $ty3 = $default3; default_ }, ), )*} } @@ -2347,19 +2347,19 @@ macro_rules! _config_data { let field = stringify!($field); let ty = stringify!($ty); - (field, ty, &[$($doc),*], serde_json::to_string(&($default as $ty)).unwrap().as_str()) + (field, ty, &[$($doc),*], serde_json::to_string(&{ let default_: $ty = $default; default_ }).unwrap().as_str()) },)* $({ let field = stringify!($field2); let ty = stringify!($ty2); - (field, ty, &[$($doc2),*], serde_json::to_string(&($default2 as $ty2)).unwrap().as_str()) + (field, ty, &[$($doc2),*], serde_json::to_string(&{ let default_: $ty2 = $default2; default_ }).unwrap().as_str()) },)* $({ let field = stringify!($field3); let ty = stringify!($ty3); - (field, ty, &[$($doc3),*], serde_json::to_string(&($default3 as $ty3)).unwrap().as_str()) + (field, ty, &[$($doc3),*], serde_json::to_string(&{ let default_: $ty3 = $default3; default_ }).unwrap().as_str()) },)* ]) } From f77b64c44cac62304c34c75220759de3d5569215 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 18 Jan 2024 23:17:21 +1100 Subject: [PATCH 14/52] Reduce repetition in config_data! macro by splitting ConfigData into fields --- crates/rust-analyzer/src/config.rs | 253 +++++++++++------------------ 1 file changed, 95 insertions(+), 158 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index bd0b3efd7eed..a21b6e51acf5 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -59,7 +59,7 @@ mod patch_old_style; // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep // parsing the old name. config_data! { - struct GlobalConfigData { + global: struct GlobalConfigData { /// Whether to insert #[must_use] when generating `as_` methods /// for enum variants. assist_emitMustUse: bool = false, @@ -361,8 +361,11 @@ config_data! { workspace_symbol_search_limit: usize = 128, /// Workspace symbol search scope. workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = WorkspaceSymbolSearchScopeDef::Workspace, - }, - struct LocalConfigData { + } +} + +config_data! { + local: struct LocalConfigData { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. completion_autoimport_enable: bool = true, @@ -535,8 +538,11 @@ config_data! { /// By disabling semantic tokens for strings, other grammars can be used to highlight /// their contents. semanticHighlighting_strings_enable: bool = true, - }, - struct ClientConfigData {} + } +} + +config_data! { + client: struct ClientConfigData {} } impl Default for ConfigData { @@ -563,15 +569,15 @@ impl Default for RootConfigData { fn default() -> Self { RootConfigData { local: RootLocalConfigData(LocalConfigData::from_json( - serde_json::Value::Null, + &mut serde_json::Value::Null, &mut Vec::new(), )), global: RootGlobalConfigData(GlobalConfigData::from_json( - serde_json::Value::Null, + &mut serde_json::Value::Null, &mut Vec::new(), )), client: RootClientConfigData(ClientConfigData::from_json( - serde_json::Value::Null, + &mut serde_json::Value::Null, &mut Vec::new(), )), } @@ -1147,7 +1153,7 @@ impl Config { .collect(); patch_old_style::patch_json_for_outdated_configs(&mut json); self.root_config.global = - RootGlobalConfigData(GlobalConfigData::from_json(json, &mut errors)); + RootGlobalConfigData(GlobalConfigData::from_json(&mut json, &mut errors)); tracing::debug!("deserialized config data: {:#?}", self.root_config.global); self.snippets.clear(); for (name, def) in self.root_config.local.0.completion_snippets_custom.iter() { @@ -2200,33 +2206,22 @@ pub enum TargetDirectory { } macro_rules! _config_data { - (struct $name:ident { + // modname is for the tests + ($modname:ident: struct $name:ident { $( $(#[doc=$doc:literal])* $field:ident $(| $alias:ident)*: $ty:ty = $default:expr, )* - }, - struct $name2:ident { - $( - $(#[doc=$doc2:literal])* - $field2:ident $(| $alias2:ident)*: $ty2:ty = $default2:expr, - )* - }, - struct $name3:ident { - $( - $(#[doc=$doc3:literal])* - $field3:ident $(| $alias3:ident)*: $ty3:ty = $default3:expr, - )* - } - ) => { + }) => { #[allow(non_snake_case)] - #[derive(Debug, Clone)] + #[derive(Debug, Clone, Serialize)] struct $name { $($field: $ty,)* } impl $name { - fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name { + #[allow(unused)] + fn from_json(json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name { $name {$( $field: get_field( - &mut json, + json, error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, @@ -2235,158 +2230,98 @@ macro_rules! _config_data { )*} } - } - - #[allow(non_snake_case)] - #[derive(Debug, Clone)] - struct $name2 { $($field2: $ty2,)* } - impl $name2 { - fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name2 { - $name2 {$( - $field2: get_field( - &mut json, - error_sink, - stringify!($field2), - None$(.or(Some(stringify!($alias2))))*, - { let default_: $ty2 = $default2; default_ }, - ), - )*} - } - } - - #[allow(non_snake_case)] - #[derive(Debug, Clone)] - struct $name3 { $($field3: $ty3,)* } - impl $name3 { - fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name3 { - $name3 {$( - $field3: get_field( - &mut json, - error_sink, - stringify!($field3), - None$(.or(Some(stringify!($alias3))))*, - { let default_: $ty3 = $default3; default_ }, - ), - )*} - } - } - - #[allow(non_snake_case)] - #[derive(Debug, Clone, Deserialize, Serialize)] - struct ConfigData { - $($field: $ty,)* - $($field2: $ty2,)* - $($field3: $ty3,)* - } - - impl ConfigData { - - fn from_toml(mut toml: toml::Table , error_sink: &mut Vec<(String, toml::de::Error)>) -> ConfigData { - ConfigData {$( + #[allow(unused)] + fn from_toml(toml: &mut toml::Table , error_sink: &mut Vec<(String, toml::de::Error)>) -> $name { + $name {$( $field: get_field_toml::<$ty>( - &mut toml, + toml, error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, { let default_: $ty = $default; default_ }, ), - )* - $( - $field2: get_field_toml::<$ty2>( - &mut toml, - error_sink, - stringify!($field2), - None$(.or(Some(stringify!($alias2))))*, - { let default_: $ty2 = $default2; default_ }, - ), - )* - $( - $field3: get_field_toml::<$ty3>( - &mut toml, - error_sink, - stringify!($field3), - None$(.or(Some(stringify!($alias3))))*, - { let default_: $ty3 = $default3; default_ }, - ), )*} } - fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> ConfigData { - ConfigData {$( - $field: get_field::<$ty>( - &mut json, - error_sink, - stringify!($field), - None$(.or(Some(stringify!($alias))))*, - { let default_: $ty = $default; default_ }, - ), - )* - $( - $field2: get_field::<$ty2>( - &mut json, - error_sink, - stringify!($field2), - None$(.or(Some(stringify!($alias2))))*, - { let default_: $ty2 = $default2; default_ }, - ), - )* - $( - $field3: get_field::<$ty3>( - &mut json, - error_sink, - stringify!($field3), - None$(.or(Some(stringify!($alias3))))*, - { let default_: $ty3 = $default3; default_ }, - ), - )*} - } - - fn json_schema() -> serde_json::Value { - schema(&[ + fn schema_fields(sink: &mut Vec) { + sink.extend_from_slice(&[ $({ let field = stringify!($field); let ty = stringify!($ty); - (field, ty, &[$($doc),*], serde_json::to_string(&{ let default_: $ty = $default; default_ }).unwrap().as_str()) - },)* - $({ - let field = stringify!($field2); - let ty = stringify!($ty2); - - (field, ty, &[$($doc2),*], serde_json::to_string(&{ let default_: $ty2 = $default2; default_ }).unwrap().as_str()) - },)* - $({ - let field = stringify!($field3); - let ty = stringify!($ty3); - - (field, ty, &[$($doc3),*], serde_json::to_string(&{ let default_: $ty3 = $default3; default_ }).unwrap().as_str()) - },)* - ]) - } - - #[cfg(test)] - fn manual() -> String { - manual(&[ - $({ - let field = stringify!($field3); - let ty = stringify!($ty3); - - (field, ty, &[$($doc3),*], $default3 as $ty3) + ( + field, + ty, + &[$($doc),*], + serde_json::to_string(&{ let default_: $ty = $default; default_ }).unwrap(), + ) },)* ]) } } - #[test] - fn fields_are_sorted() { - [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); - [$(stringify!($field2)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); - // [$(stringify!($field3)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); + mod $modname { + #[test] + fn fields_are_sorted() { + let field_names: &'static [&'static str] = &[$(stringify!($field)),*]; + field_names.windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1])); + } } }; } use _config_data as config_data; +#[derive(Debug, Clone, Serialize)] +struct ConfigData { + #[serde(flatten)] + global: GlobalConfigData, + #[serde(flatten)] + local: LocalConfigData, + #[serde(flatten)] + client: ClientConfigData, +} + +impl ConfigData { + fn from_json( + mut json: serde_json::Value, + error_sink: &mut Vec<(String, serde_json::Error)>, + ) -> ConfigData { + ConfigData { + global: GlobalConfigData::from_json(&mut json, error_sink), + local: LocalConfigData::from_json(&mut json, error_sink), + client: ClientConfigData::from_json(&mut json, error_sink), + } + } + + fn from_toml( + mut toml: toml::Table, + error_sink: &mut Vec<(String, toml::de::Error)>, + ) -> ConfigData { + ConfigData { + global: GlobalConfigData::from_toml(&mut toml, error_sink), + local: LocalConfigData::from_toml(&mut toml, error_sink), + client: ClientConfigData::from_toml(&mut toml, error_sink), + } + } + + fn schema_fields() -> Vec { + let mut fields = Vec::new(); + GlobalConfigData::schema_fields(&mut fields); + LocalConfigData::schema_fields(&mut fields); + ClientConfigData::schema_fields(&mut fields); + fields + } + + fn json_schema() -> serde_json::Value { + schema(&Self::schema_fields()) + } + + #[cfg(test)] + fn manual() -> String { + manual(&Self::schema_fields()) + } +} + fn get_field_toml( val: &toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>, @@ -2454,7 +2389,9 @@ fn get_field( .unwrap_or(default) } -fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value { +type SchemaField = (&'static str, &'static str, &'static [&'static str], String); + +fn schema(fields: &[SchemaField]) -> serde_json::Value { let map = fields .iter() .map(|(field, ty, doc, default)| { @@ -2815,7 +2752,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json } #[cfg(test)] -fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String { +fn manual(fields: &[SchemaField]) -> String { fields .iter() .map(|(field, _ty, doc, default)| { From a3643841300bae10e7102b5da01b814ee5d2da27 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 18 Jan 2024 23:56:03 +1100 Subject: [PATCH 15/52] Sort the fields, so the diffs on the generated docs/schemas are smaller --- crates/rust-analyzer/src/config.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index a21b6e51acf5..e54387401409 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -2248,13 +2248,10 @@ macro_rules! _config_data { $({ let field = stringify!($field); let ty = stringify!($ty); + let default = + serde_json::to_string(&{ let default_: $ty = $default; default_ }).unwrap(); - ( - field, - ty, - &[$($doc),*], - serde_json::to_string(&{ let default_: $ty = $default; default_ }).unwrap(), - ) + (field, ty, &[$($doc),*], default) },)* ]) } @@ -2309,6 +2306,8 @@ impl ConfigData { GlobalConfigData::schema_fields(&mut fields); LocalConfigData::schema_fields(&mut fields); ClientConfigData::schema_fields(&mut fields); + // HACK: sort the fields, so the diffs on the generated docs/schema are smaller + fields.sort_by_key(|&(x, ..)| x); fields } From 8835d42fc4d81dac0f10fbf75dc676698dc0f45d Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 19 Jan 2024 00:21:26 +1100 Subject: [PATCH 16/52] Add the default snippets back, tweak the serialize output until it's close --- crates/rust-analyzer/src/config.rs | 137 ++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 32 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e54387401409..57ae2fe4f3ba 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -383,8 +383,7 @@ config_data! { /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. completion_privateEditable_enable: bool = false, /// Custom completion snippets. - // NOTE: Keep this list in sync with the feature docs of user snippets. - completion_snippets_custom: FxHashMap = FxHashMap::default(), + completion_snippets_custom: FxHashMap = SnippetDef::default_snippets(), /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. highlightRelated_breakPoints_enable: bool = true, @@ -1957,7 +1956,7 @@ mod de_unit_v { named_unit_variant!(both); } -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[serde(rename_all = "snake_case")] enum SnippetScopeDef { Expr, @@ -1974,47 +1973,121 @@ impl Default for SnippetScopeDef { #[derive(Serialize, Deserialize, Debug, Clone, Default)] #[serde(default)] struct SnippetDef { - #[serde(deserialize_with = "single_or_array")] + #[serde(with = "single_or_array")] + #[serde(skip_serializing_if = "Vec::is_empty")] prefix: Vec, - #[serde(deserialize_with = "single_or_array")] + + #[serde(with = "single_or_array")] + #[serde(skip_serializing_if = "Vec::is_empty")] postfix: Vec, - description: Option, - #[serde(deserialize_with = "single_or_array")] + + #[serde(with = "single_or_array")] + #[serde(skip_serializing_if = "Vec::is_empty")] body: Vec, - #[serde(deserialize_with = "single_or_array")] + + #[serde(with = "single_or_array")] + #[serde(skip_serializing_if = "Vec::is_empty")] requires: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, + scope: SnippetScopeDef, } -fn single_or_array<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - struct SingleOrVec; +impl SnippetDef { + fn default_snippets() -> FxHashMap { + serde_json::from_str( + r#"{ + "Arc::new": { + "postfix": "arc", + "body": "Arc::new(${receiver})", + "requires": "std::sync::Arc", + "description": "Put the expression into an `Arc`", + "scope": "expr" + }, + "Rc::new": { + "postfix": "rc", + "body": "Rc::new(${receiver})", + "requires": "std::rc::Rc", + "description": "Put the expression into an `Rc`", + "scope": "expr" + }, + "Box::pin": { + "postfix": "pinbox", + "body": "Box::pin(${receiver})", + "requires": "std::boxed::Box", + "description": "Put the expression into a pinned `Box`", + "scope": "expr" + }, + "Ok": { + "postfix": "ok", + "body": "Ok(${receiver})", + "description": "Wrap the expression in a `Result::Ok`", + "scope": "expr" + }, + "Err": { + "postfix": "err", + "body": "Err(${receiver})", + "description": "Wrap the expression in a `Result::Err`", + "scope": "expr" + }, + "Some": { + "postfix": "some", + "body": "Some(${receiver})", + "description": "Wrap the expression in an `Option::Some`", + "scope": "expr" + } + }"#, + ) + .unwrap() + } +} + +mod single_or_array { + use serde::{Deserialize, Serialize}; - impl<'de> serde::de::Visitor<'de> for SingleOrVec { - type Value = Vec; + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + struct SingleOrVec; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("string or array of strings") - } + impl<'de> serde::de::Visitor<'de> for SingleOrVec { + type Value = Vec; - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - Ok(vec![value.to_owned()]) - } + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("string or array of strings") + } - fn visit_seq(self, seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)) + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + Ok(vec![value.to_owned()]) + } + + fn visit_seq(self, seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)) + } } + + deserializer.deserialize_any(SingleOrVec) } - deserializer.deserialize_any(SingleOrVec) + pub fn serialize(vec: &Vec, serializer: S) -> Result + where + S: serde::Serializer, + { + match &vec[..] { + // [] case is handled by skip_serializing_if + [single] => serializer.serialize_str(&single), + slice => slice.serialize(serializer), + } + } } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -2064,7 +2137,7 @@ enum InvocationStrategy { } #[derive(Serialize, Deserialize, Debug, Clone)] -struct CheckOnSaveTargets(#[serde(deserialize_with = "single_or_array")] Vec); +struct CheckOnSaveTargets(#[serde(with = "single_or_array")] Vec); #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] @@ -2249,7 +2322,7 @@ macro_rules! _config_data { let field = stringify!($field); let ty = stringify!($ty); let default = - serde_json::to_string(&{ let default_: $ty = $default; default_ }).unwrap(); + serde_json::to_string_pretty(&{ let default_: $ty = $default; default_ }).unwrap(); (field, ty, &[$($doc),*], default) },)* From 3ae48d1c9523bbd673ab24c61c154086a42b2574 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 19 Jan 2024 00:35:15 +1100 Subject: [PATCH 17/52] serialize impl for true_or_always/false_or_never --- crates/rust-analyzer/src/config.rs | 142 ++++++++++++++++------------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 57ae2fe4f3ba..95a8e761f8f7 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -1851,73 +1851,85 @@ impl Config { } // Deserialization definitions -macro_rules! create_bool_or_string_de { +macro_rules! create_bool_or_string_serde { ($ident:ident<$bool:literal, $string:literal>) => { - fn $ident<'de, D>(d: D) -> Result<(), D::Error> - where - D: serde::Deserializer<'de>, - { - struct V; - impl<'de> serde::de::Visitor<'de> for V { - type Value = (); - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str(concat!( - stringify!($bool), - " or \"", - stringify!($string), - "\"" - )) - } + mod $ident { + pub fn deserialize<'de, D>(d: D) -> Result<(), D::Error> + where + D: serde::Deserializer<'de>, + { + struct V; + impl<'de> serde::de::Visitor<'de> for V { + type Value = (); + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + formatter.write_str(concat!( + stringify!($bool), + " or \"", + stringify!($string), + "\"" + )) + } - fn visit_bool(self, v: bool) -> Result - where - E: serde::de::Error, - { - match v { - $bool => Ok(()), - _ => Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Bool(v), - &self, - )), + fn visit_bool(self, v: bool) -> Result + where + E: serde::de::Error, + { + match v { + $bool => Ok(()), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Bool(v), + &self, + )), + } } - } - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - match v { - $string => Ok(()), - _ => Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Str(v), - &self, - )), + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v { + $string => Ok(()), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(v), + &self, + )), + } } - } - fn visit_enum(self, a: A) -> Result - where - A: serde::de::EnumAccess<'de>, - { - use serde::de::VariantAccess; - let (variant, va) = a.variant::<&'de str>()?; - va.unit_variant()?; - match variant { - $string => Ok(()), - _ => Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Str(variant), - &self, - )), + fn visit_enum(self, a: A) -> Result + where + A: serde::de::EnumAccess<'de>, + { + use serde::de::VariantAccess; + let (variant, va) = a.variant::<&'de str>()?; + va.unit_variant()?; + match variant { + $string => Ok(()), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(variant), + &self, + )), + } } } + d.deserialize_any(V) + } + + pub fn serialize(serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str($string) } - d.deserialize_any(V) } }; } -create_bool_or_string_de!(true_or_always); -create_bool_or_string_de!(false_or_never); +create_bool_or_string_serde!(true_or_always); +create_bool_or_string_serde!(false_or_never); macro_rules! named_unit_variant { ($variant:ident) => { @@ -2149,9 +2161,9 @@ enum InvocationLocation { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum LifetimeElisionDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, #[serde(deserialize_with = "de_unit_v::skip_trivial")] SkipTrivial, @@ -2160,9 +2172,9 @@ enum LifetimeElisionDef { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ClosureReturnTypeHintsDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, #[serde(deserialize_with = "de_unit_v::with_block")] WithBlock, @@ -2180,9 +2192,9 @@ enum ClosureStyle { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ReborrowHintsDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, #[serde(deserialize_with = "de_unit_v::mutable")] Mutable, @@ -2191,9 +2203,9 @@ enum ReborrowHintsDef { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum AdjustmentHintsDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, #[serde(deserialize_with = "de_unit_v::reborrow")] Reborrow, @@ -2202,9 +2214,9 @@ enum AdjustmentHintsDef { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum DiscriminantHintsDef { - #[serde(deserialize_with = "true_or_always")] + #[serde(with = "true_or_always")] Always, - #[serde(deserialize_with = "false_or_never")] + #[serde(with = "false_or_never")] Never, #[serde(deserialize_with = "de_unit_v::fieldless")] Fieldless, From 86a843827263407f95426d08fb359e055cadcd13 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 19 Jan 2024 00:46:32 +1100 Subject: [PATCH 18/52] add serialize for the de_unit_v-s --- crates/rust-analyzer/src/config.rs | 77 +++++++++++++++++++----------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 95a8e761f8f7..9e3c3d237c84 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -1854,7 +1854,7 @@ impl Config { macro_rules! create_bool_or_string_serde { ($ident:ident<$bool:literal, $string:literal>) => { mod $ident { - pub fn deserialize<'de, D>(d: D) -> Result<(), D::Error> + pub(super) fn deserialize<'de, D>(d: D) -> Result<(), D::Error> where D: serde::Deserializer<'de>, { @@ -1919,7 +1919,7 @@ macro_rules! create_bool_or_string_serde { d.deserialize_any(V) } - pub fn serialize(serializer: S) -> Result + pub(super) fn serialize(serializer: S) -> Result where S: serde::Serializer, { @@ -1933,25 +1933,33 @@ create_bool_or_string_serde!(false_or_never); macro_rules! named_unit_variant { ($variant:ident) => { - pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error> - where - D: serde::Deserializer<'de>, - { - struct V; - impl<'de> serde::de::Visitor<'de> for V { - type Value = (); - fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(concat!("\"", stringify!($variant), "\"")) - } - fn visit_str(self, value: &str) -> Result { - if value == stringify!($variant) { - Ok(()) - } else { - Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) + pub(super) mod $variant { + pub(in super::super) fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> + where + D: serde::Deserializer<'de>, + { + struct V; + impl<'de> serde::de::Visitor<'de> for V { + type Value = (); + fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(concat!("\"", stringify!($variant), "\"")) + } + fn visit_str(self, value: &str) -> Result { + if value == stringify!($variant) { + Ok(()) + } else { + Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) + } } } + deserializer.deserialize_str(V) + } + pub(in super::super) fn serialize(serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(stringify!($variant)) } - deserializer.deserialize_str(V) } }; } @@ -2136,7 +2144,7 @@ enum CallableCompletionDef { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum CargoFeaturesDef { - #[serde(deserialize_with = "de_unit_v::all")] + #[serde(with = "de_unit_v::all")] All, Selected(Vec), } @@ -2165,7 +2173,7 @@ enum LifetimeElisionDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::skip_trivial")] + #[serde(with = "de_unit_v::skip_trivial")] SkipTrivial, } @@ -2176,7 +2184,7 @@ enum ClosureReturnTypeHintsDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::with_block")] + #[serde(with = "de_unit_v::with_block")] WithBlock, } @@ -2196,7 +2204,7 @@ enum ReborrowHintsDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::mutable")] + #[serde(with = "de_unit_v::mutable")] Mutable, } @@ -2207,7 +2215,7 @@ enum AdjustmentHintsDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::reborrow")] + #[serde(with = "de_unit_v::reborrow")] Reborrow, } @@ -2218,7 +2226,7 @@ enum DiscriminantHintsDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(deserialize_with = "de_unit_v::fieldless")] + #[serde(with = "de_unit_v::fieldless")] Fieldless, } @@ -2270,18 +2278,29 @@ enum WorkspaceSymbolSearchKindDef { AllSymbols, } -#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] #[serde(rename_all = "snake_case")] #[serde(untagged)] -pub enum MemoryLayoutHoverRenderKindDef { - #[serde(deserialize_with = "de_unit_v::decimal")] +enum MemoryLayoutHoverRenderKindDef { + #[serde(with = "de_unit_v::decimal")] Decimal, - #[serde(deserialize_with = "de_unit_v::hexadecimal")] + #[serde(with = "de_unit_v::hexadecimal")] Hexadecimal, - #[serde(deserialize_with = "de_unit_v::both")] + #[serde(with = "de_unit_v::both")] Both, } +#[test] +fn untagged_option_hover_render_kind() { + let hex = MemoryLayoutHoverRenderKindDef::Hexadecimal; + + let ser = serde_json::to_string(&Some(hex)).unwrap(); + assert_eq!(&ser, "\"hexadecimal\""); + + let opt: Option<_> = serde_json::from_str("\"hexadecimal\"").unwrap(); + assert_eq!(opt, Some(hex)); +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "snake_case")] #[serde(untagged)] From 48dd942e60bff8f8a331f83c6c2315f996d2791c Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 19 Jan 2024 00:47:17 +1100 Subject: [PATCH 19/52] rename de_unit_v to unit_v --- crates/rust-analyzer/src/config.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 9e3c3d237c84..8fa3b0437847 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -1964,7 +1964,7 @@ macro_rules! named_unit_variant { }; } -mod de_unit_v { +mod unit_v { named_unit_variant!(all); named_unit_variant!(skip_trivial); named_unit_variant!(mutable); @@ -2144,7 +2144,7 @@ enum CallableCompletionDef { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum CargoFeaturesDef { - #[serde(with = "de_unit_v::all")] + #[serde(with = "unit_v::all")] All, Selected(Vec), } @@ -2173,7 +2173,7 @@ enum LifetimeElisionDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(with = "de_unit_v::skip_trivial")] + #[serde(with = "unit_v::skip_trivial")] SkipTrivial, } @@ -2184,7 +2184,7 @@ enum ClosureReturnTypeHintsDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(with = "de_unit_v::with_block")] + #[serde(with = "unit_v::with_block")] WithBlock, } @@ -2204,7 +2204,7 @@ enum ReborrowHintsDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(with = "de_unit_v::mutable")] + #[serde(with = "unit_v::mutable")] Mutable, } @@ -2215,7 +2215,7 @@ enum AdjustmentHintsDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(with = "de_unit_v::reborrow")] + #[serde(with = "unit_v::reborrow")] Reborrow, } @@ -2226,7 +2226,7 @@ enum DiscriminantHintsDef { Always, #[serde(with = "false_or_never")] Never, - #[serde(with = "de_unit_v::fieldless")] + #[serde(with = "unit_v::fieldless")] Fieldless, } @@ -2282,11 +2282,11 @@ enum WorkspaceSymbolSearchKindDef { #[serde(rename_all = "snake_case")] #[serde(untagged)] enum MemoryLayoutHoverRenderKindDef { - #[serde(with = "de_unit_v::decimal")] + #[serde(with = "unit_v::decimal")] Decimal, - #[serde(with = "de_unit_v::hexadecimal")] + #[serde(with = "unit_v::hexadecimal")] Hexadecimal, - #[serde(with = "de_unit_v::both")] + #[serde(with = "unit_v::both")] Both, } From 7b0c05d5c49b81b6704875b3ae15e77cf6c92446 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 19 Jan 2024 01:20:27 +1100 Subject: [PATCH 20/52] Bring the diff down to zero using BTreeMap for snippets, @from_str: --- crates/rust-analyzer/src/config.rs | 129 ++++++++++++++++------------- 1 file changed, 72 insertions(+), 57 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 8fa3b0437847..fee08cef241e 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -30,6 +30,7 @@ use project_model::{ }; use rustc_hash::{FxHashMap, FxHashSet}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::BTreeMap; use toml; use vfs::{AbsPath, AbsPathBuf, FileId}; @@ -141,7 +142,7 @@ config_data! { // than `checkOnSave_target` cargo_target: Option = None, /// Unsets the implicit `#[cfg(test)]` for the specified crates. - cargo_unsetTest: Vec = vec!["core".to_string()], + cargo_unsetTest: Vec = @from_str: r#"["core"]"#, /// Run the check command for diagnostics on save. checkOnSave | checkOnSave_enable: bool = true, @@ -383,7 +384,48 @@ config_data! { /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. completion_privateEditable_enable: bool = false, /// Custom completion snippets. - completion_snippets_custom: FxHashMap = SnippetDef::default_snippets(), + // NOTE: we use BTreeMap for deterministic serialization ordering + completion_snippets_custom: BTreeMap = @from_str: r#"{ + "Arc::new": { + "postfix": "arc", + "body": "Arc::new(${receiver})", + "requires": "std::sync::Arc", + "description": "Put the expression into an `Arc`", + "scope": "expr" + }, + "Rc::new": { + "postfix": "rc", + "body": "Rc::new(${receiver})", + "requires": "std::rc::Rc", + "description": "Put the expression into an `Rc`", + "scope": "expr" + }, + "Box::pin": { + "postfix": "pinbox", + "body": "Box::pin(${receiver})", + "requires": "std::boxed::Box", + "description": "Put the expression into a pinned `Box`", + "scope": "expr" + }, + "Ok": { + "postfix": "ok", + "body": "Ok(${receiver})", + "description": "Wrap the expression in a `Result::Ok`", + "scope": "expr" + }, + "Err": { + "postfix": "err", + "body": "Err(${receiver})", + "description": "Wrap the expression in a `Result::Err`", + "scope": "expr" + }, + "Some": { + "postfix": "some", + "body": "Some(${receiver})", + "description": "Wrap the expression in an `Option::Some`", + "scope": "expr" + } + }"#, /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. highlightRelated_breakPoints_enable: bool = true, @@ -2015,55 +2057,6 @@ struct SnippetDef { scope: SnippetScopeDef, } -impl SnippetDef { - fn default_snippets() -> FxHashMap { - serde_json::from_str( - r#"{ - "Arc::new": { - "postfix": "arc", - "body": "Arc::new(${receiver})", - "requires": "std::sync::Arc", - "description": "Put the expression into an `Arc`", - "scope": "expr" - }, - "Rc::new": { - "postfix": "rc", - "body": "Rc::new(${receiver})", - "requires": "std::rc::Rc", - "description": "Put the expression into an `Rc`", - "scope": "expr" - }, - "Box::pin": { - "postfix": "pinbox", - "body": "Box::pin(${receiver})", - "requires": "std::boxed::Box", - "description": "Put the expression into a pinned `Box`", - "scope": "expr" - }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, - "Err": { - "postfix": "err", - "body": "Err(${receiver})", - "description": "Wrap the expression in a `Result::Err`", - "scope": "expr" - }, - "Some": { - "postfix": "some", - "body": "Some(${receiver})", - "description": "Wrap the expression in an `Option::Some`", - "scope": "expr" - } - }"#, - ) - .unwrap() - } -} - mod single_or_array { use serde::{Deserialize, Serialize}; @@ -2309,12 +2302,33 @@ pub enum TargetDirectory { Directory(PathBuf), } +macro_rules! _default_val { + (@from_str: $s:literal, $ty:ty) => {{ + let default_: $ty = serde_json::from_str(&$s).unwrap(); + default_ + }}; + ($default:expr, $ty:ty) => {{ + let default_: $ty = $default; + default_ + }}; +} + +macro_rules! _default_str { + (@from_str: $s:literal, $_ty:ty) => { + $s.to_string() + }; + ($default:expr, $ty:ty) => {{ + let val = default_val!($default, $ty); + serde_json::to_string_pretty(&val).unwrap() + }}; +} + macro_rules! _config_data { // modname is for the tests ($modname:ident: struct $name:ident { $( $(#[doc=$doc:literal])* - $field:ident $(| $alias:ident)*: $ty:ty = $default:expr, + $field:ident $(| $alias:ident)*: $ty:ty = $(@$marker:ident: )? $default:expr, )* }) => { #[allow(non_snake_case)] @@ -2329,7 +2343,7 @@ macro_rules! _config_data { error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - { let default_: $ty = $default; default_ }, + default_val!($(@$marker:)? $default, $ty), ), )*} } @@ -2342,7 +2356,7 @@ macro_rules! _config_data { error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - { let default_: $ty = $default; default_ }, + default_val!($(@$marker:)? $default, $ty), ), )*} } @@ -2352,8 +2366,7 @@ macro_rules! _config_data { $({ let field = stringify!($field); let ty = stringify!($ty); - let default = - serde_json::to_string_pretty(&{ let default_: $ty = $default; default_ }).unwrap(); + let default = default_str!($(@$marker:)? $default, $ty); (field, ty, &[$($doc),*], default) },)* @@ -2371,6 +2384,8 @@ macro_rules! _config_data { }; } use _config_data as config_data; +use _default_str as default_str; +use _default_val as default_val; #[derive(Debug, Clone, Serialize)] struct ConfigData { @@ -2545,7 +2560,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "FxHashMap, Box<[Box]>>" => set! { "type": "object", }, - "FxHashMap" => set! { + "BTreeMap" => set! { "type": "object", }, "FxHashMap" => set! { From c474ebbe84a4e4791b61a48a3f4de810ef9be4ec Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 19 Jan 2024 02:21:49 +1100 Subject: [PATCH 21/52] use indexmap instead of BTreeMap for deterministic snippet output --- Cargo.lock | 2 + crates/rust-analyzer/Cargo.toml | 1 + crates/rust-analyzer/src/config.rs | 10 ++-- docs/user/generated_config.adoc | 80 +++++++++++++++--------------- 4 files changed, 48 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46a1ebc69666..d5cebd5102de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -789,6 +789,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", + "serde", ] [[package]] @@ -1584,6 +1585,7 @@ dependencies = [ "ide", "ide-db", "ide-ssr", + "indexmap 2.0.0", "itertools", "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "load-cargo", diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 1f53e53f7082..62489ab32c73 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -52,6 +52,7 @@ always-assert = "0.1.2" # in our transitive deps to prevent them from pulling in windows-sys 0.45.0 mio = "=0.8.5" parking_lot_core = "=0.9.6" +indexmap = { version = "2.0.0", features = ["serde"] } cfg.workspace = true flycheck.workspace = true diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index fee08cef241e..54936fa44ccd 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -22,6 +22,7 @@ use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, SnippetCap, }; +use indexmap::IndexMap; use itertools::Itertools; use la_arena::Arena; use lsp_types::{ClientCapabilities, MarkupKind}; @@ -30,7 +31,6 @@ use project_model::{ }; use rustc_hash::{FxHashMap, FxHashSet}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::collections::BTreeMap; use toml; use vfs::{AbsPath, AbsPathBuf, FileId}; @@ -384,8 +384,8 @@ config_data! { /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. completion_privateEditable_enable: bool = false, /// Custom completion snippets. - // NOTE: we use BTreeMap for deterministic serialization ordering - completion_snippets_custom: BTreeMap = @from_str: r#"{ + // NOTE: we use IndexMap for deterministic serialization ordering + completion_snippets_custom: IndexMap = serde_json::from_str(r#"{ "Arc::new": { "postfix": "arc", "body": "Arc::new(${receiver})", @@ -425,7 +425,7 @@ config_data! { "description": "Wrap the expression in an `Option::Some`", "scope": "expr" } - }"#, + }"#).unwrap(), /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. highlightRelated_breakPoints_enable: bool = true, @@ -2560,7 +2560,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "FxHashMap, Box<[Box]>>" => set! { "type": "object", }, - "BTreeMap" => set! { + "IndexMap" => set! { "type": "object", }, "FxHashMap" => set! { diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 7c76ae81bea0..0e05735ac21b 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -278,46 +278,46 @@ Enables completions of private items and fields that are defined in the current Default: ---- { - "Arc::new": { - "postfix": "arc", - "body": "Arc::new(${receiver})", - "requires": "std::sync::Arc", - "description": "Put the expression into an `Arc`", - "scope": "expr" - }, - "Rc::new": { - "postfix": "rc", - "body": "Rc::new(${receiver})", - "requires": "std::rc::Rc", - "description": "Put the expression into an `Rc`", - "scope": "expr" - }, - "Box::pin": { - "postfix": "pinbox", - "body": "Box::pin(${receiver})", - "requires": "std::boxed::Box", - "description": "Put the expression into a pinned `Box`", - "scope": "expr" - }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, - "Err": { - "postfix": "err", - "body": "Err(${receiver})", - "description": "Wrap the expression in a `Result::Err`", - "scope": "expr" - }, - "Some": { - "postfix": "some", - "body": "Some(${receiver})", - "description": "Wrap the expression in an `Option::Some`", - "scope": "expr" - } - } + "Arc::new": { + "postfix": "arc", + "body": "Arc::new(${receiver})", + "requires": "std::sync::Arc", + "description": "Put the expression into an `Arc`", + "scope": "expr" + }, + "Rc::new": { + "postfix": "rc", + "body": "Rc::new(${receiver})", + "requires": "std::rc::Rc", + "description": "Put the expression into an `Rc`", + "scope": "expr" + }, + "Box::pin": { + "postfix": "pinbox", + "body": "Box::pin(${receiver})", + "requires": "std::boxed::Box", + "description": "Put the expression into a pinned `Box`", + "scope": "expr" + }, + "Ok": { + "postfix": "ok", + "body": "Ok(${receiver})", + "description": "Wrap the expression in a `Result::Ok`", + "scope": "expr" + }, + "Err": { + "postfix": "err", + "body": "Err(${receiver})", + "description": "Wrap the expression in a `Result::Err`", + "scope": "expr" + }, + "Some": { + "postfix": "some", + "body": "Some(${receiver})", + "description": "Wrap the expression in an `Option::Some`", + "scope": "expr" + } +} ---- Custom completion snippets. From 172da25acd32245c0f00d587b14eb5536cd8202c Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 19 Jan 2024 02:23:22 +1100 Subject: [PATCH 22/52] rename `@from_str: ` to `@verbatim: ` --- crates/rust-analyzer/src/config.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 54936fa44ccd..c0ac2c9a9af7 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -142,7 +142,7 @@ config_data! { // than `checkOnSave_target` cargo_target: Option = None, /// Unsets the implicit `#[cfg(test)]` for the specified crates. - cargo_unsetTest: Vec = @from_str: r#"["core"]"#, + cargo_unsetTest: Vec = @verbatim: r#"["core"]"#, /// Run the check command for diagnostics on save. checkOnSave | checkOnSave_enable: bool = true, @@ -2303,7 +2303,7 @@ pub enum TargetDirectory { } macro_rules! _default_val { - (@from_str: $s:literal, $ty:ty) => {{ + (@verbatim: $s:literal, $ty:ty) => {{ let default_: $ty = serde_json::from_str(&$s).unwrap(); default_ }}; @@ -2314,7 +2314,7 @@ macro_rules! _default_val { } macro_rules! _default_str { - (@from_str: $s:literal, $_ty:ty) => { + (@verbatim: $s:literal, $_ty:ty) => { $s.to_string() }; ($default:expr, $ty:ty) => {{ From 568ee9ad23fcd3ae699b666ce49d5f7655cea1d7 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 19 Jan 2024 02:34:36 +1100 Subject: [PATCH 23/52] Didn't mean for indexmap to be one of "these 3 deps" --- crates/rust-analyzer/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 62489ab32c73..7d6326448d8c 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -47,12 +47,12 @@ triomphe.workspace = true toml = "0.8.8" nohash-hasher.workspace = true always-assert = "0.1.2" +indexmap = { version = "2.0.0", features = ["serde"] } -# These 3 deps are not used by r-a directly, but we list them here to lock in their versions +# These 2 deps are not used by r-a directly, but we list them here to lock in their versions # in our transitive deps to prevent them from pulling in windows-sys 0.45.0 mio = "=0.8.5" parking_lot_core = "=0.9.6" -indexmap = { version = "2.0.0", features = ["serde"] } cfg.workspace = true flycheck.workspace = true From 0039f6b4ef9fdebc91a327c0524360c6dc265fb7 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 21 Jan 2024 01:18:01 +1100 Subject: [PATCH 24/52] GlobalConfigInput etc --- crates/rust-analyzer/src/config.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index c0ac2c9a9af7..8339610288a9 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -60,7 +60,7 @@ mod patch_old_style; // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep // parsing the old name. config_data! { - global: struct GlobalConfigData { + global: struct GlobalConfigData <- GlobalConfigInput { /// Whether to insert #[must_use] when generating `as_` methods /// for enum variants. assist_emitMustUse: bool = false, @@ -366,7 +366,7 @@ config_data! { } config_data! { - local: struct LocalConfigData { + local: struct LocalConfigData <- LocalConfigInput { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. completion_autoimport_enable: bool = true, @@ -583,7 +583,7 @@ config_data! { } config_data! { - client: struct ClientConfigData {} + client: struct ClientConfigData <- ClientConfigInput {} } impl Default for ConfigData { @@ -2325,7 +2325,7 @@ macro_rules! _default_str { macro_rules! _config_data { // modname is for the tests - ($modname:ident: struct $name:ident { + ($modname:ident: struct $name:ident <- $input:ident { $( $(#[doc=$doc:literal])* $field:ident $(| $alias:ident)*: $ty:ty = $(@$marker:ident: )? $default:expr, @@ -2334,6 +2334,11 @@ macro_rules! _config_data { #[allow(non_snake_case)] #[derive(Debug, Clone, Serialize)] struct $name { $($field: $ty,)* } + + #[allow(non_snake_case)] + #[derive(Debug, Clone, Serialize)] + struct $input { $($field: Option<$ty>,)* } + impl $name { #[allow(unused)] fn from_json(json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name { @@ -2372,6 +2377,14 @@ macro_rules! _config_data { },)* ]) } + + fn apply_input(&mut self, input: $input) { + $( + if let Some(value) = input.$field { + self.$field = value; + } + )* + } } mod $modname { From 9b0666e25a82f272e5488bbeadc80ab01372da91 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 21 Jan 2024 02:06:07 +1100 Subject: [PATCH 25/52] Finish splitting *ConfigData and *ConfigInput --- crates/rust-analyzer/src/config.rs | 174 ++++++++++++++++------------- 1 file changed, 99 insertions(+), 75 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 8339610288a9..d166920ab097 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -60,7 +60,7 @@ mod patch_old_style; // To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep // parsing the old name. config_data! { - global: struct GlobalConfigData <- GlobalConfigInput { + global: struct GlobalConfigData <- GlobalConfigInput -> RootGlobalConfigData { /// Whether to insert #[must_use] when generating `as_` methods /// for enum variants. assist_emitMustUse: bool = false, @@ -366,7 +366,7 @@ config_data! { } config_data! { - local: struct LocalConfigData <- LocalConfigInput { + local: struct LocalConfigData <- LocalConfigInput -> RootLocalConfigData { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. completion_autoimport_enable: bool = true, @@ -583,44 +583,25 @@ config_data! { } config_data! { - client: struct ClientConfigData <- ClientConfigInput {} + client: struct ClientConfigData <- ClientConfigInput -> RootClientConfigData {} } -impl Default for ConfigData { - fn default() -> Self { - ConfigData::from_json(serde_json::Value::Null, &mut Vec::new()) - } -} - -#[derive(Debug, Clone)] -struct RootLocalConfigData(LocalConfigData); -#[derive(Debug, Clone)] -struct RootGlobalConfigData(GlobalConfigData); -#[derive(Debug, Clone)] -struct RootClientConfigData(ClientConfigData); - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] struct RootConfigData { local: RootLocalConfigData, global: RootGlobalConfigData, client: RootClientConfigData, } -impl Default for RootConfigData { - fn default() -> Self { - RootConfigData { - local: RootLocalConfigData(LocalConfigData::from_json( - &mut serde_json::Value::Null, - &mut Vec::new(), - )), - global: RootGlobalConfigData(GlobalConfigData::from_json( - &mut serde_json::Value::Null, - &mut Vec::new(), - )), - client: RootClientConfigData(ClientConfigData::from_json( - &mut serde_json::Value::Null, - &mut Vec::new(), - )), +impl RootConfigData { + /// Reads a single root config blob. All fields are either set by the config blob, or to the + /// default value. + fn from_root_input(input: ConfigInput) -> Self { + let ConfigInput { global, local, client } = input; + Self { + global: RootGlobalConfigData::from_root_input(global), + local: RootLocalConfigData::from_root_input(local), + client: RootClientConfigData::from_root_input(client), } } } @@ -1188,13 +1169,14 @@ impl Config { } let mut errors = Vec::new(); self.detached_files = - get_field::>(&mut json, &mut errors, "detachedFiles", None, vec![]) + get_field::>(&mut json, &mut errors, "detachedFiles", None) + .unwrap_or_default() .into_iter() .map(AbsPathBuf::assert) .collect(); patch_old_style::patch_json_for_outdated_configs(&mut json); - self.root_config.global = - RootGlobalConfigData(GlobalConfigData::from_json(&mut json, &mut errors)); + let input = ConfigInput::from_json(json, &mut errors); + self.root_config = RootConfigData::from_root_input(input); tracing::debug!("deserialized config data: {:#?}", self.root_config.global); self.snippets.clear(); for (name, def) in self.root_config.local.0.completion_snippets_custom.iter() { @@ -1244,7 +1226,7 @@ impl Config { } pub fn json_schema() -> serde_json::Value { - ConfigData::json_schema() + ConfigInput::json_schema() } pub fn root_path(&self) -> &AbsPathBuf { @@ -2325,43 +2307,83 @@ macro_rules! _default_str { macro_rules! _config_data { // modname is for the tests - ($modname:ident: struct $name:ident <- $input:ident { + ($modname:ident: struct $name:ident <- $input:ident -> $root:ident { $( $(#[doc=$doc:literal])* $field:ident $(| $alias:ident)*: $ty:ty = $(@$marker:ident: )? $default:expr, )* }) => { + /// All fields raw `T`, representing either a root config, or a root config + overrides from + /// some distal configuration blob(s). #[allow(non_snake_case)] #[derive(Debug, Clone, Serialize)] struct $name { $($field: $ty,)* } + /// All fields `Option`, `None` representing fields not set in a particular JSON/TOML blob. #[allow(non_snake_case)] - #[derive(Debug, Clone, Serialize)] - struct $input { $($field: Option<$ty>,)* } + #[derive(Debug, Clone, Serialize, Default)] + struct $input { $( + #[serde(skip_serializing_if = "Option::is_none")] + $field: Option<$ty>, + )* } + + /// Newtype of + #[doc = stringify!($name)] + /// expressing that this was read directly from a single, root config blob. + #[derive(Debug, Clone, Default)] + struct $root($name); + + impl $root { + /// Reads a single root config blob. All fields are either set by the config blob, or to the + /// default value. + fn from_root_input(input: $input) -> Self { + let mut data = $name::default(); + data.apply_input(input); + Self(data) + } + } + + impl Default for $name { + fn default() -> Self { + $name {$( + $field: default_val!($(@$marker:)? $default, $ty), + )*} + } + } impl $name { + /// Applies overrides from some more local config blob, to self. #[allow(unused)] - fn from_json(json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name { - $name {$( + fn apply_input(&mut self, input: $input) { + $( + if let Some(value) = input.$field { + self.$field = value; + } + )* + } + } + + impl $input { + #[allow(unused)] + fn from_json(json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> Self { + Self {$( $field: get_field( json, error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - default_val!($(@$marker:)? $default, $ty), ), )*} } #[allow(unused)] - fn from_toml(toml: &mut toml::Table , error_sink: &mut Vec<(String, toml::de::Error)>) -> $name { - $name {$( + fn from_toml(toml: &mut toml::Table , error_sink: &mut Vec<(String, toml::de::Error)>) -> Self { + Self {$( $field: get_field_toml::<$ty>( toml, error_sink, stringify!($field), None$(.or(Some(stringify!($alias))))*, - default_val!($(@$marker:)? $default, $ty), ), )*} } @@ -2377,14 +2399,6 @@ macro_rules! _config_data { },)* ]) } - - fn apply_input(&mut self, input: $input) { - $( - if let Some(value) = input.$field { - self.$field = value; - } - )* - } } mod $modname { @@ -2400,7 +2414,8 @@ use _config_data as config_data; use _default_str as default_str; use _default_val as default_val; -#[derive(Debug, Clone, Serialize)] +/// All of the config levels, all fields raw `T`. Represents a root configuration, or a config set +#[derive(Debug, Clone, Serialize, Default)] struct ConfigData { #[serde(flatten)] global: GlobalConfigData, @@ -2410,34 +2425,47 @@ struct ConfigData { client: ClientConfigData, } -impl ConfigData { +/// All of the config levels, all fields `Option`, to describe fields that are actually set by +/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to +/// all fields being None. +#[derive(Debug, Clone, Serialize, Default)] +struct ConfigInput { + #[serde(flatten)] + global: GlobalConfigInput, + #[serde(flatten)] + local: LocalConfigInput, + #[serde(flatten)] + client: ClientConfigInput, +} + +impl ConfigInput { fn from_json( mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>, - ) -> ConfigData { - ConfigData { - global: GlobalConfigData::from_json(&mut json, error_sink), - local: LocalConfigData::from_json(&mut json, error_sink), - client: ClientConfigData::from_json(&mut json, error_sink), + ) -> ConfigInput { + ConfigInput { + global: GlobalConfigInput::from_json(&mut json, error_sink), + local: LocalConfigInput::from_json(&mut json, error_sink), + client: ClientConfigInput::from_json(&mut json, error_sink), } } fn from_toml( mut toml: toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>, - ) -> ConfigData { - ConfigData { - global: GlobalConfigData::from_toml(&mut toml, error_sink), - local: LocalConfigData::from_toml(&mut toml, error_sink), - client: ClientConfigData::from_toml(&mut toml, error_sink), + ) -> ConfigInput { + ConfigInput { + global: GlobalConfigInput::from_toml(&mut toml, error_sink), + local: LocalConfigInput::from_toml(&mut toml, error_sink), + client: ClientConfigInput::from_toml(&mut toml, error_sink), } } fn schema_fields() -> Vec { let mut fields = Vec::new(); - GlobalConfigData::schema_fields(&mut fields); - LocalConfigData::schema_fields(&mut fields); - ClientConfigData::schema_fields(&mut fields); + GlobalConfigInput::schema_fields(&mut fields); + LocalConfigInput::schema_fields(&mut fields); + ClientConfigInput::schema_fields(&mut fields); // HACK: sort the fields, so the diffs on the generated docs/schema are smaller fields.sort_by_key(|&(x, ..)| x); fields @@ -2458,8 +2486,7 @@ fn get_field_toml( error_sink: &mut Vec<(String, toml::de::Error)>, field: &'static str, alias: Option<&'static str>, - default: T, -) -> T { +) -> Option { alias .into_iter() .chain(iter::once(field)) @@ -2487,7 +2514,6 @@ fn get_field_toml( None } }) - .unwrap_or(default) } fn get_field( @@ -2495,8 +2521,7 @@ fn get_field( error_sink: &mut Vec<(String, serde_json::Error)>, field: &'static str, alias: Option<&'static str>, - default: T, -) -> T { +) -> Option { // XXX: check alias first, to work around the VS Code where it pre-fills the // defaults instead of sending an empty object. alias @@ -2517,7 +2542,6 @@ fn get_field( None } }) - .unwrap_or(default) } type SchemaField = (&'static str, &'static str, &'static [&'static str], String); @@ -2978,7 +3002,7 @@ mod tests { #[test] fn generate_config_documentation() { let docs_path = project_root().join("docs/user/generated_config.adoc"); - let expected = ConfigData::manual(); + let expected = ConfigInput::manual(); ensure_file_contents(&docs_path, &expected); } From 7b4c364457b2c98e5da3e542463e2c7ca453e875 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 21 Jan 2024 02:38:54 +1100 Subject: [PATCH 26/52] Add clone_with_overrides --- crates/rust-analyzer/src/config.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index d166920ab097..e211a7cb3de7 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -2361,6 +2361,13 @@ macro_rules! _config_data { } )* } + + #[allow(unused)] + fn clone_with_overrides(&self, input: $input) -> Self { + Self {$( + $field: input.$field.unwrap_or_else(|| self.$field.clone()), + )*} + } } impl $input { From 10c1db6f98f20e69506aac902fef5c0b75100af9 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 22:02:15 +1100 Subject: [PATCH 27/52] Compact Debug implementation for *ConfigInput --- crates/rust-analyzer/src/config.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e211a7cb3de7..3c65356bb721 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -2321,12 +2321,24 @@ macro_rules! _config_data { /// All fields `Option`, `None` representing fields not set in a particular JSON/TOML blob. #[allow(non_snake_case)] - #[derive(Debug, Clone, Serialize, Default)] + #[derive(Clone, Serialize, Default)] struct $input { $( #[serde(skip_serializing_if = "Option::is_none")] $field: Option<$ty>, )* } + impl std::fmt::Debug for $input { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut s = f.debug_struct(stringify!($input)); + $( + if let Some(val) = self.$field.as_ref() { + s.field(stringify!($field), val); + } + )* + s.finish() + } + } + /// Newtype of #[doc = stringify!($name)] /// expressing that this was read directly from a single, root config blob. From 7c5f33c1cd5ae3a258e689bc60a004e9397ef997 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 21 Jan 2024 15:06:42 +1100 Subject: [PATCH 28/52] ConfigTree --- Cargo.lock | 17 ++ crates/rust-analyzer/Cargo.toml | 2 + crates/rust-analyzer/src/config.rs | 1 + crates/rust-analyzer/src/config/tree.rs | 241 ++++++++++++++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 crates/rust-analyzer/src/config/tree.rs diff --git a/Cargo.lock b/Cargo.lock index d5cebd5102de..6265568b25b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,6 +792,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indextree" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c40411d0e5c63ef1323c3d09ce5ec6d84d71531e18daed0743fccea279d7deb6" + [[package]] name = "inotify" version = "0.9.6" @@ -1586,6 +1592,7 @@ dependencies = [ "ide-db", "ide-ssr", "indexmap 2.0.0", + "indextree", "itertools", "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "load-cargo", @@ -1609,6 +1616,7 @@ dependencies = [ "scip", "serde", "serde_json", + "slotmap", "sourcegen", "stdx", "syntax", @@ -1785,6 +1793,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.10.0" diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 7d6326448d8c..4567f382f89b 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -75,6 +75,8 @@ toolchain.workspace = true vfs-notify.workspace = true vfs.workspace = true la-arena.workspace = true +indextree = "4.6.0" +slotmap = "1.0.7" [target.'cfg(windows)'.dependencies] winapi = "0.3.9" diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 3c65356bb721..9a1f44010e05 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -42,6 +42,7 @@ use crate::{ }; mod patch_old_style; +mod tree; // Conventions for configuration keys to preserve maximal extendability without breakage: // - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable` diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs new file mode 100644 index 000000000000..fb470ae5fe94 --- /dev/null +++ b/crates/rust-analyzer/src/config/tree.rs @@ -0,0 +1,241 @@ +use indextree::NodeId; +use parking_lot::{RwLock, RwLockUpgradableReadGuard}; +use rustc_hash::FxHashMap; +use slotmap::SlotMap; +use std::sync::Arc; +use vfs::{FileId, Vfs}; + +use super::{ConfigInput, LocalConfigData, RootLocalConfigData}; + +pub struct ConcurrentConfigTree { + // One rwlock on the whole thing is probably fine. + // If you have 40,000 crates and you need to edit your config 200x/second, let us know. + rwlock: RwLock, +} + +pub enum ConfigTreeError { + Removed, + NonExistent, + Utf8(vfs::VfsPath, std::str::Utf8Error), + TomlParse(vfs::VfsPath, toml::de::Error), + TomlDeserialize { path: vfs::VfsPath, field: String, error: toml::de::Error }, +} + +/// Some rust-analyzer.toml files have changed, and/or the LSP client sent a new configuration. +pub struct ConfigChanges { + ra_toml_changes: Vec, + client_change: Option>, +} + +impl ConcurrentConfigTree { + pub fn apply_changes(&self, changes: ConfigChanges, vfs: &Vfs) -> Vec { + let mut errors = Vec::new(); + self.rwlock.write().apply_changes(changes, vfs, &mut errors); + errors + } + pub fn read_config(&self, file_id: FileId) -> Result, ConfigTreeError> { + let reader = self.rwlock.upgradable_read(); + if let Some(computed) = reader.read_only(file_id)? { + return Ok(computed); + } else { + let mut writer = RwLockUpgradableReadGuard::upgrade(reader); + return writer.compute(file_id); + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum ConfigSource { + ClientConfig, + RaToml(FileId), +} + +slotmap::new_key_type! { + struct ComputedIdx; +} + +struct ConfigNode { + src: ConfigSource, + input: Arc, + computed: ComputedIdx, +} + +struct ConfigTree { + tree: indextree::Arena, + client_config: NodeId, + ra_file_id_map: FxHashMap, + computed: SlotMap>>, +} + +fn parse_toml( + file_id: FileId, + vfs: &Vfs, + scratch: &mut Vec<(String, toml::de::Error)>, + errors: &mut Vec, +) -> Option> { + let content = vfs.file_contents(file_id); + let path = vfs.file_path(file_id); + let content_str = match std::str::from_utf8(content) { + Err(e) => { + tracing::error!("non-UTF8 TOML content for {path}: {e}"); + errors.push(ConfigTreeError::Utf8(path, e)); + return None; + } + Ok(str) => str, + }; + let table = match toml::from_str(content_str) { + Ok(table) => table, + Err(e) => { + errors.push(ConfigTreeError::TomlParse(path, e)); + return None; + } + }; + let input = Arc::new(ConfigInput::from_toml(table, scratch)); + scratch.drain(..).for_each(|(field, error)| { + errors.push(ConfigTreeError::TomlDeserialize { path: path.clone(), field, error }); + }); + Some(input) +} + +impl ConfigTree { + fn new() -> Self { + let mut tree = indextree::Arena::new(); + let mut computed = SlotMap::default(); + let client_config = tree.new_node(ConfigNode { + src: ConfigSource::ClientConfig, + input: Arc::new(ConfigInput::default()), + computed: computed.insert(Option::>::None), + }); + Self { client_config, ra_file_id_map: FxHashMap::default(), tree, computed } + } + + fn read_only(&self, file_id: FileId) -> Result>, ConfigTreeError> { + let node_id = *self.ra_file_id_map.get(&file_id).ok_or(ConfigTreeError::NonExistent)?; + // indextree does not check this during get(), probably for perf reasons? + // get() is apparently only a bounds check + if node_id.is_removed(&self.tree) { + return Err(ConfigTreeError::Removed); + } + let node = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.get(); + Ok(self.computed[node.computed].clone()) + } + + fn compute(&mut self, file_id: FileId) -> Result, ConfigTreeError> { + let node_id = *self.ra_file_id_map.get(&file_id).ok_or(ConfigTreeError::NonExistent)?; + self.compute_inner(node_id) + } + fn compute_inner(&mut self, node_id: NodeId) -> Result, ConfigTreeError> { + if node_id.is_removed(&self.tree) { + return Err(ConfigTreeError::Removed); + } + let node = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.get(); + let idx = node.computed; + let slot = &mut self.computed[idx]; + if let Some(slot) = slot { + Ok(slot.clone()) + } else { + let self_computed = if let Some(parent) = + self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.parent() + { + let self_input = node.input.clone(); + let parent_computed = self.compute_inner(parent)?; + Arc::new(parent_computed.clone_with_overrides(self_input.local.clone())) + } else { + // We have hit a root node + let self_input = node.input.clone(); + let root_local = RootLocalConfigData::from_root_input(self_input.local.clone()); + Arc::new(root_local.0) + }; + // Get a new &mut slot because self.compute(parent) also gets mut access + let slot = &mut self.computed[idx]; + slot.replace(self_computed.clone()); + Ok(self_computed) + } + } + + fn insert_toml(&mut self, file_id: FileId, input: Arc) -> NodeId { + let computed = self.computed.insert(None); + let node = + self.tree.new_node(ConfigNode { src: ConfigSource::RaToml(file_id), input, computed }); + self.ra_file_id_map.insert(file_id, node); + node + } + + fn update_toml( + &mut self, + file_id: FileId, + input: Arc, + ) -> Result<(), ConfigTreeError> { + let Some(node_id) = self.ra_file_id_map.get(&file_id).cloned() else { + return Err(ConfigTreeError::NonExistent); + }; + if node_id.is_removed(&self.tree) { + return Err(ConfigTreeError::Removed); + } + let node = self.tree.get_mut(node_id).ok_or(ConfigTreeError::NonExistent)?; + node.get_mut().input = input; + + self.invalidate_subtree(node_id); + Ok(()) + } + + fn invalidate_subtree(&mut self, node_id: NodeId) { + // + // This is why we need the computed values outside the indextree: we iterate immutably + // over the tree while holding a &mut self.computed. + node_id.descendants(&self.tree).for_each(|x| { + let Some(desc) = self.tree.get(x) else { + return; + }; + self.computed.get_mut(desc.get().computed).take(); + }); + } + + fn remove_toml(&mut self, file_id: FileId) -> Option<()> { + let node_id = self.ra_file_id_map.remove(&file_id)?; + if node_id.is_removed(&self.tree) { + return None; + } + let node = self.tree.get(node_id)?; + let idx = node.get().computed; + let _ = self.computed.remove(idx); + self.invalidate_subtree(node_id); + Some(()) + } + + fn apply_changes( + &mut self, + changes: ConfigChanges, + vfs: &Vfs, + errors: &mut Vec, + ) { + let mut scratch_errors = Vec::new(); + let ConfigChanges { client_change, ra_toml_changes } = changes; + if let Some(change) = client_change { + let node = + self.tree.get_mut(self.client_config).expect("client_config node should exist"); + node.get_mut().input = change; + self.invalidate_subtree(self.client_config); + } + for change in ra_toml_changes { + // turn and face the strain + match change.change_kind { + vfs::ChangeKind::Create => { + let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors) + .unwrap_or_default(); + let _new_node = self.insert_toml(change.file_id, input); + } + vfs::ChangeKind::Modify => { + let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors) + .unwrap_or_default(); + if let Err(e) = self.update_toml(change.file_id, input) { + errors.push(e); + } + } + vfs::ChangeKind::Delete => { + self.remove_toml(change.file_id); + } + } + } + } +} From 9c506a963a0d8c828cec6c2fd385d6a286d386ca Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 20:49:59 +1100 Subject: [PATCH 29/52] Start building the tree construction --- crates/rust-analyzer/src/config/tree.rs | 80 +++++++++++++++++++++---- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index fb470ae5fe94..bc2cb33563eb 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -24,7 +24,47 @@ pub enum ConfigTreeError { /// Some rust-analyzer.toml files have changed, and/or the LSP client sent a new configuration. pub struct ConfigChanges { ra_toml_changes: Vec, + xdg_config_change: Option>, client_change: Option>, + parent_changes: Vec, +} + +pub struct ConfigParentChange { + /// The config node in question + pub node: FileId, + pub parent: ConfigParent, +} + +pub enum ConfigParent { + /// The node is now a root in its own right, but still inherits from the config in XDG_CONFIG_HOME + /// etc + UserDefault, + /// The node is now a child of another rust-analyzer.toml. Even if that one is a non-existent + /// file, it's fine. + /// + /// + /// ```ignore,text + /// /project_root/ + /// rust-analyzer.toml + /// crate_a/ + /// crate_b/ + /// rust-analyzer.toml + /// + /// ``` + /// + /// ```ignore + /// // imagine set_file_contents = vfs.set_file_contents() and then get the vfs.file_id() + /// + /// let root = vfs.set_file_contents("/project_root/rust-analyzer.toml", Some("...")); + /// let crate_a = vfs.set_file_contents("/project_root/crate_a/rust-analyzer.toml", None); + /// let crate_b = vfs.set_file_contents("/project_root/crate_a/crate_b/rust-analyzer.toml", Some("...")); + /// let config_parent_changes = [ + /// ConfigParentChange { node: root, parent: ConfigParent::UserDefault }, + /// ConfigParentChange { node: crate_a, parent: ConfigParent::Parent(root) }, + /// ConfigParentChange { node: crate_b, parent: ConfigParent::Parent(crate_a) } + /// ]; + /// ``` + Parent(FileId), } impl ConcurrentConfigTree { @@ -56,13 +96,15 @@ slotmap::new_key_type! { struct ConfigNode { src: ConfigSource, + // TODO: make option input: Arc, computed: ComputedIdx, } struct ConfigTree { tree: indextree::Arena, - client_config: NodeId, + client_config: Arc, + xdg_config_node_id: NodeId, ra_file_id_map: FxHashMap, computed: SlotMap>>, } @@ -98,15 +140,24 @@ fn parse_toml( } impl ConfigTree { - fn new() -> Self { + fn new(xdg_config_file_id: FileId) -> Self { let mut tree = indextree::Arena::new(); let mut computed = SlotMap::default(); - let client_config = tree.new_node(ConfigNode { - src: ConfigSource::ClientConfig, + let mut ra_file_id_map = FxHashMap::default(); + let xdg_config = tree.new_node(ConfigNode { + src: ConfigSource::RaToml(xdg_config_file_id), input: Arc::new(ConfigInput::default()), computed: computed.insert(Option::>::None), }); - Self { client_config, ra_file_id_map: FxHashMap::default(), tree, computed } + ra_file_id_map.insert(xdg_config_file_id, xdg_config); + + Self { + client_config: Arc::new(Default::default()), + xdg_config_node_id: xdg_config, + ra_file_id_map, + tree, + computed, + } } fn read_only(&self, file_id: FileId) -> Result>, ConfigTreeError> { @@ -205,18 +256,27 @@ impl ConfigTree { fn apply_changes( &mut self, - changes: ConfigChanges, + mut changes: ConfigChanges, vfs: &Vfs, errors: &mut Vec, ) { let mut scratch_errors = Vec::new(); - let ConfigChanges { client_change, ra_toml_changes } = changes; + let ConfigChanges { client_change, ra_toml_changes, xdg_config_change, parent_changes } = + changes; + if let Some(change) = client_change { - let node = - self.tree.get_mut(self.client_config).expect("client_config node should exist"); + self.client_config = change; + } + + if let Some(change) = xdg_config_change { + let node = self + .tree + .get_mut(self.xdg_config_node_id) + .expect("client_config node should exist"); node.get_mut().input = change; - self.invalidate_subtree(self.client_config); + self.invalidate_subtree(self.xdg_config_node_id); } + for change in ra_toml_changes { // turn and face the strain match change.change_kind { From c442ea1acbd74b2022fad9896856a98bb11cb5c4 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 20:58:04 +1100 Subject: [PATCH 30/52] Store empty input as None --- crates/rust-analyzer/src/config/tree.rs | 52 ++++++++++++++----------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index bc2cb33563eb..32e65afa68a5 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -24,8 +24,14 @@ pub enum ConfigTreeError { /// Some rust-analyzer.toml files have changed, and/or the LSP client sent a new configuration. pub struct ConfigChanges { ra_toml_changes: Vec, - xdg_config_change: Option>, - client_change: Option>, + /// - `None` => no change + /// - `Some(None)` => the XDG_CONFIG_HOME rust-analyzer.toml file was deleted + /// - `Some(Some(...))` => the XDG_CONFIG_HOME rust-analyzer.toml file was updated + xdg_config_change: Option>>, + /// - `None` => no change + /// - `Some(None)` => the client config was removed / reset or something + /// - `Some(Some(...))` => the client config was updated + client_change: Option>>, parent_changes: Vec, } @@ -96,14 +102,13 @@ slotmap::new_key_type! { struct ConfigNode { src: ConfigSource, - // TODO: make option - input: Arc, + input: Option>, computed: ComputedIdx, } struct ConfigTree { tree: indextree::Arena, - client_config: Arc, + client_config: Option>, xdg_config_node_id: NodeId, ra_file_id_map: FxHashMap, computed: SlotMap>>, @@ -117,6 +122,9 @@ fn parse_toml( ) -> Option> { let content = vfs.file_contents(file_id); let path = vfs.file_path(file_id); + if content.is_empty() { + return None; + } let content_str = match std::str::from_utf8(content) { Err(e) => { tracing::error!("non-UTF8 TOML content for {path}: {e}"); @@ -146,18 +154,12 @@ impl ConfigTree { let mut ra_file_id_map = FxHashMap::default(); let xdg_config = tree.new_node(ConfigNode { src: ConfigSource::RaToml(xdg_config_file_id), - input: Arc::new(ConfigInput::default()), + input: None, computed: computed.insert(Option::>::None), }); ra_file_id_map.insert(xdg_config_file_id, xdg_config); - Self { - client_config: Arc::new(Default::default()), - xdg_config_node_id: xdg_config, - ra_file_id_map, - tree, - computed, - } + Self { client_config: None, xdg_config_node_id: xdg_config, ra_file_id_map, tree, computed } } fn read_only(&self, file_id: FileId) -> Result>, ConfigTreeError> { @@ -190,12 +192,20 @@ impl ConfigTree { { let self_input = node.input.clone(); let parent_computed = self.compute_inner(parent)?; - Arc::new(parent_computed.clone_with_overrides(self_input.local.clone())) + if let Some(input) = self_input.as_deref() { + Arc::new(parent_computed.clone_with_overrides(input.local.clone())) + } else { + parent_computed + } } else { // We have hit a root node let self_input = node.input.clone(); - let root_local = RootLocalConfigData::from_root_input(self_input.local.clone()); - Arc::new(root_local.0) + if let Some(input) = self_input.as_deref() { + let root_local = RootLocalConfigData::from_root_input(input.local.clone()); + Arc::new(root_local.0) + } else { + Arc::new(LocalConfigData::default()) + } }; // Get a new &mut slot because self.compute(parent) also gets mut access let slot = &mut self.computed[idx]; @@ -204,7 +214,7 @@ impl ConfigTree { } } - fn insert_toml(&mut self, file_id: FileId, input: Arc) -> NodeId { + fn insert_toml(&mut self, file_id: FileId, input: Option>) -> NodeId { let computed = self.computed.insert(None); let node = self.tree.new_node(ConfigNode { src: ConfigSource::RaToml(file_id), input, computed }); @@ -215,7 +225,7 @@ impl ConfigTree { fn update_toml( &mut self, file_id: FileId, - input: Arc, + input: Option>, ) -> Result<(), ConfigTreeError> { let Some(node_id) = self.ra_file_id_map.get(&file_id).cloned() else { return Err(ConfigTreeError::NonExistent); @@ -281,13 +291,11 @@ impl ConfigTree { // turn and face the strain match change.change_kind { vfs::ChangeKind::Create => { - let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors) - .unwrap_or_default(); + let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors); let _new_node = self.insert_toml(change.file_id, input); } vfs::ChangeKind::Modify => { - let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors) - .unwrap_or_default(); + let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors); if let Err(e) = self.update_toml(change.file_id, input) { errors.push(e); } From 18ff646e80991b50a6d38c826d929dfca42351b2 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 22:02:41 +1100 Subject: [PATCH 31/52] config tree gets a tree shape --- crates/rust-analyzer/src/config/tree.rs | 144 ++++++++++++++++++++++-- crates/vfs/src/lib.rs | 13 ++- 2 files changed, 143 insertions(+), 14 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 32e65afa68a5..643e3bc5a085 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -2,7 +2,7 @@ use indextree::NodeId; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use rustc_hash::FxHashMap; use slotmap::SlotMap; -use std::sync::Arc; +use std::{fmt, sync::Arc}; use vfs::{FileId, Vfs}; use super::{ConfigInput, LocalConfigData, RootLocalConfigData}; @@ -13,6 +13,19 @@ pub struct ConcurrentConfigTree { rwlock: RwLock, } +impl ConcurrentConfigTree { + fn new(config_tree: ConfigTree) -> Self { + Self { rwlock: RwLock::new(config_tree) } + } +} + +impl fmt::Debug for ConcurrentConfigTree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.rwlock.read().fmt(f) + } +} + +#[derive(Debug)] pub enum ConfigTreeError { Removed, NonExistent, @@ -35,12 +48,14 @@ pub struct ConfigChanges { parent_changes: Vec, } +#[derive(Debug)] pub struct ConfigParentChange { /// The config node in question - pub node: FileId, + pub file_id: FileId, pub parent: ConfigParent, } +#[derive(Debug)] pub enum ConfigParent { /// The node is now a root in its own right, but still inherits from the config in XDG_CONFIG_HOME /// etc @@ -100,6 +115,7 @@ slotmap::new_key_type! { struct ComputedIdx; } +#[derive(Debug)] struct ConfigNode { src: ConfigSource, input: Option>, @@ -190,6 +206,7 @@ impl ConfigTree { let self_computed = if let Some(parent) = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.parent() { + tracing::trace!("looking at parent of {node_id:?} -> {parent:?}"); let self_input = node.input.clone(); let parent_computed = self.compute_inner(parent)?; if let Some(input) = self_input.as_deref() { @@ -198,6 +215,7 @@ impl ConfigTree { parent_computed } } else { + tracing::trace!("{node_id:?} is a root node"); // We have hit a root node let self_input = node.input.clone(); if let Some(input) = self_input.as_deref() { @@ -218,7 +236,9 @@ impl ConfigTree { let computed = self.computed.insert(None); let node = self.tree.new_node(ConfigNode { src: ConfigSource::RaToml(file_id), input, computed }); - self.ra_file_id_map.insert(file_id, node); + if let Some(_removed) = self.ra_file_id_map.insert(file_id, node) { + panic!("ERROR: node should not have existed for {file_id:?} but it did"); + } node } @@ -226,9 +246,10 @@ impl ConfigTree { &mut self, file_id: FileId, input: Option>, - ) -> Result<(), ConfigTreeError> { + ) -> Result { let Some(node_id) = self.ra_file_id_map.get(&file_id).cloned() else { - return Err(ConfigTreeError::NonExistent); + let node_id = self.insert_toml(file_id, input); + return Ok(node_id); }; if node_id.is_removed(&self.tree) { return Err(ConfigTreeError::Removed); @@ -237,7 +258,14 @@ impl ConfigTree { node.get_mut().input = input; self.invalidate_subtree(node_id); - Ok(()) + Ok(node_id) + } + + fn ensure_node(&mut self, file_id: FileId) -> NodeId { + let Some(&node_id) = self.ra_file_id_map.get(&file_id) else { + return self.insert_toml(file_id, None); + }; + node_id } fn invalidate_subtree(&mut self, node_id: NodeId) { @@ -253,20 +281,19 @@ impl ConfigTree { } fn remove_toml(&mut self, file_id: FileId) -> Option<()> { - let node_id = self.ra_file_id_map.remove(&file_id)?; + let node_id = *self.ra_file_id_map.get(&file_id)?; if node_id.is_removed(&self.tree) { return None; } - let node = self.tree.get(node_id)?; - let idx = node.get().computed; - let _ = self.computed.remove(idx); + let node = self.tree.get_mut(node_id)?.get_mut(); + node.input = None; self.invalidate_subtree(node_id); Some(()) } fn apply_changes( &mut self, - mut changes: ConfigChanges, + changes: ConfigChanges, vfs: &Vfs, errors: &mut Vec, ) { @@ -287,12 +314,23 @@ impl ConfigTree { self.invalidate_subtree(self.xdg_config_node_id); } + for ConfigParentChange { file_id, parent } in parent_changes { + let node_id = self.ensure_node(file_id); + let parent_node_id = match parent { + ConfigParent::UserDefault => self.xdg_config_node_id, + ConfigParent::Parent(parent_file_id) => self.ensure_node(parent_file_id), + }; + // order of children within the parent node does not matter + tracing::trace!("appending child {node_id:?} to {parent_node_id:?}"); + parent_node_id.append(node_id, &mut self.tree); + } + for change in ra_toml_changes { // turn and face the strain match change.change_kind { vfs::ChangeKind::Create => { let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors); - let _new_node = self.insert_toml(change.file_id, input); + let _new_node = self.update_toml(change.file_id, input); } vfs::ChangeKind::Modify => { let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors); @@ -307,3 +345,85 @@ impl ConfigTree { } } } + +impl fmt::Debug for ConfigTree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.tree.fmt(f) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use vfs::{AbsPathBuf, VfsPath}; + + fn alloc_file_id(vfs: &mut Vfs, s: &str) -> FileId { + let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap(); + + let vfs_path = VfsPath::from(abs_path); + // FIXME: the vfs should expose this functionality more simply. + // We shouldn't have to clone the vfs path just to get a FileId. + let file_id = vfs.alloc_file_id(vfs_path); + vfs.set_file_id_contents(file_id, None); + file_id + } + + fn alloc_config(vfs: &mut Vfs, s: &str, config: &str) -> FileId { + let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap(); + + let vfs_path = VfsPath::from(abs_path); + // FIXME: the vfs should expose this functionality more simply. + // We shouldn't have to clone the vfs path just to get a FileId. + let file_id = vfs.alloc_file_id(vfs_path); + vfs.set_file_id_contents(file_id, Some(config.to_string().into_bytes())); + file_id + } + + use super::*; + #[test] + fn basic() { + let mut vfs = Vfs::default(); + let xdg_config_file_id = + alloc_file_id(&mut vfs, "/home/.config/rust-analyzer/rust-analyzer.toml"); + let config_tree = ConcurrentConfigTree::new(ConfigTree::new(xdg_config_file_id)); + + let root = alloc_config( + &mut vfs, + "/root/rust-analyzer.toml", + r#" + [completion.autoself] + enable = false + "#, + ); + + let crate_a = alloc_config( + &mut vfs, + "/root/crate_a/rust-analyzer.toml", + r#" + [completion.autoimport] + enable = false + "#, + ); + + let parent_changes = + vec![ConfigParentChange { file_id: crate_a, parent: ConfigParent::Parent(root) }]; + + let changes = ConfigChanges { + // Normally you will filter these! + ra_toml_changes: vfs.take_changes(), + parent_changes, + xdg_config_change: None, + client_change: None, + }; + + dbg!(config_tree.apply_changes(changes, &vfs)); + dbg!(&config_tree); + + let local = config_tree.read_config(crate_a).unwrap(); + // from root + assert_eq!(local.completion_autoself_enable, false); + // from crate_a + assert_eq!(local.completion_autoimport_enable, false); + } +} diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 06004adad34a..65882f72f5a7 100644 --- a/crates/vfs/src/lib.rs +++ b/crates/vfs/src/lib.rs @@ -133,6 +133,11 @@ impl Vfs { self.get(file_id).as_deref().unwrap() } + /// File content, but returns None if the file was deleted. + pub fn file_contents_opt(&self, file_id: FileId) -> Option<&[u8]> { + self.get(file_id).as_deref() + } + /// Returns the overall memory usage for the stored files. pub fn memory_usage(&self) -> usize { self.data.iter().flatten().map(|d| d.capacity()).sum() @@ -157,8 +162,12 @@ impl Vfs { /// /// If the path does not currently exists in the `Vfs`, allocates a new /// [`FileId`] for it. - pub fn set_file_contents(&mut self, path: VfsPath, mut contents: Option>) -> bool { + pub fn set_file_contents(&mut self, path: VfsPath, contents: Option>) -> bool { let file_id = self.alloc_file_id(path); + self.set_file_id_contents(file_id, contents) + } + + pub fn set_file_id_contents(&mut self, file_id: FileId, mut contents: Option>) -> bool { let change_kind = match (self.get(file_id), &contents) { (None, None) => return false, (Some(old), Some(new)) if old == new => return false, @@ -196,7 +205,7 @@ impl Vfs { /// - Else, returns `path`'s id. /// /// Does not record a change. - fn alloc_file_id(&mut self, path: VfsPath) -> FileId { + pub fn alloc_file_id(&mut self, path: VfsPath) -> FileId { let file_id = self.interner.intern(path); let idx = file_id.0 as usize; let len = self.data.len().max(idx + 1); From 47c7d45731be941430a399869b361751ce789b81 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 22:22:33 +1100 Subject: [PATCH 32/52] Make the client config override everything --- crates/rust-analyzer/src/config/tree.rs | 38 ++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 643e3bc5a085..96a6c4e21d67 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -180,18 +180,38 @@ impl ConfigTree { fn read_only(&self, file_id: FileId) -> Result>, ConfigTreeError> { let node_id = *self.ra_file_id_map.get(&file_id).ok_or(ConfigTreeError::NonExistent)?; + let stored = self.read_only_inner(node_id)?; + Ok(stored.map(|stored| { + if let Some(client_config) = self.client_config.as_deref() { + stored.clone_with_overrides(client_config.local.clone()).into() + } else { + stored + } + })) + } + + fn read_only_inner( + &self, + node_id: NodeId, + ) -> Result>, ConfigTreeError> { // indextree does not check this during get(), probably for perf reasons? // get() is apparently only a bounds check if node_id.is_removed(&self.tree) { return Err(ConfigTreeError::Removed); } let node = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.get(); - Ok(self.computed[node.computed].clone()) + let stored = self.computed[node.computed].clone(); + Ok(stored) } fn compute(&mut self, file_id: FileId) -> Result, ConfigTreeError> { let node_id = *self.ra_file_id_map.get(&file_id).ok_or(ConfigTreeError::NonExistent)?; - self.compute_inner(node_id) + let computed = self.compute_inner(node_id)?; + Ok(if let Some(client_config) = self.client_config.as_deref() { + computed.clone_with_overrides(client_config.local.clone()).into() + } else { + computed + }) } fn compute_inner(&mut self, node_id: NodeId) -> Result, ConfigTreeError> { if node_id.is_removed(&self.tree) { @@ -403,6 +423,9 @@ mod tests { r#" [completion.autoimport] enable = false + # will be overridden by client + [semanticHighlighting.strings] + enable = true "#, ); @@ -413,8 +436,13 @@ mod tests { // Normally you will filter these! ra_toml_changes: vfs.take_changes(), parent_changes, - xdg_config_change: None, - client_change: None, + client_change: Some(Some(Arc::new(ConfigInput { + local: crate::config::LocalConfigInput { + semanticHighlighting_strings_enable: Some(false), + ..Default::default() + }, + ..Default::default() + }))), }; dbg!(config_tree.apply_changes(changes, &vfs)); @@ -425,5 +453,7 @@ mod tests { assert_eq!(local.completion_autoself_enable, false); // from crate_a assert_eq!(local.completion_autoimport_enable, false); + // from client + assert_eq!(local.semanticHighlighting_strings_enable, false); } } From 69bdf4a85de3421a0978159e29df0489c39dae34 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 22:22:51 +1100 Subject: [PATCH 33/52] Add failing test for the xdg config being inherited --- crates/rust-analyzer/src/config.rs | 2 +- crates/rust-analyzer/src/config/tree.rs | 45 ++++++++++++++++--------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 9a1f44010e05..5fce21ddce77 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -2195,7 +2195,7 @@ enum AdjustmentHintsDef { Reborrow, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(untagged)] enum DiscriminantHintsDef { #[serde(with = "true_or_always")] diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 96a6c4e21d67..adddb07038ab 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -38,10 +38,6 @@ pub enum ConfigTreeError { pub struct ConfigChanges { ra_toml_changes: Vec, /// - `None` => no change - /// - `Some(None)` => the XDG_CONFIG_HOME rust-analyzer.toml file was deleted - /// - `Some(Some(...))` => the XDG_CONFIG_HOME rust-analyzer.toml file was updated - xdg_config_change: Option>>, - /// - `None` => no change /// - `Some(None)` => the client config was removed / reset or something /// - `Some(Some(...))` => the client config was updated client_change: Option>>, @@ -318,22 +314,12 @@ impl ConfigTree { errors: &mut Vec, ) { let mut scratch_errors = Vec::new(); - let ConfigChanges { client_change, ra_toml_changes, xdg_config_change, parent_changes } = - changes; + let ConfigChanges { client_change, ra_toml_changes, parent_changes } = changes; if let Some(change) = client_change { self.client_config = change; } - if let Some(change) = xdg_config_change { - let node = self - .tree - .get_mut(self.xdg_config_node_id) - .expect("client_config node should exist"); - node.get_mut().input = change; - self.invalidate_subtree(self.xdg_config_node_id); - } - for ConfigParentChange { file_id, parent } in parent_changes { let node_id = self.ensure_node(file_id); let parent_node_id = match parent { @@ -455,5 +441,34 @@ mod tests { assert_eq!(local.completion_autoimport_enable, false); // from client assert_eq!(local.semanticHighlighting_strings_enable, false); + + vfs.set_file_id_contents( + xdg_config_file_id, + Some( + r#" + # default is "never" + [inlayHints.discriminantHints] + enable = "always" + "# + .to_string() + .into_bytes(), + ), + ); + + let changes = ConfigChanges { + ra_toml_changes: vfs.take_changes(), + parent_changes: vec![], + client_change: None, + }; + dbg!(config_tree.apply_changes(changes, &vfs)); + + let local2 = config_tree.read_config(crate_a).unwrap(); + // should have recomputed + assert!(!Arc::ptr_eq(&local, &local2)); + + assert_eq!( + local.inlayHints_discriminantHints_enable, + crate::config::DiscriminantHintsDef::Always + ); } } From ab0149d0c40d6d8480ebeb13fc9c549480f7e0f7 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 22:57:14 +1100 Subject: [PATCH 34/52] fix config invalidation --- crates/rust-analyzer/src/config/tree.rs | 115 +++++++++++++++--------- 1 file changed, 74 insertions(+), 41 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index adddb07038ab..ed5fef388ce8 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -41,7 +41,7 @@ pub struct ConfigChanges { /// - `Some(None)` => the client config was removed / reset or something /// - `Some(Some(...))` => the client config was updated client_change: Option>>, - parent_changes: Vec, + parent_changes: FxHashMap, } #[derive(Debug)] @@ -103,7 +103,7 @@ impl ConcurrentConfigTree { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] enum ConfigSource { - ClientConfig, + XdgConfig(FileId), RaToml(FileId), } @@ -115,12 +115,13 @@ slotmap::new_key_type! { struct ConfigNode { src: ConfigSource, input: Option>, - computed: ComputedIdx, + computed_idx: ComputedIdx, } struct ConfigTree { tree: indextree::Arena, client_config: Option>, + xdg_config_file_id: FileId, xdg_config_node_id: NodeId, ra_file_id_map: FxHashMap, computed: SlotMap>>, @@ -164,14 +165,21 @@ impl ConfigTree { let mut tree = indextree::Arena::new(); let mut computed = SlotMap::default(); let mut ra_file_id_map = FxHashMap::default(); - let xdg_config = tree.new_node(ConfigNode { - src: ConfigSource::RaToml(xdg_config_file_id), + let xdg_config_node_id = tree.new_node(ConfigNode { + src: ConfigSource::XdgConfig(xdg_config_file_id), input: None, - computed: computed.insert(Option::>::None), + computed_idx: computed.insert(Option::>::None), }); - ra_file_id_map.insert(xdg_config_file_id, xdg_config); + ra_file_id_map.insert(xdg_config_file_id, xdg_config_node_id); - Self { client_config: None, xdg_config_node_id: xdg_config, ra_file_id_map, tree, computed } + Self { + client_config: None, + xdg_config_file_id, + xdg_config_node_id, + ra_file_id_map, + tree, + computed, + } } fn read_only(&self, file_id: FileId) -> Result>, ConfigTreeError> { @@ -196,7 +204,12 @@ impl ConfigTree { return Err(ConfigTreeError::Removed); } let node = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.get(); - let stored = self.computed[node.computed].clone(); + let stored = self.computed[node.computed_idx].clone(); + tracing::trace!( + "read_only_inner on {:?} got {:?}", + node.src, + stored.as_ref().map(|_| "some stored value") + ); Ok(stored) } @@ -214,7 +227,8 @@ impl ConfigTree { return Err(ConfigTreeError::Removed); } let node = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.get(); - let idx = node.computed; + tracing::trace!("compute_inner on {:?}", node.src); + let idx = node.computed_idx; let slot = &mut self.computed[idx]; if let Some(slot) = slot { Ok(slot.clone()) @@ -250,12 +264,17 @@ impl ConfigTree { fn insert_toml(&mut self, file_id: FileId, input: Option>) -> NodeId { let computed = self.computed.insert(None); - let node = - self.tree.new_node(ConfigNode { src: ConfigSource::RaToml(file_id), input, computed }); - if let Some(_removed) = self.ra_file_id_map.insert(file_id, node) { + let node_id = self.tree.new_node(ConfigNode { + src: ConfigSource::RaToml(file_id), + input, + computed_idx: computed, + }); + if let Some(_removed) = self.ra_file_id_map.insert(file_id, node_id) { panic!("ERROR: node should not have existed for {file_id:?} but it did"); } - node + // By default, everything is under the xdg_config_node_id + self.xdg_config_node_id.append(node_id, &mut self.tree); + node_id } fn update_toml( @@ -274,6 +293,7 @@ impl ConfigTree { node.get_mut().input = input; self.invalidate_subtree(node_id); + // tracing::trace!("invalidated subtree:\n{:#?}", node_id.debug_pretty_print(&self.tree)); Ok(node_id) } @@ -292,7 +312,16 @@ impl ConfigTree { let Some(desc) = self.tree.get(x) else { return; }; - self.computed.get_mut(desc.get().computed).take(); + let desc = desc.get(); + let Some(slot) = self.computed.get_mut(desc.computed_idx) else { + tracing::error!( + "computed_idx missing from computed local config slotmap: {:?}", + desc.computed_idx + ); + return; + }; + tracing::trace!("invalidating {x:?} / {:?}", desc.src); + slot.take(); }); } @@ -314,32 +343,22 @@ impl ConfigTree { errors: &mut Vec, ) { let mut scratch_errors = Vec::new(); - let ConfigChanges { client_change, ra_toml_changes, parent_changes } = changes; + let ConfigChanges { client_change, ra_toml_changes, mut parent_changes } = changes; if let Some(change) = client_change { self.client_config = change; } - for ConfigParentChange { file_id, parent } in parent_changes { - let node_id = self.ensure_node(file_id); - let parent_node_id = match parent { - ConfigParent::UserDefault => self.xdg_config_node_id, - ConfigParent::Parent(parent_file_id) => self.ensure_node(parent_file_id), - }; - // order of children within the parent node does not matter - tracing::trace!("appending child {node_id:?} to {parent_node_id:?}"); - parent_node_id.append(node_id, &mut self.tree); - } - for change in ra_toml_changes { // turn and face the strain match change.change_kind { - vfs::ChangeKind::Create => { - let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors); - let _new_node = self.update_toml(change.file_id, input); - } - vfs::ChangeKind::Modify => { + vfs::ChangeKind::Create | vfs::ChangeKind::Modify => { + if change.change_kind == vfs::ChangeKind::Create { + parent_changes.entry(change.file_id).or_insert(ConfigParent::UserDefault); + } let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors); + tracing::trace!("updating toml for {:?} to {:?}", change.file_id, input); + if let Err(e) = self.update_toml(change.file_id, input) { errors.push(e); } @@ -349,6 +368,18 @@ impl ConfigTree { } } } + + for (file_id, parent) in parent_changes { + let node_id = self.ensure_node(file_id); + let parent_node_id = match parent { + ConfigParent::Parent(parent_file_id) => self.ensure_node(parent_file_id), + ConfigParent::UserDefault if file_id == self.xdg_config_file_id => continue, + ConfigParent::UserDefault => self.xdg_config_node_id, + }; + // order of children within the parent node does not matter + tracing::trace!("appending child {node_id:?} to {parent_node_id:?}"); + parent_node_id.append(node_id, &mut self.tree); + } } } @@ -365,6 +396,7 @@ mod tests { use vfs::{AbsPathBuf, VfsPath}; fn alloc_file_id(vfs: &mut Vfs, s: &str) -> FileId { + tracing_subscriber::fmt().init(); let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap(); let vfs_path = VfsPath::from(abs_path); @@ -391,7 +423,7 @@ mod tests { fn basic() { let mut vfs = Vfs::default(); let xdg_config_file_id = - alloc_file_id(&mut vfs, "/home/.config/rust-analyzer/rust-analyzer.toml"); + alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); let config_tree = ConcurrentConfigTree::new(ConfigTree::new(xdg_config_file_id)); let root = alloc_config( @@ -415,8 +447,8 @@ mod tests { "#, ); - let parent_changes = - vec![ConfigParentChange { file_id: crate_a, parent: ConfigParent::Parent(root) }]; + let mut parent_changes = FxHashMap::default(); + parent_changes.insert(crate_a, ConfigParent::Parent(root)); let changes = ConfigChanges { // Normally you will filter these! @@ -432,7 +464,6 @@ mod tests { }; dbg!(config_tree.apply_changes(changes, &vfs)); - dbg!(&config_tree); let local = config_tree.read_config(crate_a).unwrap(); // from root @@ -442,6 +473,9 @@ mod tests { // from client assert_eq!(local.semanticHighlighting_strings_enable, false); + // -------------------------------------------------------- + + // Now let's modify the xdg_config_file_id, which should invalidate everything else vfs.set_file_id_contents( xdg_config_file_id, Some( @@ -456,15 +490,14 @@ mod tests { ); let changes = ConfigChanges { - ra_toml_changes: vfs.take_changes(), - parent_changes: vec![], + ra_toml_changes: dbg!(vfs.take_changes()), + parent_changes: Default::default(), client_change: None, }; dbg!(config_tree.apply_changes(changes, &vfs)); - let local2 = config_tree.read_config(crate_a).unwrap(); - // should have recomputed - assert!(!Arc::ptr_eq(&local, &local2)); + let prev = local; + let local = config_tree.read_config(crate_a).unwrap(); assert_eq!( local.inlayHints_discriminantHints_enable, From 851b9701c000233c6f9fd47e47630a24da8bc962 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 23:26:29 +1100 Subject: [PATCH 35/52] Avoid recomputing the client-config-mixed configs --- crates/rust-analyzer/src/config/tree.rs | 51 ++++++++++++++++--------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index ed5fef388ce8..0f7a12d6569e 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -125,6 +125,7 @@ struct ConfigTree { xdg_config_node_id: NodeId, ra_file_id_map: FxHashMap, computed: SlotMap>>, + with_client_config: slotmap::SecondaryMap>, } fn parse_toml( @@ -179,19 +180,13 @@ impl ConfigTree { ra_file_id_map, tree, computed, + with_client_config: Default::default(), } } fn read_only(&self, file_id: FileId) -> Result>, ConfigTreeError> { let node_id = *self.ra_file_id_map.get(&file_id).ok_or(ConfigTreeError::NonExistent)?; - let stored = self.read_only_inner(node_id)?; - Ok(stored.map(|stored| { - if let Some(client_config) = self.client_config.as_deref() { - stored.clone_with_overrides(client_config.local.clone()).into() - } else { - stored - } - })) + self.read_only_inner(node_id) } fn read_only_inner( @@ -204,7 +199,7 @@ impl ConfigTree { return Err(ConfigTreeError::Removed); } let node = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.get(); - let stored = self.computed[node.computed_idx].clone(); + let stored = self.with_client_config.get(node.computed_idx).cloned(); tracing::trace!( "read_only_inner on {:?} got {:?}", node.src, @@ -215,14 +210,19 @@ impl ConfigTree { fn compute(&mut self, file_id: FileId) -> Result, ConfigTreeError> { let node_id = *self.ra_file_id_map.get(&file_id).ok_or(ConfigTreeError::NonExistent)?; - let computed = self.compute_inner(node_id)?; - Ok(if let Some(client_config) = self.client_config.as_deref() { - computed.clone_with_overrides(client_config.local.clone()).into() + let (computed, idx) = self.compute_recursive(node_id)?; + let out = if let Some(client_config) = self.client_config.as_deref() { + Arc::new(computed.clone_with_overrides(client_config.local.clone())) } else { computed - }) + }; + self.with_client_config.insert(idx, out.clone()); + Ok(out) } - fn compute_inner(&mut self, node_id: NodeId) -> Result, ConfigTreeError> { + fn compute_recursive( + &mut self, + node_id: NodeId, + ) -> Result<(Arc, ComputedIdx), ConfigTreeError> { if node_id.is_removed(&self.tree) { return Err(ConfigTreeError::Removed); } @@ -231,14 +231,14 @@ impl ConfigTree { let idx = node.computed_idx; let slot = &mut self.computed[idx]; if let Some(slot) = slot { - Ok(slot.clone()) + Ok((slot.clone(), idx)) } else { let self_computed = if let Some(parent) = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.parent() { tracing::trace!("looking at parent of {node_id:?} -> {parent:?}"); let self_input = node.input.clone(); - let parent_computed = self.compute_inner(parent)?; + let (parent_computed, _) = self.compute_recursive(parent)?; if let Some(input) = self_input.as_deref() { Arc::new(parent_computed.clone_with_overrides(input.local.clone())) } else { @@ -258,7 +258,7 @@ impl ConfigTree { // Get a new &mut slot because self.compute(parent) also gets mut access let slot = &mut self.computed[idx]; slot.replace(self_computed.clone()); - Ok(self_computed) + Ok((self_computed, idx)) } } @@ -322,6 +322,9 @@ impl ConfigTree { }; tracing::trace!("invalidating {x:?} / {:?}", desc.src); slot.take(); + + // Also invalidate the secondary data + self.with_client_config.remove(desc.computed_idx); }); } @@ -346,7 +349,16 @@ impl ConfigTree { let ConfigChanges { client_change, ra_toml_changes, mut parent_changes } = changes; if let Some(change) = client_change { - self.client_config = change; + match (self.client_config.as_ref(), change.as_ref()) { + (None, None) => {} + (Some(a), Some(b)) if Arc::ptr_eq(a, b) => {} + _ => { + // invalidate the output table only, don't immediately need to recompute + // everything from scratch + self.with_client_config.clear(); + self.client_config = change; + } + } } for change in ra_toml_changes { @@ -498,6 +510,9 @@ mod tests { let prev = local; let local = config_tree.read_config(crate_a).unwrap(); + assert!(!Arc::ptr_eq(&prev, &local)); + let local2 = config_tree.read_config(crate_a).unwrap(); + assert!(Arc::ptr_eq(&local, &local2)); assert_eq!( local.inlayHints_discriminantHints_enable, From 77e61f7f6bcbd7bdb52276d5380822f6af986872 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 23:33:27 +1100 Subject: [PATCH 36/52] Better test for config tree --- crates/rust-analyzer/src/config/tree.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 0f7a12d6569e..edff0eac9cc0 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -292,6 +292,9 @@ impl ConfigTree { let node = self.tree.get_mut(node_id).ok_or(ConfigTreeError::NonExistent)?; node.get_mut().input = input; + // We won't do any funny business comparing the previous input to the new one, because + // that would require implementing PartialEq on the dozens and dozens of types that make + // up ConfigInput. self.invalidate_subtree(node_id); // tracing::trace!("invalidated subtree:\n{:#?}", node_id.debug_pretty_print(&self.tree)); Ok(node_id) @@ -495,6 +498,12 @@ mod tests { # default is "never" [inlayHints.discriminantHints] enable = "always" + [completion.autoself] + enable = true + [completion.autoimport] + enable = true + [semanticHighlighting.strings] + enable = true "# .to_string() .into_bytes(), @@ -510,13 +519,20 @@ mod tests { let prev = local; let local = config_tree.read_config(crate_a).unwrap(); + // Should have been recomputed assert!(!Arc::ptr_eq(&prev, &local)); - let local2 = config_tree.read_config(crate_a).unwrap(); - assert!(Arc::ptr_eq(&local, &local2)); + // But without changes in between, should give the same Arc back + assert!(Arc::ptr_eq(&local, &config_tree.read_config(crate_a).unwrap())); + // The newly added xdg_config_file_id should affect the output if nothing else touches + // this key assert_eq!( local.inlayHints_discriminantHints_enable, crate::config::DiscriminantHintsDef::Always ); + // But it should not win + assert_eq!(local.completion_autoself_enable, false); + assert_eq!(local.completion_autoimport_enable, false); + assert_eq!(local.semanticHighlighting_strings_enable, false); } } From 9d8cff2642ac4ba3e78350661de74608f5ed91f3 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 23:34:58 +1100 Subject: [PATCH 37/52] rename read_config to local_config --- crates/rust-analyzer/src/config/tree.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index edff0eac9cc0..3d6e2c99a29d 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -90,7 +90,7 @@ impl ConcurrentConfigTree { self.rwlock.write().apply_changes(changes, vfs, &mut errors); errors } - pub fn read_config(&self, file_id: FileId) -> Result, ConfigTreeError> { + pub fn local_config(&self, file_id: FileId) -> Result, ConfigTreeError> { let reader = self.rwlock.upgradable_read(); if let Some(computed) = reader.read_only(file_id)? { return Ok(computed); @@ -480,7 +480,7 @@ mod tests { dbg!(config_tree.apply_changes(changes, &vfs)); - let local = config_tree.read_config(crate_a).unwrap(); + let local = config_tree.local_config(crate_a).unwrap(); // from root assert_eq!(local.completion_autoself_enable, false); // from crate_a @@ -518,11 +518,11 @@ mod tests { dbg!(config_tree.apply_changes(changes, &vfs)); let prev = local; - let local = config_tree.read_config(crate_a).unwrap(); + let local = config_tree.local_config(crate_a).unwrap(); // Should have been recomputed assert!(!Arc::ptr_eq(&prev, &local)); // But without changes in between, should give the same Arc back - assert!(Arc::ptr_eq(&local, &config_tree.read_config(crate_a).unwrap())); + assert!(Arc::ptr_eq(&local, &config_tree.local_config(crate_a).unwrap())); // The newly added xdg_config_file_id should affect the output if nothing else touches // this key From ce341dcef4a24caf35c8c668bc33ce1edebfc27a Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Tue, 23 Jan 2024 23:44:58 +1100 Subject: [PATCH 38/52] delete ConfigParentChange --- crates/rust-analyzer/src/config/tree.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 3d6e2c99a29d..6b63f4a7b1e9 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -44,13 +44,6 @@ pub struct ConfigChanges { parent_changes: FxHashMap, } -#[derive(Debug)] -pub struct ConfigParentChange { - /// The config node in question - pub file_id: FileId, - pub parent: ConfigParent, -} - #[derive(Debug)] pub enum ConfigParent { /// The node is now a root in its own right, but still inherits from the config in XDG_CONFIG_HOME @@ -75,11 +68,11 @@ pub enum ConfigParent { /// let root = vfs.set_file_contents("/project_root/rust-analyzer.toml", Some("...")); /// let crate_a = vfs.set_file_contents("/project_root/crate_a/rust-analyzer.toml", None); /// let crate_b = vfs.set_file_contents("/project_root/crate_a/crate_b/rust-analyzer.toml", Some("...")); - /// let config_parent_changes = [ - /// ConfigParentChange { node: root, parent: ConfigParent::UserDefault }, - /// ConfigParentChange { node: crate_a, parent: ConfigParent::Parent(root) }, - /// ConfigParentChange { node: crate_b, parent: ConfigParent::Parent(crate_a) } - /// ]; + /// let parent_changes = FxHashMap::from_iter([ + /// (root, ConfigParent::UserDefault), + /// (crate_a, ConfigParent::Parent(root)), + /// (crate_b, ConfigParent::Parent(crate_a)), + /// ]); /// ``` Parent(FileId), } From 09a3888ea6306c407c2c9231608bd648723a9a25 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Wed, 24 Jan 2024 22:41:40 +1100 Subject: [PATCH 39/52] use salsa for the config tree --- Cargo.lock | 18 +- Cargo.toml | 1 + crates/base-db/Cargo.toml | 2 +- crates/rust-analyzer/Cargo.toml | 3 +- crates/rust-analyzer/src/config/tree.rs | 435 ++++++++---------------- 5 files changed, 147 insertions(+), 312 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6265568b25b7..aaedd6281a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,12 +792,6 @@ dependencies = [ "serde", ] -[[package]] -name = "indextree" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c40411d0e5c63ef1323c3d09ce5ec6d84d71531e18daed0743fccea279d7deb6" - [[package]] name = "inotify" version = "0.9.6" @@ -1592,7 +1586,6 @@ dependencies = [ "ide-db", "ide-ssr", "indexmap 2.0.0", - "indextree", "itertools", "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "load-cargo", @@ -1613,10 +1606,10 @@ dependencies = [ "rayon", "rustc-dependencies", "rustc-hash", + "salsa", "scip", "serde", "serde_json", - "slotmap", "sourcegen", "stdx", "syntax", @@ -1793,15 +1786,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] - [[package]] name = "smallvec" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index c382a5a37d23..e24ab0867236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ nohash-hasher = "0.2.0" text-size = "1.1.0" serde = { version = "1.0.156", features = ["derive"] } serde_json = "1.0.96" +salsa = "0.17.0-pre.2" triomphe = { version = "0.1.8", default-features = false, features = ["std"] } # can't upgrade due to dashmap depending on 0.12.3 currently hashbrown = { version = "0.12.3", features = [ diff --git a/crates/base-db/Cargo.toml b/crates/base-db/Cargo.toml index 8435dba86d22..d6fd0047a22c 100644 --- a/crates/base-db/Cargo.toml +++ b/crates/base-db/Cargo.toml @@ -12,7 +12,7 @@ rust-version.workspace = true doctest = false [dependencies] -salsa = "0.17.0-pre.2" +salsa.workspace = true rustc-hash = "1.1.0" triomphe.workspace = true diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 4567f382f89b..009c03b645f7 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -75,8 +75,7 @@ toolchain.workspace = true vfs-notify.workspace = true vfs.workspace = true la-arena.workspace = true -indextree = "4.6.0" -slotmap = "1.0.7" +salsa.workspace = true [target.'cfg(windows)'.dependencies] winapi = "0.3.9" diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 6b63f4a7b1e9..26897f174641 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -1,37 +1,16 @@ -use indextree::NodeId; -use parking_lot::{RwLock, RwLockUpgradableReadGuard}; -use rustc_hash::FxHashMap; -use slotmap::SlotMap; -use std::{fmt, sync::Arc}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::sync::Arc; use vfs::{FileId, Vfs}; use super::{ConfigInput, LocalConfigData, RootLocalConfigData}; -pub struct ConcurrentConfigTree { - // One rwlock on the whole thing is probably fine. - // If you have 40,000 crates and you need to edit your config 200x/second, let us know. - rwlock: RwLock, -} - -impl ConcurrentConfigTree { - fn new(config_tree: ConfigTree) -> Self { - Self { rwlock: RwLock::new(config_tree) } - } -} - -impl fmt::Debug for ConcurrentConfigTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.rwlock.read().fmt(f) - } -} - #[derive(Debug)] pub enum ConfigTreeError { Removed, NonExistent, - Utf8(vfs::VfsPath, std::str::Utf8Error), - TomlParse(vfs::VfsPath, toml::de::Error), - TomlDeserialize { path: vfs::VfsPath, field: String, error: toml::de::Error }, + Utf8(FileId, std::str::Utf8Error), + TomlParse(FileId, toml::de::Error), + TomlDeserialize { file_id: FileId, field: String, error: toml::de::Error }, } /// Some rust-analyzer.toml files have changed, and/or the LSP client sent a new configuration. @@ -77,282 +56,116 @@ pub enum ConfigParent { Parent(FileId), } -impl ConcurrentConfigTree { - pub fn apply_changes(&self, changes: ConfigChanges, vfs: &Vfs) -> Vec { - let mut errors = Vec::new(); - self.rwlock.write().apply_changes(changes, vfs, &mut errors); - errors - } - pub fn local_config(&self, file_id: FileId) -> Result, ConfigTreeError> { - let reader = self.rwlock.upgradable_read(); - if let Some(computed) = reader.read_only(file_id)? { - return Ok(computed); - } else { - let mut writer = RwLockUpgradableReadGuard::upgrade(reader); - return writer.compute(file_id); - } +/// Easier and probably more performant than making ConfigInput implement Eq +#[derive(Debug)] +struct PointerCmp(Arc); +impl PointerCmp { + fn new(t: T) -> Self { + Self(Arc::new(t)) } } - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -enum ConfigSource { - XdgConfig(FileId), - RaToml(FileId), +impl Clone for PointerCmp { + fn clone(&self) -> Self { + Self(self.0.clone()) + } } - -slotmap::new_key_type! { - struct ComputedIdx; +impl PartialEq for PointerCmp { + fn eq(&self, other: &Self) -> bool { + (Arc::as_ptr(&self.0) as *const ()).eq(&Arc::as_ptr(&other.0).cast()) + } } - -#[derive(Debug)] -struct ConfigNode { - src: ConfigSource, - input: Option>, - computed_idx: ComputedIdx, +impl Eq for PointerCmp {} +impl std::ops::Deref for PointerCmp { + type Target = T; + fn deref(&self) -> &T { + self.0.deref() + } } -struct ConfigTree { - tree: indextree::Arena, - client_config: Option>, - xdg_config_file_id: FileId, - xdg_config_node_id: NodeId, - ra_file_id_map: FxHashMap, - computed: SlotMap>>, - with_client_config: slotmap::SecondaryMap>, -} +#[salsa::query_group(ConfigTreeStorage)] +trait ConfigTreeQueries { + #[salsa::input] + fn client_config(&self) -> Option>; -fn parse_toml( - file_id: FileId, - vfs: &Vfs, - scratch: &mut Vec<(String, toml::de::Error)>, - errors: &mut Vec, -) -> Option> { - let content = vfs.file_contents(file_id); - let path = vfs.file_path(file_id); - if content.is_empty() { - return None; - } - let content_str = match std::str::from_utf8(content) { - Err(e) => { - tracing::error!("non-UTF8 TOML content for {path}: {e}"); - errors.push(ConfigTreeError::Utf8(path, e)); - return None; - } - Ok(str) => str, - }; - let table = match toml::from_str(content_str) { - Ok(table) => table, - Err(e) => { - errors.push(ConfigTreeError::TomlParse(path, e)); - return None; - } - }; - let input = Arc::new(ConfigInput::from_toml(table, scratch)); - scratch.drain(..).for_each(|(field, error)| { - errors.push(ConfigTreeError::TomlDeserialize { path: path.clone(), field, error }); - }); - Some(input) -} + #[salsa::input] + fn config_parent(&self, file_id: FileId) -> Option; -impl ConfigTree { - fn new(xdg_config_file_id: FileId) -> Self { - let mut tree = indextree::Arena::new(); - let mut computed = SlotMap::default(); - let mut ra_file_id_map = FxHashMap::default(); - let xdg_config_node_id = tree.new_node(ConfigNode { - src: ConfigSource::XdgConfig(xdg_config_file_id), - input: None, - computed_idx: computed.insert(Option::>::None), - }); - ra_file_id_map.insert(xdg_config_file_id, xdg_config_node_id); - - Self { - client_config: None, - xdg_config_file_id, - xdg_config_node_id, - ra_file_id_map, - tree, - computed, - with_client_config: Default::default(), - } - } + #[salsa::input] + fn config_input(&self, file_id: FileId) -> Option>; - fn read_only(&self, file_id: FileId) -> Result>, ConfigTreeError> { - let node_id = *self.ra_file_id_map.get(&file_id).ok_or(ConfigTreeError::NonExistent)?; - self.read_only_inner(node_id) - } + fn compute_recursive(&self, file_id: FileId) -> PointerCmp; - fn read_only_inner( - &self, - node_id: NodeId, - ) -> Result>, ConfigTreeError> { - // indextree does not check this during get(), probably for perf reasons? - // get() is apparently only a bounds check - if node_id.is_removed(&self.tree) { - return Err(ConfigTreeError::Removed); - } - let node = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.get(); - let stored = self.with_client_config.get(node.computed_idx).cloned(); - tracing::trace!( - "read_only_inner on {:?} got {:?}", - node.src, - stored.as_ref().map(|_| "some stored value") - ); - Ok(stored) - } + fn local_config(&self, file_id: FileId) -> PointerCmp; +} - fn compute(&mut self, file_id: FileId) -> Result, ConfigTreeError> { - let node_id = *self.ra_file_id_map.get(&file_id).ok_or(ConfigTreeError::NonExistent)?; - let (computed, idx) = self.compute_recursive(node_id)?; - let out = if let Some(client_config) = self.client_config.as_deref() { - Arc::new(computed.clone_with_overrides(client_config.local.clone())) - } else { - computed - }; - self.with_client_config.insert(idx, out.clone()); - Ok(out) - } - fn compute_recursive( - &mut self, - node_id: NodeId, - ) -> Result<(Arc, ComputedIdx), ConfigTreeError> { - if node_id.is_removed(&self.tree) { - return Err(ConfigTreeError::Removed); - } - let node = self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.get(); - tracing::trace!("compute_inner on {:?}", node.src); - let idx = node.computed_idx; - let slot = &mut self.computed[idx]; - if let Some(slot) = slot { - Ok((slot.clone(), idx)) - } else { - let self_computed = if let Some(parent) = - self.tree.get(node_id).ok_or(ConfigTreeError::NonExistent)?.parent() - { - tracing::trace!("looking at parent of {node_id:?} -> {parent:?}"); - let self_input = node.input.clone(); - let (parent_computed, _) = self.compute_recursive(parent)?; - if let Some(input) = self_input.as_deref() { - Arc::new(parent_computed.clone_with_overrides(input.local.clone())) - } else { - parent_computed - } +fn compute_recursive(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp { + let self_input = db.config_input(file_id); + tracing::trace!(?self_input, ?file_id); + match db.config_parent(file_id) { + Some(parent) if parent != file_id => { + let parent_computed = db.compute_recursive(parent); + if let Some(input) = self_input.as_deref() { + PointerCmp::new(parent_computed.clone_with_overrides(input.local.clone())) } else { - tracing::trace!("{node_id:?} is a root node"); - // We have hit a root node - let self_input = node.input.clone(); - if let Some(input) = self_input.as_deref() { - let root_local = RootLocalConfigData::from_root_input(input.local.clone()); - Arc::new(root_local.0) - } else { - Arc::new(LocalConfigData::default()) - } - }; - // Get a new &mut slot because self.compute(parent) also gets mut access - let slot = &mut self.computed[idx]; - slot.replace(self_computed.clone()); - Ok((self_computed, idx)) - } - } - - fn insert_toml(&mut self, file_id: FileId, input: Option>) -> NodeId { - let computed = self.computed.insert(None); - let node_id = self.tree.new_node(ConfigNode { - src: ConfigSource::RaToml(file_id), - input, - computed_idx: computed, - }); - if let Some(_removed) = self.ra_file_id_map.insert(file_id, node_id) { - panic!("ERROR: node should not have existed for {file_id:?} but it did"); + parent_computed + } } - // By default, everything is under the xdg_config_node_id - self.xdg_config_node_id.append(node_id, &mut self.tree); - node_id - } - - fn update_toml( - &mut self, - file_id: FileId, - input: Option>, - ) -> Result { - let Some(node_id) = self.ra_file_id_map.get(&file_id).cloned() else { - let node_id = self.insert_toml(file_id, input); - return Ok(node_id); - }; - if node_id.is_removed(&self.tree) { - return Err(ConfigTreeError::Removed); + _ => { + // this is a root node, or we just broke a cycle + if let Some(input) = self_input.as_deref() { + let root_local = RootLocalConfigData::from_root_input(input.local.clone()); + PointerCmp::new(root_local.0) + } else { + PointerCmp::new(LocalConfigData::default()) + } } - let node = self.tree.get_mut(node_id).ok_or(ConfigTreeError::NonExistent)?; - node.get_mut().input = input; - - // We won't do any funny business comparing the previous input to the new one, because - // that would require implementing PartialEq on the dozens and dozens of types that make - // up ConfigInput. - self.invalidate_subtree(node_id); - // tracing::trace!("invalidated subtree:\n{:#?}", node_id.debug_pretty_print(&self.tree)); - Ok(node_id) } +} - fn ensure_node(&mut self, file_id: FileId) -> NodeId { - let Some(&node_id) = self.ra_file_id_map.get(&file_id) else { - return self.insert_toml(file_id, None); - }; - node_id +fn local_config(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp { + let computed = db.compute_recursive(file_id); + if let Some(client) = db.client_config() { + PointerCmp::new(computed.clone_with_overrides(client.local.clone())) + } else { + computed } +} - fn invalidate_subtree(&mut self, node_id: NodeId) { - // - // This is why we need the computed values outside the indextree: we iterate immutably - // over the tree while holding a &mut self.computed. - node_id.descendants(&self.tree).for_each(|x| { - let Some(desc) = self.tree.get(x) else { - return; - }; - let desc = desc.get(); - let Some(slot) = self.computed.get_mut(desc.computed_idx) else { - tracing::error!( - "computed_idx missing from computed local config slotmap: {:?}", - desc.computed_idx - ); - return; - }; - tracing::trace!("invalidating {x:?} / {:?}", desc.src); - slot.take(); +#[salsa::database(ConfigTreeStorage)] +pub struct ConfigDb { + storage: salsa::Storage, + known_file_ids: FxHashSet, + xdg_config_file_id: FileId, +} - // Also invalidate the secondary data - self.with_client_config.remove(desc.computed_idx); - }); - } +impl salsa::Database for ConfigDb {} - fn remove_toml(&mut self, file_id: FileId) -> Option<()> { - let node_id = *self.ra_file_id_map.get(&file_id)?; - if node_id.is_removed(&self.tree) { - return None; - } - let node = self.tree.get_mut(node_id)?.get_mut(); - node.input = None; - self.invalidate_subtree(node_id); - Some(()) +impl ConfigDb { + pub fn new(xdg_config_file_id: FileId) -> Self { + let mut this = Self { + storage: Default::default(), + known_file_ids: FxHashSet::default(), + xdg_config_file_id, + }; + this.set_client_config(None); + this.ensure_node(xdg_config_file_id); + this.set_config_parent(xdg_config_file_id, None); + this } - fn apply_changes( - &mut self, - changes: ConfigChanges, - vfs: &Vfs, - errors: &mut Vec, - ) { + pub fn apply_changes(&mut self, changes: ConfigChanges, vfs: &Vfs) -> Vec { let mut scratch_errors = Vec::new(); + let mut errors = Vec::new(); let ConfigChanges { client_change, ra_toml_changes, mut parent_changes } = changes; if let Some(change) = client_change { - match (self.client_config.as_ref(), change.as_ref()) { + let current = self.client_config(); + let change = change.map(PointerCmp); + match (current.as_ref(), change.as_ref()) { (None, None) => {} - (Some(a), Some(b)) if Arc::ptr_eq(a, b) => {} + (Some(a), Some(b)) if a == b => {} _ => { - // invalidate the output table only, don't immediately need to recompute - // everything from scratch - self.with_client_config.clear(); - self.client_config = change; + self.set_client_config(change); } } } @@ -364,37 +177,75 @@ impl ConfigTree { if change.change_kind == vfs::ChangeKind::Create { parent_changes.entry(change.file_id).or_insert(ConfigParent::UserDefault); } - let input = parse_toml(change.file_id, vfs, &mut scratch_errors, errors); + let input = parse_toml(change.file_id, vfs, &mut scratch_errors, &mut errors) + .map(PointerCmp); tracing::trace!("updating toml for {:?} to {:?}", change.file_id, input); - if let Err(e) = self.update_toml(change.file_id, input) { - errors.push(e); - } + self.ensure_node(change.file_id); + self.set_config_input(change.file_id, input); } vfs::ChangeKind::Delete => { - self.remove_toml(change.file_id); + self.ensure_node(change.file_id); + self.set_config_input(change.file_id, None); } } } for (file_id, parent) in parent_changes { - let node_id = self.ensure_node(file_id); + self.ensure_node(file_id); let parent_node_id = match parent { - ConfigParent::Parent(parent_file_id) => self.ensure_node(parent_file_id), + ConfigParent::Parent(parent_file_id) => { + self.ensure_node(parent_file_id); + parent_file_id + } ConfigParent::UserDefault if file_id == self.xdg_config_file_id => continue, - ConfigParent::UserDefault => self.xdg_config_node_id, + ConfigParent::UserDefault => self.xdg_config_file_id, }; // order of children within the parent node does not matter - tracing::trace!("appending child {node_id:?} to {parent_node_id:?}"); - parent_node_id.append(node_id, &mut self.tree); + tracing::trace!("appending child {file_id:?} to {parent_node_id:?}"); + self.set_config_parent(file_id, Some(parent_node_id)) + } + + errors + } + + fn ensure_node(&mut self, file_id: FileId) { + if self.known_file_ids.insert(file_id) { + self.set_config_input(file_id, None); } } } -impl fmt::Debug for ConfigTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.tree.fmt(f) +fn parse_toml( + file_id: FileId, + vfs: &Vfs, + scratch: &mut Vec<(String, toml::de::Error)>, + errors: &mut Vec, +) -> Option> { + let content = vfs.file_contents(file_id); + let content_str = match std::str::from_utf8(content) { + Err(e) => { + tracing::error!("non-UTF8 TOML content for {file_id:?}: {e}"); + errors.push(ConfigTreeError::Utf8(file_id, e)); + return None; + } + Ok(str) => str, + }; + if content_str.is_empty() { + return None; } + let table = match toml::from_str(content_str) { + Ok(table) => table, + Err(e) => { + errors.push(ConfigTreeError::TomlParse(file_id, e)); + return None; + } + }; + let input = Arc::new(ConfigInput::from_toml(table, scratch)); + scratch.drain(..).for_each(|(field, error)| { + errors.push(ConfigTreeError::TomlDeserialize { file_id, field, error }); + }); + Some(input) } #[cfg(test)] @@ -432,7 +283,7 @@ mod tests { let mut vfs = Vfs::default(); let xdg_config_file_id = alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); - let config_tree = ConcurrentConfigTree::new(ConfigTree::new(xdg_config_file_id)); + let mut config_tree = ConfigDb::new(xdg_config_file_id); let root = alloc_config( &mut vfs, @@ -473,7 +324,7 @@ mod tests { dbg!(config_tree.apply_changes(changes, &vfs)); - let local = config_tree.local_config(crate_a).unwrap(); + let local = config_tree.local_config(crate_a); // from root assert_eq!(local.completion_autoself_enable, false); // from crate_a @@ -511,11 +362,11 @@ mod tests { dbg!(config_tree.apply_changes(changes, &vfs)); let prev = local; - let local = config_tree.local_config(crate_a).unwrap(); + let local = config_tree.local_config(crate_a); // Should have been recomputed - assert!(!Arc::ptr_eq(&prev, &local)); + assert_ne!(prev, local); // But without changes in between, should give the same Arc back - assert!(Arc::ptr_eq(&local, &config_tree.local_config(crate_a).unwrap())); + assert_eq!(local, config_tree.local_config(crate_a)); // The newly added xdg_config_file_id should affect the output if nothing else touches // this key From 359bf1da4e3b1ae415c617ba5314479647dec878 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Wed, 24 Jan 2024 23:11:01 +1100 Subject: [PATCH 40/52] Delte unused vfs method, document set_file_id_contents --- crates/vfs/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 65882f72f5a7..0c8474f60753 100644 --- a/crates/vfs/src/lib.rs +++ b/crates/vfs/src/lib.rs @@ -133,11 +133,6 @@ impl Vfs { self.get(file_id).as_deref().unwrap() } - /// File content, but returns None if the file was deleted. - pub fn file_contents_opt(&self, file_id: FileId) -> Option<&[u8]> { - self.get(file_id).as_deref() - } - /// Returns the overall memory usage for the stored files. pub fn memory_usage(&self) -> usize { self.data.iter().flatten().map(|d| d.capacity()).sum() @@ -167,6 +162,12 @@ impl Vfs { self.set_file_id_contents(file_id, contents) } + /// Update the given `file_id` with the given `contents`. `None` means the file was deleted. + /// + /// Returns `true` if the file was modified, and saves the [change](ChangedFile). + /// + /// If the path does not currently exists in the `Vfs`, allocates a new + /// [`FileId`] for it. pub fn set_file_id_contents(&mut self, file_id: FileId, mut contents: Option>) -> bool { let change_kind = match (self.get(file_id), &contents) { (None, None) => return false, From f1cff1e5a197e482363779cff76219e22f84560e Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 00:29:14 +1100 Subject: [PATCH 41/52] Add a test that generates a tree from running path.ancestors() on the roots Also renames a bunch of functions and hides PointerCmp from public API --- crates/rust-analyzer/src/config/tree.rs | 144 ++++++++++++++++++++---- 1 file changed, 122 insertions(+), 22 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 26897f174641..b1668f3d26ea 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -88,22 +88,23 @@ trait ConfigTreeQueries { fn client_config(&self) -> Option>; #[salsa::input] - fn config_parent(&self, file_id: FileId) -> Option; + fn parent(&self, file_id: FileId) -> Option; #[salsa::input] fn config_input(&self, file_id: FileId) -> Option>; - fn compute_recursive(&self, file_id: FileId) -> PointerCmp; + fn recursive_local(&self, file_id: FileId) -> PointerCmp; - fn local_config(&self, file_id: FileId) -> PointerCmp; + /// The output + fn computed_local_config(&self, file_id: FileId) -> PointerCmp; } -fn compute_recursive(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp { +fn recursive_local(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp { let self_input = db.config_input(file_id); tracing::trace!(?self_input, ?file_id); - match db.config_parent(file_id) { + match db.parent(file_id) { Some(parent) if parent != file_id => { - let parent_computed = db.compute_recursive(parent); + let parent_computed = db.recursive_local(parent); if let Some(input) = self_input.as_deref() { PointerCmp::new(parent_computed.clone_with_overrides(input.local.clone())) } else { @@ -122,8 +123,11 @@ fn compute_recursive(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp< } } -fn local_config(db: &dyn ConfigTreeQueries, file_id: FileId) -> PointerCmp { - let computed = db.compute_recursive(file_id); +fn computed_local_config( + db: &dyn ConfigTreeQueries, + file_id: FileId, +) -> PointerCmp { + let computed = db.recursive_local(file_id); if let Some(client) = db.client_config() { PointerCmp::new(computed.clone_with_overrides(client.local.clone())) } else { @@ -149,14 +153,35 @@ impl ConfigDb { }; this.set_client_config(None); this.ensure_node(xdg_config_file_id); - this.set_config_parent(xdg_config_file_id, None); this } + /// Gets the value of LocalConfigData for a given `rust-analyzer.toml` FileId. + /// + /// The rust-analyzer.toml does not need to exist on disk. All values are the expression of + /// overriding the parent `rust-analyzer.toml`, set by adding an entry in + /// `ConfigChanges.parent_changes`. + /// + /// If the db is not aware of the given `rust-analyzer.toml` FileId, then the config is read + /// from the user's system-wide default config. + /// + /// Note that the client config overrides all configs. + pub fn local_config(&self, ra_toml_file_id: FileId) -> Arc { + if self.known_file_ids.contains(&ra_toml_file_id) { + self.computed_local_config(ra_toml_file_id).0 + } else { + tracing::warn!(?ra_toml_file_id, "called local_config with unknown file id"); + self.computed_local_config(self.xdg_config_file_id).0 + } + } + + /// Applies a bunch of [`ConfigChanges`]. The FileIds referred to in `ConfigChanges` do not + /// need to exist. You can generate the `parent_changes` hashmap by iterating ancestors of all + /// of the [`ide::SourceRoot`]s, slapping `.map(|path| path.join("rust-analyzer.toml"))`. pub fn apply_changes(&mut self, changes: ConfigChanges, vfs: &Vfs) -> Vec { let mut scratch_errors = Vec::new(); let mut errors = Vec::new(); - let ConfigChanges { client_change, ra_toml_changes, mut parent_changes } = changes; + let ConfigChanges { client_change, ra_toml_changes, parent_changes } = changes; if let Some(change) = client_change { let current = self.client_config(); @@ -174,9 +199,6 @@ impl ConfigDb { // turn and face the strain match change.change_kind { vfs::ChangeKind::Create | vfs::ChangeKind::Modify => { - if change.change_kind == vfs::ChangeKind::Create { - parent_changes.entry(change.file_id).or_insert(ConfigParent::UserDefault); - } let input = parse_toml(change.file_id, vfs, &mut scratch_errors, &mut errors) .map(PointerCmp); tracing::trace!("updating toml for {:?} to {:?}", change.file_id, input); @@ -203,15 +225,25 @@ impl ConfigDb { }; // order of children within the parent node does not matter tracing::trace!("appending child {file_id:?} to {parent_node_id:?}"); - self.set_config_parent(file_id, Some(parent_node_id)) + self.set_parent(file_id, Some(parent_node_id)) } errors } + /// Inserts default values into the salsa inputs for the given file_id + /// if it's never been seen before fn ensure_node(&mut self, file_id: FileId) { if self.known_file_ids.insert(file_id) { self.set_config_input(file_id, None); + self.set_parent( + file_id, + if file_id == self.xdg_config_file_id { + None + } else { + Some(self.xdg_config_file_id) + }, + ); } } } @@ -250,17 +282,15 @@ fn parse_toml( #[cfg(test)] mod tests { - use std::path::PathBuf; + use std::path::{Path, PathBuf}; + use itertools::Itertools; use vfs::{AbsPathBuf, VfsPath}; fn alloc_file_id(vfs: &mut Vfs, s: &str) -> FileId { - tracing_subscriber::fmt().init(); let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap(); let vfs_path = VfsPath::from(abs_path); - // FIXME: the vfs should expose this functionality more simply. - // We shouldn't have to clone the vfs path just to get a FileId. let file_id = vfs.alloc_file_id(vfs_path); vfs.set_file_id_contents(file_id, None); file_id @@ -270,8 +300,6 @@ mod tests { let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap(); let vfs_path = VfsPath::from(abs_path); - // FIXME: the vfs should expose this functionality more simply. - // We shouldn't have to clone the vfs path just to get a FileId. let file_id = vfs.alloc_file_id(vfs_path); vfs.set_file_id_contents(file_id, Some(config.to_string().into_bytes())); file_id @@ -280,6 +308,7 @@ mod tests { use super::*; #[test] fn basic() { + tracing_subscriber::fmt().try_init().ok(); let mut vfs = Vfs::default(); let xdg_config_file_id = alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); @@ -364,9 +393,9 @@ mod tests { let prev = local; let local = config_tree.local_config(crate_a); // Should have been recomputed - assert_ne!(prev, local); + assert!(!Arc::ptr_eq(&prev, &local)); // But without changes in between, should give the same Arc back - assert_eq!(local, config_tree.local_config(crate_a)); + assert!(Arc::ptr_eq(&local, &config_tree.local_config(crate_a))); // The newly added xdg_config_file_id should affect the output if nothing else touches // this key @@ -379,4 +408,75 @@ mod tests { assert_eq!(local.completion_autoimport_enable, false); assert_eq!(local.semanticHighlighting_strings_enable, false); } + + #[test] + fn generated_parent_changes() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + + let xdg = + alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); + let mut config_tree = ConfigDb::new(xdg); + + let project_root = Path::new("/root"); + let sourceroots = + [PathBuf::new().join("/root/crate_a"), PathBuf::new().join("/root/crate_a/crate_b")]; + let sourceroot_tomls = sourceroots + .iter() + .map(|dir| dir.join("rust-analyzer.toml")) + .map(|path| AbsPathBuf::try_from(path).unwrap()) + .map(|path| vfs.alloc_file_id(path.into())) + .collect_vec(); + let &[crate_a, crate_b] = &sourceroot_tomls[..] else { + panic!(); + }; + + let parent_changes = sourceroots + .iter() + .flat_map(|path| { + path.ancestors() + .take_while(|x| x.starts_with(project_root)) + .map(|dir| dir.join("rust-analyzer.toml")) + .map(|path| AbsPathBuf::try_from(path).unwrap()) + .map(|path| vfs.alloc_file_id(path.into())) + .collect_vec() + .into_iter() + .tuple_windows() + .map(|(a, b)| (a, ConfigParent::Parent(b))) + }) + .collect::>(); + + for (&a, parent) in &parent_changes { + eprintln!( + "{a:?} ({:?}): parent = {parent:?} ({:?})", + vfs.file_path(a), + match parent { + ConfigParent::Parent(p) => vfs.file_path(*p).to_string(), + ConfigParent::UserDefault => "xdg".to_string(), + } + ); + } + + vfs.set_file_id_contents( + xdg, + Some(b"[inlayHints.discriminantHints]\nenable = \"always\"".to_vec()), + ); + vfs.set_file_id_contents(crate_a, Some(b"[completion.autoself]\nenable = false".to_vec())); + // note that crate_b's rust-analyzer.toml doesn't exist + + let changes = ConfigChanges { + ra_toml_changes: dbg!(vfs.take_changes()), + parent_changes, + client_change: None, + }; + + dbg!(config_tree.apply_changes(changes, &vfs)); + let local = config_tree.local_config(crate_b); + + assert_eq!( + local.inlayHints_discriminantHints_enable, + crate::config::DiscriminantHintsDef::Always + ); + assert_eq!(local.completion_autoself_enable, false); + } } From 575ebbc5c0d38c37d07237f8c2406e80705ec644 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 01:36:13 +1100 Subject: [PATCH 42/52] Add ancestors method to AbsPath --- crates/paths/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs index 88b8d0aee3a4..ecb1ed151c18 100644 --- a/crates/paths/src/lib.rs +++ b/crates/paths/src/lib.rs @@ -194,6 +194,10 @@ impl AbsPath { self.0.ends_with(&suffix.0) } + pub fn ancestors(&self) -> impl Iterator { + self.0.ancestors().map(AbsPath::assert) + } + pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { Some(( self.file_stem()?.to_str()?, From cc8dce589104d1e7fbd4859057d166714fbcf38e Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 01:36:36 +1100 Subject: [PATCH 43/52] Make the public api just "here's a list of source roots and a project root" --- crates/rust-analyzer/src/config/tree.rs | 157 ++++++++++++++++-------- 1 file changed, 104 insertions(+), 53 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index b1668f3d26ea..2e79c45a7041 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -1,6 +1,7 @@ +use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use std::sync::Arc; -use vfs::{FileId, Vfs}; +use vfs::{AbsPathBuf, FileId, Vfs}; use super::{ConfigInput, LocalConfigData, RootLocalConfigData}; @@ -15,12 +16,23 @@ pub enum ConfigTreeError { /// Some rust-analyzer.toml files have changed, and/or the LSP client sent a new configuration. pub struct ConfigChanges { + /// - `None` => no change + /// - `Some(None)` => the client config was removed / reset or something + /// - `Some(Some(...))` => the client config was updated + client_change: Option>>, + set_project_root: Option, + set_source_roots: Option>, ra_toml_changes: Vec, +} + +/// Internal version +struct ConfigChangesInner { /// - `None` => no change /// - `Some(None)` => the client config was removed / reset or something /// - `Some(Some(...))` => the client config was updated client_change: Option>>, parent_changes: FxHashMap, + ra_toml_changes: Vec, } #[derive(Debug)] @@ -140,16 +152,20 @@ pub struct ConfigDb { storage: salsa::Storage, known_file_ids: FxHashSet, xdg_config_file_id: FileId, + source_roots: FxHashSet, + project_root: AbsPathBuf, } impl salsa::Database for ConfigDb {} impl ConfigDb { - pub fn new(xdg_config_file_id: FileId) -> Self { + pub fn new(xdg_config_file_id: FileId, project_root: AbsPathBuf) -> Self { let mut this = Self { storage: Default::default(), known_file_ids: FxHashSet::default(), xdg_config_file_id, + source_roots: FxHashSet::default(), + project_root, }; this.set_client_config(None); this.ensure_node(xdg_config_file_id); @@ -176,12 +192,68 @@ impl ConfigDb { } /// Applies a bunch of [`ConfigChanges`]. The FileIds referred to in `ConfigChanges` do not - /// need to exist. You can generate the `parent_changes` hashmap by iterating ancestors of all - /// of the [`ide::SourceRoot`]s, slapping `.map(|path| path.join("rust-analyzer.toml"))`. - pub fn apply_changes(&mut self, changes: ConfigChanges, vfs: &Vfs) -> Vec { + /// need to exist. + pub fn apply_changes(&mut self, changes: ConfigChanges, vfs: &mut Vfs) -> Vec { + if let Some(new_project_root) = &changes.set_project_root { + self.project_root = new_project_root.clone(); + } + let source_root_change = changes.set_source_roots.as_ref().or_else(|| { + if changes.set_project_root.is_some() { + Some(&self.source_roots) + } else { + None + } + }); + let parent_changes = if let Some(source_roots) = source_root_change { + source_roots + .iter() + .flat_map(|path: &AbsPathBuf| { + path.ancestors() + .take_while(|x| x.starts_with(&self.project_root)) + .map(|dir| dir.join("rust-analyzer.toml")) + .map(|path| vfs.alloc_file_id(path.into())) + .collect_vec() + // immediately get tuple_windows before returning from flat_map + .into_iter() + .tuple_windows() + .map(|(a, b)| (a, ConfigParent::Parent(b))) + }) + .collect::>() + } else { + Default::default() + }; + + if tracing::enabled!(tracing::Level::TRACE) { + for (&a, parent) in &parent_changes { + tracing::trace!( + "{a:?} ({:?}): parent = {parent:?} ({:?})", + vfs.file_path(a), + match parent { + ConfigParent::Parent(p) => vfs.file_path(*p).to_string(), + ConfigParent::UserDefault => "xdg".to_string(), + } + ); + } + } + + // Could delete (self.known_file_ids - parent_changes.keys) here. + + let inner = ConfigChangesInner { + ra_toml_changes: changes.ra_toml_changes, + client_change: changes.client_change, + parent_changes, + }; + self.apply_changes_inner(inner, vfs) + } + + fn apply_changes_inner( + &mut self, + changes: ConfigChangesInner, + vfs: &Vfs, + ) -> Vec { let mut scratch_errors = Vec::new(); let mut errors = Vec::new(); - let ConfigChanges { client_change, ra_toml_changes, parent_changes } = changes; + let ConfigChangesInner { client_change, ra_toml_changes, parent_changes } = changes; if let Some(change) = client_change { let current = self.client_config(); @@ -285,7 +357,7 @@ mod tests { use std::path::{Path, PathBuf}; use itertools::Itertools; - use vfs::{AbsPathBuf, VfsPath}; + use vfs::{AbsPath, AbsPathBuf, VfsPath}; fn alloc_file_id(vfs: &mut Vfs, s: &str) -> FileId { let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap(); @@ -310,11 +382,14 @@ mod tests { fn basic() { tracing_subscriber::fmt().try_init().ok(); let mut vfs = Vfs::default(); + let project_root = AbsPath::assert(Path::new("/root")); let xdg_config_file_id = alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); - let mut config_tree = ConfigDb::new(xdg_config_file_id); + let mut config_tree = ConfigDb::new(xdg_config_file_id, project_root.to_path_buf()); + + let source_roots = ["/root/crate_a"].map(Path::new).map(AbsPath::assert); - let root = alloc_config( + let _root = alloc_config( &mut vfs, "/root/rust-analyzer.toml", r#" @@ -335,13 +410,12 @@ mod tests { "#, ); - let mut parent_changes = FxHashMap::default(); - parent_changes.insert(crate_a, ConfigParent::Parent(root)); - + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); let changes = ConfigChanges { // Normally you will filter these! ra_toml_changes: vfs.take_changes(), - parent_changes, + set_project_root: None, + set_source_roots: Some(new_source_roots), client_change: Some(Some(Arc::new(ConfigInput { local: crate::config::LocalConfigInput { semanticHighlighting_strings_enable: Some(false), @@ -351,7 +425,7 @@ mod tests { }))), }; - dbg!(config_tree.apply_changes(changes, &vfs)); + dbg!(config_tree.apply_changes(changes, &mut vfs)); let local = config_tree.local_config(crate_a); // from root @@ -384,11 +458,12 @@ mod tests { ); let changes = ConfigChanges { - ra_toml_changes: dbg!(vfs.take_changes()), - parent_changes: Default::default(), client_change: None, + set_project_root: None, + set_source_roots: None, + ra_toml_changes: dbg!(vfs.take_changes()), }; - dbg!(config_tree.apply_changes(changes, &vfs)); + dbg!(config_tree.apply_changes(changes, &mut vfs)); let prev = local; let local = config_tree.local_config(crate_a); @@ -410,53 +485,27 @@ mod tests { } #[test] - fn generated_parent_changes() { + fn set_source_roots() { tracing_subscriber::fmt().try_init().ok(); let mut vfs = Vfs::default(); + let project_root = AbsPath::assert(Path::new("/root")); let xdg = alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); - let mut config_tree = ConfigDb::new(xdg); + let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); - let project_root = Path::new("/root"); - let sourceroots = - [PathBuf::new().join("/root/crate_a"), PathBuf::new().join("/root/crate_a/crate_b")]; - let sourceroot_tomls = sourceroots + let source_roots = + ["/root/crate_a", "/root/crate_a/crate_b"].map(Path::new).map(AbsPath::assert); + let source_root_tomls = source_roots .iter() .map(|dir| dir.join("rust-analyzer.toml")) .map(|path| AbsPathBuf::try_from(path).unwrap()) .map(|path| vfs.alloc_file_id(path.into())) .collect_vec(); - let &[crate_a, crate_b] = &sourceroot_tomls[..] else { + let &[crate_a, crate_b] = &source_root_tomls[..] else { panic!(); }; - let parent_changes = sourceroots - .iter() - .flat_map(|path| { - path.ancestors() - .take_while(|x| x.starts_with(project_root)) - .map(|dir| dir.join("rust-analyzer.toml")) - .map(|path| AbsPathBuf::try_from(path).unwrap()) - .map(|path| vfs.alloc_file_id(path.into())) - .collect_vec() - .into_iter() - .tuple_windows() - .map(|(a, b)| (a, ConfigParent::Parent(b))) - }) - .collect::>(); - - for (&a, parent) in &parent_changes { - eprintln!( - "{a:?} ({:?}): parent = {parent:?} ({:?})", - vfs.file_path(a), - match parent { - ConfigParent::Parent(p) => vfs.file_path(*p).to_string(), - ConfigParent::UserDefault => "xdg".to_string(), - } - ); - } - vfs.set_file_id_contents( xdg, Some(b"[inlayHints.discriminantHints]\nenable = \"always\"".to_vec()), @@ -464,13 +513,15 @@ mod tests { vfs.set_file_id_contents(crate_a, Some(b"[completion.autoself]\nenable = false".to_vec())); // note that crate_b's rust-analyzer.toml doesn't exist + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); let changes = ConfigChanges { - ra_toml_changes: dbg!(vfs.take_changes()), - parent_changes, client_change: None, + set_project_root: None, // already set in ConfigDb::new(...) + set_source_roots: Some(new_source_roots), + ra_toml_changes: dbg!(vfs.take_changes()), }; - dbg!(config_tree.apply_changes(changes, &vfs)); + dbg!(config_tree.apply_changes(changes, &mut vfs)); let local = config_tree.local_config(crate_b); assert_eq!( From cbbfa3757358e5b5564131c0653d8d7d9c500ced Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 01:49:11 +1100 Subject: [PATCH 44/52] Simplify test code --- crates/rust-analyzer/src/config/tree.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 2e79c45a7041..6f23dab2485a 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -356,7 +356,6 @@ fn parse_toml( mod tests { use std::path::{Path, PathBuf}; - use itertools::Itertools; use vfs::{AbsPath, AbsPathBuf, VfsPath}; fn alloc_file_id(vfs: &mut Vfs, s: &str) -> FileId { @@ -496,15 +495,9 @@ mod tests { let source_roots = ["/root/crate_a", "/root/crate_a/crate_b"].map(Path::new).map(AbsPath::assert); - let source_root_tomls = source_roots - .iter() + let [crate_a, crate_b] = source_roots .map(|dir| dir.join("rust-analyzer.toml")) - .map(|path| AbsPathBuf::try_from(path).unwrap()) - .map(|path| vfs.alloc_file_id(path.into())) - .collect_vec(); - let &[crate_a, crate_b] = &source_root_tomls[..] else { - panic!(); - }; + .map(|path| vfs.alloc_file_id(path.into())); vfs.set_file_id_contents( xdg, From fb1149e3f8d5e0b0cd1f4814d18113676b960c95 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 21:58:36 +1100 Subject: [PATCH 45/52] Another test where the source roots are changed --- crates/rust-analyzer/src/config/tree.rs | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 6f23dab2485a..b3620212771d 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -522,5 +522,31 @@ mod tests { crate::config::DiscriminantHintsDef::Always ); assert_eq!(local.completion_autoself_enable, false); + + // ---- + + // Now move crate b to the root. This gives a new FileId for crate_b/ra.toml. + let source_roots = ["/root/crate_a", "/root/crate_b"].map(Path::new).map(AbsPath::assert); + let [crate_a, crate_b] = source_roots + .map(|dir| dir.join("rust-analyzer.toml")) + .map(|path| vfs.alloc_file_id(path.into())); + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); + let changes = ConfigChanges { + client_change: None, + set_project_root: None, // already set in ConfigDb::new(...) + set_source_roots: Some(new_source_roots), + ra_toml_changes: dbg!(vfs.take_changes()), + }; + + dbg!(config_tree.apply_changes(changes, &mut vfs)); + let local = config_tree.local_config(crate_b); + + // Still inherits from xdg + assert_eq!( + local.inlayHints_discriminantHints_enable, + crate::config::DiscriminantHintsDef::Always + ); + // new crate_b does not inherit from crate_a + assert_eq!(local.completion_autoself_enable, true); } } From f253aa4cb1281fae3f0073c22e1b2ed09fbab215 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 21:58:52 +1100 Subject: [PATCH 46/52] Test changing project root --- crates/rust-analyzer/src/config/tree.rs | 82 +++++++++++++++++++++---- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index b3620212771d..2c6555c3f3bd 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -209,6 +209,7 @@ impl ConfigDb { .iter() .flat_map(|path: &AbsPathBuf| { path.ancestors() + // Note that Path::new("/root2/abc").starts_with(Path::new("/root")) is false .take_while(|x| x.starts_with(&self.project_root)) .map(|dir| dir.join("rust-analyzer.toml")) .map(|path| vfs.alloc_file_id(path.into())) @@ -236,7 +237,17 @@ impl ConfigDb { } } - // Could delete (self.known_file_ids - parent_changes.keys) here. + // Remove source roots (& their parent config files) that are no longer part of the project root + self.known_file_ids + .iter() + .cloned() + .filter(|&x| x != self.xdg_config_file_id && !parent_changes.contains_key(&x)) + .collect_vec() + .into_iter() + .for_each(|deleted| { + self.known_file_ids.remove(&deleted); + self.reset_node(deleted); + }); let inner = ConfigChangesInner { ra_toml_changes: changes.ra_toml_changes, @@ -307,17 +318,17 @@ impl ConfigDb { /// if it's never been seen before fn ensure_node(&mut self, file_id: FileId) { if self.known_file_ids.insert(file_id) { - self.set_config_input(file_id, None); - self.set_parent( - file_id, - if file_id == self.xdg_config_file_id { - None - } else { - Some(self.xdg_config_file_id) - }, - ); + self.reset_node(file_id); } } + + fn reset_node(&mut self, file_id: FileId) { + self.set_config_input(file_id, None); + self.set_parent( + file_id, + if file_id == self.xdg_config_file_id { None } else { Some(self.xdg_config_file_id) }, + ); + } } fn parse_toml( @@ -527,7 +538,7 @@ mod tests { // Now move crate b to the root. This gives a new FileId for crate_b/ra.toml. let source_roots = ["/root/crate_a", "/root/crate_b"].map(Path::new).map(AbsPath::assert); - let [crate_a, crate_b] = source_roots + let [_crate_a, crate_b] = source_roots .map(|dir| dir.join("rust-analyzer.toml")) .map(|path| vfs.alloc_file_id(path.into())); let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); @@ -549,4 +560,53 @@ mod tests { // new crate_b does not inherit from crate_a assert_eq!(local.completion_autoself_enable, true); } + + #[test] + fn change_project_root() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + + let project_root = AbsPath::assert(Path::new("/root")); + let xdg = + alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); + let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); + + let source_roots = ["/root/crate_a"].map(Path::new).map(AbsPath::assert); + let crate_a = vfs.alloc_file_id(source_roots[0].join("rust-analyzer.toml").into()); + + let _root = alloc_config( + &mut vfs, + "/root/rust-analyzer.toml", + r#" + [completion.autoself] + enable = false + "#, + ); + + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); + let changes = ConfigChanges { + client_change: None, + set_project_root: None, // already set in ConfigDb::new(...) + set_source_roots: Some(new_source_roots), + ra_toml_changes: dbg!(vfs.take_changes()), + }; + config_tree.apply_changes(changes, &mut vfs); + let local = config_tree.local_config(crate_a); + // initially crate_a is part of the project root, so it does inherit + // from /root/rust-analyzer.toml + assert_eq!(local.completion_autoself_enable, false); + + // change project root + let changes = ConfigChanges { + client_change: None, + set_project_root: Some(AbsPath::assert(Path::new("/ro")).to_path_buf()), + set_source_roots: None, + ra_toml_changes: dbg!(vfs.take_changes()), + }; + config_tree.apply_changes(changes, &mut vfs); + // crate_a is now outside the project root and hence inherit (1) xdg (2) + // crate_a/ra.toml, but not /root/rust-analyzer.toml any more + let local = config_tree.local_config(crate_a); + assert_eq!(local.completion_autoself_enable, true); + } } From 407480397cc21976c84f4f89260aa5182fd7c68c Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 22:06:46 +1100 Subject: [PATCH 47/52] add failing test --- crates/rust-analyzer/src/config/tree.rs | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 2c6555c3f3bd..85bb22964930 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -609,4 +609,52 @@ mod tests { let local = config_tree.local_config(crate_a); assert_eq!(local.completion_autoself_enable, true); } + + #[test] + fn no_change_to_source_roots() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + + let project_root = AbsPath::assert(Path::new("/root")); + let xdg = + alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); + let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); + + let source_roots = ["/root/crate_a"].map(Path::new).map(AbsPath::assert); + let crate_a = vfs.alloc_file_id(source_roots[0].join("rust-analyzer.toml").into()); + + let _root = alloc_config( + &mut vfs, + "/root/rust-analyzer.toml", + r#" + [completion.autoself] + enable = false + "#, + ); + + let new_source_roots = source_roots.into_iter().map(|abs| abs.to_path_buf()).collect(); + let changes = ConfigChanges { + client_change: None, + set_project_root: None, // already set in ConfigDb::new(...) + set_source_roots: Some(new_source_roots), + ra_toml_changes: dbg!(vfs.take_changes()), + }; + config_tree.apply_changes(changes, &mut vfs); + let local = config_tree.local_config(crate_a); + // initially crate_a is part of the project root, so it does inherit + // from /root/rust-analyzer.toml + assert_eq!(local.completion_autoself_enable, false); + + let changes = ConfigChanges { + client_change: None, + set_project_root: None, + set_source_roots: None, + ra_toml_changes: dbg!(vfs.take_changes()), + }; + config_tree.apply_changes(changes, &mut vfs); + let local = config_tree.local_config(crate_a); + // initially crate_a is part of the project root, so it does inherit + // from /root/rust-analyzer.toml + assert_eq!(local.completion_autoself_enable, false); + } } From 6906ae9e1e35ebe219c2d8bf7e8b2ae9655d01e2 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 22:06:53 +1100 Subject: [PATCH 48/52] And fix it --- crates/rust-analyzer/src/config/tree.rs | 30 +++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 85bb22964930..2e5ad04b934d 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -205,7 +205,7 @@ impl ConfigDb { } }); let parent_changes = if let Some(source_roots) = source_root_change { - source_roots + let parent_changes = source_roots .iter() .flat_map(|path: &AbsPathBuf| { path.ancestors() @@ -219,7 +219,21 @@ impl ConfigDb { .tuple_windows() .map(|(a, b)| (a, ConfigParent::Parent(b))) }) - .collect::>() + .collect::>(); + + // Remove source roots (& their parent config files) that are no longer part of the project root + self.known_file_ids + .iter() + .cloned() + .filter(|&x| x != self.xdg_config_file_id && !parent_changes.contains_key(&x)) + .collect_vec() + .into_iter() + .for_each(|deleted| { + self.known_file_ids.remove(&deleted); + self.reset_node(deleted); + }); + + parent_changes } else { Default::default() }; @@ -237,18 +251,6 @@ impl ConfigDb { } } - // Remove source roots (& their parent config files) that are no longer part of the project root - self.known_file_ids - .iter() - .cloned() - .filter(|&x| x != self.xdg_config_file_id && !parent_changes.contains_key(&x)) - .collect_vec() - .into_iter() - .for_each(|deleted| { - self.known_file_ids.remove(&deleted); - self.reset_node(deleted); - }); - let inner = ConfigChangesInner { ra_toml_changes: changes.ra_toml_changes, client_change: changes.client_change, From c2461358b1a7929fac3c4b35cb003567d81a0297 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Thu, 25 Jan 2024 22:08:14 +1100 Subject: [PATCH 49/52] Fix comment in test --- crates/rust-analyzer/src/config/tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index 2e5ad04b934d..a4cb4d4909ea 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -647,6 +647,7 @@ mod tests { // from /root/rust-analyzer.toml assert_eq!(local.completion_autoself_enable, false); + // Send in an empty change, should have no effect let changes = ConfigChanges { client_change: None, set_project_root: None, @@ -655,8 +656,6 @@ mod tests { }; config_tree.apply_changes(changes, &mut vfs); let local = config_tree.local_config(crate_a); - // initially crate_a is part of the project root, so it does inherit - // from /root/rust-analyzer.toml assert_eq!(local.completion_autoself_enable, false); } } From bf1419a846c0922cced39a7b46dbb2e9a25e5044 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 26 Jan 2024 01:24:40 +1100 Subject: [PATCH 50/52] Make AbsPath::assert work on plain string slices too --- crates/paths/src/lib.rs | 3 +- crates/rust-analyzer/src/config/tree.rs | 44 +++++++++++-------------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs index ecb1ed151c18..55bd5376dfa8 100644 --- a/crates/paths/src/lib.rs +++ b/crates/paths/src/lib.rs @@ -136,7 +136,8 @@ impl AbsPath { /// # Panics /// /// Panics if `path` is not absolute. - pub fn assert(path: &Path) -> &AbsPath { + pub fn assert + ?Sized>(path: &P) -> &AbsPath { + let path = path.as_ref(); assert!(path.is_absolute()); unsafe { &*(path as *const Path as *const AbsPath) } } diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index a4cb4d4909ea..de54cc6aa220 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -367,13 +367,12 @@ fn parse_toml( #[cfg(test)] mod tests { - use std::path::{Path, PathBuf}; + use std::path::PathBuf; use vfs::{AbsPath, AbsPathBuf, VfsPath}; fn alloc_file_id(vfs: &mut Vfs, s: &str) -> FileId { - let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap(); - + let abs_path = AbsPath::assert(s).to_path_buf(); let vfs_path = VfsPath::from(abs_path); let file_id = vfs.alloc_file_id(vfs_path); vfs.set_file_id_contents(file_id, None); @@ -381,25 +380,26 @@ mod tests { } fn alloc_config(vfs: &mut Vfs, s: &str, config: &str) -> FileId { - let abs_path = AbsPathBuf::try_from(PathBuf::new().join(s)).unwrap(); - + let abs_path = AbsPath::assert(s).to_path_buf(); let vfs_path = VfsPath::from(abs_path); let file_id = vfs.alloc_file_id(vfs_path); vfs.set_file_id_contents(file_id, Some(config.to_string().into_bytes())); file_id } + const XDG_CONFIG_HOME_RATOML: &'static str = + "/home/username/.config/rust-analyzer/rust-analyzer.toml"; + use super::*; #[test] fn basic() { tracing_subscriber::fmt().try_init().ok(); let mut vfs = Vfs::default(); - let project_root = AbsPath::assert(Path::new("/root")); - let xdg_config_file_id = - alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); + let project_root = AbsPath::assert("/root"); + let xdg_config_file_id = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); let mut config_tree = ConfigDb::new(xdg_config_file_id, project_root.to_path_buf()); - let source_roots = ["/root/crate_a"].map(Path::new).map(AbsPath::assert); + let source_roots = ["/root/crate_a"].map(AbsPath::assert); let _root = alloc_config( &mut vfs, @@ -501,13 +501,11 @@ mod tests { tracing_subscriber::fmt().try_init().ok(); let mut vfs = Vfs::default(); - let project_root = AbsPath::assert(Path::new("/root")); - let xdg = - alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); + let project_root = AbsPath::assert("/root"); + let xdg = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); - let source_roots = - ["/root/crate_a", "/root/crate_a/crate_b"].map(Path::new).map(AbsPath::assert); + let source_roots = ["/root/crate_a", "/root/crate_a/crate_b"].map(AbsPath::assert); let [crate_a, crate_b] = source_roots .map(|dir| dir.join("rust-analyzer.toml")) .map(|path| vfs.alloc_file_id(path.into())); @@ -539,7 +537,7 @@ mod tests { // ---- // Now move crate b to the root. This gives a new FileId for crate_b/ra.toml. - let source_roots = ["/root/crate_a", "/root/crate_b"].map(Path::new).map(AbsPath::assert); + let source_roots = ["/root/crate_a", "/root/crate_b"].map(AbsPath::assert); let [_crate_a, crate_b] = source_roots .map(|dir| dir.join("rust-analyzer.toml")) .map(|path| vfs.alloc_file_id(path.into())); @@ -568,12 +566,11 @@ mod tests { tracing_subscriber::fmt().try_init().ok(); let mut vfs = Vfs::default(); - let project_root = AbsPath::assert(Path::new("/root")); - let xdg = - alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); + let project_root = AbsPath::assert("/root"); + let xdg = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); - let source_roots = ["/root/crate_a"].map(Path::new).map(AbsPath::assert); + let source_roots = ["/root/crate_a"].map(AbsPath::assert); let crate_a = vfs.alloc_file_id(source_roots[0].join("rust-analyzer.toml").into()); let _root = alloc_config( @@ -601,7 +598,7 @@ mod tests { // change project root let changes = ConfigChanges { client_change: None, - set_project_root: Some(AbsPath::assert(Path::new("/ro")).to_path_buf()), + set_project_root: Some(AbsPath::assert("/ro").to_path_buf()), set_source_roots: None, ra_toml_changes: dbg!(vfs.take_changes()), }; @@ -617,12 +614,11 @@ mod tests { tracing_subscriber::fmt().try_init().ok(); let mut vfs = Vfs::default(); - let project_root = AbsPath::assert(Path::new("/root")); - let xdg = - alloc_file_id(&mut vfs, "/home/username/.config/rust-analyzer/rust-analyzer.toml"); + let project_root = AbsPath::assert("/root"); + let xdg = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); - let source_roots = ["/root/crate_a"].map(Path::new).map(AbsPath::assert); + let source_roots = ["/root/crate_a"].map(AbsPath::assert); let crate_a = vfs.alloc_file_id(source_roots[0].join("rust-analyzer.toml").into()); let _root = alloc_config( From 8177b17502d9ac7b843be71ccd857c1dc4c6983a Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 26 Jan 2024 01:25:56 +1100 Subject: [PATCH 51/52] Add a failing test for irrelevant vfs changes --- crates/rust-analyzer/src/config/tree.rs | 29 ++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index de54cc6aa220..ff194a027449 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -5,7 +5,7 @@ use vfs::{AbsPathBuf, FileId, Vfs}; use super::{ConfigInput, LocalConfigData, RootLocalConfigData}; -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum ConfigTreeError { Removed, NonExistent, @@ -654,4 +654,31 @@ mod tests { let local = config_tree.local_config(crate_a); assert_eq!(local.completion_autoself_enable, false); } + + #[test] + fn ignore_irrelevant_vfs_changes() { + tracing_subscriber::fmt().try_init().ok(); + let mut vfs = Vfs::default(); + + let project_root = AbsPath::assert("/root"); + let xdg = alloc_file_id(&mut vfs, XDG_CONFIG_HOME_RATOML); + let mut config_tree = ConfigDb::new(xdg, project_root.to_path_buf()); + + // The main way an irrelevant vfs file change is going to show up is in TOML parse errors. + let invalid_utf8 = b"\xc3\x28"; + vfs.set_file_contents( + AbsPath::assert("/irrelevant/file.bin").to_path_buf().into(), + Some(invalid_utf8.to_vec()), + ); + let errors = config_tree.apply_changes( + ConfigChanges { + client_change: None, + set_project_root: None, + set_source_roots: None, + ra_toml_changes: vfs.take_changes(), + }, + &mut vfs, + ); + assert_eq!(errors, vec![]); + } } From 512295c0fd6d2ab55ab8416db2dd17d78c29227e Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Fri, 26 Jan 2024 01:32:21 +1100 Subject: [PATCH 52/52] Fix the failing test for irrelevant vfs changes --- crates/rust-analyzer/src/config/tree.rs | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/crates/rust-analyzer/src/config/tree.rs b/crates/rust-analyzer/src/config/tree.rs index ff194a027449..ef2803f3d1d7 100644 --- a/crates/rust-analyzer/src/config/tree.rs +++ b/crates/rust-analyzer/src/config/tree.rs @@ -280,7 +280,25 @@ impl ConfigDb { } } + for (file_id, parent) in parent_changes { + self.ensure_node(file_id); + let parent_node_id = match parent { + ConfigParent::Parent(parent_file_id) => { + self.ensure_node(parent_file_id); + parent_file_id + } + ConfigParent::UserDefault if file_id == self.xdg_config_file_id => continue, + ConfigParent::UserDefault => self.xdg_config_file_id, + }; + self.set_parent(file_id, Some(parent_node_id)) + } + for change in ra_toml_changes { + if !self.known_file_ids.contains(&change.file_id) { + // Irrelevant Vfs change. Ideally you would not pass these in at all, but it's not + // a problem to filter them out here. + continue; + } // turn and face the strain match change.change_kind { vfs::ChangeKind::Create | vfs::ChangeKind::Modify => { @@ -288,31 +306,14 @@ impl ConfigDb { .map(PointerCmp); tracing::trace!("updating toml for {:?} to {:?}", change.file_id, input); - self.ensure_node(change.file_id); self.set_config_input(change.file_id, input); } vfs::ChangeKind::Delete => { - self.ensure_node(change.file_id); self.set_config_input(change.file_id, None); } } } - for (file_id, parent) in parent_changes { - self.ensure_node(file_id); - let parent_node_id = match parent { - ConfigParent::Parent(parent_file_id) => { - self.ensure_node(parent_file_id); - parent_file_id - } - ConfigParent::UserDefault if file_id == self.xdg_config_file_id => continue, - ConfigParent::UserDefault => self.xdg_config_file_id, - }; - // order of children within the parent node does not matter - tracing::trace!("appending child {file_id:?} to {parent_node_id:?}"); - self.set_parent(file_id, Some(parent_node_id)) - } - errors }