diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-22 23:36:43 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-22 23:36:43 +0300 |
| commit | 088922dfabca152047c8f39f660893b3b19dbe2d (patch) | |
| tree | 81e4b960e0417f4fc0fcb368fb093511f26670e7 /src/config.rs | |
| parent | d7ae163f8888cd9bf13e5a34e4d37f9518f76c01 (diff) | |
fix: let user keybinds displace defaults
refs #747
Diffstat (limited to 'src/config.rs')
| -rw-r--r-- | src/config.rs | 170 |
1 files changed, 158 insertions, 12 deletions
diff --git a/src/config.rs b/src/config.rs index 003178ab..12e9cf1c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,9 +19,9 @@ pub use self::{ }, model::{ validated_sidebar_bounds, AgentPanelSortConfig, Config, ConfigReloadReport, - ConfigReloadStatus, KeysConfig, NewTerminalCwdConfig, ShellModeConfig, - ToastClipboardPosition, ToastConfig, ToastDelivery, ToastHerdrPosition, - UpdateChannelConfig, MAX_TOAST_DELAY_SECONDS, + ConfigReloadStatus, NewTerminalCwdConfig, ShellModeConfig, ToastClipboardPosition, + ToastConfig, ToastDelivery, ToastHerdrPosition, UpdateChannelConfig, + MAX_TOAST_DELAY_SECONDS, }, sound::SoundConfig, theme::{parse_color, CustomThemeColors, ThemeConfig}, @@ -80,26 +80,32 @@ impl Config { }) } + #[cfg(test)] pub fn live_keybinds(&self) -> Result<LiveKeybindConfig, Vec<String>> { + self.live_keybinds_with_diagnostics() + .map(|(live, _diagnostics)| live) + } + + pub(crate) fn live_keybinds_with_diagnostics( + &self, + ) -> Result<(LiveKeybindConfig, Vec<String>), Vec<String>> { let (prefix_diag, prefix, keybind_diags, keybinds) = self.validated_keybinds(); - let diagnostics: Vec<String> = prefix_diag.into_iter().chain(keybind_diags).collect(); - if diagnostics.is_empty() { - Ok(LiveKeybindConfig { prefix, keybinds }) + if let Some(prefix_diag) = prefix_diag { + Err(std::iter::once(prefix_diag).chain(keybind_diags).collect()) } else { - Err(diagnostics) + Ok((LiveKeybindConfig { prefix, keybinds }, keybind_diags)) } } pub(crate) fn local_keybindings_profile_toml(&self) -> Result<String, toml::ser::Error> { - let mut keys = self.keys.clone(); - keys.command.clear(); - #[derive(serde::Serialize)] struct KeysProfile { - keys: KeysConfig, + keys: model::KeysConfigOverlay, } - toml::to_string_pretty(&KeysProfile { keys }) + toml::to_string_pretty(&KeysProfile { + keys: self.keys.local_profile(&self.keybinds()), + }) } } @@ -133,6 +139,146 @@ command = "lazygit" } #[test] + fn local_keybindings_profile_preserves_user_default_provenance() { + let config: Config = toml::from_str( + r#" +[keys] +zoom = "prefix+?" +"#, + ) + .unwrap(); + + let profile = config.local_keybindings_profile_toml().unwrap(); + let round_tripped: Config = toml::from_str(&profile).unwrap(); + + assert!(profile.contains("zoom = \"prefix+?\"")); + assert!(!profile.contains("help = \"prefix+?\"")); + assert!(round_tripped + .keybinds() + .zoom + .bindings + .iter() + .any(|binding| binding.label == "prefix+?")); + assert!(round_tripped.keybinds().help.bindings.is_empty()); + } + + #[test] + fn local_keybindings_profile_omits_default_displaced_by_user_prefix() { + let config: Config = toml::from_str( + r#" +[keys] +prefix = "n" +"#, + ) + .unwrap(); + + let profile = config.local_keybindings_profile_toml().unwrap(); + let round_tripped: Config = toml::from_str(&profile).unwrap(); + + assert!(profile.contains("prefix = \"n\"")); + assert!(!profile.contains("next_tab = \"prefix+n\"")); + assert!(round_tripped.keybinds().next_tab.bindings.is_empty()); + } + + #[test] + fn local_keybindings_profile_preserves_legacy_indexed_tab_source() { + let config: Config = toml::from_str( + r#" +[keys.indexed] +tabs = "ctrl" +"#, + ) + .unwrap(); + + let profile = config.local_keybindings_profile_toml().unwrap(); + let round_tripped: Config = toml::from_str(&profile).unwrap(); + let keybinds = round_tripped.keybinds(); + let switch_tab_labels: Vec<_> = keybinds + .switch_tab + .iter() + .map(|binding| binding.label.as_str()) + .collect(); + + assert!(profile.contains("[keys.indexed]")); + assert!(profile.contains("tabs = \"ctrl\"")); + assert!(!profile.contains("switch_tab = \"prefix+1..9\"")); + assert_eq!(switch_tab_labels.len(), 9); + assert!(switch_tab_labels + .iter() + .all(|label| label.starts_with("ctrl+"))); + } + + #[test] + fn local_keybindings_profile_keeps_invalid_legacy_indexed_default_disabled() { + let config: Config = toml::from_str( + r#" +[keys.indexed] +tabs = "bogus" +"#, + ) + .unwrap(); + + let profile = config.local_keybindings_profile_toml().unwrap(); + let round_tripped: Config = toml::from_str(&profile).unwrap(); + + assert!(profile.contains("[keys.indexed]")); + assert!(profile.contains("tabs = \"bogus\"")); + assert!(!profile.contains("switch_tab = \"prefix+1..9\"")); + assert!(round_tripped.keybinds().switch_tab.is_empty()); + } + + #[test] + fn local_keybindings_profile_keeps_default_displaced_by_omitted_command_disabled() { + let config: Config = toml::from_str( + r#" +[[keys.command]] +key = "prefix+n" +command = "echo next" +"#, + ) + .unwrap(); + + let profile = config.local_keybindings_profile_toml().unwrap(); + let round_tripped: Config = toml::from_str(&profile).unwrap(); + + assert!(!profile.contains("[[keys.command]]")); + assert!(!profile.contains("command =")); + assert!(profile.contains("next_tab = \"\"")); + assert!(round_tripped.keybinds().next_tab.bindings.is_empty()); + } + + #[test] + fn local_keybindings_profile_preserves_partially_displaced_indexed_default() { + let config: Config = toml::from_str( + r#" +[[keys.command]] +key = "prefix+1" +command = "echo one" +"#, + ) + .unwrap(); + + let profile = config.local_keybindings_profile_toml().unwrap(); + let round_tripped: Config = toml::from_str(&profile).unwrap(); + let keybinds = round_tripped.keybinds(); + let switch_tab_labels: Vec<_> = keybinds + .switch_tab + .iter() + .map(|binding| binding.label.as_str()) + .collect(); + + assert!(!profile.contains("[[keys.command]]")); + assert!(!profile.contains("switch_tab = \"prefix+1..9\"")); + assert!(profile.contains("\"prefix+2\"")); + assert!(profile.contains("\"prefix+9\"")); + assert!(!switch_tab_labels.contains(&"prefix+1")); + assert_eq!(switch_tab_labels.len(), 8); + assert!(switch_tab_labels + .iter() + .all(|label| label.starts_with("prefix+"))); + } + + #[test] fn remote_image_paste_key_defaults_to_ctrl_v() { let config = Config::default(); assert_eq!( |
