Skip to content

Commit

Permalink
"Added ability to select and cut text in the input buffer"
Browse files Browse the repository at this point in the history
  • Loading branch information
Tastaturtaste committed Dec 27, 2023
1 parent f396223 commit 428c9ec
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 66 deletions.
192 changes: 132 additions & 60 deletions src/core_editor/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use crate::{core_editor::get_default_clipboard, EditCommand};
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 @@ impl Default for Editor {
cut_buffer: Box::new(get_default_clipboard()),
edit_stack: EditStack::new(),
last_undo_behavior: UndoBehavior::CreateUndoPoint,
selection_anchor: None,
}
}
}
Expand All @@ -39,79 +40,91 @@ impl Editor {
}

pub(crate) fn run_edit_command(&mut self, command: &EditCommand) {
use EditCommand as EC;
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::Complete => {}
EditCommand::InsertString(str) => self.line_buffer.insert_str(str),
EditCommand::InsertNewline => self.line_buffer.insert_newline(),
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::CutChar => self.cut_char(),
EditCommand::BackspaceWord => self.line_buffer.delete_word_left(),
EditCommand::DeleteWord => self.line_buffer.delete_word_right(),
EditCommand::Clear => self.line_buffer.clear(),
EditCommand::ClearToLineEnd => self.line_buffer.clear_to_line_end(),
EditCommand::CutCurrentLine => self.cut_current_line(),
EditCommand::CutFromStart => self.cut_from_start(),
EditCommand::CutFromLineStart => self.cut_from_line_start(),
EditCommand::CutToEnd => self.cut_from_end(),
EditCommand::CutToLineEnd => self.cut_to_line_end(),
EditCommand::CutWordLeft => self.cut_word_left(),
EditCommand::CutBigWordLeft => self.cut_big_word_left(),
EditCommand::CutWordRight => self.cut_word_right(),
EditCommand::CutBigWordRight => self.cut_big_word_right(),
EditCommand::CutWordRightToNext => self.cut_word_right_to_next(),
EditCommand::CutBigWordRightToNext => self.cut_big_word_right_to_next(),
EditCommand::PasteCutBufferBefore => self.insert_cut_buffer_before(),
EditCommand::PasteCutBufferAfter => self.insert_cut_buffer_after(),
EditCommand::UppercaseWord => self.line_buffer.uppercase_word(),
EditCommand::LowercaseWord => self.line_buffer.lowercase_word(),
EditCommand::SwitchcaseChar => self.line_buffer.switchcase_char(),
EditCommand::CapitalizeChar => self.line_buffer.capitalize_char(),
EditCommand::SwapWords => self.line_buffer.swap_words(),
EditCommand::SwapGraphemes => self.line_buffer.swap_graphemes(),
EditCommand::Undo => self.undo(),
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::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),
EC::MoveToStart => self.line_buffer.move_to_start(),
EC::MoveToLineStart => self.line_buffer.move_to_line_start(),
EC::MoveToEnd => self.line_buffer.move_to_end(),
EC::MoveToLineEnd => self.line_buffer.move_to_line_end(),
EC::MoveToPosition(pos) => self.line_buffer.set_insertion_point(*pos),
EC::MoveLeft => self.line_buffer.move_left(),
EC::MoveRight => self.line_buffer.move_right(),
EC::MoveWordLeft => self.line_buffer.move_word_left(),
EC::MoveBigWordLeft => self.line_buffer.move_big_word_left(),
EC::MoveWordRight => self.line_buffer.move_word_right(),
EC::MoveWordRightStart => self.line_buffer.move_word_right_start(),
EC::MoveBigWordRightStart => self.line_buffer.move_big_word_right_start(),
EC::MoveWordRightEnd => self.line_buffer.move_word_right_end(),
EC::MoveBigWordRightEnd => self.line_buffer.move_big_word_right_end(),

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#L45-L58

Added lines #L45 - L58 were not covered by tests
EC::InsertChar(c) => self.line_buffer.insert_char(*c),
EC::Complete => {}
EC::InsertString(str) => self.line_buffer.insert_str(str),
EC::InsertNewline => self.line_buffer.insert_newline(),
EC::ReplaceChar(chr) => self.replace_char(*chr),
EC::ReplaceChars(n_chars, str) => self.replace_chars(*n_chars, str),

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L60-L64

