Fix vi character search parsing (#483)

* Fix vi character search parsing

Fixes #473

The parser has to consume the items from the iterator to be in a
consistent state!

* Move `ViToTill` logically to `motion.rs`

* Add basic regression test for #473
This commit is contained in:
Stefan Holderbach 2022-09-18 23:42:08 +02:00 committed by GitHub
parent 7d721a10e8
commit 1bf9ad6bc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 120 additions and 46 deletions

View File

@ -1,4 +1,4 @@
use super::{motion::Motion, parser::ReedlineOption, ViToTill};
use super::{motion::Motion, motion::ViToTill, parser::ReedlineOption};
use crate::{EditCommand, ReedlineEvent, Vi};
use std::iter::Peekable;
@ -125,28 +125,40 @@ where
Some('f') => {
let _ = input.next();
match input.peek() {
Some(c) => Some(Command::MoveRightUntil(**c)),
Some(&c) => {
input.next();
Some(Command::MoveRightUntil(*c))
}
None => Some(Command::Incomplete),
}
}
Some('t') => {
let _ = input.next();
match input.peek() {
Some(c) => Some(Command::MoveRightBefore(**c)),
Some(&c) => {
input.next();
Some(Command::MoveRightBefore(*c))
}
None => Some(Command::Incomplete),
}
}
Some('F') => {
let _ = input.next();
match input.peek() {
Some(c) => Some(Command::MoveLeftUntil(**c)),
Some(&c) => {
input.next();
Some(Command::MoveLeftUntil(*c))
}
None => Some(Command::Incomplete),
}
}
Some('T') => {
let _ = input.next();
match input.peek() {
Some(c) => Some(Command::MoveLeftBefore(**c)),
Some(&c) => {
input.next();
Some(Command::MoveLeftBefore(*c))
}
None => Some(Command::Incomplete),
}
}

View File

@ -6,6 +6,8 @@ mod vi_keybindings;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
pub use vi_keybindings::{default_vi_insert_keybindings, default_vi_normal_keybindings};
use self::motion::ViToTill;
use super::EditMode;
use crate::{
edit_mode::{keybindings::Keybindings, vi::parser::parse},
@ -19,43 +21,6 @@ enum ViMode {
Insert,
}
/// Vi left-right motions to or till a character.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ViToTill {
/// f
ToRight(char),
/// F
ToLeft(char),
/// t
TillRight(char),
/// T
TillLeft(char),
}
impl ViToTill {
/// Swap the direction of the to or till for ','
pub fn reverse(&self) -> Self {
match self {
ViToTill::ToRight(c) => ViToTill::ToLeft(*c),
ViToTill::ToLeft(c) => ViToTill::ToRight(*c),
ViToTill::TillRight(c) => ViToTill::TillLeft(*c),
ViToTill::TillLeft(c) => ViToTill::TillRight(*c),
}
}
}
impl From<EditCommand> for Option<ViToTill> {
fn from(edit: EditCommand) -> Self {
match edit {
EditCommand::MoveLeftBefore(c) => Some(ViToTill::TillLeft(c)),
EditCommand::MoveLeftUntil(c) => Some(ViToTill::ToLeft(c)),
EditCommand::MoveRightBefore(c) => Some(ViToTill::TillRight(c)),
EditCommand::MoveRightUntil(c) => Some(ViToTill::ToRight(c)),
_ => None,
}
}
}
/// This parses incoming input `Event`s like a Vi-Style editor
pub struct Vi {
cache: Vec<char>,

View File

@ -1,5 +1,7 @@
use std::iter::Peekable;
use crate::EditCommand;
pub fn parse_motion<'iter, I>(input: &mut Peekable<I>) -> Option<Motion>
where
I: Iterator<Item = &'iter char>,
@ -43,19 +45,43 @@ where
}
Some('f') => {
let _ = input.next();
input.peek().map(|c| Motion::RightUntil(**c))
match input.peek() {
Some(&x) => {
input.next();
Some(Motion::RightUntil(*x))
}
None => None,
}
}
Some('t') => {
let _ = input.next();
input.peek().map(|c| Motion::RightBefore(**c))
match input.peek() {
Some(&x) => {
input.next();
Some(Motion::RightBefore(*x))
}
None => None,
}
}
Some('F') => {
let _ = input.next();
input.peek().map(|c| Motion::LeftUntil(**c))
match input.peek() {
Some(&x) => {
input.next();
Some(Motion::LeftUntil(*x))
}
None => None,
}
}
Some('T') => {
let _ = input.next();
input.peek().map(|c| Motion::LeftBefore(**c))
match input.peek() {
Some(&x) => {
input.next();
Some(Motion::LeftBefore(*x))
}
None => None,
}
}
_ => None,
}
@ -77,3 +103,40 @@ pub enum Motion {
LeftUntil(char),
LeftBefore(char),
}
/// Vi left-right motions to or till a character.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ViToTill {
/// f
ToRight(char),
/// F
ToLeft(char),
/// t
TillRight(char),
/// T
TillLeft(char),
}
impl ViToTill {
/// Swap the direction of the to or till for ','
pub fn reverse(&self) -> Self {
match self {
ViToTill::ToRight(c) => ViToTill::ToLeft(*c),
ViToTill::ToLeft(c) => ViToTill::ToRight(*c),
ViToTill::TillRight(c) => ViToTill::TillLeft(*c),
ViToTill::TillLeft(c) => ViToTill::TillRight(*c),
}
}
}
impl From<EditCommand> for Option<ViToTill> {
fn from(edit: EditCommand) -> Self {
match edit {
EditCommand::MoveLeftBefore(c) => Some(ViToTill::TillLeft(c)),
EditCommand::MoveLeftUntil(c) => Some(ViToTill::ToLeft(c)),
EditCommand::MoveRightBefore(c) => Some(ViToTill::TillRight(c)),
EditCommand::MoveRightUntil(c) => Some(ViToTill::ToRight(c)),
_ => None,
}
}
}

View File

@ -237,6 +237,23 @@ mod tests {
);
}
#[test]
fn test_find_action() {
let input = ['d', 't', 'd'];
let output = vi_parse(&input);
assert_eq!(
output,
ParseResult {
multiplier: None,
command: Some(Command::Delete),
count: None,
motion: Some(Motion::RightBefore('d')),
valid: true
}
);
}
#[test]
fn test_has_garbage() {
let input = ['2', 'd', 'm'];
@ -254,6 +271,23 @@ mod tests {
);
}
#[test]
fn test_find_motion() {
let input = ['2', 'f', 'f'];
let output = vi_parse(&input);
assert_eq!(
output,
ParseResult {
multiplier: Some(2),
command: Some(Command::MoveRightUntil('f')),
count: None,
motion: None,
valid: true
}
);
}
#[test]
fn test_two_up() {
let input = ['2', 'k'];