Skip to content

Commit

Permalink
Implement vi-mode "Yank" (copy):
Browse files Browse the repository at this point in the history
- Vi Command;
- Editor Command;
- Editor methods;
- Mode changes back to Normal after yank
  • Loading branch information
deephbz committed Dec 31, 2024
1 parent ff8a9da commit af86266
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 2 deletions.
178 changes: 178 additions & 0 deletions src/core_editor/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,49 @@ impl Editor {
EditCommand::CutSelection => self.cut_selection_to_cut_buffer(),
EditCommand::CopySelection => self.copy_selection_to_cut_buffer(),
EditCommand::Paste => self.paste_cut_buffer(),
EditCommand::CopyFromStart => self.copy_from_start(),
EditCommand::CopyFromLineStart => self.copy_from_line_start(),
EditCommand::CopyToEnd => self.copy_from_end(),
EditCommand::CopyToLineEnd => self.copy_to_line_end(),
EditCommand::CopyWordLeft => self.copy_word_left(),
EditCommand::CopyBigWordLeft => self.copy_big_word_left(),
EditCommand::CopyWordRight => self.copy_word_right(),
EditCommand::CopyBigWordRight => self.copy_big_word_right(),
EditCommand::CopyWordRightToNext => self.copy_word_right_to_next(),
EditCommand::CopyBigWordRightToNext => self.copy_big_word_right_to_next(),
EditCommand::CopyRightUntil(c) => self.copy_right_until_char(*c, false, true),
EditCommand::CopyRightBefore(c) => self.copy_right_until_char(*c, true, true),
EditCommand::CopyLeftUntil(c) => self.copy_left_until_char(*c, false, true),
EditCommand::CopyLeftBefore(c) => self.copy_left_until_char(*c, true, true),
EditCommand::CopyCurrentLine => {
let range = self.line_buffer.current_line_range();
let copy_slice = &self.line_buffer.get_buffer()[range];
if !copy_slice.is_empty() {
self.cut_buffer.set(copy_slice, ClipboardMode::Lines);
}
}
EditCommand::CopyLeft => {
let insertion_offset = self.line_buffer.insertion_point();
if insertion_offset > 0 {
let left_index = self.line_buffer.grapheme_left_index();
let copy_range = left_index..insertion_offset;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[copy_range],
ClipboardMode::Normal,
);
}
}
EditCommand::CopyRight => {
let insertion_offset = self.line_buffer.insertion_point();
let right_index = self.line_buffer.grapheme_right_index();
if right_index > insertion_offset {
let copy_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[copy_range],
ClipboardMode::Normal,
);
}
}
#[cfg(feature = "system_clipboard")]
EditCommand::CutSelectionSystem => self.cut_selection_to_system(),
#[cfg(feature = "system_clipboard")]
Expand Down Expand Up @@ -655,6 +698,141 @@ impl Editor {
self.delete_selection();
insert_clipboard_content_before(&mut self.line_buffer, self.cut_buffer.deref_mut());
}

pub(crate) fn copy_from_start(&mut self) {
let insertion_offset = self.line_buffer.insertion_point();
if insertion_offset > 0 {
self.cut_buffer.set(
&self.line_buffer.get_buffer()[..insertion_offset],
ClipboardMode::Normal,
);
}
}

pub(crate) fn copy_from_line_start(&mut self) {
let previous_offset = self.line_buffer.insertion_point();
let start_offset = {
let temp_pos = self.line_buffer.insertion_point();
self.line_buffer.move_to_line_start();
let start = self.line_buffer.insertion_point();
self.line_buffer.set_insertion_point(temp_pos);
start
};
let copy_range = start_offset..previous_offset;
let copy_slice = &self.line_buffer.get_buffer()[copy_range];
if !copy_slice.is_empty() {
self.cut_buffer.set(copy_slice, ClipboardMode::Normal);
}
}

pub(crate) fn copy_from_end(&mut self) {
let copy_slice = &self.line_buffer.get_buffer()[self.line_buffer.insertion_point()..];
if !copy_slice.is_empty() {
self.cut_buffer.set(copy_slice, ClipboardMode::Normal);
}
}

pub(crate) fn copy_to_line_end(&mut self) {
let copy_slice = &self.line_buffer.get_buffer()
[self.line_buffer.insertion_point()..self.line_buffer.find_current_line_end()];
if !copy_slice.is_empty() {
self.cut_buffer.set(copy_slice, ClipboardMode::Normal);
}
}

