work on implementing visual mode

This commit is contained in:
2025-05-30 19:06:09 -04:00
parent f8ba49ade3
commit 2ea91555f4
5 changed files with 808 additions and 19 deletions

View File

@@ -709,8 +709,6 @@ impl ParseStream {
node_tks.extend(node.tokens.clone());
body.push(node);
}
dbg!(&self.tokens);
panic!("{body:#?}");
self.catch_separator(&mut node_tks);
if !self.next_tk_is_some() {
self.panic_mode(&mut node_tks);

View File

@@ -16,10 +16,11 @@ pub enum CharClass {
Other
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MotionKind {
Forward(usize),
To(usize),
To(usize), // Land just before
On(usize), // Land directly on
Backward(usize),
Range((usize,usize)),
Line(isize), // positive = up line, negative = down line
@@ -31,6 +32,41 @@ pub enum MotionKind {
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 {
pub fn range<R: RangeBounds<usize>>(range: R) -> Self {
let start = match range.start_bound() {
@@ -157,6 +193,8 @@ pub struct LineBuf {
hint: Option<String>,
cursor: usize,
clamp_cursor: bool,
select_mode: Option<SelectionMode>,
selected_range: Option<Range<usize>>,
first_line_offset: usize,
saved_col: Option<usize>,
term_dims: (usize,usize), // Height, width
@@ -174,6 +212,20 @@ impl LineBuf {
self.buffer = initial.to_string();
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 {
self.hint.is_some()
}
@@ -271,6 +323,13 @@ impl LineBuf {
pub fn set_cursor_clamp(&mut self, yn: bool) {
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> {
if self.cursor == self.byte_len() {
None
@@ -518,6 +577,10 @@ impl LineBuf {
let end = self.end_of_line();
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) {
if let Some(hint) = self.hint.take() {
flog!(DEBUG, "accepting hint");
@@ -1014,6 +1077,10 @@ impl LineBuf {
let next_char = self.grapheme_at(self.next_pos(1)?)?;
let next_char_class = CharClass::from(next_char);
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)
}
@@ -1185,7 +1252,10 @@ impl LineBuf {
let Some(pos) = self.find_word_pos(word, to, Direction::Forward) else {
return MotionKind::Null
};
MotionKind::To(pos)
match to {
To::Start => MotionKind::To(pos),
To::End => MotionKind::On(pos),
}
}
Motion::CharSearch(direction, dest, ch) => {
match direction {
@@ -1230,11 +1300,15 @@ impl LineBuf {
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::RepeatMotion => todo!(),
Motion::RepeatMotionRev => todo!(),
Motion::Null => todo!(),
Motion::Null => MotionKind::Null
}
}
pub fn calculate_display_offset(&self, n_lines: isize) -> Option<usize> {
@@ -1308,6 +1382,10 @@ impl LineBuf {
assert!(range.end <= self.byte_len());
Some(range)
}
MotionKind::On(n) => {
let range = mk_range_inclusive(self.cursor, *n);
Some(range)
}
MotionKind::Backward(n) => {
let pos = self.prev_pos(*n)?;
let range = pos..self.cursor;
@@ -1396,14 +1474,13 @@ impl LineBuf {
match verb {
Verb::Change |
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(())
};
let restore_col = matches!(motion, MotionKind::Line(_)) && matches!(verb, Verb::Delete);
if restore_col {
self.saved_col = Some(self.cursor_column())
}
let deleted = self.buffer.drain(range.clone());
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 => {
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
return Ok(())
@@ -1447,6 +1536,38 @@ impl LineBuf {
self.cursor = cursor_pos
}
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 => {
let Some(range) = self.get_range_from_motion(&verb, &motion) else {
return Ok(())
@@ -1539,7 +1660,6 @@ impl LineBuf {
let next_line = &self.buffer[nstart..nend].trim_start().to_string(); // strip leading whitespace
flog!(DEBUG,next_line);
let replace_newline_with_space = !line.ends_with([' ', '\t']);
self.cursor = end;
if replace_newline_with_space {
self.buffer.replace_range(end..end+1, " ");
@@ -1587,6 +1707,8 @@ impl LineBuf {
Verb::ReplaceMode |
Verb::InsertMode |
Verb::NormalMode |
Verb::VisualModeLine |
Verb::VisualModeBlock |
Verb::VisualMode => {
/* Already handled */
self.apply_motion(/*forced*/ true,motion);
@@ -1595,6 +1717,7 @@ impl LineBuf {
Ok(())
}
pub fn apply_motion(&mut self, forced: bool, motion: MotionKind) {
match motion {
MotionKind::Forward(n) => {
for _ in 0..n {
@@ -1618,6 +1741,7 @@ impl LineBuf {
}
}
}
MotionKind::On(n) |
MotionKind::To(n) => {
if n > self.byte_len() {
self.cursor = self.byte_len();
@@ -1666,6 +1790,40 @@ impl LineBuf {
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 {
self.undo_stack.last().is_some_and(|edit| edit.merging)
@@ -1719,7 +1877,12 @@ impl LineBuf {
let motion_eval = motion
.clone()
.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);
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 {
self.clamp_cursor();
}
@@ -1767,6 +1933,20 @@ impl LineBuf {
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();
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() {
full_buf.push_str(&hint.styled(Style::BrightBlack));
}

View File

@@ -2,8 +2,8 @@ use std::time::Duration;
use history::{History, SearchConstraint, SearchKind};
use keys::{KeyCode, KeyEvent, ModKeys};
use linebuf::{strip_ansi_codes_and_escapes, LineBuf};
use mode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace};
use linebuf::{strip_ansi_codes_and_escapes, LineBuf, SelectionAnchor, SelectionMode};
use mode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVisual};
use term::Terminal;
use unicode_width::UnicodeWidthStr;
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
}
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() {
let count = cmd.verb_count();
let mut mode: Box<dyn ViMode> = match cmd.verb().unwrap().1 {
Verb::InsertModeLineBreak(_) |
Verb::Change |
Verb::InsertModeLineBreak(_) |
Verb::InsertMode => {
Box::new(ViInsert::new().with_count(count as u16))
}
@@ -322,7 +323,11 @@ impl FernVi {
Verb::ReplaceMode => {
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!()
};
@@ -334,7 +339,13 @@ impl FernVi {
if mode.is_repeatable() {
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() {
let Some(replay) = self.last_action.clone() else {
return Ok(())
@@ -405,12 +416,26 @@ impl FernVi {
}
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()));
}
if cmd.is_char_search() {
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(())
}
}

View File

@@ -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 crate::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ModeReport {
Insert,
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' => {
return Some(
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> {
let mut pending_cmd = ViCmd::new();
match key {

View File

@@ -145,6 +145,8 @@ pub enum Verb {
ReplaceChar(char),
Substitute,
ToggleCase,
ToLower,
ToUpper,
Complete,
CompleteBackward,
Undo,
@@ -156,6 +158,9 @@ pub enum Verb {
InsertModeLineBreak(Anchor),
NormalMode,
VisualMode,
VisualModeLine,
VisualModeBlock,
SwapVisualAnchor,
JoinLines,
InsertChar(char),
Insert(String),
@@ -189,6 +194,8 @@ impl Verb {
Self::Change |
Self::ReplaceChar(_) |
Self::Substitute |
Self::ToLower |
Self::ToUpper |
Self::ToggleCase |
Self::Put(_) |
Self::ReplaceMode |
@@ -210,6 +217,8 @@ impl Verb {
Self::ReplaceChar(_) |
Self::Substitute |
Self::ToggleCase |
Self::ToLower |
Self::ToUpper |
Self::RepeatLast |
Self::Put(_) |
Self::ReplaceMode |