feat(evil-keys): add keybinding crate with trie dispatch, count prefix, and timeout
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>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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 {}
|
||||
Reference in New Issue
Block a user