Added full undo/redo functionality.

This commit is contained in:
Nathan Vegdahl 2015-01-10 12:29:58 -08:00
parent e61149e514
commit 6b5b63dba1
5 changed files with 117 additions and 24 deletions

View File

@ -1,14 +1,17 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::mem; use std::mem;
use std::collections::DList;
use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
use self::line::{Line, LineEnding}; 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}; use string_utils::{is_line_ending, grapheme_count};
pub mod line; pub mod line;
mod node; mod node;
mod undo_stack;
//============================================================= //=============================================================
@ -19,7 +22,7 @@ mod node;
pub struct Buffer { pub struct Buffer {
text: BufferNode, text: BufferNode,
pub line_ending_type: LineEnding, pub line_ending_type: LineEnding,
undo_stack: DList<Operation>, undo_stack: UndoStack,
} }
@ -28,7 +31,7 @@ impl Buffer {
Buffer { Buffer {
text: BufferNode::new(), text: BufferNode::new(),
line_ending_type: LineEnding::LF, 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) { pub fn insert_text(&mut self, text: &str, pos: usize) {
self._insert_text(text, pos); 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) { fn _insert_text(&mut self, text: &str, pos: usize) {
@ -72,7 +75,7 @@ impl Buffer {
self._remove_text(pos_a, pos_b); self._remove_text(pos_a, pos_b);
// Push operation to the undo stack // 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) { 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 /// Undoes operations that were pushed to the undo stack, and returns a
/// cursor position that the cursor should jump to, if any. /// cursor position that the cursor should jump to, if any.
pub fn undo(&mut self) -> Option<usize> { pub fn undo(&mut self) -> Option<usize> {
if let Some(op) = self.undo_stack.pop_back() { if let Some(op) = self.undo_stack.prev() {
match op { match op {
Operation::InsertText(ref s, p) => { InsertText(ref s, p) => {
let size = grapheme_count(s.as_slice()); let size = grapheme_count(s.as_slice());
self._remove_text(p, p+size); self._remove_text(p, p+size);
return Some(p); return Some(p);
}, },
Operation::RemoveText(ref s, p) => { RemoveText(ref s, p) => {
let size = grapheme_count(s.as_slice()); let size = grapheme_count(s.as_slice());
self._insert_text(s.as_slice(), p); self._insert_text(s.as_slice(), p);
return Some(p+size); 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<usize> {
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 // TESTS
//================================================================ //================================================================
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Buffer, Operation, BufferGraphemeIter, BufferLineIter}; use super::{Buffer, BufferGraphemeIter, BufferLineIter};
#[test] #[test]
fn insert_text() { fn insert_text() {

55
src/buffer/undo_stack.rs Normal file
View File

@ -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<Operation>),
}
/// An undo/redo stack of text editing operations
pub struct UndoStack {
stack_a: DList<Operation>,
stack_b: DList<Operation>,
}
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<Operation> {
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<Operation> {
if let Some(op) = self.stack_b.pop_back() {
self.stack_a.push_back(op.clone());
return Some(op);
}
else {
return None;
}
}
}

View File

@ -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 /// Moves the editor's view the minimum amount to show the cursor
pub fn move_view_to_cursor(&mut self) { 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); let (v, h) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0, self.tab_width);

View File

@ -25,6 +25,7 @@ const K_CTRL_L: u16 = 12;
const K_CTRL_O: u16 = 15; const K_CTRL_O: u16 = 15;
const K_CTRL_Q: u16 = 17; const K_CTRL_Q: u16 = 17;
const K_CTRL_S: u16 = 19; const K_CTRL_S: u16 = 19;
const K_CTRL_Y: u16 = 25;
const K_CTRL_Z: u16 = 26; const K_CTRL_Z: u16 = 26;
@ -103,6 +104,10 @@ impl TermUI {
self.editor.undo(); self.editor.undo();
}, },
K_CTRL_Y => {
self.editor.redo();
},
K_CTRL_L => { K_CTRL_L => {
self.go_to_line_ui_loop(); self.go_to_line_ui_loop();
}, },

View File

@ -17,8 +17,8 @@
//- remove_text //- remove_text
- move_text - move_text
- Undo functionality: - Undo functionality:
- Undo //- Undo
- Redo //- Redo
- Op section begin (for delimiting composite edit operations) - Op section begin (for delimiting composite edit operations)
- Op section end - Op section end
- Info: - Info: