Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cut/Yank multiple words #631

Closed
wants to merge 12 commits into from
234 changes: 125 additions & 109 deletions src/core_editor/editor.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
use std::slice::SliceIndex;

use super::{edit_stack::EditStack, Clipboard, ClipboardMode, LineBuffer};
use crate::enums::{EditType, UndoBehavior};
use crate::{core_editor::get_default_clipboard, EditCommand};

// Determines how we update the cut buffer when we next cut
enum LastCutCommand {
// We are currently running a command that includes a cut command.
This,
// We have just run a command that includes a cut command.
Last,
// We have not recently run a cut command. Replace the cut buffer if another cut happens.
BeforeLast,
}

// Which direction are we cutting in?
enum CutDirection {
Left,
Right,
}

/// Stateful editor executing changes to the underlying [`LineBuffer`]
///
/// In comparison to the state-less [`LineBuffer`] the [`Editor`] keeps track of
/// the undo/redo history and has facilities for cut/copy/yank/paste
pub struct Editor {
line_buffer: LineBuffer,
cut_buffer: Box<dyn Clipboard>,
last_cut_command: LastCutCommand,

edit_stack: EditStack<LineBuffer>,
last_undo_behavior: UndoBehavior,
Expand All @@ -19,6 +38,7 @@
Editor {
line_buffer: LineBuffer::new(),
cut_buffer: Box::new(get_default_clipboard()),
last_cut_command: LastCutCommand::BeforeLast,
edit_stack: EditStack::new(),
last_undo_behavior: UndoBehavior::CreateUndoPoint,
}
Expand Down Expand Up @@ -96,8 +116,14 @@
EditCommand::CutLeftBefore(c) => self.cut_left_until_char(*c, true, true),
EditCommand::MoveLeftUntil(c) => self.move_left_until_char(*c, false, true),
EditCommand::MoveLeftBefore(c) => self.move_left_until_char(*c, true, true),
EditCommand::StopCutting => self.last_cut_command = LastCutCommand::BeforeLast,
}

self.last_cut_command = match self.last_cut_command {
LastCutCommand::This => LastCutCommand::Last,
LastCutCommand::Last | LastCutCommand::BeforeLast => LastCutCommand::BeforeLast,
};

let new_undo_behavior = match (command, command.edit_type()) {
(_, EditType::MoveCursor) => UndoBehavior::MoveCursor,
(EditCommand::InsertChar(c), EditType::EditText) => UndoBehavior::InsertCharacter(*c),
Expand Down Expand Up @@ -213,147 +239,155 @@
self.last_undo_behavior = undo_behavior;
}

// Only updates the cut buffer if the range is not empty.
fn maybe_cut<T>(&mut self, cut_range: T, mode: ClipboardMode, direction: CutDirection)
where
T: SliceIndex<str, Output = str> + std::ops::RangeBounds<usize> + Clone,
{
let cut_slice = &self.line_buffer.get_buffer()[cut_range.clone()];
if !cut_slice.is_empty() {
// If the last command was also a cut then we want to keep accumulating in the cut buffer.
// Otherwise, replace what's in the cut buffer.
let buf;
let cut_slice = match (&self.last_cut_command, direction) {
(LastCutCommand::BeforeLast, _) => cut_slice,
(_, CutDirection::Left) => {
let existing = self.cut_buffer.get().0;
buf = format!("{cut_slice}{existing}");
&buf

Check warning on line 257 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L255-L257

Added lines #L255 - L257 were not covered by tests
}
(_, CutDirection::Right) => {
let existing = self.cut_buffer.get().0;
buf = format!("{existing}{cut_slice}");
&buf
}
};

self.cut_buffer.set(cut_slice, mode);

let start = match cut_range.start_bound() {
std::ops::Bound::Included(start) => *start,
std::ops::Bound::Excluded(start) => *start + 1,
std::ops::Bound::Unbounded => 0,

Check warning on line 271 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L270-L271

Added lines #L270 - L271 were not covered by tests
};
self.line_buffer.set_insertion_point(start);
self.line_buffer.clear_range(cut_range);
}
self.last_cut_command = LastCutCommand::This;
}

fn cut_current_line(&mut self) {
let deletion_range = self.line_buffer.current_line_range();

let cut_slice = &self.line_buffer.get_buffer()[deletion_range.clone()];
if !cut_slice.is_empty() {
self.cut_buffer.set(cut_slice, ClipboardMode::Lines);
self.line_buffer.set_insertion_point(deletion_range.start);
self.line_buffer.clear_range(deletion_range);
}
self.maybe_cut(
deletion_range.clone(),
ClipboardMode::Lines,
// FIXME: what do other shells do here?
CutDirection::Right,
);
}

fn cut_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,
);
self.line_buffer.clear_to_insertion_point();
}
self.maybe_cut(
0..insertion_offset,
ClipboardMode::Normal,
CutDirection::Left,
);

Check warning on line 296 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L292-L296

Added lines #L292 - L296 were not covered by tests
}

fn cut_from_line_start(&mut self) {
let previous_offset = self.line_buffer.insertion_point();
self.line_buffer.move_to_line_start();
let deletion_range = self.line_buffer.insertion_point()..previous_offset;
let cut_slice = &self.line_buffer.get_buffer()[deletion_range.clone()];
if !cut_slice.is_empty() {
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
self.line_buffer.clear_range(deletion_range);
}
self.maybe_cut(
deletion_range.clone(),
ClipboardMode::Normal,
CutDirection::Left,
);

Check warning on line 307 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L303-L307

Added lines #L303 - L307 were not covered by tests
}

