From c0eff4a9a35e55763733874b904ad6863c5360fe Mon Sep 17 00:00:00 2001 From: Kyler Clay Date: Fri, 23 May 2025 09:47:05 -0400 Subject: [PATCH] implemented verb and motion repetition --- src/prompt/readline/linebuf.rs | 6 +---- src/prompt/readline/mod.rs | 47 ++++++++++++++++++++++++++-------- src/prompt/readline/mode.rs | 36 ++++++++++++++++++++++++++ src/prompt/readline/vicmd.rs | 45 +++++++++++++++++++++++++++++--- 4 files changed, 115 insertions(+), 19 deletions(-) diff --git a/src/prompt/readline/linebuf.rs b/src/prompt/readline/linebuf.rs index 48ef3a4..2bdf644 100644 --- a/src/prompt/readline/linebuf.rs +++ b/src/prompt/readline/linebuf.rs @@ -908,7 +908,7 @@ impl LineBuf { Motion::BeginningOfBuffer => MotionKind::To(0), Motion::EndOfBuffer => MotionKind::To(self.buffer.len().saturating_sub(1)), Motion::Null => MotionKind::Null, - Motion::Builder(_) => unreachable!(), + _ => unreachable!(), } } pub fn exec_verb(&mut self, verb: Verb, motion: MotionKind, register: RegisterName) -> ShResult<()> { @@ -1100,12 +1100,8 @@ impl LineBuf { self.undo_stack.push(diff); return }; - dbg!("old"); - dbg!(&edit); edit.new.append(&mut diff.new); - dbg!("new"); - dbg!(&edit); self.undo_stack.push(edit); } else { diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index 56d65ea..35a11b1 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -4,7 +4,7 @@ use linebuf::{strip_ansi_codes_and_escapes, LineBuf, TermCharBuf}; use mode::{CmdReplay, ViInsert, ViMode, ViNormal}; use term::Terminal; use unicode_width::UnicodeWidthStr; -use vicmd::{MotionCmd, RegisterName, Verb, VerbCmd, ViCmd}; +use vicmd::{Motion, MotionCmd, RegisterName, Verb, VerbCmd, ViCmd}; use crate::libsh::{error::{ShErr, ShErrKind, ShResult}, term::{Style, Styled}}; use crate::prelude::*; @@ -142,11 +142,12 @@ impl FernVi { if mode.is_repeatable() { self.last_action = mode.as_replay(); } + return self.line.exec_cmd(cmd); } else if cmd.is_cmd_repeat() { let Some(replay) = self.last_action.clone() else { return Ok(()) }; - let ViCmd { register, verb, motion, raw_seq } = cmd; + let ViCmd { verb, .. } = cmd; let VerbCmd(count,_) = verb.unwrap(); match replay { CmdReplay::ModeReplay { cmds, mut repeat } => { @@ -164,8 +165,12 @@ impl FernVi { if count > 1 { // Override the counts with the one passed to the '.' command if cmd.verb.is_some() { - cmd.verb.as_mut().map(|v| v.0 = count); - cmd.motion.as_mut().map(|m| m.0 = 0); + if let Some(v_mut) = cmd.verb.as_mut() { + v_mut.0 = count + } + if let Some(m_mut) = cmd.motion.as_mut() { + m_mut.0 = 0 + } } else { return Ok(()) // it has to have a verb to be repeatable, something weird happened } @@ -176,8 +181,8 @@ impl FernVi { } return Ok(()) } else if cmd.is_motion_repeat() { - match cmd.verb.as_ref().unwrap().1 { - Verb::RepeatMotion => { + match cmd.motion.as_ref().unwrap() { + MotionCmd(count,Motion::RepeatMotion) => { let Some(motion) = self.last_movement.clone() else { return Ok(()) }; @@ -185,15 +190,35 @@ impl FernVi { register: RegisterName::default(), verb: None, motion: Some(motion), - raw_seq: ";".into() + raw_seq: format!("{count};") }; - self.line.exec_cmd(repeat_cmd)?; + return self.line.exec_cmd(repeat_cmd); + } + MotionCmd(count,Motion::RepeatMotionRev) => { + let Some(motion) = self.last_movement.clone() else { + return Ok(()) + }; + let mut new_motion = motion.invert_char_motion(); + new_motion.0 = *count; + let repeat_cmd = ViCmd { + register: RegisterName::default(), + verb: None, + motion: Some(new_motion), + raw_seq: format!("{count},") + }; + return self.line.exec_cmd(repeat_cmd); } - Verb::RepeatMotionRev => {} _ => unreachable!() } } - self.line.exec_cmd(cmd.clone())?; - Ok(()) + + if cmd.is_repeatable() { + self.last_action = Some(CmdReplay::Single(cmd.clone())); + } + if cmd.is_char_search() { + self.last_movement = cmd.motion.clone() + } + + self.line.exec_cmd(cmd.clone()) } } diff --git a/src/prompt/readline/mode.rs b/src/prompt/readline/mode.rs index 72b2632..35806aa 100644 --- a/src/prompt/readline/mode.rs +++ b/src/prompt/readline/mode.rs @@ -383,6 +383,42 @@ impl ViNormal { break 'motion_parse None } } + 'f' => { + let Some(ch) = chars_clone.peek() else { + break 'motion_parse None + }; + + break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Forward, Dest::On, (*ch).into()))) + } + 'F' => { + let Some(ch) = chars_clone.peek() else { + break 'motion_parse None + }; + + break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Backward, Dest::On, (*ch).into()))) + } + 't' => { + let Some(ch) = chars_clone.peek() else { + break 'motion_parse None + }; + + break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Forward, Dest::Before, (*ch).into()))) + } + 'T' => { + let Some(ch) = chars_clone.peek() else { + break 'motion_parse None + }; + + break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Backward, Dest::Before, (*ch).into()))) + } + ';' => { + chars = chars_clone; + break 'motion_parse Some(MotionCmd(count, Motion::RepeatMotion)); + } + ',' => { + chars = chars_clone; + break 'motion_parse Some(MotionCmd(count, Motion::RepeatMotionRev)); + } '|' => { chars = chars_clone; break 'motion_parse Some(MotionCmd(1, Motion::ToColumn(count))); diff --git a/src/prompt/readline/vicmd.rs b/src/prompt/readline/vicmd.rs index 26147fe..f117ec3 100644 --- a/src/prompt/readline/vicmd.rs +++ b/src/prompt/readline/vicmd.rs @@ -82,11 +82,17 @@ impl ViCmd { pub fn motion_count(&self) -> usize { self.motion.as_ref().map(|m| m.0).unwrap_or(1) } + pub fn is_repeatable(&self) -> bool { + self.verb.as_ref().is_some_and(|v| v.1.is_repeatable()) + } pub fn is_cmd_repeat(&self) -> bool { self.verb.as_ref().is_some_and(|v| matches!(v.1,Verb::RepeatLast)) } pub fn is_motion_repeat(&self) -> bool { - self.verb.as_ref().is_some_and(|v| matches!(v.1,Verb::RepeatMotion | Verb::RepeatMotionRev)) + self.motion.as_ref().is_some_and(|m| matches!(m.1,Motion::RepeatMotion | Motion::RepeatMotionRev)) + } + pub fn is_char_search(&self) -> bool { + self.motion.as_ref().is_some_and(|m| matches!(m.1, Motion::CharSearch(..))) } pub fn should_submit(&self) -> bool { self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::AcceptLine)) @@ -112,6 +118,19 @@ pub struct VerbCmd(pub usize,pub Verb); #[derive(Clone,Debug)] pub struct MotionCmd(pub usize,pub Motion); +impl MotionCmd { + pub fn invert_char_motion(self) -> Self { + let MotionCmd(count,Motion::CharSearch(dir, dest, ch)) = self else { + unreachable!() + }; + let new_dir = match dir { + Direction::Forward => Direction::Backward, + Direction::Backward => Direction::Forward, + }; + MotionCmd(count,Motion::CharSearch(new_dir, dest, ch)) + } +} + #[derive(Debug, Clone, Eq, PartialEq)] #[non_exhaustive] pub enum Verb { @@ -127,8 +146,6 @@ pub enum Verb { Undo, Redo, RepeatLast, - RepeatMotion, - RepeatMotionRev, Put(Anchor), OverwriteMode, InsertMode, @@ -161,6 +178,26 @@ impl Verb { Self::Yank ) } + pub fn is_repeatable(&self) -> bool { + matches!(self, + Self::Delete | + Self::DeleteChar(_) | + Self::Change | + Self::ReplaceChar(_) | + Self::Substitute | + Self::ToggleCase | + Self::Put(_) | + Self::OverwriteMode | + Self::InsertModeLineBreak(_) | + Self::JoinLines | + Self::InsertChar(_) | + Self::Insert(_) | + Self::Breakline(_) | + Self::Indent | + Self::Dedent | + Self::Equalize + ) + } pub fn is_edit(&self) -> bool { matches!(self, Self::Delete | @@ -218,6 +255,8 @@ pub enum Motion { ToColumn(usize), Range(usize,usize), Builder(MotionBuilder), + RepeatMotion, + RepeatMotionRev, Null }