Added lines #L60 - L64 were not covered by tests
EC::Backspace => self.line_buffer.delete_left_grapheme(),
EC::Delete => self.line_buffer.delete_right_grapheme(),
EC::CutChar => self.cut_char(),
EC::BackspaceWord => self.line_buffer.delete_word_left(),
EC::DeleteWord => self.line_buffer.delete_word_right(),
EC::Clear => self.line_buffer.clear(),
EC::ClearToLineEnd => self.line_buffer.clear_to_line_end(),
EC::CutCurrentLine => self.cut_current_line(),
EC::CutFromStart => self.cut_from_start(),
EC::CutFromLineStart => self.cut_from_line_start(),
EC::CutToEnd => self.cut_from_end(),
EC::CutToLineEnd => self.cut_to_line_end(),
EC::CutWordLeft => self.cut_word_left(),
EC::CutBigWordLeft => self.cut_big_word_left(),
EC::CutWordRight => self.cut_word_right(),
EC::CutBigWordRight => self.cut_big_word_right(),
EC::CutWordRightToNext => self.cut_word_right_to_next(),
EC::CutBigWordRightToNext => self.cut_big_word_right_to_next(),
EC::PasteCutBufferBefore => self.insert_cut_buffer_before(),
EC::PasteCutBufferAfter => self.insert_cut_buffer_after(),
EC::UppercaseWord => self.line_buffer.uppercase_word(),
EC::LowercaseWord => self.line_buffer.lowercase_word(),
EC::SwitchcaseChar => self.line_buffer.switchcase_char(),
EC::CapitalizeChar => self.line_buffer.capitalize_char(),
EC::SwapWords => self.line_buffer.swap_words(),
EC::SwapGraphemes => self.line_buffer.swap_graphemes(),

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L67-L90

Added lines #L67 - L90 were not covered by tests
EC::Undo => self.undo(),
EC::Redo => self.redo(),
EC::CutRightUntil(c) => self.cut_right_until_char(*c, false, true),
EC::CutRightBefore(c) => self.cut_right_until_char(*c, true, true),
EC::MoveRightUntil(c) => self.move_right_until_char(*c, false, true),
EC::MoveRightBefore(c) => self.move_right_until_char(*c, true, true),
EC::CutLeftUntil(c) => self.cut_left_until_char(*c, false, true),
EC::CutLeftBefore(c) => self.cut_left_until_char(*c, true, true),
EC::MoveLeftUntil(c) => self.move_left_until_char(*c, false, true),
EC::MoveLeftBefore(c) => self.move_left_until_char(*c, true, true),
EC::SelectMoveLeft => self.select_move_left(),
EC::SelectMoveRight => self.select_move_right(),
EC::SelectAll => self.select_all(),
EC::CutSelection => self.cut_selection(),
EC::CopySelection => self.copy_selection(),

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L93-L105

Added lines #L93 - L105 were not covered by tests
}

