use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crate::action::{DirectKind, Expr, MotionExpr, MotionKind, Operator, Target, Token};
use crate::config::{GOTO_BINDINGS, KeySig, Keymap, OBJECT_BINDINGS, OP_PENDING_BINDINGS, Z_BINDINGS};
use crate::mode::Mode;
#[derive(Debug)]
pub(in crate::app) enum Parse {
Complete(Expr),
Incomplete,
Invalid,
}
#[derive(Debug, Clone, Copy)]
enum ParseCtx {
Initial,
LeaderPending,
OpPending,
ObjectExpected,
GotoPending,
CharArgPending,
ZPending,
}
fn context_of(prev: &[Token]) -> ParseCtx {
use Token::*;
let mut last: Option<&Token> = None;
for t in prev.iter().rev() {
if !matches!(t, Count(_)) {
last = Some(t);
break;
}
}
match last {
None => ParseCtx::Initial,
Some(LeaderPrefix) => ParseCtx::LeaderPending,
Some(Op(_)) => ParseCtx::OpPending,
Some(Scope(_)) => ParseCtx::ObjectExpected,
Some(GotoPrefix) => ParseCtx::GotoPending,
Some(FindCharPrefix { .. } | ReplaceCharPrefix) => ParseCtx::CharArgPending,
Some(ZPrefix) => ParseCtx::ZPending,
_ => ParseCtx::Initial,
}
}
pub(in crate::app) fn tokenize(
km: &Keymap,
prev: &[Token],
mode: Mode,
key: KeyEvent,
) -> Option<Token> {
debug_assert_eq!(mode, Mode::Normal);
if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('r') {
return Some(Token::Direct(DirectKind::Redo));
}
let ctx = context_of(prev);
let code = key.code;
if let Some(c) = ascii_digit(code) {
let already_counting = matches!(prev.last(), Some(Token::Count(_)));
let d = c.to_digit(10).unwrap();
return match (ctx, c, already_counting) {
(ParseCtx::Initial, '0', false) => Some(Token::Motion(MotionKind::LineStart)),
(_, '0', true) => Some(Token::Count(0)),
(ParseCtx::Initial | ParseCtx::OpPending, '1'..='9', _) => Some(Token::Count(d)),
_ => None,
};
}
let sig = KeySig::from_event(key);
match ctx {
ParseCtx::Initial => km.initial.get(&sig).copied(),
ParseCtx::LeaderPending => km.leader.get(&sig).copied(),
ParseCtx::OpPending => op_pending_token(code, prev),
ParseCtx::ObjectExpected => object_token(code),
ParseCtx::GotoPending => goto_pending_token(code),
ParseCtx::CharArgPending => char_arg_token(code, prev),
ParseCtx::ZPending => z_pending_token(code),
}
}
fn char_arg_token(code: KeyCode, prev: &[Token]) -> Option<Token> {
let prefix = prev.iter().rev().find(|t| {
matches!(
t,
Token::FindCharPrefix { .. } | Token::ReplaceCharPrefix
)
})?;
let KeyCode::Char(ch) = code else {
return None;
};
match prefix {
Token::FindCharPrefix { forward, till } => {
Some(Token::Motion(MotionKind::FindChar {
ch,
forward: *forward,
till: *till,
}))
}
Token::ReplaceCharPrefix => Some(Token::Direct(DirectKind::ReplaceChar { ch })),
_ => None,
}
}
fn z_pending_token(code: KeyCode) -> Option<Token> {
Z_BINDINGS
.iter()
.find(|b| b.matches(code))
.map(|b| b.token)
}
fn goto_pending_token(code: KeyCode) -> Option<Token> {
GOTO_BINDINGS
.iter()
.find(|b| b.matches(code))
.map(|b| b.token)
}
fn op_pending_token(code: KeyCode, prev: &[Token]) -> Option<Token> {
let pending_op = prev.iter().rev().find_map(|t| match t {
Token::Op(o) => Some(*o),
_ => None,
})?;
let same_key = matches!(
(pending_op, code),
(Operator::Delete, KeyCode::Char('d'))
| (Operator::Yank, KeyCode::Char('y'))
| (Operator::Change, KeyCode::Char('c'))
| (Operator::Indent, KeyCode::Char('>'))
| (Operator::Dedent, KeyCode::Char('<'))
);
if same_key {
return Some(Token::SelfDouble(pending_op));
}
OP_PENDING_BINDINGS
.iter()
.find(|b| b.matches(code))
.map(|b| b.token)
}
fn ascii_digit(code: KeyCode) -> Option<char> {
match code {
KeyCode::Char(c) if c.is_ascii_digit() => Some(c),
_ => None,
}
}
fn object_token(code: KeyCode) -> Option<Token> {
OBJECT_BINDINGS
.iter()
.find(|b| b.matches(code))
.map(|b| b.token)
}
fn take_count(tokens: &[Token]) -> (u32, &[Token]) {
let mut count: u32 = 0;
let mut i = 0;
while let Some(Token::Count(d)) = tokens.get(i) {
count = count.saturating_mul(10).saturating_add(*d);
i += 1;
}
if i == 0 {
(1, tokens)
} else {
(count.max(1), &tokens[i..])
}
}
pub(in crate::app) fn classify(tokens: &[Token]) -> Parse {
if let Some(expr) = build_expr(tokens) {
return Parse::Complete(expr);
}
if is_valid_prefix(tokens) {
return Parse::Incomplete;
}
Parse::Invalid
}
fn build_expr(tokens: &[Token]) -> Option<Expr> {
use Token::*;
let (outer_count, rest) = take_count(tokens);
match rest {
[Direct(d)] => Some(Expr::Direct {
kind: *d,
count: outer_count,
}),
[Motion(m)] => Some(Expr::Motion(MotionExpr {
motion: *m,
count: outer_count,
})),
[FindCharPrefix { .. }, Motion(m)] => Some(Expr::Motion(MotionExpr {
motion: *m,
count: outer_count,
})),
[LeaderPrefix, Direct(d)] => Some(Expr::Direct {
kind: *d,
count: outer_count,
}),
[GotoPrefix, GotoPrefix] => Some(Expr::Motion(MotionExpr {
motion: MotionKind::FileStart,
count: outer_count,
})),
[GotoPrefix, Direct(d)] => Some(Expr::Direct {
kind: *d,
count: outer_count,
}),
[GotoPrefix, Motion(m)] => Some(Expr::Motion(MotionExpr {
motion: *m,
count: outer_count,
})),
[ZPrefix, Direct(d)] => Some(Expr::Direct {
kind: *d,
count: outer_count,
}),
[ReplaceCharPrefix, Direct(d)] => Some(Expr::Direct {
kind: *d,
count: outer_count,
}),
[Op(op), inner @ ..] => build_op_expr(*op, inner, outer_count),
_ => None,
}
}
fn build_op_expr(op: Operator, after_op: &[Token], outer_count: u32) -> Option<Expr> {
use Token::*;
let (motion_count, body) = take_count(after_op);
match body {
[SelfDouble(_)] => Some(Expr::Op {
op,
target: Target::LineWise,
outer_count: outer_count.saturating_mul(motion_count),
}),
[Motion(m)] => Some(Expr::Op {
op,
target: Target::Motion(MotionExpr {
motion: *m,
count: motion_count,
}),
outer_count,
}),
[FindCharPrefix { .. }, Motion(m)] => Some(Expr::Op {
op,
target: Target::Motion(MotionExpr {
motion: *m,
count: motion_count,
}),
outer_count,
}),
[GotoPrefix, Motion(m)] => Some(Expr::Op {
op,
target: Target::Motion(MotionExpr {
motion: *m,
count: motion_count,
}),
outer_count,
}),
[GotoPrefix, Direct(DirectKind::SearchSelectNext { reverse })] => Some(Expr::Op {
op,
target: Target::SearchMatch { reverse: *reverse },
outer_count: outer_count.saturating_mul(motion_count),
}),
[Scope(s), Object(o)] if motion_count == 1 => Some(Expr::Op {
op,
target: Target::TextObject {
scope: *s,
object: *o,
},
outer_count,
}),
_ => None,
}
}
fn is_valid_prefix(tokens: &[Token]) -> bool {
use Token::*;
let (_, rest) = take_count(tokens);
match rest {
[] => true, [LeaderPrefix] => true, [GotoPrefix] => true, [ZPrefix] => true, [FindCharPrefix { .. }] => true, [ReplaceCharPrefix] => true, [Op(_)] => true, [Op(_), Scope(_)] => true, [Op(_), FindCharPrefix { .. }] => true, [Op(_), GotoPrefix] => true, [Op(_), Count(_), ..] => {
let after_op = &rest[1..];
let (_, after_inner_count) = take_count(after_op);
matches!(
after_inner_count,
[] | [Scope(_)] | [FindCharPrefix { .. }]
)
}
_ => false,
}
}