1#![allow(clippy::unusual_byte_groupings)]
3
4use crate::command_parser::{
5 parse_command_string, CommandParseError, CommandParser, ParsedCommands,
6};
7
8#[path = "keys/defaults.rs"]
9mod defaults;
10#[path = "keys/store.rs"]
11mod store;
12#[path = "keys/string_table.rs"]
13mod string_table;
14
15pub use store::{
16 KeyBinding, KeyBindingDisplay, KeyBindingSortOrder, KeyBindingStore, KeyBindingTable,
17 KeyBindingTableRef,
18};
19use string_table::{
20 decode_mouse_key, key_string_entry_for_key, key_string_search_table, mouse_key_name,
21};
22
23pub type KeyCode = u64;
25
26pub const KEYC_META: KeyCode = 0x0010_0000_0000_00;
28pub const KEYC_CTRL: KeyCode = 0x0020_0000_0000_00;
30pub const KEYC_SHIFT: KeyCode = 0x0040_0000_0000_00;
32pub const KEYC_LITERAL: KeyCode = 0x0100_0000_0000_00;
34pub const KEYC_KEYPAD: KeyCode = 0x0200_0000_0000_00;
36pub const KEYC_CURSOR: KeyCode = 0x0400_0000_0000_00;
38pub const KEYC_IMPLIED_META: KeyCode = 0x0800_0000_0000_00;
40pub const KEYC_BUILD_MODIFIERS: KeyCode = 0x1000_0000_0000_00;
42pub const KEYC_VI: KeyCode = 0x2000_0000_0000_00;
44pub const KEYC_SENT: KeyCode = 0x4000_0000_0000_00;
46
47pub const KEYC_MASK_TYPE: KeyCode = 0x0000_ff00_0000_00;
49pub const KEYC_MASK_MODIFIERS: KeyCode = 0x00ff_0000_0000_00;
51pub const KEYC_MASK_FLAGS: KeyCode = 0xff00_0000_0000_00;
53pub const KEYC_MASK_KEY: KeyCode = 0x0000_ffff_ffff_ff;
55
56const KEYC_NUSER: u32 = 1000;
57
58pub const KEYC_NONE: KeyCode = shift_type(KeyCodeType::Function);
60pub const KEYC_UNKNOWN: KeyCode = KEYC_NONE + 1;
62pub const KEYC_ANY: KeyCode = KEYC_NONE + 4;
64pub const KEYC_BSPACE: KeyCode = KEYC_NONE + 7;
66pub const KEYC_USER: KeyCode = shift_type(KeyCodeType::User);
68
69const KEYC_FOCUS_IN: KeyCode = KEYC_NONE + 2;
70const KEYC_FOCUS_OUT: KeyCode = KEYC_NONE + 3;
71const KEYC_PASTE_START: KeyCode = KEYC_NONE + 5;
72const KEYC_PASTE_END: KeyCode = KEYC_NONE + 6;
73const KEYC_F1: KeyCode = KEYC_NONE + 8;
74const KEYC_F2: KeyCode = KEYC_NONE + 9;
75const KEYC_F3: KeyCode = KEYC_NONE + 10;
76const KEYC_F4: KeyCode = KEYC_NONE + 11;
77const KEYC_F5: KeyCode = KEYC_NONE + 12;
78const KEYC_F6: KeyCode = KEYC_NONE + 13;
79const KEYC_F7: KeyCode = KEYC_NONE + 14;
80const KEYC_F8: KeyCode = KEYC_NONE + 15;
81const KEYC_F9: KeyCode = KEYC_NONE + 16;
82const KEYC_F10: KeyCode = KEYC_NONE + 17;
83const KEYC_F11: KeyCode = KEYC_NONE + 18;
84const KEYC_F12: KeyCode = KEYC_NONE + 19;
85const KEYC_IC: KeyCode = KEYC_NONE + 20;
86const KEYC_DC: KeyCode = KEYC_NONE + 21;
87const KEYC_HOME: KeyCode = KEYC_NONE + 22;
88const KEYC_END: KeyCode = KEYC_NONE + 23;
89const KEYC_NPAGE: KeyCode = KEYC_NONE + 24;
90const KEYC_PPAGE: KeyCode = KEYC_NONE + 25;
91const KEYC_BTAB: KeyCode = KEYC_NONE + 26;
92const KEYC_UP: KeyCode = KEYC_NONE + 27;
93const KEYC_DOWN: KeyCode = KEYC_NONE + 28;
94const KEYC_LEFT: KeyCode = KEYC_NONE + 29;
95const KEYC_RIGHT: KeyCode = KEYC_NONE + 30;
96const KEYC_KP_SLASH: KeyCode = KEYC_NONE + 31;
97const KEYC_KP_STAR: KeyCode = KEYC_NONE + 32;
98const KEYC_KP_MINUS: KeyCode = KEYC_NONE + 33;
99const KEYC_KP_SEVEN: KeyCode = KEYC_NONE + 34;
100const KEYC_KP_EIGHT: KeyCode = KEYC_NONE + 35;
101const KEYC_KP_NINE: KeyCode = KEYC_NONE + 36;
102const KEYC_KP_PLUS: KeyCode = KEYC_NONE + 37;
103const KEYC_KP_FOUR: KeyCode = KEYC_NONE + 38;
104const KEYC_KP_FIVE: KeyCode = KEYC_NONE + 39;
105const KEYC_KP_SIX: KeyCode = KEYC_NONE + 40;
106const KEYC_KP_ONE: KeyCode = KEYC_NONE + 41;
107const KEYC_KP_TWO: KeyCode = KEYC_NONE + 42;
108const KEYC_KP_THREE: KeyCode = KEYC_NONE + 43;
109const KEYC_KP_ENTER: KeyCode = KEYC_NONE + 44;
110const KEYC_KP_ZERO: KeyCode = KEYC_NONE + 45;
111const KEYC_KP_PERIOD: KeyCode = KEYC_NONE + 46;
112const KEYC_REPORT_DARK_THEME: KeyCode = KEYC_NONE + 47;
113const KEYC_REPORT_LIGHT_THEME: KeyCode = KEYC_NONE + 48;
114const KEYC_MOUSE: KeyCode = KEYC_NONE + 49;
115pub const KEYC_DRAGGING: KeyCode = KEYC_NONE + 50;
117
118pub const LIST_KEYS_TEMPLATE: &str = "#{?notes_only,#{key_prefix}#{p|#{key_string_width}:key_string} #{?key_note,#{key_note},#{key_command}},bind-key#{?key_has_repeat, #{?key_repeat,-r, },} -T #{p|#{key_table_width}:key_table} #{p|#{key_string_width}:key_string} #{key_command}}";
120
121#[must_use]
123pub const fn key_code_lookup_bits(key: KeyCode) -> KeyCode {
124 key & (KEYC_MASK_KEY | KEYC_MASK_MODIFIERS)
125}
126
127#[must_use]
129pub fn key_code_is_mouse_move(key: KeyCode) -> bool {
130 matches!(
131 decode_mouse_key(key),
132 Some((MouseEventType::MouseMove, _, _))
133 )
134}
135
136#[must_use]
138pub fn key_string_lookup_string(string: &str) -> Option<KeyCode> {
139 if string.eq_ignore_ascii_case("None") {
140 return Some(KEYC_NONE);
141 }
142 if string.eq_ignore_ascii_case("Any") {
143 return Some(KEYC_ANY);
144 }
145
146 if let Some(hex) = string.strip_prefix("0x") {
147 let value = u32::from_str_radix(hex, 16).ok()?;
148 if value < 32 {
149 return Some(KeyCode::from(value));
150 }
151 return char::from_u32(value).map(|character| character as KeyCode);
152 }
153
154 let mut modifiers = 0;
155 let mut rest = string;
156
157 if rest.starts_with('^') && rest.len() > 1 {
158 if rest.chars().count() == 2 {
159 let character = rest.chars().nth(1)?;
160 if let Some(alias) = control_key_alias(character) {
161 return Some(alias);
162 }
163 return Some(character.to_ascii_lowercase() as KeyCode | KEYC_CTRL);
164 }
165 modifiers |= KEYC_CTRL;
166 rest = &rest[1..];
167 }
168
169 modifiers |= parse_modifiers(&mut rest)?;
170 if rest.is_empty() {
171 return None;
172 }
173
174 if rest.is_ascii() {
175 let bytes = rest.as_bytes();
176 if bytes.len() == 1 {
177 let key = KeyCode::from(bytes[0]);
178 if key < 32 {
179 return None;
180 }
181 if modifiers & KEYC_CTRL != 0 {
182 if let Some(alias) = control_key_alias(char::from(bytes[0])) {
183 return Some(alias | (modifiers & !KEYC_CTRL));
184 }
185 }
186 return Some(key | modifiers);
187 }
188 } else {
189 let mut chars = rest.chars();
190 let character = chars.next()?;
191 if chars.next().is_none() {
192 return Some(character as KeyCode | modifiers);
193 }
194 }
195
196 let mut key = key_string_search_table(rest)?;
197 if modifiers & KEYC_META == 0 {
198 key &= !KEYC_IMPLIED_META;
199 }
200 Some(key | modifiers)
201}
202
203fn control_key_alias(character: char) -> Option<KeyCode> {
204 match character {
205 '[' => Some(0x1b),
206 _ => None,
207 }
208}
209
210#[must_use]
212pub fn key_string_lookup_key(key: KeyCode, with_flags: bool) -> String {
213 let saved = key;
214 let mut output = String::new();
215
216 if key & KEYC_LITERAL != 0 {
217 output.push(char::from_u32((key & 0xff) as u32).unwrap_or('\0'));
218 return maybe_append_flags(output, saved, with_flags);
219 }
220
221 if key & KEYC_CTRL != 0 {
222 output.push_str("C-");
223 }
224 if key & KEYC_META != 0 {
225 output.push_str("M-");
226 }
227 if key & KEYC_SHIFT != 0 {
228 output.push_str("S-");
229 }
230
231 let key = key & KEYC_MASK_KEY;
232 let suffix = match key {
233 KEYC_NONE => Some("None".to_owned()),
234 KEYC_UNKNOWN => Some("Unknown".to_owned()),
235 KEYC_ANY => Some("Any".to_owned()),
236 KEYC_FOCUS_IN => Some("FocusIn".to_owned()),
237 KEYC_FOCUS_OUT => Some("FocusOut".to_owned()),
238 KEYC_PASTE_START => Some("PasteStart".to_owned()),
239 KEYC_PASTE_END => Some("PasteEnd".to_owned()),
240 KEYC_REPORT_DARK_THEME => Some("ReportDarkTheme".to_owned()),
241 KEYC_REPORT_LIGHT_THEME => Some("ReportLightTheme".to_owned()),
242 KEYC_MOUSE => Some("Mouse".to_owned()),
243 KEYC_DRAGGING => Some("Dragging".to_owned()),
244 value if value == make_mouse_key(MouseEventType::MouseMove, 0, MouseLocation::Pane) => {
245 Some("MouseMovePane".to_owned())
246 }
247 value if value == make_mouse_key(MouseEventType::MouseMove, 0, MouseLocation::Status) => {
248 Some("MouseMoveStatus".to_owned())
249 }
250 value
251 if value == make_mouse_key(MouseEventType::MouseMove, 0, MouseLocation::StatusLeft) =>
252 {
253 Some("MouseMoveStatusLeft".to_owned())
254 }
255 value
256 if value
257 == make_mouse_key(MouseEventType::MouseMove, 0, MouseLocation::StatusRight) =>
258 {
259 Some("MouseMoveStatusRight".to_owned())
260 }
261 value
262 if value
263 == make_mouse_key(MouseEventType::MouseMove, 0, MouseLocation::StatusDefault) =>
264 {
265 Some("MouseMoveStatusDefault".to_owned())
266 }
267 value if value == make_mouse_key(MouseEventType::MouseMove, 0, MouseLocation::Border) => {
268 Some("MouseMoveBorder".to_owned())
269 }
270 value if is_user_key(value) => Some(format!("User{}", value - KEYC_USER)),
271 value => key_string_entry_for_key(value)
272 .map(|entry| entry.string.to_owned())
273 .or_else(|| mouse_key_name(value))
274 .or_else(|| {
275 if is_unicode_key(value) {
276 char::from_u32(value as u32).map(|character| character.to_string())
277 } else if value > 255 {
278 Some(format!("Invalid#{saved:#x}"))
279 } else if (33..=126).contains(&value) {
280 Some((value as u8 as char).to_string())
281 } else if value == 127 {
282 Some("C-?".to_owned())
283 } else if value >= 128 {
284 Some(format!("\\{:o}", value))
285 } else {
286 key_string_entry_for_key(value).map(|entry| entry.string.to_owned())
287 }
288 }),
289 };
290
291 if let Some(suffix) = suffix {
292 output.push_str(&suffix);
293 }
294 maybe_append_flags(output, saved, with_flags)
295}
296
297#[must_use]
299pub fn key_code_to_bytes(key: KeyCode) -> Option<Vec<u8>> {
300 let key = key_code_lookup_bits(key);
301 if key == KEYC_NONE || key == KEYC_UNKNOWN || KEYC_IS_MOUSE(key) {
302 return None;
303 }
304
305 let base = key & KEYC_MASK_KEY;
306 if base == b'\r' as u64 && (key & KEYC_MASK_MODIFIERS) == KEYC_SHIFT {
307 return Some(vec![b'\n']);
308 }
309 if key & KEYC_CTRL != 0 {
310 if base == b'?' as u64 {
311 return Some(vec![0x7f]);
312 }
313 if base == b' ' as u64 {
314 return Some(vec![0x00]);
315 }
316 if (b'a' as u64..=b'z' as u64).contains(&base) {
317 return Some(vec![((base as u8) - b'a') + 1]);
318 }
319 if (b'A' as u64..=b'Z' as u64).contains(&base) {
320 return Some(vec![((base as u8) - b'A') + 1]);
321 }
322 }
323
324 match base {
325 value if value == b'\r' as u64 || value == b'\t' as u64 || value == 0x1b => {
326 Some(vec![value as u8])
327 }
328 value if value == KEYC_BSPACE => Some(vec![0x7f]),
329 value if value <= 0x7f => Some(vec![value as u8]),
330 value if is_unicode_key(value) => char::from_u32(value as u32).map(|character| {
331 let mut buffer = [0_u8; 4];
332 character.encode_utf8(&mut buffer).as_bytes().to_vec()
333 }),
334 _ => None,
335 }
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339#[repr(u64)]
340enum KeyCodeType {
341 Unicode = 0,
342 User = 1,
343 Function = 2,
344 MouseMove = 3,
345 MouseDown = 4,
346 MouseUp = 5,
347 MouseDrag = 6,
348 MouseDragEnd = 7,
349 WheelDown = 8,
350 WheelUp = 9,
351 SecondClick = 10,
352 DoubleClick = 11,
353 TripleClick = 12,
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq)]
357#[repr(u64)]
358enum MouseLocation {
359 Pane = 0,
360 Status = 1,
361 StatusLeft = 2,
362 StatusRight = 3,
363 StatusDefault = 4,
364 Border = 5,
365 ScrollbarUp = 6,
366 ScrollbarSlider = 7,
367 ScrollbarDown = 8,
368 Control0 = 9,
369 Control1 = 10,
370 Control2 = 11,
371 Control3 = 12,
372 Control4 = 13,
373 Control5 = 14,
374 Control6 = 15,
375 Control7 = 16,
376 Control8 = 17,
377 Control9 = 18,
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381enum MouseEventType {
382 MouseMove,
383 MouseDown,
384 MouseUp,
385 MouseDrag,
386 MouseDragEnd,
387 WheelDown,
388 WheelUp,
389 SecondClick,
390 DoubleClick,
391 TripleClick,
392}
393
394const fn shift_type(kind: KeyCodeType) -> KeyCode {
395 (kind as KeyCode) << 32
396}
397
398const fn make_mouse_key(kind: MouseEventType, button: u64, location: MouseLocation) -> KeyCode {
399 shift_type(match kind {
400 MouseEventType::MouseMove => KeyCodeType::MouseMove,
401 MouseEventType::MouseDown => KeyCodeType::MouseDown,
402 MouseEventType::MouseUp => KeyCodeType::MouseUp,
403 MouseEventType::MouseDrag => KeyCodeType::MouseDrag,
404 MouseEventType::MouseDragEnd => KeyCodeType::MouseDragEnd,
405 MouseEventType::WheelDown => KeyCodeType::WheelDown,
406 MouseEventType::WheelUp => KeyCodeType::WheelUp,
407 MouseEventType::SecondClick => KeyCodeType::SecondClick,
408 MouseEventType::DoubleClick => KeyCodeType::DoubleClick,
409 MouseEventType::TripleClick => KeyCodeType::TripleClick,
410 }) | (button << 8)
411 | location as u64
412}
413
414const fn strip_flags(key: KeyCode) -> KeyCode {
415 key & !KEYC_MASK_FLAGS
416}
417
418const fn is_unicode_key(key: KeyCode) -> bool {
419 (key & KEYC_MASK_TYPE) == shift_type(KeyCodeType::Unicode) && (key & KEYC_MASK_KEY) > 0x7f
420}
421
422const fn is_user_key(key: KeyCode) -> bool {
423 (key & KEYC_MASK_TYPE) == shift_type(KeyCodeType::User)
424}
425
426#[allow(non_snake_case)]
427const fn KEYC_IS_MOUSE(key: KeyCode) -> bool {
428 (key & KEYC_MASK_KEY) == KEYC_MOUSE
429 || ((key & KEYC_MASK_TYPE) >= shift_type(KeyCodeType::MouseMove)
430 && (key & KEYC_MASK_TYPE) <= shift_type(KeyCodeType::TripleClick))
431}
432
433fn parse_modifiers(rest: &mut &str) -> Option<KeyCode> {
434 let mut modifiers = 0;
435 loop {
436 let bytes = rest.as_bytes();
437 if bytes.len() < 2 || bytes[1] != b'-' {
438 break;
439 }
440 match bytes[0].to_ascii_lowercase() {
441 b'c' => modifiers |= KEYC_CTRL,
442 b'm' => modifiers |= KEYC_META,
443 b's' => modifiers |= KEYC_SHIFT,
444 _ => return None,
445 }
446 *rest = &rest[2..];
447 }
448 Some(modifiers)
449}
450
451fn maybe_append_flags(mut output: String, saved: KeyCode, with_flags: bool) -> String {
452 if with_flags && (saved & KEYC_MASK_FLAGS) != 0 {
453 output.push('[');
454 if saved & KEYC_LITERAL != 0 {
455 output.push('L');
456 }
457 if saved & KEYC_KEYPAD != 0 {
458 output.push('K');
459 }
460 if saved & KEYC_CURSOR != 0 {
461 output.push('C');
462 }
463 if saved & KEYC_IMPLIED_META != 0 {
464 output.push('I');
465 }
466 if saved & KEYC_BUILD_MODIFIERS != 0 {
467 output.push('B');
468 }
469 if saved & KEYC_SENT != 0 {
470 output.push('S');
471 }
472 output.push(']');
473 }
474 output
475}
476
477pub fn parse_binding_command_tokens(
479 tokens: &[String],
480) -> Result<ParsedCommands, CommandParseError> {
481 if tokens.len() == 1 {
482 parse_command_string(&tokens[0])
483 } else {
484 CommandParser::new().parse_arguments(tokens)
485 }
486}
487
488#[cfg(test)]
489#[path = "keys/tests.rs"]
490mod tests;