use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Key {
Char(char),
Enter,
Esc,
Tab,
BackTab,
Backspace,
Delete,
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
F(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Mods {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
}
impl Mods {
pub const NONE: Mods = Mods { ctrl: false, alt: false, shift: false };
pub const CTRL: Mods = Mods { ctrl: true, alt: false, shift: false };
pub const CTRL_SHIFT: Mods = Mods { ctrl: true, alt: false, shift: true };
pub const ALT: Mods = Mods { ctrl: false, alt: true, shift: false };
pub const SHIFT: Mods = Mods { ctrl: false, alt: false, shift: true };
pub fn is_none(self) -> bool {
!self.ctrl && !self.alt && !self.shift
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum KeyContext {
Global,
Project,
Editor,
Agent,
Terminal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KeyChord {
pub key: Key,
pub mods: Mods,
}
impl KeyChord {
pub const fn new(key: Key, mods: Mods) -> Self {
Self { key, mods }
}
pub const fn plain(key: Key) -> Self {
Self { key, mods: Mods::NONE }
}
pub const fn ctrl(key: Key) -> Self {
Self { key, mods: Mods::CTRL }
}
pub const fn ctrl_shift(key: Key) -> Self {
Self { key, mods: Mods::CTRL_SHIFT }
}
pub const fn alt(key: Key) -> Self {
Self { key, mods: Mods::ALT }
}
pub const fn shift(key: Key) -> Self {
Self { key, mods: Mods::SHIFT }
}
}
impl KeyChord {
pub fn is_terminal_ambiguous(&self) -> bool {
if !self.mods.ctrl || self.mods.alt {
return false;
}
if self.mods.shift && matches!(self.key, Key::Char(_)) {
return true;
}
matches!(self.key, Key::Char('i' | 'm' | 'j' | 'h' | '[' | '`' | '@' | ' '))
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Key::Char(' ') => write!(f, "Space"),
Key::Char(c) => write!(f, "{}", c.to_ascii_uppercase()),
Key::Enter => write!(f, "Enter"),
Key::Esc => write!(f, "Esc"),
Key::Tab => write!(f, "Tab"),
Key::BackTab => write!(f, "Shift+Tab"),
Key::Backspace => write!(f, "Backspace"),
Key::Delete => write!(f, "Del"),
Key::Up => write!(f, "\u{2191}"),
Key::Down => write!(f, "\u{2193}"),
Key::Left => write!(f, "\u{2190}"),
Key::Right => write!(f, "\u{2192}"),
Key::Home => write!(f, "Home"),
Key::End => write!(f, "End"),
Key::PageUp => write!(f, "PgUp"),
Key::PageDown => write!(f, "PgDn"),
Key::F(n) => write!(f, "F{n}"),
}
}
}
impl fmt::Display for KeyChord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.mods.ctrl {
write!(f, "Ctrl+")?;
}
if self.mods.alt {
write!(f, "Alt+")?;
}
if self.mods.shift && !matches!(self.key, Key::BackTab) {
write!(f, "Shift+")?;
}
write!(f, "{}", self.key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn legacy_terminals_cannot_distinguish_ctrl_shift_letters_from_ctrl_letters() {
for letter in ['a', 'f', 'p', 'z'] {
assert!(KeyChord::ctrl_shift(Key::Char(letter)).is_terminal_ambiguous());
}
assert!(!KeyChord::plain(Key::F(9)).is_terminal_ambiguous());
assert!(!KeyChord::plain(Key::F(10)).is_terminal_ambiguous());
}
}