fix(bindings): remove legacy binding, replace with newer Key

This commit is contained in:
lalvarezt 2025-07-23 12:05:45 +02:00
parent bf3a22a7cf
commit 75b6d48372
9 changed files with 46 additions and 124 deletions

View File

@ -1,20 +1,16 @@
use crate::{
action::Action, channels::prototypes::ChannelPrototype,
config::KeyBindings, errors::unknown_channel_exit, event::Key,
};
use colored::Colorize; use colored::Colorize;
use rustc_hash::FxHashMap;
use std::{ use std::{
ops::Deref, ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use rustc_hash::FxHashMap;
use tracing::{debug, error}; use tracing::{debug, error};
use walkdir::WalkDir; 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. /// A neat `HashMap` of channel prototypes indexed by their name.
/// ///
/// This is used to store cable channel prototypes throughout the application /// This is used to store cable channel prototypes throughout the application
@ -62,28 +58,9 @@ impl Cable {
.iter() .iter()
.filter_map(|(name, prototype)| { .filter_map(|(name, prototype)| {
if let Some(keybindings) = &prototype.keybindings { if let Some(keybindings) = &prototype.keybindings {
if let Some(binding) = &keybindings.shortcut { keybindings.shortcut.as_ref().map(|key| {
// Convert Binding to Key for new architecture (*key, Action::SwitchToChannel(name.clone()).into())
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
}
} else { } else {
None None
} }
@ -96,13 +73,12 @@ impl Cable {
/// Get a channel prototype's shortcut binding. /// Get a channel prototype's shortcut binding.
/// ///
/// E.g. if the channel is "files" and the shortcut is "F1", /// E.g. if the channel is "files" and the shortcut is "F1",
/// this will return `Some(Binding::SingleKey("F1"))`. /// this will return `Some(Key::F(1))`.
pub fn get_channel_shortcut(&self, channel_name: &str) -> Option<Binding> { pub fn get_channel_shortcut(&self, channel_name: &str) -> Option<Key> {
// Get only what we need, clone at the end
self.get(channel_name) self.get(channel_name)
.and_then(|prototype| prototype.keybindings.as_ref()) .and_then(|prototype| prototype.keybindings.as_ref())
.and_then(|keybindings| keybindings.shortcut.as_ref()) .and_then(|keybindings| keybindings.shortcut.as_ref())
.cloned() .copied()
} }
} }

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
channels::prototypes::Template, config::Binding, channels::prototypes::Template,
event::Key,
screen::result_item::ResultItem, screen::result_item::ResultItem,
}; };
use devicons::FileIcon; use devicons::FileIcon;
@ -172,7 +173,7 @@ impl ResultItem for Entry {
self.match_ranges.as_deref() self.match_ranges.as_deref()
} }
fn shortcut(&self) -> Option<&Binding> { fn shortcut(&self) -> Option<&Key> {
None None
} }

View File

@ -1,6 +1,7 @@
use crate::cli::parse_source_entry_delimiter; use crate::cli::parse_source_entry_delimiter;
use crate::{ use crate::{
config::{Binding, KeyBindings, ui}, config::{KeyBindings, ui},
event::Key,
features::Features, features::Features,
screen::layout::{InputPosition, Orientation}, screen::layout::{InputPosition, Orientation},
}; };
@ -161,7 +162,7 @@ impl CommandSpec {
pub struct ChannelKeyBindings { pub struct ChannelKeyBindings {
/// Optional channel specific shortcut that, when pressed, switches directly to this channel. /// Optional channel specific shortcut that, when pressed, switches directly to this channel.
#[serde(default)] #[serde(default)]
pub shortcut: Option<Binding>, pub shortcut: Option<Key>,
/// Regular action -> binding mappings living at channel level. /// Regular action -> binding mappings living at channel level.
#[serde(flatten)] #[serde(flatten)]
#[serde(default)] #[serde(default)]
@ -169,7 +170,7 @@ pub struct ChannelKeyBindings {
} }
impl ChannelKeyBindings { impl ChannelKeyBindings {
pub fn channel_shortcut(&self) -> Option<&Binding> { pub fn channel_shortcut(&self) -> Option<&Key> {
self.shortcut.as_ref() self.shortcut.as_ref()
} }
} }

View File

@ -4,7 +4,8 @@ use crate::{
entry::into_ranges, entry::into_ranges,
prototypes::{BinaryRequirement, ChannelPrototype}, prototypes::{BinaryRequirement, ChannelPrototype},
}, },
config::{Binding, ui::RemoteControlConfig}, config::ui::RemoteControlConfig,
event::Key,
matcher::{Matcher, config::Config}, matcher::{Matcher, config::Config},
screen::result_item::ResultItem, screen::result_item::ResultItem,
}; };
@ -14,17 +15,17 @@ use devicons::FileIcon;
pub struct CableEntry { pub struct CableEntry {
pub channel_name: String, pub channel_name: String,
pub match_ranges: Option<Vec<(u32, u32)>>, pub match_ranges: Option<Vec<(u32, u32)>>,
pub shortcut: Option<Binding>, pub shortcut: Option<Key>,
pub description: Option<String>, pub description: Option<String>,
pub requirements: Vec<BinaryRequirement>, pub requirements: Vec<BinaryRequirement>,
} }
impl CableEntry { impl CableEntry {
pub fn new(name: String, shortcut: Option<&Binding>) -> Self { pub fn new(name: String, shortcut: Option<&Key>) -> Self {
CableEntry { CableEntry {
channel_name: name, channel_name: name,
match_ranges: None, match_ranges: None,
shortcut: shortcut.cloned(), shortcut: shortcut.copied(),
description: None, description: None,
requirements: Vec::new(), requirements: Vec::new(),
} }
@ -67,7 +68,7 @@ impl ResultItem for CableEntry {
self.match_ranges.as_deref() self.match_ranges.as_deref()
} }
fn shortcut(&self) -> Option<&crate::config::Binding> { fn shortcut(&self) -> Option<&Key> {
self.shortcut.as_ref() self.shortcut.as_ref()
} }
} }

View File

@ -10,28 +10,6 @@ use std::hash::Hash;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::str::FromStr; 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<Key>),
}
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<String> = 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 can map any key type to actions
/// Generic bindings structure that maps any key type to actions. /// Generic bindings structure that maps any key type to actions.

View File

@ -15,7 +15,7 @@ use std::{
use tracing::{debug, warn}; use tracing::{debug, warn};
pub use keybindings::{ pub use keybindings::{
Binding, EventBindings, EventType, KeyBindings, merge_bindings, EventBindings, EventType, KeyBindings, merge_bindings,
}; };
pub use themes::Theme; pub use themes::Theme;
pub use ui::UiConfig; pub use ui::UiConfig;
@ -478,7 +478,7 @@ mod tests {
default_config.shell_integration.keybindings.insert( default_config.shell_integration.keybindings.insert(
"command_history".to_string(), "command_history".to_string(),
Binding::SingleKey(Key::from_str("ctrl-h").unwrap()), Key::from_str("ctrl-h").unwrap(),
); );
default_config.shell_integration.merge_triggers(); default_config.shell_integration.merge_triggers();
@ -570,14 +570,14 @@ mod tests {
let config = Config::new(&config_env, None).unwrap(); let config = Config::new(&config_env, None).unwrap();
let expected: rustc_hash::FxHashMap<String, Binding> = [ let expected: rustc_hash::FxHashMap<String, Key> = [
( (
"command_history".to_string(), "command_history".to_string(),
Binding::SingleKey(Key::from_str("ctrl-[").unwrap()), Key::from_str("ctrl-[").unwrap(),
), ),
( (
"smart_autocomplete".to_string(), "smart_autocomplete".to_string(),
Binding::SingleKey(Key::from_str("ctrl-t").unwrap()), Key::from_str("ctrl-t").unwrap(),
), ),
] ]
.iter() .iter()

View File

@ -1,6 +1,6 @@
use std::hash::Hash; use std::hash::Hash;
use crate::{config::Binding, event::Key, utils::hashmaps}; use crate::{event::Key, utils::hashmaps};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -14,7 +14,7 @@ pub struct ShellIntegrationConfig {
/// {channel: [commands]} /// {channel: [commands]}
pub channel_triggers: FxHashMap<String, Vec<String>>, pub channel_triggers: FxHashMap<String, Vec<String>>,
pub fallback_channel: String, pub fallback_channel: String,
pub keybindings: FxHashMap<String, Binding>, pub keybindings: FxHashMap<String, Key>,
} }
impl Hash for ShellIntegrationConfig { impl Hash for ShellIntegrationConfig {
@ -49,7 +49,7 @@ impl ShellIntegrationConfig {
// (if any), extract the character triggers shell autocomplete // (if any), extract the character triggers shell autocomplete
pub fn get_shell_autocomplete_keybinding_character(&self) -> char { pub fn get_shell_autocomplete_keybinding_character(&self) -> char {
match self.keybindings.get(SMART_AUTOCOMPLETE_CONFIGURATION_KEY) { 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), .unwrap_or(DEFAULT_SHELL_AUTOCOMPLETE_KEY),
None => DEFAULT_SHELL_AUTOCOMPLETE_KEY, None => DEFAULT_SHELL_AUTOCOMPLETE_KEY,
} }
@ -59,21 +59,17 @@ impl ShellIntegrationConfig {
// through tv // through tv
pub fn get_command_history_keybinding_character(&self) -> char { pub fn get_command_history_keybinding_character(&self) -> char {
match self.keybindings.get(COMMAND_HISTORY_CONFIGURATION_KEY) { match self.keybindings.get(COMMAND_HISTORY_CONFIGURATION_KEY) {
Some(binding) => extract_ctrl_char(binding) Some(&key) => {
.unwrap_or(DEFAULT_COMMAND_HISTORY_KEY), extract_ctrl_char(key).unwrap_or(DEFAULT_COMMAND_HISTORY_KEY)
}
None => 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. /// (or CTRL-Space). Returns `None` otherwise.
fn extract_ctrl_char(binding: &Binding) -> Option<char> { fn extract_ctrl_char(key: Key) -> Option<char> {
let key = match binding {
Binding::SingleKey(k) => Some(k),
Binding::MultipleKeys(keys) => keys.first(),
}?;
match key { match key {
Key::Ctrl(c) => Some(c.to_ascii_uppercase()), Key::Ctrl(c) => Some(c.to_ascii_uppercase()),
Key::CtrlSpace => Some(' '), Key::CtrlSpace => Some(' '),

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
action::{Action, Actions}, action::{Action, Actions},
config::{Binding, KeyBindings}, config::KeyBindings,
television::Mode, television::Mode,
}; };
use std::fmt::Display; 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<String> {
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 /// Extract keys for a single action from the new Key->Action keybindings format
pub fn find_keys_for_action( pub fn find_keys_for_action(
keybindings: &KeyBindings, keybindings: &KeyBindings,

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
config::Binding, event::Key,
screen::{ screen::{
colors::ResultsColorscheme, colors::ResultsColorscheme,
constants::{DESELECTED_SYMBOL, POINTER_SYMBOL, SELECTED_SYMBOL}, constants::{DESELECTED_SYMBOL, POINTER_SYMBOL, SELECTED_SYMBOL},
@ -40,7 +40,7 @@ pub trait ResultItem {
} }
/// Optional shortcut binding shown after the name (remote-control entries). /// Optional shortcut binding shown after the name (remote-control entries).
fn shortcut(&self) -> Option<&Binding> { fn shortcut(&self) -> Option<&Key> {
None None
} }
@ -82,13 +82,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
let shortcut_extra: u16 = item let shortcut_extra: u16 = item
.shortcut() .shortcut()
.map(|b| match b { .map(|k| 2 + k.to_string().len() as u16) // space + key
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(),
})
.unwrap_or(0); .unwrap_or(0);
let item_max_width = area_width let item_max_width = area_width
@ -126,26 +120,13 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
} }
// Show shortcut if present. // Show shortcut if present.
if let Some(binding) = item.shortcut() { if let Some(key) = item.shortcut() {
spans.push(Span::raw(" ")); 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( spans.push(Span::styled(
k.to_string(), key.to_string(),
Style::default().fg(match_fg), Style::default().fg(match_fg),
)); ));
} }
}
}
}
Line::from(spans) Line::from(spans)
} }