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

Add ability to select and cut text in the input buffer #689

Merged
merged 6 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 194 additions & 37 deletions src/core_editor/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
pub struct Editor {
line_buffer: LineBuffer,
cut_buffer: Box<dyn Clipboard>,

edit_stack: EditStack<LineBuffer>,
last_undo_behavior: UndoBehavior,
selection_anchor: Option<usize>,
}

impl Default for Editor {
Expand All @@ -21,6 +21,7 @@
cut_buffer: Box::new(get_default_clipboard()),
edit_stack: EditStack::new(),
last_undo_behavior: UndoBehavior::CreateUndoPoint,
selection_anchor: None,
}
}
}
Expand All @@ -40,28 +41,32 @@

pub(crate) fn run_edit_command(&mut self, command: &EditCommand) {
match command {
EditCommand::MoveToStart => self.line_buffer.move_to_start(),
EditCommand::MoveToLineStart => self.line_buffer.move_to_line_start(),
EditCommand::MoveToEnd => self.line_buffer.move_to_end(),
EditCommand::MoveToLineEnd => self.line_buffer.move_to_line_end(),
EditCommand::MoveToPosition(pos) => self.line_buffer.set_insertion_point(*pos),
EditCommand::MoveLeft => self.line_buffer.move_left(),
EditCommand::MoveRight => self.line_buffer.move_right(),
EditCommand::MoveWordLeft => self.line_buffer.move_word_left(),
EditCommand::MoveBigWordLeft => self.line_buffer.move_big_word_left(),
EditCommand::MoveWordRight => self.line_buffer.move_word_right(),
EditCommand::MoveWordRightStart => self.line_buffer.move_word_right_start(),
EditCommand::MoveBigWordRightStart => self.line_buffer.move_big_word_right_start(),
EditCommand::MoveWordRightEnd => self.line_buffer.move_word_right_end(),
EditCommand::MoveBigWordRightEnd => self.line_buffer.move_big_word_right_end(),
EditCommand::InsertChar(c) => self.line_buffer.insert_char(*c),
EditCommand::MoveToStart { select } => self.move_to_start(*select),
EditCommand::MoveToLineStart { select } => self.move_to_line_start(*select),
EditCommand::MoveToEnd { select } => self.move_to_end(*select),
EditCommand::MoveToLineEnd { select } => self.move_to_line_end(*select),
EditCommand::MoveToPosition { position, select } => {
self.move_to_position(*position, *select)

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L44-L49

Added lines #L44 - L49 were not covered by tests
}
EditCommand::MoveLeft { select } => self.move_left(*select),
EditCommand::MoveRight { select } => self.move_right(*select),
EditCommand::MoveWordLeft { select } => self.move_word_left(*select),
EditCommand::MoveBigWordLeft { select } => self.move_big_word_left(*select),
EditCommand::MoveWordRight { select } => self.move_word_right(*select),
EditCommand::MoveWordRightStart { select } => self.move_word_right_start(*select),
EditCommand::MoveBigWordRightStart { select } => {
self.move_big_word_right_start(*select)

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L51-L58

Added lines #L51 - L58 were not covered by tests
}
EditCommand::MoveWordRightEnd { select } => self.move_word_right_end(*select),
EditCommand::MoveBigWordRightEnd { select } => self.move_big_word_right_end(*select),

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L60-L61

Added lines #L60 - L61 were not covered by tests
EditCommand::InsertChar(c) => self.insert_char(*c),
EditCommand::Complete => {}
EditCommand::InsertString(str) => self.line_buffer.insert_str(str),
EditCommand::InsertNewline => self.line_buffer.insert_newline(),
EditCommand::InsertString(str) => self.insert_str(str),
EditCommand::InsertNewline => self.insert_newline(),

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L64-L65

Added lines #L64 - L65 were not covered by tests
EditCommand::ReplaceChar(chr) => self.replace_char(*chr),
EditCommand::ReplaceChars(n_chars, str) => self.replace_chars(*n_chars, str),
EditCommand::Backspace => self.line_buffer.delete_left_grapheme(),
EditCommand::Delete => self.line_buffer.delete_right_grapheme(),
EditCommand::Backspace => self.backspace(),
EditCommand::Delete => self.delete(),
EditCommand::CutChar => self.cut_char(),
EditCommand::BackspaceWord => self.line_buffer.delete_word_left(),
EditCommand::DeleteWord => self.line_buffer.delete_word_right(),
Expand Down Expand Up @@ -90,16 +95,31 @@
EditCommand::Redo => self.redo(),
EditCommand::CutRightUntil(c) => self.cut_right_until_char(*c, false, true),
EditCommand::CutRightBefore(c) => self.cut_right_until_char(*c, true, true),
EditCommand::MoveRightUntil(c) => self.move_right_until_char(*c, false, true),
EditCommand::MoveRightBefore(c) => self.move_right_until_char(*c, true, true),
EditCommand::MoveRightUntil { c, select } => {
self.move_right_until_char(*c, false, true, *select)

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L98-L99

Added lines #L98 - L99 were not covered by tests
}
EditCommand::MoveRightBefore { c, select } => {
self.move_right_until_char(*c, true, true, *select)

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L101-L102

Added lines #L101 - L102 were not covered by tests
}
EditCommand::CutLeftUntil(c) => self.cut_left_until_char(*c, false, true),
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::MoveLeftUntil { c, select } => {
self.move_left_until_char(*c, false, true, *select)

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L106-L107

Added lines #L106 - L107 were not covered by tests
}
EditCommand::MoveLeftBefore { c, select } => {
self.move_left_until_char(*c, true, true, *select)

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L109-L110

Added lines #L109 - L110 were not covered by tests
}
EditCommand::SelectAll => self.select_all(),
EditCommand::CutSelection => self.cut_selection(),
EditCommand::CopySelection => self.copy_selection(),

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L112-L114

Added lines #L112 - L114 were not covered by tests
}
if !matches!(command.edit_type(), EditType::MoveCursor { select: true }) {
self.selection_anchor = None;
}
if let EditType::MoveCursor { select: true } = command.edit_type() {}

let new_undo_behavior = match (command, command.edit_type()) {
(_, EditType::MoveCursor) => UndoBehavior::MoveCursor,
(_, EditType::MoveCursor { .. }) => UndoBehavior::MoveCursor,

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L122

Added line #L122 was not covered by tests
(EditCommand::InsertChar(c), EditType::EditText) => UndoBehavior::InsertCharacter(*c),
(EditCommand::Delete, EditType::EditText) => {
let deleted_char = self.edit_stack.current().grapheme_right().chars().next();
Expand All @@ -112,8 +132,21 @@
(_, EditType::UndoRedo) => UndoBehavior::UndoRedo,
(_, _) => UndoBehavior::CreateUndoPoint,
};

self.update_undo_state(new_undo_behavior);
}
fn update_selection_anchor(&mut self, select: bool) {
self.selection_anchor = if select {
self.selection_anchor
.or_else(|| Some(self.insertion_point()))

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L138-L141

Added lines #L138 - L141 were not covered by tests
} else {
None

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L143

Added line #L143 was not covered by tests
};
}
fn move_to_position(&mut self, position: usize, select: bool) {
self.update_selection_anchor(select);
self.line_buffer.set_insertion_point(position)
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L145-L149

Added lines #L145 - L149 were not covered by tests

pub(crate) fn move_line_up(&mut self) {
self.line_buffer.move_line_up();
Expand Down Expand Up @@ -170,25 +203,24 @@
self.edit_stack.reset();
}

pub(crate) fn move_to_start(&mut self, undo_behavior: UndoBehavior) {
pub(crate) fn move_to_start(&mut self, select: bool) {
self.update_selection_anchor(select);

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L206-L207

Added lines #L206 - L207 were not covered by tests
self.line_buffer.move_to_start();
self.update_undo_state(undo_behavior);
}

pub(crate) fn move_to_end(&mut self, undo_behavior: UndoBehavior) {
pub(crate) fn move_to_end(&mut self, select: bool) {
self.update_selection_anchor(select);

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L211-L212

Added lines #L211 - L212 were not covered by tests
self.line_buffer.move_to_end();
self.update_undo_state(undo_behavior);
}

#[allow(dead_code)]
pub(crate) fn move_to_line_start(&mut self, undo_behavior: UndoBehavior) {
pub(crate) fn move_to_line_start(&mut self, select: bool) {
self.update_selection_anchor(select);

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L216-L217

Added lines #L216 - L217 were not covered by tests
self.line_buffer.move_to_line_start();
self.update_undo_state(undo_behavior);
}

pub(crate) fn move_to_line_end(&mut self, undo_behavior: UndoBehavior) {
pub(crate) fn move_to_line_end(&mut self, select: bool) {
self.update_selection_anchor(select);

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L221-L222

Added lines #L221 - L222 were not covered by tests
self.line_buffer.move_to_line_end();
self.update_undo_state(undo_behavior);
}

fn undo(&mut self) {
Expand All @@ -201,7 +233,7 @@
self.line_buffer = val.clone();
}

fn update_undo_state(&mut self, undo_behavior: UndoBehavior) {
pub(crate) fn update_undo_state(&mut self, undo_behavior: UndoBehavior) {
if matches!(undo_behavior, UndoBehavior::UndoRedo) {
self.last_undo_behavior = UndoBehavior::UndoRedo;
return;
Expand Down Expand Up @@ -357,6 +389,7 @@
}

fn insert_cut_buffer_before(&mut self) {
self.delete_selection();

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L392

Added line #L392 was not covered by tests
match self.cut_buffer.get() {
(content, ClipboardMode::Normal) => {
self.line_buffer.insert_str(&content);
Expand All @@ -375,6 +408,7 @@
}

fn insert_cut_buffer_after(&mut self) {
self.delete_selection();

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L411

Added line #L411 was not covered by tests
match self.cut_buffer.get() {
(content, ClipboardMode::Normal) => {
self.line_buffer.move_right();
Expand All @@ -393,15 +427,29 @@
}
}

fn move_right_until_char(&mut self, c: char, before_char: bool, current_line: bool) {
fn move_right_until_char(
&mut self,
c: char,
before_char: bool,
current_line: bool,
select: bool,
) {
self.update_selection_anchor(select);

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L430-L437

Added lines #L430 - L437 were not covered by tests
if before_char {
self.line_buffer.move_right_before(c, current_line);
} else {
self.line_buffer.move_right_until(c, current_line);
}
}

fn move_left_until_char(&mut self, c: char, before_char: bool, current_line: bool) {
fn move_left_until_char(
&mut self,
c: char,
before_char: bool,
current_line: bool,
select: bool,
) {
self.update_selection_anchor(select);

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L445-L452

Added lines #L445 - L452 were not covered by tests
if before_char {
self.line_buffer.move_left_before(c, current_line);
} else {
Expand Down Expand Up @@ -462,6 +510,115 @@

self.line_buffer.insert_str(string);
}

fn move_left(&mut self, select: bool) {
self.update_selection_anchor(select);
self.line_buffer.move_left();
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L514-L517

Added lines #L514 - L517 were not covered by tests

fn move_right(&mut self, select: bool) {
self.update_selection_anchor(select);
self.line_buffer.move_right();
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L519-L522

Added lines #L519 - L522 were not covered by tests

fn select_all(&mut self) {
self.selection_anchor = Some(0);
self.line_buffer.move_to_end();
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L524-L527

Added lines #L524 - L527 were not covered by tests

fn cut_selection(&mut self) {
if let Some((start, end)) = self.get_selection() {
let cut_slice = &self.line_buffer.get_buffer()[start..end];
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
self.line_buffer.clear_range_safe(start, end);
self.selection_anchor = None;
}
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L529-L536

Added lines #L529 - L536 were not covered by tests

fn copy_selection(&mut self) {
if let Some((start, end)) = self.get_selection() {
let cut_slice = &self.line_buffer.get_buffer()[start..end];
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
}
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L538-L543

Added lines #L538 - L543 were not covered by tests

/// If a selection is active returns the selected range, otherwise None.
/// The range is guaranteed to be ascending.
pub fn get_selection(&self) -> Option<(usize, usize)> {
self.selection_anchor.map(|selection_anchor| {
if self.insertion_point() > selection_anchor {
(selection_anchor, self.insertion_point())

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L549-L550

Added lines #L549 - L550 were not covered by tests
} else {
(self.insertion_point(), selection_anchor)

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L552

Added line #L552 was not covered by tests
}
})
}

fn delete_selection(&mut self) {
if let Some((start, end)) = self.get_selection() {
self.line_buffer.clear_range_safe(start, end);
self.selection_anchor = None;

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L559-L560

Added lines #L559 - L560 were not covered by tests
}
}

fn backspace(&mut self) {
if self.selection_anchor.is_some() {
self.delete_selection();

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L566

Added line #L566 was not covered by tests
} else {
self.line_buffer.delete_left_grapheme();
}
}

fn delete(&mut self) {
if self.selection_anchor.is_some() {
self.delete_selection();

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L574

Added line #L574 was not covered by tests
} else {
self.line_buffer.delete_right_grapheme();
}
}

fn move_word_left(&mut self, select: bool) {
self.move_to_position(self.line_buffer.word_left_index(), select);
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L580-L582

Added lines #L580 - L582 were not covered by tests

fn move_big_word_left(&mut self, select: bool) {
self.move_to_position(self.line_buffer.big_word_left_index(), select);
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L584-L586

Added lines #L584 - L586 were not covered by tests

fn move_word_right(&mut self, select: bool) {
self.move_to_position(self.line_buffer.word_right_index(), select);
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L588-L590

Added lines #L588 - L590 were not covered by tests

fn move_word_right_start(&mut self, select: bool) {
self.move_to_position(self.line_buffer.word_right_start_index(), select);
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L592-L594

Added lines #L592 - L594 were not covered by tests

fn move_big_word_right_start(&mut self, select: bool) {
self.move_to_position(self.line_buffer.big_word_right_start_index(), select);
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L596-L598

Added lines #L596 - L598 were not covered by tests

fn move_word_right_end(&mut self, select: bool) {
self.move_to_position(self.line_buffer.word_right_end_index(), select);
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L600-L602

Added lines #L600 - L602 were not covered by tests

fn move_big_word_right_end(&mut self, select: bool) {
self.move_to_position(self.line_buffer.big_word_right_end_index(), select);
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L604-L606

Added lines #L604 - L606 were not covered by tests

fn insert_char(&mut self, c: char) {
self.delete_selection();
self.line_buffer.insert_char(c);
}

fn insert_str(&mut self, str: &str) {
self.delete_selection();
self.line_buffer.insert_str(str);
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L613-L616

Added lines #L613 - L616 were not covered by tests

fn insert_newline(&mut self) {
self.delete_selection();
self.line_buffer.insert_newline();
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L618-L621

Added lines #L618 - L621 were not covered by tests
}

#[cfg(test)]
Expand Down
21 changes: 21 additions & 0 deletions src/core_editor/line_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,27 @@
self.insertion_point = 0;
}

/// Clear all contents between `start` and `end` and change insertion point if necessary.
///
/// If the cursor is located between `start` and `end` it is adjusted to `start`.
/// If the cursor is located after `end` it is adjusted to stay at its current char boundary.
pub fn clear_range_safe(&mut self, start: usize, end: usize) {
let (start, end) = if start > end {
(end, start)

Check warning on line 405 in src/core_editor/line_buffer.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/line_buffer.rs#L403-L405

Added lines #L403 - L405 were not covered by tests
} else {
(start, end)

Check warning on line 407 in src/core_editor/line_buffer.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/line_buffer.rs#L407

Added line #L407 was not covered by tests
};
if self.insertion_point <= start {
// No action necessary
} else if self.insertion_point < end {
self.insertion_point = start;
} else {
// Insertion point after end
self.insertion_point -= end - start;
}
self.clear_range(start..end);
}

Check warning on line 418 in src/core_editor/line_buffer.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/line_buffer.rs#L409-L418

Added lines #L409 - L418 were not covered by tests

/// Clear text covered by `range` in the current line
///
/// Safety: Does not change the insertion point/offset and is thus not unicode safe!
Expand Down
Loading