pub(crate) fn copy_word_left(&mut self) {
let insertion_offset = self.line_buffer.insertion_point();
let left_index = self.line_buffer.word_left_index();
if left_index < insertion_offset {
let copy_range = left_index..insertion_offset;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[copy_range],
ClipboardMode::Normal,
);
}
}

pub(crate) fn copy_big_word_left(&mut self) {
let insertion_offset = self.line_buffer.insertion_point();
let left_index = self.line_buffer.big_word_left_index();
if left_index < insertion_offset {
let copy_range = left_index..insertion_offset;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[copy_range],
ClipboardMode::Normal,
);
}
}

pub(crate) fn copy_word_right(&mut self) {
let insertion_offset = self.line_buffer.insertion_point();
let right_index = self.line_buffer.word_right_index();
if right_index > insertion_offset {
let copy_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[copy_range],
ClipboardMode::Normal,
);
}
}

pub(crate) fn copy_big_word_right(&mut self) {
let insertion_offset = self.line_buffer.insertion_point();
let right_index = self.line_buffer.next_whitespace();
if right_index > insertion_offset {
let copy_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[copy_range],
ClipboardMode::Normal,
);
}
}

pub(crate) fn copy_word_right_to_next(&mut self) {
let insertion_offset = self.line_buffer.insertion_point();
let right_index = self.line_buffer.word_right_start_index();
if right_index > insertion_offset {
let copy_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[copy_range],
ClipboardMode::Normal,
);
}
}

pub(crate) fn copy_big_word_right_to_next(&mut self) {
let insertion_offset = self.line_buffer.insertion_point();
let right_index = self.line_buffer.big_word_right_start_index();
if right_index > insertion_offset {
let copy_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[copy_range],
ClipboardMode::Normal,
);
}
}

pub(crate) fn copy_right_until_char(&mut self, c: char, before_char: bool, current_line: bool) {
if let Some(index) = self.line_buffer.find_char_right(c, current_line) {
let extra = if before_char { 0 } else { c.len_utf8() };
let copy_slice =
&self.line_buffer.get_buffer()[self.line_buffer.insertion_point()..index + extra];
if !copy_slice.is_empty() {
self.cut_buffer.set(copy_slice, ClipboardMode::Normal);
}
}
}

pub(crate) fn copy_left_until_char(&mut self, c: char, before_char: bool, current_line: bool) {
if let Some(index) = self.line_buffer.find_char_left(c, current_line) {
let extra = if before_char { c.len_utf8() } else { 0 };
let copy_slice =
&self.line_buffer.get_buffer()[index + extra..self.line_buffer.insertion_point()];
if !copy_slice.is_empty() {
self.cut_buffer.set(copy_slice, ClipboardMode::Normal);
}
}
}
}

