bubbletea_widgets/
key.rs

1//! Key binding component for managing keybindings, ported from the Go version.
2//!
3//! This module provides types and functions for defining keybindings that can be
4//! used for both input handling and generating help views. It offers a type-safe
5//! alternative to string-based key matching.
6
7use bubbletea_rs::KeyMsg;
8use crossterm::event::{KeyCode, KeyModifiers};
9
10/// Represents a specific key press, combining a `KeyCode` and `KeyModifiers`.
11///
12/// This provides a structured, type-safe way to define key combinations that can
13/// be used throughout the application for consistent key handling.
14///
15/// # Examples
16///
17/// ```rust
18/// use bubbletea_widgets::key::KeyPress;
19/// use crossterm::event::{KeyCode, KeyModifiers};
20///
21/// // Create a Ctrl+C key press
22/// let ctrl_c = KeyPress {
23///     code: KeyCode::Char('c'),
24///     mods: KeyModifiers::CONTROL,
25/// };
26///
27/// // Create from tuple
28/// let alt_f4: KeyPress = (KeyCode::F(4), KeyModifiers::ALT).into();
29///
30/// // Create from string
31/// let escape: KeyPress = "esc".into();
32/// ```
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct KeyPress {
35    /// The key code representing the physical key pressed.
36    pub code: KeyCode,
37    /// The modifier keys (Ctrl, Alt, Shift) held during the key press.
38    pub mods: KeyModifiers,
39}
40
41/// Creates a `KeyPress` from a tuple of `(KeyCode, KeyModifiers)`.
42///
43/// This provides a convenient way to create key press instances from tuples,
44/// making it easy to define key combinations inline.
45///
46/// # Examples
47///
48/// ```rust
49/// use bubbletea_widgets::key::KeyPress;
50/// use crossterm::event::{KeyCode, KeyModifiers};
51///
52/// let save_key: KeyPress = (KeyCode::Char('s'), KeyModifiers::CONTROL).into();
53/// let quit_key: KeyPress = (KeyCode::Char('q'), KeyModifiers::CONTROL).into();
54/// ```
55impl From<(KeyCode, KeyModifiers)> for KeyPress {
56    fn from((code, mods): (KeyCode, KeyModifiers)) -> Self {
57        Self { code, mods }
58    }
59}
60
61/// Creates a `KeyPress` from just a `KeyCode` with no modifiers.
62///
63/// This is useful for simple keys that don't require modifier combinations,
64/// such as arrow keys, function keys, or single character keys.
65///
66/// # Examples
67///
68/// ```rust
69/// use bubbletea_widgets::key::KeyPress;
70/// use crossterm::event::KeyCode;
71///
72/// let enter_key: KeyPress = KeyCode::Enter.into();
73/// let up_arrow: KeyPress = KeyCode::Up.into();
74/// let letter_a: KeyPress = KeyCode::Char('a').into();
75/// ```
76impl From<KeyCode> for KeyPress {
77    fn from(code: KeyCode) -> Self {
78        Self {
79            code,
80            mods: KeyModifiers::NONE,
81        }
82    }
83}
84
85/// Creates a `KeyPress` from a string representation.
86///
87/// This provides a human-readable way to define key combinations using string
88/// names. Supports both simple keys and modifier combinations.
89///
90/// # Supported Formats
91///
92/// - Simple keys: "enter", "tab", "esc", "space", "up", "down", etc.
93/// - Function keys: "f1", "f2", ..., "f12"
94/// - Single characters: "a", "1", "?", "/"
95/// - Modifier combinations: "ctrl+c", "alt+f4", "shift+tab"
96/// - Complex combinations: "ctrl+alt+a"
97///
98/// # Examples
99///
100/// ```rust
101/// use bubbletea_widgets::key::KeyPress;
102///
103/// let enter: KeyPress = "enter".into();
104/// let ctrl_c: KeyPress = "ctrl+c".into();
105/// let alt_f4: KeyPress = "alt+f4".into();
106/// let page_up: KeyPress = "pgup".into();
107/// ```
108///
109/// # Panics
110///
111/// This function does not panic. Unknown key combinations will result in
112/// a `KeyPress` with `KeyCode::Null`.
113impl From<&str> for KeyPress {
114    fn from(s: &str) -> Self {
115        parse_key_string(s)
116    }
117}
118
119/// Help information for displaying keybinding documentation.
120///
121/// This structure contains the human-readable representation of a key binding
122/// that will be shown in help views and documentation.
123///
124/// # Examples
125///
126/// ```rust
127/// use bubbletea_widgets::key::Help;
128///
129/// let help = Help {
130///     key: "ctrl+s".to_string(),
131///     desc: "Save file".to_string(),
132/// };
133/// ```
134#[derive(Debug, Clone, Default, PartialEq, Eq)]
135pub struct Help {
136    /// The human-readable representation of the key combination (e.g., "ctrl+s", "enter").
137    pub key: String,
138    /// A brief description of what the key binding does.
139    pub desc: String,
140}
141
142/// Describes a set of keybindings and their associated help text. A `Binding`
143/// represents a single semantic action that can be triggered by one or more
144/// physical key presses.
145#[derive(Debug, Clone, Default)]
146pub struct Binding {
147    /// The key press combinations that trigger this binding.
148    keys: Vec<KeyPress>,
149    /// The help information for displaying in help views.
150    help: Help,
151    /// Whether the binding is currently enabled and should match key events.
152    enabled: bool,
153}
154
155/// Option type for configuring a Binding during initialization.
156pub type BindingOpt = Box<dyn FnOnce(&mut Binding)>;
157
158impl Binding {
159    /// Creates a new keybinding with a set of associated key presses.
160    ///
161    /// The input can be anything convertible into a `Vec<KeyPress>`, making it
162    /// ergonomic to define keys with or without modifiers. The binding is
163    /// created in an enabled state with empty help text.
164    ///
165    /// # Arguments
166    ///
167    /// * `keys` - A vector of items that can be converted to `KeyPress`
168    ///
169    /// # Examples
170    ///
171    /// ```rust
172    /// use bubbletea_widgets::key::Binding;
173    /// use crossterm::event::{KeyCode, KeyModifiers};
174    ///
175    /// // Binding with multiple keys, some with modifiers
176    /// let save_binding = Binding::new(vec![
177    ///     (KeyCode::Char('s'), KeyModifiers::CONTROL), // Ctrl+S
178    ///     (KeyCode::F(2), KeyModifiers::NONE), // F2 key
179    /// ]);
180    ///
181    /// // Binding from string representations
182    /// let quit_binding = Binding::new(vec!["q", "ctrl+c"]);
183    /// ```
184    pub fn new<K: Into<KeyPress>>(keys: Vec<K>) -> Self {
185        Self {
186            keys: keys.into_iter().map(Into::into).collect(),
187            help: Help::default(),
188            enabled: true,
189        }
190    }
191
192    /// Creates a new binding using builder options.
193    ///
194    /// This provides a flexible way to create bindings with various configuration
195    /// options applied. Similar to Go's NewBinding function.
196    ///
197    /// # Arguments
198    ///
199    /// * `opts` - A vector of builder options to configure the binding
200    ///
201    /// # Examples
202    ///
203    /// ```rust
204    /// use bubbletea_widgets::key::{Binding, with_keys_str, with_help};
205    ///
206    /// let save_binding = Binding::new_binding(vec![
207    ///     with_keys_str(&["ctrl+s", "f2"]),
208    ///     with_help("ctrl+s", "Save the current file"),
209    /// ]);
210    /// ```
211    pub fn new_binding(opts: Vec<BindingOpt>) -> Self {
212        let mut binding = Self {
213            keys: Vec::new(),
214            help: Help::default(),
215            enabled: true,
216        };
217        for opt in opts {
218            opt(&mut binding);
219        }
220        binding
221    }
222
223    /// Sets the help text for the keybinding using a builder pattern.
224    ///
225    /// # Arguments
226    ///
227    /// * `key` - The human-readable key representation for help display
228    /// * `desc` - A brief description of what the binding does
229    ///
230    /// # Examples
231    ///
232    /// ```rust
233    /// use bubbletea_widgets::key::Binding;
234    /// use crossterm::event::KeyCode;
235    ///
236    /// let binding = Binding::new(vec![KeyCode::Enter])
237    ///     .with_help("enter", "Confirm selection");
238    /// ```
239    pub fn with_help(mut self, key: impl Into<String>, desc: impl Into<String>) -> Self {
240        self.help = Help {
241            key: key.into(),
242            desc: desc.into(),
243        };
244        self
245    }
246
247    /// Sets the initial enabled state of the keybinding.
248    ///
249    /// Disabled bindings will not match key events and will not appear in help views.
250    ///
251    /// # Arguments
252    ///
253    /// * `enabled` - Whether the binding should be enabled
254    ///
255    /// # Examples
256    ///
257    /// ```rust
258    /// use bubbletea_widgets::key::Binding;
259    /// use crossterm::event::KeyCode;
260    ///
261    /// let disabled_binding = Binding::new(vec![KeyCode::F(1)])
262    ///     .with_enabled(false);
263    /// ```
264    pub fn with_enabled(mut self, enabled: bool) -> Self {
265        self.enabled = enabled;
266        self
267    }
268
269    /// Sets the keybinding to disabled state (convenience method).
270    ///
271    /// This is equivalent to calling `with_enabled(false)` but more readable
272    /// when you specifically want to disable a binding.
273    ///
274    /// # Examples
275    ///
276    /// ```rust
277    /// use bubbletea_widgets::key::Binding;
278    /// use crossterm::event::KeyCode;
279    ///
280    /// let disabled_binding = Binding::new(vec![KeyCode::F(1)])
281    ///     .with_disabled();
282    /// ```
283    pub fn with_disabled(mut self) -> Self {
284        self.enabled = false;
285        self
286    }
287
288    /// Sets the keys for this binding from string representations.
289    ///
290    /// This provides a convenient way to set multiple keys using human-readable
291    /// string representations.
292    ///
293    /// # Arguments
294    ///
295    /// * `keys` - Array of string key representations
296    ///
297    /// # Examples
298    ///
299    /// ```rust
300    /// use bubbletea_widgets::key::Binding;
301    ///
302    /// let binding = Binding::new::<&str>(vec![])
303    ///     .with_keys(&["ctrl+s", "f2", "alt+s"]);
304    /// ```
305    pub fn with_keys(mut self, keys: &[&str]) -> Self {
306        self.keys = keys.iter().map(|k| parse_key_string(k)).collect();
307        self
308    }
309
310    /// Sets the keys for the keybinding (mutable version).
311    ///
312    /// # Arguments
313    ///
314    /// * `keys` - A vector of items that can be converted to `KeyPress`
315    ///
316    /// # Examples
317    ///
318    /// ```rust
319    /// use bubbletea_widgets::key::Binding;
320    /// use crossterm::event::KeyCode;
321    ///
322    /// let mut binding = Binding::new::<&str>(vec![]);
323    /// binding.set_keys(vec![KeyCode::Enter, KeyCode::Char(' ')]);
324    /// ```
325    pub fn set_keys<K: Into<KeyPress>>(&mut self, keys: Vec<K>) {
326        self.keys = keys.into_iter().map(Into::into).collect();
327    }
328
329    /// Returns the key presses associated with this binding.
330    ///
331    /// # Examples
332    ///
333    /// ```rust
334    /// use bubbletea_widgets::key::Binding;
335    /// use crossterm::event::KeyCode;
336    ///
337    /// let binding = Binding::new(vec![KeyCode::Enter]);
338    /// let keys = binding.keys();
339    /// assert_eq!(keys.len(), 1);
340    /// ```
341    pub fn keys(&self) -> &[KeyPress] {
342        &self.keys
343    }
344
345    /// Sets the help text for the keybinding (mutable version).
346    ///
347    /// # Arguments
348    ///
349    /// * `key` - The human-readable key representation for help display
350    /// * `desc` - A brief description of what the binding does
351    ///
352    /// # Examples
353    ///
354    /// ```rust
355    /// use bubbletea_widgets::key::Binding;
356    /// use crossterm::event::KeyCode;
357    ///
358    /// let mut binding = Binding::new(vec![KeyCode::Enter]);
359    /// binding.set_help("enter", "Confirm selection");
360    /// ```
361    pub fn set_help(&mut self, key: impl Into<String>, desc: impl Into<String>) {
362        self.help = Help {
363            key: key.into(),
364            desc: desc.into(),
365        };
366    }
367
368    /// Returns the help information for this binding.
369    ///
370    /// # Examples
371    ///
372    /// ```rust
373    /// use bubbletea_widgets::key::Binding;
374    /// use crossterm::event::KeyCode;
375    ///
376    /// let binding = Binding::new(vec![KeyCode::Enter])
377    ///     .with_help("enter", "Confirm selection");
378    /// let help = binding.help();
379    /// assert_eq!(help.key, "enter");
380    /// assert_eq!(help.desc, "Confirm selection");
381    /// ```
382    pub fn help(&self) -> &Help {
383        &self.help
384    }
385
386    /// Returns `true` if the keybinding is enabled and has keys configured.
387    ///
388    /// Disabled bindings or bindings with no keys will not match key events
389    /// and will not appear in help views.
390    ///
391    /// # Examples
392    ///
393    /// ```rust
394    /// use bubbletea_widgets::key::Binding;
395    /// use crossterm::event::KeyCode;
396    ///
397    /// let binding = Binding::new(vec![KeyCode::Enter]);
398    /// assert!(binding.enabled());
399    ///
400    /// let disabled = Binding::new(vec![KeyCode::F(1)]).with_disabled();
401    /// assert!(!disabled.enabled());
402    ///
403    /// let empty = Binding::new::<KeyCode>(vec![]);
404    /// assert!(!empty.enabled());
405    /// ```
406    pub fn enabled(&self) -> bool {
407        self.enabled && !self.keys.is_empty()
408    }
409
410    /// Sets the enabled state of the keybinding (mutable version).
411    ///
412    /// # Arguments
413    ///
414    /// * `enabled` - Whether the binding should be enabled
415    ///
416    /// # Examples
417    ///
418    /// ```rust
419    /// use bubbletea_widgets::key::Binding;
420    /// use crossterm::event::KeyCode;
421    ///
422    /// let mut binding = Binding::new(vec![KeyCode::F(1)]);
423    /// binding.set_enabled(false);
424    /// assert!(!binding.enabled());
425    /// ```
426    pub fn set_enabled(&mut self, enabled: bool) {
427        self.enabled = enabled;
428    }
429
430    /// Removes all keys and help text, effectively nullifying the binding.
431    ///
432    /// After calling this method, the binding will be disabled and will not
433    /// match any key events or appear in help views.
434    ///
435    /// # Examples
436    ///
437    /// ```rust
438    /// use bubbletea_widgets::key::Binding;
439    /// use crossterm::event::KeyCode;
440    ///
441    /// let mut binding = Binding::new(vec![KeyCode::Enter])
442    ///     .with_help("enter", "Confirm");
443    /// assert!(binding.enabled());
444    ///
445    /// binding.unbind();
446    /// assert!(!binding.enabled());
447    /// assert!(binding.keys().is_empty());
448    /// ```
449    pub fn unbind(&mut self) {
450        self.keys.clear();
451        self.help = Help::default();
452    }
453
454    /// Checks if a `KeyMsg` from `bubbletea-rs` matches this binding.
455    ///
456    /// The match is successful if the binding is enabled and the event's
457    /// `code` and `modifiers` match one of the `KeyPress` entries exactly.
458    ///
459    /// # Arguments
460    ///
461    /// * `key_msg` - The key message to test against this binding
462    ///
463    /// # Returns
464    ///
465    /// `true` if the key message matches this binding, `false` otherwise.
466    ///
467    /// # Examples
468    ///
469    /// ```rust
470    /// use bubbletea_widgets::key::Binding;
471    /// use bubbletea_rs::KeyMsg;
472    /// use crossterm::event::{KeyCode, KeyModifiers};
473    ///
474    /// let binding = Binding::new(vec![(KeyCode::Char('s'), KeyModifiers::CONTROL)]);
475    ///
476    /// let ctrl_s = KeyMsg {
477    ///     key: KeyCode::Char('s'),
478    ///     modifiers: KeyModifiers::CONTROL,
479    /// };
480    ///
481    /// assert!(binding.matches(&ctrl_s));
482    /// ```
483    pub fn matches(&self, key_msg: &KeyMsg) -> bool {
484        if !self.enabled() {
485            return false;
486        }
487        for key_press in &self.keys {
488            if key_msg.key == key_press.code && key_msg.modifiers == key_press.mods {
489                return true;
490            }
491        }
492        false
493    }
494
495    /// A convenience function that checks if a `KeyMsg` matches any of the provided bindings.
496    ///
497    /// # Arguments
498    ///
499    /// * `key_msg` - The key message to test
500    /// * `bindings` - A slice of binding references to check against
501    ///
502    /// # Returns
503    ///
504    /// `true` if the key message matches any of the bindings, `false` otherwise.
505    ///
506    /// # Examples
507    ///
508    /// ```rust
509    /// use bubbletea_widgets::key::Binding;
510    /// use bubbletea_rs::KeyMsg;
511    /// use crossterm::event::{KeyCode, KeyModifiers};
512    ///
513    /// let save = Binding::new(vec![(KeyCode::Char('s'), KeyModifiers::CONTROL)]);
514    /// let quit = Binding::new(vec![(KeyCode::Char('q'), KeyModifiers::CONTROL)]);
515    ///
516    /// let bindings = vec![&save, &quit];
517    /// let ctrl_s = KeyMsg {
518    ///     key: KeyCode::Char('s'),
519    ///     modifiers: KeyModifiers::CONTROL,
520    /// };
521    ///
522    /// assert!(Binding::matches_any(&ctrl_s, &bindings));
523    /// ```
524    pub fn matches_any(key_msg: &KeyMsg, bindings: &[&Self]) -> bool {
525        for binding in bindings {
526            if binding.matches(key_msg) {
527                return true;
528            }
529        }
530        false
531    }
532}
533
534/// KeyMap trait for components that provide help information.
535///
536/// This trait should be implemented by any component that wants to provide
537/// contextual help information through the help component. It matches the
538/// Go implementation's KeyMap interface.
539///
540/// # Examples
541///
542/// ```rust
543/// use bubbletea_widgets::key::{KeyMap, Binding};
544/// use crossterm::event::KeyCode;
545///
546/// struct MyComponent {
547///     save: Binding,
548///     quit: Binding,
549/// }
550///
551/// impl KeyMap for MyComponent {
552///     fn short_help(&self) -> Vec<&Binding> {
553///         vec![&self.save, &self.quit]
554///     }
555///
556///     fn full_help(&self) -> Vec<Vec<&Binding>> {
557///         vec![
558///             vec![&self.save],    // File operations column
559///             vec![&self.quit],    // Application column
560///         ]
561///     }
562/// }
563/// ```
564pub trait KeyMap {
565    /// Returns a slice of bindings to be displayed in the short version of help.
566    ///
567    /// This should return the most important or commonly used key bindings
568    /// that will fit on a single line.
569    fn short_help(&self) -> Vec<&Binding>;
570
571    /// Returns an extended group of help items, grouped by columns.
572    ///
573    /// Each inner vector represents a column of related key bindings. This
574    /// allows for organized display of comprehensive help information.
575    fn full_help(&self) -> Vec<Vec<&Binding>>;
576}
577
578/// Checks if the given KeyMsg matches any of the given bindings.
579///
580/// This is a standalone function similar to Go's Matches function that provides
581/// a convenient way to test a key message against multiple bindings at once.
582///
583/// # Arguments
584///
585/// * `key_msg` - The key message to test
586/// * `bindings` - A slice of binding references to check against
587///
588/// # Returns
589///
590/// `true` if the key message matches any of the bindings, `false` otherwise.
591///
592/// # Examples
593///
594/// ```rust
595/// use bubbletea_widgets::key::{matches, Binding};
596/// use bubbletea_rs::KeyMsg;
597/// use crossterm::event::{KeyCode, KeyModifiers};
598///
599/// let save = Binding::new(vec![(KeyCode::Char('s'), KeyModifiers::CONTROL)]);
600/// let quit = Binding::new(vec![(KeyCode::Char('q'), KeyModifiers::CONTROL)]);
601///
602/// let bindings = vec![&save, &quit];
603/// let ctrl_s = KeyMsg {
604///     key: KeyCode::Char('s'),
605///     modifiers: KeyModifiers::CONTROL,
606/// };
607///
608/// assert!(matches(&ctrl_s, &bindings));
609/// ```
610pub fn matches(key_msg: &KeyMsg, bindings: &[&Binding]) -> bool {
611    for binding in bindings {
612        if (*binding).matches(key_msg) {
613            return true;
614        }
615    }
616    false
617}
618
619/// Creates a new binding from options - Go compatibility function.
620///
621/// This function provides Go-style binding creation using a vector of options.
622/// It's equivalent to calling `Binding::new_binding()` but provides a more
623/// functional programming style interface.
624///
625/// # Arguments
626///
627/// * `opts` - A vector of builder options to configure the binding
628///
629/// # Examples
630///
631/// ```rust
632/// use bubbletea_widgets::key::{new_binding, with_keys_str, with_help};
633///
634/// let save_binding = new_binding(vec![
635///     with_keys_str(&["ctrl+s", "f2"]),
636///     with_help("ctrl+s", "Save the current file"),
637/// ]);
638/// ```
639pub fn new_binding(opts: Vec<BindingOpt>) -> Binding {
640    Binding::new_binding(opts)
641}
642
643/// Creates a binding option that sets the keys from string names.
644///
645/// This function provides Go-style `WithKeys` functionality, allowing you to
646/// set multiple key bindings using human-readable string representations.
647///
648/// # Arguments
649///
650/// * `keys` - Array of string key representations
651///
652/// # Examples
653///
654/// ```rust
655/// use bubbletea_widgets::key::{new_binding, with_keys_str, with_help};
656///
657/// let binding = new_binding(vec![
658///     with_keys_str(&["ctrl+s", "alt+s", "f2"]),
659///     with_help("ctrl+s", "Save file"),
660/// ]);
661/// ```
662pub fn with_keys_str(keys: &[&str]) -> BindingOpt {
663    // Pre-parse into KeyPress so the closure can be 'static
664    let parsed: Vec<KeyPress> = keys.iter().map(|s| parse_key_string(s)).collect();
665    Box::new(move |b: &mut Binding| {
666        b.keys = parsed.clone();
667    })
668}
669
670/// Checks if a KeyMsg matches a specific binding - Go compatibility.
671///
672/// This is a convenience function that provides Go-style compatibility for
673/// checking if a single binding matches a key message.
674///
675/// # Arguments
676///
677/// * `key_msg` - The key message to test
678/// * `binding` - The binding to check against
679///
680/// # Returns
681///
682/// `true` if the key message matches the binding, `false` otherwise.
683///
684/// # Examples
685///
686/// ```rust
687/// use bubbletea_widgets::key::{matches_binding, Binding};
688/// use bubbletea_rs::KeyMsg;
689/// use crossterm::event::{KeyCode, KeyModifiers};
690///
691/// let binding = Binding::new(vec![(KeyCode::Char('s'), KeyModifiers::CONTROL)]);
692/// let ctrl_s = KeyMsg {
693///     key: KeyCode::Char('s'),
694///     modifiers: KeyModifiers::CONTROL,
695/// };
696///
697/// assert!(matches_binding(&ctrl_s, &binding));
698/// ```
699pub fn matches_binding(key_msg: &KeyMsg, binding: &Binding) -> bool {
700    binding.matches(key_msg)
701}
702
703/// Creates a binding option that sets the keys from KeyPress values.
704///
705/// This function allows you to set key bindings using strongly-typed KeyPress
706/// values rather than string representations.
707///
708/// # Arguments
709///
710/// * `keys` - A vector of items that can be converted to KeyPress
711///
712/// # Examples
713///
714/// ```rust
715/// use bubbletea_widgets::key::{new_binding, with_keys, with_help};
716/// use crossterm::event::{KeyCode, KeyModifiers};
717///
718/// let binding = new_binding(vec![
719///     with_keys(vec![
720///         (KeyCode::Char('s'), KeyModifiers::CONTROL),
721///         (KeyCode::F(2), KeyModifiers::NONE),
722///     ]),
723///     with_help("ctrl+s", "Save file"),
724/// ]);
725/// ```
726pub fn with_keys<K: Into<KeyPress> + Clone + 'static>(keys: Vec<K>) -> BindingOpt {
727    Box::new(move |b: &mut Binding| {
728        b.keys = keys.into_iter().map(Into::into).collect();
729    })
730}
731
732/// Creates a binding option that sets the help text.
733///
734/// This function provides Go-style `WithHelp` functionality for setting
735/// the help text that will be displayed in help views.
736///
737/// # Arguments
738///
739/// * `key` - The human-readable key representation for help display
740/// * `desc` - A brief description of what the binding does
741///
742/// # Examples
743///
744/// ```rust
745/// use bubbletea_widgets::key::{new_binding, with_keys_str, with_help};
746///
747/// let binding = new_binding(vec![
748///     with_keys_str(&["ctrl+s"]),
749///     with_help("ctrl+s", "Save the current file"),
750/// ]);
751/// ```
752pub fn with_help(
753    key: impl Into<String> + 'static,
754    desc: impl Into<String> + 'static,
755) -> BindingOpt {
756    Box::new(move |b: &mut Binding| {
757        b.help = Help {
758            key: key.into(),
759            desc: desc.into(),
760        };
761    })
762}
763
764/// Creates a binding option that disables the binding.
765///
766/// This function provides Go-style `WithDisabled` functionality for creating
767/// bindings that are initially disabled.
768///
769/// # Examples
770///
771/// ```rust
772/// use bubbletea_widgets::key::{new_binding, with_keys_str, with_disabled};
773///
774/// let binding = new_binding(vec![
775///     with_keys_str(&["f1"]),
776///     with_disabled(),
777/// ]);
778///
779/// assert!(!binding.enabled());
780/// ```
781pub fn with_disabled() -> BindingOpt {
782    Box::new(|b: &mut Binding| {
783        b.enabled = false;
784    })
785}
786
787/// Parses string representations of keys into KeyPress instances.
788///
789/// This function converts human-readable key descriptions into structured
790/// KeyPress objects. It supports a wide variety of key formats and combinations.
791///
792/// # Supported Formats
793///
794/// ## Simple Keys
795/// - Arrow keys: "up", "down", "left", "right"
796/// - Special keys: "enter", "tab", "esc"/"escape", "space", "backspace"
797/// - Navigation: "home", "end", "pgup"/"pageup", "pgdown"/"pagedown"/"pgdn"
798/// - Function keys: "f1" through "f12"
799/// - Characters: "a", "1", "?", "/", etc.
800///
801/// ## Modifier Combinations
802/// - Single modifier: "ctrl+c", "alt+f4", "shift+tab"
803/// - Double modifiers: "ctrl+alt+a", "ctrl+shift+s"
804///
805/// # Arguments
806///
807/// * `s` - The string representation of the key
808///
809/// # Returns
810///
811/// A `KeyPress` representing the parsed key combination. Unknown keys
812/// result in `KeyCode::Null`.
813///
814/// # Examples
815///
816/// ```rust
817/// use bubbletea_widgets::key::parse_key_string;
818/// use crossterm::event::{KeyCode, KeyModifiers};
819///
820/// let enter = parse_key_string("enter");
821/// let ctrl_c = parse_key_string("ctrl+c");
822/// let alt_f4 = parse_key_string("alt+f4");
823/// let complex = parse_key_string("ctrl+alt+a");
824///
825/// assert_eq!(enter.code, KeyCode::Enter);
826/// assert_eq!(ctrl_c.code, KeyCode::Char('c'));
827/// assert_eq!(ctrl_c.mods, KeyModifiers::CONTROL);
828/// ```
829///
830/// # Panics
831///
832/// This function does not panic. Invalid or unknown key combinations will
833/// result in a KeyPress with `KeyCode::Null`.
834pub fn parse_key_string(s: &str) -> KeyPress {
835    match s {
836        "up" => KeyPress::from(KeyCode::Up),
837        "down" => KeyPress::from(KeyCode::Down),
838        "left" => KeyPress::from(KeyCode::Left),
839        "right" => KeyPress::from(KeyCode::Right),
840        "enter" => KeyPress::from(KeyCode::Enter),
841        "tab" => KeyPress::from(KeyCode::Tab),
842        "backspace" => KeyPress::from(KeyCode::Backspace),
843        "delete" | "del" => KeyPress::from(KeyCode::Delete),
844        "esc" | "escape" => KeyPress::from(KeyCode::Esc),
845        "home" => KeyPress::from(KeyCode::Home),
846        "end" => KeyPress::from(KeyCode::End),
847        "pgup" | "pageup" => KeyPress::from(KeyCode::PageUp),
848        "pgdown" | "pagedown" | "pgdn" => KeyPress::from(KeyCode::PageDown),
849        "f1" => KeyPress::from(KeyCode::F(1)),
850        "f2" => KeyPress::from(KeyCode::F(2)),
851        "f3" => KeyPress::from(KeyCode::F(3)),
852        "f4" => KeyPress::from(KeyCode::F(4)),
853        "f5" => KeyPress::from(KeyCode::F(5)),
854        "f6" => KeyPress::from(KeyCode::F(6)),
855        "f7" => KeyPress::from(KeyCode::F(7)),
856        "f8" => KeyPress::from(KeyCode::F(8)),
857        "f9" => KeyPress::from(KeyCode::F(9)),
858        "f10" => KeyPress::from(KeyCode::F(10)),
859        "f11" => KeyPress::from(KeyCode::F(11)),
860        "f12" => KeyPress::from(KeyCode::F(12)),
861        // Handle compound key combinations
862        key if key.contains('+') => {
863            let parts: Vec<&str> = key.split('+').collect();
864            if parts.len() == 2 {
865                let (modifier_str, key_str) = (parts[0], parts[1]);
866                let modifiers = match modifier_str {
867                    "ctrl" => KeyModifiers::CONTROL,
868                    "alt" => KeyModifiers::ALT,
869                    "shift" => KeyModifiers::SHIFT,
870                    _ => KeyModifiers::NONE,
871                };
872
873                let keycode = match key_str {
874                    "tab" if modifier_str == "shift" => KeyCode::BackTab,
875                    "space" | " " => KeyCode::Char(' '),
876                    key_str if key_str.len() == 1 => KeyCode::Char(key_str.chars().next().unwrap()),
877                    _ => parse_key_string(key_str).code, // Recursive parse for complex keys
878                };
879
880                KeyPress::from((keycode, modifiers))
881            } else if parts.len() == 3 {
882                // Handle ctrl+alt+key combinations
883                let key_str = parts[2];
884                let mut modifiers = KeyModifiers::NONE;
885
886                for part in &parts[0..2] {
887                    match *part {
888                        "ctrl" => modifiers |= KeyModifiers::CONTROL,
889                        "alt" => modifiers |= KeyModifiers::ALT,
890                        "shift" => modifiers |= KeyModifiers::SHIFT,
891                        _ => {}
892                    }
893                }
894
895                let keycode = if key_str.len() == 1 {
896                    KeyCode::Char(key_str.chars().next().unwrap())
897                } else {
898                    parse_key_string(key_str).code
899                };
900
901                KeyPress::from((keycode, modifiers))
902            } else {
903                KeyPress::from(KeyCode::Null)
904            }
905        }
906        " " | "space" => KeyPress::from(KeyCode::Char(' ')),
907        "?" => KeyPress::from(KeyCode::Char('?')),
908        "/" => KeyPress::from(KeyCode::Char('/')),
909        "insert" => KeyPress::from(KeyCode::Insert),
910        "null" => KeyPress::from(KeyCode::Null),
911        // Single characters
912        s if s.len() == 1 => {
913            let ch = s.chars().next().unwrap();
914            KeyPress::from(KeyCode::Char(ch))
915        }
916        _ => KeyPress::from(KeyCode::Null), // Unknown key
917    }
918}