use super::AppSettings;
use super::SettingsError;
pub const CURRENT_CONFIG_VERSION: u32 = 6;
pub struct ConfigMigration;
impl ConfigMigration {
pub fn run(settings: &mut AppSettings) -> Result<bool, SettingsError> {
let mut migrated = false;
if let Some(ref mut wc) = settings.workspace_config
&& !wc.global.current_workspace.is_empty()
&& !wc.workspaces.contains_key(&wc.global.current_workspace)
{
let first = wc.workspaces.keys().next().cloned().unwrap_or_default();
tracing::warn!(
"current_workspace '{}' does not exist, resetting to '{}'",
wc.global.current_workspace,
first
);
wc.global.current_workspace = first;
migrated = true;
}
if settings.config_version < 4 {
Self::migrate_to_v4(settings);
migrated = true;
}
if settings.config_version < 5 {
Self::migrate_to_v5(settings);
migrated = true;
}
if settings.config_version < 6 {
Self::migrate_to_v6(settings);
migrated = true;
}
if migrated {
settings.config_version = CURRENT_CONFIG_VERSION;
}
Ok(migrated)
}
fn migrate_to_v6(settings: &mut AppSettings) {
use crate::keys::KeyBindings;
use crate::keys::action_shortcuts::ActionShortcuts;
use crate::keys::key_combo::KeyCombo;
use crate::keys::key_strike::KeyStrike;
let ctrl = crate::keys::key_combo::KeyModifiers::new().and_ctrl();
let ctrl_shift_p = KeyCombo::new(ctrl.and_shift(), KeyStrike::KeyP);
let ctrl_comma = KeyCombo::new(ctrl, KeyStrike::Comma);
let mut map = settings.key_bindings.to_hashmap();
let at_old_default = map
.get(&ActionShortcuts::OpenPreferences)
.is_some_and(|v| v.as_slice() == [ctrl_shift_p]);
let comma_free = !map.values().flatten().any(|c| *c == ctrl_comma);
if at_old_default && comma_free {
map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_comma]);
}
settings.key_bindings = KeyBindings::from_hashmap(map);
}
fn migrate_to_v5(settings: &mut AppSettings) {
use crate::keys::KeyBindings;
use crate::keys::action_shortcuts::ActionShortcuts;
use crate::keys::key_combo::KeyCombo;
use crate::keys::key_strike::KeyStrike;
let ctrl = crate::keys::key_combo::KeyModifiers::new().and_ctrl();
let ctrl_shift = ctrl.and_shift();
let ctrl_p = KeyCombo::new(ctrl, KeyStrike::KeyP);
let ctrl_shift_p = KeyCombo::new(ctrl_shift, KeyStrike::KeyP);
let mut map = settings.key_bindings.to_hashmap();
let settings_is_old_default = map
.get(&ActionShortcuts::OpenPreferences)
.is_some_and(|v| v.as_slice() == [ctrl_p]);
let palette_unset_or_old_default = map
.get(&ActionShortcuts::OpenCommandPalette)
.is_none_or(|v| v.is_empty() || v.as_slice() == [ctrl_shift_p]);
if settings_is_old_default && palette_unset_or_old_default {
map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_shift_p]);
map.insert(ActionShortcuts::OpenCommandPalette, vec![ctrl_p]);
}
settings.key_bindings = KeyBindings::from_hashmap(map);
}
fn migrate_to_v4(settings: &mut AppSettings) {
use crate::keys::KeyBindings;
use crate::keys::action_shortcuts::ActionShortcuts;
use crate::keys::key_combo::KeyCombo;
use crate::keys::key_strike::KeyStrike;
let ctrl = crate::keys::key_combo::KeyModifiers::new().and_ctrl();
let ctrl_g = KeyCombo::new(ctrl, KeyStrike::KeyG);
let ctrl_n = KeyCombo::new(ctrl, KeyStrike::KeyN);
let mut map = settings.key_bindings.to_hashmap();
let follow_is_old_default = map
.get(&ActionShortcuts::FollowLink)
.is_some_and(|v| v.as_slice() == [ctrl_g]);
if follow_is_old_default {
map.insert(ActionShortcuts::FollowLink, vec![ctrl_n]);
map.entry(ActionShortcuts::Leader).or_default().push(ctrl_g);
}
settings.key_bindings = KeyBindings::from_hashmap(map);
}
}
#[cfg(test)]
#[allow(clippy::field_reassign_with_default)]
mod tests {
use super::*;
use crate::settings::workspace_config::WorkspaceConfig;
#[test]
fn a_config_already_at_the_current_version_is_left_alone() {
let mut settings = AppSettings::default();
settings.config_version = CURRENT_CONFIG_VERSION;
settings.workspace_config = Some(WorkspaceConfig::new_empty());
let migrated = ConfigMigration::run(&mut settings).unwrap();
assert!(!migrated);
}
#[test]
fn v4_moves_ctrl_g_from_followlink_to_leader() {
use crate::keys::KeyBindings;
use crate::keys::action_shortcuts::ActionShortcuts;
use crate::keys::key_combo::{KeyCombo, KeyModifiers};
use crate::keys::key_strike::KeyStrike;
let ctrl = KeyModifiers::new().and_ctrl();
let ctrl_g = KeyCombo::new(ctrl, KeyStrike::KeyG);
let ctrl_n = KeyCombo::new(ctrl, KeyStrike::KeyN);
let mut settings = AppSettings::default();
let mut map = std::collections::HashMap::new();
map.insert(ActionShortcuts::FollowLink, vec![ctrl_g]);
settings.key_bindings = KeyBindings::from_hashmap(map);
settings.config_version = 3;
assert!(ConfigMigration::run(&mut settings).unwrap());
let map = settings.key_bindings.to_hashmap();
assert_eq!(map.get(&ActionShortcuts::Leader), Some(&vec![ctrl_g]));
assert_eq!(map.get(&ActionShortcuts::FollowLink), Some(&vec![ctrl_n]));
assert_eq!(settings.config_version, CURRENT_CONFIG_VERSION);
}
#[test]
fn v6_moves_settings_to_ctrl_comma() {
use crate::keys::KeyBindings;
use crate::keys::action_shortcuts::ActionShortcuts;
use crate::keys::key_combo::{KeyCombo, KeyModifiers};
use crate::keys::key_strike::KeyStrike;
let ctrl = KeyModifiers::new().and_ctrl();
let ctrl_shift_p = KeyCombo::new(ctrl.and_shift(), KeyStrike::KeyP);
let ctrl_comma = KeyCombo::new(ctrl, KeyStrike::Comma);
let mut settings = AppSettings::default();
let mut map = std::collections::HashMap::new();
map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_shift_p]);
settings.key_bindings = KeyBindings::from_hashmap(map);
settings.config_version = 5;
assert!(ConfigMigration::run(&mut settings).unwrap());
let map = settings.key_bindings.to_hashmap();
assert_eq!(
map.get(&ActionShortcuts::OpenPreferences),
Some(&vec![ctrl_comma])
);
}
#[test]
fn v5_swaps_palette_onto_ctrl_p() {
use crate::keys::KeyBindings;
use crate::keys::action_shortcuts::ActionShortcuts;
use crate::keys::key_combo::{KeyCombo, KeyModifiers};
use crate::keys::key_strike::KeyStrike;
let ctrl = KeyModifiers::new().and_ctrl();
let ctrl_p = KeyCombo::new(ctrl, KeyStrike::KeyP);
let ctrl_shift_p = KeyCombo::new(ctrl.and_shift(), KeyStrike::KeyP);
let mut settings = AppSettings::default();
let mut map = std::collections::HashMap::new();
map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_p]);
settings.key_bindings = KeyBindings::from_hashmap(map);
settings.config_version = 4;
assert!(ConfigMigration::run(&mut settings).unwrap());
let map = settings.key_bindings.to_hashmap();
assert_eq!(
map.get(&ActionShortcuts::OpenCommandPalette),
Some(&vec![ctrl_p])
);
let ctrl_comma = KeyCombo::new(ctrl, KeyStrike::Comma);
assert_eq!(
map.get(&ActionShortcuts::OpenPreferences),
Some(&vec![ctrl_comma])
);
let _ = ctrl_shift_p;
}
#[test]
fn v5_leaves_customised_settings_binding_alone() {
use crate::keys::KeyBindings;
use crate::keys::action_shortcuts::ActionShortcuts;
use crate::keys::key_combo::{KeyCombo, KeyModifiers};
use crate::keys::key_strike::KeyStrike;
let ctrl = KeyModifiers::new().and_ctrl();
let ctrl_x = KeyCombo::new(ctrl, KeyStrike::KeyX);
let mut settings = AppSettings::default();
let mut map = std::collections::HashMap::new();
map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_x]);
settings.key_bindings = KeyBindings::from_hashmap(map);
settings.config_version = 4;
ConfigMigration::run(&mut settings).unwrap();
let map = settings.key_bindings.to_hashmap();
assert_eq!(
map.get(&ActionShortcuts::OpenPreferences),
Some(&vec![ctrl_x])
);
}
#[test]
fn v4_leaves_customised_followlink_alone() {
use crate::keys::KeyBindings;
use crate::keys::action_shortcuts::ActionShortcuts;
use crate::keys::key_combo::{KeyCombo, KeyModifiers};
use crate::keys::key_strike::KeyStrike;
let ctrl = KeyModifiers::new().and_ctrl();
let ctrl_x = KeyCombo::new(ctrl, KeyStrike::KeyX);
let mut settings = AppSettings::default();
let mut map = std::collections::HashMap::new();
map.insert(ActionShortcuts::FollowLink, vec![ctrl_x]);
settings.key_bindings = KeyBindings::from_hashmap(map);
settings.config_version = 3;
ConfigMigration::run(&mut settings).unwrap();
let map = settings.key_bindings.to_hashmap();
assert_eq!(map.get(&ActionShortcuts::FollowLink), Some(&vec![ctrl_x]));
assert!(
map.get(&ActionShortcuts::Leader)
.is_none_or(|v| v.is_empty())
);
}
}