Add initial support for undo/redo stack
We're still not capturing selections/anchors, that's right up next.
This commit is contained in:
parent
07b8a105a6
commit
e534fe9112
2 changed files with 141 additions and 0 deletions
|
@ -28,8 +28,11 @@ use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
str,
|
str,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const UNDO_GROUP_INTERVAL: Duration = Duration::from_millis(300);
|
||||||
|
|
||||||
pub type SelectionSetId = time::Lamport;
|
pub type SelectionSetId = time::Lamport;
|
||||||
pub type SelectionsVersion = usize;
|
pub type SelectionsVersion = usize;
|
||||||
|
|
||||||
|
@ -65,6 +68,7 @@ pub struct Buffer {
|
||||||
saved_version: time::Global,
|
saved_version: time::Global,
|
||||||
last_edit: time::Local,
|
last_edit: time::Local,
|
||||||
undo_map: UndoMap,
|
undo_map: UndoMap,
|
||||||
|
undo_history: UndoHistory,
|
||||||
selections: HashMap<SelectionSetId, Vec<Selection>>,
|
selections: HashMap<SelectionSetId, Vec<Selection>>,
|
||||||
pub selections_last_update: SelectionsVersion,
|
pub selections_last_update: SelectionsVersion,
|
||||||
deferred_ops: OperationQueue<Operation>,
|
deferred_ops: OperationQueue<Operation>,
|
||||||
|
@ -126,6 +130,67 @@ impl UndoMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct EditGroup {
|
||||||
|
edits: Vec<time::Local>,
|
||||||
|
last_edit_at: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct UndoHistory {
|
||||||
|
group_interval: Duration,
|
||||||
|
undo_stack: Vec<EditGroup>,
|
||||||
|
redo_stack: Vec<EditGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UndoHistory {
|
||||||
|
fn new(group_interval: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
group_interval,
|
||||||
|
undo_stack: Vec::new(),
|
||||||
|
redo_stack: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, edit_id: time::Local, now: Instant) {
|
||||||
|
self.redo_stack.clear();
|
||||||
|
if let Some(edit_group) = self.undo_stack.last_mut() {
|
||||||
|
if now - edit_group.last_edit_at <= self.group_interval {
|
||||||
|
edit_group.edits.push(edit_id);
|
||||||
|
edit_group.last_edit_at = now;
|
||||||
|
} else {
|
||||||
|
self.undo_stack.push(EditGroup {
|
||||||
|
edits: vec![edit_id],
|
||||||
|
last_edit_at: now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.undo_stack.push(EditGroup {
|
||||||
|
edits: vec![edit_id],
|
||||||
|
last_edit_at: now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_undo(&mut self) -> Option<&EditGroup> {
|
||||||
|
if let Some(edit_group) = self.undo_stack.pop() {
|
||||||
|
self.redo_stack.push(edit_group);
|
||||||
|
self.redo_stack.last()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_redo(&mut self) -> Option<&EditGroup> {
|
||||||
|
if let Some(edit_group) = self.redo_stack.pop() {
|
||||||
|
self.undo_stack.push(edit_group);
|
||||||
|
self.undo_stack.last()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CharIter<'a> {
|
pub struct CharIter<'a> {
|
||||||
fragments_cursor: Cursor<'a, Fragment, usize, usize>,
|
fragments_cursor: Cursor<'a, Fragment, usize, usize>,
|
||||||
|
@ -307,6 +372,7 @@ impl Buffer {
|
||||||
saved_version: time::Global::new(),
|
saved_version: time::Global::new(),
|
||||||
last_edit: time::Local::default(),
|
last_edit: time::Local::default(),
|
||||||
undo_map: Default::default(),
|
undo_map: Default::default(),
|
||||||
|
undo_history: UndoHistory::new(UNDO_GROUP_INTERVAL),
|
||||||
selections: HashMap::default(),
|
selections: HashMap::default(),
|
||||||
selections_last_update: 0,
|
selections_last_update: 0,
|
||||||
deferred_ops: OperationQueue::new(),
|
deferred_ops: OperationQueue::new(),
|
||||||
|
@ -494,6 +560,21 @@ impl Buffer {
|
||||||
new_text: T,
|
new_text: T,
|
||||||
ctx: Option<&mut ModelContext<Self>>,
|
ctx: Option<&mut ModelContext<Self>>,
|
||||||
) -> Result<Vec<Operation>>
|
) -> Result<Vec<Operation>>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Range<S>>,
|
||||||
|
S: ToOffset,
|
||||||
|
T: Into<Text>,
|
||||||
|
{
|
||||||
|
self.edit_at(old_ranges, new_text, Instant::now(), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_at<I, S, T>(
|
||||||
|
&mut self,
|
||||||
|
old_ranges: I,
|
||||||
|
new_text: T,
|
||||||
|
now: Instant,
|
||||||
|
ctx: Option<&mut ModelContext<Self>>,
|
||||||
|
) -> Result<Vec<Operation>>
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = Range<S>>,
|
I: IntoIterator<Item = Range<S>>,
|
||||||
S: ToOffset,
|
S: ToOffset,
|
||||||
|
@ -523,6 +604,7 @@ impl Buffer {
|
||||||
for op in &ops {
|
for op in &ops {
|
||||||
if let Operation::Edit { edit, .. } = op {
|
if let Operation::Edit { edit, .. } = op {
|
||||||
self.edit_ops.insert(edit.id, edit.clone());
|
self.edit_ops.insert(edit.id, edit.clone());
|
||||||
|
self.undo_history.push(edit.id, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -931,6 +1013,50 @@ impl Buffer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn undo(&mut self, ctx: Option<&mut ModelContext<Self>>) -> Vec<Operation> {
|
||||||
|
let was_dirty = self.is_dirty();
|
||||||
|
let old_version = self.version.clone();
|
||||||
|
|
||||||
|
let mut ops = Vec::new();
|
||||||
|
if let Some(edit_group) = self.undo_history.pop_undo() {
|
||||||
|
for edit_id in edit_group.edits.clone() {
|
||||||
|
ops.push(self.undo_or_redo(edit_id).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ctx) = ctx {
|
||||||
|
ctx.notify();
|
||||||
|
let changes = self.edits_since(old_version).collect::<Vec<_>>();
|
||||||
|
if !changes.is_empty() {
|
||||||
|
self.did_edit(changes, was_dirty, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self, ctx: Option<&mut ModelContext<Self>>) -> Vec<Operation> {
|
||||||
|
let was_dirty = self.is_dirty();
|
||||||
|
let old_version = self.version.clone();
|
||||||
|
|
||||||
|
let mut ops = Vec::new();
|
||||||
|
if let Some(edit_group) = self.undo_history.pop_redo() {
|
||||||
|
for edit_id in edit_group.edits.clone() {
|
||||||
|
ops.push(self.undo_or_redo(edit_id).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ctx) = ctx {
|
||||||
|
ctx.notify();
|
||||||
|
let changes = self.edits_since(old_version).collect::<Vec<_>>();
|
||||||
|
if !changes.is_empty() {
|
||||||
|
self.did_edit(changes, was_dirty, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
fn undo_or_redo(&mut self, edit_id: time::Local) -> Result<Operation> {
|
fn undo_or_redo(&mut self, edit_id: time::Local) -> Result<Operation> {
|
||||||
let undo = UndoOperation {
|
let undo = UndoOperation {
|
||||||
id: self.local_clock.tick(),
|
id: self.local_clock.tick(),
|
||||||
|
@ -1580,6 +1706,7 @@ impl Clone for Buffer {
|
||||||
saved_version: self.saved_version.clone(),
|
saved_version: self.saved_version.clone(),
|
||||||
last_edit: self.last_edit.clone(),
|
last_edit: self.last_edit.clone(),
|
||||||
undo_map: self.undo_map.clone(),
|
undo_map: self.undo_map.clone(),
|
||||||
|
undo_history: self.undo_history.clone(),
|
||||||
selections: self.selections.clone(),
|
selections: self.selections.clone(),
|
||||||
selections_last_update: self.selections_last_update.clone(),
|
selections_last_update: self.selections_last_update.clone(),
|
||||||
deferred_ops: self.deferred_ops.clone(),
|
deferred_ops: self.deferred_ops.clone(),
|
||||||
|
|
|
@ -28,6 +28,8 @@ pub fn init(app: &mut App) {
|
||||||
app.add_bindings(vec![
|
app.add_bindings(vec![
|
||||||
Binding::new("backspace", "buffer:backspace", Some("BufferView")),
|
Binding::new("backspace", "buffer:backspace", Some("BufferView")),
|
||||||
Binding::new("enter", "buffer:newline", Some("BufferView")),
|
Binding::new("enter", "buffer:newline", Some("BufferView")),
|
||||||
|
Binding::new("cmd-z", "buffer:undo", Some("BufferView")),
|
||||||
|
Binding::new("cmd-shift-Z", "buffer:redo", Some("BufferView")),
|
||||||
Binding::new("up", "buffer:move_up", Some("BufferView")),
|
Binding::new("up", "buffer:move_up", Some("BufferView")),
|
||||||
Binding::new("down", "buffer:move_down", Some("BufferView")),
|
Binding::new("down", "buffer:move_down", Some("BufferView")),
|
||||||
Binding::new("left", "buffer:move_left", Some("BufferView")),
|
Binding::new("left", "buffer:move_left", Some("BufferView")),
|
||||||
|
@ -52,6 +54,8 @@ pub fn init(app: &mut App) {
|
||||||
app.add_action("buffer:insert", BufferView::insert);
|
app.add_action("buffer:insert", BufferView::insert);
|
||||||
app.add_action("buffer:newline", BufferView::newline);
|
app.add_action("buffer:newline", BufferView::newline);
|
||||||
app.add_action("buffer:backspace", BufferView::backspace);
|
app.add_action("buffer:backspace", BufferView::backspace);
|
||||||
|
app.add_action("buffer:undo", BufferView::undo);
|
||||||
|
app.add_action("buffer:redo", BufferView::redo);
|
||||||
app.add_action("buffer:move_up", BufferView::move_up);
|
app.add_action("buffer:move_up", BufferView::move_up);
|
||||||
app.add_action("buffer:move_down", BufferView::move_down);
|
app.add_action("buffer:move_down", BufferView::move_down);
|
||||||
app.add_action("buffer:move_left", BufferView::move_left);
|
app.add_action("buffer:move_left", BufferView::move_left);
|
||||||
|
@ -435,6 +439,16 @@ impl BufferView {
|
||||||
self.insert(&String::new(), ctx);
|
self.insert(&String::new(), ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn undo(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
|
self.buffer
|
||||||
|
.update(ctx, |buffer, ctx| buffer.undo(Some(ctx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
|
self.buffer
|
||||||
|
.update(ctx, |buffer, ctx| buffer.redo(Some(ctx)));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
{
|
{
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue