216a11b9db
Plug'n'play modal keybinding system inspired by Doom Emacs + Evil mode.
Generic over consumer Action type. Core: Key parser ("C-d", "SPC"),
trie-based sequence matching with conflict detection, count prefix (5j),
timeout tracking, which-key introspection, and multi-mode dispatch.
78 unit tests covering key parsing, trie conflicts, dispatch state machine,
count accumulation, timeout expiry, and which-key generation.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
79 lines
2.2 KiB
Rust
79 lines
2.2 KiB
Rust
use std::fmt;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum ParseError {
|
|
EmptyInput,
|
|
UnknownKey(String),
|
|
DanglingModifier,
|
|
DuplicateModifier,
|
|
RedundantShift,
|
|
}
|
|
|
|
impl fmt::Display for ParseError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::EmptyInput => write!(f, "empty input"),
|
|
Self::UnknownKey(k) => write!(f, "unknown key: {k}"),
|
|
Self::DanglingModifier => write!(f, "dangling modifier (e.g. \"C-\")"),
|
|
Self::DuplicateModifier => write!(f, "duplicate modifier"),
|
|
Self::RedundantShift => write!(
|
|
f,
|
|
"redundant shift on uppercase char (use \"G\" not \"S-G\")"
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ParseError {}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum BindError {
|
|
EmptySequence,
|
|
ConflictWithLeaf { existing_keys: String },
|
|
ConflictWithPrefix { existing_keys: String },
|
|
Parse(ParseError),
|
|
}
|
|
|
|
impl fmt::Display for BindError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::EmptySequence => write!(f, "empty key sequence"),
|
|
Self::ConflictWithLeaf { existing_keys } => {
|
|
write!(
|
|
f,
|
|
"conflicts with existing leaf binding at \"{existing_keys}\""
|
|
)
|
|
}
|
|
Self::ConflictWithPrefix { existing_keys } => {
|
|
write!(f, "conflicts with existing prefix at \"{existing_keys}\"")
|
|
}
|
|
Self::Parse(e) => write!(f, "parse error: {e}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for BindError {}
|
|
|
|
impl From<ParseError> for BindError {
|
|
fn from(e: ParseError) -> Self {
|
|
Self::Parse(e)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum ModeError {
|
|
UnknownMode(String),
|
|
DuplicateMode(String),
|
|
}
|
|
|
|
impl fmt::Display for ModeError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::UnknownMode(m) => write!(f, "unknown mode: \"{m}\""),
|
|
Self::DuplicateMode(m) => write!(f, "mode already exists: \"{m}\""),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ModeError {}
|