From 75b6d48372161009ad47bfb1ad394f7ba049bea4 Mon Sep 17 00:00:00 2001 From: lalvarezt Date: Wed, 23 Jul 2025 12:05:45 +0200 Subject: [PATCH] fix(bindings): remove legacy binding, replace with newer Key --- television/cable.rs | 46 ++++++-------------------- television/channels/entry.rs | 5 +-- television/channels/prototypes.rs | 7 ++-- television/channels/remote_control.rs | 11 +++--- television/config/keybindings.rs | 22 ------------ television/config/mod.rs | 10 +++--- television/config/shell_integration.rs | 20 +++++------ television/screen/keybindings.rs | 14 +------- television/screen/result_item.rs | 35 +++++--------------- 9 files changed, 46 insertions(+), 124 deletions(-) diff --git a/television/cable.rs b/television/cable.rs index 4f8742a..6177c2c 100644 --- a/television/cable.rs +++ b/television/cable.rs @@ -1,20 +1,16 @@ +use crate::{ + action::Action, channels::prototypes::ChannelPrototype, + config::KeyBindings, errors::unknown_channel_exit, event::Key, +}; use colored::Colorize; +use rustc_hash::FxHashMap; use std::{ ops::Deref, path::{Path, PathBuf}, }; - -use rustc_hash::FxHashMap; use tracing::{debug, error}; use walkdir::WalkDir; -use crate::{ - action::Action, - channels::prototypes::ChannelPrototype, - config::{Binding, KeyBindings}, - errors::unknown_channel_exit, -}; - /// A neat `HashMap` of channel prototypes indexed by their name. /// /// This is used to store cable channel prototypes throughout the application @@ -62,28 +58,9 @@ impl Cable { .iter() .filter_map(|(name, prototype)| { if let Some(keybindings) = &prototype.keybindings { - if let Some(binding) = &keybindings.shortcut { - // Convert Binding to Key for new architecture - match binding { - Binding::SingleKey(key) => Some(( - *key, - Action::SwitchToChannel(name.clone()).into(), - )), - // For multiple keys, use the first one - Binding::MultipleKeys(keys) - if !keys.is_empty() => - { - Some(( - keys[0], - Action::SwitchToChannel(name.clone()) - .into(), - )) - } - Binding::MultipleKeys(_) => None, - } - } else { - None - } + keybindings.shortcut.as_ref().map(|key| { + (*key, Action::SwitchToChannel(name.clone()).into()) + }) } else { None } @@ -96,13 +73,12 @@ impl Cable { /// Get a channel prototype's shortcut binding. /// /// E.g. if the channel is "files" and the shortcut is "F1", - /// this will return `Some(Binding::SingleKey("F1"))`. - pub fn get_channel_shortcut(&self, channel_name: &str) -> Option { - // Get only what we need, clone at the end + /// this will return `Some(Key::F(1))`. + pub fn get_channel_shortcut(&self, channel_name: &str) -> Option { self.get(channel_name) .and_then(|prototype| prototype.keybindings.as_ref()) .and_then(|keybindings| keybindings.shortcut.as_ref()) - .cloned() + .copied() } } diff --git a/television/channels/entry.rs b/television/channels/entry.rs index 541ca7b..4c3d455 100644 --- a/television/channels/entry.rs +++ b/television/channels/entry.rs @@ -1,5 +1,6 @@ use crate::{ - channels::prototypes::Template, config::Binding, + channels::prototypes::Template, + event::Key, screen::result_item::ResultItem, }; use devicons::FileIcon; @@ -172,7 +173,7 @@ impl ResultItem for Entry { self.match_ranges.as_deref() } - fn shortcut(&self) -> Option<&Binding> { + fn shortcut(&self) -> Option<&Key> { None } diff --git a/television/channels/prototypes.rs b/television/channels/prototypes.rs index 9e27c6e..55f7ad4 100644 --- a/television/channels/prototypes.rs +++ b/television/channels/prototypes.rs @@ -1,6 +1,7 @@ use crate::cli::parse_source_entry_delimiter; use crate::{ - config::{Binding, KeyBindings, ui}, + config::{KeyBindings, ui}, + event::Key, features::Features, screen::layout::{InputPosition, Orientation}, }; @@ -161,7 +162,7 @@ impl CommandSpec { pub struct ChannelKeyBindings { /// Optional channel specific shortcut that, when pressed, switches directly to this channel. #[serde(default)] - pub shortcut: Option, + pub shortcut: Option, /// Regular action -> binding mappings living at channel level. #[serde(flatten)] #[serde(default)] @@ -169,7 +170,7 @@ pub struct ChannelKeyBindings { } impl ChannelKeyBindings { - pub fn channel_shortcut(&self) -> Option<&Binding> { + pub fn channel_shortcut(&self) -> Option<&Key> { self.shortcut.as_ref() } } diff --git a/television/channels/remote_control.rs b/television/channels/remote_control.rs index 050cc67..f927b16 100644 --- a/television/channels/remote_control.rs +++ b/television/channels/remote_control.rs @@ -4,7 +4,8 @@ use crate::{ entry::into_ranges, prototypes::{BinaryRequirement, ChannelPrototype}, }, - config::{Binding, ui::RemoteControlConfig}, + config::ui::RemoteControlConfig, + event::Key, matcher::{Matcher, config::Config}, screen::result_item::ResultItem, }; @@ -14,17 +15,17 @@ use devicons::FileIcon; pub struct CableEntry { pub channel_name: String, pub match_ranges: Option>, - pub shortcut: Option, + pub shortcut: Option, pub description: Option, pub requirements: Vec, } impl CableEntry { - pub fn new(name: String, shortcut: Option<&Binding>) -> Self { + pub fn new(name: String, shortcut: Option<&Key>) -> Self { CableEntry { channel_name: name, match_ranges: None, - shortcut: shortcut.cloned(), + shortcut: shortcut.copied(), description: None, requirements: Vec::new(), } @@ -67,7 +68,7 @@ impl ResultItem for CableEntry { self.match_ranges.as_deref() } - fn shortcut(&self) -> Option<&crate::config::Binding> { + fn shortcut(&self) -> Option<&Key> { self.shortcut.as_ref() } } diff --git a/television/config/keybindings.rs b/television/config/keybindings.rs index 1778453..e77d397 100644 --- a/television/config/keybindings.rs +++ b/television/config/keybindings.rs @@ -10,28 +10,6 @@ use std::hash::Hash; use std::ops::{Deref, DerefMut}; use std::str::FromStr; -// Legacy binding structure for backward compatibility with shell integration -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Hash)] -#[serde(untagged)] -pub enum Binding { - SingleKey(Key), - MultipleKeys(Vec), -} - -impl Display for Binding { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Binding::SingleKey(key) => write!(f, "{key}"), - Binding::MultipleKeys(keys) => { - let keys_str: Vec = keys - .iter() - .map(std::string::ToString::to_string) - .collect(); - write!(f, "{}", keys_str.join(", ")) - } - } - } -} /// Generic bindings structure that can map any key type to actions /// Generic bindings structure that maps any key type to actions. diff --git a/television/config/mod.rs b/television/config/mod.rs index 69c2543..359604b 100644 --- a/television/config/mod.rs +++ b/television/config/mod.rs @@ -15,7 +15,7 @@ use std::{ use tracing::{debug, warn}; pub use keybindings::{ - Binding, EventBindings, EventType, KeyBindings, merge_bindings, + EventBindings, EventType, KeyBindings, merge_bindings, }; pub use themes::Theme; pub use ui::UiConfig; @@ -478,7 +478,7 @@ mod tests { default_config.shell_integration.keybindings.insert( "command_history".to_string(), - Binding::SingleKey(Key::from_str("ctrl-h").unwrap()), + Key::from_str("ctrl-h").unwrap(), ); default_config.shell_integration.merge_triggers(); @@ -570,14 +570,14 @@ mod tests { let config = Config::new(&config_env, None).unwrap(); - let expected: rustc_hash::FxHashMap = [ + let expected: rustc_hash::FxHashMap = [ ( "command_history".to_string(), - Binding::SingleKey(Key::from_str("ctrl-[").unwrap()), + Key::from_str("ctrl-[").unwrap(), ), ( "smart_autocomplete".to_string(), - Binding::SingleKey(Key::from_str("ctrl-t").unwrap()), + Key::from_str("ctrl-t").unwrap(), ), ] .iter() diff --git a/television/config/shell_integration.rs b/television/config/shell_integration.rs index dab240e..b971c36 100644 --- a/television/config/shell_integration.rs +++ b/television/config/shell_integration.rs @@ -1,6 +1,6 @@ use std::hash::Hash; -use crate::{config::Binding, event::Key, utils::hashmaps}; +use crate::{event::Key, utils::hashmaps}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; @@ -14,7 +14,7 @@ pub struct ShellIntegrationConfig { /// {channel: [commands]} pub channel_triggers: FxHashMap>, pub fallback_channel: String, - pub keybindings: FxHashMap, + pub keybindings: FxHashMap, } impl Hash for ShellIntegrationConfig { @@ -49,7 +49,7 @@ impl ShellIntegrationConfig { // (if any), extract the character triggers shell autocomplete pub fn get_shell_autocomplete_keybinding_character(&self) -> char { match self.keybindings.get(SMART_AUTOCOMPLETE_CONFIGURATION_KEY) { - Some(binding) => extract_ctrl_char(binding) + Some(&key) => extract_ctrl_char(key) .unwrap_or(DEFAULT_SHELL_AUTOCOMPLETE_KEY), None => DEFAULT_SHELL_AUTOCOMPLETE_KEY, } @@ -59,21 +59,17 @@ impl ShellIntegrationConfig { // through tv pub fn get_command_history_keybinding_character(&self) -> char { match self.keybindings.get(COMMAND_HISTORY_CONFIGURATION_KEY) { - Some(binding) => extract_ctrl_char(binding) - .unwrap_or(DEFAULT_COMMAND_HISTORY_KEY), + Some(&key) => { + extract_ctrl_char(key).unwrap_or(DEFAULT_COMMAND_HISTORY_KEY) + } None => DEFAULT_COMMAND_HISTORY_KEY, } } } -/// Extract an upper-case character from a `Binding` if it is a single CTRL key +/// Extract an upper-case character from a `Key` if it is a single CTRL key /// (or CTRL-Space). Returns `None` otherwise. -fn extract_ctrl_char(binding: &Binding) -> Option { - let key = match binding { - Binding::SingleKey(k) => Some(k), - Binding::MultipleKeys(keys) => keys.first(), - }?; - +fn extract_ctrl_char(key: Key) -> Option { match key { Key::Ctrl(c) => Some(c.to_ascii_uppercase()), Key::CtrlSpace => Some(' '), diff --git a/television/screen/keybindings.rs b/television/screen/keybindings.rs index a27d57d..01f31f0 100644 --- a/television/screen/keybindings.rs +++ b/television/screen/keybindings.rs @@ -1,6 +1,6 @@ use crate::{ action::{Action, Actions}, - config::{Binding, KeyBindings}, + config::KeyBindings, television::Mode, }; use std::fmt::Display; @@ -151,18 +151,6 @@ impl ActionMapping { } } -/// Unified key extraction function that works for both systems -pub fn extract_keys_from_binding(binding: &Binding) -> Vec { - match binding { - Binding::SingleKey(key) => { - vec![key.to_string()] - } - Binding::MultipleKeys(keys) => { - keys.iter().map(ToString::to_string).collect() - } - } -} - /// Extract keys for a single action from the new Key->Action keybindings format pub fn find_keys_for_action( keybindings: &KeyBindings, diff --git a/television/screen/result_item.rs b/television/screen/result_item.rs index 1ef749f..0cda15e 100644 --- a/television/screen/result_item.rs +++ b/television/screen/result_item.rs @@ -1,5 +1,5 @@ use crate::{ - config::Binding, + event::Key, screen::{ colors::ResultsColorscheme, constants::{DESELECTED_SYMBOL, POINTER_SYMBOL, SELECTED_SYMBOL}, @@ -40,7 +40,7 @@ pub trait ResultItem { } /// Optional shortcut binding shown after the name (remote-control entries). - fn shortcut(&self) -> Option<&Binding> { + fn shortcut(&self) -> Option<&Key> { None } @@ -82,13 +82,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>( let shortcut_extra: u16 = item .shortcut() - .map(|b| match b { - Binding::SingleKey(k) => 2 + k.to_string().len() as u16, // space + key - Binding::MultipleKeys(keys) => keys - .iter() - .map(|k| 1 + k.to_string().len() as u16) // space + key - .sum(), - }) + .map(|k| 2 + k.to_string().len() as u16) // space + key .unwrap_or(0); let item_max_width = area_width @@ -126,25 +120,12 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>( } // Show shortcut if present. - if let Some(binding) = item.shortcut() { + if let Some(key) = item.shortcut() { spans.push(Span::raw(" ")); - match binding { - Binding::SingleKey(k) => spans.push(Span::styled( - k.to_string(), - Style::default().fg(match_fg), - )), - Binding::MultipleKeys(keys) => { - for (i, k) in keys.iter().enumerate() { - if i > 0 { - spans.push(Span::raw(" ")); - } - spans.push(Span::styled( - k.to_string(), - Style::default().fg(match_fg), - )); - } - } - } + spans.push(Span::styled( + key.to_string(), + Style::default().fg(match_fg), + )); } Line::from(spans)