mirror of
https://github.com/alexpasmantier/television.git
synced 2025-07-28 13:51:41 +00:00
docs(bindings): added docs
This commit is contained in:
parent
78cc303003
commit
bf3a22a7cf
@ -117,16 +117,78 @@ pub enum Action {
|
||||
MouseClickAt(u16, u16),
|
||||
}
|
||||
|
||||
/// Container for one or more actions that can be executed together.
|
||||
///
|
||||
/// This enum enables binding single keys to multiple actions, allowing for
|
||||
/// complex behaviors triggered by a single key press. It supports both
|
||||
/// single action bindings (for backward compatibility) and multiple action
|
||||
/// sequences.
|
||||
///
|
||||
/// # Variants
|
||||
///
|
||||
/// - `Single(Action)` - A single action binding
|
||||
/// - `Multiple(Vec<Action>)` - Multiple actions executed in sequence
|
||||
///
|
||||
/// # Configuration Examples
|
||||
///
|
||||
/// ```toml
|
||||
/// # Single action (traditional)
|
||||
/// esc = "quit"
|
||||
///
|
||||
/// # Multiple actions (new feature)
|
||||
/// "ctrl-r" = ["reload_source", "copy_entry_to_clipboard"]
|
||||
/// ```
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// // Single action
|
||||
/// let single = Actions::Single(Action::Quit);
|
||||
/// assert_eq!(single.as_slice(), &[Action::Quit]);
|
||||
///
|
||||
/// // Multiple actions
|
||||
/// let multiple = Actions::Multiple(vec![Action::ReloadSource, Action::Quit]);
|
||||
/// assert_eq!(multiple.as_slice(), &[Action::ReloadSource, Action::Quit]);
|
||||
///
|
||||
/// // Convert to vector for execution
|
||||
/// let actions_vec = multiple.into_vec();
|
||||
/// assert_eq!(actions_vec, vec![Action::ReloadSource, Action::Quit]);
|
||||
/// ```
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord,
|
||||
)]
|
||||
#[serde(untagged)]
|
||||
pub enum Actions {
|
||||
/// A single action binding
|
||||
Single(Action),
|
||||
/// Multiple actions executed in sequence
|
||||
Multiple(Vec<Action>),
|
||||
}
|
||||
|
||||
impl Actions {
|
||||
/// Converts the `Actions` into a `Vec<Action>` for execution.
|
||||
///
|
||||
/// This method consumes the `Actions` and returns a vector containing
|
||||
/// all actions to be executed. For `Single`, it returns a vector with
|
||||
/// one element. For `Multiple`, it returns the contained vector.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Vec<Action>` containing all actions to execute.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let single = Actions::Single(Action::Quit);
|
||||
/// assert_eq!(single.into_vec(), vec![Action::Quit]);
|
||||
///
|
||||
/// let multiple = Actions::Multiple(vec![Action::ReloadSource, Action::Quit]);
|
||||
/// assert_eq!(multiple.into_vec(), vec![Action::ReloadSource, Action::Quit]);
|
||||
/// ```
|
||||
pub fn into_vec(self) -> Vec<Action> {
|
||||
match self {
|
||||
Actions::Single(action) => vec![action],
|
||||
@ -134,6 +196,26 @@ impl Actions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a slice view of the actions without consuming the `Actions`.
|
||||
///
|
||||
/// This method provides efficient access to the contained actions as a slice,
|
||||
/// useful for iteration and inspection without taking ownership.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `&[Action]` slice containing all actions.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let single = Actions::Single(Action::Quit);
|
||||
/// assert_eq!(single.as_slice(), &[Action::Quit]);
|
||||
///
|
||||
/// let multiple = Actions::Multiple(vec![Action::ReloadSource, Action::Quit]);
|
||||
/// assert_eq!(multiple.as_slice(), &[Action::ReloadSource, Action::Quit]);
|
||||
/// ```
|
||||
pub fn as_slice(&self) -> &[Action] {
|
||||
match self {
|
||||
Actions::Single(action) => std::slice::from_ref(action),
|
||||
@ -143,12 +225,54 @@ impl Actions {
|
||||
}
|
||||
|
||||
impl From<Action> for Actions {
|
||||
/// Converts a single `Action` into `Actions::Single`.
|
||||
///
|
||||
/// This conversion allows seamless use of single actions where
|
||||
/// `Actions` is expected, maintaining backward compatibility.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let actions: Actions = Action::Quit.into();
|
||||
/// assert_eq!(actions, Actions::Single(Action::Quit));
|
||||
/// ```
|
||||
fn from(action: Action) -> Self {
|
||||
Actions::Single(action)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Action>> for Actions {
|
||||
/// Converts a `Vec<Action>` into `Actions`.
|
||||
///
|
||||
/// This conversion optimizes single-element vectors into `Actions::Single`
|
||||
/// for efficiency, while multi-element vectors become `Actions::Multiple`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `actions` - Vector of actions to convert
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Actions::Single` if the vector has exactly one element
|
||||
/// - `Actions::Multiple` if the vector has zero or multiple elements
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// // Single element becomes Single
|
||||
/// let single_vec = vec![Action::Quit];
|
||||
/// let actions: Actions = single_vec.into();
|
||||
/// assert_eq!(actions, Actions::Single(Action::Quit));
|
||||
///
|
||||
/// // Multiple elements become Multiple
|
||||
/// let multi_vec = vec![Action::ReloadSource, Action::Quit];
|
||||
/// let actions: Actions = multi_vec.into();
|
||||
/// assert!(matches!(actions, Actions::Multiple(_)));
|
||||
/// ```
|
||||
fn from(actions: Vec<Action>) -> Self {
|
||||
if actions.len() == 1 {
|
||||
Actions::Single(actions.into_iter().next().unwrap())
|
||||
@ -159,6 +283,25 @@ impl From<Vec<Action>> for Actions {
|
||||
}
|
||||
|
||||
impl Display for Action {
|
||||
/// Formats the action as its string representation for configuration files.
|
||||
///
|
||||
/// This implementation provides the `snake_case` string representation of each
|
||||
/// action as used in TOML configuration files. The output matches the
|
||||
/// `#[serde(rename_all = "snake_case")]` serialization format.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The `snake_case` string representation of the action.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// assert_eq!(Action::Quit.to_string(), "quit");
|
||||
/// assert_eq!(Action::SelectNextEntry.to_string(), "select_next_entry");
|
||||
/// assert_eq!(Action::TogglePreview.to_string(), "toggle_preview");
|
||||
/// ```
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Action::AddInputChar(_) => write!(f, "add_input_char"),
|
||||
|
@ -34,6 +34,30 @@ impl Display for Binding {
|
||||
}
|
||||
|
||||
/// Generic bindings structure that can map any key type to actions
|
||||
/// Generic bindings structure that maps any key type to actions.
|
||||
///
|
||||
/// This is the core structure for storing key/event bindings in Television.
|
||||
/// It provides a flexible mapping system that can work with different key types
|
||||
/// (keyboard keys, mouse events, etc.) and supports serialization to/from TOML.
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `K` - The key type (must implement `Display`, `FromStr`, `Eq`, `Hash`)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::config::keybindings::{Bindings, KeyBindings};
|
||||
/// use television::event::Key;
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// // Create new empty bindings
|
||||
/// let mut bindings = KeyBindings::new();
|
||||
///
|
||||
/// // Add a binding
|
||||
/// bindings.insert(Key::Enter, Action::ConfirmSelection.into());
|
||||
/// assert_eq!(bindings.len(), 1);
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Bindings<K>
|
||||
where
|
||||
@ -53,6 +77,20 @@ where
|
||||
K: Display + FromStr + Eq + Hash,
|
||||
K::Err: Display,
|
||||
{
|
||||
/// Creates a new empty bindings collection.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new `Bindings` instance with no key mappings.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::config::keybindings::KeyBindings;
|
||||
///
|
||||
/// let bindings = KeyBindings::new();
|
||||
/// assert!(bindings.is_empty());
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Bindings {
|
||||
bindings: FxHashMap::default(),
|
||||
@ -60,16 +98,83 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of keybindings that maps keys directly to actions.
|
||||
/// A set of keybindings that maps keyboard keys directly to actions.
|
||||
///
|
||||
/// This struct represents the new architecture where keybindings are structured as
|
||||
/// Key -> Action mappings in the configuration file. This eliminates the need for
|
||||
/// runtime inversion and provides better discoverability.
|
||||
/// This type alias represents the primary keybinding system in Television, where
|
||||
/// keyboard keys are mapped directly to actions.
|
||||
///
|
||||
/// # Features
|
||||
///
|
||||
/// - Direct key-to-action mapping
|
||||
/// - Support for single and multiple actions per key
|
||||
/// - Key unbinding support (setting to `false`)
|
||||
/// - Modifier key combinations (Ctrl, Alt, Shift, Super/Cmd)
|
||||
///
|
||||
/// # Configuration Format
|
||||
///
|
||||
/// ```toml
|
||||
/// # Single action
|
||||
/// esc = "quit"
|
||||
/// enter = "confirm_selection"
|
||||
///
|
||||
/// # Multiple actions
|
||||
/// "ctrl-s" = ["reload_source", "copy_entry_to_clipboard"]
|
||||
///
|
||||
/// # Unbind a key
|
||||
/// "ctrl-c" = false
|
||||
/// ```
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::config::keybindings::KeyBindings;
|
||||
/// use television::event::Key;
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// let mut bindings = KeyBindings::new();
|
||||
/// bindings.insert(Key::Enter, Action::ConfirmSelection.into());
|
||||
/// bindings.insert(Key::Esc, Action::Quit.into());
|
||||
/// assert_eq!(bindings.len(), 2);
|
||||
/// ```
|
||||
pub type KeyBindings = Bindings<Key>;
|
||||
|
||||
/// Types of events that can be bound to actions.
|
||||
///
|
||||
/// This enum defines the various non-keyboard events that can trigger actions
|
||||
/// in Television, such as mouse events and terminal resize events.
|
||||
///
|
||||
/// # Variants
|
||||
///
|
||||
/// - `MouseClick` - Mouse button click events
|
||||
/// - `MouseScrollUp` - Mouse wheel scroll up
|
||||
/// - `MouseScrollDown` - Mouse wheel scroll down
|
||||
/// - `Resize` - Terminal window resize events
|
||||
/// - `Custom(String)` - Custom event types for extensibility
|
||||
///
|
||||
/// # Configuration
|
||||
///
|
||||
/// Events use kebab-case naming in TOML configuration:
|
||||
///
|
||||
/// ```toml
|
||||
/// mouse-click = "confirm_selection"
|
||||
/// mouse-scroll-up = "scroll_preview_up"
|
||||
/// resize = "refresh_layout"
|
||||
/// ```
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::config::keybindings::EventType;
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// let event = EventType::from_str("mouse-click").unwrap();
|
||||
/// assert_eq!(event, EventType::MouseClick);
|
||||
///
|
||||
/// let custom = EventType::Custom("my-event".to_string());
|
||||
/// assert_eq!(custom.to_string(), "my-event");
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// Types of events that can be bound to actions
|
||||
pub enum EventType {
|
||||
MouseClick,
|
||||
MouseScrollUp,
|
||||
@ -114,7 +219,36 @@ impl Display for EventType {
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of event bindings that maps events to actions.
|
||||
/// A set of event bindings that maps non-keyboard events to actions.
|
||||
///
|
||||
/// This type alias provides bindings for mouse events, resize events,
|
||||
/// and other non-keyboard interactions. It uses the same underlying
|
||||
/// `Bindings` structure as `KeyBindings` but for `EventType` instead of `Key`.
|
||||
///
|
||||
/// # Default Bindings
|
||||
///
|
||||
/// - `mouse-scroll-up` → `scroll_preview_up`
|
||||
/// - `mouse-scroll-down` → `scroll_preview_down`
|
||||
///
|
||||
/// # Configuration Example
|
||||
///
|
||||
/// ```toml
|
||||
/// [event-bindings]
|
||||
/// mouse-click = "confirm_selection"
|
||||
/// mouse-scroll-up = "scroll_preview_up"
|
||||
/// resize = "refresh_layout"
|
||||
/// ```
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::config::keybindings::{EventBindings, EventType};
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// let mut bindings = EventBindings::new();
|
||||
/// bindings.insert(EventType::MouseClick, Action::ConfirmSelection.into());
|
||||
/// assert_eq!(bindings.len(), 1);
|
||||
/// ```
|
||||
pub type EventBindings = Bindings<EventType>;
|
||||
|
||||
impl<K, I> From<I> for Bindings<K>
|
||||
@ -168,7 +302,39 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic merge function for bindings
|
||||
/// Merges two binding collections, with new bindings taking precedence.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bindings` - The base bindings collection (will be consumed)
|
||||
/// * `new_bindings` - The new bindings to merge in (higher precedence)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new `Bindings` collection with merged key mappings.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::config::keybindings::{KeyBindings, merge_bindings};
|
||||
/// use television::event::Key;
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// let base = KeyBindings::from(vec![
|
||||
/// (Key::Enter, Action::ConfirmSelection),
|
||||
/// (Key::Esc, Action::Quit),
|
||||
/// ]);
|
||||
///
|
||||
/// let custom = KeyBindings::from(vec![
|
||||
/// (Key::Esc, Action::NoOp), // Override quit with no-op
|
||||
/// (Key::Tab, Action::ToggleSelectionDown), // Add new binding
|
||||
/// ]);
|
||||
///
|
||||
/// let merged = merge_bindings(base, &custom);
|
||||
/// assert_eq!(merged.get(&Key::Enter), Some(&Action::ConfirmSelection.into()));
|
||||
/// assert_eq!(merged.get(&Key::Esc), Some(&Action::NoOp.into()));
|
||||
/// assert_eq!(merged.get(&Key::Tab), Some(&Action::ToggleSelectionDown.into()));
|
||||
/// ```
|
||||
pub fn merge_bindings<K>(
|
||||
mut bindings: Bindings<K>,
|
||||
new_bindings: &Bindings<K>,
|
||||
@ -256,12 +422,69 @@ impl Default for Bindings<EventType> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a string representation of a key event into a `KeyEvent`.
|
||||
///
|
||||
/// This function converts human-readable key descriptions (like "ctrl-a", "alt-enter")
|
||||
/// into crossterm `KeyEvent` structures.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `raw` - String representation of the key (e.g., "ctrl-a", "shift-f1", "esc")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(KeyEvent)` - Successfully parsed key event
|
||||
/// * `Err(String)` - Parse error with description
|
||||
///
|
||||
/// # Supported Modifiers
|
||||
///
|
||||
/// - `ctrl-` - Control key
|
||||
/// - `alt-` - Alt key
|
||||
/// - `shift-` - Shift key
|
||||
/// - `cmd-` - Command key (macOS)
|
||||
/// - `super-` - Super key (Linux/Windows)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::config::keybindings::parse_key_event;
|
||||
/// use crossterm::event::{KeyCode, KeyModifiers};
|
||||
///
|
||||
/// let event = parse_key_event("ctrl-a").unwrap();
|
||||
/// assert_eq!(event.code, KeyCode::Char('a'));
|
||||
/// assert_eq!(event.modifiers, KeyModifiers::CONTROL);
|
||||
///
|
||||
/// let event = parse_key_event("alt-enter").unwrap();
|
||||
/// assert_eq!(event.code, KeyCode::Enter);
|
||||
/// assert_eq!(event.modifiers, KeyModifiers::ALT);
|
||||
/// ```
|
||||
pub fn parse_key_event(raw: &str) -> anyhow::Result<KeyEvent, String> {
|
||||
let raw_lower = raw.to_ascii_lowercase();
|
||||
let (remaining, modifiers) = extract_modifiers(&raw_lower);
|
||||
parse_key_code_with_modifiers(remaining, modifiers)
|
||||
}
|
||||
|
||||
/// Extracts modifier keys from a raw key string.
|
||||
///
|
||||
/// This helper function parses modifier prefixes (ctrl-, alt-, shift-, etc.)
|
||||
/// from a key string and returns the remaining key part along with the
|
||||
/// extracted modifiers as a `KeyModifiers` bitfield.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `raw` - The raw key string (already lowercased)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A tuple of (`remaining_key_string`, `extracted_modifiers`)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// let (key, mods) = extract_modifiers("ctrl-alt-a");
|
||||
/// assert_eq!(key, "a");
|
||||
/// assert!(mods.contains(KeyModifiers::CONTROL | KeyModifiers::ALT));
|
||||
/// ```
|
||||
fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
|
||||
let mut modifiers = KeyModifiers::empty();
|
||||
let mut current = raw;
|
||||
@ -298,6 +521,28 @@ fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
|
||||
(current, modifiers)
|
||||
}
|
||||
|
||||
/// Parses a key code string with pre-extracted modifiers into a `KeyEvent`.
|
||||
///
|
||||
/// This function handles the actual key code parsing after modifiers have
|
||||
/// been extracted. It supports named keys (like "esc", "enter") and
|
||||
/// single character keys.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `raw` - The key string with modifiers already removed
|
||||
/// * `modifiers` - Pre-extracted modifier keys
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(KeyEvent)` - Successfully parsed key event
|
||||
/// * `Err(String)` - Parse error for unrecognized keys
|
||||
///
|
||||
/// # Supported Keys
|
||||
///
|
||||
/// - Named keys: esc, enter, left, right, up, down, home, end, etc.
|
||||
/// - Function keys: f1-f12
|
||||
/// - Special keys: space, tab, backspace, delete, etc.
|
||||
/// - Single characters: a-z, 0-9, punctuation
|
||||
fn parse_key_code_with_modifiers(
|
||||
raw: &str,
|
||||
mut modifiers: KeyModifiers,
|
||||
@ -361,6 +606,32 @@ fn parse_key_code_with_modifiers(
|
||||
Ok(KeyEvent::new(c, modifiers))
|
||||
}
|
||||
|
||||
/// Converts a `KeyEvent` back to its string representation.
|
||||
///
|
||||
/// This function performs the reverse operation of `parse_key_event`,
|
||||
/// converting a crossterm `KeyEvent` back into a human-readable string
|
||||
/// format that can be used in configuration files.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key_event` - The key event to convert
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// String representation of the key event (e.g., "ctrl-a", "alt-enter")
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::config::keybindings::key_event_to_string;
|
||||
/// use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
|
||||
///
|
||||
/// let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
|
||||
/// assert_eq!(key_event_to_string(&event), "ctrl-a");
|
||||
///
|
||||
/// let event = KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT);
|
||||
/// assert_eq!(key_event_to_string(&event), "alt-enter");
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn key_event_to_string(key_event: &KeyEvent) -> String {
|
||||
let char;
|
||||
@ -456,11 +727,25 @@ impl FromStr for Key {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_key(raw: &str) -> anyhow::Result<Key, String> {
|
||||
Key::from_str(raw)
|
||||
}
|
||||
|
||||
/// Generic serializer that converts any key type to string for TOML compatibility
|
||||
/// Generic serializer that converts any key type to string for TOML compatibility.
|
||||
///
|
||||
/// This function enables serialization of the bindings `HashMap` to TOML format
|
||||
/// by converting keys to their string representation using the `Display` trait.
|
||||
/// This is used internally by serde when serializing `Bindings` structs.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bindings` - The bindings `HashMap` to serialize
|
||||
/// * `serializer` - The serde serializer instance
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `K` - Key type that implements `Display`
|
||||
/// * `S` - Serializer type
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result of the serialization operation
|
||||
fn serialize_bindings<K, S>(
|
||||
bindings: &FxHashMap<K, Actions>,
|
||||
serializer: S,
|
||||
@ -477,7 +762,30 @@ where
|
||||
map.end()
|
||||
}
|
||||
|
||||
/// Generic deserializer that parses string keys back to key enum
|
||||
/// Generic deserializer that parses string keys back to key enum.
|
||||
///
|
||||
/// This function enables deserialization from TOML format by parsing string keys
|
||||
/// back into their appropriate key types using the `FromStr` trait. It also handles
|
||||
/// special cases like boolean values for key unbinding.
|
||||
///
|
||||
/// # Special Value Handling
|
||||
///
|
||||
/// - `false` - Binds the key to `Action::NoOp` (effectively unbinding)
|
||||
/// - `true` - Ignores the binding (preserves existing or default binding)
|
||||
/// - String/Array - Normal action binding
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `deserializer` - The serde deserializer instance
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `K` - Key type that implements `FromStr`, `Eq`, and `Hash`
|
||||
/// * `D` - Deserializer type
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result containing the parsed bindings `HashMap` or deserialization error
|
||||
fn deserialize_bindings<'de, K, D>(
|
||||
deserializer: D,
|
||||
) -> Result<FxHashMap<K, Actions>, D::Error>
|
||||
|
@ -15,7 +15,7 @@ use std::{
|
||||
use tracing::{debug, warn};
|
||||
|
||||
pub use keybindings::{
|
||||
Binding, EventBindings, EventType, KeyBindings, merge_bindings, parse_key,
|
||||
Binding, EventBindings, EventType, KeyBindings, merge_bindings,
|
||||
};
|
||||
pub use themes::Theme;
|
||||
pub use ui::UiConfig;
|
||||
@ -371,6 +371,7 @@ mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
@ -477,7 +478,7 @@ mod tests {
|
||||
|
||||
default_config.shell_integration.keybindings.insert(
|
||||
"command_history".to_string(),
|
||||
Binding::SingleKey(parse_key("ctrl-h").unwrap()),
|
||||
Binding::SingleKey(Key::from_str("ctrl-h").unwrap()),
|
||||
);
|
||||
default_config.shell_integration.merge_triggers();
|
||||
|
||||
@ -550,8 +551,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_shell_integration_keybindings_are_overwritten_by_user() {
|
||||
use crate::config::parse_key;
|
||||
|
||||
let user_config = r#"
|
||||
[shell_integration.keybindings]
|
||||
"smart_autocomplete" = "ctrl-t"
|
||||
@ -574,11 +573,11 @@ mod tests {
|
||||
let expected: rustc_hash::FxHashMap<String, Binding> = [
|
||||
(
|
||||
"command_history".to_string(),
|
||||
Binding::SingleKey(parse_key("ctrl-[").unwrap()),
|
||||
Binding::SingleKey(Key::from_str("ctrl-[").unwrap()),
|
||||
),
|
||||
(
|
||||
"smart_autocomplete".to_string(),
|
||||
Binding::SingleKey(parse_key("ctrl-t").unwrap()),
|
||||
Binding::SingleKey(Key::from_str("ctrl-t").unwrap()),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
|
@ -1,11 +1,3 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll as TaskPoll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crossterm::event::{
|
||||
KeyCode::{
|
||||
BackTab, Backspace, Char, Delete, Down, End, Enter, Esc, F, Home,
|
||||
@ -14,11 +6,17 @@ use crossterm::event::{
|
||||
KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
str::FromStr,
|
||||
task::{Context, Poll as TaskPoll},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{signal, sync::mpsc};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::config::parse_key;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Event<I> {
|
||||
Closed,
|
||||
@ -70,19 +68,89 @@ pub enum Key {
|
||||
Tab,
|
||||
}
|
||||
|
||||
/// Unified input event type that encompasses all possible inputs.
|
||||
///
|
||||
/// This enum provides a unified interface for handling different types of input
|
||||
/// events in Television, including keyboard input, mouse events, terminal resize
|
||||
/// events, and custom events. It enables the new binding system to map any
|
||||
/// type of input to actions.
|
||||
///
|
||||
/// # Variants
|
||||
///
|
||||
/// - `Key(Key)` - Keyboard input events
|
||||
/// - `Mouse(MouseInputEvent)` - Mouse events with position information
|
||||
/// - `Resize(u16, u16)` - Terminal resize events with new dimensions
|
||||
/// - `Custom(String)` - Custom events for extensibility
|
||||
///
|
||||
/// # Usage in Bindings
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::event::{InputEvent, Key, MouseInputEvent};
|
||||
/// use television::keymap::InputMap;
|
||||
///
|
||||
/// let input_map = InputMap::new();
|
||||
///
|
||||
/// // Handle keyboard input
|
||||
/// let key_event = InputEvent::Key(Key::Enter);
|
||||
/// let actions = input_map.get_actions_for_input(&key_event);
|
||||
/// assert_eq!(actions, None); // No bindings in empty map
|
||||
///
|
||||
/// // Handle mouse input
|
||||
/// let mouse_event = InputEvent::Mouse(MouseInputEvent {
|
||||
/// kind: crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left),
|
||||
/// position: (10, 5),
|
||||
/// });
|
||||
/// let actions = input_map.get_actions_for_input(&mouse_event);
|
||||
/// assert_eq!(actions, None); // No bindings in empty map
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
/// Unified input event type that encompasses all possible inputs
|
||||
pub enum InputEvent {
|
||||
/// Keyboard input event
|
||||
Key(Key),
|
||||
/// Mouse event with position information
|
||||
Mouse(MouseInputEvent),
|
||||
/// Terminal resize event with new dimensions (width, height)
|
||||
Resize(u16, u16),
|
||||
/// Custom event for extensibility
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
/// Mouse event with position information for input mapping.
|
||||
///
|
||||
/// This structure combines a mouse event type with its screen coordinates,
|
||||
/// enabling position-aware mouse handling in the binding system. It provides
|
||||
/// the information needed to map mouse events to appropriate actions.
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// - `kind` - The type of mouse event (click, scroll, etc.)
|
||||
/// - `position` - Screen coordinates as (column, row) tuple
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::event::MouseInputEvent;
|
||||
/// use crossterm::event::{MouseEventKind, MouseButton};
|
||||
///
|
||||
/// // Left mouse button click at position (10, 5)
|
||||
/// let click_event = MouseInputEvent {
|
||||
/// kind: MouseEventKind::Down(MouseButton::Left),
|
||||
/// position: (10, 5),
|
||||
/// };
|
||||
/// assert_eq!(click_event.position, (10, 5));
|
||||
///
|
||||
/// // Mouse scroll up at position (20, 15)
|
||||
/// let scroll_event = MouseInputEvent {
|
||||
/// kind: MouseEventKind::ScrollUp,
|
||||
/// position: (20, 15),
|
||||
/// };
|
||||
/// assert_eq!(scroll_event.position, (20, 15));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
/// Mouse event with position information for input mapping
|
||||
pub struct MouseInputEvent {
|
||||
/// The type of mouse event (click, scroll, etc.)
|
||||
pub kind: MouseEventKind,
|
||||
/// Screen coordinates as (column, row)
|
||||
pub position: (u16, u16),
|
||||
}
|
||||
|
||||
@ -92,7 +160,7 @@ impl<'de> Deserialize<'de> for Key {
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
parse_key(&s).map_err(serde::de::Error::custom)
|
||||
Key::from_str(&s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,6 +338,39 @@ fn flush_resize_events(first_resize: (u16, u16)) -> ((u16, u16), (u16, u16)) {
|
||||
(first_resize, last_resize)
|
||||
}
|
||||
|
||||
/// Converts a crossterm `KeyEvent` into Television's internal `Key` representation.
|
||||
///
|
||||
/// This function handles the conversion from crossterm's key event format into
|
||||
/// Television's simplified key representation, applying modifier key combinations
|
||||
/// and filtering out key release events.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event` - The crossterm `KeyEvent` to convert
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The corresponding `Key` enum variant, or `Key::Null` for unsupported events
|
||||
///
|
||||
/// # Key Mapping
|
||||
///
|
||||
/// - Modifier combinations are mapped to specific variants (e.g., `Ctrl+a` → `Key::Ctrl('a')`)
|
||||
/// - Key release events are ignored (return `Key::Null`)
|
||||
/// - Special keys are mapped directly (e.g., `Enter` → `Key::Enter`)
|
||||
/// - Function keys preserve their number (e.g., `F1` → `Key::F(1)`)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::event::{convert_raw_event_to_key, Key};
|
||||
/// use crossterm::event::{KeyEvent, KeyCode, KeyModifiers, KeyEventKind};
|
||||
///
|
||||
/// let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
|
||||
/// assert_eq!(convert_raw_event_to_key(event), Key::Ctrl('a'));
|
||||
///
|
||||
/// let event = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
|
||||
/// assert_eq!(convert_raw_event_to_key(event), Key::Enter);
|
||||
/// ```
|
||||
pub fn convert_raw_event_to_key(event: KeyEvent) -> Key {
|
||||
trace!("Raw event: {:?}", event);
|
||||
if event.kind == KeyEventKind::Release {
|
||||
|
@ -6,27 +6,66 @@ use crate::{
|
||||
use crossterm::event::MouseEventKind;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
/// An input map that handles both keyboard and non-keyboard input events.
|
||||
///
|
||||
/// This replaces the old Keymap structure and provides unified access to
|
||||
/// both key bindings and event bindings through a single interface.
|
||||
/// This structure supports multiple-actions-per-key and handles various
|
||||
/// input types including keyboard keys, mouse events, resize events, and
|
||||
/// custom events.
|
||||
///
|
||||
/// # Example:
|
||||
/// ```ignore
|
||||
/// InputMap {
|
||||
/// Key::Char('j') => Action::MoveDown,
|
||||
/// Key::Char('k') => Action::MoveUp,
|
||||
/// EventType::MouseClick => Action::ConfirmSelection,
|
||||
/// }
|
||||
/// # Fields
|
||||
///
|
||||
/// - `key_actions` - Maps keyboard keys to actions
|
||||
/// - `event_actions` - Maps non-keyboard events to actions
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::event::{Key, InputEvent};
|
||||
/// use television::action::{Action, Actions};
|
||||
/// use television::config::{KeyBindings, EventType};
|
||||
///
|
||||
/// // Create from key bindings
|
||||
/// let keybindings = KeyBindings::from(vec![
|
||||
/// (Key::Char('j'), Action::SelectNextEntry),
|
||||
/// (Key::Char('k'), Action::SelectPrevEntry),
|
||||
/// ]);
|
||||
/// let input_map: InputMap = (&keybindings).into();
|
||||
///
|
||||
/// // Query actions for input
|
||||
/// let key_input = InputEvent::Key(Key::Char('j'));
|
||||
/// let actions = input_map.get_actions_for_input(&key_input);
|
||||
/// assert_eq!(actions, Some(vec![Action::SelectNextEntry]));
|
||||
/// ```
|
||||
/// ```
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct InputMap {
|
||||
/// Maps keyboard keys to their associated actions
|
||||
pub key_actions: FxHashMap<Key, Actions>,
|
||||
/// Maps non-keyboard events to their associated actions
|
||||
pub event_actions: FxHashMap<EventType, Actions>,
|
||||
}
|
||||
|
||||
impl InputMap {
|
||||
/// Create a new empty `InputMap`
|
||||
/// Creates a new empty `InputMap`.
|
||||
///
|
||||
/// Returns an empty input map with no key or event bindings.
|
||||
/// Bindings can be added using the `merge` methods or by directly
|
||||
/// manipulating the `key_actions` and `event_actions` fields.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new empty `InputMap` instance.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
///
|
||||
/// let input_map = InputMap::new();
|
||||
/// assert!(input_map.key_actions.is_empty());
|
||||
/// assert!(input_map.event_actions.is_empty());
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
key_actions: FxHashMap::default(),
|
||||
@ -34,12 +73,67 @@ impl InputMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the actions for a given key
|
||||
/// Gets all actions bound to a specific key.
|
||||
///
|
||||
/// Returns a reference to the `Actions` (single or multiple) bound to
|
||||
/// the given key, or `None` if no binding exists.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to look up
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Some(&Actions)` - The actions bound to the key
|
||||
/// - `None` - No binding exists for this key
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::event::Key;
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let mut input_map = InputMap::new();
|
||||
/// input_map.key_actions.insert(Key::Enter, Actions::Single(Action::ConfirmSelection));
|
||||
///
|
||||
/// let actions = input_map.get_actions_for_key(&Key::Enter).unwrap();
|
||||
/// assert_eq!(actions.as_slice(), &[Action::ConfirmSelection]);
|
||||
/// ```
|
||||
pub fn get_actions_for_key(&self, key: &Key) -> Option<&Actions> {
|
||||
self.key_actions.get(key)
|
||||
}
|
||||
|
||||
/// Get the actions for a given event type
|
||||
/// Gets all actions bound to a specific event type.
|
||||
///
|
||||
/// Returns a reference to the `Actions` (single or multiple) bound to
|
||||
/// the given event type, or `None` if no binding exists.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event` - The event type to look up
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Some(&Actions)` - The actions bound to the event
|
||||
/// - `None` - No binding exists for this event type
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::config::EventType;
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let mut input_map = InputMap::new();
|
||||
/// input_map.event_actions.insert(
|
||||
/// EventType::MouseClick,
|
||||
/// Actions::Single(Action::ConfirmSelection)
|
||||
/// );
|
||||
///
|
||||
/// let actions = input_map.get_actions_for_event(&EventType::MouseClick).unwrap();
|
||||
/// assert_eq!(actions.as_slice(), &[Action::ConfirmSelection]);
|
||||
/// ```
|
||||
pub fn get_actions_for_event(
|
||||
&self,
|
||||
event: &EventType,
|
||||
@ -47,7 +141,37 @@ impl InputMap {
|
||||
self.event_actions.get(event)
|
||||
}
|
||||
|
||||
/// Get the action for a given key (backward compatibility)
|
||||
/// Gets the first action bound to a specific key (backward compatibility).
|
||||
///
|
||||
/// This method provides backward compatibility with the old single-action
|
||||
/// binding system. For keys with multiple actions, it returns only the
|
||||
/// first action. Use `get_actions_for_key()` to get all actions.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - The key to look up
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Some(Action)` - The first action bound to the key
|
||||
/// - `None` - No binding exists for this key
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::event::Key;
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let mut input_map = InputMap::new();
|
||||
/// input_map.key_actions.insert(
|
||||
/// Key::Ctrl('r'),
|
||||
/// Actions::Multiple(vec![Action::ReloadSource, Action::ClearScreen])
|
||||
/// );
|
||||
///
|
||||
/// // Returns only the first action
|
||||
/// assert_eq!(input_map.get_action_for_key(&Key::Ctrl('r')), Some(Action::ReloadSource));
|
||||
/// ```
|
||||
pub fn get_action_for_key(&self, key: &Key) -> Option<Action> {
|
||||
self.key_actions.get(key).and_then(|actions| match actions {
|
||||
Actions::Single(action) => Some(action.clone()),
|
||||
@ -55,7 +179,40 @@ impl InputMap {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the action for a given event type (backward compatibility)
|
||||
/// Gets the first action bound to a specific event type (backward compatibility).
|
||||
///
|
||||
/// This method provides backward compatibility with the old single-action
|
||||
/// binding system. For events with multiple actions, it returns only the
|
||||
/// first action. Use `get_actions_for_event()` to get all actions.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event` - The event type to look up
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Some(Action)` - The first action bound to the event
|
||||
/// - `None` - No binding exists for this event type
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::config::EventType;
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let mut input_map = InputMap::new();
|
||||
/// input_map.event_actions.insert(
|
||||
/// EventType::MouseClick,
|
||||
/// Actions::Multiple(vec![Action::ConfirmSelection, Action::TogglePreview])
|
||||
/// );
|
||||
///
|
||||
/// // Returns only the first action
|
||||
/// assert_eq!(
|
||||
/// input_map.get_action_for_event(&EventType::MouseClick),
|
||||
/// Some(Action::ConfirmSelection)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn get_action_for_event(&self, event: &EventType) -> Option<Action> {
|
||||
self.event_actions
|
||||
.get(event)
|
||||
@ -65,7 +222,47 @@ impl InputMap {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get all actions for any input event
|
||||
/// Gets all actions for any input event.
|
||||
///
|
||||
/// This is the primary method for querying actions in the new input system.
|
||||
/// It handles all types of input events and returns a vector of all actions
|
||||
/// that should be executed in response to the input.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `input` - The input event to look up
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Some(Vec<Action>)` - All actions bound to the input event
|
||||
/// - `None` - No binding exists for this input
|
||||
///
|
||||
/// # Supported Input Types
|
||||
///
|
||||
/// - `InputEvent::Key` - Keyboard input
|
||||
/// - `InputEvent::Mouse` - Mouse clicks and scrolling
|
||||
/// - `InputEvent::Resize` - Terminal resize events
|
||||
/// - `InputEvent::Custom` - Custom event types
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::event::{Key, InputEvent, MouseInputEvent};
|
||||
/// use television::config::EventType;
|
||||
/// use television::action::{Action, Actions};
|
||||
/// use crossterm::event::MouseEventKind;
|
||||
///
|
||||
/// let mut input_map = InputMap::new();
|
||||
/// input_map.key_actions.insert(
|
||||
/// Key::Ctrl('s'),
|
||||
/// Actions::Multiple(vec![Action::ReloadSource, Action::CopyEntryToClipboard])
|
||||
/// );
|
||||
///
|
||||
/// let key_input = InputEvent::Key(Key::Ctrl('s'));
|
||||
/// let actions = input_map.get_actions_for_input(&key_input).unwrap();
|
||||
/// assert_eq!(actions, vec![Action::ReloadSource, Action::CopyEntryToClipboard]);
|
||||
/// ```
|
||||
pub fn get_actions_for_input(
|
||||
&self,
|
||||
input: &InputEvent,
|
||||
@ -88,7 +285,47 @@ impl InputMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an action for any input event (backward compatibility)
|
||||
/// Gets the first action for any input event (backward compatibility).
|
||||
///
|
||||
/// This method provides backward compatibility with the old single-action
|
||||
/// system. It handles all input types and returns only the first action
|
||||
/// bound to the input. Use `get_actions_for_input()` to get all actions.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `input` - The input event to look up
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Some(Action)` - The first action bound to the input
|
||||
/// - `None` - No binding exists for this input
|
||||
///
|
||||
/// # Supported Input Types
|
||||
///
|
||||
/// - `InputEvent::Key` - Returns first action for the key
|
||||
/// - `InputEvent::Mouse` - Maps to appropriate event type and returns first action
|
||||
/// - `InputEvent::Resize` - Returns first action for resize events
|
||||
/// - `InputEvent::Custom` - Returns first action for the custom event
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::event::{Key, InputEvent};
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let mut input_map = InputMap::new();
|
||||
/// input_map.key_actions.insert(
|
||||
/// Key::Enter,
|
||||
/// Actions::Multiple(vec![Action::ConfirmSelection, Action::Quit])
|
||||
/// );
|
||||
///
|
||||
/// let key_input = InputEvent::Key(Key::Enter);
|
||||
/// assert_eq!(
|
||||
/// input_map.get_action_for_input(&key_input),
|
||||
/// Some(Action::ConfirmSelection) // Only first action
|
||||
/// );
|
||||
/// ```
|
||||
pub fn get_action_for_input(&self, input: &InputEvent) -> Option<Action> {
|
||||
match input {
|
||||
InputEvent::Key(key) => self.get_action_for_key(key),
|
||||
@ -112,10 +349,38 @@ impl InputMap {
|
||||
}
|
||||
|
||||
impl From<&KeyBindings> for InputMap {
|
||||
/// Convert a `KeyBindings` into an `InputMap`.
|
||||
/// Converts `KeyBindings` into an `InputMap`.
|
||||
///
|
||||
/// Since the new `KeyBindings` already store Key -> Action mappings,
|
||||
/// we can directly copy the bindings without inversion.
|
||||
/// This conversion creates an input map containing only keyboard bindings.
|
||||
/// The event bindings will be empty. Since the new `KeyBindings` structure
|
||||
/// already stores Key → Actions mappings, we can directly copy the bindings
|
||||
/// without the inversion that was needed in the old system.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keybindings` - The key bindings to convert
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `InputMap` with the key bindings and empty event bindings.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::config::KeyBindings;
|
||||
/// use television::event::Key;
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// let keybindings = KeyBindings::from(vec![
|
||||
/// (Key::Enter, Action::ConfirmSelection),
|
||||
/// (Key::Esc, Action::Quit),
|
||||
/// ]);
|
||||
///
|
||||
/// let input_map: InputMap = (&keybindings).into();
|
||||
/// assert_eq!(input_map.get_action_for_key(&Key::Enter), Some(Action::ConfirmSelection));
|
||||
/// assert!(input_map.event_actions.is_empty());
|
||||
/// ```
|
||||
fn from(keybindings: &KeyBindings) -> Self {
|
||||
Self {
|
||||
key_actions: keybindings.bindings.clone(),
|
||||
@ -125,7 +390,39 @@ impl From<&KeyBindings> for InputMap {
|
||||
}
|
||||
|
||||
impl From<&EventBindings> for InputMap {
|
||||
/// Convert `EventBindings` into an `InputMap`.
|
||||
/// Converts `EventBindings` into an `InputMap`.
|
||||
///
|
||||
/// This conversion creates an input map containing only event bindings.
|
||||
/// The key bindings will be empty. This is useful when you want to
|
||||
/// handle only non-keyboard events.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_bindings` - The event bindings to convert
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `InputMap` with the event bindings and empty key bindings.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::config::{EventBindings, EventType};
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// let event_bindings = EventBindings::from(vec![
|
||||
/// (EventType::MouseClick, Action::ConfirmSelection),
|
||||
/// (EventType::Resize, Action::ClearScreen),
|
||||
/// ]);
|
||||
///
|
||||
/// let input_map: InputMap = (&event_bindings).into();
|
||||
/// assert_eq!(
|
||||
/// input_map.get_action_for_event(&EventType::MouseClick),
|
||||
/// Some(Action::ConfirmSelection)
|
||||
/// );
|
||||
/// assert!(input_map.key_actions.is_empty());
|
||||
/// ```
|
||||
fn from(event_bindings: &EventBindings) -> Self {
|
||||
Self {
|
||||
key_actions: FxHashMap::default(),
|
||||
@ -135,7 +432,43 @@ impl From<&EventBindings> for InputMap {
|
||||
}
|
||||
|
||||
impl From<(&KeyBindings, &EventBindings)> for InputMap {
|
||||
/// Convert both `KeyBindings` and `EventBindings` into an `InputMap`.
|
||||
/// Converts both `KeyBindings` and `EventBindings` into an `InputMap`.
|
||||
///
|
||||
/// This conversion creates a complete input map with both keyboard and
|
||||
/// event bindings. This is the most common way to create a fully
|
||||
/// configured input map that handles all types of input events.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keybindings` - The keyboard bindings to include
|
||||
/// * `event_bindings` - The event bindings to include
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `InputMap` containing both key and event bindings.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::config::{KeyBindings, EventBindings, EventType};
|
||||
/// use television::event::Key;
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// let keybindings = KeyBindings::from(vec![
|
||||
/// (Key::Enter, Action::ConfirmSelection),
|
||||
/// ]);
|
||||
/// let event_bindings = EventBindings::from(vec![
|
||||
/// (EventType::MouseClick, Action::ConfirmSelection),
|
||||
/// ]);
|
||||
///
|
||||
/// let input_map: InputMap = (&keybindings, &event_bindings).into();
|
||||
/// assert_eq!(input_map.get_action_for_key(&Key::Enter), Some(Action::ConfirmSelection));
|
||||
/// assert_eq!(
|
||||
/// input_map.get_action_for_event(&EventType::MouseClick),
|
||||
/// Some(Action::ConfirmSelection)
|
||||
/// );
|
||||
/// ```
|
||||
fn from(
|
||||
(keybindings, event_bindings): (&KeyBindings, &EventBindings),
|
||||
) -> Self {
|
||||
@ -147,9 +480,40 @@ impl From<(&KeyBindings, &EventBindings)> for InputMap {
|
||||
}
|
||||
|
||||
impl InputMap {
|
||||
/// Merge another `InputMap` into this one.
|
||||
/// Merges another `InputMap` into this one.
|
||||
///
|
||||
/// This will overwrite any existing keys/events in `self` with the mappings from `other`.
|
||||
/// This method combines the bindings from another `InputMap` into this one,
|
||||
/// with bindings from `other` taking precedence over existing bindings.
|
||||
/// This is useful for applying configuration hierarchies and user customizations.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `other` - The input map to merge into this one
|
||||
///
|
||||
/// # Behavior
|
||||
///
|
||||
/// - Existing keys/events in `self` are overwritten by those in `other`
|
||||
/// - New keys/events from `other` are added to `self`
|
||||
/// - Both key bindings and event bindings are merged
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::event::Key;
|
||||
/// use television::action::{Action, Actions};
|
||||
///
|
||||
/// let mut base_map = InputMap::new();
|
||||
/// base_map.key_actions.insert(Key::Enter, Actions::Single(Action::ConfirmSelection));
|
||||
///
|
||||
/// let mut custom_map = InputMap::new();
|
||||
/// custom_map.key_actions.insert(Key::Enter, Actions::Single(Action::Quit)); // Override
|
||||
/// custom_map.key_actions.insert(Key::Esc, Actions::Single(Action::Quit)); // New binding
|
||||
///
|
||||
/// base_map.merge(&custom_map);
|
||||
/// assert_eq!(base_map.get_action_for_key(&Key::Enter), Some(Action::Quit));
|
||||
/// assert_eq!(base_map.get_action_for_key(&Key::Esc), Some(Action::Quit));
|
||||
/// ```
|
||||
pub fn merge(&mut self, other: &InputMap) {
|
||||
for (key, action) in &other.key_actions {
|
||||
self.key_actions.insert(*key, action.clone());
|
||||
@ -159,14 +523,66 @@ impl InputMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge key bindings into this `InputMap`
|
||||
/// Merges key bindings into this `InputMap`.
|
||||
///
|
||||
/// This method adds all key bindings from a `KeyBindings` configuration
|
||||
/// into this input map, overwriting any existing bindings for the same keys.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keybindings` - The key bindings to merge
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::config::KeyBindings;
|
||||
/// use television::event::Key;
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// let mut input_map = InputMap::new();
|
||||
/// let keybindings = KeyBindings::from(vec![
|
||||
/// (Key::Enter, Action::ConfirmSelection),
|
||||
/// (Key::Esc, Action::Quit),
|
||||
/// ]);
|
||||
///
|
||||
/// input_map.merge_key_bindings(&keybindings);
|
||||
/// assert_eq!(input_map.get_action_for_key(&Key::Enter), Some(Action::ConfirmSelection));
|
||||
/// ```
|
||||
pub fn merge_key_bindings(&mut self, keybindings: &KeyBindings) {
|
||||
for (key, action) in &keybindings.bindings {
|
||||
self.key_actions.insert(*key, action.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge event bindings into this `InputMap`
|
||||
/// Merges event bindings into this `InputMap`.
|
||||
///
|
||||
/// This method adds all event bindings from an `EventBindings` configuration
|
||||
/// into this input map, overwriting any existing bindings for the same events.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_bindings` - The event bindings to merge
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use television::keymap::InputMap;
|
||||
/// use television::config::{EventBindings, EventType};
|
||||
/// use television::action::Action;
|
||||
///
|
||||
/// let mut input_map = InputMap::new();
|
||||
/// let event_bindings = EventBindings::from(vec![
|
||||
/// (EventType::MouseClick, Action::ConfirmSelection),
|
||||
/// (EventType::Resize, Action::ClearScreen),
|
||||
/// ]);
|
||||
///
|
||||
/// input_map.merge_event_bindings(&event_bindings);
|
||||
/// assert_eq!(
|
||||
/// input_map.get_action_for_event(&EventType::MouseClick),
|
||||
/// Some(Action::ConfirmSelection)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn merge_event_bindings(&mut self, event_bindings: &EventBindings) {
|
||||
for (event, action) in &event_bindings.bindings {
|
||||
self.event_actions.insert(event.clone(), action.clone());
|
||||
|
Loading…
x
Reference in New Issue
Block a user