fn insert_clipboard_content_before(line_buffer: &mut LineBuffer, clipboard: &mut dyn Clipboard) {
Expand Down
86 changes: 85 additions & 1 deletion src/edit_mode/vi/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ where
Some(Command::Delete)
}
}
Some('y') => {
let _ = input.next();
if let Some('i') = input.peek() {
let _ = input.next();
match input.next() {
Some(c)
if is_valid_change_inside_left(c) || is_valid_change_inside_right(c) =>
{
Some(Command::YankInside(*c))
}
_ => None,
}
} else {
Some(Command::Yank)
}
}
Some('p') => {
let _ = input.next();
Some(Command::PasteAfter)
Expand Down Expand Up @@ -133,19 +149,22 @@ pub enum Command {
RepeatLastAction,
ChangeInside(char),
DeleteInside(char),
Yank,
YankInside(char),
}

impl Command {
pub fn whole_line_char(&self) -> Option<char> {
match self {
Command::Delete => Some('d'),
Command::Change => Some('c'),
Command::Yank => Some('y'),
_ => None,
}
}

pub fn requires_motion(&self) -> bool {
matches!(self, Command::Delete | Command::Change)
matches!(self, Command::Delete | Command::Change | Command::Yank)
}

pub fn to_reedline(&self, vi_state: &mut Vi) -> Vec<ReedlineOption> {
Expand Down Expand Up @@ -187,6 +206,7 @@ impl Command {
Self::Switchcase => vec![ReedlineOption::Edit(EditCommand::SwitchcaseChar)],
// Whenever a motion is required to finish the command we must be in visual mode
Self::Delete | Self::Change => vec![ReedlineOption::Edit(EditCommand::CutSelection)],
Self::Yank => vec![ReedlineOption::Edit(EditCommand::CopySelection)],
Self::Incomplete => vec![ReedlineOption::Incomplete],
Self::RepeatLastAction => match &vi_state.previous {
Some(event) => vec![ReedlineOption::Event(event.clone())],
Expand Down Expand Up @@ -226,6 +246,23 @@ impl Command {
Self::DeleteInside(_) => {
vec![]
}
Self::YankInside(left) if is_valid_change_inside_left(left) => {
let right = bracket_for(left);
vec![
ReedlineOption::Edit(EditCommand::CopyLeftBefore(*left)),
ReedlineOption::Edit(EditCommand::CopyRightBefore(right)),
]
}
Self::YankInside(right) if is_valid_change_inside_right(right) => {
let left = bracket_for(right);
vec![
ReedlineOption::Edit(EditCommand::CopyLeftBefore(left)),
ReedlineOption::Edit(EditCommand::CopyRightBefore(*right)),
]
}
Self::YankInside(_) => {
vec![]
}
}
}

Expand Down Expand Up @@ -344,6 +381,53 @@ impl Command {
vec
})
}
Self::Yank => match motion {
Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::CopyToLineEnd)]),
Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::CopyCurrentLine)]),
Motion::NextWord => {
Some(vec![ReedlineOption::Edit(EditCommand::CopyWordRightToNext)])
}
Motion::NextBigWord => Some(vec![ReedlineOption::Edit(
EditCommand::CopyBigWordRightToNext,
)]),
Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CopyWordRight)]),
Motion::NextBigWordEnd => {
Some(vec![ReedlineOption::Edit(EditCommand::CopyBigWordRight)])
}
Motion::PreviousWord => Some(vec![ReedlineOption::Edit(EditCommand::CopyWordLeft)]),
Motion::PreviousBigWord => {
Some(vec![ReedlineOption::Edit(EditCommand::CopyBigWordLeft)])
}
Motion::RightUntil(c) => {
vi_state.last_char_search = Some(ViCharSearch::ToRight(*c));
Some(vec![ReedlineOption::Edit(EditCommand::CopyRightUntil(*c))])
}
Motion::RightBefore(c) => {
vi_state.last_char_search = Some(ViCharSearch::TillRight(*c));
Some(vec![ReedlineOption::Edit(EditCommand::CopyRightBefore(*c))])
}
Motion::LeftUntil(c) => {
vi_state.last_char_search = Some(ViCharSearch::ToLeft(*c));
Some(vec![ReedlineOption::Edit(EditCommand::CopyLeftUntil(*c))])
}
Motion::LeftBefore(c) => {
vi_state.last_char_search = Some(ViCharSearch::TillLeft(*c));
Some(vec![ReedlineOption::Edit(EditCommand::CopyLeftBefore(*c))])
}
Motion::Start => Some(vec![ReedlineOption::Edit(EditCommand::CopyFromLineStart)]),
Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::CopyLeft)]),
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::CopyRight)]),
Motion::Up => None,
Motion::Down => None,
Motion::ReplayCharSearch => vi_state
.last_char_search
.as_ref()
.map(|char_search| vec![ReedlineOption::Edit(char_search.to_copy())]),
Motion::ReverseCharSearch => vi_state
.last_char_search
.as_ref()
.map(|char_search| vec![ReedlineOption::Edit(char_search.reverse().to_copy())]),
},
_ => None,
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/edit_mode/vi/motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,13 @@ impl ViCharSearch {
ViCharSearch::TillLeft(c) => EditCommand::CutLeftBefore(*c),
}
}

pub fn to_copy(&self) -> EditCommand {
match self {
ViCharSearch::ToRight(c) => EditCommand::CopyRightUntil(*c),
ViCharSearch::TillRight(c) => EditCommand::CopyRightBefore(*c),
ViCharSearch::ToLeft(c) => EditCommand::CopyLeftUntil(*c),
ViCharSearch::TillLeft(c) => EditCommand::CopyLeftBefore(*c),
}
}
}
6 changes: 5 additions & 1 deletion src/edit_mode/vi/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ impl ParsedViSequence {
| (Some(Command::DeleteToEnd), ParseResult::Incomplete)
| (Some(Command::Delete), ParseResult::Valid(_))
| (Some(Command::DeleteChar), ParseResult::Valid(_))
| (Some(Command::DeleteToEnd), ParseResult::Valid(_)) => Some(ViMode::Normal),
| (Some(Command::DeleteToEnd), ParseResult::Valid(_))
| (Some(Command::Yank), ParseResult::Valid(_))
| (Some(Command::Yank), ParseResult::Incomplete)
| (Some(Command::YankInside(_)), ParseResult::Valid(_))
| (Some(Command::YankInside(_)), ParseResult::Incomplete) => Some(ViMode::Normal),
_ => None,
}
}
Expand Down
Loading

0 comments on commit af86266

Please sign in to comment.