implemented quote/delimiter text objects
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
use std::{fmt::Display, ops::{Range, RangeBounds, RangeInclusive}, string::Drain};
|
||||
use std::{fmt::Display, ops::{Range, RangeInclusive}};
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::{term::Layout, vicmd::{Anchor, Dest, Direction, Motion, MotionBehavior, MotionCmd, RegisterName, To, Verb, ViCmd, Word}};
|
||||
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, term::{Style, Styled}}, prelude::*};
|
||||
use super::vicmd::{Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb, ViCmd, Word};
|
||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*};
|
||||
|
||||
#[derive(Default,PartialEq,Eq,Debug,Clone,Copy)]
|
||||
pub enum CharClass {
|
||||
@@ -75,10 +75,6 @@ fn is_other_class_or_is_ws(a: &str, b: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_other_class_and_is_ws(a: &str, b: &str) -> bool {
|
||||
is_other_class(a, b) && (is_whitespace(a) || is_whitespace(b))
|
||||
}
|
||||
|
||||
#[derive(Default,Clone,Copy,PartialEq,Eq,Debug)]
|
||||
pub enum SelectAnchor {
|
||||
#[default]
|
||||
@@ -93,6 +89,28 @@ pub enum SelectMode {
|
||||
Block(SelectAnchor),
|
||||
}
|
||||
|
||||
impl SelectMode {
|
||||
pub fn anchor(&self) -> &SelectAnchor {
|
||||
match self {
|
||||
SelectMode::Char(anchor) |
|
||||
SelectMode::Line(anchor) |
|
||||
SelectMode::Block(anchor) => anchor
|
||||
}
|
||||
}
|
||||
pub fn invert_anchor(&mut self) {
|
||||
match self {
|
||||
SelectMode::Char(anchor) |
|
||||
SelectMode::Line(anchor) |
|
||||
SelectMode::Block(anchor) => {
|
||||
*anchor = match anchor {
|
||||
SelectAnchor::Start => SelectAnchor::End,
|
||||
SelectAnchor::End => SelectAnchor::Start
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MotionKind {
|
||||
To(usize), // Absolute position, exclusive
|
||||
@@ -323,7 +341,12 @@ impl LineBuf {
|
||||
pub fn accept_hint(&mut self) {
|
||||
let Some(hint) = self.hint.take() else { return };
|
||||
|
||||
let old = self.buffer.clone();
|
||||
let cursor_pos = self.cursor.get();
|
||||
self.push_str(&hint);
|
||||
let new = self.as_str();
|
||||
let edit = Edit::diff(&old, new, cursor_pos);
|
||||
self.undo_stack.push(edit);
|
||||
self.cursor.add(hint.len());
|
||||
}
|
||||
pub fn set_cursor_clamp(&mut self, yn: bool) {
|
||||
@@ -339,6 +362,12 @@ impl LineBuf {
|
||||
.copied()
|
||||
.unwrap_or(self.buffer.len())
|
||||
}
|
||||
pub fn read_idx_byte_pos(&self, index: usize) -> usize {
|
||||
self.grapheme_indices()
|
||||
.get(index)
|
||||
.copied()
|
||||
.unwrap_or(self.buffer.len())
|
||||
}
|
||||
/// Update self.grapheme_indices with the indices of the current buffer
|
||||
#[track_caller]
|
||||
pub fn update_graphemes(&mut self) {
|
||||
@@ -628,15 +657,297 @@ impl LineBuf {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn dispatch_text_obj(
|
||||
&mut self,
|
||||
count: usize,
|
||||
text_obj: TextObj,
|
||||
bound: Bound
|
||||
) -> Option<(usize,usize)> {
|
||||
match text_obj {
|
||||
// Text groups
|
||||
TextObj::Word(word) => self.text_obj_word(count, bound, word),
|
||||
TextObj::Sentence(dir) => self.text_obj_sentence(count, dir, bound),
|
||||
TextObj::Paragraph(dir) => self.text_obj_paragraph(count, dir, bound),
|
||||
|
||||
pub fn dispatch_word_motion(&mut self, count: usize, to: To, word: Word, dir: Direction) -> usize {
|
||||
// Quoted blocks
|
||||
TextObj::DoubleQuote |
|
||||
TextObj::SingleQuote |
|
||||
TextObj::BacktickQuote => self.text_obj_quote(count, text_obj, bound),
|
||||
|
||||
// Delimited blocks
|
||||
TextObj::Paren |
|
||||
TextObj::Bracket |
|
||||
TextObj::Brace |
|
||||
TextObj::Angle => self.text_obj_delim(count, text_obj, bound),
|
||||
|
||||
// Other stuff
|
||||
TextObj::Tag => todo!(),
|
||||
TextObj::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
pub fn text_obj_word(&mut self, count: usize, bound: Bound, word: Word) -> Option<(usize,usize)> {
|
||||
todo!()
|
||||
}
|
||||
pub fn text_obj_sentence(&mut self, count: usize, dir: Direction, bound: Bound) -> Option<(usize, usize)> {
|
||||
todo!()
|
||||
}
|
||||
pub fn text_obj_paragraph(&mut self, count: usize, dir: Direction, bound: Bound) -> Option<(usize, usize)> {
|
||||
todo!()
|
||||
}
|
||||
pub fn text_obj_delim(&mut self, count: usize, text_obj: TextObj, bound: Bound) -> Option<(usize,usize)> {
|
||||
let mut backward_indices = (0..self.cursor.get()).rev();
|
||||
let (opener,closer) = match text_obj {
|
||||
TextObj::Paren => ("(",")"),
|
||||
TextObj::Bracket => ("[","]"),
|
||||
TextObj::Brace => ("{","}"),
|
||||
TextObj::Angle => ("<",">"),
|
||||
_ => unreachable!()
|
||||
};
|
||||
|
||||
let mut start_pos = None;
|
||||
let mut closer_count: u32 = 0;
|
||||
while let Some(idx) = backward_indices.next() {
|
||||
let gr = self.grapheme_at(idx)?.to_string();
|
||||
if gr != closer && gr != opener { continue }
|
||||
|
||||
let mut escaped = false;
|
||||
while let Some(idx) = backward_indices.next() {
|
||||
// Keep consuming indices as long as they refer to a backslash
|
||||
let Some("\\") = self.grapheme_at(idx) else {
|
||||
break
|
||||
};
|
||||
// On each backslash, flip this boolean
|
||||
escaped = !escaped
|
||||
}
|
||||
|
||||
// If there are an even number of backslashes (or none), we are not escaped
|
||||
// Therefore, we have found the start position
|
||||
if !escaped {
|
||||
if gr == closer {
|
||||
closer_count += 1;
|
||||
} else if closer_count == 0 {
|
||||
start_pos = Some(idx);
|
||||
break
|
||||
} else {
|
||||
closer_count = closer_count.saturating_sub(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (mut start, mut end) = if let Some(pos) = start_pos {
|
||||
let start = pos;
|
||||
let mut forward_indices = start+1..self.cursor.max;
|
||||
let mut end = None;
|
||||
let mut opener_count: u32 = 0;
|
||||
|
||||
while let Some(idx) = forward_indices.next() {
|
||||
match self.grapheme_at(idx)? {
|
||||
"\\" => { forward_indices.next(); }
|
||||
gr if gr == opener => opener_count += 1,
|
||||
gr if gr == closer => {
|
||||
if opener_count == 0 {
|
||||
end = Some(idx);
|
||||
break
|
||||
} else {
|
||||
opener_count = opener_count.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
_ => { /* Continue */ }
|
||||
}
|
||||
}
|
||||
|
||||
(start,end?)
|
||||
} else {
|
||||
let mut forward_indices = self.cursor.get()..self.cursor.max;
|
||||
let mut start = None;
|
||||
let mut end = None;
|
||||
let mut opener_count: u32 = 0;
|
||||
|
||||
while let Some(idx) = forward_indices.next() {
|
||||
match self.grapheme_at(idx)? {
|
||||
"\\" => { forward_indices.next(); }
|
||||
gr if gr == opener => {
|
||||
if opener_count == 0 {
|
||||
start = Some(idx);
|
||||
}
|
||||
opener_count += 1;
|
||||
}
|
||||
gr if gr == closer => {
|
||||
if opener_count == 1 {
|
||||
end = Some(idx);
|
||||
break
|
||||
} else {
|
||||
opener_count = opener_count.saturating_sub(1)
|
||||
}
|
||||
}
|
||||
_ => { /* Continue */ }
|
||||
}
|
||||
}
|
||||
|
||||
(start?,end?)
|
||||
};
|
||||
|
||||
match bound {
|
||||
Bound::Inside => {
|
||||
// Start includes the quote, so push it forward
|
||||
start += 1;
|
||||
}
|
||||
Bound::Around => {
|
||||
// End excludes the quote, so push it forward
|
||||
end += 1;
|
||||
|
||||
// We also need to include any trailing whitespace
|
||||
let end_of_line = self.end_of_line();
|
||||
let remainder = end..end_of_line;
|
||||
for idx in remainder {
|
||||
let Some(gr) = self.grapheme_at(idx) else { break };
|
||||
flog!(DEBUG, gr);
|
||||
if is_whitespace(gr) {
|
||||
end += 1;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some((start,end))
|
||||
}
|
||||
pub fn text_obj_quote(&mut self, count: usize, text_obj: TextObj, bound: Bound) -> Option<(usize,usize)> {
|
||||
let (start,end) = self.this_line(); // Only operates on the current line
|
||||
|
||||
// Get the grapheme indices backward from the cursor
|
||||
let mut backward_indices = (start..self.cursor.get()).rev();
|
||||
let target = match text_obj {
|
||||
TextObj::DoubleQuote => "\"",
|
||||
TextObj::SingleQuote => "'",
|
||||
TextObj::BacktickQuote => "`",
|
||||
_ => unreachable!()
|
||||
};
|
||||
let mut start_pos = None;
|
||||
while let Some(idx) = backward_indices.next() {
|
||||
match self.grapheme_at(idx)? {
|
||||
gr if gr == target => {
|
||||
// We are going backwards, so we need to handle escapes differently
|
||||
// These things were not meant to be read backwards, so it's a little fucked up
|
||||
let mut escaped = false;
|
||||
while let Some(idx) = backward_indices.next() {
|
||||
// Keep consuming indices as long as they refer to a backslash
|
||||
let Some("\\") = self.grapheme_at(idx) else {
|
||||
break
|
||||
};
|
||||
// On each backslash, flip this boolean
|
||||
escaped = !escaped
|
||||
}
|
||||
|
||||
// If there are an even number of backslashes, we are not escaped
|
||||
// Therefore, we have found the start position
|
||||
if !escaped {
|
||||
start_pos = Some(idx);
|
||||
break
|
||||
}
|
||||
}
|
||||
_ => { /* Continue */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find a quote backwards
|
||||
let (mut start, mut end) = if let Some(pos) = start_pos {
|
||||
// Found one, only one more to go
|
||||
let start = pos;
|
||||
let mut forward_indices = start+1..end;
|
||||
let mut end = None;
|
||||
|
||||
while let Some(idx) = forward_indices.next() {
|
||||
match self.grapheme_at(idx)? {
|
||||
"\\" => { forward_indices.next(); }
|
||||
gr if gr == target => {
|
||||
end = Some(idx);
|
||||
break;
|
||||
}
|
||||
_ => { /* Continue */ }
|
||||
}
|
||||
}
|
||||
let end = end?;
|
||||
|
||||
(start,end)
|
||||
} else {
|
||||
// Did not find one, have two find two of them forward now
|
||||
let mut forward_indices = self.cursor.get()..end;
|
||||
let mut start = None;
|
||||
let mut end = None;
|
||||
|
||||
while let Some(idx) = forward_indices.next() {
|
||||
match self.grapheme_at(idx)? {
|
||||
"\\" => { forward_indices.next(); }
|
||||
gr if gr == target => {
|
||||
start = Some(idx);
|
||||
break
|
||||
}
|
||||
_ => { /* Continue */ }
|
||||
}
|
||||
}
|
||||
let start = start?;
|
||||
|
||||
while let Some(idx) = forward_indices.next() {
|
||||
match self.grapheme_at(idx)? {
|
||||
"\\" => { forward_indices.next(); }
|
||||
gr if gr == target => {
|
||||
end = Some(idx);
|
||||
break;
|
||||
}
|
||||
_ => { /* Continue */ }
|
||||
}
|
||||
}
|
||||
let end = end?;
|
||||
|
||||
(start,end)
|
||||
};
|
||||
|
||||
match bound {
|
||||
Bound::Inside => {
|
||||
// Start includes the quote, so push it forward
|
||||
start += 1;
|
||||
}
|
||||
Bound::Around => {
|
||||
// End excludes the quote, so push it forward
|
||||
end += 1;
|
||||
|
||||
// We also need to include any trailing whitespace
|
||||
let end_of_line = self.end_of_line();
|
||||
let remainder = end..end_of_line;
|
||||
for idx in remainder {
|
||||
let Some(gr) = self.grapheme_at(idx) else { break };
|
||||
flog!(DEBUG, gr);
|
||||
if is_whitespace(gr) {
|
||||
end += 1;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some((start, end))
|
||||
}
|
||||
pub fn dispatch_word_motion(
|
||||
&mut self,
|
||||
count: usize,
|
||||
to: To,
|
||||
word: Word,
|
||||
dir: Direction,
|
||||
include_last_char: bool
|
||||
) -> usize {
|
||||
// Not sorry for these method names btw
|
||||
let mut pos = ClampedUsize::new(self.cursor.get(), self.cursor.max, false);
|
||||
for _ in 0..count {
|
||||
for i in 0..count {
|
||||
// We alter 'include_last_char' to only be true on the last iteration
|
||||
// Therefore, '5cw' will find the correct range for the first four and stop on the end of the fifth word
|
||||
let include_last_char_and_is_last_word = include_last_char && i == count.saturating_sub(1);
|
||||
pos.set(match to {
|
||||
To::Start => {
|
||||
match dir {
|
||||
Direction::Forward => self.start_of_word_forward_or_end_of_word_backward_from(pos.get(), word, dir),
|
||||
Direction::Forward => self.start_of_word_forward_or_end_of_word_backward_from(pos.get(), word, dir, include_last_char_and_is_last_word),
|
||||
Direction::Backward => 'backward: {
|
||||
// We also need to handle insert mode's Ctrl+W behaviors here
|
||||
let target = self.end_of_word_forward_or_start_of_word_backward_from(pos.get(), word, dir);
|
||||
@@ -662,7 +973,7 @@ impl LineBuf {
|
||||
To::End => {
|
||||
match dir {
|
||||
Direction::Forward => self.end_of_word_forward_or_start_of_word_backward_from(pos.get(), word, dir),
|
||||
Direction::Backward => self.start_of_word_forward_or_end_of_word_backward_from(pos.get(), word, dir),
|
||||
Direction::Backward => self.start_of_word_forward_or_end_of_word_backward_from(pos.get(), word, dir, false),
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -676,7 +987,7 @@ impl LineBuf {
|
||||
/// are logically the same operation, if you use a reversed iterator for the backward motion.
|
||||
///
|
||||
/// Tied with 'end_of_word_forward_or_start_of_word_backward_from()' for the longest method name I have ever written
|
||||
pub fn start_of_word_forward_or_end_of_word_backward_from(&mut self, mut pos: usize, word: Word, dir: Direction) -> usize {
|
||||
pub fn start_of_word_forward_or_end_of_word_backward_from(&mut self, mut pos: usize, word: Word, dir: Direction, include_last_char: bool) -> usize {
|
||||
let default = match dir {
|
||||
Direction::Backward => 0,
|
||||
Direction::Forward => self.grapheme_indices().len()
|
||||
@@ -691,7 +1002,12 @@ impl LineBuf {
|
||||
let on_boundary = self.grapheme_at(*next).is_none_or(is_whitespace);
|
||||
if on_boundary {
|
||||
let Some(idx) = indices_iter.next() else { return default };
|
||||
pos = idx;
|
||||
// We have a 'cw' call, do not include the trailing whitespace
|
||||
if include_last_char {
|
||||
return idx;
|
||||
} else {
|
||||
pos = idx;
|
||||
}
|
||||
}
|
||||
|
||||
// Check current grapheme
|
||||
@@ -702,9 +1018,12 @@ impl LineBuf {
|
||||
|
||||
// Find the next whitespace
|
||||
if !on_whitespace {
|
||||
let Some(_ws_pos) = indices_iter.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace)) else {
|
||||
let Some(ws_pos) = indices_iter.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace)) else {
|
||||
return default
|
||||
};
|
||||
if include_last_char {
|
||||
return ws_pos
|
||||
}
|
||||
}
|
||||
|
||||
// Return the next visible grapheme position
|
||||
@@ -716,7 +1035,11 @@ impl LineBuf {
|
||||
let Some(next_idx) = indices_iter.peek() else { return default };
|
||||
let on_boundary = !is_whitespace(&cur_char) && self.grapheme_at(*next_idx).is_none_or(|c| is_other_class_or_is_ws(c, &cur_char));
|
||||
if on_boundary {
|
||||
pos = *next_idx
|
||||
if include_last_char {
|
||||
return *next_idx
|
||||
} else {
|
||||
pos = *next_idx;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(next_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||
@@ -739,9 +1062,9 @@ impl LineBuf {
|
||||
return default
|
||||
};
|
||||
// If we hit a different character class, we return here
|
||||
if self.grapheme_at(other_class_pos).is_some_and(|c| !is_whitespace(c)) {
|
||||
if self.grapheme_at(other_class_pos).is_some_and(|c| !is_whitespace(c)) || include_last_char {
|
||||
return other_class_pos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are now certainly on a whitespace character. Advance until a non-whitespace character.
|
||||
@@ -939,6 +1262,11 @@ impl LineBuf {
|
||||
pub fn find<F: Fn(&str) -> bool>(&mut self, op: F) -> usize {
|
||||
self.find_from(self.cursor.get(), op)
|
||||
}
|
||||
pub fn insert_str_at(&mut self, pos: usize, new: &str) {
|
||||
let idx = self.index_byte_pos(pos);
|
||||
self.buffer.insert_str(idx, new);
|
||||
self.update_graphemes();
|
||||
}
|
||||
pub fn replace_at_cursor(&mut self, new: &str) {
|
||||
self.replace_at(self.cursor.get(), new);
|
||||
}
|
||||
@@ -966,7 +1294,7 @@ impl LineBuf {
|
||||
let end = start + gr.len();
|
||||
self.buffer.replace_range(start..end, new);
|
||||
}
|
||||
pub fn eval_motion(&mut self, motion: MotionCmd) -> MotionKind {
|
||||
pub fn eval_motion(&mut self, verb: Option<&Verb>, motion: MotionCmd) -> MotionKind {
|
||||
let buffer = self.buffer.clone();
|
||||
if self.has_hint() {
|
||||
let hint = self.hint.clone().unwrap();
|
||||
@@ -1004,7 +1332,12 @@ impl LineBuf {
|
||||
MotionKind::InclusiveWithTargetCol((start,end),target_pos)
|
||||
}
|
||||
MotionCmd(count,Motion::WordMotion(to, word, dir)) => {
|
||||
let pos = self.dispatch_word_motion(count, to, word, dir);
|
||||
// 'cw' is a weird case
|
||||
// if you are on the word's left boundary, it will not delete whitespace after the end of the word
|
||||
let include_last_char = verb == Some(&Verb::Change) &&
|
||||
matches!(motion.1, Motion::WordMotion(To::Start, _, Direction::Forward));
|
||||
|
||||
let pos = self.dispatch_word_motion(count, to, word, dir, include_last_char);
|
||||
let pos = ClampedUsize::new(pos,self.cursor.max,false);
|
||||
// End-based operations must include the last character
|
||||
// But the cursor must also stop just before it when moving
|
||||
@@ -1023,7 +1356,17 @@ impl LineBuf {
|
||||
MotionKind::On(pos.get())
|
||||
}
|
||||
}
|
||||
MotionCmd(count,Motion::TextObj(text_obj, bound)) => todo!(),
|
||||
MotionCmd(count,Motion::TextObj(text_obj, bound)) => {
|
||||
let Some((start,end)) = self.dispatch_text_obj(count, text_obj, bound) else {
|
||||
return MotionKind::Null
|
||||
};
|
||||
|
||||
MotionKind::Inclusive((start,end))
|
||||
}
|
||||
MotionCmd(count,Motion::ToDelimMatch) => todo!(),
|
||||
MotionCmd(count,Motion::ToBrace(direction)) => todo!(),
|
||||
MotionCmd(count,Motion::ToBracket(direction)) => todo!(),
|
||||
MotionCmd(count,Motion::ToParen(direction)) => todo!(),
|
||||
MotionCmd(count,Motion::EndOfLastWord) => {
|
||||
let start = self.start_of_line();
|
||||
let mut newline_count = 0;
|
||||
@@ -1204,11 +1547,21 @@ impl LineBuf {
|
||||
MotionCmd(_count,Motion::BeginningOfBuffer) => MotionKind::On(0),
|
||||
MotionCmd(_count,Motion::EndOfBuffer) => MotionKind::To(self.grapheme_indices().len()),
|
||||
MotionCmd(_count,Motion::ToColumn) => todo!(),
|
||||
MotionCmd(count,Motion::ToDelimMatch) => todo!(),
|
||||
MotionCmd(count,Motion::ToBrace(direction)) => todo!(),
|
||||
MotionCmd(count,Motion::ToBracket(direction)) => todo!(),
|
||||
MotionCmd(count,Motion::ToParen(direction)) => todo!(),
|
||||
MotionCmd(count,Motion::Range(start, end)) => todo!(),
|
||||
MotionCmd(count,Motion::Range(start, end)) => {
|
||||
let mut final_end = end;
|
||||
if self.cursor.exclusive {
|
||||
final_end += 1;
|
||||
}
|
||||
let delta = end - start;
|
||||
let count = count.saturating_sub(1); // Becomes number of times to multiply the range
|
||||
|
||||
for _ in 0..count {
|
||||
final_end += delta;
|
||||
}
|
||||
|
||||
final_end = final_end.min(self.cursor.max);
|
||||
MotionKind::Inclusive((start,final_end))
|
||||
}
|
||||
MotionCmd(count,Motion::RepeatMotion) => todo!(),
|
||||
MotionCmd(count,Motion::RepeatMotionRev) => todo!(),
|
||||
MotionCmd(count,Motion::Null) => MotionKind::Null
|
||||
@@ -1268,6 +1621,35 @@ impl LineBuf {
|
||||
self.move_cursor(motion);
|
||||
}
|
||||
self.update_graphemes();
|
||||
self.update_select_range();
|
||||
}
|
||||
pub fn update_select_range(&mut self) {
|
||||
if let Some(mut mode) = self.select_mode {
|
||||
let Some((mut start,mut end)) = self.select_range.clone() else {
|
||||
return
|
||||
};
|
||||
match mode {
|
||||
SelectMode::Char(anchor) => {
|
||||
match anchor {
|
||||
SelectAnchor::Start => {
|
||||
start = self.cursor.get();
|
||||
}
|
||||
SelectAnchor::End => {
|
||||
end = self.cursor.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectMode::Line(anchor) => todo!(),
|
||||
SelectMode::Block(anchor) => todo!(),
|
||||
}
|
||||
if start >= end {
|
||||
mode.invert_anchor();
|
||||
std::mem::swap(&mut start, &mut end);
|
||||
|
||||
self.select_mode = Some(mode);
|
||||
}
|
||||
self.select_range = Some((start,end));
|
||||
}
|
||||
}
|
||||
pub fn move_cursor(&mut self, motion: MotionKind) {
|
||||
match motion {
|
||||
@@ -1382,24 +1764,45 @@ impl LineBuf {
|
||||
self.replace_at_cursor(new);
|
||||
self.apply_motion(motion);
|
||||
}
|
||||
Verb::ToggleCaseSingle => {
|
||||
let Some(gr) = self.grapheme_at_cursor() else {
|
||||
return Ok(())
|
||||
};
|
||||
if gr.len() > 1 || gr.is_empty() {
|
||||
return Ok(())
|
||||
Verb::ReplaceCharInplace(ch,count) => {
|
||||
for i in 0..count {
|
||||
let mut buf = [0u8;4];
|
||||
let new = ch.encode_utf8(&mut buf);
|
||||
self.replace_at_cursor(new);
|
||||
|
||||
// try to increment the cursor until we are on the last iteration
|
||||
// or until we hit the end of the buffer
|
||||
if i != count.saturating_sub(1) && !self.cursor.inc() {
|
||||
break
|
||||
}
|
||||
}
|
||||
let ch = gr.chars().next().unwrap();
|
||||
if !ch.is_alphabetic() {
|
||||
return Ok(())
|
||||
}
|
||||
Verb::ToggleCaseInplace(count) => {
|
||||
for i in 0..count {
|
||||
let Some(gr) = self.grapheme_at_cursor() else {
|
||||
return Ok(())
|
||||
};
|
||||
if gr.len() > 1 || gr.is_empty() {
|
||||
return Ok(())
|
||||
}
|
||||
let ch = gr.chars().next().unwrap();
|
||||
if !ch.is_alphabetic() {
|
||||
return Ok(())
|
||||
}
|
||||
let mut buf = [0u8;4];
|
||||
let new = if ch.is_ascii_lowercase() {
|
||||
ch.to_ascii_uppercase().encode_utf8(&mut buf)
|
||||
} else {
|
||||
ch.to_ascii_lowercase().encode_utf8(&mut buf)
|
||||
};
|
||||
self.replace_at_cursor(new);
|
||||
|
||||
// try to increment the cursor until we are on the last iteration
|
||||
// or until we hit the end of the buffer
|
||||
if i != count.saturating_sub(1) && !self.cursor.inc() {
|
||||
break
|
||||
}
|
||||
}
|
||||
let mut buf = [0u8;4];
|
||||
let new = if ch.is_ascii_lowercase() {
|
||||
ch.to_ascii_uppercase().encode_utf8(&mut buf)
|
||||
} else {
|
||||
ch.to_ascii_lowercase().encode_utf8(&mut buf)
|
||||
};
|
||||
self.replace_at_cursor(new);
|
||||
}
|
||||
Verb::ToggleCaseRange => {
|
||||
let Some((start,end)) = self.range_from_motion(&motion) else {
|
||||
@@ -1476,7 +1879,9 @@ impl LineBuf {
|
||||
Verb::Redo |
|
||||
Verb::Undo => {
|
||||
let (edit_provider,edit_receiver) = match verb {
|
||||
// Redo = pop from redo stack, push to undo stack
|
||||
Verb::Redo => (&mut self.redo_stack, &mut self.undo_stack),
|
||||
// Undo = pop from undo stack, push to redo stack
|
||||
Verb::Undo => (&mut self.undo_stack, &mut self.redo_stack),
|
||||
_ => unreachable!()
|
||||
};
|
||||
@@ -1495,8 +1900,30 @@ impl LineBuf {
|
||||
self.update_graphemes();
|
||||
}
|
||||
Verb::RepeatLast => todo!(),
|
||||
Verb::Put(anchor) => todo!(),
|
||||
Verb::SwapVisualAnchor => todo!(),
|
||||
Verb::Put(anchor) => {
|
||||
let Some(content) = register.read_from_register() else {
|
||||
return Ok(())
|
||||
};
|
||||
let insert_idx = match anchor {
|
||||
Anchor::After => self.cursor.ret_add(1),
|
||||
Anchor::Before => self.cursor.get()
|
||||
};
|
||||
self.insert_str_at(insert_idx, &content);
|
||||
self.cursor.add(content.len().saturating_sub(1));
|
||||
}
|
||||
Verb::SwapVisualAnchor => {
|
||||
if let Some((start,end)) = self.select_range() {
|
||||
if let Some(mut mode) = self.select_mode {
|
||||
mode.invert_anchor();
|
||||
let new_cursor_pos = match mode.anchor() {
|
||||
SelectAnchor::Start => start,
|
||||
SelectAnchor::End => end,
|
||||
};
|
||||
self.cursor.set(new_cursor_pos);
|
||||
self.select_mode = Some(mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
Verb::JoinLines => {
|
||||
let start = self.start_of_line();
|
||||
let Some((_,mut end)) = self.nth_next_line(1) else {
|
||||
@@ -1529,7 +1956,6 @@ impl LineBuf {
|
||||
let graphemes = string.graphemes(true).count();
|
||||
self.cursor.add(graphemes);
|
||||
}
|
||||
Verb::Breakline(anchor) => todo!(),
|
||||
Verb::Indent => {
|
||||
let Some((start,end)) = self.range_from_motion(&motion) else {
|
||||
return Ok(())
|
||||
@@ -1622,49 +2048,60 @@ impl LineBuf {
|
||||
}
|
||||
}
|
||||
|
||||
let ViCmd { register, verb, motion, raw_seq: _ } = cmd;
|
||||
let ViCmd { register, verb, motion, flags, raw_seq: _ } = cmd;
|
||||
|
||||
let verb_count = verb.as_ref().map(|v| v.0).unwrap_or(1);
|
||||
let motion_count = motion.as_ref().map(|m| m.0);
|
||||
let verb_cmd_ref = verb.as_ref();
|
||||
let verb_ref = verb_cmd_ref.map(|v| v.1.clone());
|
||||
let verb_count = verb_cmd_ref.map(|v| v.0).unwrap_or(1);
|
||||
|
||||
let before = self.buffer.clone();
|
||||
let cursor_pos = self.cursor.get();
|
||||
|
||||
for i in 0..verb_count {
|
||||
/*
|
||||
* Let's evaluate the motion now
|
||||
* If motion is None, we will try to use self.select_range
|
||||
* If self.select_range is None, we will use MotionKind::Null
|
||||
*/
|
||||
let motion_eval = motion
|
||||
/*
|
||||
* Let's evaluate the motion now
|
||||
* If we got some weird command like 'dvw' we will have to simulate a visual selection to get the range
|
||||
* If motion is None, we will try to use self.select_range
|
||||
* If self.select_range is None, we will use MotionKind::Null
|
||||
*/
|
||||
let motion_eval = if flags.intersects(CmdFlags::VISUAL | CmdFlags::VISUAL_LINE | CmdFlags::VISUAL_BLOCK) {
|
||||
let motion = motion
|
||||
.clone()
|
||||
.map(|m| self.eval_motion(m))
|
||||
.map(|m| self.eval_motion(verb_ref.as_ref(), m))
|
||||
.unwrap_or(MotionKind::Null);
|
||||
let mode = match flags {
|
||||
CmdFlags::VISUAL => SelectMode::Char(SelectAnchor::End),
|
||||
CmdFlags::VISUAL_LINE => SelectMode::Line(SelectAnchor::End),
|
||||
CmdFlags::VISUAL_BLOCK => SelectMode::Block(SelectAnchor::End),
|
||||
_ => unreachable!()
|
||||
};
|
||||
// Start a selection
|
||||
self.start_selecting(mode);
|
||||
// Apply the cursor motion
|
||||
self.apply_motion(motion);
|
||||
|
||||
// Use the selection range created by the motion
|
||||
self.select_range
|
||||
.map(MotionKind::Inclusive)
|
||||
.unwrap_or(MotionKind::Null)
|
||||
} else {
|
||||
motion
|
||||
.clone()
|
||||
.map(|m| self.eval_motion(verb_ref.as_ref(), m))
|
||||
.unwrap_or({
|
||||
self.select_range
|
||||
.map(MotionKind::Inclusive)
|
||||
.unwrap_or(MotionKind::Null)
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(verb) = verb.clone() {
|
||||
self.exec_verb(verb.1, motion_eval, register)?;
|
||||
|
||||
if is_inplace_edit && i != verb_count.saturating_sub(1) {
|
||||
/*
|
||||
Used to calculate motions for stuff like '5~' or '8rg'
|
||||
Those verbs don't have a motion, and always land on
|
||||
the last character that they operate on.
|
||||
Therefore, we increment the cursor until we hit verb_count - 1
|
||||
or the end of the buffer
|
||||
*/
|
||||
if !self.cursor.inc() {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.apply_motion(motion_eval);
|
||||
}
|
||||
if let Some(verb) = verb.clone() {
|
||||
self.exec_verb(verb.1, motion_eval, register)?;
|
||||
} else {
|
||||
self.apply_motion(motion_eval);
|
||||
}
|
||||
|
||||
/* Done executing, do some cleanup */
|
||||
|
||||
let after = self.buffer.clone();
|
||||
if clear_redos {
|
||||
self.redo_stack.clear();
|
||||
@@ -1701,14 +2138,32 @@ impl LineBuf {
|
||||
}
|
||||
|
||||
impl Display for LineBuf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let buf = self.buffer.clone();
|
||||
write!(f,"{buf}")?;
|
||||
if let Some(hint) = self.hint() {
|
||||
let hint_styled = hint.styled(Style::BrightBlack);
|
||||
write!(f,"{hint_styled}")?;
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut full_buf = self.buffer.clone();
|
||||
if let Some((start,end)) = self.select_range.clone() {
|
||||
let mode = self.select_mode.unwrap();
|
||||
let start_byte = self.read_idx_byte_pos(start);
|
||||
let end_byte = self.read_idx_byte_pos(end);
|
||||
|
||||
match mode.anchor() {
|
||||
SelectAnchor::Start => {
|
||||
let mut inclusive = start_byte..=end_byte;
|
||||
if *inclusive.end() == full_buf.len() {
|
||||
inclusive = start_byte..=end_byte.saturating_sub(1);
|
||||
}
|
||||
let selected = full_buf[inclusive.clone()].styled(Style::BgWhite | Style::Black);
|
||||
full_buf.replace_range(inclusive, &selected);
|
||||
}
|
||||
SelectAnchor::End => {
|
||||
let selected = full_buf[start..end].styled(Style::BgWhite | Style::Black);
|
||||
full_buf.replace_range(start_byte..end_byte, &selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
if let Some(hint) = self.hint.as_ref() {
|
||||
full_buf.push_str(&hint.styled(Style::BrightBlack));
|
||||
}
|
||||
write!(f,"{}",full_buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use keys::{KeyCode, KeyEvent, ModKeys};
|
||||
use linebuf::{LineBuf, SelectAnchor, SelectMode};
|
||||
use nix::libc::STDOUT_FILENO;
|
||||
use term::{get_win_size, raw_mode, KeyReader, Layout, LineWriter, TermReader, TermWriter};
|
||||
use vicmd::{Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd};
|
||||
use vicmd::{CmdFlags, Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd};
|
||||
use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVisual};
|
||||
|
||||
use crate::libsh::{error::{ShErr, ShErrKind, ShResult}, sys::sh_quit, term::{Style, Styled}};
|
||||
@@ -107,7 +107,7 @@ impl FernVi {
|
||||
old_layout: None,
|
||||
repeat_action: None,
|
||||
repeat_motion: None,
|
||||
editor: LineBuf::new(),
|
||||
editor: LineBuf::new().with_initial("this buffer has (some delimited) text", 0),
|
||||
history: History::new()?
|
||||
})
|
||||
}
|
||||
@@ -280,12 +280,12 @@ impl FernVi {
|
||||
|
||||
std::mem::swap(&mut mode, &mut self.mode);
|
||||
|
||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||
if mode.is_repeatable() {
|
||||
self.repeat_action = mode.as_replay();
|
||||
}
|
||||
|
||||
self.editor.exec_cmd(cmd)?;
|
||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||
|
||||
if selecting {
|
||||
self.editor.start_selecting(SelectMode::Char(SelectAnchor::End));
|
||||
@@ -345,7 +345,8 @@ impl FernVi {
|
||||
register: RegisterName::default(),
|
||||
verb: None,
|
||||
motion: Some(motion),
|
||||
raw_seq: format!("{count};")
|
||||
raw_seq: format!("{count};"),
|
||||
flags: CmdFlags::empty()
|
||||
};
|
||||
return self.editor.exec_cmd(repeat_cmd);
|
||||
}
|
||||
@@ -359,7 +360,8 @@ impl FernVi {
|
||||
register: RegisterName::default(),
|
||||
verb: None,
|
||||
motion: Some(new_motion),
|
||||
raw_seq: format!("{count},")
|
||||
raw_seq: format!("{count},"),
|
||||
flags: CmdFlags::empty()
|
||||
};
|
||||
return self.editor.exec_cmd(repeat_cmd);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
use super::register::{append_register, read_register, write_register};
|
||||
|
||||
//TODO: write tests that take edit results and cursor positions from actual neovim edits and test them against the behavior of this editor
|
||||
@@ -54,12 +56,22 @@ impl Default for RegisterName {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct CmdFlags: u32 {
|
||||
const VISUAL = 1<<0;
|
||||
const VISUAL_LINE = 1<<1;
|
||||
const VISUAL_BLOCK = 1<<2;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Default,Debug)]
|
||||
pub struct ViCmd {
|
||||
pub register: RegisterName,
|
||||
pub verb: Option<VerbCmd>,
|
||||
pub motion: Option<MotionCmd>,
|
||||
pub raw_seq: String,
|
||||
pub flags: CmdFlags,
|
||||
}
|
||||
|
||||
impl ViCmd {
|
||||
@@ -84,6 +96,15 @@ impl ViCmd {
|
||||
pub fn motion_count(&self) -> usize {
|
||||
self.motion.as_ref().map(|m| m.0).unwrap_or(1)
|
||||
}
|
||||
pub fn normalize_counts(&mut self) {
|
||||
let Some(verb) = self.verb.as_mut() else { return };
|
||||
let Some(motion) = self.motion.as_mut() else { return };
|
||||
let VerbCmd(v_count, _) = verb;
|
||||
let MotionCmd(m_count, _) = motion;
|
||||
let product = *v_count * *m_count;
|
||||
verb.0 = 1;
|
||||
motion.0 = product;
|
||||
}
|
||||
pub fn is_repeatable(&self) -> bool {
|
||||
self.verb.as_ref().is_some_and(|v| v.1.is_repeatable())
|
||||
}
|
||||
@@ -103,7 +124,7 @@ impl ViCmd {
|
||||
self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::Undo | Verb::Redo))
|
||||
}
|
||||
pub fn is_inplace_edit(&self) -> bool {
|
||||
self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::ReplaceChar(_) | Verb::ToggleCaseSingle)) &&
|
||||
self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::ReplaceCharInplace(_,_) | Verb::ToggleCaseInplace(_))) &&
|
||||
self.motion.is_none()
|
||||
}
|
||||
pub fn is_line_motion(&self) -> bool {
|
||||
@@ -168,8 +189,9 @@ pub enum Verb {
|
||||
Change,
|
||||
Yank,
|
||||
Rot13, // lol
|
||||
ReplaceChar(char),
|
||||
ToggleCaseSingle,
|
||||
ReplaceChar(char), // char to replace with, number of chars to replace
|
||||
ReplaceCharInplace(char,u16), // char to replace with, number of chars to replace
|
||||
ToggleCaseInplace(u16), // Number of chars to toggle
|
||||
ToggleCaseRange,
|
||||
ToLower,
|
||||
ToUpper,
|
||||
@@ -191,7 +213,6 @@ pub enum Verb {
|
||||
JoinLines,
|
||||
InsertChar(char),
|
||||
Insert(String),
|
||||
Breakline(Anchor),
|
||||
Indent,
|
||||
Dedent,
|
||||
Equalize,
|
||||
@@ -206,17 +227,17 @@ impl Verb {
|
||||
Self::Delete |
|
||||
Self::Change |
|
||||
Self::ReplaceChar(_) |
|
||||
Self::ReplaceCharInplace(_,_) |
|
||||
Self::ToLower |
|
||||
Self::ToUpper |
|
||||
Self::ToggleCaseRange |
|
||||
Self::ToggleCaseSingle |
|
||||
Self::ToggleCaseInplace(_) |
|
||||
Self::Put(_) |
|
||||
Self::ReplaceMode |
|
||||
Self::InsertModeLineBreak(_) |
|
||||
Self::JoinLines |
|
||||
Self::InsertChar(_) |
|
||||
Self::Insert(_) |
|
||||
Self::Breakline(_) |
|
||||
Self::Indent |
|
||||
Self::Dedent |
|
||||
Self::Equalize
|
||||
@@ -227,8 +248,9 @@ impl Verb {
|
||||
Self::Delete |
|
||||
Self::Change |
|
||||
Self::ReplaceChar(_) |
|
||||
Self::ReplaceCharInplace(_,_) |
|
||||
Self::ToggleCaseRange |
|
||||
Self::ToggleCaseSingle |
|
||||
Self::ToggleCaseInplace(_) |
|
||||
Self::ToLower |
|
||||
Self::ToUpper |
|
||||
Self::RepeatLast |
|
||||
@@ -238,7 +260,6 @@ impl Verb {
|
||||
Self::JoinLines |
|
||||
Self::InsertChar(_) |
|
||||
Self::Insert(_) |
|
||||
Self::Breakline(_) |
|
||||
Self::Rot13 |
|
||||
Self::EndOfFile
|
||||
)
|
||||
@@ -247,7 +268,8 @@ impl Verb {
|
||||
matches!(self,
|
||||
Self::Change |
|
||||
Self::InsertChar(_) |
|
||||
Self::ReplaceChar(_)
|
||||
Self::ReplaceChar(_) |
|
||||
Self::ReplaceCharInplace(_,_)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -320,10 +342,8 @@ impl Motion {
|
||||
Self::ScreenLineUpCharwise |
|
||||
Self::ScreenLineDownCharwise |
|
||||
Self::ToColumn |
|
||||
Self::TextObj(TextObj::ForwardSentence,_) |
|
||||
Self::TextObj(TextObj::BackwardSentence,_) |
|
||||
Self::TextObj(TextObj::ForwardParagraph,_) |
|
||||
Self::TextObj(TextObj::BackwardParagraph,_) |
|
||||
Self::TextObj(TextObj::Sentence(_),_) |
|
||||
Self::TextObj(TextObj::Paragraph(_),_) |
|
||||
Self::CharSearch(Direction::Backward, _, _) |
|
||||
Self::WordMotion(To::Start,_,_) |
|
||||
Self::ToBrace(_) |
|
||||
@@ -355,16 +375,11 @@ pub enum TextObj {
|
||||
/// `iw`, `aw` — inner word, around word
|
||||
Word(Word),
|
||||
|
||||
/// for stuff like 'dd'
|
||||
Line,
|
||||
|
||||
/// `is`, `as` — inner sentence, around sentence
|
||||
ForwardSentence,
|
||||
BackwardSentence,
|
||||
Sentence(Direction),
|
||||
|
||||
/// `ip`, `ap` — inner paragraph, around paragraph
|
||||
ForwardParagraph,
|
||||
BackwardParagraph,
|
||||
Paragraph(Direction),
|
||||
|
||||
/// `i"`, `a"` — inner/around double quotes
|
||||
DoubleQuote,
|
||||
|
||||
@@ -6,7 +6,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use super::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
|
||||
use super::linebuf::CharClass;
|
||||
use super::vicmd::{Anchor, Bound, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb, VerbCmd, ViCmd, Word};
|
||||
use super::vicmd::{Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb, VerbCmd, ViCmd, Word};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -82,7 +82,8 @@ impl ViInsert {
|
||||
self
|
||||
}
|
||||
pub fn register_and_return(&mut self) -> Option<ViCmd> {
|
||||
let cmd = self.take_cmd();
|
||||
let mut cmd = self.take_cmd();
|
||||
cmd.normalize_counts();
|
||||
self.register_cmd(&cmd);
|
||||
Some(cmd)
|
||||
}
|
||||
@@ -188,19 +189,11 @@ impl ViReplace {
|
||||
self
|
||||
}
|
||||
pub fn register_and_return(&mut self) -> Option<ViCmd> {
|
||||
let cmd = self.take_cmd();
|
||||
let mut cmd = self.take_cmd();
|
||||
cmd.normalize_counts();
|
||||
self.register_cmd(&cmd);
|
||||
Some(cmd)
|
||||
}
|
||||
pub fn ctrl_w_is_undo(&self) -> bool {
|
||||
let insert_count = self.cmds.iter().filter(|cmd| {
|
||||
matches!(cmd.verb(),Some(VerbCmd(1, Verb::ReplaceChar(_))))
|
||||
}).count();
|
||||
let backspace_count = self.cmds.iter().filter(|cmd| {
|
||||
matches!(cmd.verb(),Some(VerbCmd(1, Verb::Delete)))
|
||||
}).count();
|
||||
insert_count > backspace_count
|
||||
}
|
||||
pub fn register_cmd(&mut self, cmd: &ViCmd) {
|
||||
self.cmds.push(cmd.clone())
|
||||
}
|
||||
@@ -218,14 +211,9 @@ impl ViMode for ViReplace {
|
||||
self.register_and_return()
|
||||
}
|
||||
E(K::Char('W'), M::CTRL) => {
|
||||
if self.ctrl_w_is_undo() {
|
||||
self.pending_cmd.set_verb(VerbCmd(1,Verb::Undo));
|
||||
self.cmds.clear();
|
||||
Some(self.take_cmd())
|
||||
} else {
|
||||
self.pending_cmd.set_motion(MotionCmd(1, Motion::WordMotion(To::Start, Word::Normal, Direction::Backward)));
|
||||
self.register_and_return()
|
||||
}
|
||||
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
|
||||
self.pending_cmd.set_motion(MotionCmd(1, Motion::WordMotion(To::Start, Word::Normal, Direction::Backward)));
|
||||
self.register_and_return()
|
||||
}
|
||||
E(K::Char('H'), M::CTRL) |
|
||||
E(K::Backspace, M::NONE) => {
|
||||
@@ -280,6 +268,7 @@ impl ViMode for ViReplace {
|
||||
#[derive(Default,Debug)]
|
||||
pub struct ViNormal {
|
||||
pending_seq: String,
|
||||
pending_flags: CmdFlags,
|
||||
}
|
||||
|
||||
impl ViNormal {
|
||||
@@ -292,6 +281,9 @@ impl ViNormal {
|
||||
pub fn take_cmd(&mut self) -> String {
|
||||
std::mem::take(&mut self.pending_seq)
|
||||
}
|
||||
pub fn flags(&self) -> CmdFlags {
|
||||
self.pending_flags
|
||||
}
|
||||
#[allow(clippy::unnecessary_unwrap)]
|
||||
fn validate_combination(&self, verb: Option<&Verb>, motion: Option<&Motion>) -> CmdState {
|
||||
if verb.is_none() {
|
||||
@@ -337,6 +329,12 @@ impl ViNormal {
|
||||
self.pending_seq.push(ch);
|
||||
let mut chars = self.pending_seq.chars().peekable();
|
||||
|
||||
/*
|
||||
* Parse the register
|
||||
*
|
||||
* Registers can be any letter a-z or A-Z.
|
||||
* While uncommon, it is possible to give a count to a register name.
|
||||
*/
|
||||
let register = 'reg_parse: {
|
||||
let mut chars_clone = chars.clone();
|
||||
let count = self.parse_count(&mut chars_clone);
|
||||
@@ -345,7 +343,7 @@ impl ViNormal {
|
||||
break 'reg_parse RegisterName::default()
|
||||
};
|
||||
|
||||
let Some(reg_name) = chars_clone.next() else {
|
||||
let Some(reg_name) = chars_clone.next() else {
|
||||
return None // Pending register name
|
||||
};
|
||||
match reg_name {
|
||||
@@ -358,6 +356,17 @@ impl ViNormal {
|
||||
RegisterName::new(Some(reg_name), count)
|
||||
};
|
||||
|
||||
/*
|
||||
* We will now parse the verb
|
||||
* If we hit an invalid sequence, we will call 'return self.quit_parse()'
|
||||
* self.quit_parse() will clear the pending command and return None
|
||||
*
|
||||
* If we hit an incomplete sequence, we will simply return None.
|
||||
* returning None leaves the pending sequence where it is
|
||||
*
|
||||
* Note that we do use a label here for the block and 'return' values from this scope
|
||||
* using "break 'verb_parse <value>"
|
||||
*/
|
||||
let verb = 'verb_parse: {
|
||||
let mut chars_clone = chars.clone();
|
||||
let count = self.parse_count(&mut chars_clone).unwrap_or(1);
|
||||
@@ -375,7 +384,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::VisualModeSelectLast)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags(),
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -412,6 +422,7 @@ impl ViNormal {
|
||||
verb: Some(VerbCmd(count, Verb::RepeatLast)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -421,7 +432,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::ForwardChar)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -431,7 +443,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::BackwardChar)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -441,8 +454,9 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Change)),
|
||||
motion: Some(MotionCmd(1, Motion::ForwardChar)),
|
||||
raw_seq: self.take_cmd()
|
||||
}
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
},
|
||||
)
|
||||
}
|
||||
'S' => {
|
||||
@@ -451,7 +465,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Change)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -476,9 +491,10 @@ impl ViNormal {
|
||||
return Some(
|
||||
ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::ReplaceChar(ch))),
|
||||
verb: Some(VerbCmd(1, Verb::ReplaceCharInplace(ch,count as u16))),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -488,7 +504,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::ReplaceMode)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -496,9 +513,10 @@ impl ViNormal {
|
||||
return Some(
|
||||
ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::ToggleCaseSingle)),
|
||||
verb: Some(VerbCmd(1, Verb::ToggleCaseInplace(count as u16))),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -508,7 +526,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Undo)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -518,7 +537,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::VisualMode)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -528,7 +548,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::VisualModeLine)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -538,7 +559,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::InsertModeLineBreak(Anchor::After))),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -548,7 +570,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::InsertModeLineBreak(Anchor::Before))),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -558,7 +581,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||
motion: Some(MotionCmd(1, Motion::ForwardChar)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -568,7 +592,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||
motion: Some(MotionCmd(1, Motion::EndOfLine)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -578,7 +603,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -588,7 +614,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||
motion: Some(MotionCmd(1, Motion::BeginningOfFirstWord)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -598,7 +625,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::JoinLines)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -620,7 +648,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Yank)),
|
||||
motion: Some(MotionCmd(1, Motion::EndOfLine)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -630,7 +659,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::EndOfLine)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -640,7 +670,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Change)),
|
||||
motion: Some(MotionCmd(1, Motion::EndOfLine)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -672,15 +703,6 @@ impl ViNormal {
|
||||
('~', Some(VerbCmd(_,Verb::ToggleCaseRange))) |
|
||||
('>', Some(VerbCmd(_,Verb::Indent))) |
|
||||
('<', Some(VerbCmd(_,Verb::Dedent))) => break 'motion_parse Some(MotionCmd(count, Motion::WholeLine)),
|
||||
|
||||
// Exception cases
|
||||
('w', Some(VerbCmd(_, Verb::Change))) => {
|
||||
// 'w' usually means 'to the start of the next word'
|
||||
// e.g. 'dw' deleted to the start of the next word
|
||||
// however, 'cw' only changes to the end of the current word
|
||||
// 'caw' is used to change to the start of the next word
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WordMotion(To::End, Word::Normal, Direction::Forward)));
|
||||
}
|
||||
('W', Some(VerbCmd(_, Verb::Change))) => {
|
||||
// Same with 'W'
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WordMotion(To::End, Word::Big, Direction::Forward)));
|
||||
@@ -689,47 +711,69 @@ impl ViNormal {
|
||||
}
|
||||
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::WordMotion(To::End, Word::Normal, Direction::Backward)));
|
||||
}
|
||||
'E' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WordMotion(To::End, Word::Big, Direction::Backward)));
|
||||
}
|
||||
'k' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineUp));
|
||||
}
|
||||
'j' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineDown));
|
||||
}
|
||||
'_' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::EndOfLastWord));
|
||||
}
|
||||
'0' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfScreenLine));
|
||||
}
|
||||
'^' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::FirstGraphicalOnScreenLine));
|
||||
}
|
||||
_ => return self.quit_parse()
|
||||
}
|
||||
} else {
|
||||
let Some(ch) = chars_clone.peek() else {
|
||||
break 'motion_parse None
|
||||
};
|
||||
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::WordMotion(To::End, Word::Normal, Direction::Backward)));
|
||||
}
|
||||
'E' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WordMotion(To::End, Word::Big, Direction::Backward)));
|
||||
}
|
||||
'k' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineUp));
|
||||
}
|
||||
'j' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineDown));
|
||||
}
|
||||
'_' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::EndOfLastWord));
|
||||
}
|
||||
'0' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfScreenLine));
|
||||
}
|
||||
'^' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::FirstGraphicalOnScreenLine));
|
||||
}
|
||||
_ => return self.quit_parse()
|
||||
}
|
||||
}
|
||||
'v' => {
|
||||
// We got 'v' after a verb
|
||||
// Instead of normal operations, we will calculate the span based on how visual mode would see it
|
||||
if self.flags().intersects(CmdFlags::VISUAL | CmdFlags::VISUAL_LINE | CmdFlags::VISUAL_BLOCK) {
|
||||
// We can't have more than one of these
|
||||
return self.quit_parse();
|
||||
}
|
||||
self.pending_flags |= CmdFlags::VISUAL;
|
||||
break 'motion_parse None
|
||||
}
|
||||
'V' => {
|
||||
// We got 'V' after a verb
|
||||
// Instead of normal operations, we will calculate the span based on how visual line mode would see it
|
||||
if self.flags().intersects(CmdFlags::VISUAL | CmdFlags::VISUAL_LINE | CmdFlags::VISUAL_BLOCK) {
|
||||
// We can't have more than one of these
|
||||
// I know vim can technically do this, but it doesn't really make sense to allow it
|
||||
// since even in vim only the first one given is used
|
||||
return self.quit_parse();
|
||||
}
|
||||
self.pending_flags |= CmdFlags::VISUAL;
|
||||
break 'motion_parse None
|
||||
}
|
||||
// TODO: figure out how to include 'Ctrl+V' here, might need a refactor
|
||||
'G' => {
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::EndOfBuffer));
|
||||
@@ -840,6 +884,7 @@ impl ViNormal {
|
||||
'W' => TextObj::Word(Word::Big),
|
||||
'"' => TextObj::DoubleQuote,
|
||||
'\'' => TextObj::SingleQuote,
|
||||
'`' => TextObj::BacktickQuote,
|
||||
'(' | ')' | 'b' => TextObj::Paren,
|
||||
'{' | '}' | 'B' => TextObj::Brace,
|
||||
'[' | ']' => TextObj::Bracket,
|
||||
@@ -868,7 +913,8 @@ impl ViNormal {
|
||||
register,
|
||||
verb,
|
||||
motion,
|
||||
raw_seq: std::mem::take(&mut self.pending_seq)
|
||||
raw_seq: std::mem::take(&mut self.pending_seq),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -885,7 +931,7 @@ impl ViNormal {
|
||||
|
||||
impl ViMode for ViNormal {
|
||||
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||
match key {
|
||||
let mut cmd = match key {
|
||||
E(K::Char(ch), M::NONE) => self.try_parse(ch),
|
||||
E(K::Backspace, M::NONE) => {
|
||||
Some(ViCmd {
|
||||
@@ -893,6 +939,7 @@ impl ViMode for ViNormal {
|
||||
verb: None,
|
||||
motion: Some(MotionCmd(1, Motion::BackwardChar)),
|
||||
raw_seq: "".into(),
|
||||
flags: self.flags()
|
||||
})
|
||||
}
|
||||
E(K::Char('R'), M::CTRL) => {
|
||||
@@ -903,7 +950,8 @@ impl ViMode for ViNormal {
|
||||
register: RegisterName::default(),
|
||||
verb: Some(VerbCmd(count,Verb::Redo)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -919,7 +967,12 @@ impl ViMode for ViNormal {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(cmd) = cmd.as_mut() {
|
||||
cmd.normalize_counts();
|
||||
};
|
||||
cmd
|
||||
}
|
||||
|
||||
fn is_repeatable(&self) -> bool {
|
||||
@@ -1051,7 +1104,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::VisualModeSelectLast)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1061,7 +1115,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Rot13)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1078,6 +1133,7 @@ impl ViVisual {
|
||||
verb: Some(VerbCmd(count, Verb::RepeatLast)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1092,6 +1148,7 @@ impl ViVisual {
|
||||
verb: Some(VerbCmd(1, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1101,7 +1158,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Yank)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1111,7 +1169,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1123,6 +1182,7 @@ impl ViVisual {
|
||||
verb: Some(VerbCmd(1, Verb::Change)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1133,6 +1193,7 @@ impl ViVisual {
|
||||
verb: Some(VerbCmd(1, Verb::Indent)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1143,6 +1204,7 @@ impl ViVisual {
|
||||
verb: Some(VerbCmd(1, Verb::Dedent)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1153,6 +1215,7 @@ impl ViVisual {
|
||||
verb: Some(VerbCmd(1, Verb::Equalize)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1168,7 +1231,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::ReplaceChar(ch))),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1178,7 +1242,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::ToggleCaseRange)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1188,7 +1253,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::ToLower)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1198,7 +1264,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::ToUpper)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1209,7 +1276,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::SwapVisualAnchor)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1219,7 +1287,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||
motion: Some(MotionCmd(1, Motion::ForwardChar)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1229,7 +1298,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::InsertMode)),
|
||||
motion: Some(MotionCmd(1, Motion::BeginningOfLine)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1239,7 +1309,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::JoinLines)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1264,7 +1335,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb: Some(verb),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1294,18 +1366,22 @@ impl ViVisual {
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfBuffer))
|
||||
}
|
||||
'e' => {
|
||||
chars_clone.next();
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WordMotion(To::End, Word::Normal, Direction::Backward)));
|
||||
}
|
||||
'E' => {
|
||||
chars_clone.next();
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WordMotion(To::End, Word::Big, Direction::Backward)));
|
||||
}
|
||||
'k' => {
|
||||
chars_clone.next();
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineUp));
|
||||
}
|
||||
'j' => {
|
||||
chars_clone.next();
|
||||
chars = chars_clone;
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineDown));
|
||||
}
|
||||
@@ -1417,6 +1493,7 @@ impl ViVisual {
|
||||
'W' => TextObj::Word(Word::Big),
|
||||
'"' => TextObj::DoubleQuote,
|
||||
'\'' => TextObj::SingleQuote,
|
||||
'`' => TextObj::BacktickQuote,
|
||||
'(' | ')' | 'b' => TextObj::Paren,
|
||||
'{' | '}' | 'B' => TextObj::Brace,
|
||||
'[' | ']' => TextObj::Bracket,
|
||||
@@ -1445,7 +1522,8 @@ impl ViVisual {
|
||||
register,
|
||||
verb,
|
||||
motion,
|
||||
raw_seq: std::mem::take(&mut self.pending_seq)
|
||||
raw_seq: std::mem::take(&mut self.pending_seq),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1462,7 +1540,7 @@ impl ViVisual {
|
||||
|
||||
impl ViMode for ViVisual {
|
||||
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||
match key {
|
||||
let mut cmd = match key {
|
||||
E(K::Char(ch), M::NONE) => self.try_parse(ch),
|
||||
E(K::Backspace, M::NONE) => {
|
||||
Some(ViCmd {
|
||||
@@ -1470,6 +1548,7 @@ impl ViMode for ViVisual {
|
||||
verb: None,
|
||||
motion: Some(MotionCmd(1, Motion::BackwardChar)),
|
||||
raw_seq: "".into(),
|
||||
flags: CmdFlags::empty()
|
||||
})
|
||||
}
|
||||
E(K::Char('R'), M::CTRL) => {
|
||||
@@ -1480,7 +1559,8 @@ impl ViMode for ViVisual {
|
||||
register: RegisterName::default(),
|
||||
verb: Some(VerbCmd(count,Verb::Redo)),
|
||||
motion: None,
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1490,7 +1570,8 @@ impl ViMode for ViVisual {
|
||||
register: Default::default(),
|
||||
verb: Some(VerbCmd(1, Verb::NormalMode)),
|
||||
motion: Some(MotionCmd(1, Motion::Null)),
|
||||
raw_seq: self.take_cmd()
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty()
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
@@ -1501,7 +1582,12 @@ impl ViMode for ViVisual {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(cmd) = cmd.as_mut() {
|
||||
cmd.normalize_counts();
|
||||
};
|
||||
cmd
|
||||
}
|
||||
|
||||
fn is_repeatable(&self) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user