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 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<Binding> {
// 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<Key> {
self.get(channel_name)
.and_then(|prototype| prototype.keybindings.as_ref())
.and_then(|keybindings| keybindings.shortcut.as_ref())
.cloned()
.copied()
}
}

View File

@ -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
}

View File

@ -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<Binding>,
pub shortcut: Option<Key>,
/// 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()
}
}

View File

@ -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<Vec<(u32, u32)>>,
pub shortcut: Option<Binding>,
pub shortcut: Option<Key>,
pub description: Option<String>,
pub requirements: Vec<BinaryRequirement>,
}
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()
}
}

View File

@ -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<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 maps any key type to actions.

View File

@ -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<String, Binding> = [
let expected: rustc_hash::FxHashMap<String, Key> = [
(
"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()

View File

@ -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<String, Vec<String>>,
pub fallback_channel: String,
pub keybindings: FxHashMap<String, Binding>,
pub keybindings: FxHashMap<String, Key>,
}
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<char> {
let key = match binding {
Binding::SingleKey(k) => Some(k),
Binding::MultipleKeys(keys) => keys.first(),
}?;
fn extract_ctrl_char(key: Key) -> Option<char> {
match key {
Key::Ctrl(c) => Some(c.to_ascii_uppercase()),
Key::CtrlSpace => Some(' '),

View File

@ -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<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
pub fn find_keys_for_action(
keybindings: &KeyBindings,

View File

@ -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,26 +120,13 @@ 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(),
key.to_string(),
Style::default().fg(match_fg),
));
}
}
}
}
Line::from(spans)
}