work on implementing visual mode
This commit is contained in:
@@ -709,8 +709,6 @@ impl ParseStream {
|
|||||||
node_tks.extend(node.tokens.clone());
|
node_tks.extend(node.tokens.clone());
|
||||||
body.push(node);
|
body.push(node);
|
||||||
}
|
}
|
||||||
dbg!(&self.tokens);
|
|
||||||
panic!("{body:#?}");
|
|
||||||
self.catch_separator(&mut node_tks);
|
self.catch_separator(&mut node_tks);
|
||||||
if !self.next_tk_is_some() {
|
if !self.next_tk_is_some() {
|
||||||
self.panic_mode(&mut node_tks);
|
self.panic_mode(&mut node_tks);
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ pub enum CharClass {
|
|||||||
Other
|
Other
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum MotionKind {
|
pub enum MotionKind {
|
||||||
Forward(usize),
|
Forward(usize),
|
||||||
To(usize),
|
To(usize), // Land just before
|
||||||
|
On(usize), // Land directly on
|
||||||
Backward(usize),
|
Backward(usize),
|
||||||
Range((usize,usize)),
|
Range((usize,usize)),
|
||||||
Line(isize), // positive = up line, negative = down line
|
Line(isize), // positive = up line, negative = down line
|
||||||
@@ -31,6 +32,41 @@ pub enum MotionKind {
|
|||||||
ScreenLine(isize)
|
ScreenLine(isize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SelectionAnchor {
|
||||||
|
Start,
|
||||||
|
End
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SelectionMode {
|
||||||
|
Char(SelectionAnchor),
|
||||||
|
Line(SelectionAnchor),
|
||||||
|
Block(SelectionAnchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionMode {
|
||||||
|
pub fn anchor(&self) -> &SelectionAnchor {
|
||||||
|
match self {
|
||||||
|
SelectionMode::Char(anchor) |
|
||||||
|
SelectionMode::Line(anchor) |
|
||||||
|
SelectionMode::Block(anchor) => anchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn invert_anchor(&mut self) {
|
||||||
|
match self {
|
||||||
|
SelectionMode::Char(anchor) |
|
||||||
|
SelectionMode::Line(anchor) |
|
||||||
|
SelectionMode::Block(anchor) => {
|
||||||
|
*anchor = match anchor {
|
||||||
|
SelectionAnchor::Start => SelectionAnchor::End,
|
||||||
|
SelectionAnchor::End => SelectionAnchor::Start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MotionKind {
|
impl MotionKind {
|
||||||
pub fn range<R: RangeBounds<usize>>(range: R) -> Self {
|
pub fn range<R: RangeBounds<usize>>(range: R) -> Self {
|
||||||
let start = match range.start_bound() {
|
let start = match range.start_bound() {
|
||||||
@@ -157,6 +193,8 @@ pub struct LineBuf {
|
|||||||
hint: Option<String>,
|
hint: Option<String>,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
clamp_cursor: bool,
|
clamp_cursor: bool,
|
||||||
|
select_mode: Option<SelectionMode>,
|
||||||
|
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
|
||||||
@@ -174,6 +212,20 @@ impl LineBuf {
|
|||||||
self.buffer = initial.to_string();
|
self.buffer = initial.to_string();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn selected_range(&self) -> Option<&Range<usize>> {
|
||||||
|
self.selected_range.as_ref()
|
||||||
|
}
|
||||||
|
pub fn is_selecting(&self) -> bool {
|
||||||
|
self.select_mode.is_some()
|
||||||
|
}
|
||||||
|
pub fn stop_selecting(&mut self) {
|
||||||
|
self.select_mode = None;
|
||||||
|
self.selected_range = None;
|
||||||
|
}
|
||||||
|
pub fn start_selecting(&mut self, mode: SelectionMode) {
|
||||||
|
self.select_mode = Some(mode);
|
||||||
|
self.selected_range = Some(self.cursor..(self.cursor + 1).min(self.byte_len().saturating_sub(1)))
|
||||||
|
}
|
||||||
pub fn has_hint(&self) -> bool {
|
pub fn has_hint(&self) -> bool {
|
||||||
self.hint.is_some()
|
self.hint.is_some()
|
||||||
}
|
}
|
||||||
@@ -271,6 +323,13 @@ impl LineBuf {
|
|||||||
pub fn set_cursor_clamp(&mut self, yn: bool) {
|
pub fn set_cursor_clamp(&mut self, yn: bool) {
|
||||||
self.clamp_cursor = yn
|
self.clamp_cursor = yn
|
||||||
}
|
}
|
||||||
|
pub fn g_idx_to_byte_pos(&self, pos: usize) -> Option<usize> {
|
||||||
|
if pos >= self.byte_len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.buffer.grapheme_indices(true).map(|(i,_)| i).nth(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn grapheme_at_cursor(&self) -> Option<&str> {
|
pub fn grapheme_at_cursor(&self) -> Option<&str> {
|
||||||
if self.cursor == self.byte_len() {
|
if self.cursor == self.byte_len() {
|
||||||
None
|
None
|
||||||
@@ -518,6 +577,10 @@ impl LineBuf {
|
|||||||
let end = self.end_of_line();
|
let end = self.end_of_line();
|
||||||
self.move_to(end)
|
self.move_to(end)
|
||||||
}
|
}
|
||||||
|
/// Consume the LineBuf and return the buffer
|
||||||
|
pub fn pack_line(self) -> String {
|
||||||
|
self.buffer
|
||||||
|
}
|
||||||
pub fn accept_hint(&mut self) {
|
pub fn accept_hint(&mut self) {
|
||||||
if let Some(hint) = self.hint.take() {
|
if let Some(hint) = self.hint.take() {
|
||||||
flog!(DEBUG, "accepting hint");
|
flog!(DEBUG, "accepting hint");
|
||||||
@@ -1014,6 +1077,10 @@ impl LineBuf {
|
|||||||
let next_char = self.grapheme_at(self.next_pos(1)?)?;
|
let next_char = self.grapheme_at(self.next_pos(1)?)?;
|
||||||
let next_char_class = CharClass::from(next_char);
|
let next_char_class = CharClass::from(next_char);
|
||||||
if cur_char_class != next_char_class && next_char_class != CharClass::Whitespace {
|
if cur_char_class != next_char_class && next_char_class != CharClass::Whitespace {
|
||||||
|
let Some(end_pos) = self.find_from(pos, |c| is_other_class_or_ws(c, next_char)) else {
|
||||||
|
return Some(self.byte_len())
|
||||||
|
};
|
||||||
|
pos = end_pos.saturating_sub(1);
|
||||||
return Some(pos)
|
return Some(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1185,7 +1252,10 @@ impl LineBuf {
|
|||||||
let Some(pos) = self.find_word_pos(word, to, Direction::Forward) else {
|
let Some(pos) = self.find_word_pos(word, to, Direction::Forward) else {
|
||||||
return MotionKind::Null
|
return MotionKind::Null
|
||||||
};
|
};
|
||||||
MotionKind::To(pos)
|
match to {
|
||||||
|
To::Start => MotionKind::To(pos),
|
||||||
|
To::End => MotionKind::On(pos),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Motion::CharSearch(direction, dest, ch) => {
|
Motion::CharSearch(direction, dest, ch) => {
|
||||||
match direction {
|
match direction {
|
||||||
@@ -1230,11 +1300,15 @@ impl LineBuf {
|
|||||||
MotionKind::To(pos)
|
MotionKind::To(pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Motion::Range(_, _) => todo!(),
|
Motion::Range(start, end) => {
|
||||||
|
let start = start.clamp(0, self.byte_len().saturating_sub(1));
|
||||||
|
let end = end.clamp(0, self.byte_len().saturating_sub(1));
|
||||||
|
MotionKind::range(mk_range(start, end))
|
||||||
|
}
|
||||||
Motion::Builder(_) => todo!(),
|
Motion::Builder(_) => todo!(),
|
||||||
Motion::RepeatMotion => todo!(),
|
Motion::RepeatMotion => todo!(),
|
||||||
Motion::RepeatMotionRev => todo!(),
|
Motion::RepeatMotionRev => todo!(),
|
||||||
Motion::Null => todo!(),
|
Motion::Null => MotionKind::Null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn calculate_display_offset(&self, n_lines: isize) -> Option<usize> {
|
pub fn calculate_display_offset(&self, n_lines: isize) -> Option<usize> {
|
||||||
@@ -1308,6 +1382,10 @@ impl LineBuf {
|
|||||||
assert!(range.end <= self.byte_len());
|
assert!(range.end <= self.byte_len());
|
||||||
Some(range)
|
Some(range)
|
||||||
}
|
}
|
||||||
|
MotionKind::On(n) => {
|
||||||
|
let range = mk_range_inclusive(self.cursor, *n);
|
||||||
|
Some(range)
|
||||||
|
}
|
||||||
MotionKind::Backward(n) => {
|
MotionKind::Backward(n) => {
|
||||||
let pos = self.prev_pos(*n)?;
|
let pos = self.prev_pos(*n)?;
|
||||||
let range = pos..self.cursor;
|
let range = pos..self.cursor;
|
||||||
@@ -1396,14 +1474,13 @@ impl LineBuf {
|
|||||||
match verb {
|
match verb {
|
||||||
Verb::Change |
|
Verb::Change |
|
||||||
Verb::Delete => {
|
Verb::Delete => {
|
||||||
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
let Some(mut range) = self.get_range_from_motion(&verb, &motion) else {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
};
|
};
|
||||||
let restore_col = matches!(motion, MotionKind::Line(_)) && matches!(verb, Verb::Delete);
|
let restore_col = matches!(motion, MotionKind::Line(_)) && matches!(verb, Verb::Delete);
|
||||||
if restore_col {
|
if restore_col {
|
||||||
self.saved_col = Some(self.cursor_column())
|
self.saved_col = Some(self.cursor_column())
|
||||||
}
|
}
|
||||||
|
|
||||||
let deleted = self.buffer.drain(range.clone());
|
let deleted = self.buffer.drain(range.clone());
|
||||||
register.write_to_register(deleted.collect());
|
register.write_to_register(deleted.collect());
|
||||||
|
|
||||||
@@ -1429,6 +1506,18 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Verb::SwapVisualAnchor => {
|
||||||
|
if let Some(range) = self.selected_range() {
|
||||||
|
if let Some(mut mode) = self.select_mode {
|
||||||
|
mode.invert_anchor();
|
||||||
|
self.cursor = match mode.anchor() {
|
||||||
|
SelectionAnchor::Start => range.start,
|
||||||
|
SelectionAnchor::End => range.end,
|
||||||
|
};
|
||||||
|
self.select_mode = Some(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Verb::Yank => {
|
Verb::Yank => {
|
||||||
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
@@ -1447,6 +1536,38 @@ impl LineBuf {
|
|||||||
self.cursor = cursor_pos
|
self.cursor = cursor_pos
|
||||||
}
|
}
|
||||||
Verb::Substitute => todo!(),
|
Verb::Substitute => todo!(),
|
||||||
|
Verb::ToLower => {
|
||||||
|
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_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::ToUpper => {
|
||||||
|
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 {
|
||||||
|
new_range.push(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.buffer.replace_range(range.clone(), &new_range);
|
||||||
|
self.cursor = range.end;
|
||||||
|
}
|
||||||
Verb::ToggleCase => {
|
Verb::ToggleCase => {
|
||||||
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
@@ -1539,7 +1660,6 @@ impl LineBuf {
|
|||||||
let next_line = &self.buffer[nstart..nend].trim_start().to_string(); // strip leading whitespace
|
let next_line = &self.buffer[nstart..nend].trim_start().to_string(); // strip leading whitespace
|
||||||
flog!(DEBUG,next_line);
|
flog!(DEBUG,next_line);
|
||||||
let replace_newline_with_space = !line.ends_with([' ', '\t']);
|
let replace_newline_with_space = !line.ends_with([' ', '\t']);
|
||||||
|
|
||||||
self.cursor = end;
|
self.cursor = end;
|
||||||
if replace_newline_with_space {
|
if replace_newline_with_space {
|
||||||
self.buffer.replace_range(end..end+1, " ");
|
self.buffer.replace_range(end..end+1, " ");
|
||||||
@@ -1587,6 +1707,8 @@ impl LineBuf {
|
|||||||
Verb::ReplaceMode |
|
Verb::ReplaceMode |
|
||||||
Verb::InsertMode |
|
Verb::InsertMode |
|
||||||
Verb::NormalMode |
|
Verb::NormalMode |
|
||||||
|
Verb::VisualModeLine |
|
||||||
|
Verb::VisualModeBlock |
|
||||||
Verb::VisualMode => {
|
Verb::VisualMode => {
|
||||||
/* Already handled */
|
/* Already handled */
|
||||||
self.apply_motion(/*forced*/ true,motion);
|
self.apply_motion(/*forced*/ true,motion);
|
||||||
@@ -1595,6 +1717,7 @@ impl LineBuf {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn apply_motion(&mut self, forced: bool, motion: MotionKind) {
|
pub fn apply_motion(&mut self, forced: bool, motion: MotionKind) {
|
||||||
|
|
||||||
match motion {
|
match motion {
|
||||||
MotionKind::Forward(n) => {
|
MotionKind::Forward(n) => {
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
@@ -1618,6 +1741,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MotionKind::On(n) |
|
||||||
MotionKind::To(n) => {
|
MotionKind::To(n) => {
|
||||||
if n > self.byte_len() {
|
if n > self.byte_len() {
|
||||||
self.cursor = self.byte_len();
|
self.cursor = self.byte_len();
|
||||||
@@ -1666,6 +1790,40 @@ impl LineBuf {
|
|||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(mut mode) = self.select_mode {
|
||||||
|
let Some(range) = self.selected_range.clone() else {
|
||||||
|
return
|
||||||
|
};
|
||||||
|
let (mut start,mut end) = (range.start,range.end);
|
||||||
|
match mode {
|
||||||
|
SelectionMode::Char(anchor) => {
|
||||||
|
match anchor {
|
||||||
|
SelectionAnchor::Start => {
|
||||||
|
start = self.cursor;
|
||||||
|
}
|
||||||
|
SelectionAnchor::End => {
|
||||||
|
end = self.cursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectionMode::Line(anchor) => todo!(),
|
||||||
|
SelectionMode::Block(anchor) => todo!(),
|
||||||
|
}
|
||||||
|
if start >= end {
|
||||||
|
flog!(DEBUG, "inverting anchor");
|
||||||
|
mode.invert_anchor();
|
||||||
|
flog!(DEBUG,start,end);
|
||||||
|
std::mem::swap(&mut start, &mut end);
|
||||||
|
match mode.anchor() {
|
||||||
|
SelectionAnchor::Start => end += 1,
|
||||||
|
SelectionAnchor::End => start -= 1,
|
||||||
|
}
|
||||||
|
self.select_mode = Some(mode);
|
||||||
|
flog!(DEBUG,start,end);
|
||||||
|
flog!(DEBUG,mode);
|
||||||
|
}
|
||||||
|
self.selected_range = Some(start..end);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn edit_is_merging(&self) -> bool {
|
pub fn edit_is_merging(&self) -> bool {
|
||||||
self.undo_stack.last().is_some_and(|edit| edit.merging)
|
self.undo_stack.last().is_some_and(|edit| edit.merging)
|
||||||
@@ -1719,7 +1877,12 @@ impl LineBuf {
|
|||||||
let motion_eval = motion
|
let motion_eval = motion
|
||||||
.clone()
|
.clone()
|
||||||
.map(|m| self.eval_motion(m.1))
|
.map(|m| self.eval_motion(m.1))
|
||||||
.unwrap_or(MotionKind::Null);
|
.unwrap_or({
|
||||||
|
self.selected_range
|
||||||
|
.clone()
|
||||||
|
.map(MotionKind::range)
|
||||||
|
.unwrap_or(MotionKind::Null)
|
||||||
|
});
|
||||||
|
|
||||||
flog!(DEBUG,self.hint);
|
flog!(DEBUG,self.hint);
|
||||||
if let Some(verb) = verb.clone() {
|
if let Some(verb) = verb.clone() {
|
||||||
@@ -1756,6 +1919,9 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flog!(DEBUG, self.select_mode);
|
||||||
|
flog!(DEBUG, self.selected_range);
|
||||||
|
|
||||||
if self.clamp_cursor {
|
if self.clamp_cursor {
|
||||||
self.clamp_cursor();
|
self.clamp_cursor();
|
||||||
}
|
}
|
||||||
@@ -1767,6 +1933,20 @@ impl LineBuf {
|
|||||||
impl Display for LineBuf {
|
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() {
|
||||||
|
let mode = self.select_mode.unwrap();
|
||||||
|
match mode.anchor() {
|
||||||
|
SelectionAnchor::Start => {
|
||||||
|
let inclusive = range.start..=range.end;
|
||||||
|
let selected = full_buf[inclusive.clone()].styled(Style::BgWhite | Style::Black);
|
||||||
|
full_buf.replace_range(inclusive, &selected);
|
||||||
|
}
|
||||||
|
SelectionAnchor::End => {
|
||||||
|
let selected = full_buf[range.clone()].styled(Style::BgWhite | Style::Black);
|
||||||
|
full_buf.replace_range(range, &selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(hint) = self.hint.as_ref() {
|
if let Some(hint) = self.hint.as_ref() {
|
||||||
full_buf.push_str(&hint.styled(Style::BrightBlack));
|
full_buf.push_str(&hint.styled(Style::BrightBlack));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use history::{History, SearchConstraint, SearchKind};
|
use history::{History, SearchConstraint, SearchKind};
|
||||||
use keys::{KeyCode, KeyEvent, ModKeys};
|
use keys::{KeyCode, KeyEvent, ModKeys};
|
||||||
use linebuf::{strip_ansi_codes_and_escapes, LineBuf};
|
use linebuf::{strip_ansi_codes_and_escapes, LineBuf, SelectionAnchor, SelectionMode};
|
||||||
use mode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace};
|
use mode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVisual};
|
||||||
use term::Terminal;
|
use term::Terminal;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
use vicmd::{Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd};
|
use vicmd::{Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd};
|
||||||
@@ -307,12 +307,13 @@ impl FernVi {
|
|||||||
}
|
}
|
||||||
strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width() + 1 // 1 indexed
|
strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width() + 1 // 1 indexed
|
||||||
}
|
}
|
||||||
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, mut cmd: ViCmd) -> ShResult<()> {
|
||||||
|
let mut selecting = false;
|
||||||
if cmd.is_mode_transition() {
|
if cmd.is_mode_transition() {
|
||||||
let count = cmd.verb_count();
|
let count = cmd.verb_count();
|
||||||
let mut mode: Box<dyn ViMode> = match cmd.verb().unwrap().1 {
|
let mut mode: Box<dyn ViMode> = match cmd.verb().unwrap().1 {
|
||||||
Verb::InsertModeLineBreak(_) |
|
|
||||||
Verb::Change |
|
Verb::Change |
|
||||||
|
Verb::InsertModeLineBreak(_) |
|
||||||
Verb::InsertMode => {
|
Verb::InsertMode => {
|
||||||
Box::new(ViInsert::new().with_count(count as u16))
|
Box::new(ViInsert::new().with_count(count as u16))
|
||||||
}
|
}
|
||||||
@@ -322,7 +323,11 @@ 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::VisualMode => todo!(),
|
Verb::VisualMode => {
|
||||||
|
selecting = true;
|
||||||
|
self.line.start_selecting(SelectionMode::Char(SelectionAnchor::End));
|
||||||
|
Box::new(ViVisual::new())
|
||||||
|
}
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -334,7 +339,13 @@ impl FernVi {
|
|||||||
if mode.is_repeatable() {
|
if mode.is_repeatable() {
|
||||||
self.last_action = mode.as_replay();
|
self.last_action = mode.as_replay();
|
||||||
}
|
}
|
||||||
return self.line.exec_cmd(cmd);
|
self.line.exec_cmd(cmd)?;
|
||||||
|
if selecting {
|
||||||
|
self.line.start_selecting(SelectionMode::Char(SelectionAnchor::End));
|
||||||
|
} else {
|
||||||
|
self.line.stop_selecting();
|
||||||
|
}
|
||||||
|
return Ok(())
|
||||||
} else if cmd.is_cmd_repeat() {
|
} else if cmd.is_cmd_repeat() {
|
||||||
let Some(replay) = self.last_action.clone() else {
|
let Some(replay) = self.last_action.clone() else {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
@@ -405,12 +416,26 @@ impl FernVi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cmd.is_repeatable() {
|
if cmd.is_repeatable() {
|
||||||
|
if self.mode.report_mode() == ModeReport::Visual {
|
||||||
|
// The motion is assigned in the line buffer execution, so we also have to assign it here
|
||||||
|
// in order to be able to repeat it
|
||||||
|
let range = self.line.selected_range().unwrap();
|
||||||
|
cmd.motion = Some(MotionCmd(1,Motion::Range(range.start, range.end)))
|
||||||
|
}
|
||||||
self.last_action = Some(CmdReplay::Single(cmd.clone()));
|
self.last_action = Some(CmdReplay::Single(cmd.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.is_char_search() {
|
if cmd.is_char_search() {
|
||||||
self.last_movement = cmd.motion.clone()
|
self.last_movement = cmd.motion.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.line.exec_cmd(cmd.clone())
|
self.line.exec_cmd(cmd.clone())?;
|
||||||
|
|
||||||
|
if self.mode.report_mode() == ModeReport::Visual && cmd.verb().is_some_and(|v| v.1.is_edit()) {
|
||||||
|
self.line.stop_selecting();
|
||||||
|
let mut mode: Box<dyn ViMode> = Box::new(ViNormal::new());
|
||||||
|
std::mem::swap(&mut mode, &mut self.mode);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use super::keys::{KeyEvent as E, KeyCode as K, ModKeys as M};
|
|||||||
use super::vicmd::{Anchor, Bound, Dest, Direction, Motion, MotionBuilder, MotionCmd, RegisterName, TextObj, To, Verb, VerbBuilder, VerbCmd, ViCmd, Word};
|
use super::vicmd::{Anchor, Bound, Dest, Direction, Motion, MotionBuilder, MotionCmd, RegisterName, TextObj, To, Verb, VerbBuilder, VerbCmd, ViCmd, Word};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum ModeReport {
|
pub enum ModeReport {
|
||||||
Insert,
|
Insert,
|
||||||
Normal,
|
Normal,
|
||||||
@@ -432,6 +433,26 @@ impl ViNormal {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
'v' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::VisualMode)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'V' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::VisualModeLine)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
'o' => {
|
'o' => {
|
||||||
return Some(
|
return Some(
|
||||||
ViCmd {
|
ViCmd {
|
||||||
@@ -814,6 +835,562 @@ impl ViMode for ViNormal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default,Debug)]
|
||||||
|
pub struct ViVisual {
|
||||||
|
pending_seq: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViVisual {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
pub fn clear_cmd(&mut self) {
|
||||||
|
self.pending_seq = String::new();
|
||||||
|
}
|
||||||
|
pub fn take_cmd(&mut self) -> String {
|
||||||
|
std::mem::take(&mut self.pending_seq)
|
||||||
|
}
|
||||||
|
fn validate_combination(&self, verb: Option<&Verb>, motion: Option<&Motion>) -> CmdState {
|
||||||
|
if verb.is_none() {
|
||||||
|
match motion {
|
||||||
|
Some(Motion::TextObj(_,_)) => return CmdState::Invalid,
|
||||||
|
Some(_) => return CmdState::Complete,
|
||||||
|
None => return CmdState::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if verb.is_some() && motion.is_none() {
|
||||||
|
match verb.unwrap() {
|
||||||
|
Verb::Put(_) |
|
||||||
|
Verb::DeleteChar(_) => CmdState::Complete,
|
||||||
|
_ => CmdState::Pending
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CmdState::Complete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn parse_count(&self, chars: &mut Peekable<Chars<'_>>) -> Option<usize> {
|
||||||
|
let mut count = String::new();
|
||||||
|
let Some(_digit @ '1'..='9') = chars.peek() else {
|
||||||
|
return None
|
||||||
|
};
|
||||||
|
count.push(chars.next().unwrap());
|
||||||
|
while let Some(_digit @ '0'..='9') = chars.peek() {
|
||||||
|
count.push(chars.next().unwrap());
|
||||||
|
}
|
||||||
|
if !count.is_empty() {
|
||||||
|
count.parse::<usize>().ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// End the parse and clear the pending sequence
|
||||||
|
#[track_caller]
|
||||||
|
pub fn quit_parse(&mut self) -> Option<ViCmd> {
|
||||||
|
flog!(DEBUG, std::panic::Location::caller());
|
||||||
|
flog!(WARN, "exiting parse early with sequence: {}",self.pending_seq);
|
||||||
|
self.clear_cmd();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
pub fn try_parse(&mut self, ch: char) -> Option<ViCmd> {
|
||||||
|
self.pending_seq.push(ch);
|
||||||
|
let mut chars = self.pending_seq.chars().peekable();
|
||||||
|
|
||||||
|
let register = 'reg_parse: {
|
||||||
|
let mut chars_clone = chars.clone();
|
||||||
|
let count = self.parse_count(&mut chars_clone);
|
||||||
|
|
||||||
|
let Some('"') = chars_clone.next() else {
|
||||||
|
break 'reg_parse RegisterName::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(reg_name) = chars_clone.next() else {
|
||||||
|
return None // Pending register name
|
||||||
|
};
|
||||||
|
match reg_name {
|
||||||
|
'a'..='z' |
|
||||||
|
'A'..='Z' => { /* proceed */ }
|
||||||
|
_ => return self.quit_parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
chars = chars_clone;
|
||||||
|
RegisterName::new(Some(reg_name), count)
|
||||||
|
};
|
||||||
|
|
||||||
|
let verb = 'verb_parse: {
|
||||||
|
let mut chars_clone = chars.clone();
|
||||||
|
let count = self.parse_count(&mut chars_clone).unwrap_or(1);
|
||||||
|
|
||||||
|
let Some(ch) = chars_clone.next() else {
|
||||||
|
break 'verb_parse None
|
||||||
|
};
|
||||||
|
match ch {
|
||||||
|
'.' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::RepeatLast)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'x' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'verb_parse Some(VerbCmd(count, Verb::Delete));
|
||||||
|
}
|
||||||
|
'X' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::Delete)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||||
|
raw_seq: self.take_cmd(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'Y' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::Yank)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'D' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::Delete)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'R' |
|
||||||
|
'C' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::Change)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||||
|
raw_seq: self.take_cmd(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'>' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::Indent)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||||
|
raw_seq: self.take_cmd(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'<' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::Dedent)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||||
|
raw_seq: self.take_cmd(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'=' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::Equalize)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||||
|
raw_seq: self.take_cmd(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'p' |
|
||||||
|
'P' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'verb_parse Some(VerbCmd(count, Verb::Put(Anchor::Before)));
|
||||||
|
}
|
||||||
|
'r' => {
|
||||||
|
let ch = chars_clone.next()?;
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::ReplaceChar(ch))),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'~' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(1, Verb::ToggleCase)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'u' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::ToLower)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'U' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::ToUpper)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'O' |
|
||||||
|
'o' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::SwapVisualAnchor)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'A' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::ForwardChar)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'I' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::BeginningOfLine)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'J' => {
|
||||||
|
return Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(VerbCmd(count, Verb::JoinLines)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'y' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'verb_parse Some(VerbCmd(count, Verb::Yank))
|
||||||
|
}
|
||||||
|
'd' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'verb_parse Some(VerbCmd(count, Verb::Delete))
|
||||||
|
}
|
||||||
|
'c' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'verb_parse Some(VerbCmd(count, Verb::Change))
|
||||||
|
}
|
||||||
|
_ => break 'verb_parse None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(verb) = verb {
|
||||||
|
return Some(ViCmd {
|
||||||
|
register,
|
||||||
|
verb: Some(verb),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let motion = 'motion_parse: {
|
||||||
|
let mut chars_clone = chars.clone();
|
||||||
|
let count = self.parse_count(&mut chars_clone).unwrap_or(1);
|
||||||
|
|
||||||
|
let Some(ch) = chars_clone.next() else {
|
||||||
|
break 'motion_parse None
|
||||||
|
};
|
||||||
|
match (ch, &verb) {
|
||||||
|
('d', Some(VerbCmd(_,Verb::Delete))) |
|
||||||
|
('c', Some(VerbCmd(_,Verb::Change))) |
|
||||||
|
('y', Some(VerbCmd(_,Verb::Yank))) |
|
||||||
|
('=', Some(VerbCmd(_,Verb::Equalize))) |
|
||||||
|
('>', Some(VerbCmd(_,Verb::Indent))) |
|
||||||
|
('<', Some(VerbCmd(_,Verb::Dedent))) => break 'motion_parse Some(MotionCmd(count, Motion::WholeLine)),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
match ch {
|
||||||
|
'g' => {
|
||||||
|
if let Some(ch) = chars_clone.peek() {
|
||||||
|
match ch {
|
||||||
|
'g' => {
|
||||||
|
chars_clone.next();
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfBuffer))
|
||||||
|
}
|
||||||
|
'e' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::End, Word::Normal)));
|
||||||
|
}
|
||||||
|
'E' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::End, Word::Big)));
|
||||||
|
}
|
||||||
|
'k' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineUp));
|
||||||
|
}
|
||||||
|
'j' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineDown));
|
||||||
|
}
|
||||||
|
_ => return self.quit_parse()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break 'motion_parse None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'f' => {
|
||||||
|
let Some(ch) = chars_clone.peek() else {
|
||||||
|
break 'motion_parse None
|
||||||
|
};
|
||||||
|
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Forward, Dest::On, (*ch).into())))
|
||||||
|
}
|
||||||
|
'F' => {
|
||||||
|
let Some(ch) = chars_clone.peek() else {
|
||||||
|
break 'motion_parse None
|
||||||
|
};
|
||||||
|
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Backward, Dest::On, (*ch).into())))
|
||||||
|
}
|
||||||
|
't' => {
|
||||||
|
let Some(ch) = chars_clone.peek() else {
|
||||||
|
break 'motion_parse None
|
||||||
|
};
|
||||||
|
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Forward, Dest::Before, (*ch).into())))
|
||||||
|
}
|
||||||
|
'T' => {
|
||||||
|
let Some(ch) = chars_clone.peek() else {
|
||||||
|
break 'motion_parse None
|
||||||
|
};
|
||||||
|
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Backward, Dest::Before, (*ch).into())))
|
||||||
|
}
|
||||||
|
';' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::RepeatMotion));
|
||||||
|
}
|
||||||
|
',' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::RepeatMotionRev));
|
||||||
|
}
|
||||||
|
'|' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(1, Motion::ToColumn(count)));
|
||||||
|
}
|
||||||
|
'0' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfLine));
|
||||||
|
}
|
||||||
|
'$' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::EndOfLine));
|
||||||
|
}
|
||||||
|
'k' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::LineUp));
|
||||||
|
}
|
||||||
|
'j' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::LineDown));
|
||||||
|
}
|
||||||
|
'h' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::BackwardChar));
|
||||||
|
}
|
||||||
|
'l' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ForwardChar));
|
||||||
|
}
|
||||||
|
'w' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ForwardWord(To::Start, Word::Normal)));
|
||||||
|
}
|
||||||
|
'W' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ForwardWord(To::Start, Word::Big)));
|
||||||
|
}
|
||||||
|
'e' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ForwardWord(To::End, Word::Normal)));
|
||||||
|
}
|
||||||
|
'E' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ForwardWord(To::End, Word::Big)));
|
||||||
|
}
|
||||||
|
'b' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::Start, Word::Normal)));
|
||||||
|
}
|
||||||
|
'B' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::Start, Word::Big)));
|
||||||
|
}
|
||||||
|
ch if ch == 'i' || ch == 'a' => {
|
||||||
|
let bound = match ch {
|
||||||
|
'i' => Bound::Inside,
|
||||||
|
'a' => Bound::Around,
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
if chars_clone.peek().is_none() {
|
||||||
|
break 'motion_parse None
|
||||||
|
}
|
||||||
|
let obj = match chars_clone.next().unwrap() {
|
||||||
|
'w' => TextObj::Word(Word::Normal),
|
||||||
|
'W' => TextObj::Word(Word::Big),
|
||||||
|
'"' => TextObj::DoubleQuote,
|
||||||
|
'\'' => TextObj::SingleQuote,
|
||||||
|
'(' | ')' | 'b' => TextObj::Paren,
|
||||||
|
'{' | '}' | 'B' => TextObj::Brace,
|
||||||
|
'[' | ']' => TextObj::Bracket,
|
||||||
|
'<' | '>' => TextObj::Angle,
|
||||||
|
_ => return self.quit_parse()
|
||||||
|
};
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::TextObj(obj, bound)))
|
||||||
|
}
|
||||||
|
_ => return self.quit_parse(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if chars.peek().is_some() {
|
||||||
|
flog!(WARN, "Unused characters in Vi command parse!");
|
||||||
|
flog!(WARN, "{:?}",chars)
|
||||||
|
}
|
||||||
|
|
||||||
|
let verb_ref = verb.as_ref().map(|v| &v.1);
|
||||||
|
let motion_ref = motion.as_ref().map(|m| &m.1);
|
||||||
|
|
||||||
|
match self.validate_combination(verb_ref, motion_ref) {
|
||||||
|
CmdState::Complete => {
|
||||||
|
let cmd = Some(
|
||||||
|
ViCmd {
|
||||||
|
register,
|
||||||
|
verb,
|
||||||
|
motion,
|
||||||
|
raw_seq: std::mem::take(&mut self.pending_seq)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
CmdState::Pending => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
CmdState::Invalid => {
|
||||||
|
self.pending_seq.clear();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViMode for ViVisual {
|
||||||
|
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||||
|
match key {
|
||||||
|
E(K::Char(ch), M::NONE) => self.try_parse(ch),
|
||||||
|
E(K::Backspace, M::NONE) => {
|
||||||
|
Some(ViCmd {
|
||||||
|
register: Default::default(),
|
||||||
|
verb: None,
|
||||||
|
motion: Some(MotionCmd(1, Motion::BackwardChar)),
|
||||||
|
raw_seq: "".into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
E(K::Char('R'), M::CTRL) => {
|
||||||
|
let mut chars = self.pending_seq.chars().peekable();
|
||||||
|
let count = self.parse_count(&mut chars).unwrap_or(1);
|
||||||
|
Some(
|
||||||
|
ViCmd {
|
||||||
|
register: RegisterName::default(),
|
||||||
|
verb: Some(VerbCmd(count,Verb::Redo)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
E(K::Esc, M::NONE) => {
|
||||||
|
Some(
|
||||||
|
ViCmd {
|
||||||
|
register: Default::default(),
|
||||||
|
verb: Some(VerbCmd(1, Verb::NormalMode)),
|
||||||
|
motion: Some(MotionCmd(1, Motion::Null)),
|
||||||
|
raw_seq: self.take_cmd()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(cmd) = common_cmds(key) {
|
||||||
|
self.clear_cmd();
|
||||||
|
Some(cmd)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_repeatable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_replay(&self) -> Option<CmdReplay> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_style(&self) -> String {
|
||||||
|
"\x1b[2 q".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_seq(&self) -> Option<String> {
|
||||||
|
Some(self.pending_seq.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_cursor_on_undo(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clamp_cursor(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hist_scroll_start_pos(&self) -> Option<To> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report_mode(&self) -> ModeReport {
|
||||||
|
ModeReport::Visual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn common_cmds(key: E) -> Option<ViCmd> {
|
pub fn common_cmds(key: E) -> Option<ViCmd> {
|
||||||
let mut pending_cmd = ViCmd::new();
|
let mut pending_cmd = ViCmd::new();
|
||||||
match key {
|
match key {
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ pub enum Verb {
|
|||||||
ReplaceChar(char),
|
ReplaceChar(char),
|
||||||
Substitute,
|
Substitute,
|
||||||
ToggleCase,
|
ToggleCase,
|
||||||
|
ToLower,
|
||||||
|
ToUpper,
|
||||||
Complete,
|
Complete,
|
||||||
CompleteBackward,
|
CompleteBackward,
|
||||||
Undo,
|
Undo,
|
||||||
@@ -156,6 +158,9 @@ pub enum Verb {
|
|||||||
InsertModeLineBreak(Anchor),
|
InsertModeLineBreak(Anchor),
|
||||||
NormalMode,
|
NormalMode,
|
||||||
VisualMode,
|
VisualMode,
|
||||||
|
VisualModeLine,
|
||||||
|
VisualModeBlock,
|
||||||
|
SwapVisualAnchor,
|
||||||
JoinLines,
|
JoinLines,
|
||||||
InsertChar(char),
|
InsertChar(char),
|
||||||
Insert(String),
|
Insert(String),
|
||||||
@@ -189,6 +194,8 @@ impl Verb {
|
|||||||
Self::Change |
|
Self::Change |
|
||||||
Self::ReplaceChar(_) |
|
Self::ReplaceChar(_) |
|
||||||
Self::Substitute |
|
Self::Substitute |
|
||||||
|
Self::ToLower |
|
||||||
|
Self::ToUpper |
|
||||||
Self::ToggleCase |
|
Self::ToggleCase |
|
||||||
Self::Put(_) |
|
Self::Put(_) |
|
||||||
Self::ReplaceMode |
|
Self::ReplaceMode |
|
||||||
@@ -210,6 +217,8 @@ impl Verb {
|
|||||||
Self::ReplaceChar(_) |
|
Self::ReplaceChar(_) |
|
||||||
Self::Substitute |
|
Self::Substitute |
|
||||||
Self::ToggleCase |
|
Self::ToggleCase |
|
||||||
|
Self::ToLower |
|
||||||
|
Self::ToUpper |
|
||||||
Self::RepeatLast |
|
Self::RepeatLast |
|
||||||
Self::Put(_) |
|
Self::Put(_) |
|
||||||
Self::ReplaceMode |
|
Self::ReplaceMode |
|
||||||
|
|||||||
Reference in New Issue
Block a user