From 0c887d1eb9354442ef6fe5d3d8c385438be7e406 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sat, 22 Feb 2020 18:00:28 +0900 Subject: [PATCH] Beginnings of an undo system for the new backend. Just going to keep it stupid and simple for now. More sophisticated undo functionality can be added in the future. --- sub_crates/backend/src/buffer.rs | 45 +++++++++++++++++++++++++++++-- sub_crates/backend/src/history.rs | 45 +++++++++++++++++++++++++++++++ sub_crates/backend/src/lib.rs | 1 + sub_crates/backend/src/marks.rs | 38 +++++++++++++++++++++++++- 4 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 sub_crates/backend/src/history.rs diff --git a/sub_crates/backend/src/buffer.rs b/sub_crates/backend/src/buffer.rs index fe80ab5..8a24baf 100644 --- a/sub_crates/backend/src/buffer.rs +++ b/sub_crates/backend/src/buffer.rs @@ -1,6 +1,9 @@ use ropey::Rope; -use crate::marks::MarkSet; +use crate::{ + history::{Edit, History}, + marks::MarkSet, +}; /// An open text buffer, currently being edited. #[derive(Debug, Clone)] @@ -8,6 +11,7 @@ pub struct Buffer { pub is_dirty: bool, // Is this buffer currently out of sync with disk. pub text: Rope, // The actual text content. pub mark_sets: Vec, // MarkSets for cursors, view positions, etc. + history: History, } impl Buffer { @@ -16,10 +20,14 @@ impl Buffer { is_dirty: false, text: text, mark_sets: Vec::new(), + history: History::new(), } } - // Replaces the given range of chars with the given text. + /// Replaces the given range of chars with the given text. + /// + /// The range does not have to be ordered (i.e. the first component can be + /// greater than the second). pub fn edit(&mut self, char_idx_range: (usize, usize), text: &str) { self.is_dirty = true; @@ -30,6 +38,31 @@ impl Buffer { (char_idx_range.1, char_idx_range.0) }; + // Update undo stack. + if char_idx_range.0 == char_idx_range.1 { + // Fast-path for insertion-only edits. + self.history.push_edit(Edit { + char_idx: start, + from: String::new(), + to: text.into(), + }); + } else { + self.history.push_edit(Edit { + char_idx: start, + from: self.text.slice(start..end).into(), + to: text.into(), + }); + } + + // Update mark sets. + for mark_set in self.mark_sets.iter_mut() { + for mark in mark_set.marks.iter_mut() { + *mark = mark.edit((start, end), text.chars().count()); + } + + mark_set.merge_touching(); + } + // Do removal if needed. if start != end { self.text.remove(start..end); @@ -41,6 +74,14 @@ impl Buffer { } } + pub fn undo(&mut self) { + todo!() + } + + pub fn redo(&mut self) { + todo!() + } + /// Creates a new empty mark set, and returns the set index. pub fn add_mark_set(&mut self) -> usize { self.mark_sets.push(MarkSet::new()); diff --git a/sub_crates/backend/src/history.rs b/sub_crates/backend/src/history.rs new file mode 100644 index 0000000..aafce83 --- /dev/null +++ b/sub_crates/backend/src/history.rs @@ -0,0 +1,45 @@ +#[derive(Debug, Clone)] +pub struct History { + edits: Vec, + position: usize, // Where we are in the history. +} + +impl History { + pub fn new() -> History { + History { + edits: Vec::new(), + position: 0, + } + } + + pub fn push_edit(&mut self, edit: Edit) { + self.edits.truncate(self.position); + self.edits.push(edit); + } + + pub fn undo(&mut self) -> Option<&Edit> { + if self.position > 0 { + self.position -= 1; + Some(&self.edits[self.position]) + } else { + None + } + } + + pub fn redo(&mut self) -> Option<&Edit> { + if self.position < self.edits.len() { + let edit = &self.edits[self.position]; + self.position += 1; + Some(edit) + } else { + None + } + } +} + +#[derive(Debug, Clone)] +pub struct Edit { + pub char_idx: usize, + pub from: String, + pub to: String, +} diff --git a/sub_crates/backend/src/lib.rs b/sub_crates/backend/src/lib.rs index 39d8765..545c468 100644 --- a/sub_crates/backend/src/lib.rs +++ b/sub_crates/backend/src/lib.rs @@ -4,5 +4,6 @@ extern crate unicode_segmentation; pub mod buffer; pub mod editor; pub mod hash; +pub mod history; pub mod marks; pub mod project; diff --git a/sub_crates/backend/src/marks.rs b/sub_crates/backend/src/marks.rs index 104650f..2c2bed5 100644 --- a/sub_crates/backend/src/marks.rs +++ b/sub_crates/backend/src/marks.rs @@ -43,6 +43,7 @@ impl Mark { } } + #[must_use] pub fn merge(&self, other: Mark) -> Mark { let r1 = self.range(); let r2 = other.range(); @@ -63,9 +64,44 @@ impl Mark { } } } + + /// Modify the mark based on an edit that occured to the text. + /// + /// `range` is the char range affected by the edit, and `new_len` is the + /// new length of that range after the edit. + /// + /// `range` must be correctly ordered. + #[must_use] + pub fn edit(&self, range: (usize, usize), new_len: usize) -> Mark { + assert!(range.0 <= range.1); + + // Head. + let head = if self.head > range.1 { + self.head + new_len - (range.1 - range.0) + } else if self.head >= range.0 { + range.0 + new_len + } else { + self.head + }; + + // Tail. + let tail = if self.tail > range.1 { + self.tail + new_len - (range.1 - range.0) + } else if self.tail >= range.0 { + range.0 + new_len + } else { + self.tail + }; + + Mark { + head: head, + tail: tail, + hh_pos: None, + } + } } -//-------------------------------------------------------------------------- +//---------------------------------------------------------------------- /// A set of disjoint Marks, sorted by position in the text. ///