diff options
| -rw-r--r-- | crates/atuin/src/command/client/search/interactive.rs | 16 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/search/keybindings/key.rs | 77 | ||||
| -rw-r--r-- | docs/docs/configuration/advanced-key-binding.md | 3 |
3 files changed, 89 insertions, 7 deletions
diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs index d5bf78f4..bcbb1307 100644 --- a/crates/atuin/src/command/client/search/interactive.rs +++ b/crates/atuin/src/command/client/search/interactive.rs @@ -369,12 +369,16 @@ impl State { self.execute_action(&action, settings) } else { // No action matched. In insert-capable modes, insert the character. - if self.is_insert_mode() - && let KeyCodeValue::Char(c) = single.code - && !single.ctrl - && !single.alt - { - self.search.input.insert(c); + if self.is_insert_mode() && !single.ctrl && !single.alt { + match single.code { + KeyCodeValue::Char(c) => { + self.search.input.insert(c); + } + KeyCodeValue::Space => { + self.search.input.insert(' '); + } + _ => {} + } } InputAction::Continue } diff --git a/crates/atuin/src/command/client/search/keybindings/key.rs b/crates/atuin/src/command/client/search/keybindings/key.rs index 945e4156..bd541fbf 100644 --- a/crates/atuin/src/command/client/search/keybindings/key.rs +++ b/crates/atuin/src/command/client/search/keybindings/key.rs @@ -32,6 +32,7 @@ pub enum KeyCodeValue { PageUp, PageDown, Space, + F(u8), } /// A key input that may be a single key or a multi-key sequence (e.g. `g g`). @@ -79,6 +80,7 @@ impl SingleKey { KeyCode::End => KeyCodeValue::End, KeyCode::PageUp => KeyCodeValue::PageUp, KeyCode::PageDown => KeyCodeValue::PageDown, + KeyCode::F(n) => KeyCodeValue::F(n), // For keys we don't handle, store them as a null char _ => KeyCodeValue::Char('\0'), }; @@ -134,6 +136,18 @@ impl SingleKey { "pageup" => KeyCodeValue::PageUp, "pagedown" => KeyCodeValue::PageDown, "space" => KeyCodeValue::Space, + s if s.starts_with('f') && s.len() > 1 => { + // Parse function keys like "f1", "f12" + if let Ok(n) = s[1..].parse::<u8>() { + if (1..=24).contains(&n) { + KeyCodeValue::F(n) + } else { + return Err(format!("function key out of range: {key_part}")); + } + } else { + return Err(format!("unknown key: {key_part}")); + } + } "[" => KeyCodeValue::Char('['), "]" => KeyCodeValue::Char(']'), "?" => KeyCodeValue::Char('?'), @@ -199,6 +213,7 @@ impl fmt::Display for SingleKey { KeyCodeValue::PageUp => write!(f, "pageup"), KeyCodeValue::PageDown => write!(f, "pagedown"), KeyCodeValue::Space => write!(f, "space"), + KeyCodeValue::F(n) => write!(f, "f{n}"), } } } @@ -448,4 +463,66 @@ mod tests { assert!(SingleKey::parse("ctrl-alt-shift-xxx").is_err()); assert!(SingleKey::parse("foobar-a").is_err()); } + + #[test] + fn parse_function_keys() { + let k = SingleKey::parse("f1").unwrap(); + assert_eq!(k.code, KeyCodeValue::F(1)); + assert!(!k.ctrl && !k.alt && !k.shift); + + let k = SingleKey::parse("F12").unwrap(); + assert_eq!(k.code, KeyCodeValue::F(12)); + + let k = SingleKey::parse("ctrl-f5").unwrap(); + assert_eq!(k.code, KeyCodeValue::F(5)); + assert!(k.ctrl); + + // F24 is valid (some keyboards have extended function keys) + let k = SingleKey::parse("f24").unwrap(); + assert_eq!(k.code, KeyCodeValue::F(24)); + + // F0 and F25+ are invalid + assert!(SingleKey::parse("f0").is_err()); + assert!(SingleKey::parse("f25").is_err()); + } + + #[test] + fn from_event_function_keys() { + let event = KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE); + let k = SingleKey::from_event(&event); + assert_eq!(k.code, KeyCodeValue::F(1)); + + let event = KeyEvent::new(KeyCode::F(12), KeyModifiers::CONTROL); + let k = SingleKey::from_event(&event); + assert_eq!(k.code, KeyCodeValue::F(12)); + assert!(k.ctrl); + } + + #[test] + fn display_function_keys() { + let k = SingleKey::parse("f1").unwrap(); + assert_eq!(k.to_string(), "f1"); + + let k = SingleKey::parse("ctrl-f12").unwrap(); + assert_eq!(k.to_string(), "ctrl-f12"); + } + + #[test] + fn function_key_round_trip() { + let cases = ["f1", "f12", "ctrl-f5", "alt-f10"]; + for s in cases { + let k = KeyInput::parse(s).unwrap(); + let display = k.to_string(); + let k2 = KeyInput::parse(&display).unwrap(); + assert_eq!(k, k2, "round-trip failed for {s}"); + } + } + + #[test] + fn from_event_function_key_matches_parsed() { + let event = KeyEvent::new(KeyCode::F(12), KeyModifiers::NONE); + let from_event = SingleKey::from_event(&event); + let parsed = SingleKey::parse("f12").unwrap(); + assert_eq!(from_event, parsed); + } } diff --git a/docs/docs/configuration/advanced-key-binding.md b/docs/docs/configuration/advanced-key-binding.md index ecd7bf8c..c8ad3347 100644 --- a/docs/docs/configuration/advanced-key-binding.md +++ b/docs/docs/configuration/advanced-key-binding.md @@ -5,7 +5,7 @@ Atuin includes a powerful keybinding system that can be used to fully customize The `[keymap]` section in your config replaces the older `[keys]` section. If any `[keymap]` settings are present, the `[keys]` section is ignored entirely. !!! warning - Modifier keys and some special characters work best with a terminal that implements the kitty keyboard protocol. Notably, the default macOS Terminal app does _not_ include this feature. For more information and a list of terminals that are known to support this protocol, see https://sw.kovidgoyal.net/kitty/keyboard-protocol/ + Modifier keys, F1-F24 keys, and some special characters work best - or _only_ work - with a terminal that implements the kitty keyboard protocol. Notably, the default macOS Terminal app does _not_ include this feature. For more information and a list of terminals that are known to support this protocol, see [https://sw.kovidgoyal.net/kitty/keyboard-protocol/](https://sw.kovidgoyal.net/kitty/keyboard-protocol/). ## Keymaps @@ -39,6 +39,7 @@ Lowercase letters, digits, and named keys: "enter", "esc", "tab", "space", "backspace", "delete" "up", "down", "left", "right" "home", "end", "pageup", "pagedown" +"f1", "f2", ... "f12", ... "f24" ``` `return` is an alias for `enter`. `escape` is an alias for `esc`. `del` is an alias for `delete`. |
