From e534fe9112a91f850bacaf0d4f433cdccc914ac8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 9 Apr 2021 14:13:25 +0200 Subject: [PATCH] Add initial support for undo/redo stack We're still not capturing selections/anchors, that's right up next. --- zed/src/editor/buffer/mod.rs | 127 ++++++++++++++++++++++++++++++++++ zed/src/editor/buffer_view.rs | 14 ++++ 2 files changed, 141 insertions(+) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 827da6be9d..7523179af3 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -28,8 +28,11 @@ use std::{ path::PathBuf, str, sync::Arc, + time::{Duration, Instant}, }; +const UNDO_GROUP_INTERVAL: Duration = Duration::from_millis(300); + pub type SelectionSetId = time::Lamport; pub type SelectionsVersion = usize; @@ -65,6 +68,7 @@ pub struct Buffer { saved_version: time::Global, last_edit: time::Local, undo_map: UndoMap, + undo_history: UndoHistory, selections: HashMap>, pub selections_last_update: SelectionsVersion, deferred_ops: OperationQueue, @@ -126,6 +130,67 @@ impl UndoMap { } } +#[derive(Clone)] +struct EditGroup { + edits: Vec, + last_edit_at: Instant, +} + +#[derive(Clone)] +struct UndoHistory { + group_interval: Duration, + undo_stack: Vec, + redo_stack: Vec, +} + +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)] pub struct CharIter<'a> { fragments_cursor: Cursor<'a, Fragment, usize, usize>, @@ -307,6 +372,7 @@ impl Buffer { saved_version: time::Global::new(), last_edit: time::Local::default(), undo_map: Default::default(), + undo_history: UndoHistory::new(UNDO_GROUP_INTERVAL), selections: HashMap::default(), selections_last_update: 0, deferred_ops: OperationQueue::new(), @@ -494,6 +560,21 @@ impl Buffer { new_text: T, ctx: Option<&mut ModelContext>, ) -> Result> + where + I: IntoIterator>, + S: ToOffset, + T: Into, + { + self.edit_at(old_ranges, new_text, Instant::now(), ctx) + } + + fn edit_at( + &mut self, + old_ranges: I, + new_text: T, + now: Instant, + ctx: Option<&mut ModelContext>, + ) -> Result> where I: IntoIterator>, S: ToOffset, @@ -523,6 +604,7 @@ impl Buffer { for op in &ops { if let Operation::Edit { edit, .. } = op { self.edit_ops.insert(edit.id, edit.clone()); + self.undo_history.push(edit.id, now); } } @@ -931,6 +1013,50 @@ impl Buffer { Ok(()) } + pub fn undo(&mut self, ctx: Option<&mut ModelContext>) -> Vec { + 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::>(); + if !changes.is_empty() { + self.did_edit(changes, was_dirty, ctx); + } + } + + ops + } + + pub fn redo(&mut self, ctx: Option<&mut ModelContext>) -> Vec { + 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::>(); + if !changes.is_empty() { + self.did_edit(changes, was_dirty, ctx); + } + } + + ops + } + fn undo_or_redo(&mut self, edit_id: time::Local) -> Result { let undo = UndoOperation { id: self.local_clock.tick(), @@ -1580,6 +1706,7 @@ impl Clone for Buffer { saved_version: self.saved_version.clone(), last_edit: self.last_edit.clone(), undo_map: self.undo_map.clone(), + undo_history: self.undo_history.clone(), selections: self.selections.clone(), selections_last_update: self.selections_last_update.clone(), deferred_ops: self.deferred_ops.clone(), diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 6f3d6f108e..15be98dad3 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -28,6 +28,8 @@ pub fn init(app: &mut App) { app.add_bindings(vec![ Binding::new("backspace", "buffer:backspace", 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("down", "buffer:move_down", 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:newline", BufferView::newline); 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_down", BufferView::move_down); app.add_action("buffer:move_left", BufferView::move_left); @@ -435,6 +439,16 @@ impl BufferView { self.insert(&String::new(), ctx); } + pub fn undo(&mut self, _: &(), ctx: &mut ViewContext) { + self.buffer + .update(ctx, |buffer, ctx| buffer.undo(Some(ctx))); + } + + pub fn redo(&mut self, _: &(), ctx: &mut ViewContext) { + self.buffer + .update(ctx, |buffer, ctx| buffer.redo(Some(ctx))); + } + pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext) { { let app = ctx.app();