#![warn(missing_docs)]
pub mod parser;
pub mod reader;
mod kitty;
mod legacy;
mod mouse;
mod params;
#[cfg(test)]
mod editor_matrix_tests;
#[cfg(test)]
mod parser_tests;
pub use parser::{Parser, Pending};
pub use reader::{probe_active, EventReader};
use crate::base::{Point, Size};
use crate::term::caps::CapsReply;
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
pub struct Mods(pub u8);
impl Mods {
pub const NONE: Mods = Mods(0);
pub const SHIFT: Mods = Mods(1);
pub const ALT: Mods = Mods(2);
pub const CTRL: Mods = Mods(4);
pub const SUPER: Mods = Mods(8);
pub const HYPER: Mods = Mods(16);
pub const META: Mods = Mods(32);
pub const CAPS_LOCK: Mods = Mods(64);
pub const NUM_LOCK: Mods = Mods(128);
pub const fn contains(self, other: Mods) -> bool {
self.0 & other.0 == other.0
}
pub const fn union(self, other: Mods) -> Mods {
Mods(self.0 | other.0)
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn without_locks(self) -> Mods {
Mods(self.0 & !(Self::CAPS_LOCK.0 | Self::NUM_LOCK.0))
}
}
impl std::ops::BitOr for Mods {
type Output = Mods;
fn bitor(self, rhs: Mods) -> Mods {
self.union(rhs)
}
}
impl std::fmt::Debug for Mods {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_empty() {
return write!(f, "Mods(none)");
}
let names = [
(Self::SHIFT, "shift"),
(Self::ALT, "alt"),
(Self::CTRL, "ctrl"),
(Self::SUPER, "super"),
(Self::HYPER, "hyper"),
(Self::META, "meta"),
(Self::CAPS_LOCK, "caps"),
(Self::NUM_LOCK, "num"),
];
let mut first = true;
write!(f, "Mods(")?;
for (m, n) in names {
if self.contains(m) {
if !first {
write!(f, "+")?;
}
write!(f, "{n}")?;
first = false;
}
}
write!(f, ")")
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)] pub enum ModifierKey {
LeftShift,
LeftCtrl,
LeftAlt,
LeftSuper,
LeftHyper,
LeftMeta,
RightShift,
RightCtrl,
RightAlt,
RightSuper,
RightHyper,
RightMeta,
IsoLevel3Shift,
IsoLevel5Shift,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum KeyCode {
Char(char),
Enter,
Tab,
Backspace,
Esc,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Insert,
Delete,
F(u8),
CapsLock,
ScrollLock,
NumLock,
PrintScreen,
Pause,
Menu,
Modifier(ModifierKey),
Functional(u32),
Unidentified,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub enum KeyEventKind {
#[default]
Press,
Repeat,
Release,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyEvent {
pub code: KeyCode,
pub mods: Mods,
pub kind: KeyEventKind,
pub text: Option<String>,
pub keypad: bool,
}
impl KeyEvent {
pub fn new(code: KeyCode, mods: Mods) -> Self {
KeyEvent {
code,
mods,
kind: KeyEventKind::Press,
text: None,
keypad: false,
}
}
pub fn plain(code: KeyCode) -> Self {
Self::new(code, Mods::NONE)
}
pub fn char(c: char) -> Self {
Self::plain(KeyCode::Char(c))
}
pub fn key(code: KeyCode) -> Self {
Self::plain(code)
}
pub fn with_mods(mut self, mods: Mods) -> Self {
self.mods = mods;
self
}
pub fn with_kind(mut self, kind: KeyEventKind) -> Self {
self.kind = kind;
self
}
pub fn with_text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
pub fn on_keypad(mut self) -> Self {
self.keypad = true;
self
}
pub fn is_press(&self) -> bool {
self.kind == KeyEventKind::Press
}
pub fn is_repeat(&self) -> bool {
self.kind == KeyEventKind::Repeat
}
pub fn is_release(&self) -> bool {
self.kind == KeyEventKind::Release
}
pub fn is_down(&self) -> bool {
matches!(self.kind, KeyEventKind::Press | KeyEventKind::Repeat)
}
pub fn chord_matches(&self, code: KeyCode, mods: Mods) -> bool {
self.is_down() && self.code == code && self.mods.without_locks() == mods.without_locks()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum MouseButton {
Left,
Middle,
Right,
Back,
Forward,
None,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum MouseKind {
Down,
Up,
Drag,
Move,
WheelUp,
WheelDown,
WheelLeft,
WheelRight,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MouseEvent {
pub kind: MouseKind,
pub button: MouseButton,
pub pos: Point,
pub pixel: Option<Point>,
pub mods: Mods,
}
impl MouseEvent {
pub fn new(kind: MouseKind, button: MouseButton, pos: Point, mods: Mods) -> Self {
MouseEvent {
kind,
button,
pos,
pixel: None,
mods,
}
}
pub fn with_pixel(mut self, pixel: Point) -> Self {
self.pixel = Some(pixel);
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Event {
Key(KeyEvent),
Mouse(MouseEvent),
Paste(String),
FocusGained,
FocusLost,
Resize(Size),
CapsReply(CapsReply),
Unknown(Vec<u8>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mods_ops() {
let m = Mods::CTRL | Mods::SHIFT | Mods::CAPS_LOCK;
assert!(m.contains(Mods::CTRL) && m.contains(Mods::SHIFT));
assert!(!m.contains(Mods::ALT));
assert_eq!(m.without_locks(), Mods::CTRL | Mods::SHIFT);
assert_eq!(format!("{:?}", Mods::CTRL | Mods::ALT), "Mods(alt+ctrl)");
assert_eq!(format!("{:?}", Mods::NONE), "Mods(none)");
}
#[test]
fn key_event_kind_helpers() {
let mut k = KeyEvent::new(KeyCode::Char('s'), Mods::CTRL);
assert!(k.is_press() && k.is_down() && !k.is_repeat() && !k.is_release());
k.kind = KeyEventKind::Repeat;
assert!(!k.is_press() && k.is_down() && k.is_repeat());
k.kind = KeyEventKind::Release;
assert!(!k.is_down() && k.is_release());
}
#[test]
fn chord_matching_ignores_locks_and_releases() {
let mut k = KeyEvent::new(KeyCode::Char('s'), Mods::CTRL | Mods::CAPS_LOCK);
assert!(k.chord_matches(KeyCode::Char('s'), Mods::CTRL));
assert!(!k.chord_matches(KeyCode::Char('s'), Mods::CTRL | Mods::ALT));
assert!(!k.chord_matches(KeyCode::Char('x'), Mods::CTRL));
k.kind = KeyEventKind::Repeat;
assert!(k.chord_matches(KeyCode::Char('s'), Mods::CTRL));
k.kind = KeyEventKind::Release;
assert!(!k.chord_matches(KeyCode::Char('s'), Mods::CTRL));
}
}