From 1fe55d041f7f08029c69a6174dde786598d9326d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 5 Jun 2021 12:02:04 +0200 Subject: [PATCH 01/15] 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 02/15] 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 03/15] 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 04/15] 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 05/15] 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| { From 71f50ce361655ce3e9e28a36b187624a23aae095 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 9 Jun 2021 20:51:14 +0200 Subject: [PATCH 06/15] Restore random edits in test to 5 --- zed/src/editor/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 6b8a4d247fa5b873ca4683c872e65acca68c49c7..97eb555be4e395c30f916478d72eb70f41f0c667 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -3461,7 +3461,7 @@ mod tests { where T: Rng, { - let (old_ranges, new_text, operation) = self.randomly_edit(rng, 2, cx.as_deref_mut()); + let (old_ranges, new_text, operation) = self.randomly_edit(rng, 5, cx.as_deref_mut()); let mut operations = Vec::from_iter(operation); // Randomly add, remove or mutate selection sets. From b5cb41c7f03d35167ec640663032f3d362c9aa05 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 9 Jun 2021 15:34:45 -0600 Subject: [PATCH 07/15] Open a new workspace on File > New if none exists --- zed/src/menus.rs | 2 +- zed/src/workspace.rs | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/zed/src/menus.rs b/zed/src/menus.rs index e9d50c78244b94cc75a62ad097471c8e8457e3cc..da053348f198868b0067830d980ff58b482cff6e 100644 --- a/zed/src/menus.rs +++ b/zed/src/menus.rs @@ -29,7 +29,7 @@ pub fn menus(state: AppState) -> Vec> { name: "New", keystroke: Some("cmd-n"), action: "workspace:new_file", - arg: None, + arg: Some(Box::new(state.clone())), }, MenuItem::Separator, MenuItem::Action { diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 36ef65b1747689564aef5e776c5bbb70e69d95c0..19932d122c77b61c82c6b2f586e01054bad66b67 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -29,6 +29,7 @@ use std::{ pub fn init(cx: &mut MutableAppContext) { cx.add_global_action("workspace:open", open); cx.add_global_action("workspace:open_paths", open_paths); + cx.add_global_action("workspace:new_file", open_new); cx.add_global_action("app:quit", quit); cx.add_action("workspace:save", Workspace::save_active_item); cx.add_action("workspace:debug_elements", Workspace::debug_elements); @@ -98,6 +99,19 @@ fn open_paths(params: &OpenParams, cx: &mut MutableAppContext) { }); } +fn open_new(app_state: &AppState, cx: &mut MutableAppContext) { + cx.add_window(|cx| { + let mut view = Workspace::new( + 0, + app_state.settings.clone(), + app_state.language_registry.clone(), + cx, + ); + view.open_new_file(&app_state, cx); + view + }); +} + fn quit(_: &(), cx: &mut MutableAppContext) { cx.platform().quit(); } @@ -449,7 +463,7 @@ impl Workspace { } } - pub fn open_new_file(&mut self, _: &(), cx: &mut ViewContext) { + pub fn open_new_file(&mut self, _: &AppState, cx: &mut ViewContext) { let buffer = cx.add_model(|cx| Buffer::new(self.replica_id, "", cx)); let buffer_view = cx.add_view(|cx| Editor::for_buffer(buffer.clone(), self.settings.clone(), cx)); From 66c76d5469bc0ec113848b6dbfffd15ffa257cc7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 9 Jun 2021 16:38:32 -0600 Subject: [PATCH 08/15] Test creating a new empty workspace and fix test compile errors --- gpui/src/app.rs | 6 ++++++ zed/src/workspace.rs | 28 +++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index b11f5c63da91f9365f0e7aab957399458ece87c7..296eb50f10c41170beb5434ef30ca84eb00e32cc 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -264,6 +264,12 @@ impl TestAppContext { ); } + pub fn dispatch_global_action(&self, name: &str, arg: T) { + self.0 + .borrow_mut() + .dispatch_global_action(name, arg); + } + pub fn dispatch_keystroke( &self, window_id: usize, diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 19932d122c77b61c82c6b2f586e01054bad66b67..04d1b0f5a13d5868cd346bfe77325432fb140fb3 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -1085,9 +1085,10 @@ mod tests { async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) { let dir = TempDir::new("test-new-file").unwrap(); let app_state = cx.read(build_app_state); + let app_state2 = app_state.clone(); let (_, workspace) = cx.add_window(|cx| { let mut workspace = - Workspace::new(0, app_state.settings, app_state.language_registry, cx); + Workspace::new(0, app_state2.settings, app_state2.language_registry, cx); workspace.add_worktree(dir.path(), cx); workspace }); @@ -1103,8 +1104,9 @@ mod tests { tree.flush_fs_events(&cx).await; // Create a new untitled buffer + let app_state2 = app_state.clone(); let editor = workspace.update(&mut cx, |workspace, cx| { - workspace.open_new_file(&(), cx); + workspace.open_new_file(&app_state2, cx); workspace .active_item(cx) .unwrap() @@ -1112,6 +1114,7 @@ mod tests { .downcast::() .unwrap() }); + editor.update(&mut cx, |editor, cx| { assert!(!editor.is_dirty(cx.as_ref())); assert_eq!(editor.title(cx.as_ref()), "untitled"); @@ -1154,7 +1157,7 @@ mod tests { // Open the same newly-created file in another pane item. The new editor should reuse // the same buffer. workspace.update(&mut cx, |workspace, cx| { - workspace.open_new_file(&(), cx); + workspace.open_new_file(&app_state, cx); workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); assert!(workspace .open_entry((tree.id(), Path::new("the-new-name").into()), cx) @@ -1173,6 +1176,25 @@ mod tests { }) } + #[gpui::test] + async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) { + cx.update(init); + + let app_state = cx.read(build_app_state); + cx.dispatch_global_action("workspace:new_file", app_state); + let window_id = *cx.window_ids().first().unwrap(); + let workspace = cx.root_view::(window_id).unwrap(); + workspace.update(&mut cx, |workspace, cx| { + let editor = workspace + .active_item(cx) + .unwrap() + .to_any() + .downcast::() + .unwrap(); + assert!(editor.read(cx).text(cx.as_ref()).is_empty()); + }); + } + #[gpui::test] async fn test_pane_actions(mut cx: gpui::TestAppContext) { cx.update(|cx| pane::init(cx)); From f294bfdbd94705ee4023a6af2fe93900da2d0f30 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Jun 2021 13:42:43 +0200 Subject: [PATCH 09/15] Introduce `Cursor::seek_start` to compute `VersionedOffset` once --- zed/src/editor/buffer.rs | 14 ++++++-------- zed/src/sum_tree.rs | 12 ------------ zed/src/sum_tree/cursor.rs | 4 ++++ 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 97eb555be4e395c30f916478d72eb70f41f0c667..566446a4e6d068767dab2f7b82e03bef2f632aca 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -1679,9 +1679,7 @@ impl Buffer { bias, version, } => { - let mut cursor = self - .fragments - .cursor::(); + let mut cursor = self.fragments.cursor::(); cursor.seek( &VersionedOffset::Offset(*offset), *bias, @@ -1689,12 +1687,12 @@ impl Buffer { ); let fragment = cursor.item().unwrap(); let overshoot = if fragment.visible { - offset - cursor.start().0.offset() + offset - cursor.seek_start().offset() } else { 0 }; - self.text_summary_for_range(0..cursor.start().1 + overshoot) + self.text_summary_for_range(0..cursor.start() + overshoot) } } } @@ -1713,14 +1711,14 @@ impl Buffer { } => { let mut cursor = self .fragments - .cursor::(); + .cursor::(); cursor.seek( &VersionedOffset::Offset(*offset), *bias, &Some(version.clone()), ); - let overshoot = offset - cursor.start().0.offset(); - let summary = cursor.start().1; + let overshoot = offset - cursor.seek_start().offset(); + let summary = cursor.start(); summary.visible + summary.deleted + overshoot } } diff --git a/zed/src/sum_tree.rs b/zed/src/sum_tree.rs index e307d2668b5c704ffdc1017570f03198c2bb8715..1cadf1a4d47ea81fdc3500a8fb7dd195175f5639 100644 --- a/zed/src/sum_tree.rs +++ b/zed/src/sum_tree.rs @@ -37,18 +37,6 @@ impl<'a, T: Summary> Dimension<'a, T> for () { fn add_summary(&mut self, _: &'a T, _: &T::Context) {} } -impl<'a, S, D1, D2> Dimension<'a, S> for (D1, D2) -where - S: Summary, - D1: Dimension<'a, S>, - D2: Dimension<'a, S>, -{ - fn add_summary(&mut self, summary: &'a S, cx: &S::Context) { - self.0.add_summary(summary, cx); - self.1.add_summary(summary, cx); - } -} - pub trait SeekDimension<'a, T: Summary>: Dimension<'a, T> { fn cmp(&self, other: &Self, cx: &T::Context) -> Ordering; } diff --git a/zed/src/sum_tree/cursor.rs b/zed/src/sum_tree/cursor.rs index 8374546a20f112035e139e35d714e3fd36ebdcb3..6d441133a2e9c51d278c9469f965d7dfeae5ebd9 100644 --- a/zed/src/sum_tree/cursor.rs +++ b/zed/src/sum_tree/cursor.rs @@ -45,6 +45,10 @@ where self.sum_dimension = U::default(); } + pub fn seek_start(&self) -> &S { + &self.seek_dimension + } + pub fn start(&self) -> &U { &self.sum_dimension } From dc2805bb14700d3bb2c006befb95b967a3c5e5bf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Jun 2021 13:55:15 +0200 Subject: [PATCH 10/15] Add `Cursor::seek_end` and audit the codebase to use `seek_start` more --- zed/src/editor/buffer/rope.rs | 52 +++++++-------- zed/src/editor/display_map/fold_map.rs | 88 ++++++++++++-------------- zed/src/sum_tree/cursor.rs | 10 +++ zed/src/worktree.rs | 8 +-- 4 files changed, 82 insertions(+), 76 deletions(-) diff --git a/zed/src/editor/buffer/rope.rs b/zed/src/editor/buffer/rope.rs index 268924c158864952feed9f759fbc1e03eb8ea70f..565c4a173db5bb3225ef6f432feb8aeff2acfeb1 100644 --- a/zed/src/editor/buffer/rope.rs +++ b/zed/src/editor/buffer/rope.rs @@ -128,10 +128,10 @@ impl Rope { pub fn to_point(&self, offset: usize) -> Point { assert!(offset <= self.summary().bytes); - let mut cursor = self.chunks.cursor::(); + let mut cursor = self.chunks.cursor::(); cursor.seek(&offset, Bias::Left, &()); - let overshoot = offset - cursor.start().bytes; - cursor.start().lines + let overshoot = offset - cursor.seek_start(); + *cursor.start() + cursor .item() .map_or(Point::zero(), |chunk| chunk.to_point(overshoot)) @@ -139,17 +139,17 @@ impl Rope { pub fn to_offset(&self, point: Point) -> usize { assert!(point <= self.summary().lines); - let mut cursor = self.chunks.cursor::(); + let mut cursor = self.chunks.cursor::(); cursor.seek(&point, Bias::Left, &()); - let overshoot = point - cursor.start().lines; - cursor.start().bytes + cursor.item().map_or(0, |chunk| chunk.to_offset(overshoot)) + let overshoot = point - cursor.seek_start(); + cursor.start() + cursor.item().map_or(0, |chunk| chunk.to_offset(overshoot)) } pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize { - let mut cursor = self.chunks.cursor::(); + let mut cursor = self.chunks.cursor::(); cursor.seek(&offset, Bias::Left, &()); if let Some(chunk) = cursor.item() { - let mut ix = offset - cursor.start(); + let mut ix = offset - cursor.seek_start(); while !chunk.0.is_char_boundary(ix) { match bias { Bias::Left => { @@ -169,11 +169,11 @@ impl Rope { } pub fn clip_point(&self, point: Point, bias: Bias) -> Point { - let mut cursor = self.chunks.cursor::(); + let mut cursor = self.chunks.cursor::(); cursor.seek(&point, Bias::Right, &()); if let Some(chunk) = cursor.item() { - let overshoot = point - cursor.start(); - *cursor.start() + chunk.clip_point(overshoot, bias) + let overshoot = point - cursor.seek_start(); + *cursor.seek_start() + chunk.clip_point(overshoot, bias) } else { self.summary().lines } @@ -190,7 +190,7 @@ impl<'a> From<&'a str> for Rope { pub struct Cursor<'a> { rope: &'a Rope, - chunks: sum_tree::Cursor<'a, Chunk, usize, usize>, + chunks: sum_tree::Cursor<'a, Chunk, usize, ()>, offset: usize, } @@ -222,18 +222,18 @@ impl<'a> Cursor<'a> { let mut slice = Rope::new(); if let Some(start_chunk) = self.chunks.item() { - let start_ix = self.offset - self.chunks.start(); - let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start(); + let start_ix = self.offset - self.chunks.seek_start(); + let end_ix = cmp::min(end_offset, self.chunks.seek_end(&())) - self.chunks.seek_start(); slice.push(&start_chunk.0[start_ix..end_ix]); } - if end_offset > self.chunks.end(&()) { + if end_offset > self.chunks.seek_end(&()) { self.chunks.next(&()); slice.append(Rope { chunks: self.chunks.slice(&end_offset, Bias::Right, &()), }); if let Some(end_chunk) = self.chunks.item() { - let end_ix = end_offset - self.chunks.start(); + let end_ix = end_offset - self.chunks.seek_start(); slice.push(&end_chunk.0[..end_ix]); } } @@ -247,16 +247,16 @@ impl<'a> Cursor<'a> { let mut summary = TextSummary::default(); if let Some(start_chunk) = self.chunks.item() { - let start_ix = self.offset - self.chunks.start(); - let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start(); + let start_ix = self.offset - self.chunks.seek_start(); + let end_ix = cmp::min(end_offset, self.chunks.seek_end(&())) - self.chunks.seek_start(); summary = TextSummary::from(&start_chunk.0[start_ix..end_ix]); } - if end_offset > self.chunks.end(&()) { + if end_offset > self.chunks.seek_end(&()) { self.chunks.next(&()); summary += &self.chunks.summary(&end_offset, Bias::Right, &()); if let Some(end_chunk) = self.chunks.item() { - let end_ix = end_offset - self.chunks.start(); + let end_ix = end_offset - self.chunks.seek_start(); summary += TextSummary::from(&end_chunk.0[..end_ix]); } } @@ -274,7 +274,7 @@ impl<'a> Cursor<'a> { } pub struct Chunks<'a> { - chunks: sum_tree::Cursor<'a, Chunk, usize, usize>, + chunks: sum_tree::Cursor<'a, Chunk, usize, ()>, range: Range, } @@ -286,11 +286,11 @@ impl<'a> Chunks<'a> { } pub fn offset(&self) -> usize { - self.range.start.max(*self.chunks.start()) + self.range.start.max(*self.chunks.seek_start()) } pub fn seek(&mut self, offset: usize) { - if offset >= self.chunks.end(&()) { + if offset >= self.chunks.seek_end(&()) { self.chunks.seek_forward(&offset, Bias::Right, &()); } else { self.chunks.seek(&offset, Bias::Right, &()); @@ -300,10 +300,10 @@ impl<'a> Chunks<'a> { pub fn peek(&self) -> Option<&'a str> { if let Some(chunk) = self.chunks.item() { - let offset = *self.chunks.start(); + let offset = *self.chunks.seek_start(); if self.range.end > offset { - let start = self.range.start.saturating_sub(*self.chunks.start()); - let end = self.range.end - self.chunks.start(); + let start = self.range.start.saturating_sub(*self.chunks.seek_start()); + let end = self.range.end - self.chunks.seek_start(); return Some(&chunk.0[start..chunk.0.len().min(end)]); } } diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 39b7b91b13230546126396480d601270da8c1ba8..efea294d0914a4de5ad8630b368bbc3224a7787a 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -210,20 +210,20 @@ impl FoldMap { let buffer = self.buffer.read(cx); let offset = offset.to_offset(buffer); let transforms = self.sync(cx); - let mut cursor = transforms.cursor::(); + let mut cursor = transforms.cursor::(); cursor.seek(&offset, Bias::Right, &()); cursor.item().map_or(false, |t| t.display_text.is_some()) } pub fn is_line_folded(&self, display_row: u32, cx: &AppContext) -> bool { let transforms = self.sync(cx); - let mut cursor = transforms.cursor::(); + let mut cursor = transforms.cursor::(); cursor.seek(&DisplayPoint::new(display_row, 0), Bias::Right, &()); while let Some(transform) = cursor.item() { if transform.display_text.is_some() { return true; } - if cursor.end(&()).row() == display_row { + if cursor.seek_end(&()).row() == display_row { cursor.next(&()) } else { break; @@ -242,21 +242,18 @@ impl FoldMap { pub fn to_buffer_point(&self, display_point: DisplayPoint, cx: &AppContext) -> Point { let transforms = self.sync(cx); - let mut cursor = transforms.cursor::(); + let mut cursor = transforms.cursor::(); cursor.seek(&display_point, Bias::Right, &()); - let overshoot = display_point.0 - cursor.start().display.lines; - cursor.start().buffer.lines + overshoot + let overshoot = display_point.0 - cursor.seek_start().0; + *cursor.start() + overshoot } pub fn to_display_point(&self, point: Point, cx: &AppContext) -> DisplayPoint { let transforms = self.sync(cx); - let mut cursor = transforms.cursor::(); + let mut cursor = transforms.cursor::(); cursor.seek(&point, Bias::Right, &()); - let overshoot = point - cursor.start().buffer.lines; - DisplayPoint(cmp::min( - cursor.start().display.lines + overshoot, - cursor.end(&()).display.lines, - )) + let overshoot = point - cursor.seek_start(); + DisplayPoint(cmp::min(cursor.start().0 + overshoot, cursor.end(&()).0)) } fn sync(&self, cx: &AppContext) -> MutexGuard> { @@ -275,20 +272,20 @@ impl FoldMap { let mut new_transforms = SumTree::new(); let mut transforms = self.transforms.lock(); - let mut cursor = transforms.cursor::(); + let mut cursor = transforms.cursor::(); cursor.seek(&0, Bias::Right, &()); while let Some(mut edit) = edits.next() { new_transforms.push_tree(cursor.slice(&edit.old_range.start, Bias::Left, &()), &()); - edit.new_range.start -= edit.old_range.start - cursor.start(); - edit.old_range.start = *cursor.start(); + edit.new_range.start -= edit.old_range.start - cursor.seek_start(); + edit.old_range.start = *cursor.seek_start(); cursor.seek(&edit.old_range.end, Bias::Right, &()); cursor.next(&()); let mut delta = edit.delta(); loop { - edit.old_range.end = *cursor.start(); + edit.old_range.end = *cursor.seek_start(); if let Some(next_edit) = edits.peek() { if next_edit.old_range.start > edit.old_range.end { @@ -443,10 +440,10 @@ impl FoldMapSnapshot { } pub fn chunks_at(&self, offset: DisplayOffset) -> Chunks { - let mut transform_cursor = self.transforms.cursor::(); + let mut transform_cursor = self.transforms.cursor::(); transform_cursor.seek(&offset, Bias::Right, &()); - let overshoot = offset.0 - transform_cursor.start().display.bytes; - let buffer_offset = transform_cursor.start().buffer.bytes + overshoot; + let overshoot = offset.0 - transform_cursor.seek_start().0; + let buffer_offset = transform_cursor.start() + overshoot; Chunks { transform_cursor, buffer_offset, @@ -455,15 +452,15 @@ impl FoldMapSnapshot { } pub fn highlighted_chunks(&mut self, range: Range) -> HighlightedChunks { - let mut transform_cursor = self.transforms.cursor::(); + let mut transform_cursor = self.transforms.cursor::(); transform_cursor.seek(&range.end, Bias::Right, &()); - let overshoot = range.end.0 - transform_cursor.start().display.bytes; - let buffer_end = transform_cursor.start().buffer.bytes + overshoot; + let overshoot = range.end.0 - transform_cursor.seek_start().0; + let buffer_end = transform_cursor.start() + overshoot; transform_cursor.seek(&range.start, Bias::Right, &()); - let overshoot = range.start.0 - transform_cursor.start().display.bytes; - let buffer_start = transform_cursor.start().buffer.bytes + overshoot; + let overshoot = range.start.0 - transform_cursor.seek_start().0; + let buffer_start = transform_cursor.start() + overshoot; HighlightedChunks { transform_cursor, @@ -497,28 +494,27 @@ impl FoldMapSnapshot { } pub fn to_buffer_offset(&self, point: DisplayPoint) -> usize { - let mut cursor = self.transforms.cursor::(); + let mut cursor = self.transforms.cursor::(); cursor.seek(&point, Bias::Right, &()); - let overshoot = point.0 - cursor.start().display.lines; - self.buffer - .to_offset(cursor.start().buffer.lines + overshoot) + let overshoot = point.0 - cursor.seek_start().0; + self.buffer.to_offset(*cursor.start() + overshoot) } #[cfg(test)] pub fn clip_offset(&self, offset: DisplayOffset, bias: Bias) -> DisplayOffset { - let mut cursor = self.transforms.cursor::(); + let mut cursor = self.transforms.cursor::(); cursor.seek(&offset, Bias::Right, &()); if let Some(transform) = cursor.item() { - let transform_start = cursor.start().display.bytes; + let transform_start = cursor.seek_start().0; if transform.display_text.is_some() { if offset.0 == transform_start || matches!(bias, Bias::Left) { DisplayOffset(transform_start) } else { - DisplayOffset(cursor.end(&()).display.bytes) + DisplayOffset(cursor.seek_end(&()).0) } } else { let overshoot = offset.0 - transform_start; - let buffer_offset = cursor.start().buffer.bytes + overshoot; + let buffer_offset = cursor.start() + overshoot; let clipped_buffer_offset = self.buffer.clip_offset(buffer_offset, bias); DisplayOffset( (offset.0 as isize + (clipped_buffer_offset as isize - buffer_offset as isize)) @@ -531,19 +527,19 @@ impl FoldMapSnapshot { } pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint { - let mut cursor = self.transforms.cursor::(); + let mut cursor = self.transforms.cursor::(); cursor.seek(&point, Bias::Right, &()); if let Some(transform) = cursor.item() { - let transform_start = cursor.start().display.lines; + let transform_start = cursor.seek_start().0; if transform.display_text.is_some() { if point.0 == transform_start || matches!(bias, Bias::Left) { DisplayPoint(transform_start) } else { - DisplayPoint(cursor.end(&()).display.lines) + DisplayPoint(cursor.seek_end(&()).0) } } else { let overshoot = point.0 - transform_start; - let buffer_position = cursor.start().buffer.lines + overshoot; + let buffer_position = *cursor.start() + overshoot; let clipped_buffer_position = self.buffer.clip_point(buffer_position, bias); DisplayPoint::new( point.row(), @@ -681,7 +677,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { } pub struct BufferRows<'a> { - cursor: Cursor<'a, Transform, DisplayPoint, TransformSummary>, + cursor: Cursor<'a, Transform, DisplayPoint, Point>, display_point: Point, } @@ -689,7 +685,7 @@ impl<'a> Iterator for BufferRows<'a> { type Item = u32; fn next(&mut self) -> Option { - while self.display_point > self.cursor.end(&()).display.lines { + while self.display_point > self.cursor.seek_end(&()).0 { self.cursor.next(&()); if self.cursor.item().is_none() { // TODO: Return a bool from next? @@ -698,8 +694,8 @@ impl<'a> Iterator for BufferRows<'a> { } if self.cursor.item().is_some() { - let overshoot = self.display_point - self.cursor.start().display.lines; - let buffer_point = self.cursor.start().buffer.lines + overshoot; + let overshoot = self.display_point - self.cursor.seek_start().0; + let buffer_point = *self.cursor.start() + overshoot; self.display_point.row += 1; Some(buffer_point.row) } else { @@ -709,7 +705,7 @@ impl<'a> Iterator for BufferRows<'a> { } pub struct Chunks<'a> { - transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, + transform_cursor: Cursor<'a, Transform, DisplayOffset, usize>, buffer_chunks: buffer::Chunks<'a>, buffer_offset: usize, } @@ -730,7 +726,7 @@ impl<'a> Iterator for Chunks<'a> { self.buffer_offset += transform.summary.buffer.bytes; self.buffer_chunks.seek(self.buffer_offset); - while self.buffer_offset >= self.transform_cursor.end(&()).buffer.bytes + while self.buffer_offset >= self.transform_cursor.end(&()) && self.transform_cursor.item().is_some() { self.transform_cursor.next(&()); @@ -745,7 +741,7 @@ impl<'a> Iterator for Chunks<'a> { chunk = &chunk[offset_in_chunk..]; // Truncate the chunk so that it ends at the next fold. - let region_end = self.transform_cursor.end(&()).buffer.bytes - self.buffer_offset; + let region_end = self.transform_cursor.end(&()) - self.buffer_offset; if chunk.len() >= region_end { chunk = &chunk[0..region_end]; self.transform_cursor.next(&()); @@ -762,7 +758,7 @@ impl<'a> Iterator for Chunks<'a> { } pub struct HighlightedChunks<'a> { - transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, + transform_cursor: Cursor<'a, Transform, DisplayOffset, usize>, buffer_chunks: buffer::HighlightedChunks<'a>, buffer_chunk: Option<(usize, &'a str, StyleId)>, buffer_offset: usize, @@ -785,7 +781,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.buffer_offset += transform.summary.buffer.bytes; self.buffer_chunks.seek(self.buffer_offset); - while self.buffer_offset >= self.transform_cursor.end(&()).buffer.bytes + while self.buffer_offset >= self.transform_cursor.end(&()) && self.transform_cursor.item().is_some() { self.transform_cursor.next(&()); @@ -809,7 +805,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { chunk = &chunk[offset_in_chunk..]; // Truncate the chunk so that it ends at the next fold. - let region_end = self.transform_cursor.end(&()).buffer.bytes - self.buffer_offset; + let region_end = self.transform_cursor.end(&()) - self.buffer_offset; if chunk.len() >= region_end { chunk = &chunk[0..region_end]; self.transform_cursor.next(&()); diff --git a/zed/src/sum_tree/cursor.rs b/zed/src/sum_tree/cursor.rs index 6d441133a2e9c51d278c9469f965d7dfeae5ebd9..7e2961baa21972a04c550b1f6a5514aaa7bebc80 100644 --- a/zed/src/sum_tree/cursor.rs +++ b/zed/src/sum_tree/cursor.rs @@ -49,6 +49,16 @@ where &self.seek_dimension } + pub fn seek_end(&self, cx: &::Context) -> S { + if let Some(item_summary) = self.item_summary() { + let mut end = self.seek_start().clone(); + end.add_summary(item_summary, cx); + end + } else { + self.seek_start().clone() + } + } + pub fn start(&self) -> &U { &self.sum_dimension } diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 762d98ee7212f0d1ffade0654970aac4f6fa7704..0d580581a0c630e7d217a21e12abfd7954b189e8 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -1290,8 +1290,8 @@ impl WorktreeHandle for ModelHandle { } pub enum FileIter<'a> { - All(Cursor<'a, Entry, FileCount, FileCount>), - Visible(Cursor<'a, Entry, VisibleFileCount, VisibleFileCount>), + All(Cursor<'a, Entry, FileCount, ()>), + Visible(Cursor<'a, Entry, VisibleFileCount, ()>), } impl<'a> FileIter<'a> { @@ -1310,11 +1310,11 @@ impl<'a> FileIter<'a> { fn next_internal(&mut self) { match self { Self::All(cursor) => { - let ix = *cursor.start(); + let ix = *cursor.seek_start(); cursor.seek_forward(&FileCount(ix.0 + 1), Bias::Right, &()); } Self::Visible(cursor) => { - let ix = *cursor.start(); + let ix = *cursor.seek_start(); cursor.seek_forward(&VisibleFileCount(ix.0 + 1), Bias::Right, &()); } } From 742241a903f404b8f8fbb5723cb97da802945a04 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Jun 2021 14:06:27 +0200 Subject: [PATCH 11/15] Rename `Cursor::{start,end}` to `Cursor::{sum_start,sum_end}` --- zed/src/editor/buffer.rs | 28 ++++++++-------- zed/src/editor/buffer/rope.rs | 4 +-- zed/src/editor/display_map/fold_map.rs | 29 +++++++++-------- zed/src/sum_tree.rs | 44 +++++++++++++------------- zed/src/sum_tree/cursor.rs | 8 ++--- 5 files changed, 58 insertions(+), 55 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 566446a4e6d068767dab2f7b82e03bef2f632aca..3a2f62ace0bdd337159c3a20dcaa264768194c50 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -1210,7 +1210,7 @@ impl Buffer { old_fragments.slice(&VersionedOffset::Offset(ranges[0].start), Bias::Left, &cx); new_ropes.push_tree(new_fragments.summary().text); - let mut fragment_start = old_fragments.start().offset(); + let mut fragment_start = old_fragments.sum_start().offset(); for range in ranges { let fragment_end = old_fragments.end(&cx).offset(); @@ -1219,7 +1219,7 @@ impl Buffer { if fragment_end < range.start { // If the current fragment has been partially consumed, then consume the rest of it // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().offset() { + if fragment_start > old_fragments.sum_start().offset() { if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end - fragment_start; @@ -1233,7 +1233,7 @@ impl Buffer { old_fragments.slice(&VersionedOffset::Offset(range.start), Bias::Left, &cx); new_ropes.push_tree(slice.summary().text); new_fragments.push_tree(slice, &None); - fragment_start = old_fragments.start().offset(); + fragment_start = old_fragments.sum_start().offset(); } // If we are at the end of a non-concurrent fragment, advance to the next one. @@ -1244,7 +1244,7 @@ impl Buffer { new_ropes.push_fragment(&fragment, fragment.visible); new_fragments.push(fragment, &None); old_fragments.next(&cx); - fragment_start = old_fragments.start().offset(); + fragment_start = old_fragments.sum_start().offset(); } // Skip over insertions that are concurrent to this edit, but have a lower lamport @@ -1312,7 +1312,7 @@ impl Buffer { // If the current fragment has been partially consumed, then consume the rest of it // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().offset() { + if fragment_start > old_fragments.sum_start().offset() { let fragment_end = old_fragments.end(&cx).offset(); if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); @@ -1539,7 +1539,7 @@ impl Buffer { let mut new_fragments = old_fragments.slice(&ranges[0].start, Bias::Right, &None); new_ropes.push_tree(new_fragments.summary().text); - let mut fragment_start = old_fragments.start().visible; + let mut fragment_start = old_fragments.sum_start().visible; for range in ranges { let fragment_end = old_fragments.end(&None).visible; @@ -1548,7 +1548,7 @@ impl Buffer { if fragment_end < range.start { // If the current fragment has been partially consumed, then consume the rest of it // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().visible { + if fragment_start > old_fragments.sum_start().visible { if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end - fragment_start; @@ -1561,10 +1561,10 @@ impl Buffer { let slice = old_fragments.slice(&range.start, Bias::Right, &None); new_ropes.push_tree(slice.summary().text); new_fragments.push_tree(slice, &None); - fragment_start = old_fragments.start().visible; + fragment_start = old_fragments.sum_start().visible; } - let full_range_start = range.start + old_fragments.start().deleted; + let full_range_start = range.start + old_fragments.sum_start().deleted; // Preserve any portion of the current fragment that precedes this range. if fragment_start < range.start { @@ -1612,13 +1612,13 @@ impl Buffer { } } - let full_range_end = range.end + old_fragments.start().deleted; + let full_range_end = range.end + old_fragments.sum_start().deleted; edit.ranges.push(full_range_start..full_range_end); } // If the current fragment has been partially consumed, then consume the rest of it // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().visible { + if fragment_start > old_fragments.sum_start().visible { let fragment_end = old_fragments.end(&None).visible; if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); @@ -1663,7 +1663,7 @@ impl Buffer { let mut cursor = self.fragments.cursor::(); cursor.seek(&offset, bias, &None); Anchor::Middle { - offset: offset + cursor.start().deleted, + offset: offset + cursor.sum_start().deleted, bias, version: self.version(), } @@ -1692,7 +1692,7 @@ impl Buffer { 0 }; - self.text_summary_for_range(0..cursor.start() + overshoot) + self.text_summary_for_range(0..cursor.sum_start() + overshoot) } } } @@ -1718,7 +1718,7 @@ impl Buffer { &Some(version.clone()), ); let overshoot = offset - cursor.seek_start().offset(); - let summary = cursor.start(); + let summary = cursor.sum_start(); summary.visible + summary.deleted + overshoot } } diff --git a/zed/src/editor/buffer/rope.rs b/zed/src/editor/buffer/rope.rs index 565c4a173db5bb3225ef6f432feb8aeff2acfeb1..4c1a3ed56d709cb83a13fa032227771683b4547d 100644 --- a/zed/src/editor/buffer/rope.rs +++ b/zed/src/editor/buffer/rope.rs @@ -131,7 +131,7 @@ impl Rope { let mut cursor = self.chunks.cursor::(); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.seek_start(); - *cursor.start() + *cursor.sum_start() + cursor .item() .map_or(Point::zero(), |chunk| chunk.to_point(overshoot)) @@ -142,7 +142,7 @@ impl Rope { let mut cursor = self.chunks.cursor::(); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.seek_start(); - cursor.start() + cursor.item().map_or(0, |chunk| chunk.to_offset(overshoot)) + cursor.sum_start() + cursor.item().map_or(0, |chunk| chunk.to_offset(overshoot)) } pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize { diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index efea294d0914a4de5ad8630b368bbc3224a7787a..3193d9002c24c9ae9aec1f15784e917b3d76a696 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -245,7 +245,7 @@ impl FoldMap { let mut cursor = transforms.cursor::(); cursor.seek(&display_point, Bias::Right, &()); let overshoot = display_point.0 - cursor.seek_start().0; - *cursor.start() + overshoot + *cursor.sum_start() + overshoot } pub fn to_display_point(&self, point: Point, cx: &AppContext) -> DisplayPoint { @@ -253,7 +253,10 @@ impl FoldMap { let mut cursor = transforms.cursor::(); cursor.seek(&point, Bias::Right, &()); let overshoot = point - cursor.seek_start(); - DisplayPoint(cmp::min(cursor.start().0 + overshoot, cursor.end(&()).0)) + DisplayPoint(cmp::min( + cursor.sum_start().0 + overshoot, + cursor.end(&()).0, + )) } fn sync(&self, cx: &AppContext) -> MutexGuard> { @@ -443,7 +446,7 @@ impl FoldMapSnapshot { let mut transform_cursor = self.transforms.cursor::(); transform_cursor.seek(&offset, Bias::Right, &()); let overshoot = offset.0 - transform_cursor.seek_start().0; - let buffer_offset = transform_cursor.start() + overshoot; + let buffer_offset = transform_cursor.sum_start() + overshoot; Chunks { transform_cursor, buffer_offset, @@ -456,11 +459,11 @@ impl FoldMapSnapshot { transform_cursor.seek(&range.end, Bias::Right, &()); let overshoot = range.end.0 - transform_cursor.seek_start().0; - let buffer_end = transform_cursor.start() + overshoot; + let buffer_end = transform_cursor.sum_start() + overshoot; transform_cursor.seek(&range.start, Bias::Right, &()); let overshoot = range.start.0 - transform_cursor.seek_start().0; - let buffer_start = transform_cursor.start() + overshoot; + let buffer_start = transform_cursor.sum_start() + overshoot; HighlightedChunks { transform_cursor, @@ -480,15 +483,15 @@ impl FoldMapSnapshot { pub fn to_display_offset(&self, point: DisplayPoint) -> DisplayOffset { let mut cursor = self.transforms.cursor::(); cursor.seek(&point, Bias::Right, &()); - let overshoot = point.0 - cursor.start().display.lines; - let mut offset = cursor.start().display.bytes; + let overshoot = point.0 - cursor.sum_start().display.lines; + let mut offset = cursor.sum_start().display.bytes; if !overshoot.is_zero() { let transform = cursor.item().expect("display point out of range"); assert!(transform.display_text.is_none()); let end_buffer_offset = self .buffer - .to_offset(cursor.start().buffer.lines + overshoot); - offset += end_buffer_offset - cursor.start().buffer.bytes; + .to_offset(cursor.sum_start().buffer.lines + overshoot); + offset += end_buffer_offset - cursor.sum_start().buffer.bytes; } DisplayOffset(offset) } @@ -497,7 +500,7 @@ impl FoldMapSnapshot { let mut cursor = self.transforms.cursor::(); cursor.seek(&point, Bias::Right, &()); let overshoot = point.0 - cursor.seek_start().0; - self.buffer.to_offset(*cursor.start() + overshoot) + self.buffer.to_offset(*cursor.sum_start() + overshoot) } #[cfg(test)] @@ -514,7 +517,7 @@ impl FoldMapSnapshot { } } else { let overshoot = offset.0 - transform_start; - let buffer_offset = cursor.start() + overshoot; + let buffer_offset = cursor.sum_start() + overshoot; let clipped_buffer_offset = self.buffer.clip_offset(buffer_offset, bias); DisplayOffset( (offset.0 as isize + (clipped_buffer_offset as isize - buffer_offset as isize)) @@ -539,7 +542,7 @@ impl FoldMapSnapshot { } } else { let overshoot = point.0 - transform_start; - let buffer_position = *cursor.start() + overshoot; + let buffer_position = *cursor.sum_start() + overshoot; let clipped_buffer_position = self.buffer.clip_point(buffer_position, bias); DisplayPoint::new( point.row(), @@ -695,7 +698,7 @@ impl<'a> Iterator for BufferRows<'a> { if self.cursor.item().is_some() { let overshoot = self.display_point - self.cursor.seek_start().0; - let buffer_point = *self.cursor.start() + overshoot; + let buffer_point = *self.cursor.sum_start() + overshoot; self.display_point.row += 1; Some(buffer_point.row) } else { diff --git a/zed/src/sum_tree.rs b/zed/src/sum_tree.rs index 1cadf1a4d47ea81fdc3500a8fb7dd195175f5639..c30c00d445291908729717de3c9d89cfcf8c6a84 100644 --- a/zed/src/sum_tree.rs +++ b/zed/src/sum_tree.rs @@ -651,7 +651,7 @@ mod tests { cursor.seek(&Count(pos), Bias::Right, &()); for i in 0..10 { - assert_eq!(cursor.start().0, pos); + assert_eq!(cursor.sum_start().0, pos); if pos > 0 { assert_eq!(cursor.prev_item().unwrap(), &reference_items[pos - 1]); @@ -710,7 +710,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); - assert_eq!(cursor.start(), &Sum(0)); + assert_eq!(cursor.sum_start(), &Sum(0)); // Single-element tree let mut tree = SumTree::::new(); @@ -722,23 +722,23 @@ mod tests { ); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); - assert_eq!(cursor.start(), &Sum(0)); + assert_eq!(cursor.sum_start(), &Sum(0)); cursor.next(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); - assert_eq!(cursor.start(), &Sum(1)); + assert_eq!(cursor.sum_start(), &Sum(1)); cursor.prev(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); - assert_eq!(cursor.start(), &Sum(0)); + assert_eq!(cursor.sum_start(), &Sum(0)); let mut cursor = tree.cursor::(); assert_eq!(cursor.slice(&Count(1), Bias::Right, &()).items(&()), [1]); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); - assert_eq!(cursor.start(), &Sum(1)); + assert_eq!(cursor.sum_start(), &Sum(1)); cursor.seek(&Count(0), Bias::Right, &()); assert_eq!( @@ -749,7 +749,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); - assert_eq!(cursor.start(), &Sum(1)); + assert_eq!(cursor.sum_start(), &Sum(1)); // Multiple-element tree let mut tree = SumTree::new(); @@ -759,68 +759,68 @@ mod tests { assert_eq!(cursor.slice(&Count(2), Bias::Right, &()).items(&()), [1, 2]); assert_eq!(cursor.item(), Some(&3)); assert_eq!(cursor.prev_item(), Some(&2)); - assert_eq!(cursor.start(), &Sum(3)); + assert_eq!(cursor.sum_start(), &Sum(3)); cursor.next(&()); assert_eq!(cursor.item(), Some(&4)); assert_eq!(cursor.prev_item(), Some(&3)); - assert_eq!(cursor.start(), &Sum(6)); + assert_eq!(cursor.sum_start(), &Sum(6)); cursor.next(&()); assert_eq!(cursor.item(), Some(&5)); assert_eq!(cursor.prev_item(), Some(&4)); - assert_eq!(cursor.start(), &Sum(10)); + assert_eq!(cursor.sum_start(), &Sum(10)); cursor.next(&()); assert_eq!(cursor.item(), Some(&6)); assert_eq!(cursor.prev_item(), Some(&5)); - assert_eq!(cursor.start(), &Sum(15)); + assert_eq!(cursor.sum_start(), &Sum(15)); cursor.next(&()); cursor.next(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); - assert_eq!(cursor.start(), &Sum(21)); + assert_eq!(cursor.sum_start(), &Sum(21)); cursor.prev(&()); assert_eq!(cursor.item(), Some(&6)); assert_eq!(cursor.prev_item(), Some(&5)); - assert_eq!(cursor.start(), &Sum(15)); + assert_eq!(cursor.sum_start(), &Sum(15)); cursor.prev(&()); assert_eq!(cursor.item(), Some(&5)); assert_eq!(cursor.prev_item(), Some(&4)); - assert_eq!(cursor.start(), &Sum(10)); + assert_eq!(cursor.sum_start(), &Sum(10)); cursor.prev(&()); assert_eq!(cursor.item(), Some(&4)); assert_eq!(cursor.prev_item(), Some(&3)); - assert_eq!(cursor.start(), &Sum(6)); + assert_eq!(cursor.sum_start(), &Sum(6)); cursor.prev(&()); assert_eq!(cursor.item(), Some(&3)); assert_eq!(cursor.prev_item(), Some(&2)); - assert_eq!(cursor.start(), &Sum(3)); + assert_eq!(cursor.sum_start(), &Sum(3)); cursor.prev(&()); assert_eq!(cursor.item(), Some(&2)); assert_eq!(cursor.prev_item(), Some(&1)); - assert_eq!(cursor.start(), &Sum(1)); + assert_eq!(cursor.sum_start(), &Sum(1)); cursor.prev(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); - assert_eq!(cursor.start(), &Sum(0)); + assert_eq!(cursor.sum_start(), &Sum(0)); cursor.prev(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); - assert_eq!(cursor.start(), &Sum(0)); + assert_eq!(cursor.sum_start(), &Sum(0)); cursor.next(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); - assert_eq!(cursor.start(), &Sum(0)); + assert_eq!(cursor.sum_start(), &Sum(0)); let mut cursor = tree.cursor::(); assert_eq!( @@ -831,7 +831,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); - assert_eq!(cursor.start(), &Sum(21)); + assert_eq!(cursor.sum_start(), &Sum(21)); cursor.seek(&Count(3), Bias::Right, &()); assert_eq!( @@ -842,7 +842,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); - assert_eq!(cursor.start(), &Sum(21)); + assert_eq!(cursor.sum_start(), &Sum(21)); // Seeking can bias left or right cursor.seek(&Count(1), Bias::Left, &()); diff --git a/zed/src/sum_tree/cursor.rs b/zed/src/sum_tree/cursor.rs index 7e2961baa21972a04c550b1f6a5514aaa7bebc80..c4cc68e7785236daf6e900224541e6a127177b74 100644 --- a/zed/src/sum_tree/cursor.rs +++ b/zed/src/sum_tree/cursor.rs @@ -59,17 +59,17 @@ where } } - pub fn start(&self) -> &U { + pub fn sum_start(&self) -> &U { &self.sum_dimension } pub fn end(&self, cx: &::Context) -> U { if let Some(item_summary) = self.item_summary() { - let mut end = self.start().clone(); + let mut end = self.sum_start().clone(); end.add_summary(item_summary, cx); end } else { - self.start().clone() + self.sum_start().clone() } } @@ -627,7 +627,7 @@ where } pub fn start(&self) -> &U { - self.cursor.start() + self.cursor.sum_start() } pub fn item(&self) -> Option<&'a T> { From 37cc1e8e83c2527ae1fd675b82abcba2eff20ba1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 15 Jun 2021 17:20:36 +0200 Subject: [PATCH 12/15] Ensure all bytes are written when saving a buffer --- zed/src/worktree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 0d580581a0c630e7d217a21e12abfd7954b189e8..e79082803deb981211ccd883766b6cdf24f3e8ea 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -208,7 +208,7 @@ impl Worktree { let file = fs::File::create(&abs_path)?; let mut writer = io::BufWriter::with_capacity(buffer_size, &file); for chunk in content.chunks() { - writer.write(chunk.as_bytes())?; + writer.write_all(chunk.as_bytes())?; } writer.flush()?; From 3d67266d0bfb90befc01b64320f4886722d44bf1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 24 Jun 2021 17:51:16 -0600 Subject: [PATCH 13/15] Add a "retries" option to gpui::test macro and use it in flaky tests --- gpui/src/platform/mac/fonts.rs | 20 +++++--- gpui_macros/src/lib.rs | 90 ++++++++++++++++++++++++++++++---- zed/src/file_finder.rs | 2 +- 3 files changed, 95 insertions(+), 17 deletions(-) diff --git a/gpui/src/platform/mac/fonts.rs b/gpui/src/platform/mac/fonts.rs index b1605c8b0bad6328fca5c991d127396c7ee5a030..85a628f0d6fa59fe7bec1c8dfd32a2af412f81e4 100644 --- a/gpui/src/platform/mac/fonts.rs +++ b/gpui/src/platform/mac/fonts.rs @@ -311,17 +311,24 @@ impl FontSystemState { #[cfg(test)] mod tests { + use crate::MutableAppContext; + use super::*; use font_kit::properties::{Style, Weight}; use platform::FontSystem as _; - #[test] - fn test_layout_str() -> anyhow::Result<()> { + #[crate::test(self, retries = 5)] + fn test_layout_str(_: &mut MutableAppContext) { + // This is failing intermittently on CI and we don't have time to figure it out let fonts = FontSystem::new(); - let menlo = fonts.load_family("Menlo")?; - let menlo_regular = fonts.select_font(&menlo, &Properties::new())?; - let menlo_italic = fonts.select_font(&menlo, &Properties::new().style(Style::Italic))?; - let menlo_bold = fonts.select_font(&menlo, &Properties::new().weight(Weight::BOLD))?; + let menlo = fonts.load_family("Menlo").unwrap(); + let menlo_regular = fonts.select_font(&menlo, &Properties::new()).unwrap(); + let menlo_italic = fonts + .select_font(&menlo, &Properties::new().style(Style::Italic)) + .unwrap(); + let menlo_bold = fonts + .select_font(&menlo, &Properties::new().weight(Weight::BOLD)) + .unwrap(); assert_ne!(menlo_regular, menlo_italic); assert_ne!(menlo_regular, menlo_bold); assert_ne!(menlo_italic, menlo_bold); @@ -342,7 +349,6 @@ mod tests { assert_eq!(line.runs[1].glyphs.len(), 4); assert_eq!(line.runs[2].font_id, menlo_regular); assert_eq!(line.runs[2].glyphs.len(), 5); - Ok(()) } #[test] diff --git a/gpui_macros/src/lib.rs b/gpui_macros/src/lib.rs index c7218387dffe627a1f40084a670f8702628beccc..af7da1d1e2baab4bab9b1fcc3be54b9feec3e3ce 100644 --- a/gpui_macros/src/lib.rs +++ b/gpui_macros/src/lib.rs @@ -1,14 +1,16 @@ -use std::mem; - use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, parse_quote, AttributeArgs, ItemFn, Meta, NestedMeta}; +use std::mem; +use syn::{ + parse_macro_input, parse_quote, AttributeArgs, ItemFn, Lit, Meta, MetaNameValue, NestedMeta, +}; #[proc_macro_attribute] pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let mut namespace = format_ident!("gpui"); let args = syn::parse_macro_input!(args as AttributeArgs); + let mut max_retries = 0; for arg in args { match arg { NestedMeta::Meta(Meta::Path(name)) @@ -16,6 +18,14 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { { namespace = format_ident!("crate"); } + NestedMeta::Meta(Meta::NameValue(meta)) => { + if let Some(result) = parse_retries(&meta) { + match result { + Ok(retries) => max_retries = retries, + Err(error) => return TokenStream::from(error.into_compile_error()), + } + } + } other => { return TokenStream::from( syn::Error::new_spanned(other, "invalid argument").into_compile_error(), @@ -34,9 +44,32 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { fn #outer_fn_name() { #inner_fn - #namespace::App::test_async((), move |cx| async { - #inner_fn_name(cx).await; - }); + if #max_retries > 0 { + let mut retries = 0; + loop { + let result = std::panic::catch_unwind(|| { + #namespace::App::test_async((), move |cx| async { + #inner_fn_name(cx).await; + }); + }); + + match result { + Ok(result) => return result, + Err(error) => { + if retries < #max_retries { + retries += 1; + println!("retrying: attempt {}", retries); + } else { + std::panic::resume_unwind(error); + } + } + } + } + } else { + #namespace::App::test_async((), move |cx| async { + #inner_fn_name(cx).await; + }); + } } } } else { @@ -45,9 +78,32 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { fn #outer_fn_name() { #inner_fn - #namespace::App::test((), |cx| { - #inner_fn_name(cx); - }); + if #max_retries > 0 { + let mut retries = 0; + loop { + let result = std::panic::catch_unwind(|| { + #namespace::App::test((), |cx| { + #inner_fn_name(cx); + }); + }); + + match result { + Ok(result) => return result, + Err(error) => { + if retries < #max_retries { + retries += 1; + println!("retrying: attempt {}", retries); + } else { + std::panic::resume_unwind(error); + } + } + } + } + } else { + #namespace::App::test((), |cx| { + #inner_fn_name(cx); + }); + } } } }; @@ -55,3 +111,19 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { TokenStream::from(quote!(#outer_fn)) } + +fn parse_retries(meta: &MetaNameValue) -> Option> { + let ident = meta.path.get_ident(); + if ident.map_or(false, |n| n == "retries") { + if let Lit::Int(int) = &meta.lit { + Some(int.base10_parse()) + } else { + Some(Err(syn::Error::new( + meta.lit.span(), + "retries mut be an integer", + ))) + } + } else { + None + } +} diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 83227f309f5c3560b212957bee37250cd9800534..1f5e05e9b92a0e9728f6ca5f45753a16747c42fb 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -651,7 +651,7 @@ mod tests { finder.read_with(&cx, |f, _| assert_eq!(f.matches.len(), 0)); } - #[gpui::test] + #[gpui::test(retries = 5)] async fn test_multiple_matches_with_same_relative_path(mut cx: gpui::TestAppContext) { let tmp_dir = temp_tree(json!({ "dir1": { "a.txt": "" }, From 8f7111b0f75d6111e2406b31a577c335fa5fdb3d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 24 Jun 2021 18:02:53 -0600 Subject: [PATCH 14/15] Upgrade bindgen to fix warnings on new Rust --- Cargo.lock | 10 ++++++---- gpui/Cargo.toml | 2 +- gpui/build.rs | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5645c6f25154581d84cbfe088fa4c3eff984242..70a027a677c437ecc3bc7ff2297d2121ecff5ddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" version = "0.14.1" @@ -221,9 +223,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bindgen" -version = "0.57.0" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" +checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" dependencies = [ "bitflags 1.2.1", "cexpr", @@ -2428,9 +2430,9 @@ checksum = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" [[package]] name = "shlex" -version = "0.1.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" +checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" [[package]] name = "signal-hook" diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index 7f436094c9e8ae892b0cf3cf99d4ccb9c6993b16..4288795d923f58cc05290f5b29d3e374ba9cb3e2 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -30,7 +30,7 @@ tree-sitter = "0.19" usvg = "0.14" [build-dependencies] -bindgen = "0.57" +bindgen = "0.58.1" cc = "1.0.67" [dev-dependencies] diff --git a/gpui/build.rs b/gpui/build.rs index f8e885e9194323486e8977ef18354ca4d10059ee..a913f69a066229ff8ae701bb603262906855a9aa 100644 --- a/gpui/build.rs +++ b/gpui/build.rs @@ -17,8 +17,8 @@ fn generate_dispatch_bindings() { let bindings = bindgen::Builder::default() .header("src/platform/mac/dispatch.h") - .whitelist_var("_dispatch_main_q") - .whitelist_function("dispatch_async_f") + .allowlist_var("_dispatch_main_q") + .allowlist_function("dispatch_async_f") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .generate() .expect("unable to generate bindings"); @@ -94,7 +94,7 @@ fn compile_metal_shaders() { fn generate_shader_bindings() { let bindings = bindgen::Builder::default() .header(SHADER_HEADER_PATH) - .whitelist_type("GPUI.*") + .allowlist_type("GPUI.*") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .generate() .expect("unable to generate bindings"); From 0faabc21770e3f43f46569ad4230857e9eed9dae Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 24 Jun 2021 19:02:08 -0600 Subject: [PATCH 15/15] Only upload artifacts to workflow runs on the main branch We're running out of actions storage, and we don't really need an artifact stored for every single build. --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35495f1d2b166fb6e1c82b7f91ac35d47c93d2cc..2ca39c84207a449fe6c8c3b24b49982dc45f7a6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: push: branches: - - master + - main tags: - "v*" pull_request: @@ -61,14 +61,15 @@ jobs: - name: Create app bundle run: script/bundle - - name: Upload app bundle to workflow run + - name: Upload app bundle to workflow run if main branch uses: actions/upload-artifact@v2 + if: ${{ github.ref == 'refs/heads/main' }} with: name: Zed.dmg path: target/release/Zed.dmg - uses: softprops/action-gh-release@v1 - name: Upload app bundle to release + name: Upload app bundle to release if release tag if: ${{ startsWith(github.ref, 'refs/tags/v') }} with: draft: true