work on implementing visual mode
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user