Skip to main content

rusty_bubbles/
key.rs

1//! Cleanroom Rust port of upstream Go source file: `key/key.go`
2//! Upstream Target Tag / Version: `v2.1.0`
3//!
4//! <public-docs>
5//! # Keymappings
6//!
7//! Types and functions for generating user-definable keymappings useful in
8//! Bubble Tea components. There are a few different ways you can define a
9//! keymapping with this package. Here's one example:
10//!
11//! ```rust
12//! use rusty_bubbles::key;
13//!
14//! struct KeyMap {
15//!     up: key::Binding,
16//!     down: key::Binding,
17//! }
18//!
19//! fn default_key_map() -> KeyMap {
20//!     KeyMap {
21//!         // actual keybindings
22//!         up: key::new_binding(
23//!             key::with_keys(&["k", "up"]),
24//!             // corresponding help text
25//!             key::with_help("↑/k", "move up"),
26//!         ),
27//!         down: key::new_binding(
28//!             key::with_keys(&["j", "down"]),
29//!             key::with_help("↓/j", "move down"),
30//!         ),
31//!     }
32//! }
33//! ```
34//!
35//! The help information, which is not used in the example above, can be used
36//! to render help text for keystrokes in your views.
37//! </public-docs>
38
39use std::fmt;
40
41/// Binding describes a set of keybindings and, optionally, their associated
42/// help text.
43#[derive(Debug, Clone)]
44pub struct Binding {
45    keys: Vec<String>,
46    help: Help,
47    disabled: bool,
48}
49
50/// BindingOpt is an initialization option for a keybinding. It's used as an
51/// argument to [`new_binding`].
52pub type BindingOpt = Box<dyn FnOnce(&mut Binding)>;
53
54/// NewBinding returns a new keybinding from a set of BindingOpt options.
55pub fn new_binding(opts: Vec<BindingOpt>) -> Binding {
56    let mut b = Binding {
57        keys: Vec::new(),
58        help: Help {
59            key: String::new(),
60            desc: String::new(),
61        },
62        disabled: false,
63    };
64    for opt in opts {
65        opt(&mut b);
66    }
67    b
68}
69
70/// WithKeys initializes a keybinding with the given keystrokes.
71pub fn with_keys(keys: &[&str]) -> BindingOpt {
72    let keys: Vec<String> = keys.iter().map(|k| k.to_string()).collect();
73    Box::new(move |b| {
74        b.keys = keys;
75    })
76}
77
78/// WithHelp initializes a keybinding with the given help text.
79pub fn with_help(key: &str, desc: &str) -> BindingOpt {
80    let key = key.to_string();
81    let desc = desc.to_string();
82    Box::new(move |b| {
83        b.help = Help { key, desc };
84    })
85}
86
87/// WithDisabled initializes a disabled keybinding.
88pub fn with_disabled() -> BindingOpt {
89    Box::new(|b| {
90        b.disabled = true;
91    })
92}
93
94impl Binding {
95    /// SetKeys sets the keys for the keybinding.
96    pub fn set_keys(&mut self, keys: &[&str]) {
97        self.keys = keys.iter().map(|k| k.to_string()).collect();
98    }
99
100    /// Keys returns the keys for the keybinding.
101    pub fn keys(&self) -> Vec<String> {
102        self.keys.clone()
103    }
104
105    /// SetHelp sets the help text for the keybinding.
106    pub fn set_help(&mut self, key: &str, desc: &str) {
107        self.help = Help {
108            key: key.to_string(),
109            desc: desc.to_string(),
110        };
111    }
112
113    /// Help returns the Help information for the keybinding.
114    pub fn help(&self) -> Help {
115        self.help.clone()
116    }
117
118    /// Enabled returns whether or not the keybinding is enabled. Disabled
119    /// keybindings won't be activated and won't show up in help. Keybindings
120    /// are enabled by default.
121    pub fn enabled(&self) -> bool {
122        !self.disabled && !self.keys.is_empty()
123    }
124
125    /// SetEnabled enables or disables the keybinding.
126    pub fn set_enabled(&mut self, v: bool) {
127        self.disabled = !v;
128    }
129
130    /// Unbind removes the keys and help from this binding, effectively
131    /// nullifying it. This is a step beyond disabling it, since applications
132    /// can enable or disable key bindings based on application state.
133    pub fn unbind(&mut self) {
134        self.keys.clear();
135        self.help = Help {
136            key: String::new(),
137            desc: String::new(),
138        };
139    }
140}
141
142/// Help is help information for a given keybinding.
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct Help {
145    /// The key(s) used for the binding, e.g. "↑/k".
146    pub key: String,
147    /// A short description of the action, e.g. "move up".
148    pub desc: String,
149}
150
151/// Matches checks if the given key matches the given bindings.
152pub fn matches<K: fmt::Display>(k: K, bindings: &[Binding]) -> bool {
153    let keys = k.to_string();
154    for binding in bindings {
155        for v in &binding.keys {
156            if keys == *v && binding.enabled() {
157                return true;
158            }
159        }
160    }
161    false
162}