From 1fe55d041f7f08029c69a6174dde786598d9326d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 5 Jun 2021 12:02:04 +0200 Subject: [PATCH 1/5] Speed up undoing multi-cursor changes --- zed/src/editor/buffer.rs | 46 ++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 68ddb22b5acb8b4c7a177a1375a4fb28f19847d4..7971327c33e44633c9b96d3da76909486d460278 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -1340,33 +1340,42 @@ impl Buffer { fn apply_undo(&mut self, undo: UndoOperation) -> Result<()> { self.undo_map.insert(undo); + let edit = &self.history.ops[&undo.edit_id]; - let version = Some(edit.version.clone()); + let mut cx = edit.version.clone(); + cx.observe(undo.edit_id); + let cx = Some(cx); let mut old_fragments = self.fragments.cursor::(); - old_fragments.seek(&VersionedOffset::Offset(0), Bias::Left, &version); - - let mut new_fragments = SumTree::new(); + let mut new_fragments = old_fragments.slice( + &VersionedOffset::Offset(edit.ranges[0].start), + Bias::Right, + &cx, + ); let mut new_ropes = RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + new_ropes.push_tree(new_fragments.summary().text); - for range in &edit.ranges { - let mut end_offset = old_fragments.end(&version).offset(); + let insertion_len = edit.new_text.as_ref().map_or(0, |i| i.len()); + for (ix, range) in edit.ranges.iter().enumerate() { + let delta = ix * insertion_len; + let mut end_offset = old_fragments.end(&cx).offset(); - if end_offset < range.start { + if end_offset < range.start + delta { let preceding_fragments = old_fragments.slice( - &VersionedOffset::Offset(range.start), - Bias::Left, - &version, + &VersionedOffset::Offset(range.start + delta), + Bias::Right, + &cx, ); new_ropes.push_tree(preceding_fragments.summary().text); new_fragments.push_tree(preceding_fragments, &None); } - while end_offset <= range.end { + while end_offset <= delta + range.end + insertion_len { if let Some(fragment) = old_fragments.item() { let mut fragment = fragment.clone(); let fragment_was_visible = fragment.visible; + if fragment.was_visible(&edit.version, &self.undo_map) || fragment.timestamp.local() == edit.timestamp.local() { @@ -1376,15 +1385,24 @@ impl Buffer { new_ropes.push_fragment(&fragment, fragment_was_visible); new_fragments.push(fragment, &None); - old_fragments.next(&version); - end_offset = old_fragments.end(&version).offset(); + old_fragments.next(&cx); + if end_offset == old_fragments.end(&cx).offset() { + let unseen_fragments = old_fragments.slice( + &VersionedOffset::Offset(end_offset), + Bias::Right, + &cx, + ); + new_ropes.push_tree(unseen_fragments.summary().text); + new_fragments.push_tree(unseen_fragments, &None); + } + end_offset = old_fragments.end(&cx).offset(); } else { break; } } } - let suffix = old_fragments.suffix(&version); + let suffix = old_fragments.suffix(&cx); new_ropes.push_tree(suffix.summary().text); new_fragments.push_tree(suffix, &None); From 2bf6fe773562620a1b398575d482d412d4a44502 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 5 Jun 2021 12:22:59 +0200 Subject: [PATCH 2/5] Group only transactions that are temporally contiguous --- zed/src/editor/buffer.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 7971327c33e44633c9b96d3da76909486d460278..049bf9156e59fe6cd6085ce7adde11268f148aad 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -137,6 +137,7 @@ struct SyntaxTree { #[derive(Clone)] struct Transaction { start: time::Global, + end: time::Global, buffer_was_dirty: bool, edits: Vec, selections_before: Option<(SelectionSetId, Arc<[Selection]>)>, @@ -182,7 +183,8 @@ impl History { self.transaction_depth += 1; if self.transaction_depth == 1 { self.undo_stack.push(Transaction { - start, + start: start.clone(), + end: start, buffer_was_dirty, edits: Vec::new(), selections_before: selections, @@ -217,10 +219,12 @@ impl History { if let Some(mut transaction) = transactions.next_back() { for prev_transaction in transactions.next_back() { if transaction.first_edit_at - prev_transaction.last_edit_at <= self.group_interval + && transaction.start == prev_transaction.end { prev_transaction.edits.append(&mut transaction.edits); prev_transaction.last_edit_at = transaction.last_edit_at; prev_transaction.selections_after = transaction.selections_after.take(); + prev_transaction.end = transaction.end.clone(); transaction = prev_transaction; new_len -= 1; } else { @@ -234,7 +238,9 @@ impl History { fn push_undo(&mut self, edit_id: time::Local) { assert_ne!(self.transaction_depth, 0); - self.undo_stack.last_mut().unwrap().edits.push(edit_id); + let last_transaction = self.undo_stack.last_mut().unwrap(); + last_transaction.edits.push(edit_id); + last_transaction.end.observe(edit_id); } fn pop_undo(&mut self) -> Option<&Transaction> { From 98ea7b3d64559ec5053dad7c5af8189f50a5b277 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 7 Jun 2021 15:38:24 +0200 Subject: [PATCH 3/5] Batch edits contained in undo operations Co-Authored-By: Nathan Sobo --- zed/src/editor/buffer.rs | 246 +++++++++++++++++++++++---------------- 1 file changed, 144 insertions(+), 102 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 049bf9156e59fe6cd6085ce7adde11268f148aad..26291372ad7e50b34986299d95d2edacef1f6afd 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -35,8 +35,6 @@ use std::{ time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; -const UNDO_GROUP_INTERVAL: Duration = Duration::from_millis(300); - #[derive(Clone, Default)] struct DeterministicState; @@ -134,18 +132,67 @@ struct SyntaxTree { version: time::Global, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Transaction { start: time::Global, end: time::Global, buffer_was_dirty: bool, edits: Vec, + ranges: Vec>, selections_before: Option<(SelectionSetId, Arc<[Selection]>)>, selections_after: Option<(SelectionSetId, Arc<[Selection]>)>, first_edit_at: Instant, last_edit_at: Instant, } +impl Transaction { + fn push_edit(&mut self, edit: &EditOperation) { + self.edits.push(edit.timestamp.local()); + self.end.observe(edit.timestamp.local()); + + let mut other_ranges = edit.ranges.iter().peekable(); + let mut new_ranges: Vec> = Vec::new(); + let insertion_len = edit.new_text.as_ref().map_or(0, |t| t.len()); + let mut delta = 0; + + for mut self_range in self.ranges.iter().cloned() { + self_range.start += delta; + self_range.end += delta; + + while let Some(other_range) = other_ranges.peek() { + let mut other_range = (*other_range).clone(); + other_range.start += delta; + other_range.end += delta; + + if other_range.start <= self_range.end { + other_ranges.next().unwrap(); + delta += insertion_len; + + if other_range.end < self_range.start { + new_ranges.push(other_range.start..other_range.end + insertion_len); + self_range.start += insertion_len; + self_range.end += insertion_len; + } else { + self_range.start = cmp::min(self_range.start, other_range.start); + self_range.end = cmp::max(self_range.end, other_range.end) + insertion_len; + } + } else { + break; + } + } + + new_ranges.push(self_range); + } + + for other_range in other_ranges { + new_ranges.push(other_range.start + delta..other_range.end + delta + insertion_len); + delta += insertion_len; + } + + self.ranges = new_ranges; + } +} + #[derive(Clone)] pub struct History { // TODO: Turn this into a String or Rope, maybe. @@ -165,7 +212,7 @@ impl History { undo_stack: Vec::new(), redo_stack: Vec::new(), transaction_depth: 0, - group_interval: UNDO_GROUP_INTERVAL, + group_interval: Duration::from_millis(300), } } @@ -181,12 +228,17 @@ impl History { now: Instant, ) { self.transaction_depth += 1; - if self.transaction_depth == 1 { + if self.transaction_depth == 1 + && self.undo_stack.last().map_or(true, |transaction| { + transaction.end != start || (now - transaction.last_edit_at) > self.group_interval + }) + { self.undo_stack.push(Transaction { start: start.clone(), end: start, buffer_was_dirty, edits: Vec::new(), + ranges: Vec::new(), selections_before: selections, selections_after: None, first_edit_at: now, @@ -212,35 +264,10 @@ impl History { } } - fn group(&mut self) { - let mut new_len = self.undo_stack.len(); - let mut transactions = self.undo_stack.iter_mut(); - - if let Some(mut transaction) = transactions.next_back() { - for prev_transaction in transactions.next_back() { - if transaction.first_edit_at - prev_transaction.last_edit_at <= self.group_interval - && transaction.start == prev_transaction.end - { - prev_transaction.edits.append(&mut transaction.edits); - prev_transaction.last_edit_at = transaction.last_edit_at; - prev_transaction.selections_after = transaction.selections_after.take(); - prev_transaction.end = transaction.end.clone(); - transaction = prev_transaction; - new_len -= 1; - } else { - break; - } - } - } - - self.undo_stack.truncate(new_len); - } - fn push_undo(&mut self, edit_id: time::Local) { assert_ne!(self.transaction_depth, 0); let last_transaction = self.undo_stack.last_mut().unwrap(); - last_transaction.edits.push(edit_id); - last_transaction.end.observe(edit_id); + last_transaction.push_edit(&self.ops[&edit_id]); } fn pop_undo(&mut self) -> Option<&Transaction> { @@ -265,11 +292,13 @@ impl History { } #[derive(Clone, Default, Debug)] -struct UndoMap(HashMap>); +struct UndoMap(HashMap>); impl UndoMap { - fn insert(&mut self, undo: UndoOperation) { - self.0.entry(undo.edit_id).or_default().push(undo); + fn insert(&mut self, undo: &UndoOperation) { + for (edit_id, count) in &undo.counts { + self.0.entry(*edit_id).or_default().push((undo.id, *count)); + } } fn is_undone(&self, edit_id: time::Local) -> bool { @@ -282,8 +311,8 @@ impl UndoMap { .get(&edit_id) .unwrap_or(&Vec::new()) .iter() - .filter(|undo| version.observed(undo.id)) - .map(|undo| undo.count) + .filter(|(undo_id, _)| version.observed(*undo_id)) + .map(|(_, undo_count)| *undo_count) .max() .unwrap_or(0); undo_count % 2 == 1 @@ -294,7 +323,7 @@ impl UndoMap { .get(&edit_id) .unwrap_or(&Vec::new()) .iter() - .map(|undo| undo.count) + .map(|(_, undo_count)| *undo_count) .max() .unwrap_or(0) } @@ -407,11 +436,12 @@ pub struct EditOperation { new_text: Option, } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct UndoOperation { id: time::Local, - edit_id: time::Local, - count: u32, + counts: HashMap, + ranges: Vec>, + version: time::Global, } impl Buffer { @@ -901,7 +931,6 @@ impl Buffer { if let Some(transaction) = self.history.end_transaction(selections, now) { let since = transaction.start.clone(); let was_dirty = transaction.buffer_was_dirty; - self.history.group(); if let Some(cx) = cx { cx.notify(); @@ -1108,7 +1137,7 @@ impl Buffer { lamport_timestamp, } => { if !self.version.observed(undo.id) { - self.apply_undo(undo)?; + self.apply_undo(&undo)?; self.version.observe(undo.id); self.lamport_clock.observe(lamport_timestamp); } @@ -1280,12 +1309,9 @@ impl Buffer { let old_version = self.version.clone(); let mut ops = Vec::new(); - if let Some(transaction) = self.history.pop_undo() { + if let Some(transaction) = self.history.pop_undo().cloned() { let selections = transaction.selections_before.clone(); - for edit_id in transaction.edits.clone() { - ops.push(self.undo_or_redo(edit_id).unwrap()); - } - + ops.push(self.undo_or_redo(transaction).unwrap()); if let Some((set_id, selections)) = selections { let _ = self.update_selection_set(set_id, selections, cx.as_deref_mut()); } @@ -1307,12 +1333,9 @@ impl Buffer { let old_version = self.version.clone(); let mut ops = Vec::new(); - if let Some(transaction) = self.history.pop_redo() { + if let Some(transaction) = self.history.pop_redo().cloned() { let selections = transaction.selections_after.clone(); - for edit_id in transaction.edits.clone() { - ops.push(self.undo_or_redo(edit_id).unwrap()); - } - + ops.push(self.undo_or_redo(transaction).unwrap()); if let Some((set_id, selections)) = selections { let _ = self.update_selection_set(set_id, selections, cx.as_deref_mut()); } @@ -1329,13 +1352,19 @@ impl Buffer { ops } - fn undo_or_redo(&mut self, edit_id: time::Local) -> Result { + fn undo_or_redo(&mut self, transaction: Transaction) -> Result { + let mut counts = HashMap::default(); + for edit_id in transaction.edits { + counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1); + } + let undo = UndoOperation { id: self.local_clock.tick(), - edit_id, - count: self.undo_map.undo_count(edit_id) + 1, + counts, + ranges: transaction.ranges, + version: transaction.start.clone(), }; - self.apply_undo(undo)?; + self.apply_undo(&undo)?; self.version.observe(undo.id); Ok(Operation::Undo { @@ -1344,17 +1373,18 @@ impl Buffer { }) } - fn apply_undo(&mut self, undo: UndoOperation) -> Result<()> { + fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { self.undo_map.insert(undo); - let edit = &self.history.ops[&undo.edit_id]; - let mut cx = edit.version.clone(); - cx.observe(undo.edit_id); + let mut cx = undo.version.clone(); + for edit_id in undo.counts.keys().copied() { + cx.observe(edit_id); + } let cx = Some(cx); let mut old_fragments = self.fragments.cursor::(); let mut new_fragments = old_fragments.slice( - &VersionedOffset::Offset(edit.ranges[0].start), + &VersionedOffset::Offset(undo.ranges[0].start), Bias::Right, &cx, ); @@ -1362,28 +1392,23 @@ impl Buffer { RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); new_ropes.push_tree(new_fragments.summary().text); - let insertion_len = edit.new_text.as_ref().map_or(0, |i| i.len()); - for (ix, range) in edit.ranges.iter().enumerate() { - let delta = ix * insertion_len; + for range in &undo.ranges { let mut end_offset = old_fragments.end(&cx).offset(); - if end_offset < range.start + delta { - let preceding_fragments = old_fragments.slice( - &VersionedOffset::Offset(range.start + delta), - Bias::Right, - &cx, - ); + if end_offset < range.start { + let preceding_fragments = + old_fragments.slice(&VersionedOffset::Offset(range.start), Bias::Right, &cx); new_ropes.push_tree(preceding_fragments.summary().text); new_fragments.push_tree(preceding_fragments, &None); } - while end_offset <= delta + range.end + insertion_len { + while end_offset <= range.end { if let Some(fragment) = old_fragments.item() { let mut fragment = fragment.clone(); let fragment_was_visible = fragment.visible; - if fragment.was_visible(&edit.version, &self.undo_map) - || fragment.timestamp.local() == edit.timestamp.local() + if fragment.was_visible(&undo.version, &self.undo_map) + || undo.counts.contains_key(&fragment.timestamp.local()) { fragment.visible = fragment.is_visible(&self.undo_map); fragment.max_undos.observe(undo.id); @@ -1441,7 +1466,7 @@ impl Buffer { } else { match op { Operation::Edit(edit) => self.version >= edit.version, - Operation::Undo { undo, .. } => self.version.observed(undo.edit_id), + Operation::Undo { undo, .. } => self.version >= undo.version, Operation::UpdateSelections { selections, .. } => { if let Some(selections) = selections { selections.iter().all(|selection| { @@ -2249,6 +2274,14 @@ mod tests { sync::atomic::{self, AtomicUsize}, }; + #[gpui::test] + fn test_transaction_push_edit(cx: &mut gpui::MutableAppContext) { + let buffer = cx.add_model(|cx| Buffer::new(0, "", cx)); + buffer.update(cx, |buf, cx| buf.edit(Some(0..0), "a", Some(cx))); + buffer.update(cx, |buf, cx| buf.edit(Some(1..1), "b", Some(cx))); + buffer.update(cx, |buf, cx| buf.edit(Some(2..2), "c", Some(cx))); + } + #[gpui::test] fn test_edit(cx: &mut gpui::MutableAppContext) { cx.add_model(|cx| { @@ -2345,6 +2378,7 @@ mod tests { .collect::(); cx.add_model(|cx| { let mut buffer = Buffer::new(0, reference_string.as_str(), cx); + buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); let mut buffer_versions = Vec::new(); log::info!( "buffer text {:?}, version: {:?}", @@ -2367,6 +2401,11 @@ mod tests { if rng.gen_bool(0.25) { buffer.randomly_undo_redo(rng); reference_string = buffer.text(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); } let range = buffer.random_byte_range(0, rng); @@ -2923,31 +2962,36 @@ mod tests { fn test_undo_redo(cx: &mut gpui::MutableAppContext) { cx.add_model(|cx| { let mut buffer = Buffer::new(0, "1234", cx); + // Set group interval to zero so as to not group edits in the undo stack. + buffer.history.group_interval = Duration::from_secs(0); - let edit1 = buffer.edit(vec![1..1], "abx", None).unwrap(); - let edit2 = buffer.edit(vec![3..4], "yzef", None).unwrap(); - let edit3 = buffer.edit(vec![3..5], "cd", None).unwrap(); + buffer.edit(vec![1..1], "abx", None).unwrap(); + buffer.edit(vec![3..4], "yzef", None).unwrap(); + buffer.edit(vec![3..5], "cd", None).unwrap(); assert_eq!(buffer.text(), "1abcdef234"); - buffer.undo_or_redo(edit1.edit_id().unwrap()).unwrap(); + let transactions = buffer.history.undo_stack.clone(); + assert_eq!(transactions.len(), 3); + + buffer.undo_or_redo(transactions[0].clone()).unwrap(); assert_eq!(buffer.text(), "1cdef234"); - buffer.undo_or_redo(edit1.edit_id().unwrap()).unwrap(); + buffer.undo_or_redo(transactions[0].clone()).unwrap(); assert_eq!(buffer.text(), "1abcdef234"); - buffer.undo_or_redo(edit2.edit_id().unwrap()).unwrap(); + buffer.undo_or_redo(transactions[1].clone()).unwrap(); assert_eq!(buffer.text(), "1abcdx234"); - buffer.undo_or_redo(edit3.edit_id().unwrap()).unwrap(); + buffer.undo_or_redo(transactions[2].clone()).unwrap(); assert_eq!(buffer.text(), "1abx234"); - buffer.undo_or_redo(edit2.edit_id().unwrap()).unwrap(); + buffer.undo_or_redo(transactions[1].clone()).unwrap(); assert_eq!(buffer.text(), "1abyzef234"); - buffer.undo_or_redo(edit3.edit_id().unwrap()).unwrap(); + buffer.undo_or_redo(transactions[2].clone()).unwrap(); assert_eq!(buffer.text(), "1abcdef234"); - buffer.undo_or_redo(edit3.edit_id().unwrap()).unwrap(); + buffer.undo_or_redo(transactions[2].clone()).unwrap(); assert_eq!(buffer.text(), "1abyzef234"); - buffer.undo_or_redo(edit1.edit_id().unwrap()).unwrap(); + buffer.undo_or_redo(transactions[0].clone()).unwrap(); assert_eq!(buffer.text(), "1yzef234"); - buffer.undo_or_redo(edit2.edit_id().unwrap()).unwrap(); + buffer.undo_or_redo(transactions[1].clone()).unwrap(); assert_eq!(buffer.text(), "1234"); buffer @@ -2981,7 +3025,7 @@ mod tests { assert_eq!(buffer.text(), "12cde6"); assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); - now += UNDO_GROUP_INTERVAL + Duration::from_millis(1); + now += buffer.history.group_interval + Duration::from_millis(1); buffer.start_transaction_at(Some(set_id), now).unwrap(); buffer .update_selection_set( @@ -3092,7 +3136,11 @@ mod tests { let mut buffers = Vec::new(); let mut network = Network::new(); for i in 0..peers { - let buffer = cx.add_model(|cx| Buffer::new(i as ReplicaId, base_text.as_str(), cx)); + let buffer = cx.add_model(|cx| { + let mut buf = Buffer::new(i as ReplicaId, base_text.as_str(), cx); + buf.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); + buf + }); buffers.push(buffer); replica_ids.push(i as u16); network.add_peer(i as u16); @@ -3389,7 +3437,7 @@ mod tests { where T: Rng, { - let (old_ranges, new_text, operation) = self.randomly_edit(rng, 5, cx.as_deref_mut()); + let (old_ranges, new_text, operation) = self.randomly_edit(rng, 2, cx.as_deref_mut()); let mut operations = Vec::from_iter(operation); // Randomly add, remove or mutate selection sets. @@ -3424,9 +3472,13 @@ mod tests { pub fn randomly_undo_redo(&mut self, rng: &mut impl Rng) -> Vec { let mut ops = Vec::new(); for _ in 0..rng.gen_range(1..=5) { - if let Some(edit_id) = self.history.ops.keys().choose(rng).copied() { - log::info!("undoing buffer {} operation {:?}", self.replica_id, edit_id); - ops.push(self.undo_or_redo(edit_id).unwrap()); + if let Some(transaction) = self.history.undo_stack.choose(rng).cloned() { + log::info!( + "undoing buffer {} transaction {:?}", + self.replica_id, + transaction + ); + ops.push(self.undo_or_redo(transaction).unwrap()); } } ops @@ -3505,14 +3557,4 @@ mod tests { }) } } - - impl Operation { - fn edit_id(&self) -> Option { - match self { - Operation::Edit(edit) => Some(edit.timestamp.local()), - Operation::Undo { undo, .. } => Some(undo.edit_id), - Operation::UpdateSelections { .. } => None, - } - } - } } From 467e1a44d39b3f65f993972c0c46b82e9b802392 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 8 Jun 2021 11:51:14 +0200 Subject: [PATCH 4/5] Restore `History::group` --- zed/src/editor/buffer.rs | 42 +++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 26291372ad7e50b34986299d95d2edacef1f6afd..7b4f8f4e9a36768c22677f2c3864095a6b476e74 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -228,11 +228,7 @@ impl History { now: Instant, ) { self.transaction_depth += 1; - if self.transaction_depth == 1 - && self.undo_stack.last().map_or(true, |transaction| { - transaction.end != start || (now - transaction.last_edit_at) > self.group_interval - }) - { + if self.transaction_depth == 1 { self.undo_stack.push(Transaction { start: start.clone(), end: start, @@ -264,6 +260,41 @@ impl History { } } + fn group(&mut self) { + let mut new_len = self.undo_stack.len(); + let mut transactions = self.undo_stack.iter_mut(); + + if let Some(mut transaction) = transactions.next_back() { + while let Some(prev_transaction) = transactions.next_back() { + if transaction.first_edit_at - prev_transaction.last_edit_at <= self.group_interval + && transaction.start == prev_transaction.end + { + transaction = prev_transaction; + new_len -= 1; + } else { + break; + } + } + } + + let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len); + if let Some(last_transaction) = transactions_to_keep.last_mut() { + for transaction in &*transactions_to_merge { + for edit_id in &transaction.edits { + last_transaction.push_edit(&self.ops[edit_id]); + } + } + + if let Some(transaction) = transactions_to_merge.last_mut() { + last_transaction.last_edit_at = transaction.last_edit_at; + last_transaction.selections_after = transaction.selections_after.take(); + last_transaction.end = transaction.end.clone(); + } + } + + self.undo_stack.truncate(new_len); + } + fn push_undo(&mut self, edit_id: time::Local) { assert_ne!(self.transaction_depth, 0); let last_transaction = self.undo_stack.last_mut().unwrap(); @@ -931,6 +962,7 @@ impl Buffer { if let Some(transaction) = self.history.end_transaction(selections, now) { let since = transaction.start.clone(); let was_dirty = transaction.buffer_was_dirty; + self.history.group(); if let Some(cx) = cx { cx.notify(); From 072a57580d38c646728139ec36d1fcda7f8cc5e9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 8 Jun 2021 11:55:17 +0200 Subject: [PATCH 5/5] Delete leftover test --- zed/src/editor/buffer.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 7b4f8f4e9a36768c22677f2c3864095a6b476e74..6b8a4d247fa5b873ca4683c872e65acca68c49c7 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -2306,14 +2306,6 @@ mod tests { sync::atomic::{self, AtomicUsize}, }; - #[gpui::test] - fn test_transaction_push_edit(cx: &mut gpui::MutableAppContext) { - let buffer = cx.add_model(|cx| Buffer::new(0, "", cx)); - buffer.update(cx, |buf, cx| buf.edit(Some(0..0), "a", Some(cx))); - buffer.update(cx, |buf, cx| buf.edit(Some(1..1), "b", Some(cx))); - buffer.update(cx, |buf, cx| buf.edit(Some(2..2), "c", Some(cx))); - } - #[gpui::test] fn test_edit(cx: &mut gpui::MutableAppContext) { cx.add_model(|cx| {