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.
This commit is contained in:
parent
62bfd9e49c
commit
0c887d1eb9
|
@ -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<MarkSet>, // 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());
|
||||
|
|
45
sub_crates/backend/src/history.rs
Normal file
45
sub_crates/backend/src/history.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct History {
|
||||
edits: Vec<Edit>,
|
||||
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,
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue
Block a user