diff --git a/src/prompt/readline/line.rs b/src/prompt/readline/line.rs index de3c310..e3566e9 100644 --- a/src/prompt/readline/line.rs +++ b/src/prompt/readline/line.rs @@ -2,12 +2,14 @@ use std::ops::Range; use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prompt::readline::linecmd::Anchor}; -use super::linecmd::{At, CharSearch, MoveCmd, Movement, Verb, VerbCmd, Word}; +use super::linecmd::{At, CharSearch, MoveCmd, Movement, Repeat, Verb, VerbCmd, Word}; #[derive(Default,Debug)] pub struct LineBuf { pub buffer: Vec, + pub inserting: bool, + pub last_insert: String, cursor: usize } @@ -19,6 +21,15 @@ impl LineBuf { self.buffer = init.to_string().chars().collect(); self } + pub fn begin_insert(&mut self) { + self.inserting = true; + } + pub fn finish_insert(&mut self) { + self.inserting = false; + } + pub fn take_ins_text(&mut self) -> String { + std::mem::take(&mut self.last_insert) + } pub fn display_lines(&self) -> Vec { let line_bullet = "∙ ".styled(Style::Dim); self.split_lines() @@ -519,7 +530,20 @@ impl LineBuf { } } } - Verb::InsertChar(ch) => self.insert_at_cursor(ch), + Verb::InsertChar(ch) => { + if self.inserting { + self.last_insert.push(ch); + } + self.insert_at_cursor(ch) + } + Verb::Insert(text) => { + for ch in text.chars() { + if self.inserting { + self.last_insert.push(ch); + } + self.insert_at_cursor(ch); + } + } Verb::InsertMode => todo!(), Verb::JoinLines => todo!(), Verb::ToggleCase => todo!(), @@ -579,6 +603,9 @@ impl LineBuf { self.buffer.drain(range); self.repos_cursor(); }); + } + Verb::Repeat(rep) => { + } Verb::DeleteOne(anchor) => todo!(), Verb::Breakline(anchor) => todo!(), diff --git a/src/prompt/readline/linecmd.rs b/src/prompt/readline/linecmd.rs index e3058bb..c5c72c0 100644 --- a/src/prompt/readline/linecmd.rs +++ b/src/prompt/readline/linecmd.rs @@ -110,6 +110,54 @@ pub struct MoveCmd { pub movement: Movement } +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub struct Repeat { + pub movement: Option, + pub verb: Option>, + pub ins_text: Option +} + +impl Repeat { + pub fn from_cmd(cmd: ViCmd) -> Self { + match cmd { + ViCmd::MoveVerb(verb_cmd, move_cmd) => { + Self { + movement: Some(move_cmd), + verb: Some(Box::new(verb_cmd)), + ins_text: None, + } + } + ViCmd::Verb(verb_cmd) => { + Self { + movement: None, + verb: Some(Box::new(verb_cmd)), + ins_text: None, + } + } + ViCmd::Move(move_cmd) => { + Self { + movement: Some(move_cmd), + verb: None, + ins_text: None, + } + } + } + } + pub fn set_ins_text(&mut self, ins_text: String) { + self.ins_text = Some(ins_text); + } + fn is_empty(&self) -> bool { + self.movement.is_none() && self.verb.is_none() && self.ins_text.is_none() + } + pub fn to_option(self) -> Option { + if self.is_empty() { + None + } else { + Some(self) + } + } +} + #[derive(Debug, Clone, Eq, PartialEq)] #[non_exhaustive] pub enum Verb { @@ -139,7 +187,9 @@ pub enum Verb { InsertMode, /// `J` — join lines JoinLines, + Repeat(Repeat), InsertChar(char), + Insert(String), Breakline(Anchor), Indent, Dedent @@ -161,6 +211,8 @@ impl Verb { Verb::Indent | Verb::InsertChar(_) | Verb::Breakline(_) | + Verb::Repeat(_) | + Verb::Insert(_) | Verb::ReplaceChar(_) => false, Verb::Delete | Verb::Change | diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index afaf32b..6d130d0 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -8,6 +8,7 @@ use term::Terminal; use unicode_width::UnicodeWidthStr; use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, sys::sh_quit}, prelude::*}; +use linecmd::Repeat; pub mod term; pub mod line; pub mod keys; @@ -46,9 +47,7 @@ pub struct FernReader { pub prompt: String, pub line: LineBuf, pub edit_mode: InputMode, - pub count_arg: u16, - pub last_effect: Option, - pub last_movement: Option + pub last_vicmd: Option, } impl FernReader { @@ -59,9 +58,7 @@ impl FernReader { prompt, line, edit_mode: Default::default(), - count_arg: Default::default(), - last_effect: Default::default(), - last_movement: Default::default(), + last_vicmd: Default::default() } } fn pack_line(&mut self) -> String { @@ -142,6 +139,16 @@ impl FernReader { } } } + pub fn set_normal_mode(&mut self) { + self.edit_mode = InputMode::Normal; + self.line.finish_insert(); + let ins_text = self.line.take_ins_text(); + self.last_vicmd.as_mut().map(|cmd| cmd.set_ins_text(ins_text)); + } + pub fn set_insert_mode(&mut self) { + self.edit_mode = InputMode::Insert; + self.line.begin_insert(); + } pub fn next_cmd(&mut self) -> ShResult { let vi_cmd = ViCmdBuilder::new(); match self.edit_mode { @@ -166,10 +173,8 @@ impl FernReader { E(K::Tab, M::NONE) => LineCmd::Complete, E(K::Esc, M::NONE) => { - self.edit_mode = InputMode::Normal; build_movement!(pending_cmd, Movement::BackwardChar)? } - E(K::Char('D'), M::CTRL) => LineCmd::EndOfFile, _ => { flog!(INFO, "unhandled key in get_insert_cmd, trying common_cmd..."); return self.common_cmd(key, pending_cmd) @@ -217,6 +222,14 @@ impl FernReader { .build()?; LineCmd::ViCmd(cmd) } + E(K::Char('.'), M::NONE) => { + match &self.last_vicmd { + None => LineCmd::Null, + Some(cmd) => { + build_verb!(pending_cmd, Verb::Repeat(cmd.clone()))? + } + } + } E(K::Char('j'), M::NONE) => LineCmd::LineDownOrNextHistory, E(K::Char('k'), M::NONE) => LineCmd::LineUpOrPreviousHistory, E(K::Char('D'), M::NONE) => build_moveverb!(pending_cmd,Verb::Delete,Movement::EndOfLine)?, @@ -234,27 +247,27 @@ impl FernReader { E(K::Char('$'), M::NONE) => build_movement!(pending_cmd,Movement::EndOfLine)?, E(K::Char('x'), M::NONE) => build_verb!(pending_cmd,Verb::DeleteOne(Anchor::After))?, E(K::Char('o'), M::NONE) => { - self.edit_mode = InputMode::Insert; + self.set_insert_mode(); build_verb!(pending_cmd,Verb::Breakline(Anchor::After))? } E(K::Char('O'), M::NONE) => { - self.edit_mode = InputMode::Insert; + self.set_insert_mode(); build_verb!(pending_cmd,Verb::Breakline(Anchor::Before))? } E(K::Char('i'), M::NONE) => { - self.edit_mode = InputMode::Insert; + self.set_insert_mode(); LineCmd::Null } E(K::Char('I'), M::NONE) => { - self.edit_mode = InputMode::Insert; + self.set_insert_mode(); build_movement!(pending_cmd,Movement::BeginningOfFirstWord)? } E(K::Char('a'), M::NONE) => { - self.edit_mode = InputMode::Insert; + self.set_insert_mode(); build_movement!(pending_cmd,Movement::ForwardChar)? } E(K::Char('A'), M::NONE) => { - self.edit_mode = InputMode::Insert; + self.set_insert_mode(); build_movement!(pending_cmd,Movement::EndOfLine)? } E(K::Char('c'), M::NONE) => { @@ -324,6 +337,7 @@ impl FernReader { E(K::Up, M::NONE) => Ok(LineCmd::LineUpOrPreviousHistory), E(K::Down, M::NONE) => Ok(LineCmd::LineDownOrNextHistory), E(K::Enter, M::NONE) => Ok(LineCmd::AcceptLine), + E(K::Char('D'), M::CTRL) => Ok(LineCmd::EndOfFile), E(K::Backspace, M::NONE) | E(K::Char('h'), M::CTRL) => { Ok(LineCmd::backspace()) @@ -331,29 +345,28 @@ impl FernReader { _ => Err(ShErr::simple(ShErrKind::ReadlineErr,format!("Unhandled common key event: {key:?}"))) } } + pub fn handle_repeat(&mut self, cmd: &ViCmd) -> ShResult<()> { + Ok(()) + } pub fn exec_vi_cmd(&mut self, cmd: ViCmd) -> ShResult<()> { + self.last_vicmd = Some(Repeat::from_cmd(cmd.clone())); match cmd { ViCmd::MoveVerb(verb_cmd, move_cmd) => { - self.last_effect = Some(verb_cmd.clone()); - self.last_movement = Some(move_cmd.clone()); - let VerbCmd { verb_count, verb } = verb_cmd; for _ in 0..verb_count { self.line.exec_vi_cmd(Some(verb.clone()), Some(move_cmd.clone()))?; } if verb == Verb::Change { - self.edit_mode = InputMode::Insert + self.set_insert_mode(); } } ViCmd::Verb(verb_cmd) => { - self.last_effect = Some(verb_cmd.clone()); let VerbCmd { verb_count, verb } = verb_cmd; for _ in 0..verb_count { self.line.exec_vi_cmd(Some(verb.clone()), None)?; } } ViCmd::Move(move_cmd) => { - self.last_movement = Some(move_cmd.clone()); self.line.exec_vi_cmd(None, Some(move_cmd))?; } }