diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 0dee4f3..2852d4a 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -1,14 +1,17 @@ #![allow(dead_code)] use std::mem; -use std::collections::DList; -use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter}; + use self::line::{Line, LineEnding}; +use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter}; +use self::undo_stack::{UndoStack}; +use self::undo_stack::Operation::*; use string_utils::{is_line_ending, grapheme_count}; pub mod line; mod node; +mod undo_stack; //============================================================= @@ -19,7 +22,7 @@ mod node; pub struct Buffer { text: BufferNode, pub line_ending_type: LineEnding, - undo_stack: DList, + undo_stack: UndoStack, } @@ -28,7 +31,7 @@ impl Buffer { Buffer { text: BufferNode::new(), line_ending_type: LineEnding::LF, - undo_stack: DList::new(), + undo_stack: UndoStack::new(), } } @@ -57,7 +60,7 @@ impl Buffer { pub fn insert_text(&mut self, text: &str, pos: usize) { self._insert_text(text, pos); - self.undo_stack.push_back(Operation::InsertText(String::from_str(text), pos)); + self.undo_stack.push(InsertText(String::from_str(text), pos)); } fn _insert_text(&mut self, text: &str, pos: usize) { @@ -72,7 +75,7 @@ impl Buffer { self._remove_text(pos_a, pos_b); // Push operation to the undo stack - self.undo_stack.push_back(Operation::RemoveText(removed_text, pos_a)); + self.undo_stack.push(RemoveText(removed_text, pos_a)); } fn _remove_text(&mut self, pos_a: usize, pos_b: usize) { @@ -146,19 +149,50 @@ impl Buffer { /// Undoes operations that were pushed to the undo stack, and returns a /// cursor position that the cursor should jump to, if any. pub fn undo(&mut self) -> Option { - if let Some(op) = self.undo_stack.pop_back() { + if let Some(op) = self.undo_stack.prev() { match op { - Operation::InsertText(ref s, p) => { + InsertText(ref s, p) => { let size = grapheme_count(s.as_slice()); self._remove_text(p, p+size); return Some(p); }, - Operation::RemoveText(ref s, p) => { + RemoveText(ref s, p) => { let size = grapheme_count(s.as_slice()); self._insert_text(s.as_slice(), p); return Some(p+size); }, + + _ => { + return None; + }, + } + } + + return None; + } + + + /// Redoes the last undone operation, and returns a cursor position that + /// the cursor should jump to, if any. + pub fn redo(&mut self) -> Option { + if let Some(op) = self.undo_stack.next() { + match op { + InsertText(ref s, p) => { + let size = grapheme_count(s.as_slice()); + self._insert_text(s.as_slice(), p); + return Some(p+size); + }, + + RemoveText(ref s, p) => { + let size = grapheme_count(s.as_slice()); + self._remove_text(p, p+size); + return Some(p); + }, + + _ => { + return None; + }, } } @@ -379,25 +413,13 @@ impl<'a> Iterator for BufferLineIter<'a> { -//================================================================ -// Buffer undo structures -//================================================================ - -enum Operation { - InsertText(String, usize), - RemoveText(String, usize), -} - - - - //================================================================ // TESTS //================================================================ #[cfg(test)] mod tests { - use super::{Buffer, Operation, BufferGraphemeIter, BufferLineIter}; + use super::{Buffer, BufferGraphemeIter, BufferLineIter}; #[test] fn insert_text() { diff --git a/src/buffer/undo_stack.rs b/src/buffer/undo_stack.rs new file mode 100644 index 0000000..9d63652 --- /dev/null +++ b/src/buffer/undo_stack.rs @@ -0,0 +1,55 @@ +use std::collections::DList; + + +/// A text editing operation +#[derive(Clone)] +pub enum Operation { + InsertText(String, usize), + RemoveText(String, usize), + MoveText(usize, usize, usize), + CompositeOp(Vec), +} + + +/// An undo/redo stack of text editing operations +pub struct UndoStack { + stack_a: DList, + stack_b: DList, +} + +impl UndoStack { + pub fn new() -> UndoStack { + UndoStack { + stack_a: DList::new(), + stack_b: DList::new(), + } + } + + + pub fn push(&mut self, op: Operation) { + self.stack_a.push_back(op); + self.stack_b.clear(); + } + + + pub fn prev(&mut self) -> Option { + if let Some(op) = self.stack_a.pop_back() { + self.stack_b.push_back(op.clone()); + return Some(op); + } + else { + return None; + } + } + + + pub fn next(&mut self) -> Option { + if let Some(op) = self.stack_b.pop_back() { + self.stack_a.push_back(op.clone()); + return Some(op); + } + else { + return None; + } + } +} \ No newline at end of file diff --git a/src/editor.rs b/src/editor.rs index fca62f1..651d5d4 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -200,6 +200,17 @@ impl Editor { } + pub fn redo(&mut self) { + if let Some(pos) = self.buffer.redo() { + self.cursor.range.0 = pos; + self.cursor.range.1 = pos; + self.cursor.update_vis_start(&(self.buffer), self.tab_width); + + self.move_view_to_cursor(); + } + } + + /// Moves the editor's view the minimum amount to show the cursor pub fn move_view_to_cursor(&mut self) { let (v, h) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0, self.tab_width); diff --git a/src/term_ui.rs b/src/term_ui.rs index 2c20b53..f1b49f3 100644 --- a/src/term_ui.rs +++ b/src/term_ui.rs @@ -25,6 +25,7 @@ const K_CTRL_L: u16 = 12; const K_CTRL_O: u16 = 15; const K_CTRL_Q: u16 = 17; const K_CTRL_S: u16 = 19; +const K_CTRL_Y: u16 = 25; const K_CTRL_Z: u16 = 26; @@ -103,6 +104,10 @@ impl TermUI { self.editor.undo(); }, + K_CTRL_Y => { + self.editor.redo(); + }, + K_CTRL_L => { self.go_to_line_ui_loop(); }, diff --git a/todo.md b/todo.md index 1d17f94..75c5800 100644 --- a/todo.md +++ b/todo.md @@ -17,8 +17,8 @@ //- remove_text - move_text - Undo functionality: - - Undo - - Redo + //- Undo + //- Redo - Op section begin (for delimiting composite edit operations) - Op section end - Info: