diff --git a/src/completion/circular.rs b/src/completion/circular.rs deleted file mode 100644 index 8132ffc..0000000 --- a/src/completion/circular.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::{core_editor::LineBuffer, Completer}; - -/// A simple handler that will do a cycle-based rotation through the options given by the Completer -pub struct CircularCompletionHandler { - initial_line: LineBuffer, - index: usize, - - last_buffer: Option, -} - -impl Default for CircularCompletionHandler { - fn default() -> Self { - CircularCompletionHandler { - initial_line: LineBuffer::new(), - index: 0, - last_buffer: None, - } - } -} - -impl CircularCompletionHandler { - fn reset_index(&mut self) { - self.index = 0; - } - // With this function we handle the tab events. - // - // If completions vector is not empty we proceed to replace - // in the line_buffer only the specified range of characters. - // If internal index is 0 it means that is the first tab event pressed. - // If internal index is greater than completions vector, we bring it back to 0. - pub(crate) fn handle( - &mut self, - completer: &mut dyn Completer, - present_buffer: &mut LineBuffer, - ) { - if let Some(last_buffer) = &self.last_buffer { - if last_buffer != present_buffer { - self.reset_index(); - } - } - - // NOTE: This is required to cycle through the tabs for what is presently present in the - // buffer. Without this `repetitive_calls_to_handle_works` will not work - if self.index == 0 { - self.initial_line = present_buffer.clone(); - } else { - *present_buffer = self.initial_line.clone(); - } - - let completions = completer.complete( - present_buffer.get_buffer(), - present_buffer.insertion_point(), - ); - - if !completions.is_empty() { - match self.index { - index if index < completions.len() => { - self.index += 1; - let span = completions[index].span; - - let mut offset = present_buffer.insertion_point(); - offset += completions[index].value.len() - (span.end - span.start); - - // TODO improve the support for multiline replace - present_buffer.replace(span.start..span.end, &completions[index].value); - present_buffer.set_insertion_point(offset); - } - _ => { - self.reset_index(); - } - } - } - self.last_buffer = Some(present_buffer.clone()); - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::DefaultCompleter; - use pretty_assertions::assert_eq; - - fn get_completer(values: Vec<&'_ str>) -> DefaultCompleter { - let mut completer = DefaultCompleter::default(); - completer.insert(values.iter().map(|s| s.to_string()).collect()); - - completer - } - - fn buffer_with(content: &str) -> LineBuffer { - let mut line_buffer = LineBuffer::new(); - line_buffer.insert_str(content); - - line_buffer - } - - #[test] - fn repetitive_calls_to_handle_works() { - let mut tab = CircularCompletionHandler::default(); - let mut comp = get_completer(vec!["login", "logout"]); - let mut buf = buffer_with("lo"); - tab.handle(&mut comp, &mut buf); - - assert_eq!(buf, buffer_with("login")); - tab.handle(&mut comp, &mut buf); - assert_eq!(buf, buffer_with("logout")); - tab.handle(&mut comp, &mut buf); - assert_eq!(buf, buffer_with("lo")); - } - - #[test] - fn behaviour_with_hyphens_and_underscores() { - let mut tab = CircularCompletionHandler::default(); - let mut comp = get_completer(vec!["test-hyphen", "test_underscore"]); - let mut buf = buffer_with("te"); - tab.handle(&mut comp, &mut buf); - - assert_eq!(buf, buffer_with("test")); - tab.handle(&mut comp, &mut buf); - assert_eq!(buf, buffer_with("te")); - } - - #[test] - fn auto_resets_on_new_query() { - let mut tab = CircularCompletionHandler::default(); - let mut comp = get_completer(vec!["login", "logout", "exit"]); - let mut buf = buffer_with("log"); - tab.handle(&mut comp, &mut buf); - - assert_eq!(buf, buffer_with("login")); - let mut new_buf = buffer_with("ex"); - tab.handle(&mut comp, &mut new_buf); - assert_eq!(new_buf, buffer_with("exit")); - } - - #[test] - fn same_string_different_places() { - let mut tab = CircularCompletionHandler::default(); - let mut comp = get_completer(vec!["that", "this"]); - let mut buf = buffer_with("th is my test th"); - - // Hitting tab after `th` fills the first completion `that` - buf.set_insertion_point(2); - tab.handle(&mut comp, &mut buf); - let mut expected_buffer = buffer_with("that is my test th"); - expected_buffer.set_insertion_point(4); - assert_eq!(buf, expected_buffer); - - // updating the cursor to end should reset the completions - buf.set_insertion_point(18); - tab.handle(&mut comp, &mut buf); - assert_eq!(buf, buffer_with("that is my test that")); - } -} diff --git a/src/completion/mod.rs b/src/completion/mod.rs index 0f6eef9..10f196e 100644 --- a/src/completion/mod.rs +++ b/src/completion/mod.rs @@ -1,8 +1,6 @@ mod base; -mod circular; mod default; pub(crate) mod history; pub use base::{Completer, Span, Suggestion}; -pub use circular::CircularCompletionHandler; pub use default::DefaultCompleter; diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index 75b013b..cb2a875 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -1,5 +1,6 @@ use super::{edit_stack::EditStack, Clipboard, ClipboardMode, LineBuffer}; -use crate::{core_editor::get_default_clipboard, EditCommand, UndoBehavior}; +use crate::enums::{EditType, UndoBehavior}; +use crate::{core_editor::get_default_clipboard, EditCommand}; /// Stateful editor executing changes to the underlying [`LineBuffer`] /// @@ -10,6 +11,7 @@ pub struct Editor { cut_buffer: Box, edit_stack: EditStack, + last_undo_behavior: UndoBehavior, } impl Default for Editor { @@ -18,23 +20,25 @@ impl Default for Editor { line_buffer: LineBuffer::new(), cut_buffer: Box::new(get_default_clipboard()), edit_stack: EditStack::new(), + last_undo_behavior: UndoBehavior::CreateUndoPoint, } } } impl Editor { - pub fn line_buffer_immut(&self) -> &LineBuffer { + /// Get the current LineBuffer + pub fn line_buffer(&self) -> &LineBuffer { &self.line_buffer } - pub fn line_buffer(&mut self) -> &mut LineBuffer { - &mut self.line_buffer - } - pub fn set_line_buffer(&mut self, line_buffer: LineBuffer) { + /// Set the current LineBuffer. + /// Undo behavior specifies how this change should be reflected on the undo stack. + pub(crate) fn set_line_buffer(&mut self, line_buffer: LineBuffer, undo_behavior: UndoBehavior) { self.line_buffer = line_buffer; + self.update_undo_state(undo_behavior); } - pub fn run_edit_command(&mut self, command: &EditCommand) { + 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(), @@ -50,7 +54,7 @@ impl Editor { 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.insert_char(*c), + EditCommand::InsertChar(c) => self.line_buffer.insert_char(*c), EditCommand::InsertString(str) => self.line_buffer.insert_str(str), EditCommand::InsertNewline => self.line_buffer.insert_newline(), EditCommand::ReplaceChar(chr) => self.replace_char(*chr), @@ -92,99 +96,98 @@ impl Editor { EditCommand::MoveLeftUntil(c) => self.move_left_until_char(*c, false, true), EditCommand::MoveLeftBefore(c) => self.move_left_until_char(*c, true, true), } - match command.undo_behavior() { - UndoBehavior::Ignore => {} - UndoBehavior::Full => { - self.remember_undo_state(true); + + 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) => { + let deleted_char = self.edit_stack.current().grapheme_right().chars().next(); + UndoBehavior::Delete(deleted_char) } - UndoBehavior::Coalesce => { - self.remember_undo_state(false); + (EditCommand::Backspace, EditType::EditText) => { + let deleted_char = self.edit_stack.current().grapheme_left().chars().next(); + UndoBehavior::Backspace(deleted_char) } - } + (_, EditType::UndoRedo) => UndoBehavior::UndoRedo, + (_, _) => UndoBehavior::CreateUndoPoint, + }; + self.update_undo_state(new_undo_behavior); } - pub fn move_line_up(&mut self) { + pub(crate) fn move_line_up(&mut self) { self.line_buffer.move_line_up(); + self.update_undo_state(UndoBehavior::MoveCursor); } - pub fn move_line_down(&mut self) { + pub(crate) fn move_line_down(&mut self) { self.line_buffer.move_line_down(); + self.update_undo_state(UndoBehavior::MoveCursor); } - pub fn insert_char(&mut self, c: char) { - self.line_buffer.insert_char(c); - } - - /// Directly change the cursor position measured in bytes in the buffer - /// - /// ## Unicode safety: - /// Not checked, inproper use may cause panics in following operations - pub(crate) fn set_insertion_point(&mut self, pos: usize) { - self.line_buffer.set_insertion_point(pos); - } - + /// Get the text of the current LineBuffer pub fn get_buffer(&self) -> &str { self.line_buffer.get_buffer() } - pub fn set_buffer(&mut self, buffer: String) { - self.line_buffer.set_buffer(buffer); - } - - pub fn clear_to_end(&mut self) { - self.line_buffer.clear_to_end(); - } - - fn clear_to_insertion_point(&mut self) { - self.line_buffer.clear_to_insertion_point(); - } - - fn clear_range(&mut self, range: R) + /// Edit the line buffer in an undo-safe manner. + pub fn edit_buffer(&mut self, func: F, undo_behavior: UndoBehavior) where - R: std::ops::RangeBounds, + F: FnOnce(&mut LineBuffer), { - self.line_buffer.clear_range(range); + self.update_undo_state(undo_behavior); + func(&mut self.line_buffer); } - pub fn insertion_point(&self) -> usize { + /// Set the text of the current LineBuffer given the specified UndoBehavior + /// Insertion point update to the end of the buffer. + pub(crate) fn set_buffer(&mut self, buffer: String, undo_behavior: UndoBehavior) { + self.line_buffer.set_buffer(buffer); + self.update_undo_state(undo_behavior); + } + + pub(crate) fn insertion_point(&self) -> usize { self.line_buffer.insertion_point() } - pub fn is_empty(&self) -> bool { + pub(crate) fn is_empty(&self) -> bool { self.line_buffer.is_empty() } - pub fn is_cursor_at_first_line(&self) -> bool { + pub(crate) fn is_cursor_at_first_line(&self) -> bool { self.line_buffer.is_cursor_at_first_line() } - pub fn is_cursor_at_last_line(&self) -> bool { + pub(crate) fn is_cursor_at_last_line(&self) -> bool { self.line_buffer.is_cursor_at_last_line() } - pub fn is_cursor_at_buffer_end(&self) -> bool { + pub(crate) fn is_cursor_at_buffer_end(&self) -> bool { self.line_buffer.insertion_point() == self.get_buffer().len() } - pub fn reset_undo_stack(&mut self) { + pub(crate) fn reset_undo_stack(&mut self) { self.edit_stack.reset(); } - pub fn move_to_start(&mut self) { + pub(crate) fn move_to_start(&mut self, undo_behavior: UndoBehavior) { self.line_buffer.move_to_start(); + self.update_undo_state(undo_behavior); } - pub fn move_to_end(&mut self) { + pub(crate) fn move_to_end(&mut self, undo_behavior: UndoBehavior) { self.line_buffer.move_to_end(); + self.update_undo_state(undo_behavior); } #[allow(dead_code)] - pub fn move_to_line_start(&mut self) { + pub(crate) fn move_to_line_start(&mut self, undo_behavior: UndoBehavior) { self.line_buffer.move_to_line_start(); + self.update_undo_state(undo_behavior); } - pub fn move_to_line_end(&mut self) { + pub(crate) fn move_to_line_end(&mut self, undo_behavior: UndoBehavior) { self.line_buffer.move_to_line_end(); + self.update_undo_state(undo_behavior); } fn undo(&mut self) { @@ -197,13 +200,16 @@ impl Editor { self.line_buffer = val.clone(); } - pub fn remember_undo_state(&mut self, is_after_action: bool) { - if self.edit_stack.current().word_count() == self.line_buffer.word_count() - && !is_after_action - { + fn update_undo_state(&mut self, undo_behavior: UndoBehavior) { + if matches!(undo_behavior, UndoBehavior::UndoRedo) { + self.last_undo_behavior = UndoBehavior::UndoRedo; + return; + } + if !undo_behavior.create_undo_point_after(&self.last_undo_behavior) { self.edit_stack.undo(); } self.edit_stack.insert(self.line_buffer.clone()); + self.last_undo_behavior = undo_behavior; } fn cut_current_line(&mut self) { @@ -212,8 +218,8 @@ impl Editor { 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.set_insertion_point(deletion_range.start); - self.clear_range(deletion_range); + self.line_buffer.set_insertion_point(deletion_range.start); + self.line_buffer.clear_range(deletion_range); } } @@ -224,7 +230,7 @@ impl Editor { &self.line_buffer.get_buffer()[..insertion_offset], ClipboardMode::Normal, ); - self.clear_to_insertion_point(); + self.line_buffer.clear_to_insertion_point(); } } @@ -239,11 +245,11 @@ impl Editor { } } - pub fn cut_from_end(&mut self) { + 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.clear_to_end(); + self.line_buffer.clear_to_end(); } } @@ -265,7 +271,7 @@ impl Editor { &self.line_buffer.get_buffer()[cut_range.clone()], ClipboardMode::Normal, ); - self.clear_range(cut_range); + self.line_buffer.clear_range(cut_range); self.line_buffer.set_insertion_point(left_index); } } @@ -279,7 +285,7 @@ impl Editor { &self.line_buffer.get_buffer()[cut_range.clone()], ClipboardMode::Normal, ); - self.clear_range(cut_range); + self.line_buffer.clear_range(cut_range); self.line_buffer.set_insertion_point(left_index); } } @@ -293,7 +299,7 @@ impl Editor { &self.line_buffer.get_buffer()[cut_range.clone()], ClipboardMode::Normal, ); - self.clear_range(cut_range); + self.line_buffer.clear_range(cut_range); } } @@ -306,7 +312,7 @@ impl Editor { &self.line_buffer.get_buffer()[cut_range.clone()], ClipboardMode::Normal, ); - self.clear_range(cut_range); + self.line_buffer.clear_range(cut_range); } } @@ -319,7 +325,7 @@ impl Editor { &self.line_buffer.get_buffer()[cut_range.clone()], ClipboardMode::Normal, ); - self.clear_range(cut_range); + self.line_buffer.clear_range(cut_range); } } @@ -332,7 +338,7 @@ impl Editor { &self.line_buffer.get_buffer()[cut_range.clone()], ClipboardMode::Normal, ); - self.clear_range(cut_range); + self.line_buffer.clear_range(cut_range); } } @@ -345,7 +351,7 @@ impl Editor { &self.line_buffer.get_buffer()[cut_range.clone()], ClipboardMode::Normal, ); - self.clear_range(cut_range); + self.line_buffer.clear_range(cut_range); } } @@ -465,7 +471,7 @@ mod test { fn editor_with(buffer: &str) -> Editor { let mut editor = Editor::default(); - editor.line_buffer.set_buffer(buffer.to_string()); + editor.set_buffer(buffer.to_string(), UndoBehavior::CreateUndoPoint); editor } @@ -475,7 +481,7 @@ mod test { #[case("abc def.ghi", 11, "abc ")] fn test_cut_word_left(#[case] input: &str, #[case] position: usize, #[case] expected: &str) { let mut editor = editor_with(input); - editor.set_insertion_point(position); + editor.line_buffer.set_insertion_point(position); editor.cut_word_left(); @@ -492,7 +498,7 @@ mod test { #[case] expected: &str, ) { let mut editor = editor_with(input); - editor.set_insertion_point(position); + editor.line_buffer.set_insertion_point(position); editor.cut_big_word_left(); @@ -511,7 +517,7 @@ mod test { #[case] expected: &str, ) { let mut editor = editor_with(input); - editor.set_insertion_point(position); + editor.line_buffer.set_insertion_point(position); editor.replace_char(replacement); @@ -523,26 +529,119 @@ mod test { } #[test] - fn test_undo_works_on_work_boundries() { - let mut editor = editor_with("This is a"); + fn test_undo_insert_works_on_work_boundries() { + let mut editor = editor_with("This is a"); for cmd in str_to_edit_commands(" test") { editor.run_edit_command(&cmd); } - assert_eq!(editor.get_buffer(), "This is a test"); + assert_eq!(editor.get_buffer(), "This is a test"); editor.run_edit_command(&EditCommand::Undo); - assert_eq!(editor.get_buffer(), "This is a "); + assert_eq!(editor.get_buffer(), "This is a"); + editor.run_edit_command(&EditCommand::Redo); + assert_eq!(editor.get_buffer(), "This is a test"); } #[test] - fn test_redo_works_on_word_boundries() { + fn test_undo_backspace_works_on_word_boundaries() { + let mut editor = editor_with("This is a test"); + for _ in 0..6 { + editor.run_edit_command(&EditCommand::Backspace); + } + assert_eq!(editor.get_buffer(), "This is "); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a test"); + } + + #[test] + fn test_undo_delete_works_on_word_boundaries() { + let mut editor = editor_with("This is a test"); + editor.line_buffer.set_insertion_point(0); + for _ in 0..7 { + editor.run_edit_command(&EditCommand::Delete); + } + assert_eq!(editor.get_buffer(), "s a test"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "is a test"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a test"); + } + + #[test] + fn test_undo_insert_with_newline() { let mut editor = editor_with("This is a"); - for cmd in str_to_edit_commands(" test") { + for cmd in str_to_edit_commands(" \n test") { editor.run_edit_command(&cmd); } - assert_eq!(editor.get_buffer(), "This is a test"); + assert_eq!(editor.get_buffer(), "This is a \n test"); editor.run_edit_command(&EditCommand::Undo); - assert_eq!(editor.get_buffer(), "This is a "); - editor.run_edit_command(&EditCommand::Redo); - assert_eq!(editor.get_buffer(), "This is a test"); + assert_eq!(editor.get_buffer(), "This is a \n"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a"); + } + + #[test] + fn test_undo_backspace_with_newline() { + let mut editor = editor_with("This is a \n test"); + for _ in 0..8 { + editor.run_edit_command(&EditCommand::Backspace); + } + assert_eq!(editor.get_buffer(), "This is "); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a \n"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a \n test"); + } + + #[test] + fn test_undo_backspace_with_crlf() { + let mut editor = editor_with("This is a \r\n test"); + for _ in 0..8 { + editor.run_edit_command(&EditCommand::Backspace); + } + assert_eq!(editor.get_buffer(), "This is "); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a \r\n"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This is a \r\n test"); + } + + #[test] + fn test_undo_delete_with_newline() { + let mut editor = editor_with("This \n is a test"); + editor.line_buffer.set_insertion_point(0); + for _ in 0..8 { + editor.run_edit_command(&EditCommand::Delete); + } + assert_eq!(editor.get_buffer(), "s a test"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "is a test"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "\n is a test"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This \n is a test"); + } + + #[test] + fn test_undo_delete_with_crlf() { + // CLRF delete is a special case, since the first character of the + // grapheme is \r rather than \n + let mut editor = editor_with("This \r\n is a test"); + editor.line_buffer.set_insertion_point(0); + for _ in 0..8 { + editor.run_edit_command(&EditCommand::Delete); + } + assert_eq!(editor.get_buffer(), "s a test"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "is a test"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "\r\n is a test"); + editor.run_edit_command(&EditCommand::Undo); + assert_eq!(editor.get_buffer(), "This \r\n is a test"); } } diff --git a/src/core_editor/line_buffer.rs b/src/core_editor/line_buffer.rs index a7c46ec..f5db2aa 100644 --- a/src/core_editor/line_buffer.rs +++ b/src/core_editor/line_buffer.rs @@ -25,11 +25,6 @@ impl LineBuffer { Self::default() } - /// Replaces the content between [`start`..`end`] with `text` - pub fn replace(&mut self, range: Range, text: &str) { - self.lines.replace_range(range, text); - } - /// Check to see if the line buffer is empty pub fn is_empty(&self) -> bool { self.lines.is_empty() @@ -71,6 +66,8 @@ impl LineBuffer { } /// Sets the current edit position + /// ## Unicode safety: + /// Not checked, inproper use may cause panics in following operations pub fn set_insertion_point(&mut self, offset: usize) { self.insertion_point = offset; } @@ -406,7 +403,7 @@ impl LineBuffer { /// Substitute text covered by `range` in the current line /// /// Safety: Does not change the insertion point/offset and is thus not unicode safe! - pub(crate) fn replace_range(&mut self, range: R, replace_with: &str) + pub fn replace_range(&mut self, range: R, replace_with: &str) where R: std::ops::RangeBounds, { @@ -422,6 +419,16 @@ impl LineBuffer { .unwrap_or(false) } + /// Get the grapheme immediately to the right of the cursor, if any + pub fn grapheme_right(&self) -> &str { + &self.lines[self.insertion_point..self.grapheme_right_index()] + } + + /// Get the grapheme immediately to the left of the cursor, if any + pub fn grapheme_left(&self) -> &str { + &self.lines[self.grapheme_left_index()..self.insertion_point] + } + /// Gets the range of the word the current edit position is pointing to pub fn current_word_range(&self) -> Range { let right_index = self.word_right_index(); @@ -489,11 +496,6 @@ impl LineBuffer { } } - /// Counts the number of words in the buffer - pub fn word_count(&self) -> usize { - self.lines.split_whitespace().count() - } - /// Capitalize the character at insertion point (or the first character /// following the whitespace at the insertion point) and move the insertion /// point right one grapheme. @@ -901,17 +903,6 @@ mod test { line_buffer.assert_valid(); } - #[rstest] - #[case("This is a te", 4)] - #[case("This is a test", 4)] - #[case("This is a test", 4)] - fn word_count_works(#[case] input: &str, #[case] expected_count: usize) { - let line_buffer = buffer_with(input); - - assert_eq!(expected_count, line_buffer.word_count()); - line_buffer.assert_valid(); - } - #[rstest] #[case("This is a test", 13, "This is a tesT", 14)] #[case("This is a test", 10, "This is a Test", 11)] diff --git a/src/engine.rs b/src/engine.rs index 747a05c..3e59751 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -7,7 +7,7 @@ use crate::{ use crate::result::{ReedlineError, ReedlineErrorVariants}; use { crate::{ - completion::{CircularCompletionHandler, Completer, DefaultCompleter}, + completion::{Completer, DefaultCompleter}, core_editor::Editor, edit_mode::{EditMode, Emacs}, enums::{EventStatus, ReedlineEvent}, @@ -21,7 +21,7 @@ use { prompt::{PromptEditMode, PromptHistorySearchStatus}, utils::text_manipulation, EditCommand, ExampleHighlighter, Highlighter, LineBuffer, Menu, MenuEvent, Prompt, - PromptHistorySearch, ReedlineMenu, Signal, ValidationResult, Validator, + PromptHistorySearch, ReedlineMenu, Signal, UndoBehavior, ValidationResult, Validator, }, crossterm::{ event, @@ -104,9 +104,6 @@ pub struct Reedline { quick_completions: bool, partial_completions: bool, - // Performs bash style circular rotation through the available completions - circular_completion_handler: CircularCompletionHandler, - // Highlight the edit buffer highlighter: Box, @@ -163,7 +160,6 @@ impl Reedline { completer, quick_completions: false, partial_completions: false, - circular_completion_handler: CircularCompletionHandler::default(), highlighter: buffer_highlighter, hinter, hide_hints: false, @@ -578,8 +574,8 @@ impl Reedline { } ReedlineEvent::Enter | ReedlineEvent::HistoryHintComplete => { if let Some(string) = self.history_cursor.string_at_cursor() { - self.editor.set_buffer(string); - self.editor.remember_undo_state(true); + self.editor + .set_buffer(string, UndoBehavior::CreateUndoPoint); } self.input_mode = InputMode::Regular; @@ -627,7 +623,6 @@ impl Reedline { // TODO: Check if events should be handled ReedlineEvent::Right | ReedlineEvent::Left - | ReedlineEvent::ActionHandler | ReedlineEvent::Multiple(_) | ReedlineEvent::None | ReedlineEvent::HistoryHintWordComplete @@ -658,7 +653,7 @@ impl Reedline { if self.quick_completions && menu.can_quick_complete() { menu.update_values( - self.editor.line_buffer(), + &mut self.editor, self.completer.as_mut(), self.history.as_ref(), ); @@ -671,7 +666,7 @@ impl Reedline { if self.partial_completions && menu.can_partially_complete( self.quick_completions, - self.editor.line_buffer(), + &mut self.editor, self.completer.as_mut(), self.history.as_ref(), ) @@ -768,12 +763,6 @@ impl Reedline { } Ok(EventStatus::Inapplicable) } - ReedlineEvent::ActionHandler => { - let line_buffer = self.editor.line_buffer(); - self.circular_completion_handler - .handle(self.completer.as_mut(), line_buffer); - Ok(EventStatus::Handled) - } ReedlineEvent::Esc => { self.deactivate_menus(); Ok(EventStatus::Handled) @@ -806,7 +795,7 @@ impl Reedline { ReedlineEvent::Enter => { for menu in self.menus.iter_mut() { if menu.is_active() { - menu.replace_in_buffer(self.editor.line_buffer()); + menu.replace_in_buffer(&mut self.editor); menu.menu_event(MenuEvent::Deactivate); return Ok(EventStatus::Handled); @@ -859,7 +848,7 @@ impl Reedline { if self.quick_completions && menu.can_quick_complete() { menu.menu_event(MenuEvent::Edit(self.quick_completions)); menu.update_values( - self.editor.line_buffer(), + &mut self.editor, self.completer.as_mut(), self.history.as_ref(), ); @@ -912,9 +901,6 @@ impl Reedline { Ok(EventStatus::Handled) } ReedlineEvent::SearchHistory => { - // Make sure we are able to undo the result of a reverse history search - self.editor.remember_undo_state(true); - self.enter_history_search(); Ok(EventStatus::Handled) } @@ -980,8 +966,9 @@ impl Reedline { .back(self.history.as_ref()) .expect("todo: error handling"); self.update_buffer_from_history(); - self.editor.move_to_start(); - self.editor.move_to_line_end(); + self.editor.move_to_start(UndoBehavior::HistoryNavigation); + self.editor + .move_to_line_end(UndoBehavior::HistoryNavigation); } fn next_history(&mut self) { @@ -995,7 +982,7 @@ impl Reedline { .forward(self.history.as_ref()) .expect("todo: error handling"); self.update_buffer_from_history(); - self.editor.move_to_end(); + self.editor.move_to_end(UndoBehavior::HistoryNavigation); } /// Enable the search and navigation through the history from the line buffer prompt @@ -1006,7 +993,7 @@ impl Reedline { // Perform bash-style basic up/down entry walking HistoryNavigationQuery::Normal( // Hack: Tight coupling point to be able to restore previously typed input - self.editor.line_buffer_immut().clone(), + self.editor.line_buffer().clone(), ) } else { // Prefix search like found in fish, zsh, etc. @@ -1078,20 +1065,21 @@ impl Reedline { match self.history_cursor.get_navigation() { HistoryNavigationQuery::Normal(original) => { if let Some(buffer_to_paint) = self.history_cursor.string_at_cursor() { - self.editor.set_buffer(buffer_to_paint.clone()); - self.editor.set_insertion_point(buffer_to_paint.len()); + self.editor + .set_buffer(buffer_to_paint, UndoBehavior::HistoryNavigation); } else { // Hack - self.editor.set_line_buffer(original); + self.editor + .set_line_buffer(original, UndoBehavior::HistoryNavigation); } } HistoryNavigationQuery::PrefixSearch(prefix) => { if let Some(prefix_result) = self.history_cursor.string_at_cursor() { - self.editor.set_buffer(prefix_result.clone()); - self.editor.set_insertion_point(prefix_result.len()); + self.editor + .set_buffer(prefix_result, UndoBehavior::HistoryNavigation); } else { - self.editor.set_buffer(prefix.clone()); - self.editor.set_insertion_point(prefix.len()); + self.editor + .set_buffer(prefix, UndoBehavior::HistoryNavigation); } } HistoryNavigationQuery::SubstringSearch(_) => todo!(), @@ -1106,7 +1094,8 @@ impl Reedline { HistoryNavigationQuery::Normal(_) ) { if let Some(string) = self.history_cursor.string_at_cursor() { - self.editor.set_buffer(string); + self.editor + .set_buffer(string, UndoBehavior::HistoryNavigation); } } self.input_mode = InputMode::Regular; @@ -1273,7 +1262,7 @@ impl Reedline { let res = std::fs::read_to_string(temp_file)?; let res = res.trim_end().to_string(); - self.editor.line_buffer().set_buffer(res); + self.editor.set_buffer(res, UndoBehavior::CreateUndoPoint); Ok(()) } @@ -1369,7 +1358,7 @@ impl Reedline { for menu in self.menus.iter_mut() { if menu.is_active() { menu.update_working_details( - self.editor.line_buffer(), + &mut self.editor, self.completer.as_mut(), self.history.as_ref(), &self.painter, diff --git a/src/enums.rs b/src/enums.rs index bf2625a..7741f36 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -252,7 +252,7 @@ impl Display for EditCommand { impl EditCommand { /// Determine if a certain operation should be undoable /// or if the operations should be coalesced for undoing - pub fn undo_behavior(&self) -> UndoBehavior { + pub fn edit_type(&self) -> EditType { match self { // Cursor moves EditCommand::MoveToStart @@ -272,13 +272,11 @@ impl EditCommand { | EditCommand::MoveRightUntil(_) | EditCommand::MoveRightBefore(_) | EditCommand::MoveLeftUntil(_) - | EditCommand::MoveLeftBefore(_) => UndoBehavior::Full, + | EditCommand::MoveLeftBefore(_) => EditType::MoveCursor, - // Coalesceable insert - EditCommand::InsertChar(_) => UndoBehavior::Coalesce, - - // Full edits - EditCommand::Backspace + // Text edits + EditCommand::InsertChar(_) + | EditCommand::Backspace | EditCommand::Delete | EditCommand::CutChar | EditCommand::InsertString(_) @@ -311,25 +309,76 @@ impl EditCommand { | EditCommand::CutRightUntil(_) | EditCommand::CutRightBefore(_) | EditCommand::CutLeftUntil(_) - | EditCommand::CutLeftBefore(_) => UndoBehavior::Full, + | EditCommand::CutLeftBefore(_) => EditType::EditText, - EditCommand::Undo | EditCommand::Redo => UndoBehavior::Ignore, + EditCommand::Undo | EditCommand::Redo => EditType::UndoRedo, } } } -/// Specifies how the (previously executed) operation should be treated in the Undo stack. +/// Specifies the types of edit commands, used to simplify grouping edits +/// to mark undo behavior +#[derive(PartialEq, Eq)] +pub enum EditType { + /// Cursor movement commands + MoveCursor, + /// Undo/Redo commands + UndoRedo, + /// Text editing commands + EditText, +} + +/// Every line change should come with an UndoBehavior tag, which can be used to +/// calculate how the change should be reflected on the undo stack +#[derive(Debug)] pub enum UndoBehavior { - /// Operation is not affecting the LineBuffers content and should be ignored - /// - /// e.g. the undo commands themselves are not stored in the undo stack - Ignore, - /// The operation is one logical unit of work that should be stored in the undo stack - Full, - /// The operation is a single operation that should be best coalesced in logical units such as words - /// - /// e.g. insertion of characters by typing - Coalesce, + /// Character insertion, tracking the character inserted + InsertCharacter(char), + /// Backspace command, tracking the deleted character (left of cursor) + /// Warning: this does not track the whole grapheme, just the character + Backspace(Option), + /// Delete command, tracking the deleted character (right of cursor) + /// Warning: this does not track the whole grapheme, just the character + Delete(Option), + /// Move the cursor position + MoveCursor, + /// Navigated the history using up or down arrows + HistoryNavigation, + /// Catch-all for actions that should always form a unique undo point and never be + /// grouped with later edits + CreateUndoPoint, + /// Undo/Redo actions shouldn't be reflected on the edit stack + UndoRedo, +} + +impl UndoBehavior { + /// Return if the current operation should start a new undo set, or be + /// combined with the previous operation + pub fn create_undo_point_after(&self, previous: &UndoBehavior) -> bool { + use UndoBehavior as UB; + match (previous, self) { + // Never start an undo set with cursor movement + (_, UB::MoveCursor) => false, + (UB::HistoryNavigation, UB::HistoryNavigation) => false, + // When inserting/deleting repeatedly, each undo set should encompass + // inserting/deleting a complete word and the associated whitespace + (UB::InsertCharacter(c_prev), UB::InsertCharacter(c_new)) => { + (*c_prev == '\n' || *c_prev == '\r') + || (!c_prev.is_whitespace() && c_new.is_whitespace()) + } + (UB::Backspace(Some(c_prev)), UB::Backspace(Some(c_new))) => { + (*c_new == '\n' || *c_new == '\r') + || (c_prev.is_whitespace() && !c_new.is_whitespace()) + } + (UB::Backspace(_), UB::Backspace(_)) => false, + (UB::Delete(Some(c_prev)), UB::Delete(Some(c_new))) => { + (*c_new == '\n' || *c_new == '\r') + || (c_prev.is_whitespace() && !c_new.is_whitespace()) + } + (UB::Delete(_), UB::Delete(_)) => false, + (_, _) => true, + } + } } /// Reedline supported actions. @@ -344,9 +393,6 @@ pub enum ReedlineEvent { /// Complete a single token/word of the history hint HistoryHintWordComplete, - /// Action event - ActionHandler, - /// Handle EndOfLine event /// /// Expected Behavior: @@ -462,7 +508,6 @@ impl Display for ReedlineEvent { ReedlineEvent::None => write!(f, "None"), ReedlineEvent::HistoryHintComplete => write!(f, "HistoryHintComplete"), ReedlineEvent::HistoryHintWordComplete => write!(f, "HistoryHintWordComplete"), - ReedlineEvent::ActionHandler => write!(f, "ActionHandler"), ReedlineEvent::CtrlD => write!(f, "CtrlD"), ReedlineEvent::CtrlC => write!(f, "CtrlC"), ReedlineEvent::ClearScreen => write!(f, "ClearScreen"), diff --git a/src/lib.rs b/src/lib.rs index da4e4c1..f8e371d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -205,6 +205,7 @@ #![warn(missing_docs)] // #![deny(warnings)] mod core_editor; +pub use core_editor::Editor; pub use core_editor::LineBuffer; mod enums; diff --git a/src/menu/columnar_menu.rs b/src/menu/columnar_menu.rs index c71f21b..f3d1b6a 100644 --- a/src/menu/columnar_menu.rs +++ b/src/menu/columnar_menu.rs @@ -1,6 +1,7 @@ use super::{menu_functions::find_common_string, Menu, MenuEvent, MenuTextStyle}; use crate::{ - menu_functions::string_difference, painting::Painter, Completer, LineBuffer, Suggestion, + core_editor::Editor, menu_functions::string_difference, painting::Painter, Completer, + Suggestion, UndoBehavior, }; use nu_ansi_term::{ansi::RESET, Style}; @@ -464,13 +465,13 @@ impl Menu for ColumnarMenu { fn can_partially_complete( &mut self, values_updated: bool, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, ) -> bool { // If the values were already updated (e.g. quick completions are true) // there is no need to update the values from the menu if !values_updated { - self.update_values(line_buffer, completer); + self.update_values(editor, completer); } let values = self.get_values(); @@ -479,11 +480,11 @@ impl Menu for ColumnarMenu { let matching = &value[0..index]; // make sure that the partial completion does not overwrite user entered input - let extends_input = - matching.starts_with(&line_buffer.get_buffer()[span.start..span.end]); + let extends_input = matching.starts_with(&editor.get_buffer()[span.start..span.end]); if !matching.is_empty() && extends_input { - line_buffer.replace(span.start..span.end, matching); + let mut line_buffer = editor.line_buffer().clone(); + line_buffer.replace_range(span.start..span.end, matching); let offset = if matching.len() < (span.end - span.start) { line_buffer @@ -494,10 +495,11 @@ impl Menu for ColumnarMenu { }; line_buffer.set_insertion_point(offset); + editor.set_line_buffer(line_buffer, UndoBehavior::CreateUndoPoint); // The values need to be updated because the spans need to be // recalculated for accurate replacement in the string - self.update_values(line_buffer, completer); + self.update_values(editor, completer); true } else { @@ -523,10 +525,10 @@ impl Menu for ColumnarMenu { } /// Updates menu values - fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) { + fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) { if self.only_buffer_difference { if let Some(old_string) = &self.input { - let (start, input) = string_difference(line_buffer.get_buffer(), old_string); + let (start, input) = string_difference(editor.get_buffer(), old_string); if !input.is_empty() { self.values = completer.complete(input, start); self.reset_position(); @@ -538,9 +540,8 @@ impl Menu for ColumnarMenu { // editing a multiline buffer. // Also, by replacing the new line character with a space, the insert // position is maintain in the line buffer. - let trimmed_buffer = line_buffer.get_buffer().replace('\n', " "); - self.values = - completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point()); + let trimmed_buffer = editor.get_buffer().replace('\n', " "); + self.values = completer.complete(trimmed_buffer.as_str(), editor.insertion_point()); self.reset_position(); } } @@ -549,7 +550,7 @@ impl Menu for ColumnarMenu { /// collected from the completer fn update_working_details( &mut self, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, painter: &Painter, ) { @@ -618,13 +619,13 @@ impl Menu for ColumnarMenu { self.reset_position(); self.input = if self.only_buffer_difference { - Some(line_buffer.get_buffer().to_string()) + Some(editor.get_buffer().to_string()) } else { None }; if !updated { - self.update_values(line_buffer, completer); + self.update_values(editor, completer); } } MenuEvent::Deactivate => self.active = false, @@ -632,7 +633,7 @@ impl Menu for ColumnarMenu { self.reset_position(); if !updated { - self.update_values(line_buffer, completer); + self.update_values(editor, completer); } } MenuEvent::NextElement => self.move_next(), @@ -649,7 +650,7 @@ impl Menu for ColumnarMenu { } /// The buffer gets replaced in the Span location - fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) { + fn replace_in_buffer(&self, editor: &mut Editor) { if let Some(Suggestion { mut value, span, @@ -657,16 +658,18 @@ impl Menu for ColumnarMenu { .. }) = self.get_value() { - let start = span.start.min(line_buffer.len()); - let end = span.end.min(line_buffer.len()); + let start = span.start.min(editor.line_buffer().len()); + let end = span.end.min(editor.line_buffer().len()); if append_whitespace { value.push(' '); } - line_buffer.replace(start..end, &value); + let mut line_buffer = editor.line_buffer().clone(); + line_buffer.replace_range(start..end, &value); let mut offset = line_buffer.insertion_point(); offset += value.len().saturating_sub(end.saturating_sub(start)); line_buffer.set_insertion_point(offset); + editor.set_line_buffer(line_buffer, UndoBehavior::CreateUndoPoint); } } @@ -728,7 +731,7 @@ mod tests { macro_rules! partial_completion_tests { (name: $test_group_name:ident, completions: $completions:expr, test_cases: $($name:ident: $value:expr,)*) => { mod $test_group_name { - use crate::{menu::Menu, ColumnarMenu, LineBuffer}; + use crate::{menu::Menu, ColumnarMenu, core_editor::Editor, enums::UndoBehavior}; use super::FakeCompleter; $( @@ -736,13 +739,13 @@ mod tests { fn $name() { let (input, expected) = $value; let mut menu = ColumnarMenu::default(); - let mut line_buffer = LineBuffer::default(); - line_buffer.set_buffer(input.to_string()); + let mut editor = Editor::default(); + editor.set_buffer(input.to_string(), UndoBehavior::CreateUndoPoint); let mut completer = FakeCompleter::new(&$completions); - menu.can_partially_complete(false, &mut line_buffer, &mut completer); + menu.can_partially_complete(false, &mut editor, &mut completer); - assert_eq!(line_buffer.get_buffer(), expected); + assert_eq!(editor.get_buffer(), expected); } )* } diff --git a/src/menu/list_menu.rs b/src/menu/list_menu.rs index d6baf5a..adf41a2 100644 --- a/src/menu/list_menu.rs +++ b/src/menu/list_menu.rs @@ -1,3 +1,5 @@ +use crate::{core_editor::Editor, UndoBehavior}; + use { super::{ menu_functions::{parse_selection_char, string_difference}, @@ -5,7 +7,7 @@ use { }, crate::{ painting::{estimate_single_line_wraps, Painter}, - Completer, LineBuffer, Suggestion, + Completer, Suggestion, }, nu_ansi_term::{ansi::RESET, Style}, std::iter::Sum, @@ -371,7 +373,7 @@ impl Menu for ListMenu { fn can_partially_complete( &mut self, _values_updated: bool, - _line_buffer: &mut LineBuffer, + _editor: &mut Editor, _completer: &mut dyn Completer, ) -> bool { false @@ -392,7 +394,8 @@ impl Menu for ListMenu { } /// Collecting the value from the completer to be shown in the menu - fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) { + fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) { + let line_buffer = editor.line_buffer(); let (start, input) = if self.only_buffer_difference { match &self.input { Some(old_string) => { @@ -463,7 +466,7 @@ impl Menu for ListMenu { } /// The buffer gets cleared with the actual value - fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) { + fn replace_in_buffer(&self, editor: &mut Editor) { if let Some(Suggestion { mut value, span, @@ -471,22 +474,25 @@ impl Menu for ListMenu { .. }) = self.get_value() { - let start = span.start.min(line_buffer.len()); - let end = span.end.min(line_buffer.len()); + let buffer_len = editor.line_buffer().len(); + let start = span.start.min(buffer_len); + let end = span.end.min(buffer_len); if append_whitespace { value.push(' '); } - line_buffer.replace(start..end, &value); + let mut line_buffer = editor.line_buffer().clone(); + line_buffer.replace_range(start..end, &value); let mut offset = line_buffer.insertion_point(); offset += value.len().saturating_sub(end.saturating_sub(start)); line_buffer.set_insertion_point(offset); + editor.set_line_buffer(line_buffer, UndoBehavior::CreateUndoPoint); } } fn update_working_details( &mut self, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, painter: &Painter, ) { @@ -496,12 +502,12 @@ impl Menu for ListMenu { self.reset_position(); self.input = if self.only_buffer_difference { - Some(line_buffer.get_buffer().to_string()) + Some(editor.get_buffer().to_string()) } else { None }; - self.update_values(line_buffer, completer); + self.update_values(editor, completer); self.pages.push(Page { size: self.printable_entries(painter), @@ -513,7 +519,7 @@ impl Menu for ListMenu { self.input = None; } MenuEvent::Edit(_) => { - self.update_values(line_buffer, completer); + self.update_values(editor, completer); self.pages.push(Page { size: self.printable_entries(painter), full: false, @@ -525,7 +531,7 @@ impl Menu for ListMenu { if let Some(page) = self.pages.get(self.page) { if new_pos >= page.size as u16 { self.event = Some(MenuEvent::NextPage); - self.update_working_details(line_buffer, completer, painter); + self.update_working_details(editor, completer, painter); } else { self.row_position = new_pos; } @@ -546,7 +552,7 @@ impl Menu for ListMenu { } self.event = Some(MenuEvent::PreviousPage); - self.update_working_details(line_buffer, completer, painter); + self.update_working_details(editor, completer, painter); } } MenuEvent::NextPage => { @@ -566,12 +572,12 @@ impl Menu for ListMenu { } } - self.update_values(line_buffer, completer); + self.update_values(editor, completer); self.set_actual_page_size(self.printable_entries(painter)); } else { self.row_position = 0; self.page = 0; - self.update_values(line_buffer, completer); + self.update_values(editor, completer); } } MenuEvent::PreviousPage => { @@ -579,7 +585,7 @@ impl Menu for ListMenu { Some(page_num) => self.page = page_num, None => self.page = self.pages.len().saturating_sub(1), } - self.update_values(line_buffer, completer); + self.update_values(editor, completer); } } diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 31e061d..fb55958 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -2,10 +2,9 @@ mod columnar_menu; mod list_menu; pub mod menu_functions; +use crate::core_editor::Editor; use crate::History; -use crate::{ - completion::history::HistoryCompleter, painting::Painter, Completer, LineBuffer, Suggestion, -}; +use crate::{completion::history::HistoryCompleter, painting::Painter, Completer, Suggestion}; pub use columnar_menu::ColumnarMenu; pub use list_menu::ListMenu; use nu_ansi_term::{Color, Style}; @@ -82,7 +81,7 @@ pub trait Menu: Send { fn can_partially_complete( &mut self, values_updated: bool, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, ) -> bool; @@ -91,7 +90,7 @@ pub trait Menu: Send { /// activated or the `quick_completion` option is true, the len of the values /// is calculated to know if there is only one value so it can be selected /// immediately - fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer); + fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer); /// The working details of a menu are values that could change based on /// the menu conditions before it being printed, such as the number or size @@ -100,13 +99,13 @@ pub trait Menu: Send { /// it is called just before painting the menu fn update_working_details( &mut self, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, painter: &Painter, ); /// Indicates how to replace in the line buffer the selected value from the menu - fn replace_in_buffer(&self, line_buffer: &mut LineBuffer); + fn replace_in_buffer(&self, editor: &mut Editor); /// Calculates the real required lines for the menu considering how many lines /// wrap the terminal or if entries have multiple lines @@ -157,66 +156,66 @@ impl ReedlineMenu { pub(crate) fn can_partially_complete( &mut self, values_updated: bool, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, history: &dyn History, ) -> bool { match self { Self::EngineCompleter(menu) => { - menu.can_partially_complete(values_updated, line_buffer, completer) + menu.can_partially_complete(values_updated, editor, completer) } Self::HistoryMenu(menu) => { let mut history_completer = HistoryCompleter::new(history); - menu.can_partially_complete(values_updated, line_buffer, &mut history_completer) + menu.can_partially_complete(values_updated, editor, &mut history_completer) } Self::WithCompleter { menu, completer: own_completer, - } => menu.can_partially_complete(values_updated, line_buffer, own_completer.as_mut()), + } => menu.can_partially_complete(values_updated, editor, own_completer.as_mut()), } } pub(crate) fn update_values( &mut self, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, history: &dyn History, ) { match self { - Self::EngineCompleter(menu) => menu.update_values(line_buffer, completer), + Self::EngineCompleter(menu) => menu.update_values(editor, completer), Self::HistoryMenu(menu) => { let mut history_completer = HistoryCompleter::new(history); - menu.update_values(line_buffer, &mut history_completer); + menu.update_values(editor, &mut history_completer); } Self::WithCompleter { menu, completer: own_completer, } => { - menu.update_values(line_buffer, own_completer.as_mut()); + menu.update_values(editor, own_completer.as_mut()); } } } pub(crate) fn update_working_details( &mut self, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, history: &dyn History, painter: &Painter, ) { match self { Self::EngineCompleter(menu) => { - menu.update_working_details(line_buffer, completer, painter); + menu.update_working_details(editor, completer, painter); } Self::HistoryMenu(menu) => { let mut history_completer = HistoryCompleter::new(history); - menu.update_working_details(line_buffer, &mut history_completer, painter); + menu.update_working_details(editor, &mut history_completer, painter); } Self::WithCompleter { menu, completer: own_completer, } => { - menu.update_working_details(line_buffer, own_completer.as_mut(), painter); + menu.update_working_details(editor, own_completer.as_mut(), painter); } } } @@ -246,55 +245,55 @@ impl Menu for ReedlineMenu { fn can_partially_complete( &mut self, values_updated: bool, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, ) -> bool { match self { Self::EngineCompleter(menu) | Self::HistoryMenu(menu) => { - menu.can_partially_complete(values_updated, line_buffer, completer) + menu.can_partially_complete(values_updated, editor, completer) } Self::WithCompleter { menu, completer: own_completer, - } => menu.can_partially_complete(values_updated, line_buffer, own_completer.as_mut()), + } => menu.can_partially_complete(values_updated, editor, own_completer.as_mut()), } } - fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) { + fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) { match self { Self::EngineCompleter(menu) | Self::HistoryMenu(menu) => { - menu.update_values(line_buffer, completer); + menu.update_values(editor, completer); } Self::WithCompleter { menu, completer: own_completer, } => { - menu.update_values(line_buffer, own_completer.as_mut()); + menu.update_values(editor, own_completer.as_mut()); } } } fn update_working_details( &mut self, - line_buffer: &mut LineBuffer, + editor: &mut Editor, completer: &mut dyn Completer, painter: &Painter, ) { match self { Self::EngineCompleter(menu) | Self::HistoryMenu(menu) => { - menu.update_working_details(line_buffer, completer, painter); + menu.update_working_details(editor, completer, painter); } Self::WithCompleter { menu, completer: own_completer, } => { - menu.update_working_details(line_buffer, own_completer.as_mut(), painter); + menu.update_working_details(editor, own_completer.as_mut(), painter); } } } - fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) { - self.as_ref().replace_in_buffer(line_buffer); + fn replace_in_buffer(&self, editor: &mut Editor) { + self.as_ref().replace_in_buffer(editor); } fn menu_required_lines(&self, terminal_columns: u16) -> u16 {