fn cut_from_end(&mut self) {
let cut_slice = &self.line_buffer.get_buffer()[self.line_buffer.insertion_point()..];
if !cut_slice.is_empty() {
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
self.line_buffer.clear_to_end();
}
self.maybe_cut(
self.line_buffer.insertion_point()..,
ClipboardMode::Normal,
CutDirection::Right,
);

Check warning on line 315 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L311-L315

Added lines #L311 - L315 were not covered by tests
}

fn cut_to_line_end(&mut self) {
let cut_slice = &self.line_buffer.get_buffer()
[self.line_buffer.insertion_point()..self.line_buffer.find_current_line_end()];
if !cut_slice.is_empty() {
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
self.line_buffer.clear_to_line_end();
}
let cut_range =
self.line_buffer.insertion_point()..self.line_buffer.find_current_line_end();
self.maybe_cut(cut_range, ClipboardMode::Normal, CutDirection::Right);

Check warning on line 321 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L319-L321

Added lines #L319 - L321 were not covered by tests
}

fn cut_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 cut_range = left_index..insertion_offset;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[cut_range.clone()],
ClipboardMode::Normal,
);
self.line_buffer.clear_range(cut_range);
self.line_buffer.set_insertion_point(left_index);
}
let cut_range = left_index..insertion_offset;
self.maybe_cut(cut_range.clone(), ClipboardMode::Normal, CutDirection::Left);
}

fn cut_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 cut_range = left_index..insertion_offset;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[cut_range.clone()],
ClipboardMode::Normal,
);
self.line_buffer.clear_range(cut_range);
self.line_buffer.set_insertion_point(left_index);
}
let cut_range = left_index..insertion_offset;
self.maybe_cut(cut_range.clone(), ClipboardMode::Normal, CutDirection::Left);
}

fn cut_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 cut_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[cut_range.clone()],
ClipboardMode::Normal,
);
self.line_buffer.clear_range(cut_range);
}
let cut_range = insertion_offset..right_index;
self.maybe_cut(
cut_range.clone(),
ClipboardMode::Normal,
CutDirection::Right,
);

Check warning on line 346 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L341-L346

Added lines #L341 - L346 were not covered by tests
}

fn cut_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 cut_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[cut_range.clone()],
ClipboardMode::Normal,
);
self.line_buffer.clear_range(cut_range);
}
let cut_range = insertion_offset..right_index;
self.maybe_cut(
cut_range.clone(),
ClipboardMode::Normal,
CutDirection::Right,
);

Check warning on line 357 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L352-L357

Added lines #L352 - L357 were not covered by tests
}

fn cut_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 cut_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[cut_range.clone()],
ClipboardMode::Normal,
);
self.line_buffer.clear_range(cut_range);
}
let cut_range = insertion_offset..right_index;
self.maybe_cut(
cut_range.clone(),
ClipboardMode::Normal,
CutDirection::Right,
);
}

fn cut_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 cut_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[cut_range.clone()],
ClipboardMode::Normal,
);
self.line_buffer.clear_range(cut_range);
}
let cut_range = insertion_offset..right_index;
self.maybe_cut(
cut_range.clone(),
ClipboardMode::Normal,
CutDirection::Right,
);
}

fn cut_char(&mut self) {
let insertion_offset = self.line_buffer.insertion_point();
let right_index = self.line_buffer.grapheme_right_index();
if right_index > insertion_offset {
let cut_range = insertion_offset..right_index;
self.cut_buffer.set(
&self.line_buffer.get_buffer()[cut_range.clone()],
ClipboardMode::Normal,
);
self.line_buffer.clear_range(cut_range);
}
let cut_range = insertion_offset..right_index;
self.maybe_cut(
cut_range.clone(),
ClipboardMode::Normal,
CutDirection::Right,
);

Check warning on line 390 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L385-L390

Added lines #L385 - L390 were not covered by tests
}

fn insert_cut_buffer_before(&mut self) {
Expand Down Expand Up @@ -414,18 +448,9 @@
// Saving the section of the string that will be deleted to be
// stored into the buffer
let extra = if before_char { 0 } else { c.len_utf8() };
let cut_slice =
&self.line_buffer.get_buffer()[self.line_buffer.insertion_point()..index + extra];

if !cut_slice.is_empty() {
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
let cut_range = self.line_buffer.insertion_point()..index + extra;

if before_char {
self.line_buffer.delete_right_before_char(c, current_line);
} else {
self.line_buffer.delete_right_until_char(c, current_line);
}
}
self.maybe_cut(cut_range, ClipboardMode::Normal, CutDirection::Right);
}
}

Expand All @@ -434,18 +459,9 @@
// Saving the section of the string that will be deleted to be
// stored into the buffer
let extra = if before_char { c.len_utf8() } else { 0 };
let cut_slice =
&self.line_buffer.get_buffer()[index + extra..self.line_buffer.insertion_point()];

if !cut_slice.is_empty() {
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
let cut_range = index + extra..self.line_buffer.insertion_point();

if before_char {
self.line_buffer.delete_left_before_char(c, current_line);
} else {
self.line_buffer.delete_left_until_char(c, current_line);
}
}
self.maybe_cut(cut_range, ClipboardMode::Normal, CutDirection::Left);
}
}

Expand Down
Loading