use std::time::{Duration, Instant};
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
#[serde(transparent)]
pub struct KeyModifiers {
bits: u8,
}
impl<'de> Deserialize<'de> for KeyModifiers {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bits = u8::deserialize(deserializer)?;
Self::from_bits(bits).ok_or_else(|| {
serde::de::Error::custom(format_args!(
"KeyModifiers value {bits:#010b} sets bits outside the valid mask {:#010b}",
Self::VALID_MASK
))
})
}
}
impl KeyModifiers {
pub const NONE: Self = Self { bits: 0 };
pub const SHIFT: Self = Self { bits: 0b0000_0001 };
pub const CONTROL: Self = Self { bits: 0b0000_0010 };
pub const ALT: Self = Self { bits: 0b0000_0100 };
pub const SUPER: Self = Self { bits: 0b0000_1000 };
pub const HYPER: Self = Self { bits: 0b0001_0000 };
pub const META: Self = Self { bits: 0b0010_0000 };
const VALID_MASK: u8 = 0b0011_1111;
#[must_use]
pub const fn empty() -> Self {
Self::NONE
}
#[must_use]
pub const fn bits(self) -> u8 {
self.bits
}
#[must_use]
pub const fn from_bits(bits: u8) -> Option<Self> {
if (bits & !Self::VALID_MASK) == 0 {
Some(Self { bits })
} else {
None
}
}
#[must_use]
pub const fn from_bits_truncate(bits: u8) -> Self {
Self {
bits: bits & Self::VALID_MASK,
}
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.bits == 0
}
#[must_use]
pub const fn contains(self, other: Self) -> bool {
(self.bits & other.bits) == other.bits
}
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self {
bits: self.bits | other.bits,
}
}
#[must_use]
pub const fn intersection(self, other: Self) -> Self {
Self {
bits: self.bits & other.bits,
}
}
#[must_use]
pub const fn symmetric_difference(self, other: Self) -> Self {
Self {
bits: self.bits ^ other.bits,
}
}
}
impl std::ops::BitOr for KeyModifiers {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
impl std::ops::BitAnd for KeyModifiers {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
self.intersection(rhs)
}
}
impl std::ops::BitXor for KeyModifiers {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self {
self.symmetric_difference(rhs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum KeyCode {
Char(char),
F(u8),
Backspace,
Enter,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Tab,
BackTab,
Delete,
Insert,
Esc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct KeyEvent {
pub code: KeyCode,
pub modifiers: KeyModifiers,
}
impl KeyEvent {
#[must_use]
pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
Self { code, modifiers }
}
#[must_use]
pub const fn bare(code: KeyCode) -> Self {
Self::new(code, KeyModifiers::NONE)
}
#[must_use]
pub const fn ctrl(ch: char) -> Self {
Self::new(KeyCode::Char(ch), KeyModifiers::CONTROL)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DetachChord {
pub prefix: KeyEvent,
pub detach: KeyEvent,
}
impl DetachChord {
#[must_use]
pub const fn tmux_default() -> Self {
Self {
prefix: KeyEvent::ctrl('b'),
detach: KeyEvent::bare(KeyCode::Char('d')),
}
}
#[must_use]
pub const fn new(prefix: KeyEvent, detach: KeyEvent) -> Self {
Self { prefix, detach }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DetachOutcome {
Forward(Vec<KeyEvent>),
Armed,
DetachRequested,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DetectorState {
Idle,
PrefixHeld { since: Instant },
}
#[derive(Debug, Clone)]
pub struct DetachDetector {
chord: DetachChord,
timeout: Duration,
state: DetectorState,
}
impl DetachDetector {
pub const DEFAULT_TIMEOUT: Duration = Duration::from_millis(1_000);
#[must_use]
pub const fn new(chord: DetachChord) -> Self {
Self::with_timeout(chord, Self::DEFAULT_TIMEOUT)
}
#[must_use]
pub const fn with_timeout(chord: DetachChord, timeout: Duration) -> Self {
Self {
chord,
timeout,
state: DetectorState::Idle,
}
}
#[must_use]
pub const fn chord(&self) -> &DetachChord {
&self.chord
}
#[must_use]
pub const fn timeout(&self) -> Duration {
self.timeout
}
#[must_use]
pub const fn is_prefix_armed(&self) -> bool {
matches!(self.state, DetectorState::PrefixHeld { .. })
}
pub fn reset(&mut self) {
self.state = DetectorState::Idle;
}
#[must_use]
pub fn feed(&mut self, event: KeyEvent, now: Instant) -> DetachOutcome {
if let DetectorState::PrefixHeld { since } = self.state {
if now.saturating_duration_since(since) >= self.timeout {
self.state = DetectorState::Idle;
let mut forwarded = vec![self.chord.prefix];
match self.process_idle(event, now) {
DetachOutcome::Forward(extra) => forwarded.extend(extra),
DetachOutcome::Armed => {}
DetachOutcome::DetachRequested => {
unreachable!("process_idle never returns DetachRequested from idle state",)
}
}
return DetachOutcome::Forward(forwarded);
}
}
match self.state {
DetectorState::Idle => self.process_idle(event, now),
DetectorState::PrefixHeld { .. } => self.process_prefix_held(event),
}
}
#[must_use]
pub fn tick(&mut self, now: Instant) -> DetachOutcome {
match self.state {
DetectorState::Idle => DetachOutcome::Forward(Vec::new()),
DetectorState::PrefixHeld { since } => {
if now.saturating_duration_since(since) >= self.timeout {
self.state = DetectorState::Idle;
DetachOutcome::Forward(vec![self.chord.prefix])
} else {
DetachOutcome::Armed
}
}
}
}
fn process_idle(&mut self, event: KeyEvent, now: Instant) -> DetachOutcome {
if event == self.chord.prefix {
self.state = DetectorState::PrefixHeld { since: now };
DetachOutcome::Armed
} else {
DetachOutcome::Forward(vec![event])
}
}
fn process_prefix_held(&mut self, event: KeyEvent) -> DetachOutcome {
if event == self.chord.detach {
self.state = DetectorState::Idle;
return DetachOutcome::DetachRequested;
}
self.state = DetectorState::Idle;
DetachOutcome::Forward(vec![self.chord.prefix, event])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyConversionError {
UnsupportedKeyCode(&'static str),
UnsupportedModifier(&'static str),
NonPressEvent,
}
impl std::fmt::Display for KeyConversionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsupportedKeyCode(name) => {
write!(f, "unsupported foreign key code: {name}")
}
Self::UnsupportedModifier(name) => {
write!(f, "unsupported foreign modifier: {name}")
}
Self::NonPressEvent => f.write_str("foreign event was not a key press"),
}
}
}
impl std::error::Error for KeyConversionError {}
#[cfg(feature = "crossterm")]
mod crossterm_compat;