Skip to content
Closed
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 @@ impl Default for Editor {
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 @@ impl Editor {
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 @@ impl Editor {
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
}
(_, 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,
};
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,
);
}

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,
);
}

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,
);
}

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);
}

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,
);
}

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,
);
}

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,
);
}

fn insert_cut_buffer_before(&mut self) {
Expand Down Expand Up @@ -414,18 +448,9 @@ impl Editor {
// 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 @@ impl Editor {
// 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