From e7d8b98a73c451802fb441e4eb27851813ba4698 Mon Sep 17 00:00:00 2001 From: Kyler Clay Date: Fri, 30 May 2025 20:15:41 -0400 Subject: [PATCH] implemented 'gv' to select the previously selected visual selection --- src/prompt/readline/history.rs | 20 +++++++++++++++++- src/prompt/readline/linebuf.rs | 31 +++++++++++++++++++++++---- src/prompt/readline/mod.rs | 19 +++++++++++++++-- src/prompt/readline/mode.rs | 38 ++++++++++++++++++++++++++++++++++ src/prompt/readline/vicmd.rs | 25 ++++++---------------- 5 files changed, 107 insertions(+), 26 deletions(-) diff --git a/src/prompt/readline/history.rs b/src/prompt/readline/history.rs index 0abef8e..f12898d 100644 --- a/src/prompt/readline/history.rs +++ b/src/prompt/readline/history.rs @@ -313,7 +313,25 @@ impl History { .append(true) .open(&self.path)?; - let entries = self.entries.iter_mut().filter(|ent| ent.new && !ent.command.is_empty()); + let last_file_entry = self.entries + .iter() + .filter(|ent| !ent.new) + .next_back() + .map(|ent| ent.command.clone()) + .unwrap_or_default(); + + let entries = self.entries + .iter_mut() + .filter(|ent| { + ent.new && + !ent.command.is_empty() && + if self.ignore_dups { + ent.command() != last_file_entry + } else { + true + } + }); + let mut data = String::new(); for ent in entries { ent.new = false; diff --git a/src/prompt/readline/linebuf.rs b/src/prompt/readline/linebuf.rs index af22c09..ecdd15f 100644 --- a/src/prompt/readline/linebuf.rs +++ b/src/prompt/readline/linebuf.rs @@ -32,9 +32,10 @@ pub enum MotionKind { ScreenLine(isize) } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum SelectionAnchor { Start, + #[default] End } @@ -45,6 +46,12 @@ pub enum SelectionMode { Block(SelectionAnchor) } +impl Default for SelectionMode { + fn default() -> Self { + Self::Char(Default::default()) + } +} + impl SelectionMode { pub fn anchor(&self) -> &SelectionAnchor { match self { @@ -195,6 +202,7 @@ pub struct LineBuf { clamp_cursor: bool, select_mode: Option, selected_range: Option>, + last_selected_range: Option>, first_line_offset: usize, saved_col: Option, term_dims: (usize,usize), // Height, width @@ -220,7 +228,9 @@ impl LineBuf { } pub fn stop_selecting(&mut self) { self.select_mode = None; - self.selected_range = None; + if self.selected_range().is_some() { + self.last_selected_range = self.selected_range.take(); + } } pub fn start_selecting(&mut self, mode: SelectionMode) { self.select_mode = Some(mode); @@ -1506,6 +1516,16 @@ impl LineBuf { } } } + Verb::VisualModeSelectLast => { + if let Some(range) = self.last_selected_range.as_ref() { + self.selected_range = Some(range.clone()); + let mode = self.select_mode.unwrap_or_default(); + self.cursor = match mode.anchor() { + SelectionAnchor::Start => range.start, + SelectionAnchor::End => range.end + } + } + } Verb::SwapVisualAnchor => { if let Some(range) = self.selected_range() { if let Some(mut mode) = self.select_mode { @@ -1934,10 +1954,13 @@ impl Display for LineBuf { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut full_buf = self.buffer.clone(); if let Some(range) = self.selected_range.clone() { - let mode = self.select_mode.unwrap(); + let mode = self.select_mode.unwrap_or_default(); match mode.anchor() { SelectionAnchor::Start => { - let inclusive = range.start..=range.end; + let mut inclusive = range.start..=range.end; + if *inclusive.end() == self.byte_len() { + inclusive = range.start..=range.end.saturating_sub(1); + } let selected = full_buf[inclusive.clone()].styled(Style::BgWhite | Style::Black); full_buf.replace_range(inclusive, &selected); } diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index 8ef6731..3495c70 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -88,11 +88,11 @@ impl Readline for FernVi { if cmd.should_submit() { self.term.unposition_cursor()?; self.term.write("\n"); - let command = self.line.to_string(); + let command = std::mem::take(&mut self.line).pack_line(); if !command.is_empty() { // We're just going to trim the command // reduces clutter in the case of two history commands whose only difference is insignificant whitespace - self.history.push(command.trim().to_string()); + self.history.update_pending_cmd(&command); self.history.save()?; } return Ok(command); @@ -323,6 +323,17 @@ impl FernVi { Verb::ReplaceMode => { Box::new(ViReplace::new().with_count(count as u16)) } + Verb::VisualModeSelectLast => { + if self.mode.report_mode() != ModeReport::Visual { + self.line.start_selecting(SelectionMode::Char(SelectionAnchor::End)); + } + let mut mode: Box = Box::new(ViVisual::new()); + 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()); + return self.line.exec_cmd(cmd) + } Verb::VisualMode => { selecting = true; self.line.start_selecting(SelectionMode::Char(SelectionAnchor::End)); @@ -331,7 +342,11 @@ impl FernVi { _ => unreachable!() }; + flog!(DEBUG, self.mode.report_mode()); + flog!(DEBUG, mode.report_mode()); std::mem::swap(&mut mode, &mut self.mode); + + flog!(DEBUG, self.mode.report_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()); diff --git a/src/prompt/readline/mode.rs b/src/prompt/readline/mode.rs index 4ee5f9f..0e119a5 100644 --- a/src/prompt/readline/mode.rs +++ b/src/prompt/readline/mode.rs @@ -358,6 +358,25 @@ impl ViNormal { break 'verb_parse None }; match ch { + 'g' => { + if let Some(ch) = chars_clone.peek() { + match ch { + 'v' => { + return Some( + ViCmd { + register, + verb: Some(VerbCmd(1, Verb::VisualModeSelectLast)), + motion: None, + raw_seq: self.take_cmd() + } + ) + } + _ => break 'verb_parse None + } + } else { + break 'verb_parse None + } + } '.' => { return Some( ViCmd { @@ -924,6 +943,25 @@ impl ViVisual { break 'verb_parse None }; match ch { + 'g' => { + if let Some(ch) = chars_clone.peek() { + match ch { + 'v' => { + return Some( + ViCmd { + register, + verb: Some(VerbCmd(1, Verb::VisualModeSelectLast)), + motion: None, + raw_seq: self.take_cmd() + } + ) + } + _ => break 'verb_parse None + } + } else { + break 'verb_parse None + } + } '.' => { return Some( ViCmd { diff --git a/src/prompt/readline/vicmd.rs b/src/prompt/readline/vicmd.rs index a7c8b9f..27d364f 100644 --- a/src/prompt/readline/vicmd.rs +++ b/src/prompt/readline/vicmd.rs @@ -110,6 +110,7 @@ impl ViCmd { Verb::InsertMode | Verb::InsertModeLineBreak(_) | Verb::NormalMode | + Verb::VisualModeSelectLast | Verb::VisualMode | Verb::ReplaceMode ) @@ -159,7 +160,8 @@ pub enum Verb { NormalMode, VisualMode, VisualModeLine, - VisualModeBlock, + VisualModeBlock, // dont even know if im going to implement this + VisualModeSelectLast, SwapVisualAnchor, JoinLines, InsertChar(char), @@ -241,37 +243,22 @@ impl Verb { #[derive(Debug, Clone, Eq, PartialEq)] pub enum Motion { - /// Whole current line (not really a movement but a range) WholeLine, TextObj(TextObj, Bound), BeginningOfFirstWord, - /// beginning-of-line BeginningOfLine, - /// end-of-line EndOfLine, - /// backward-word, vi-prev-word - BackwardWord(To, Word), // Backward until start of word - /// forward-word, vi-end-word, vi-next-word - ForwardWord(To, Word), // Forward until start/end of word - /// character-search, character-search-backward, vi-char-search + BackwardWord(To, Word), + ForwardWord(To, Word), CharSearch(Direction,Dest,char), - /// backward-char BackwardChar, - /// forward-char ForwardChar, - /// move to the same column on the previous line LineUp, - /// move to the same column on the previous visual line ScreenLineUp, - /// move to the same column on the next line LineDown, - /// move to the same column on the next visual line ScreenLineDown, - /// Whole user input (not really a movement but a range) WholeBuffer, - /// beginning-of-register BeginningOfBuffer, - /// end-of-register EndOfBuffer, ToColumn(usize), Range(usize,usize), @@ -328,7 +315,7 @@ pub enum TextObj { /// `i<`, `a<` Angle, - /// `it`, `at` — HTML/XML tags (if you support it) + /// `it`, `at` — HTML/XML tags Tag, /// Custom user-defined objects maybe?