implemented 'gv' to select the previously selected visual selection

This commit is contained in:
2025-05-30 20:15:41 -04:00
parent 09767c9682
commit e7d8b98a73
5 changed files with 107 additions and 26 deletions

View File

@@ -313,7 +313,25 @@ impl History {
.append(true) .append(true)
.open(&self.path)?; .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(); let mut data = String::new();
for ent in entries { for ent in entries {
ent.new = false; ent.new = false;

View File

@@ -32,9 +32,10 @@ pub enum MotionKind {
ScreenLine(isize) ScreenLine(isize)
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum SelectionAnchor { pub enum SelectionAnchor {
Start, Start,
#[default]
End End
} }
@@ -45,6 +46,12 @@ pub enum SelectionMode {
Block(SelectionAnchor) Block(SelectionAnchor)
} }
impl Default for SelectionMode {
fn default() -> Self {
Self::Char(Default::default())
}
}
impl SelectionMode { impl SelectionMode {
pub fn anchor(&self) -> &SelectionAnchor { pub fn anchor(&self) -> &SelectionAnchor {
match self { match self {
@@ -195,6 +202,7 @@ pub struct LineBuf {
clamp_cursor: bool, clamp_cursor: bool,
select_mode: Option<SelectionMode>, select_mode: Option<SelectionMode>,
selected_range: Option<Range<usize>>, selected_range: Option<Range<usize>>,
last_selected_range: Option<Range<usize>>,
first_line_offset: usize, first_line_offset: usize,
saved_col: Option<usize>, saved_col: Option<usize>,
term_dims: (usize,usize), // Height, width term_dims: (usize,usize), // Height, width
@@ -220,7 +228,9 @@ impl LineBuf {
} }
pub fn stop_selecting(&mut self) { pub fn stop_selecting(&mut self) {
self.select_mode = None; 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) { pub fn start_selecting(&mut self, mode: SelectionMode) {
self.select_mode = Some(mode); 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 => { Verb::SwapVisualAnchor => {
if let Some(range) = self.selected_range() { if let Some(range) = self.selected_range() {
if let Some(mut mode) = self.select_mode { 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut full_buf = self.buffer.clone(); let mut full_buf = self.buffer.clone();
if let Some(range) = self.selected_range.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() { match mode.anchor() {
SelectionAnchor::Start => { 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); let selected = full_buf[inclusive.clone()].styled(Style::BgWhite | Style::Black);
full_buf.replace_range(inclusive, &selected); full_buf.replace_range(inclusive, &selected);
} }

View File

@@ -88,11 +88,11 @@ impl Readline for FernVi {
if cmd.should_submit() { if cmd.should_submit() {
self.term.unposition_cursor()?; self.term.unposition_cursor()?;
self.term.write("\n"); self.term.write("\n");
let command = self.line.to_string(); let command = std::mem::take(&mut self.line).pack_line();
if !command.is_empty() { if !command.is_empty() {
// We're just going to trim the command // We're just going to trim the command
// reduces clutter in the case of two history commands whose only difference is insignificant whitespace // 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()?; self.history.save()?;
} }
return Ok(command); return Ok(command);
@@ -323,6 +323,17 @@ impl FernVi {
Verb::ReplaceMode => { Verb::ReplaceMode => {
Box::new(ViReplace::new().with_count(count as u16)) 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<dyn ViMode> = 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 => { Verb::VisualMode => {
selecting = true; selecting = true;
self.line.start_selecting(SelectionMode::Char(SelectionAnchor::End)); self.line.start_selecting(SelectionMode::Char(SelectionAnchor::End));
@@ -331,7 +342,11 @@ impl FernVi {
_ => unreachable!() _ => unreachable!()
}; };
flog!(DEBUG, self.mode.report_mode());
flog!(DEBUG, mode.report_mode());
std::mem::swap(&mut mode, &mut self.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_cursor_clamp(self.mode.clamp_cursor());
self.line.set_move_cursor_on_undo(self.mode.move_cursor_on_undo()); self.line.set_move_cursor_on_undo(self.mode.move_cursor_on_undo());
self.term.write(&mode.cursor_style()); self.term.write(&mode.cursor_style());

View File

@@ -358,6 +358,25 @@ impl ViNormal {
break 'verb_parse None break 'verb_parse None
}; };
match ch { 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( return Some(
ViCmd { ViCmd {
@@ -924,6 +943,25 @@ impl ViVisual {
break 'verb_parse None break 'verb_parse None
}; };
match ch { 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( return Some(
ViCmd { ViCmd {

View File

@@ -110,6 +110,7 @@ impl ViCmd {
Verb::InsertMode | Verb::InsertMode |
Verb::InsertModeLineBreak(_) | Verb::InsertModeLineBreak(_) |
Verb::NormalMode | Verb::NormalMode |
Verb::VisualModeSelectLast |
Verb::VisualMode | Verb::VisualMode |
Verb::ReplaceMode Verb::ReplaceMode
) )
@@ -159,7 +160,8 @@ pub enum Verb {
NormalMode, NormalMode,
VisualMode, VisualMode,
VisualModeLine, VisualModeLine,
VisualModeBlock, VisualModeBlock, // dont even know if im going to implement this
VisualModeSelectLast,
SwapVisualAnchor, SwapVisualAnchor,
JoinLines, JoinLines,
InsertChar(char), InsertChar(char),
@@ -241,37 +243,22 @@ impl Verb {
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum Motion { pub enum Motion {
/// Whole current line (not really a movement but a range)
WholeLine, WholeLine,
TextObj(TextObj, Bound), TextObj(TextObj, Bound),
BeginningOfFirstWord, BeginningOfFirstWord,
/// beginning-of-line
BeginningOfLine, BeginningOfLine,
/// end-of-line
EndOfLine, EndOfLine,
/// backward-word, vi-prev-word BackwardWord(To, Word),
BackwardWord(To, Word), // Backward until start of word ForwardWord(To, 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
CharSearch(Direction,Dest,char), CharSearch(Direction,Dest,char),
/// backward-char
BackwardChar, BackwardChar,
/// forward-char
ForwardChar, ForwardChar,
/// move to the same column on the previous line
LineUp, LineUp,
/// move to the same column on the previous visual line
ScreenLineUp, ScreenLineUp,
/// move to the same column on the next line
LineDown, LineDown,
/// move to the same column on the next visual line
ScreenLineDown, ScreenLineDown,
/// Whole user input (not really a movement but a range)
WholeBuffer, WholeBuffer,
/// beginning-of-register
BeginningOfBuffer, BeginningOfBuffer,
/// end-of-register
EndOfBuffer, EndOfBuffer,
ToColumn(usize), ToColumn(usize),
Range(usize,usize), Range(usize,usize),
@@ -328,7 +315,7 @@ pub enum TextObj {
/// `i<`, `a<` /// `i<`, `a<`
Angle, Angle,
/// `it`, `at` — HTML/XML tags (if you support it) /// `it`, `at` — HTML/XML tags
Tag, Tag,
/// Custom user-defined objects maybe? /// Custom user-defined objects maybe?