input/
key_bindings.rs

1use crate::{Event, KeyCode, KeyEvent, KeyModifiers};
2
3/// Represents a mapping between an input event and an action.
4#[derive(Debug)]
5#[non_exhaustive]
6pub struct KeyBindings<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent> {
7	/// Key bindings for redoing a change.
8	pub redo: Vec<Event<CustomEvent>>,
9	/// Key bindings for undoing a change.
10	pub undo: Vec<Event<CustomEvent>>,
11
12	/// Key bindings for scrolling down.
13	pub scroll_down: Vec<Event<CustomEvent>>,
14	/// Key bindings for scrolling to the end.
15	pub scroll_end: Vec<Event<CustomEvent>>,
16	/// Key bindings for scrolling to the start.
17	pub scroll_home: Vec<Event<CustomEvent>>,
18	/// Key bindings for scrolling to the left.
19	pub scroll_left: Vec<Event<CustomEvent>>,
20	/// Key bindings for scrolling to the right.
21	pub scroll_right: Vec<Event<CustomEvent>>,
22	/// Key bindings for scrolling up.
23	pub scroll_up: Vec<Event<CustomEvent>>,
24	/// Key bindings for scrolling down a step.
25	pub scroll_step_down: Vec<Event<CustomEvent>>,
26	/// Key bindings for scrolling up a step.
27	pub scroll_step_up: Vec<Event<CustomEvent>>,
28
29	/// Key bindings for help.
30	pub help: Vec<Event<CustomEvent>>,
31
32	/// Key bindings for starting search.
33	pub search_start: Vec<Event<CustomEvent>>,
34	/// Key bindings for next search match.
35	pub search_next: Vec<Event<CustomEvent>>,
36	/// Key bindings for previous search match.
37	pub search_previous: Vec<Event<CustomEvent>>,
38
39	/// Custom keybindings
40	pub custom: CustomKeybinding,
41}
42
43/// Map a keybinding to a list of events.
44#[must_use]
45#[inline]
46#[allow(clippy::string_slice, clippy::missing_panics_doc)]
47pub fn map_keybindings<CustomEvent: crate::CustomEvent>(bindings: &[String]) -> Vec<Event<CustomEvent>> {
48	bindings
49		.iter()
50		.map(|b| {
51			let mut key = String::from(b);
52			let mut modifiers = KeyModifiers::empty();
53			if key.contains("Control") {
54				key = key.replace("Control", "");
55				modifiers.insert(KeyModifiers::CONTROL);
56			}
57			if key.contains("Alt") {
58				key = key.replace("Alt", "");
59				modifiers.insert(KeyModifiers::ALT);
60			}
61			if key.contains("Shift") {
62				key = key.replace("Shift", "");
63				modifiers.insert(KeyModifiers::SHIFT);
64			}
65
66			let code = match key.as_str() {
67				"Backspace" => KeyCode::Backspace,
68				"BackTab" => KeyCode::BackTab,
69				"Delete" => KeyCode::Delete,
70				"Down" => KeyCode::Down,
71				"End" => KeyCode::End,
72				"Enter" => KeyCode::Enter,
73				"Esc" => KeyCode::Esc,
74				"Home" => KeyCode::Home,
75				"Insert" => KeyCode::Insert,
76				"Left" => KeyCode::Left,
77				"PageDown" => KeyCode::PageDown,
78				"PageUp" => KeyCode::PageUp,
79				"Right" => KeyCode::Right,
80				"Tab" => KeyCode::Tab,
81				"Up" => KeyCode::Up,
82				// assume that this is an F key
83				k if k.len() > 1 => {
84					let key_number = k[1..].parse::<u8>().unwrap_or(1);
85					KeyCode::F(key_number)
86				},
87				k => KeyCode::Char(k.chars().next().expect("Expected only one character from Char KeyCode")),
88			};
89			Event::Key(KeyEvent::new(code, modifiers))
90		})
91		.collect()
92}
93
94impl<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent>
95	KeyBindings<CustomKeybinding, CustomEvent>
96{
97	/// Create a new instance from the configuration keybindings.
98	#[inline]
99	#[must_use]
100	pub fn new(key_bindings: &config::KeyBindings) -> Self {
101		Self {
102			redo: map_keybindings(&key_bindings.redo),
103			undo: map_keybindings(&key_bindings.undo),
104			scroll_down: map_keybindings(&key_bindings.scroll_down),
105			scroll_end: map_keybindings(&key_bindings.scroll_end),
106			scroll_home: map_keybindings(&key_bindings.scroll_home),
107			scroll_left: map_keybindings(&key_bindings.scroll_left),
108			scroll_right: map_keybindings(&key_bindings.scroll_right),
109			scroll_up: map_keybindings(&key_bindings.scroll_up),
110			scroll_step_down: map_keybindings(&key_bindings.scroll_step_down),
111			scroll_step_up: map_keybindings(&key_bindings.scroll_step_up),
112			help: map_keybindings(&key_bindings.help),
113			search_start: map_keybindings(&key_bindings.search_start),
114			search_next: map_keybindings(&key_bindings.search_next),
115			search_previous: map_keybindings(&key_bindings.search_previous),
116			custom: CustomKeybinding::new(key_bindings),
117		}
118	}
119}
120
121#[cfg(test)]
122mod tests {
123	use rstest::rstest;
124
125	use super::*;
126	use crate::testutil::local::{TestEvent, TestKeybinding};
127
128	#[test]
129	fn new() {
130		let _key_bindings = KeyBindings::<TestKeybinding, TestEvent>::new(&config::KeyBindings::new());
131	}
132
133	#[test]
134	fn map_keybindings_with_modifiers() {
135		assert_eq!(
136			map_keybindings::<TestEvent>(&[String::from("ControlAltShiftUp")]),
137			vec![Event::Key(KeyEvent::new(
138				KeyCode::Up,
139				KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
140			))]
141		);
142	}
143
144	#[rstest]
145	#[case::backspace("Backspace", KeyCode::Backspace)]
146	#[case::back_tab("BackTab", KeyCode::BackTab)]
147	#[case::delete("Delete", KeyCode::Delete)]
148	#[case::down("Down", KeyCode::Down)]
149	#[case::end("End", KeyCode::End)]
150	#[case::enter("Enter", KeyCode::Enter)]
151	#[case::esc("Esc", KeyCode::Esc)]
152	#[case::home("Home", KeyCode::Home)]
153	#[case::insert("Insert", KeyCode::Insert)]
154	#[case::left("Left", KeyCode::Left)]
155	#[case::page_down("PageDown", KeyCode::PageDown)]
156	#[case::page_up("PageUp", KeyCode::PageUp)]
157	#[case::right("Right", KeyCode::Right)]
158	#[case::tab("Tab", KeyCode::Tab)]
159	#[case::up("Up", KeyCode::Up)]
160	#[case::function_in_range("F10", KeyCode::F(10))]
161	#[case::function_out_of_range("F10000", KeyCode::F(1))]
162	#[case::char("a", KeyCode::Char('a'))]
163	fn map_keybindings_key_code(#[case] binding: &str, #[case] key_code: KeyCode) {
164		assert_eq!(map_keybindings::<TestEvent>(&[String::from(binding)]), vec![
165			Event::from(key_code)
166		]);
167	}
168}