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}