let new_undo_behavior = match (command, command.edit_type()) {
(_, EditType::MoveCursor) => UndoBehavior::MoveCursor,
(EditCommand::InsertChar(c), EditType::EditText) => UndoBehavior::InsertCharacter(*c),
(EditCommand::Delete, EditType::EditText) => {
(EC::InsertChar(c), EditType::EditText) => UndoBehavior::InsertCharacter(*c),
(EC::Delete, EditType::EditText) => {
let deleted_char = self.edit_stack.current().grapheme_right().chars().next();
UndoBehavior::Delete(deleted_char)
}
(EditCommand::Backspace, EditType::EditText) => {
(EC::Backspace, EditType::EditText) => {
let deleted_char = self.edit_stack.current().grapheme_left().chars().next();
UndoBehavior::Backspace(deleted_char)
}
(_, EditType::UndoRedo) => UndoBehavior::UndoRedo,
(_, _) => UndoBehavior::CreateUndoPoint,
};
if !matches!(
command,
EC::SelectMoveLeft | EC::SelectMoveRight | EC::SelectAll
) {
self.reset_selection();
}
self.update_undo_state(new_undo_behavior);
}

Expand Down Expand Up @@ -462,6 +475,65 @@ impl Editor {

self.line_buffer.insert_str(string);
}

fn select_move_left(&mut self) {
if self.selection_anchor.is_none() {
self.selection_anchor = Some(self.insertion_point());
}
self.line_buffer.move_left();
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L479-L484

Added lines #L479 - L484 were not covered by tests

fn select_move_right(&mut self) {
if self.selection_anchor.is_none() {
self.selection_anchor = Some(self.insertion_point());
}
self.line_buffer.move_right();
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L486-L491

Added lines #L486 - L491 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 496 in src/core_editor/editor.rs

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L493-L496

Added lines #L493 - L496 were not covered by tests

fn cut_selection(&mut self) {
if let Some(selection_anchor) = self.selection_anchor {
let (start, end) = if self.insertion_point() > selection_anchor {
(selection_anchor, self.insertion_point())

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L499-L501

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

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L503

Added line #L503 was not covered by tests
};
let cut_slice = &self.line_buffer.get_buffer()[start..end];
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
if self.insertion_point() <= start {
// No action necessary
} else if self.insertion_point() < end {
// Insertion point is in selected area
self.line_buffer.set_insertion_point(start);
} else {
// Insertion point after end
self.line_buffer
.set_insertion_point(self.insertion_point() - (end - start));
}
self.line_buffer
.clear_range_safe(self.insertion_point(), selection_anchor);
}
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L505-L520

Added lines #L505 - L520 were not covered by tests

fn copy_selection(&mut self) {
if let Some(selection_anchor) = self.selection_anchor {
let (start, end) = if self.insertion_point() > selection_anchor {
(selection_anchor, self.insertion_point())

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L523-L525

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

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#L527

Added line #L527 was not covered by tests
};
let cut_slice = &self.line_buffer.get_buffer()[start..end];
self.cut_buffer.set(cut_slice, ClipboardMode::Normal);
}
}

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

View check run for this annotation

Codecov / codecov/patch

src/core_editor/editor.rs#L529-L532

Added lines #L529 - L532 were not covered by tests

fn reset_selection(&mut self) {
self.selection_anchor = None;
}
}

#[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 @@ impl LineBuffer {
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
6 changes: 3 additions & 3 deletions src/edit_mode/emacs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
edit_mode::{
keybindings::{
add_common_control_bindings, add_common_edit_bindings, add_common_navigation_bindings,
edit_bind, Keybindings,
add_common_selection_bindings, edit_bind, Keybindings,
},
EditMode,
},
Expand All @@ -21,6 +21,7 @@ pub fn default_emacs_keybindings() -> Keybindings {
add_common_control_bindings(&mut kb);
add_common_navigation_bindings(&mut kb);
add_common_edit_bindings(&mut kb);
add_common_selection_bindings(&mut kb);

// This could be in common, but in Vi it also changes the mode
kb.add_binding(KM::NONE, KC::Enter, ReedlineEvent::Enter);
Expand Down Expand Up @@ -53,6 +54,7 @@ pub fn default_emacs_keybindings() -> Keybindings {
kb.add_binding(KM::CONTROL, KC::Char('w'), edit_bind(EC::CutWordLeft));
kb.add_binding(KM::CONTROL, KC::Char('k'), edit_bind(EC::CutToEnd));
kb.add_binding(KM::CONTROL, KC::Char('u'), edit_bind(EC::CutFromStart));
kb.add_binding(KM::ALT, KC::Char('d'), edit_bind(EC::CutWordRight));
// Edits
kb.add_binding(KM::CONTROL, KC::Char('t'), edit_bind(EC::SwapGraphemes));

Expand Down Expand Up @@ -84,8 +86,6 @@ pub fn default_emacs_keybindings() -> Keybindings {
KC::Char('m'),
ReedlineEvent::Edit(vec![EditCommand::BackspaceWord]),
);
// Cutting
kb.add_binding(KM::ALT, KC::Char('d'), edit_bind(EC::CutWordRight));
// Case changes
kb.add_binding(KM::ALT, KC::Char('u'), edit_bind(EC::UppercaseWord));
kb.add_binding(KM::ALT, KC::Char('l'), edit_bind(EC::LowercaseWord));
Expand Down
17 changes: 17 additions & 0 deletions src/edit_mode/keybindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,21 @@ pub fn add_common_edit_bindings(kb: &mut Keybindings) {
// Base commands should not affect cut buffer
kb.add_binding(KM::CONTROL, KC::Char('h'), edit_bind(EC::Backspace));
kb.add_binding(KM::CONTROL, KC::Char('w'), edit_bind(EC::BackspaceWord));
kb.add_binding(KM::CONTROL, KC::Char('x'), edit_bind(EC::CutSelection));
kb.add_binding(KM::CONTROL, KC::Char('c'), edit_bind(EC::CopySelection));
kb.add_binding(
KM::CONTROL,
KC::Char('v'),
edit_bind(EC::PasteCutBufferBefore),
);
}

pub fn add_common_selection_bindings(kb: &mut Keybindings) {
use EditCommand as EC;
use KeyCode as KC;
use KeyModifiers as KM;

kb.add_binding(KM::SHIFT, KC::Left, edit_bind(EC::SelectMoveLeft));
kb.add_binding(KM::SHIFT, KC::Right, edit_bind(EC::SelectMoveRight));
kb.add_binding(KM::CONTROL, KC::Char('a'), edit_bind(EC::SelectAll));
}
4 changes: 3 additions & 1 deletion src/edit_mode/vi/vi_keybindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
edit_mode::{
keybindings::{
add_common_control_bindings, add_common_edit_bindings, add_common_navigation_bindings,
edit_bind,
add_common_selection_bindings, edit_bind,
},
Keybindings,
},
Expand All @@ -20,6 +20,7 @@ pub fn default_vi_normal_keybindings() -> Keybindings {

add_common_control_bindings(&mut kb);
add_common_navigation_bindings(&mut kb);
add_common_selection_bindings(&mut kb);
// Replicate vi's default behavior for Backspace and delete
kb.add_binding(KM::NONE, KC::Backspace, edit_bind(EC::MoveLeft));
kb.add_binding(KM::NONE, KC::Delete, edit_bind(EC::Delete));
Expand All @@ -34,6 +35,7 @@ pub fn default_vi_insert_keybindings() -> Keybindings {
add_common_control_bindings(&mut kb);
add_common_navigation_bindings(&mut kb);
add_common_edit_bindings(&mut kb);
add_common_selection_bindings(&mut kb);

kb
}
Loading

0 comments on commit 428c9ec

Please sign in to comment.