Something went wrong. Try again.
Vim cannot change; vim cannot stay the same
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462//! Pending-input parser for Normal and Visual mode keystrokes.//!//! The parser owns only the typing state a command needs before it can execute://! an optional leading count, a pending `g`, an operator with its own optional//! motion count, and an awaited `f`/`F`/`t`/`T` target or `r` replacement.//! Resolving motions and editing the document stay with the editor.
use crate::motion::{FindKind, MotionSpec};
/// Counts saturate here so no traversal or allocation is driven by unchecked input.pub const COUNT_LIMIT: usize = 1_000_000;
pub fn append_digit(count: usize, ch: char) -> usize { let digit = ch.to_digit(10).unwrap_or(0) as usize; count .saturating_mul(10) .saturating_add(digit) .min(COUNT_LIMIT)}
pub fn multiply(first: Option<usize>, second: Option<usize>) -> usize { first .unwrap_or(1) .saturating_mul(second.unwrap_or(1)) .min(COUNT_LIMIT)}
/// Digits of an explicitly supplied count, including `1`.////// Callers pass digits only when a count was actually typed, so a recipe can/// distinguish `1dG` from `dG` on replay.pub fn count_digits(count: usize) -> Vec<char> { count.to_string().chars().collect()}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum Operator { Delete, Change, Yank,}
impl Operator { pub fn key(self) -> char { match self { Self::Delete => 'd', Self::Change => 'c', Self::Yank => 'y', } }
pub fn from_key(ch: char) -> Option<Self> { match ch { 'd' => Some(Self::Delete), 'c' => Some(Self::Change), 'y' => Some(Self::Yank), _ => None, } }}
/// How an Insert session starts.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum InsertEntry { /// `i` Before, /// `I` FirstNonBlank, /// `a` After, /// `A` LineEnd, /// `o` OpenBelow, /// `O` OpenAbove, /// `c`, `s`, `C`, or a Visual change: the cursor already sits at the /// insertion boundary left by the change. Change,}
impl InsertEntry { pub fn key(self) -> char { match self { Self::Before => 'i', Self::FirstNonBlank => 'I', Self::After => 'a', Self::LineEnd => 'A', Self::OpenBelow => 'o', Self::OpenAbove => 'O', Self::Change => 'c', } }
pub fn from_key(ch: char) -> Option<Self> { match ch { 'i' => Some(Self::Before), 'I' => Some(Self::FirstNonBlank), 'a' => Some(Self::After), 'A' => Some(Self::LineEnd), 'o' => Some(Self::OpenBelow), 'O' => Some(Self::OpenAbove), _ => None, } }
pub fn is_open(self) -> bool { matches!(self, Self::OpenBelow | Self::OpenAbove) }}
/// Target character for `r`.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum ReplaceTarget { Char(char), Tab, LineBreak,}
/// What the editor should do with one keystroke.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum Step { /// Pending state changed; nothing executes yet. Waiting, /// The prefix was abandoned; nothing executes. Cancel, /// A complete non-motion command such as `x`, `p`, or `i`. Command { key: char, count: Option<usize> }, Motion { spec: MotionSpec, count: Option<usize>, }, Operator { op: Operator, spec: MotionSpec, count: Option<usize>, }, /// `dd`, `cc`, `yy`, and count-collapsed forms such as `2y3y`. OperatorLinewise { op: Operator, count: usize }, OperatorFind { op: Operator, kind: FindKind, target: char, count: usize, }, Find { kind: FindKind, target: char, count: usize, }, Replace { target: ReplaceTarget, /// `Some` only when a count was typed; `1rX` and `rX` are equivalent but /// the presence is kept so repeat recipes stay exact. count: Option<usize>, },}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]enum State { #[default] Idle, Count(usize), Goto(Option<usize>), Operator { op: Operator, count1: Option<usize>, }, OperatorCount { op: Operator, count1: Option<usize>, count2: usize, }, OperatorGoto { op: Operator, count1: Option<usize>, }, OperatorFind { op: Operator, count1: Option<usize>, kind: FindKind, }, Find { kind: FindKind, count: usize, }, Replace { count: Option<usize>, },}
/// One keystroke of pending command state.#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]pub struct Parser { state: State,}
impl Parser { pub fn new() -> Self { Self::default() }
pub fn reset(&mut self) { self.state = State::Idle; }
pub fn is_idle(&self) -> bool { matches!(self.state, State::Idle) }
/// Consumes and returns a pending leading count, if any. pub fn take_count(&mut self) -> Option<usize> { match self.state { State::Count(count) => { self.state = State::Idle; Some(count) } _ => None, } }
/// Compact status text for the pending prefix, e.g. `2d`, `df`, or `2`. pub fn display(&self) -> String { fn prefix(count: Option<usize>) -> String { count.map(|count| count.to_string()).unwrap_or_default() } match self.state { State::Idle => String::new(), State::Count(count) => count.to_string(), State::Goto(count) => format!("{}g", prefix(count)), State::Operator { op, count1 } => format!("{}{}", prefix(count1), op.key()), State::OperatorCount { op, count1, count2 } => { format!("{}{}{}", prefix(count1), op.key(), count2) } State::OperatorGoto { op, count1 } => format!("{}{}g", prefix(count1), op.key()), State::OperatorFind { op, count1, kind } => { format!("{}{}{}", prefix(count1), op.key(), kind.key()) } State::Find { kind, count } => { format!( "{}{}", prefix(Some(count).filter(|count| *count > 1)), kind.key() ) } State::Replace { count } => { format!("{}r", prefix(count.filter(|count| *count > 1))) } } }
/// Feeds one printable key, or returns `Cancel` for an unsupported /// continuation. A canceled prefix never executes its trailing key. pub fn push(&mut self, ch: char) -> Step { match self.state { State::Idle => self.start(ch, None), State::Count(count) => { if ch.is_ascii_digit() { self.state = State::Count(append_digit(count, ch)); Step::Waiting } else { self.start(ch, Some(count)) } } State::Goto(count) => { self.state = State::Idle; if ch == 'g' { Step::Motion { spec: MotionSpec::GotoFirstLine(count), count, } } else { Step::Cancel } } State::Operator { op, count1 } => self.operator_key(op, count1, ch), State::OperatorCount { op, count1, count2 } => { if ch.is_ascii_digit() { self.state = State::OperatorCount { op, count1, count2: append_digit(count2, ch), }; Step::Waiting } else { self.operator_key(op, Some(multiply(count1, Some(count2))), ch) } } State::OperatorGoto { op, count1 } => { self.state = State::Idle; if ch == 'g' { Step::Operator { op, spec: MotionSpec::GotoFirstLine(count1), count: count1, } } else { Step::Cancel } } State::OperatorFind { op, count1, kind } => { self.state = State::Idle; if is_target_char(ch) { Step::OperatorFind { op, kind, target: ch, count: count1.unwrap_or(1), } } else { Step::Cancel } } State::Find { kind, count } => { self.state = State::Idle; if is_target_char(ch) { Step::Find { kind, target: ch, count, } } else { Step::Cancel } } State::Replace { count } => { self.state = State::Idle; if is_target_char(ch) { Step::Replace { target: ReplaceTarget::Char(ch), count, } } else { Step::Cancel } } } }
/// Enter while an `r` target is awaited replaces with one line break. pub fn push_enter(&mut self) -> Step { match self.state { State::Replace { count } => { self.state = State::Idle; Step::Replace { target: ReplaceTarget::LineBreak, count, } } State::Idle => Step::Cancel, _ => { self.state = State::Idle; Step::Cancel } } }
/// Tab while an `r` target is awaited inserts tab characters. pub fn push_tab(&mut self) -> Step { match self.state { State::Replace { count } => { self.state = State::Idle; Step::Replace { target: ReplaceTarget::Tab, count, } } State::Idle => Step::Cancel, _ => { self.state = State::Idle; Step::Cancel } } }
fn start(&mut self, ch: char, count: Option<usize>) -> Step { self.state = State::Idle; match ch { '1'..='9' if count.is_none() => { self.state = State::Count(ch.to_digit(10).unwrap_or(1) as usize); Step::Waiting } 'd' | 'c' | 'y' => { let op = Operator::from_key(ch).expect("operator key"); self.state = State::Operator { op, count1: count }; Step::Waiting } 'g' => { self.state = State::Goto(count); Step::Waiting } 'f' | 'F' | 't' | 'T' => { let kind = FindKind::from_key(ch).expect("find key"); self.state = State::Find { kind, count: count.unwrap_or(1), }; Step::Waiting } 'r' => { self.state = State::Replace { count }; Step::Waiting } _ => match MotionSpec::from_key(ch) { Some(spec) => { let spec = match spec { MotionSpec::LineEnd(_) => MotionSpec::LineEnd(count.unwrap_or(1)), MotionSpec::GotoLastLine(_) => MotionSpec::GotoLastLine(count), other => other, }; Step::Motion { spec, count } } None => Step::Command { key: ch, count }, }, } }
fn operator_key(&mut self, op: Operator, count1: Option<usize>, ch: char) -> Step { self.state = State::Idle; // After an operator, `0` is the line-start motion, not a count digit; a // motion count can only start with 1-9. Once a motion count has started, // further zeros are digits (handled by the OperatorCount state). if ch.is_ascii_digit() && ch != '0' { self.state = State::OperatorCount { op, count1, count2: ch.to_digit(10).unwrap_or(0) as usize, }; return Step::Waiting; } if Operator::from_key(ch) == Some(op) { return Step::OperatorLinewise { op, count: multiply(count1, None), }; } match ch { 'g' => { self.state = State::OperatorGoto { op, count1 }; Step::Waiting } 'f' | 'F' | 't' | 'T' => { let kind = FindKind::from_key(ch).expect("find key"); self.state = State::OperatorFind { op, count1, kind }; Step::Waiting } _ => match MotionSpec::from_key(ch) { Some(spec) => Step::Operator { op, spec, count: count1, }, None => Step::Cancel, }, } }}
fn is_target_char(ch: char) -> bool { !ch.is_control()}