diff --git a/src/prompt/mod.rs b/src/prompt/mod.rs index e727de6..02535f9 100644 --- a/src/prompt/mod.rs +++ b/src/prompt/mod.rs @@ -10,23 +10,23 @@ use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, shopt::Fe /// Initialize the line editor fn get_prompt() -> ShResult { let Ok(prompt) = env::var("PS1") else { + // prompt expands to: + // // username@hostname // short/path/to/pwd/ // $ - let default = "\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$\\e[0m "; - return Ok(format!("\n{}",expand_prompt(default)?)) + let default = "\\n\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$\\e[0m "; + return Ok(format!("{}",expand_prompt(default)?)) }; Ok(format!("\n{}",expand_prompt(&prompt)?)) } pub fn read_line(edit_mode: FernEditMode) -> ShResult { - dbg!("hi"); let prompt = get_prompt()?; let mut reader: Box = match edit_mode { FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))), FernEditMode::Emacs => todo!() }; - dbg!("there"); reader.readline() } diff --git a/src/prompt/readline/linebuf.rs b/src/prompt/readline/linebuf.rs index 2746fb9..4509764 100644 --- a/src/prompt/readline/linebuf.rs +++ b/src/prompt/readline/linebuf.rs @@ -91,6 +91,7 @@ pub struct Edit { pub cursor_pos: usize, pub old: String, pub new: String, + pub merging: bool, } impl Edit { @@ -111,6 +112,7 @@ impl Edit { cursor_pos: old_cursor_pos, old: String::new(), new: String::new(), + merging: false, }; } @@ -132,8 +134,15 @@ impl Edit { cursor_pos: old_cursor_pos, old, new, + merging: false } } + pub fn start_merge(&mut self) { + self.merging = true + } + pub fn stop_merge(&mut self) { + self.merging = false + } } #[derive(Default,Debug)] @@ -143,7 +152,7 @@ pub struct LineBuf { clamp_cursor: bool, first_line_offset: usize, saved_col: Option, - merge_edit: bool, + move_cursor_on_undo: bool, undo_stack: Vec, redo_stack: Vec, } @@ -176,11 +185,13 @@ impl LineBuf { pub fn is_empty(&self) -> bool { self.buffer.is_empty() } + pub fn set_move_cursor_on_undo(&mut self, yn: bool) { + self.move_cursor_on_undo = yn; + } pub fn clamp_cursor(&mut self) { // Normal mode does not allow you to sit on the edge of the buffer, you must be hovering over a character // Insert mode does let you set on the edge though, so that you can append new characters // This method is used in Normal mode - dbg!("clamping"); if self.cursor == self.byte_len() || self.grapheme_at_cursor() == Some("\n") { self.cursor_back(1); } @@ -417,11 +428,8 @@ impl LineBuf { if self.start_of_line() == 0 { return None }; - let mut col = self.saved_col.unwrap_or(self.cursor_column()); + let col = self.saved_col.unwrap_or(self.cursor_column()); let line = self.line_no(); - if line == 1 { - col = col.saturating_sub(self.first_line_offset.saturating_sub(1)) - } if self.saved_col.is_none() { self.saved_col = Some(col); } @@ -432,11 +440,8 @@ impl LineBuf { if self.end_of_line() == self.byte_len() { return None }; - let mut col = self.saved_col.unwrap_or(self.cursor_column()); + let col = self.saved_col.unwrap_or(self.cursor_column()); let line = self.line_no(); - if line == 0 { - col += self.first_line_offset.saturating_sub(1); - } if self.saved_col.is_none() { self.saved_col = Some(col); } @@ -668,9 +673,7 @@ impl LineBuf { } } false => { - let last_ws = self.rfind_from(pos, |c| CharClass::from(c) == CharClass::Whitespace)?; - let prev_word_end = self.rfind_from(last_ws, |c| CharClass::from(c) != CharClass::Whitespace)?; - match self.rfind_from(prev_word_end, |c| CharClass::from(c) == CharClass::Whitespace) { + match self.rfind_from(pos, |c| CharClass::from(c) == CharClass::Whitespace) { Some(n) => Some(n + 1), // Land on char after whitespace None => Some(0) // Start of buffer } @@ -750,24 +753,23 @@ impl LineBuf { Direction::Backward => { match to { To::Start => { - if self.on_start_of_word(word) { - pos = pos.checked_sub(1)?; - } - let cur_graph = self.grapheme_at(pos)?; - let diff_class_pos = self.rfind_from(pos, |c| is_other_class_or_ws(c, cur_graph))?; - if let CharClass::Whitespace = self.grapheme_at(diff_class_pos)?.into() { - let prev_word_end = self.rfind_from(diff_class_pos, |c| CharClass::from(c) != CharClass::Whitespace)?; - let cur_graph = self.grapheme_at(prev_word_end)?; - let Some(prev_word_start) = self.rfind_from(prev_word_end, |c| is_other_class_or_ws(c, cur_graph)) else { - return Some(0) - }; - Some(prev_word_start + 1) - } else { - let cur_graph = self.grapheme_at(diff_class_pos)?; - let Some(prev_word_start) = self.rfind_from(diff_class_pos, |c| is_other_class_or_ws(c, cur_graph)) else { - return Some(0) - }; - Some(prev_word_start + 1) + match self.on_start_of_word(word) { + true => { + pos = pos.checked_sub(1)?; + let prev_word_end = self.rfind_from(pos, |c| CharClass::from(c) != CharClass::Whitespace)?; + let cur_graph = self.grapheme_at(prev_word_end)?; + match self.rfind_from(prev_word_end, |c| is_other_class_or_ws(c, cur_graph)) { + Some(n) => Some(n + 1), // Land on char after whitespace + None => Some(0) // Start of buffer + } + } + false => { + let cur_graph = self.grapheme_at(pos)?; + match self.rfind_from(pos, |c| is_other_class_or_ws(c, cur_graph)) { + Some(n) => Some(n + 1), // Land on char after whitespace + None => Some(0) // Start of buffer + } + } } } To::End => { @@ -879,18 +881,8 @@ impl LineBuf { } Motion::BackwardChar => MotionKind::Backward(1), Motion::ForwardChar => MotionKind::Forward(1), - Motion::LineUp => { - match self.find_prev_line_pos() { - None => MotionKind::Null, - Some(pos) => MotionKind::To(pos) - } - } - Motion::LineDown => { - match self.find_next_line_pos() { - None => MotionKind::Null, - Some(pos) => MotionKind::To(pos) - } - } + Motion::LineUp => MotionKind::Line(-1), + Motion::LineDown => MotionKind::Line(1), Motion::WholeBuffer => todo!(), Motion::BeginningOfBuffer => MotionKind::To(0), Motion::EndOfBuffer => MotionKind::To(self.byte_len()), @@ -910,62 +902,63 @@ impl LineBuf { Motion::Null => todo!(), } } + pub fn get_range_from_motion(&self, verb: &Verb, motion: &MotionKind) -> Option> { + match motion { + MotionKind::Forward(n) => { + let pos = self.next_pos(*n)?; + let range = self.cursor..pos; + assert!(range.end <= self.byte_len()); + Some(range) + } + MotionKind::To(n) => { + let range = mk_range(self.cursor, *n); + assert!(range.end <= self.byte_len()); + Some(range) + } + MotionKind::Backward(n) => { + let pos = self.prev_pos(*n)?; + let range = pos..self.cursor; + Some(range) + } + MotionKind::Range(range) => { + Some(range.0..range.1) + } + MotionKind::Line(n) => { + let (start,end) = match n.cmp(&0) { + Ordering::Less => self.select_lines_up(n.unsigned_abs()), + Ordering::Equal => self.this_line(), + Ordering::Greater => self.select_lines_down(*n as usize) + }; + let range = match verb { + Verb::Change => mk_range(start,end), + Verb::Delete => mk_range(start,(end + 1).min(self.byte_len())), + _ => unreachable!() + }; + Some(range) + } + MotionKind::ToLine(n) => { + let (start,end) = self.select_lines_to(*n); + let range = match verb { + Verb::Change => start..end, + Verb::Delete => start..end.saturating_add(1), + _ => unreachable!() + }; + Some(range) + } + MotionKind::Null => return None, + MotionKind::ToScreenPos(n) => todo!(), + } + } pub fn exec_verb(&mut self, verb: Verb, motion: MotionKind, register: RegisterName) -> ShResult<()> { match verb { Verb::Change | Verb::Delete => { - let deleted = match motion { - MotionKind::Forward(n) => { - let Some(pos) = self.next_pos(n) else { - return Ok(()) - }; - let range = self.cursor..pos; - assert!(range.end < self.byte_len()); - self.buffer.drain(range) - } - MotionKind::To(n) => { - let range = mk_range(self.cursor, n); - assert!(range.end < self.byte_len()); - self.buffer.drain(range) - } - MotionKind::Backward(n) => { - let Some(back) = self.prev_pos(n) else { - return Ok(()) - }; - let range = back..self.cursor; - dbg!(&range); - self.buffer.drain(range) - } - MotionKind::Range(range) => { - self.buffer.drain(range.0..range.1) - } - MotionKind::Line(n) => { - let (start,end) = match n.cmp(&0) { - Ordering::Less => self.select_lines_up(n.unsigned_abs()), - Ordering::Equal => self.this_line(), - Ordering::Greater => self.select_lines_down(n as usize) - }; - let range = match verb { - Verb::Change => start..end, - Verb::Delete => start..end.saturating_add(1), - _ => unreachable!() - }; - self.buffer.drain(range) - } - MotionKind::ToLine(n) => { - let (start,end) = self.select_lines_to(n); - let range = match verb { - Verb::Change => start..end, - Verb::Delete => start..end.saturating_add(1), - _ => unreachable!() - }; - self.buffer.drain(range) - } - MotionKind::Null => return Ok(()), - MotionKind::ToScreenPos(n) => todo!(), + let Some(range) = self.get_range_from_motion(&verb, &motion) else { + return Ok(()) }; + let deleted = self.buffer.drain(range.clone()); register.write_to_register(deleted.collect()); - self.apply_motion(motion); + self.cursor = range.start; } Verb::DeleteChar(anchor) => { match anchor { @@ -981,16 +974,94 @@ impl LineBuf { } } } - Verb::Yank => todo!(), - Verb::ReplaceChar(_) => todo!(), + Verb::Yank => { + let Some(range) = self.get_range_from_motion(&verb, &motion) else { + return Ok(()) + }; + let yanked = &self.buffer[range.clone()]; + register.write_to_register(yanked.to_string()); + self.cursor = range.start; + } + Verb::ReplaceChar(c) => { + let Some(range) = self.get_range_from_motion(&verb, &motion) else { + return Ok(()) + }; + let new_range = format!("{c}"); + let cursor_pos = range.end; + self.buffer.replace_range(range, &new_range); + self.cursor = cursor_pos + } Verb::Substitute => todo!(), - Verb::ToggleCase => todo!(), + Verb::ToggleCase => { + let Some(range) = self.get_range_from_motion(&verb, &motion) else { + return Ok(()) + }; + let mut new_range = String::new(); + let slice = &self.buffer[range.clone()]; + for ch in slice.chars() { + if ch.is_ascii_lowercase() { + new_range.push(ch.to_ascii_uppercase()) + } else if ch.is_ascii_uppercase() { + new_range.push(ch.to_ascii_lowercase()) + } else { + new_range.push(ch) + } + } + self.buffer.replace_range(range.clone(), &new_range); + self.cursor = range.end; + } Verb::Complete => todo!(), Verb::CompleteBackward => todo!(), - Verb::Undo => todo!(), - Verb::Redo => todo!(), + Verb::Undo => { + let Some(undo) = self.undo_stack.pop() else { + return Ok(()) + }; + flog!(DEBUG, undo); + let Edit { pos, cursor_pos, old, new, .. } = undo; + let range = pos..pos + new.len(); + self.buffer.replace_range(range, &old); + let redo_cursor_pos = self.cursor; + if self.move_cursor_on_undo { + self.cursor = cursor_pos; + } + let redo = Edit { pos, cursor_pos: redo_cursor_pos, old: new, new: old, merging: false }; + self.redo_stack.push(redo); + } + Verb::Redo => { + let Some(redo) = self.redo_stack.pop() else { + return Ok(()) + }; + flog!(DEBUG, redo); + let Edit { pos, cursor_pos, old, new, .. } = redo; + let range = pos..pos + new.len(); + self.buffer.replace_range(range, &old); + let undo_cursor_pos = self.cursor; + if self.move_cursor_on_undo { + self.cursor = cursor_pos; + } + let undo = Edit { pos, cursor_pos: undo_cursor_pos, old: new, new: old, merging: false }; + self.undo_stack.push(undo); + } Verb::RepeatLast => todo!(), - Verb::Put(anchor) => todo!(), + Verb::Put(anchor) => { + let Some(register_content) = register.read_from_register() else { + return Ok(()) + }; + match anchor { + Anchor::After => { + for ch in register_content.chars() { + self.cursor_fwd(1); // Only difference is which one you start with + self.insert(ch); + } + } + Anchor::Before => { + for ch in register_content.chars() { + self.insert(ch); + self.cursor_fwd(1); + } + } + } + } Verb::InsertModeLineBreak(anchor) => { match anchor { Anchor::After => { @@ -1015,12 +1086,19 @@ impl LineBuf { Verb::Breakline(anchor) => todo!(), Verb::Indent => todo!(), Verb::Dedent => todo!(), - Verb::Equalize => todo!(), - Verb::AcceptLine => todo!(), + Verb::Equalize => todo!(), // I fear this one Verb::Builder(verb_builder) => todo!(), - Verb::EndOfFile => todo!(), + Verb::EndOfFile => { + if !self.buffer.is_empty() { + self.cursor = 0; + self.buffer.clear(); + } else { + sh_quit(0) + } + } - Verb::OverwriteMode | + Verb::AcceptLine | + Verb::ReplaceMode | Verb::InsertMode | Verb::NormalMode | Verb::VisualMode => { @@ -1031,7 +1109,6 @@ impl LineBuf { Ok(()) } pub fn apply_motion(&mut self, motion: MotionKind) { - dbg!(&motion); match motion { MotionKind::Forward(n) => { for _ in 0..n { @@ -1048,8 +1125,11 @@ impl LineBuf { } } MotionKind::To(n) => { - assert!((0..=self.byte_len()).contains(&n)); - self.cursor = n + if n > self.byte_len() { + self.cursor = self.byte_len(); + } else { + self.cursor = n + } } MotionKind::Range(range) => { assert!((0..self.byte_len()).contains(&range.0)); @@ -1085,8 +1165,11 @@ impl LineBuf { MotionKind::ToScreenPos(_) => todo!(), } } + pub fn edit_is_merging(&self) -> bool { + self.undo_stack.last().is_some_and(|edit| edit.merging) + } pub fn handle_edit(&mut self, old: String, new: String, curs_pos: usize) { - if self.merge_edit { + if self.edit_is_merging() { let diff = Edit::diff(&old, &new, curs_pos); let Some(mut edit) = self.undo_stack.pop() else { self.undo_stack.push(diff); @@ -1094,6 +1177,7 @@ impl LineBuf { }; edit.new.push_str(&diff.new); + edit.old.push_str(&diff.old); self.undo_stack.push(edit); } else { @@ -1102,15 +1186,16 @@ impl LineBuf { } } pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> { - flog!(DEBUG, cmd); let clear_redos = !cmd.is_undo_op() || cmd.verb.as_ref().is_some_and(|v| v.1.is_edit()); let is_char_insert = cmd.verb.as_ref().is_some_and(|v| v.1.is_char_insert()); let is_line_motion = cmd.is_line_motion(); let is_undo_op = cmd.is_undo_op(); // Merge character inserts into one edit - if self.merge_edit && cmd.verb.as_ref().is_none_or(|v| !v.1.is_char_insert()) { - self.merge_edit = false; + if self.edit_is_merging() && cmd.verb.as_ref().is_none_or(|v| !v.1.is_char_insert()) { + if let Some(edit) = self.undo_stack.last_mut() { + edit.stop_merge(); + } } let ViCmd { register, verb, motion, .. } = cmd; @@ -1150,7 +1235,9 @@ impl LineBuf { } if is_char_insert { - self.merge_edit = true; + if let Some(edit) = self.undo_stack.last_mut() { + edit.start_merge(); + } } if self.clamp_cursor { diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index cf1ac1f..3aba052 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -1,7 +1,7 @@ use std::time::Duration; use linebuf::{strip_ansi_codes_and_escapes, LineBuf}; -use mode::{CmdReplay, ViInsert, ViMode, ViNormal}; +use mode::{CmdReplay, ViInsert, ViMode, ViNormal, ViReplace}; use term::Terminal; use unicode_width::UnicodeWidthStr; use vicmd::{Motion, MotionCmd, RegisterName, Verb, VerbCmd, ViCmd}; @@ -60,6 +60,7 @@ impl Readline for FernVi { }; if cmd.should_submit() { + self.term.write("\n"); return Ok(self.line.to_string()); } @@ -110,20 +111,23 @@ impl FernVi { let count = cmd.verb_count(); let mut mode: Box = match cmd.verb().unwrap().1 { Verb::InsertModeLineBreak(_) | + Verb::Change | Verb::InsertMode => { - self.line.set_cursor_clamp(false); Box::new(ViInsert::new().with_count(count as u16)) } Verb::NormalMode => { - self.line.set_cursor_clamp(true); Box::new(ViNormal::new()) } + Verb::ReplaceMode => { + Box::new(ViReplace::new().with_count(count as u16)) + } Verb::VisualMode => todo!(), - Verb::OverwriteMode => todo!(), _ => unreachable!() }; std::mem::swap(&mut mode, &mut self.mode); + self.line.set_cursor_clamp(self.mode.clamp_cursor()); + self.line.set_move_cursor_on_undo(self.mode.move_cursor_on_undo()); self.term.write(&mode.cursor_style()); if mode.is_repeatable() { diff --git a/src/prompt/readline/mode.rs b/src/prompt/readline/mode.rs index c35b123..6aefaef 100644 --- a/src/prompt/readline/mode.rs +++ b/src/prompt/readline/mode.rs @@ -38,6 +38,8 @@ pub trait ViMode { fn as_replay(&self) -> Option; fn cursor_style(&self) -> String; fn pending_seq(&self) -> Option; + fn move_cursor_on_undo(&self) -> bool; + fn clamp_cursor(&self) -> bool; } #[derive(Default,Debug)] @@ -58,7 +60,16 @@ impl ViInsert { pub fn register_and_return(&mut self) -> Option { let cmd = self.take_cmd(); self.register_cmd(&cmd); - return Some(cmd) + Some(cmd) + } + pub fn ctrl_w_is_undo(&self) -> bool { + let insert_count = self.cmds.iter().filter(|cmd| { + matches!(cmd.verb(),Some(VerbCmd(1, Verb::InsertChar(_)))) + }).count(); + let backspace_count = self.cmds.iter().filter(|cmd| { + matches!(cmd.verb(),Some(VerbCmd(1, Verb::Delete))) + }).count(); + insert_count > backspace_count } pub fn register_cmd(&mut self, cmd: &ViCmd) { self.cmds.push(cmd.clone()) @@ -76,6 +87,17 @@ impl ViMode for ViInsert { self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar)); self.register_and_return() } + E(K::Char('W'), M::CTRL) => { + if self.ctrl_w_is_undo() { + self.pending_cmd.set_verb(VerbCmd(1,Verb::Undo)); + self.cmds.clear(); + Some(self.take_cmd()) + } else { + self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete)); + self.pending_cmd.set_motion(MotionCmd(1, Motion::BackwardWord(To::Start, Word::Normal))); + self.register_and_return() + } + } E(K::Char('H'), M::CTRL) | E(K::Backspace, M::NONE) => { self.pending_cmd.set_verb(VerbCmd(1,Verb::Delete)); @@ -117,8 +139,113 @@ impl ViMode for ViInsert { fn pending_seq(&self) -> Option { None } + fn move_cursor_on_undo(&self) -> bool { + true + } + fn clamp_cursor(&self) -> bool { + false + } } +#[derive(Default,Debug)] +pub struct ViReplace { + cmds: Vec, + pending_cmd: ViCmd, + repeat_count: u16 +} + +impl ViReplace { + pub fn new() -> Self { + Self::default() + } + pub fn with_count(mut self, repeat_count: u16) -> Self { + self.repeat_count = repeat_count; + self + } + pub fn register_and_return(&mut self) -> Option { + let cmd = self.take_cmd(); + self.register_cmd(&cmd); + Some(cmd) + } + pub fn ctrl_w_is_undo(&self) -> bool { + let insert_count = self.cmds.iter().filter(|cmd| { + matches!(cmd.verb(),Some(VerbCmd(1, Verb::ReplaceChar(_)))) + }).count(); + let backspace_count = self.cmds.iter().filter(|cmd| { + matches!(cmd.verb(),Some(VerbCmd(1, Verb::Delete))) + }).count(); + insert_count > backspace_count + } + pub fn register_cmd(&mut self, cmd: &ViCmd) { + self.cmds.push(cmd.clone()) + } + pub fn take_cmd(&mut self) -> ViCmd { + std::mem::take(&mut self.pending_cmd) + } +} + +impl ViMode for ViReplace { + fn handle_key(&mut self, key: E) -> Option { + match key { + E(K::Char(ch), M::NONE) => { + self.pending_cmd.set_verb(VerbCmd(1,Verb::ReplaceChar(ch))); + self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar)); + self.register_and_return() + } + E(K::Char('W'), M::CTRL) => { + if self.ctrl_w_is_undo() { + self.pending_cmd.set_verb(VerbCmd(1,Verb::Undo)); + self.cmds.clear(); + Some(self.take_cmd()) + } else { + self.pending_cmd.set_motion(MotionCmd(1, Motion::BackwardWord(To::Start, Word::Normal))); + self.register_and_return() + } + } + E(K::Char('H'), M::CTRL) | + E(K::Backspace, M::NONE) => { + self.pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar)); + self.register_and_return() + } + + E(K::BackTab, M::NONE) => { + self.pending_cmd.set_verb(VerbCmd(1,Verb::CompleteBackward)); + self.register_and_return() + } + + E(K::Char('I'), M::CTRL) | + E(K::Tab, M::NONE) => { + self.pending_cmd.set_verb(VerbCmd(1,Verb::Complete)); + self.register_and_return() + } + + E(K::Esc, M::NONE) => { + self.pending_cmd.set_verb(VerbCmd(1,Verb::NormalMode)); + self.pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar)); + self.register_and_return() + } + _ => common_cmds(key) + } + } + fn is_repeatable(&self) -> bool { + true + } + fn cursor_style(&self) -> String { + "\x1b[4 q".to_string() + } + fn pending_seq(&self) -> Option { + None + } + fn as_replay(&self) -> Option { + Some(CmdReplay::mode(self.cmds.clone(), self.repeat_count)) + } + fn move_cursor_on_undo(&self) -> bool { + true + } + fn clamp_cursor(&self) -> bool { + true + } +} #[derive(Default,Debug)] pub struct ViNormal { pending_seq: String, @@ -235,9 +362,7 @@ impl ViNormal { break 'verb_parse Some(VerbCmd(count, Verb::Put(Anchor::Before))); } 'r' => { - let Some(ch) = chars_clone.next() else { - return None - }; + let ch = chars_clone.next()?; return Some( ViCmd { register, @@ -247,6 +372,26 @@ impl ViNormal { } ) } + 'R' => { + return Some( + ViCmd { + register, + verb: Some(VerbCmd(count, Verb::ReplaceMode)), + motion: None, + raw_seq: self.take_cmd() + } + ) + } + '~' => { + return Some( + ViCmd { + register, + verb: Some(VerbCmd(1, Verb::ToggleCase)), + motion: Some(MotionCmd(count, Motion::ForwardChar)), + raw_seq: self.take_cmd() + } + ) + } 'u' => { return Some( ViCmd { @@ -329,6 +474,36 @@ impl ViNormal { chars = chars_clone; break 'verb_parse Some(VerbCmd(count, Verb::Change)) } + 'Y' => { + return Some( + ViCmd { + register, + verb: Some(VerbCmd(count, Verb::Yank)), + motion: Some(MotionCmd(1, Motion::EndOfLine)), + raw_seq: self.take_cmd() + } + ) + } + 'D' => { + return Some( + ViCmd { + register, + verb: Some(VerbCmd(count, Verb::Delete)), + motion: Some(MotionCmd(1, Motion::EndOfLine)), + raw_seq: self.take_cmd() + } + ) + } + 'C' => { + return Some( + ViCmd { + register, + verb: Some(VerbCmd(count, Verb::Change)), + motion: Some(MotionCmd(1, Motion::EndOfLine)), + raw_seq: self.take_cmd() + } + ) + } '=' => { chars = chars_clone; break 'verb_parse Some(VerbCmd(count, Verb::Equalize)) @@ -580,6 +755,13 @@ impl ViMode for ViNormal { fn pending_seq(&self) -> Option { Some(self.pending_seq.clone()) } + + fn move_cursor_on_undo(&self) -> bool { + false + } + fn clamp_cursor(&self) -> bool { + true + } } pub fn common_cmds(key: E) -> Option { diff --git a/src/prompt/readline/term.rs b/src/prompt/readline/term.rs index bc1a2b2..716222f 100644 --- a/src/prompt/readline/term.rs +++ b/src/prompt/readline/term.rs @@ -181,6 +181,7 @@ impl Terminal { Ok(()) } + /// Rewinds terminal writing, clears lines and lands on the anchor point of the prompt pub fn unwrite(&mut self) -> ShResult<()> { self.unposition_cursor()?; let WriteMap { lines, cols, offset } = self.write_records; @@ -194,7 +195,6 @@ impl Terminal { } pub fn position_cursor(&mut self, (lines,col): (usize,usize)) -> ShResult<()> { - dbg!(self.cursor_pos()); self.cursor_records.lines = lines; self.cursor_records.cols = col; self.cursor_records.offset = self.cursor_pos().1; @@ -205,14 +205,11 @@ impl Terminal { self.write(&format!("\x1b[{col}G")); - dbg!("done moving"); - dbg!(self.cursor_pos()); - Ok(()) } + /// Rewinds cursor positioning, lands on the end of the buffer pub fn unposition_cursor(&mut self) ->ShResult<()> { - dbg!(self.cursor_pos()); let WriteMap { lines, cols, offset } = self.cursor_records; for _ in 0..lines { @@ -221,9 +218,6 @@ impl Terminal { self.write(&format!("\x1b[{offset}G")); - dbg!("done moving back"); - dbg!(self.cursor_pos()); - Ok(()) } diff --git a/src/prompt/readline/vicmd.rs b/src/prompt/readline/vicmd.rs index 301cd17..20f11da 100644 --- a/src/prompt/readline/vicmd.rs +++ b/src/prompt/readline/vicmd.rs @@ -106,11 +106,12 @@ impl ViCmd { pub fn is_mode_transition(&self) -> bool { self.verb.as_ref().is_some_and(|v| { matches!(v.1, + Verb::Change | Verb::InsertMode | Verb::InsertModeLineBreak(_) | Verb::NormalMode | Verb::VisualMode | - Verb::OverwriteMode + Verb::ReplaceMode ) }) } @@ -150,7 +151,7 @@ pub enum Verb { Redo, RepeatLast, Put(Anchor), - OverwriteMode, + ReplaceMode, InsertMode, InsertModeLineBreak(Anchor), NormalMode, @@ -190,7 +191,7 @@ impl Verb { Self::Substitute | Self::ToggleCase | Self::Put(_) | - Self::OverwriteMode | + Self::ReplaceMode | Self::InsertModeLineBreak(_) | Self::JoinLines | Self::InsertChar(_) | @@ -211,7 +212,7 @@ impl Verb { Self::ToggleCase | Self::RepeatLast | Self::Put(_) | - Self::OverwriteMode | + Self::ReplaceMode | Self::InsertModeLineBreak(_) | Self::JoinLines | Self::InsertChar(_) | @@ -221,7 +222,10 @@ impl Verb { ) } pub fn is_char_insert(&self) -> bool { - matches!(self, Self::InsertChar(_)) + matches!(self, + Self::InsertChar(_) | + Self::ReplaceChar(_) + ) } }