use crate::config::CargoPortConfig;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum ConfigKey {
InvertScroll,
IncludeNonRust,
CiRunCount,
Editor,
MainBranch,
OtherPrimaryBranches,
IncludeDirs,
InlineDirs,
StatusFlashSecs,
TaskLingerSecs,
DiscoveryShimmerSecs,
CacheRoot,
LintEnabled,
LintInclude,
LintExclude,
LintCommands,
LintCacheSize,
LintOnDiscovery,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) struct ReloadActions {
pub rebuild_tree: bool,
pub rescan: bool,
pub refresh_lint_runtime: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) struct ReloadContext {
pub scan_complete: bool,
pub has_cached_non_rust: bool,
}
#[derive(Clone, Copy)]
struct ConfigHandler {
key: ConfigKey,
mark: fn(&mut ReloadActions, &CargoPortConfig, &CargoPortConfig, ReloadContext),
}
const CONFIG_HANDLERS: &[ConfigHandler] = &[
ConfigHandler {
key: ConfigKey::InlineDirs,
mark: mark_rebuild_tree,
},
ConfigHandler {
key: ConfigKey::IncludeNonRust,
mark: mark_include_non_rust,
},
ConfigHandler {
key: ConfigKey::CiRunCount,
mark: mark_rescan,
},
ConfigHandler {
key: ConfigKey::MainBranch,
mark: mark_rescan,
},
ConfigHandler {
key: ConfigKey::OtherPrimaryBranches,
mark: mark_rescan,
},
ConfigHandler {
key: ConfigKey::IncludeDirs,
mark: mark_rescan,
},
ConfigHandler {
key: ConfigKey::CacheRoot,
mark: mark_rescan,
},
ConfigHandler {
key: ConfigKey::CacheRoot,
mark: mark_refresh_lint_runtime,
},
ConfigHandler {
key: ConfigKey::LintEnabled,
mark: mark_refresh_lint_runtime,
},
ConfigHandler {
key: ConfigKey::LintInclude,
mark: mark_refresh_lint_runtime,
},
ConfigHandler {
key: ConfigKey::LintExclude,
mark: mark_refresh_lint_runtime,
},
ConfigHandler {
key: ConfigKey::LintCommands,
mark: mark_refresh_lint_runtime,
},
ConfigHandler {
key: ConfigKey::LintCacheSize,
mark: mark_refresh_lint_runtime,
},
ConfigHandler {
key: ConfigKey::LintOnDiscovery,
mark: mark_refresh_lint_runtime,
},
];
const fn mark_rebuild_tree(
actions: &mut ReloadActions,
_old: &CargoPortConfig,
_new: &CargoPortConfig,
_context: ReloadContext,
) {
actions.rebuild_tree = true;
}
const fn mark_rescan(
actions: &mut ReloadActions,
_old: &CargoPortConfig,
_new: &CargoPortConfig,
_context: ReloadContext,
) {
actions.rescan = true;
}
const fn mark_refresh_lint_runtime(
actions: &mut ReloadActions,
_old: &CargoPortConfig,
_new: &CargoPortConfig,
_context: ReloadContext,
) {
actions.refresh_lint_runtime = true;
}
const fn mark_include_non_rust(
actions: &mut ReloadActions,
old: &CargoPortConfig,
new: &CargoPortConfig,
context: ReloadContext,
) {
if !context.scan_complete {
actions.rescan = true;
return;
}
let enabling_non_rust = !old.tui.include_non_rust.includes_non_rust()
&& new.tui.include_non_rust.includes_non_rust();
if enabling_non_rust && !context.has_cached_non_rust {
actions.rescan = true;
} else {
actions.rebuild_tree = true;
}
}
pub(super) fn changed_keys(old: &CargoPortConfig, new: &CargoPortConfig) -> Vec<ConfigKey> {
let mut keys = Vec::new();
if old.mouse.invert_scroll != new.mouse.invert_scroll {
keys.push(ConfigKey::InvertScroll);
}
if old.tui.include_non_rust != new.tui.include_non_rust {
keys.push(ConfigKey::IncludeNonRust);
}
if old.tui.ci_run_count != new.tui.ci_run_count {
keys.push(ConfigKey::CiRunCount);
}
if old.tui.editor != new.tui.editor {
keys.push(ConfigKey::Editor);
}
if old.tui.main_branch != new.tui.main_branch {
keys.push(ConfigKey::MainBranch);
}
if old.tui.other_primary_branches != new.tui.other_primary_branches {
keys.push(ConfigKey::OtherPrimaryBranches);
}
if old.tui.include_dirs != new.tui.include_dirs {
keys.push(ConfigKey::IncludeDirs);
}
if old.tui.inline_dirs != new.tui.inline_dirs {
keys.push(ConfigKey::InlineDirs);
}
if old.tui.status_flash_secs.to_bits() != new.tui.status_flash_secs.to_bits() {
keys.push(ConfigKey::StatusFlashSecs);
}
if old.tui.task_linger_secs.to_bits() != new.tui.task_linger_secs.to_bits() {
keys.push(ConfigKey::TaskLingerSecs);
}
if old.tui.discovery_shimmer_secs.to_bits() != new.tui.discovery_shimmer_secs.to_bits() {
keys.push(ConfigKey::DiscoveryShimmerSecs);
}
if old.cache.root != new.cache.root {
keys.push(ConfigKey::CacheRoot);
}
if old.lint.enabled != new.lint.enabled {
keys.push(ConfigKey::LintEnabled);
}
if old.lint.include != new.lint.include {
keys.push(ConfigKey::LintInclude);
}
if old.lint.exclude != new.lint.exclude {
keys.push(ConfigKey::LintExclude);
}
if old.lint.commands != new.lint.commands {
keys.push(ConfigKey::LintCommands);
}
if old.lint.cache_size != new.lint.cache_size {
keys.push(ConfigKey::LintCacheSize);
}
if old.lint.on_discovery != new.lint.on_discovery {
keys.push(ConfigKey::LintOnDiscovery);
}
keys
}
pub(super) fn collect_reload_actions(
old: &CargoPortConfig,
new: &CargoPortConfig,
context: ReloadContext,
) -> ReloadActions {
let mut actions = ReloadActions::default();
for key in changed_keys(old, new) {
for handler in CONFIG_HANDLERS {
if handler.key == key {
(handler.mark)(&mut actions, old, new, context);
}
}
}
actions
}
#[cfg(test)]
#[allow(
clippy::expect_used,
reason = "tests should panic on unexpected values"
)]
mod tests {
use super::*;
#[test]
fn changed_keys_include_value_only_settings_without_actions() {
let mut new = CargoPortConfig::default();
new.mouse.invert_scroll.toggle();
new.tui.editor = "helix".to_string();
new.tui.status_flash_secs = 10.0;
new.tui.discovery_shimmer_secs = 4.0;
let keys = changed_keys(&CargoPortConfig::default(), &new);
assert!(keys.contains(&ConfigKey::InvertScroll));
assert!(keys.contains(&ConfigKey::Editor));
assert!(keys.contains(&ConfigKey::StatusFlashSecs));
assert!(keys.contains(&ConfigKey::DiscoveryShimmerSecs));
assert_eq!(
collect_reload_actions(&CargoPortConfig::default(), &new, ReloadContext::default()),
ReloadActions::default()
);
}
#[test]
fn reload_actions_coalesce_rescan_triggers() {
let mut new = CargoPortConfig::default();
new.tui.ci_run_count = 9;
new.tui.include_dirs = vec!["rust".to_string()];
new.tui.include_non_rust.toggle();
assert_eq!(
collect_reload_actions(&CargoPortConfig::default(), &new, ReloadContext::default()),
ReloadActions {
rebuild_tree: false,
rescan: true,
refresh_lint_runtime: false,
}
);
}
#[test]
fn completed_scan_rebuilds_tree_when_hiding_cached_non_rust_projects() {
let mut old = CargoPortConfig::default();
old.tui.include_non_rust.toggle();
let mut new = old.clone();
new.tui.include_non_rust.toggle();
assert_eq!(
collect_reload_actions(
&old,
&new,
ReloadContext {
scan_complete: true,
has_cached_non_rust: true,
},
),
ReloadActions {
rebuild_tree: true,
rescan: false,
refresh_lint_runtime: false,
}
);
}
#[test]
fn completed_scan_rescans_when_enabling_non_rust_without_cached_projects() {
let mut new = CargoPortConfig::default();
new.tui.include_non_rust.toggle();
assert_eq!(
collect_reload_actions(
&CargoPortConfig::default(),
&new,
ReloadContext {
scan_complete: true,
has_cached_non_rust: false,
},
),
ReloadActions {
rebuild_tree: false,
rescan: true,
refresh_lint_runtime: false,
}
);
}
#[test]
fn reload_actions_coalesce_lint_triggers() {
let mut new = CargoPortConfig::default();
new.lint.enabled = true;
new.lint.include = vec!["hana".to_string()];
new.lint.commands = vec![crate::config::default_clippy_lint_command()];
assert_eq!(
collect_reload_actions(&CargoPortConfig::default(), &new, ReloadContext::default()),
ReloadActions {
rebuild_tree: false,
rescan: false,
refresh_lint_runtime: true,
}
);
}
#[test]
fn cache_root_marks_rescan_and_lint_runtime_refresh() {
let mut new = CargoPortConfig::default();
new.cache.root = "tmp-cache".to_string();
assert_eq!(
collect_reload_actions(&CargoPortConfig::default(), &new, ReloadContext::default()),
ReloadActions {
rebuild_tree: false,
rescan: true,
refresh_lint_runtime: true,
}
);
}
}