kimun_notes/settings/
config_migration.rs1use super::AppSettings;
8use super::SettingsError;
9
10pub const CURRENT_CONFIG_VERSION: u32 = 6;
17
18pub struct ConfigMigration;
21
22impl ConfigMigration {
23 pub fn run(settings: &mut AppSettings) -> Result<bool, SettingsError> {
26 let mut migrated = false;
27
28 if let Some(ref mut wc) = settings.workspace_config
30 && !wc.global.current_workspace.is_empty()
31 && !wc.workspaces.contains_key(&wc.global.current_workspace)
32 {
33 let first = wc.workspaces.keys().next().cloned().unwrap_or_default();
34 tracing::warn!(
35 "current_workspace '{}' does not exist, resetting to '{}'",
36 wc.global.current_workspace,
37 first
38 );
39 wc.global.current_workspace = first;
40 migrated = true;
41 }
42
43 if settings.config_version < 4 {
46 Self::migrate_to_v4(settings);
47 migrated = true;
48 }
49
50 if settings.config_version < 5 {
53 Self::migrate_to_v5(settings);
54 migrated = true;
55 }
56
57 if settings.config_version < 6 {
60 Self::migrate_to_v6(settings);
61 migrated = true;
62 }
63
64 if migrated {
68 settings.config_version = CURRENT_CONFIG_VERSION;
69 }
70
71 Ok(migrated)
72 }
73
74 fn migrate_to_v6(settings: &mut AppSettings) {
79 use crate::keys::KeyBindings;
80 use crate::keys::action_shortcuts::ActionShortcuts;
81 use crate::keys::key_combo::KeyCombo;
82 use crate::keys::key_strike::KeyStrike;
83
84 let ctrl = crate::keys::key_combo::KeyModifiers::new().and_ctrl();
85 let ctrl_shift_p = KeyCombo::new(ctrl.and_shift(), KeyStrike::KeyP);
86 let ctrl_comma = KeyCombo::new(ctrl, KeyStrike::Comma);
87
88 let mut map = settings.key_bindings.to_hashmap();
89 let at_old_default = map
90 .get(&ActionShortcuts::OpenPreferences)
91 .is_some_and(|v| v.as_slice() == [ctrl_shift_p]);
92 let comma_free = !map.values().flatten().any(|c| *c == ctrl_comma);
93 if at_old_default && comma_free {
94 map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_comma]);
95 }
96 settings.key_bindings = KeyBindings::from_hashmap(map);
97 }
98
99 fn migrate_to_v5(settings: &mut AppSettings) {
103 use crate::keys::KeyBindings;
104 use crate::keys::action_shortcuts::ActionShortcuts;
105 use crate::keys::key_combo::KeyCombo;
106 use crate::keys::key_strike::KeyStrike;
107
108 let ctrl = crate::keys::key_combo::KeyModifiers::new().and_ctrl();
109 let ctrl_shift = ctrl.and_shift();
110 let ctrl_p = KeyCombo::new(ctrl, KeyStrike::KeyP);
111 let ctrl_shift_p = KeyCombo::new(ctrl_shift, KeyStrike::KeyP);
112
113 let mut map = settings.key_bindings.to_hashmap();
114 let settings_is_old_default = map
115 .get(&ActionShortcuts::OpenPreferences)
116 .is_some_and(|v| v.as_slice() == [ctrl_p]);
117 let palette_unset_or_old_default = map
118 .get(&ActionShortcuts::OpenCommandPalette)
119 .is_none_or(|v| v.is_empty() || v.as_slice() == [ctrl_shift_p]);
120 if settings_is_old_default && palette_unset_or_old_default {
121 map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_shift_p]);
122 map.insert(ActionShortcuts::OpenCommandPalette, vec![ctrl_p]);
123 }
124 settings.key_bindings = KeyBindings::from_hashmap(map);
125 }
126
127 fn migrate_to_v4(settings: &mut AppSettings) {
132 use crate::keys::KeyBindings;
133 use crate::keys::action_shortcuts::ActionShortcuts;
134 use crate::keys::key_combo::KeyCombo;
135 use crate::keys::key_strike::KeyStrike;
136
137 let ctrl = crate::keys::key_combo::KeyModifiers::new().and_ctrl();
138 let ctrl_g = KeyCombo::new(ctrl, KeyStrike::KeyG);
139 let ctrl_n = KeyCombo::new(ctrl, KeyStrike::KeyN);
140
141 let mut map = settings.key_bindings.to_hashmap();
142 let follow_is_old_default = map
143 .get(&ActionShortcuts::FollowLink)
144 .is_some_and(|v| v.as_slice() == [ctrl_g]);
145 if follow_is_old_default {
146 map.insert(ActionShortcuts::FollowLink, vec![ctrl_n]);
148 map.entry(ActionShortcuts::Leader).or_default().push(ctrl_g);
149 }
150 settings.key_bindings = KeyBindings::from_hashmap(map);
151 }
155}
156
157#[cfg(test)]
158#[allow(clippy::field_reassign_with_default)]
159mod tests {
160 use super::*;
161 use crate::settings::workspace_config::WorkspaceConfig;
162
163 #[test]
164 fn a_config_already_at_the_current_version_is_left_alone() {
165 let mut settings = AppSettings::default();
166 settings.config_version = CURRENT_CONFIG_VERSION;
167 settings.workspace_config = Some(WorkspaceConfig::new_empty());
168
169 let migrated = ConfigMigration::run(&mut settings).unwrap();
170 assert!(!migrated);
171 }
172
173 #[test]
174 fn v4_moves_ctrl_g_from_followlink_to_leader() {
175 use crate::keys::KeyBindings;
176 use crate::keys::action_shortcuts::ActionShortcuts;
177 use crate::keys::key_combo::{KeyCombo, KeyModifiers};
178 use crate::keys::key_strike::KeyStrike;
179
180 let ctrl = KeyModifiers::new().and_ctrl();
181 let ctrl_g = KeyCombo::new(ctrl, KeyStrike::KeyG);
182 let ctrl_n = KeyCombo::new(ctrl, KeyStrike::KeyN);
183
184 let mut settings = AppSettings::default();
186 let mut map = std::collections::HashMap::new();
187 map.insert(ActionShortcuts::FollowLink, vec![ctrl_g]);
188 settings.key_bindings = KeyBindings::from_hashmap(map);
189 settings.config_version = 3;
190
191 assert!(ConfigMigration::run(&mut settings).unwrap());
192 let map = settings.key_bindings.to_hashmap();
193 assert_eq!(map.get(&ActionShortcuts::Leader), Some(&vec![ctrl_g]));
194 assert_eq!(map.get(&ActionShortcuts::FollowLink), Some(&vec![ctrl_n]));
195 assert_eq!(settings.config_version, CURRENT_CONFIG_VERSION);
196 }
197
198 #[test]
199 fn v6_moves_settings_to_ctrl_comma() {
200 use crate::keys::KeyBindings;
201 use crate::keys::action_shortcuts::ActionShortcuts;
202 use crate::keys::key_combo::{KeyCombo, KeyModifiers};
203 use crate::keys::key_strike::KeyStrike;
204
205 let ctrl = KeyModifiers::new().and_ctrl();
206 let ctrl_shift_p = KeyCombo::new(ctrl.and_shift(), KeyStrike::KeyP);
207 let ctrl_comma = KeyCombo::new(ctrl, KeyStrike::Comma);
208
209 let mut settings = AppSettings::default();
210 let mut map = std::collections::HashMap::new();
211 map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_shift_p]);
212 settings.key_bindings = KeyBindings::from_hashmap(map);
213 settings.config_version = 5;
214
215 assert!(ConfigMigration::run(&mut settings).unwrap());
216 let map = settings.key_bindings.to_hashmap();
217 assert_eq!(
218 map.get(&ActionShortcuts::OpenPreferences),
219 Some(&vec![ctrl_comma])
220 );
221 }
222
223 #[test]
224 fn v5_swaps_palette_onto_ctrl_p() {
225 use crate::keys::KeyBindings;
226 use crate::keys::action_shortcuts::ActionShortcuts;
227 use crate::keys::key_combo::{KeyCombo, KeyModifiers};
228 use crate::keys::key_strike::KeyStrike;
229
230 let ctrl = KeyModifiers::new().and_ctrl();
231 let ctrl_p = KeyCombo::new(ctrl, KeyStrike::KeyP);
232 let ctrl_shift_p = KeyCombo::new(ctrl.and_shift(), KeyStrike::KeyP);
233
234 let mut settings = AppSettings::default();
235 let mut map = std::collections::HashMap::new();
236 map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_p]);
237 settings.key_bindings = KeyBindings::from_hashmap(map);
238 settings.config_version = 4;
239
240 assert!(ConfigMigration::run(&mut settings).unwrap());
241 let map = settings.key_bindings.to_hashmap();
242 assert_eq!(
243 map.get(&ActionShortcuts::OpenCommandPalette),
244 Some(&vec![ctrl_p])
245 );
246 let ctrl_comma = KeyCombo::new(ctrl, KeyStrike::Comma);
248 assert_eq!(
249 map.get(&ActionShortcuts::OpenPreferences),
250 Some(&vec![ctrl_comma])
251 );
252 let _ = ctrl_shift_p;
253 }
254
255 #[test]
256 fn v5_leaves_customised_settings_binding_alone() {
257 use crate::keys::KeyBindings;
258 use crate::keys::action_shortcuts::ActionShortcuts;
259 use crate::keys::key_combo::{KeyCombo, KeyModifiers};
260 use crate::keys::key_strike::KeyStrike;
261
262 let ctrl = KeyModifiers::new().and_ctrl();
263 let ctrl_x = KeyCombo::new(ctrl, KeyStrike::KeyX);
264
265 let mut settings = AppSettings::default();
266 let mut map = std::collections::HashMap::new();
267 map.insert(ActionShortcuts::OpenPreferences, vec![ctrl_x]);
268 settings.key_bindings = KeyBindings::from_hashmap(map);
269 settings.config_version = 4;
270
271 ConfigMigration::run(&mut settings).unwrap();
272 let map = settings.key_bindings.to_hashmap();
273 assert_eq!(
274 map.get(&ActionShortcuts::OpenPreferences),
275 Some(&vec![ctrl_x])
276 );
277 }
278
279 #[test]
280 fn v4_leaves_customised_followlink_alone() {
281 use crate::keys::KeyBindings;
282 use crate::keys::action_shortcuts::ActionShortcuts;
283 use crate::keys::key_combo::{KeyCombo, KeyModifiers};
284 use crate::keys::key_strike::KeyStrike;
285
286 let ctrl = KeyModifiers::new().and_ctrl();
287 let ctrl_x = KeyCombo::new(ctrl, KeyStrike::KeyX);
288
289 let mut settings = AppSettings::default();
290 let mut map = std::collections::HashMap::new();
291 map.insert(ActionShortcuts::FollowLink, vec![ctrl_x]);
292 settings.key_bindings = KeyBindings::from_hashmap(map);
293 settings.config_version = 3;
294
295 ConfigMigration::run(&mut settings).unwrap();
296 let map = settings.key_bindings.to_hashmap();
297 assert_eq!(map.get(&ActionShortcuts::FollowLink), Some(&vec![ctrl_x]));
299 assert!(
300 map.get(&ActionShortcuts::Leader)
301 .is_none_or(|v| v.is_empty())
302 );
303 }
304}