Added full undo/redo functionality.
This commit is contained in:
parent
e61149e514
commit
6b5b63dba1
|
@ -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
55
src/buffer/undo_stack.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
},
|
},
|
||||||
|
|
4
todo.md
4
todo.md
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user