diff --git a/Cargo.toml b/Cargo.toml index 6b1012c..5c73a8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ self_cell = "1.0.1" swash = { version = "0.1.8", optional = true } syntect = { version = "5.1.0", optional = true } sys-locale = { version = "0.3.1", optional = true } +undo_2 = { version = "0.2.0", optional = true } unicode-linebreak = "0.1.5" unicode-script = "0.5.5" unicode-segmentation = "1.10.1" @@ -46,7 +47,7 @@ std = [ "sys-locale", "unicode-bidi/std", ] -vi = ["modit", "syntect"] +vi = ["modit", "syntect", "undo_2"] wasm-web = ["sys-locale?/js"] warn_on_missing_glyphs = [] fontconfig = ["fontdb/fontconfig", "std"] diff --git a/src/edit/editor.rs b/src/edit/editor.rs index a9f8faf..1ab8639 100644 --- a/src/edit/editor.rs +++ b/src/edit/editor.rs @@ -124,7 +124,12 @@ impl Editor { } if let Some(ref mut change) = self.change { - let item = ChangeItem::Delete(start, change_text); + let item = ChangeItem { + start, + end, + text: change_text, + insert: false, + }; change.items.push(item); } } @@ -140,10 +145,8 @@ impl Editor { return cursor; } - if let Some(ref mut change) = self.change { - let item = ChangeItem::Insert(cursor, data.to_string()); - change.items.push(item); - } + // Save cursor for change tracking + let start = cursor; let line: &mut BufferLine = &mut self.buffer.lines[cursor.line]; let insert_line = cursor.line + 1; @@ -208,6 +211,16 @@ impl Editor { // Append the text after insertion cursor.index = self.buffer.lines[cursor.line].text().len() - after_len; + if let Some(ref mut change) = self.change { + let item = ChangeItem { + start, + end: cursor, + text: data.to_string(), + insert: true, + }; + change.items.push(item); + } + cursor } } @@ -348,9 +361,36 @@ impl Edit for Editor { self.set_cursor(new_cursor); } + fn apply_change(&mut self, change: &Change) -> bool { + // Cannot apply changes if there is a pending change + match self.change.take() { + Some(pending) => { + if !pending.items.is_empty() { + //TODO: is this a good idea? + log::warn!("pending change caused apply_change to be ignored!"); + self.change = Some(pending); + return false; + } + } + None => {} + } + + for item in change.items.iter() { + //TODO: edit cursor if needed? + if item.insert { + self.cursor = self.insert_at(item.start, &item.text, None); + } else { + self.cursor = item.start; + self.delete_range(item.start, item.end); + } + } + true + } + fn start_change(&mut self) { - //TODO: what to do if overwriting change? - self.change = Some(Change::default()); + if self.change.is_none() { + self.change = Some(Change::default()); + } } fn finish_change(&mut self) -> Option { diff --git a/src/edit/mod.rs b/src/edit/mod.rs index e5027a7..fbe66c2 100644 --- a/src/edit/mod.rs +++ b/src/edit/mod.rs @@ -93,15 +93,41 @@ pub enum Action { GotoLine(usize), } +/// A unique change to an editor #[derive(Clone, Debug)] -pub enum ChangeItem { - Delete(Cursor, String), - Insert(Cursor, String), +pub struct ChangeItem { + /// Cursor indicating start of change + pub start: Cursor, + /// Cursor indicating end of change + pub end: Cursor, + /// Text to be inserted or deleted + pub text: String, + /// Insert if true, delete if false + pub insert: bool, } +impl ChangeItem { + // Reverse change item (in place) + pub fn reverse(&mut self) { + self.insert = !self.insert; + } +} + +/// A set of change items grouped into one logical change #[derive(Clone, Debug, Default)] pub struct Change { - items: Vec, + /// Change items grouped into one change + pub items: Vec, +} + +impl Change { + // Reverse change (in place) + pub fn reverse(&mut self) { + self.items.reverse(); + for item in self.items.iter_mut() { + item.reverse(); + } + } } /// A trait to allow easy replacements of [`Editor`], like `SyntaxEditor` @@ -158,6 +184,9 @@ pub trait Edit { /// attributes, or with the previous character's attributes if None is given. fn insert_string(&mut self, data: &str, attrs_list: Option); + /// Apply a change + fn apply_change(&mut self, change: &Change) -> bool; + /// Start collecting change fn start_change(&mut self); diff --git a/src/edit/syntect.rs b/src/edit/syntect.rs index 75b401a..ff6ccd5 100644 --- a/src/edit/syntect.rs +++ b/src/edit/syntect.rs @@ -304,6 +304,10 @@ impl<'a> Edit for SyntaxEditor<'a> { self.editor.insert_string(data, attrs_list); } + fn apply_change(&mut self, change: &Change) -> bool { + self.editor.apply_change(change) + } + fn start_change(&mut self) { self.editor.start_change(); } diff --git a/src/edit/vi.rs b/src/edit/vi.rs index f5e7c73..331ec50 100644 --- a/src/edit/vi.rs +++ b/src/edit/vi.rs @@ -1,6 +1,7 @@ use alloc::string::String; use core::cmp; use modit::{Event, Key, Motion, Parser, TextObject, WordIter}; +use undo_2::Commands; use unicode_segmentation::UnicodeSegmentation; use crate::{ @@ -10,6 +11,20 @@ use crate::{ pub use modit::{ViMode, ViParser}; +fn undo_2_action(editor: &mut E, action: undo_2::Action<&Change>) { + match action { + undo_2::Action::Do(change) => { + editor.apply_change(change); + } + undo_2::Action::Undo(change) => { + //TODO: make this more efficient + let mut reversed = change.clone(); + reversed.reverse(); + editor.apply_change(&reversed); + } + } +} + fn search(editor: &mut E, search: &str, forwards: bool) { let mut cursor = editor.cursor(); let start_line = cursor.line; @@ -124,6 +139,7 @@ pub struct ViEditor<'a> { parser: ViParser, passthrough: bool, search_opt: Option<(String, bool)>, + commands: Commands, } impl<'a> ViEditor<'a> { @@ -133,6 +149,7 @@ impl<'a> ViEditor<'a> { parser: ViParser::new(), passthrough: false, search_opt: None, + commands: Commands::new(), } } @@ -179,66 +196,24 @@ impl<'a> ViEditor<'a> { pub fn parser(&self) -> &ViParser { &self.parser } -} -impl<'a> Edit for ViEditor<'a> { - fn buffer(&self) -> &Buffer { - self.editor.buffer() + /// Redo a change + pub fn redo(&mut self) { + log::info!("Redo"); + for action in self.commands.redo() { + undo_2_action(&mut self.editor, action); + } } - fn buffer_mut(&mut self) -> &mut Buffer { - self.editor.buffer_mut() + /// Undo a change + pub fn undo(&mut self) { + log::info!("Undo"); + for action in self.commands.undo() { + undo_2_action(&mut self.editor, action); + } } - fn cursor(&self) -> Cursor { - self.editor.cursor() - } - - fn set_cursor(&mut self, cursor: Cursor) { - self.editor.set_cursor(cursor); - } - - fn select_opt(&self) -> Option { - self.editor.select_opt() - } - - fn set_select_opt(&mut self, select_opt: Option) { - self.editor.set_select_opt(select_opt); - } - - fn tab_width(&self) -> usize { - self.editor.tab_width() - } - - fn set_tab_width(&mut self, tab_width: usize) { - self.editor.set_tab_width(tab_width); - } - - fn shape_as_needed(&mut self, font_system: &mut FontSystem) { - self.editor.shape_as_needed(font_system); - } - - fn copy_selection(&self) -> Option { - self.editor.copy_selection() - } - - fn delete_selection(&mut self) -> bool { - self.editor.delete_selection() - } - - fn insert_string(&mut self, data: &str, attrs_list: Option) { - self.editor.insert_string(data, attrs_list); - } - - fn start_change(&mut self) { - self.editor.start_change(); - } - - fn finish_change(&mut self) -> Option { - self.editor.finish_change() - } - - fn action(&mut self, font_system: &mut FontSystem, action: Action) { + fn action_inner(&mut self, font_system: &mut FontSystem, action: Action) { let editor = &mut self.editor; log::info!("Action {:?}", action); @@ -268,6 +243,7 @@ impl<'a> Edit for ViEditor<'a> { return editor.action(font_system, action); } }; + self.parser.parse(key, false, |event| { log::info!(" Event {:?}", event); let action = match event { @@ -347,7 +323,9 @@ impl<'a> Edit for ViEditor<'a> { return; } Event::Undo => { - log::info!("TODO"); + for action in self.commands.undo() { + undo_2_action(editor, action); + } return; } Event::Motion(motion) => { @@ -636,6 +614,84 @@ impl<'a> Edit for ViEditor<'a> { editor.action(font_system, action); }); } +} + +impl<'a> Edit for ViEditor<'a> { + fn buffer(&self) -> &Buffer { + self.editor.buffer() + } + + fn buffer_mut(&mut self) -> &mut Buffer { + self.editor.buffer_mut() + } + + fn cursor(&self) -> Cursor { + self.editor.cursor() + } + + fn set_cursor(&mut self, cursor: Cursor) { + self.editor.set_cursor(cursor); + } + + fn select_opt(&self) -> Option { + self.editor.select_opt() + } + + fn set_select_opt(&mut self, select_opt: Option) { + self.editor.set_select_opt(select_opt); + } + + fn tab_width(&self) -> usize { + self.editor.tab_width() + } + + fn set_tab_width(&mut self, tab_width: usize) { + self.editor.set_tab_width(tab_width); + } + + fn shape_as_needed(&mut self, font_system: &mut FontSystem) { + self.editor.shape_as_needed(font_system); + } + + fn copy_selection(&self) -> Option { + self.editor.copy_selection() + } + + fn delete_selection(&mut self) -> bool { + self.editor.delete_selection() + } + + fn insert_string(&mut self, data: &str, attrs_list: Option) { + self.editor.insert_string(data, attrs_list); + } + + fn apply_change(&mut self, change: &Change) -> bool { + self.editor.apply_change(change) + } + + fn start_change(&mut self) { + self.editor.start_change(); + } + + fn finish_change(&mut self) -> Option { + self.editor.finish_change() + } + + fn action(&mut self, font_system: &mut FontSystem, action: Action) { + self.start_change(); + + self.action_inner(font_system, action); + + match self.finish_change() { + Some(change) => { + if !change.items.is_empty() { + log::info!("{:?}", change); + self.commands.push(change); + } + } + None => {} + } + } #[cfg(feature = "swash")] fn draw(