implemented 'gv' to select the previously selected visual selection
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
Reference in New Issue
Block a user