WIP

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

Cargo.lock                             |  10 
zed/Cargo.toml                         |   1 
zed/src/editor/buffer/mod.rs           | 524 ++++++++++++++++++---------
zed/src/editor/buffer/text.rs          | 461 +-----------------------
zed/src/editor/display_map/fold_map.rs |  20 
zed/src/operation_queue.rs             |   3 
zed/src/sum_tree/mod.rs                | 111 +++--
zed/src/worktree.rs                    |   3 
8 files changed, 462 insertions(+), 671 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2188,6 +2188,15 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
 
+[[package]]
+name = "ropey"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f3ef16589fdbb3e8fbce3dca944c08e61f39c7f16064b21a257d68ea911a83"
+dependencies = [
+ "smallvec",
+]
+
 [[package]]
 name = "roxmltree"
 version = "0.14.1"
@@ -2978,6 +2987,7 @@ dependencies = [
  "parking_lot",
  "postage",
  "rand 0.8.3",
+ "ropey",
  "rust-embed",
  "seahash",
  "serde 1.0.125",

zed/Cargo.toml 🔗

@@ -31,6 +31,7 @@ num_cpus = "1.13.0"
 parking_lot = "0.11.1"
 postage = {version = "0.4.1", features = ["futures-traits"]}
 rand = "0.8.3"
+ropey = "1.2"
 rust-embed = "5.9.0"
 seahash = "4.1"
 serde = {version = "1", features = ["derive"]}

zed/src/editor/buffer/mod.rs 🔗

@@ -5,6 +5,7 @@ mod text;
 
 pub use anchor::*;
 pub use point::*;
+use ropey::Rope;
 use seahash::SeaHasher;
 pub use selection::*;
 use similar::{ChangeTag, TextDiff};
@@ -25,6 +26,7 @@ use std::{
     cmp,
     hash::BuildHasher,
     iter::{self, Iterator},
+    mem,
     ops::{AddAssign, Range},
     str,
     sync::Arc,
@@ -57,6 +59,8 @@ type HashMap<K, V> = std::collections::HashMap<K, V>;
 type HashSet<T> = std::collections::HashSet<T>;
 
 pub struct Buffer {
+    visible_text: Rope,
+    deleted_text: Rope,
     fragments: SumTree<Fragment>,
     insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>,
     pub version: time::Global,
@@ -92,6 +96,7 @@ struct Transaction {
 
 #[derive(Clone)]
 pub struct History {
+    // TODO: Turn this into a String or Rope, maybe.
     pub base_text: Arc<str>,
     ops: HashMap<time::Local, EditOperation>,
     undo_stack: Vec<Transaction>,
@@ -285,15 +290,14 @@ pub struct Insertion {
     id: time::Local,
     parent_id: time::Local,
     offset_in_parent: usize,
-    text: Text,
     lamport_timestamp: time::Lamport,
 }
 
 #[derive(Eq, PartialEq, Clone, Debug)]
 struct Fragment {
     id: FragmentId,
-    insertion: Insertion,
-    text: Text,
+    insertion: Arc<Insertion>,
+    range_in_insertion: Range<usize>,
     deletions: HashSet<time::Local>,
     max_undos: time::Global,
     visible: bool,
@@ -301,11 +305,24 @@ struct Fragment {
 
 #[derive(Eq, PartialEq, Clone, Debug)]
 pub struct FragmentSummary {
-    text_summary: TextSummary,
+    text: FragmentTextSummary,
     max_fragment_id: FragmentId,
     max_version: time::Global,
 }
 
+#[derive(Default, Clone, Debug, PartialEq, Eq)]
+struct FragmentTextSummary {
+    visible: TextSummary,
+    deleted: TextSummary,
+}
+
+impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary {
+    fn add_summary(&mut self, summary: &'a FragmentSummary) {
+        self.visible += &summary.text.visible;
+        self.deleted += &summary.text.deleted;
+    }
+}
+
 #[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
 struct FragmentExtent {
     chars: usize,
@@ -348,7 +365,7 @@ pub struct EditOperation {
     end_id: time::Local,
     end_offset: usize,
     version_in_range: time::Global,
-    new_text: Option<Text>,
+    new_text: Option<String>,
 }
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -418,16 +435,17 @@ impl Buffer {
             saved_mtime = UNIX_EPOCH;
         }
 
+        let mut visible_text = Rope::new();
         let mut insertion_splits = HashMap::default();
         let mut fragments = SumTree::new();
 
-        let base_insertion = Insertion {
+        let base_text = Rope::from(history.base_text.as_ref());
+        let base_insertion = Arc::new(Insertion {
             id: time::Local::default(),
             parent_id: time::Local::default(),
             offset_in_parent: 0,
-            text: history.base_text.clone().into(),
             lamport_timestamp: time::Lamport::default(),
-        };
+        });
 
         insertion_splits.insert(
             base_insertion.id,
@@ -437,45 +455,43 @@ impl Buffer {
                     extent: 0,
                 },
                 &(),
+                &(),
             ),
         );
         fragments.push(
-            Fragment {
-                id: FragmentId::min_value().clone(),
-                insertion: base_insertion.clone(),
-                text: base_insertion.text.slice(0..0),
-                deletions: Default::default(),
-                max_undos: Default::default(),
-                visible: true,
-            },
+            Fragment::new(
+                FragmentId::min_value().clone(),
+                base_insertion.clone(),
+                0..0,
+            ),
+            &FragmentContext::default(),
             &(),
         );
 
-        if base_insertion.text.len() > 0 {
+        if base_text.len_chars() > 0 {
             let base_fragment_id =
                 FragmentId::between(&FragmentId::min_value(), &FragmentId::max_value());
+            let range_in_insertion = 0..base_text.len_chars();
 
+            visible_text = base_text.clone();
             insertion_splits.get_mut(&base_insertion.id).unwrap().push(
                 InsertionSplit {
                     fragment_id: base_fragment_id.clone(),
-                    extent: base_insertion.text.len(),
+                    extent: range_in_insertion.end,
                 },
                 &(),
+                &(),
             );
             fragments.push(
-                Fragment {
-                    id: base_fragment_id,
-                    text: base_insertion.text.clone(),
-                    insertion: base_insertion,
-                    deletions: Default::default(),
-                    max_undos: Default::default(),
-                    visible: true,
-                },
+                Fragment::new(base_fragment_id, base_insertion, range_in_insertion.clone()),
+                &FragmentContext::new(base_text, Rope::new(), Default::default()),
                 &(),
             );
         }
 
         Self {
+            visible_text,
+            deleted_text: Rope::new(),
             fragments,
             insertion_splits,
             version: time::Global::new(),
@@ -613,29 +629,7 @@ impl Buffer {
     }
 
     pub fn text_summary_for_range(&self, range: Range<usize>) -> TextSummary {
-        let mut summary = TextSummary::default();
-
-        let mut cursor = self.fragments.cursor::<usize, usize>();
-        cursor.seek(&range.start, SeekBias::Right, &());
-
-        if let Some(fragment) = cursor.item() {
-            let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
-            let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
-            summary += fragment.text.slice(summary_start..summary_end).summary();
-            cursor.next();
-        }
-
-        if range.end > *cursor.start() {
-            summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
-
-            if let Some(fragment) = cursor.item() {
-                let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
-                let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
-                summary += fragment.text.slice(summary_start..summary_end).summary();
-            }
-        }
-
-        summary
+        TextSummary::from(self.visible_text.slice(range))
     }
 
     pub fn len(&self) -> usize {
@@ -654,37 +648,39 @@ impl Buffer {
     }
 
     pub fn rightmost_point(&self) -> Point {
-        self.fragments.summary().text_summary.rightmost_point
+        todo!()
+        // self.fragments.summary().text_summary.rightmost_point
     }
 
     pub fn rightmost_point_in_range(&self, range: Range<usize>) -> Point {
-        let mut summary = TextSummary::default();
+        todo!()
+        // let mut summary = TextSummary::default();
 
-        let mut cursor = self.fragments.cursor::<usize, usize>();
-        cursor.seek(&range.start, SeekBias::Right, &());
+        // let mut cursor = self.fragments.cursor::<usize, usize>();
+        // cursor.seek(&range.start, SeekBias::Right, &());
 
-        if let Some(fragment) = cursor.item() {
-            let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
-            let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
-            summary += fragment.text.slice(summary_start..summary_end).summary();
-            cursor.next();
-        }
+        // if let Some(fragment) = cursor.item() {
+        //     let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
+        //     let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
+        //     summary += fragment.text.slice(summary_start..summary_end).summary();
+        //     cursor.next();
+        // }
 
-        if range.end > *cursor.start() {
-            summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
+        // if range.end > *cursor.start() {
+        //     summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
 
-            if let Some(fragment) = cursor.item() {
-                let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
-                let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
-                summary += fragment.text.slice(summary_start..summary_end).summary();
-            }
-        }
+        //     if let Some(fragment) = cursor.item() {
+        //         let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
+        //         let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
+        //         summary += fragment.text.slice(summary_start..summary_end).summary();
+        //     }
+        // }
 
-        summary.rightmost_point
+        // summary.rightmost_point
     }
 
     pub fn max_point(&self) -> Point {
-        self.fragments.extent()
+        TextSummary::from(&self.visible_text).lines
     }
 
     pub fn line(&self, row: u32) -> Result<String> {
@@ -807,7 +803,7 @@ impl Buffer {
     where
         I: IntoIterator<Item = Range<S>>,
         S: ToOffset,
-        T: Into<Text>,
+        T: Into<String>,
     {
         self.start_transaction_at(None, Instant::now())?;
 
@@ -827,7 +823,7 @@ impl Buffer {
             old_ranges
                 .into_iter()
                 .filter(|old_range| new_text.is_some() || old_range.end > old_range.start),
-            new_text.clone(),
+            new_text.into(),
         );
 
         for op in &ops {
@@ -1022,7 +1018,7 @@ impl Buffer {
                         edit.start_offset,
                         edit.end_id,
                         edit.end_offset,
-                        edit.new_text.as_ref().cloned(),
+                        edit.new_text.as_deref(),
                         &edit.version_in_range,
                         edit.id,
                         lamport_timestamp,
@@ -1064,12 +1060,11 @@ impl Buffer {
         start_offset: usize,
         end_id: time::Local,
         end_offset: usize,
-        new_text: Option<Text>,
+        new_text: Option<&str>,
         version_in_range: &time::Global,
         local_timestamp: time::Local,
         lamport_timestamp: time::Lamport,
     ) -> Result<()> {
-        let mut new_text = new_text.as_ref().cloned();
         let start_fragment_id = self.resolve_fragment_id(start_id, start_offset)?;
         let end_fragment_id = self.resolve_fragment_id(end_id, end_offset)?;
 
@@ -1077,12 +1072,24 @@ impl Buffer {
         let last_id = old_fragments.extent::<FragmentIdRef>().0.unwrap();
         let last_id_ref = FragmentIdRef::new(&last_id);
 
-        let mut cursor = old_fragments.cursor::<FragmentIdRef, ()>();
+        let mut cursor = old_fragments.cursor::<FragmentIdRef, FragmentTextSummary>();
         let mut new_fragments =
             cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
+        let mut new_visible_text = self.visible_text.clone();
+        let mut new_deleted_text = self.deleted_text.clone();
 
-        if start_offset == cursor.item().unwrap().end_offset() {
-            new_fragments.push(cursor.item().unwrap().clone(), &());
+        let start_fragment = cursor.item().unwrap();
+        if start_offset == start_fragment.range_in_insertion.end {
+            // TODO: maybe don't recompute this fragment and its summary.
+            new_fragments.push(
+                cursor.item().unwrap().clone(),
+                &FragmentContext::new(
+                    new_visible_text.clone(),
+                    new_deleted_text.clone(),
+                    cursor.start().clone(),
+                ),
+                &(),
+            );
             cursor.next();
         }
 
@@ -1097,12 +1104,12 @@ impl Buffer {
                 let split_start = if start_fragment_id == fragment.id {
                     start_offset
                 } else {
-                    fragment.start_offset()
+                    fragment.range_in_insertion.start
                 };
                 let split_end = if end_fragment_id == fragment.id {
                     end_offset
                 } else {
-                    fragment.end_offset()
+                    fragment.range_in_insertion.end
                 };
                 let (before_range, within_range, after_range) = self.split_fragment(
                     cursor.prev_item().as_ref().unwrap(),
@@ -1121,30 +1128,84 @@ impl Buffer {
                     None
                 };
                 if let Some(fragment) = before_range {
-                    new_fragments.push(fragment, &());
+                    new_fragments.push(
+                        fragment,
+                        &FragmentContext::new(
+                            new_visible_text.clone(),
+                            new_deleted_text.clone(),
+                            new_fragments.summary().text,
+                        ),
+                        &(),
+                    );
                 }
                 if let Some(fragment) = insertion {
-                    new_fragments.push(fragment, &());
+                    new_visible_text.insert(
+                        new_fragments.summary().text.visible.chars,
+                        new_text.unwrap(),
+                    );
+                    new_fragments.push(
+                        fragment,
+                        &FragmentContext::new(
+                            new_visible_text.clone(),
+                            new_deleted_text.clone(),
+                            new_fragments.summary().text,
+                        ),
+                        &(),
+                    );
                 }
                 if let Some(mut fragment) = within_range {
                     if fragment.was_visible(&version_in_range, &self.undo_map) {
                         fragment.deletions.insert(local_timestamp);
                         fragment.visible = false;
+
+                        // TODO: avoid calling to_string on rope slice.
+                        let deleted_start = new_fragments.summary().text.visible.chars;
+                        let deleted_range = deleted_start..deleted_start + fragment.len();
+                        new_deleted_text.insert(
+                            new_fragments.summary().text.deleted.chars,
+                            &new_visible_text.slice(deleted_range).to_string(),
+                        );
+                        new_visible_text.remove(deleted_range);
                     }
-                    new_fragments.push(fragment, &());
+
+                    new_fragments.push(
+                        fragment,
+                        &FragmentContext::new(
+                            new_visible_text.clone(),
+                            new_deleted_text.clone(),
+                            new_fragments.summary().text,
+                        ),
+                        &(),
+                    );
                 }
                 if let Some(fragment) = after_range {
-                    new_fragments.push(fragment, &());
+                    new_fragments.push(
+                        fragment,
+                        &FragmentContext::new(
+                            new_visible_text.clone(),
+                            new_deleted_text.clone(),
+                            new_fragments.summary().text,
+                        ),
+                        &(),
+                    );
                 }
             } else {
                 if new_text.is_some() && lamport_timestamp > fragment.insertion.lamport_timestamp {
+                    let new_text = new_text.take().unwrap();
+                    let fragment = self.build_fragment_to_insert(
+                        cursor.prev_item().as_ref().unwrap(),
+                        Some(&fragment),
+                        new_text,
+                        local_timestamp,
+                        lamport_timestamp,
+                    );
+                    new_visible_text.insert(new_fragments.summary().text.visible.chars, new_text);
                     new_fragments.push(
-                        self.build_fragment_to_insert(
-                            cursor.prev_item().as_ref().unwrap(),
-                            Some(&fragment),
-                            new_text.take().unwrap(),
-                            local_timestamp,
-                            lamport_timestamp,
+                        fragment,
+                        &FragmentContext::new(
+                            new_visible_text.clone(),
+                            new_deleted_text.clone(),
+                            new_fragments.summary().text,
                         ),
                         &(),
                     );
@@ -1155,27 +1216,54 @@ impl Buffer {
                 {
                     fragment.deletions.insert(local_timestamp);
                     fragment.visible = false;
+
+                    // TODO: avoid calling to_string on rope slice.
+                    let deleted_start = new_fragments.summary().text.visible.chars;
+                    let deleted_range = deleted_start..deleted_start + fragment.len();
+                    new_deleted_text.insert(
+                        new_fragments.summary().text.deleted.chars,
+                        &new_visible_text.slice(deleted_range).to_string(),
+                    );
+                    new_visible_text.remove(deleted_range);
                 }
-                new_fragments.push(fragment, &());
+
+                new_fragments.push(
+                    fragment,
+                    &FragmentContext::new(
+                        new_visible_text.clone(),
+                        new_deleted_text.clone(),
+                        new_fragments.summary().text,
+                    ),
+                    &(),
+                );
             }
 
             cursor.next();
         }
 
         if let Some(new_text) = new_text {
+            let fragment = self.build_fragment_to_insert(
+                cursor.prev_item().as_ref().unwrap(),
+                None,
+                new_text,
+                local_timestamp,
+                lamport_timestamp,
+            );
+            new_visible_text.insert(new_fragments.summary().text.visible.chars, new_text);
             new_fragments.push(
-                self.build_fragment_to_insert(
-                    cursor.prev_item().as_ref().unwrap(),
-                    None,
-                    new_text,
-                    local_timestamp,
-                    lamport_timestamp,
+                fragment,
+                &FragmentContext::new(
+                    new_visible_text.clone(),
+                    new_deleted_text.clone(),
+                    new_fragments.summary().text,
                 ),
                 &(),
             );
         }
 
         new_fragments.push_tree(cursor.slice(&last_id_ref, SeekBias::Right, &()), &());
+        self.visible_text = new_visible_text;
+        self.deleted_text = new_deleted_text;
         self.fragments = new_fragments;
         self.local_clock.observe(local_timestamp);
         self.lamport_clock.observe(lamport_timestamp);
@@ -1251,6 +1339,8 @@ impl Buffer {
 
     fn apply_undo(&mut self, undo: UndoOperation) -> Result<()> {
         let mut new_fragments;
+        let mut new_visible_text = self.visible_text.clone();
+        let mut new_deleted_text = self.deleted_text.clone();
 
         self.undo_map.insert(undo);
         let edit = &self.history.ops[&undo.edit_id];
@@ -1267,9 +1357,38 @@ impl Buffer {
 
             loop {
                 let mut fragment = cursor.item().unwrap().clone();
-                fragment.visible = fragment.is_visible(&self.undo_map);
+                let was_visible =
+                    mem::replace(&mut fragment.visible, fragment.is_visible(&self.undo_map));
                 fragment.max_undos.observe(undo.id);
-                new_fragments.push(fragment, &());
+
+                // TODO: avoid calling to_string on rope slice.
+                if fragment.visible && !was_visible {
+                    let visible_start = new_fragments.summary().text.deleted.chars;
+                    let visible_range = visible_start..visible_start + fragment.len();
+                    new_visible_text.insert(
+                        new_fragments.summary().text.visible.chars,
+                        &new_deleted_text.slice(visible_range).to_string(),
+                    );
+                    new_deleted_text.remove(visible_range);
+                } else if !fragment.visible && was_visible {
+                    let deleted_start = new_fragments.summary().text.visible.chars;
+                    let deleted_range = deleted_start..deleted_start + fragment.len();
+                    new_deleted_text.insert(
+                        new_fragments.summary().text.deleted.chars,
+                        &new_visible_text.slice(deleted_range).to_string(),
+                    );
+                    new_visible_text.remove(deleted_range);
+                }
+
+                new_fragments.push(
+                    fragment,
+                    &FragmentContext::new(
+                        new_visible_text.clone(),
+                        new_deleted_text.clone(),
+                        new_fragments.summary().text,
+                    ),
+                    &(),
+                );
                 cursor.next();
                 if let Some(split_id) = insertion_splits.next() {
                     new_fragments.push_tree(
@@ -1291,10 +1410,41 @@ impl Buffer {
                     if edit.version_in_range.observed(fragment.insertion.id)
                         || fragment.insertion.id == undo.edit_id
                     {
-                        fragment.visible = fragment.is_visible(&self.undo_map);
+                        let was_visible = mem::replace(
+                            &mut fragment.visible,
+                            fragment.is_visible(&self.undo_map),
+                        );
                         fragment.max_undos.observe(undo.id);
+
+                        // TODO: avoid calling to_string on rope slice.
+                        if fragment.visible && !was_visible {
+                            let visible_start = new_fragments.summary().text.deleted.chars;
+                            let visible_range = visible_start..visible_start + fragment.len();
+                            new_visible_text.insert(
+                                new_fragments.summary().text.visible.chars,
+                                &new_deleted_text.slice(visible_range).to_string(),
+                            );
+                            new_deleted_text.remove(visible_range);
+                        } else if !fragment.visible && was_visible {
+                            let deleted_start = new_fragments.summary().text.visible.chars;
+                            let deleted_range = deleted_start..deleted_start + fragment.len();
+                            new_deleted_text.insert(
+                                new_fragments.summary().text.deleted.chars,
+                                &new_visible_text.slice(deleted_range).to_string(),
+                            );
+                            new_visible_text.remove(deleted_range);
+                        }
                     }
-                    new_fragments.push(fragment, &());
+
+                    new_fragments.push(
+                        fragment,
+                        &FragmentContext::new(
+                            new_visible_text.clone(),
+                            new_deleted_text.clone(),
+                            new_fragments.summary().text,
+                        ),
+                        &(),
+                    );
                     cursor.next();
                 }
             }
@@ -1372,7 +1522,7 @@ impl Buffer {
             .clone())
     }
 
-    fn splice_fragments<I>(&mut self, mut old_ranges: I, new_text: Option<Text>) -> Vec<Operation>
+    fn splice_fragments<I>(&mut self, mut old_ranges: I, new_text: Option<String>) -> Vec<Operation>
     where
         I: Iterator<Item = Range<usize>>,
     {
@@ -1386,6 +1536,9 @@ impl Buffer {
         let old_fragments = self.fragments.clone();
         let mut cursor = old_fragments.cursor::<usize, usize>();
         let mut new_fragments = SumTree::new();
+        let mut new_visible_text = self.visible_text.clone();
+        let mut new_deleted_text = self.deleted_text.clone();
+
         new_fragments.push_tree(
             cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
             &(),
@@ -1412,7 +1565,7 @@ impl Buffer {
                 .unwrap();
             let mut splits_cursor = old_split_tree.cursor::<usize, ()>();
             let mut new_split_tree =
-                splits_cursor.slice(&fragment.start_offset(), SeekBias::Right, &());
+                splits_cursor.slice(&fragment.range_in_insertion.start, SeekBias::Right, &());
 
             // Find all splices that start or end within the current fragment. Then, split the
             // fragment and reassemble it in both trees accounting for the deleted and the newly
@@ -1421,38 +1574,43 @@ impl Buffer {
                 let range = cur_range.clone().unwrap();
                 if range.start > fragment_start {
                     let mut prefix = fragment.clone();
-                    prefix.set_end_offset(prefix.start_offset() + (range.start - fragment_start));
+                    prefix.range_in_insertion.end =
+                        prefix.range_in_insertion.start + (range.start - fragment_start);
                     prefix.id =
                         FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
-                    fragment.set_start_offset(prefix.end_offset());
-                    new_fragments.push(prefix.clone(), &());
+                    fragment.range_in_insertion.start = prefix.range_in_insertion.end;
+                    new_fragments.push(
+                        prefix.clone(),
+                        &FragmentContext::new(new_visible_text.clone()) & (),
+                    );
                     new_split_tree.push(
                         InsertionSplit {
-                            extent: prefix.end_offset() - prefix.start_offset(),
+                            extent: prefix.range_in_insertion.end - prefix.range_in_insertion.start,
                             fragment_id: prefix.id,
                         },
                         &(),
+                        &(),
                     );
                     fragment_start = range.start;
                 }
 
                 if range.end == fragment_start {
                     end_id = Some(new_fragments.last().unwrap().insertion.id);
-                    end_offset = Some(new_fragments.last().unwrap().end_offset());
+                    end_offset = Some(new_fragments.last().unwrap().range_in_insertion.end);
                 } else if range.end == fragment_end {
                     end_id = Some(fragment.insertion.id);
-                    end_offset = Some(fragment.end_offset());
+                    end_offset = Some(fragment.range_in_insertion.end);
                 }
 
                 if range.start == fragment_start {
                     start_id = Some(new_fragments.last().unwrap().insertion.id);
-                    start_offset = Some(new_fragments.last().unwrap().end_offset());
+                    start_offset = Some(new_fragments.last().unwrap().range_in_insertion.end);
 
                     if let Some(new_text) = new_text.clone() {
                         let new_fragment = self.build_fragment_to_insert(
                             &new_fragments.last().unwrap(),
                             Some(&fragment),
-                            new_text,
+                            &new_text,
                             local_timestamp,
                             lamport_timestamp,
                         );
@@ -1463,7 +1621,8 @@ impl Buffer {
                 if range.end < fragment_end {
                     if range.end > fragment_start {
                         let mut prefix = fragment.clone();
-                        prefix.set_end_offset(prefix.start_offset() + (range.end - fragment_start));
+                        prefix.range_in_insertion.end =
+                            prefix.range_in_insertion.start + (range.end - fragment_start);
                         prefix.id =
                             FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
                         version_in_range.observe_all(&fragment_summary.max_version);
@@ -1471,18 +1630,19 @@ impl Buffer {
                             prefix.deletions.insert(local_timestamp);
                             prefix.visible = false;
                         }
-                        fragment.set_start_offset(prefix.end_offset());
+                        fragment.range_in_insertion.start = prefix.range_in_insertion.end;
                         new_fragments.push(prefix.clone(), &());
                         new_split_tree.push(
                             InsertionSplit {
-                                extent: prefix.end_offset() - prefix.start_offset(),
+                                extent: prefix.range_in_insertion.end
+                                    - prefix.range_in_insertion.start,
                                 fragment_id: prefix.id,
                             },
                             &(),
                         );
                         fragment_start = range.end;
                         end_id = Some(fragment.insertion.id);
-                        end_offset = Some(fragment.start_offset());
+                        end_offset = Some(fragment.range_in_insertion.start);
                     }
                 } else {
                     version_in_range.observe_all(&fragment_summary.max_version);
@@ -1525,7 +1685,7 @@ impl Buffer {
             }
             new_split_tree.push(
                 InsertionSplit {
-                    extent: fragment.end_offset() - fragment.start_offset(),
+                    extent: fragment.range_in_insertion.end - fragment.range_in_insertion.start,
                     fragment_id: fragment.id.clone(),
                 },
                 &(),
@@ -1558,7 +1718,7 @@ impl Buffer {
 
                         if range.end == fragment_end {
                             end_id = Some(fragment.insertion.id);
-                            end_offset = Some(fragment.end_offset());
+                            end_offset = Some(fragment.range_in_insertion.end);
                             ops.push(Operation::Edit {
                                 edit: EditOperation {
                                     id: local_timestamp,
@@ -1611,9 +1771,9 @@ impl Buffer {
                 edit: EditOperation {
                     id: local_timestamp,
                     start_id: last_fragment.insertion.id,
-                    start_offset: last_fragment.end_offset(),
+                    start_offset: last_fragment.range_in_insertion.end,
                     end_id: last_fragment.insertion.id,
-                    end_offset: last_fragment.end_offset(),
+                    end_offset: last_fragment.range_in_insertion.end,
                     version_in_range: time::Global::new(),
                     new_text: new_text.clone(),
                 },
@@ -1647,24 +1807,26 @@ impl Buffer {
         fragment: &Fragment,
         range: Range<usize>,
     ) -> (Option<Fragment>, Option<Fragment>, Option<Fragment>) {
-        debug_assert!(range.start >= fragment.start_offset());
-        debug_assert!(range.start <= fragment.end_offset());
-        debug_assert!(range.end <= fragment.end_offset());
-        debug_assert!(range.end >= fragment.start_offset());
+        debug_assert!(range.start >= fragment.range_in_insertion.start);
+        debug_assert!(range.start <= fragment.range_in_insertion.end);
+        debug_assert!(range.end <= fragment.range_in_insertion.end);
+        debug_assert!(range.end >= fragment.range_in_insertion.start);
 
-        if range.end == fragment.start_offset() {
+        if range.end == fragment.range_in_insertion.start {
             (None, None, Some(fragment.clone()))
-        } else if range.start == fragment.end_offset() {
+        } else if range.start == fragment.range_in_insertion.end {
             (Some(fragment.clone()), None, None)
-        } else if range.start == fragment.start_offset() && range.end == fragment.end_offset() {
+        } else if range.start == fragment.range_in_insertion.start
+            && range.end == fragment.range_in_insertion.end
+        {
             (None, Some(fragment.clone()), None)
         } else {
             let mut prefix = fragment.clone();
 
-            let after_range = if range.end < fragment.end_offset() {
+            let after_range = if range.end < fragment.range_in_insertion.end {
                 let mut suffix = prefix.clone();
-                suffix.set_start_offset(range.end);
-                prefix.set_end_offset(range.end);
+                suffix.range_in_insertion.start = range.end;
+                prefix.range_in_insertion.end = range.end;
                 prefix.id = FragmentId::between(&prev_fragment.id, &suffix.id);
                 Some(suffix)
             } else {
@@ -1673,15 +1835,15 @@ impl Buffer {
 
             let within_range = if range.start != range.end {
                 let mut suffix = prefix.clone();
-                suffix.set_start_offset(range.start);
-                prefix.set_end_offset(range.start);
+                suffix.range_in_insertion.start = range.start;
+                prefix.range_in_insertion.end = range.start;
                 prefix.id = FragmentId::between(&prev_fragment.id, &suffix.id);
                 Some(suffix)
             } else {
                 None
             };
 
-            let before_range = if range.start > fragment.start_offset() {
+            let before_range = if range.start > fragment.range_in_insertion.start {
                 Some(prefix)
             } else {
                 None
@@ -1692,15 +1854,17 @@ impl Buffer {
                 .remove(&fragment.insertion.id)
                 .unwrap();
             let mut cursor = old_split_tree.cursor::<usize, ()>();
-            let mut new_split_tree = cursor.slice(&fragment.start_offset(), SeekBias::Right, &());
+            let mut new_split_tree =
+                cursor.slice(&fragment.range_in_insertion.start, SeekBias::Right, &());
 
             if let Some(ref fragment) = before_range {
                 new_split_tree.push(
                     InsertionSplit {
-                        extent: range.start - fragment.start_offset(),
+                        extent: range.start - fragment.range_in_insertion.start,
                         fragment_id: fragment.id.clone(),
                     },
                     &(),
+                    &(),
                 );
             }
 
@@ -1711,16 +1875,18 @@ impl Buffer {
                         fragment_id: fragment.id.clone(),
                     },
                     &(),
+                    &(),
                 );
             }
 
             if let Some(ref fragment) = after_range {
                 new_split_tree.push(
                     InsertionSplit {
-                        extent: fragment.end_offset() - range.end,
+                        extent: fragment.range_in_insertion.end - range.end,
                         fragment_id: fragment.id.clone(),
                     },
                     &(),
+                    &(),
                 );
             }
 
@@ -1741,8 +1907,8 @@ impl Buffer {
         &mut self,
         prev_fragment: &Fragment,
         next_fragment: Option<&Fragment>,
-        text: Text,
-        local_timestamp: time::Local,
+        text: &str,
+        insertion_id: time::Local,
         lamport_timestamp: time::Lamport,
     ) -> Fragment {
         let new_fragment_id = FragmentId::between(
@@ -1752,25 +1918,28 @@ impl Buffer {
                 .unwrap_or(&FragmentId::max_value()),
         );
 
+        // TODO: extent could be expressed in bytes, which would save a linear scan.
+        let range_in_insertion = 0..text.chars().count();
         let mut split_tree = SumTree::new();
         split_tree.push(
             InsertionSplit {
-                extent: text.len(),
+                extent: range_in_insertion.len(),
                 fragment_id: new_fragment_id.clone(),
             },
             &(),
+            &(),
         );
-        self.insertion_splits.insert(local_timestamp, split_tree);
+        self.insertion_splits.insert(insertion_id, split_tree);
 
         Fragment::new(
             new_fragment_id,
-            Insertion {
-                id: local_timestamp,
+            Arc::new(Insertion {
+                id: insertion_id,
                 parent_id: prev_fragment.insertion.id,
-                offset_in_parent: prev_fragment.end_offset(),
-                text,
+                offset_in_parent: prev_fragment.range_in_insertion.end,
                 lamport_timestamp,
-            },
+            }),
+            range_in_insertion,
         )
     }
 
@@ -1811,7 +1980,7 @@ impl Buffer {
         cursor.seek(&offset, seek_bias, &());
         let fragment = cursor.item().unwrap();
         let offset_in_fragment = offset - cursor.start();
-        let offset_in_insertion = fragment.start_offset() + offset_in_fragment;
+        let offset_in_insertion = fragment.range_in_insertion.start + offset_in_fragment;
         let anchor = Anchor::Middle {
             insertion_id: fragment.insertion.id,
             offset: offset_in_insertion,
@@ -1883,7 +2052,7 @@ impl Buffer {
                 if fragment.visible {
                     summary += fragment
                         .text
-                        .slice(..offset - fragment.start_offset())
+                        .slice(..offset - fragment.range_in_insertion.start)
                         .summary();
                 }
                 Ok(summary)
@@ -1910,6 +2079,7 @@ impl Buffer {
 impl Clone for Buffer {
     fn clone(&self) -> Self {
         Self {
+            visible_text: self.visible_text.clone(),
             fragments: self.fragments.clone(),
             insertion_splits: self.insertion_splits.clone(),
             version: self.version.clone(),
@@ -2205,45 +2375,17 @@ impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentIdRef<'a> {
 }
 
 impl Fragment {
-    fn new(id: FragmentId, insertion: Insertion) -> Self {
+    fn new(id: FragmentId, insertion: Arc<Insertion>, range_in_insertion: Range<usize>) -> Self {
         Self {
             id,
-            text: insertion.text.clone(),
             insertion,
+            range_in_insertion,
             deletions: Default::default(),
             max_undos: Default::default(),
             visible: true,
         }
     }
 
-    fn start_offset(&self) -> usize {
-        self.text.range().start
-    }
-
-    fn set_start_offset(&mut self, offset: usize) {
-        self.text = self.insertion.text.slice(offset..self.end_offset());
-    }
-
-    fn end_offset(&self) -> usize {
-        self.text.range().end
-    }
-
-    fn set_end_offset(&mut self, offset: usize) {
-        self.text = self.insertion.text.slice(self.start_offset()..offset);
-    }
-
-    fn visible_len(&self) -> usize {
-        if self.visible {
-            self.len()
-        } else {
-            0
-        }
-    }
-
-    fn len(&self) -> usize {
-        self.text.len()
-    }
-
     fn is_visible(&self, undos: &UndoMap) -> bool {
         !undos.is_undone(self.insertion.id) && self.deletions.iter().all(|d| undos.is_undone(*d))
     }
@@ -2256,19 +2398,41 @@ impl Fragment {
                 .all(|d| !version.observed(*d) || undos.was_undone(*d, version))
     }
 
-    fn point_for_offset(&self, offset: usize) -> Result<Point> {
-        Ok(self.text.point_for_offset(offset))
+    fn len(&self) -> usize {
+        self.range_in_insertion.len()
     }
 
-    fn offset_for_point(&self, point: Point) -> Result<usize> {
-        Ok(self.text.offset_for_point(point))
+    fn visible_len(&self) -> usize {
+        if self.visible {
+            self.range_in_insertion.len()
+        } else {
+            0
+        }
+    }
+}
+
+#[derive(Default)]
+struct FragmentContext {
+    visible_text: Rope,
+    deleted_text: Rope,
+    start: FragmentTextSummary,
+}
+
+impl FragmentContext {
+    fn new(visible_text: Rope, deleted_text: Rope, start: FragmentTextSummary) -> Self {
+        Self {
+            visible_text,
+            deleted_text,
+            start,
+        }
     }
 }
 
 impl sum_tree::Item for Fragment {
+    type Context = FragmentContext;
     type Summary = FragmentSummary;
 
-    fn summary(&self) -> Self::Summary {
+    fn summary(&self, ctx: &FragmentContext) -> Self::Summary {
         let mut max_version = time::Global::new();
         max_version.observe(self.insertion.id);
         for deletion in &self.deletions {

zed/src/editor/buffer/text.rs 🔗

@@ -1,84 +1,45 @@
-use super::Point;
-use crate::sum_tree::{self, SeekBias, SumTree};
-use arrayvec::ArrayVec;
-use std::{
-    cmp,
-    fmt::{self, Debug},
-    ops::{Bound, Index, Range, RangeBounds},
-    sync::Arc,
-};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-enum Run {
-    Newline,
-    Chars { len: usize, char_size: u8 },
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
-struct ByteOffset(usize);
+use ropey::{Rope, RopeSlice};
 
-impl sum_tree::Item for Run {
-    type Summary = TextSummary;
-
-    fn summary(&self) -> Self::Summary {
-        match *self {
-            Run::Newline => TextSummary {
-                chars: 1,
-                bytes: 1,
-                lines: Point::new(1, 0),
-                first_line_len: 0,
-                rightmost_point: Point::new(0, 0),
-            },
-            Run::Chars { len, char_size } => TextSummary {
-                chars: len,
-                bytes: len * char_size as usize,
-                lines: Point::new(0, len as u32),
-                first_line_len: len as u32,
-                rightmost_point: Point::new(0, len as u32),
-            },
-        }
-    }
-}
-
-impl Run {
-    fn char_size(&self) -> u8 {
-        match self {
-            Run::Newline => 1,
-            Run::Chars { char_size, .. } => *char_size,
-        }
-    }
-}
+use super::Point;
 
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
 pub struct TextSummary {
     pub chars: usize,
     pub bytes: usize,
     pub lines: Point,
-    pub first_line_len: u32,
-    pub rightmost_point: Point,
 }
 
-impl sum_tree::Summary for TextSummary {
-    type Context = ();
+impl<'a> From<RopeSlice<'a>> for TextSummary {
+    fn from(slice: RopeSlice<'a>) -> Self {
+        let last_row = slice.len_lines() - 1;
+        let last_column = slice.line(last_row).len_chars();
+        Self {
+            chars: slice.len_chars(),
+            bytes: slice.len_bytes(),
+            lines: Point::new(last_row as u32, last_column as u32),
+        }
+    }
+}
 
-    fn add_summary(&mut self, other: &Self, _: &()) {
-        *self += other;
+impl<'a> From<&'a Rope> for TextSummary {
+    fn from(text: &'a Rope) -> Self {
+        Self::from(text.slice(..))
     }
 }
 
 impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
     fn add_assign(&mut self, other: &'a Self) {
-        let joined_line_len = self.lines.column + other.first_line_len;
-        if joined_line_len > self.rightmost_point.column {
-            self.rightmost_point = Point::new(self.lines.row, joined_line_len);
-        }
-        if other.rightmost_point.column > self.rightmost_point.column {
-            self.rightmost_point = self.lines + &other.rightmost_point;
-        }
-
-        if self.lines.row == 0 {
-            self.first_line_len += other.first_line_len;
-        }
+        // let joined_line_len = self.lines.column + other.first_line_len;
+        // if joined_line_len > self.rightmost_point.column {
+        //     self.rightmost_point = Point::new(self.lines.row, joined_line_len);
+        // }
+        // if other.rightmost_point.column > self.rightmost_point.column {
+        //     self.rightmost_point = self.lines + &other.rightmost_point;
+        // }
+
+        // if self.lines.row == 0 {
+        //     self.first_line_len += other.first_line_len;
+        // }
 
         self.chars += other.chars;
         self.bytes += other.bytes;
@@ -91,371 +52,3 @@ impl std::ops::AddAssign<Self> for TextSummary {
         *self += &other;
     }
 }
-
-impl<'a> sum_tree::Dimension<'a, TextSummary> for TextSummary {
-    fn add_summary(&mut self, other: &TextSummary) {
-        *self += other;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
-    fn add_summary(&mut self, summary: &TextSummary) {
-        *self += &summary.lines;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TextSummary> for ByteOffset {
-    fn add_summary(&mut self, summary: &TextSummary) {
-        self.0 += summary.bytes
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
-    fn add_summary(&mut self, summary: &TextSummary) {
-        *self += summary.chars;
-    }
-}
-
-#[derive(Clone)]
-pub struct Text {
-    text: Arc<str>,
-    runs: SumTree<Run>,
-    range: Range<usize>,
-}
-
-impl From<String> for Text {
-    fn from(text: String) -> Self {
-        Self::from(Arc::from(text))
-    }
-}
-
-impl<'a> From<&'a str> for Text {
-    fn from(text: &'a str) -> Self {
-        Self::from(Arc::from(text))
-    }
-}
-
-impl From<Arc<str>> for Text {
-    fn from(text: Arc<str>) -> Self {
-        let mut runs = Vec::new();
-
-        let mut chars_len = 0;
-        let mut run_char_size = 0;
-        let mut run_chars = 0;
-
-        let mut chars = text.chars();
-        loop {
-            let ch = chars.next();
-            let ch_size = ch.map_or(0, |ch| ch.len_utf8());
-            if run_chars != 0 && (ch.is_none() || ch == Some('\n') || run_char_size != ch_size) {
-                runs.push(Run::Chars {
-                    len: run_chars,
-                    char_size: run_char_size as u8,
-                });
-                run_chars = 0;
-            }
-            run_char_size = ch_size;
-
-            match ch {
-                Some('\n') => runs.push(Run::Newline),
-                Some(_) => run_chars += 1,
-                None => break,
-            }
-            chars_len += 1;
-        }
-
-        let mut tree = SumTree::new();
-        tree.extend(runs, &());
-        Text {
-            text,
-            runs: tree,
-            range: 0..chars_len,
-        }
-    }
-}
-
-impl Debug for Text {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_tuple("Text").field(&self.as_str()).finish()
-    }
-}
-
-impl PartialEq for Text {
-    fn eq(&self, other: &Self) -> bool {
-        self.text == other.text
-    }
-}
-
-impl Eq for Text {}
-
-impl<T: RangeBounds<usize>> Index<T> for Text {
-    type Output = str;
-
-    fn index(&self, range: T) -> &Self::Output {
-        let start = match range.start_bound() {
-            Bound::Included(start) => cmp::min(self.range.start + start, self.range.end),
-            Bound::Excluded(_) => unimplemented!(),
-            Bound::Unbounded => self.range.start,
-        };
-        let end = match range.end_bound() {
-            Bound::Included(end) => cmp::min(self.range.start + end + 1, self.range.end),
-            Bound::Excluded(end) => cmp::min(self.range.start + end, self.range.end),
-            Bound::Unbounded => self.range.end,
-        };
-
-        let byte_start = self.abs_byte_offset_for_offset(start);
-        let byte_end = self.abs_byte_offset_for_offset(end);
-        &self.text[byte_start..byte_end]
-    }
-}
-
-impl Text {
-    pub fn range(&self) -> Range<usize> {
-        self.range.clone()
-    }
-
-    pub fn as_str(&self) -> &str {
-        &self[..]
-    }
-
-    pub fn slice<T: RangeBounds<usize>>(&self, range: T) -> Text {
-        let start = match range.start_bound() {
-            Bound::Included(start) => cmp::min(self.range.start + start, self.range.end),
-            Bound::Excluded(_) => unimplemented!(),
-            Bound::Unbounded => self.range.start,
-        };
-        let end = match range.end_bound() {
-            Bound::Included(end) => cmp::min(self.range.start + end + 1, self.range.end),
-            Bound::Excluded(end) => cmp::min(self.range.start + end, self.range.end),
-            Bound::Unbounded => self.range.end,
-        };
-
-        Text {
-            text: self.text.clone(),
-            runs: self.runs.clone(),
-            range: start..end,
-        }
-    }
-
-    pub fn line_len(&self, row: u32) -> u32 {
-        let mut cursor = self.runs.cursor::<usize, Point>();
-        cursor.seek(&self.range.start, SeekBias::Right, &());
-        let absolute_row = cursor.start().row + row;
-
-        let mut cursor = self.runs.cursor::<Point, usize>();
-        cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right, &());
-        let prefix_len = self.range.start.saturating_sub(*cursor.start());
-        let line_len =
-            cursor.summary::<usize>(&Point::new(absolute_row + 1, 0), SeekBias::Left, &());
-        let suffix_len = cursor.start().saturating_sub(self.range.end);
-
-        line_len
-            .saturating_sub(prefix_len)
-            .saturating_sub(suffix_len) as u32
-    }
-
-    pub fn len(&self) -> usize {
-        self.range.end - self.range.start
-    }
-
-    pub fn lines(&self) -> Point {
-        self.abs_point_for_offset(self.range.end) - &self.abs_point_for_offset(self.range.start)
-    }
-
-    pub fn rightmost_point(&self) -> Point {
-        let lines = self.lines();
-
-        let mut candidates = ArrayVec::<[Point; 3]>::new();
-        candidates.push(lines);
-        if lines.row > 0 {
-            candidates.push(Point::new(0, self.line_len(0)));
-            if lines.row > 1 {
-                let mut cursor = self.runs.cursor::<usize, Point>();
-                cursor.seek(&self.range.start, SeekBias::Right, &());
-                let absolute_start_row = cursor.start().row;
-
-                let mut cursor = self.runs.cursor::<Point, usize>();
-                cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right, &());
-                let summary = cursor.summary::<TextSummary>(
-                    &Point::new(absolute_start_row + lines.row, 0),
-                    SeekBias::Left,
-                    &(),
-                );
-
-                candidates.push(Point::new(1, 0) + &summary.rightmost_point);
-            }
-        }
-
-        candidates.into_iter().max_by_key(|p| p.column).unwrap()
-    }
-
-    pub fn point_for_offset(&self, offset: usize) -> Point {
-        self.abs_point_for_offset(self.range.start + offset)
-            - &self.abs_point_for_offset(self.range.start)
-    }
-
-    pub fn offset_for_point(&self, point: Point) -> usize {
-        let mut cursor = self.runs.cursor::<Point, TextSummary>();
-        let abs_point = self.abs_point_for_offset(self.range.start) + &point;
-        cursor.seek(&abs_point, SeekBias::Right, &());
-        let overshoot = abs_point - &cursor.start().lines;
-        let abs_offset = cursor.start().chars + overshoot.column as usize;
-        abs_offset - self.range.start
-    }
-
-    pub fn summary(&self) -> TextSummary {
-        TextSummary {
-            chars: self.range.end - self.range.start,
-            bytes: self.abs_byte_offset_for_offset(self.range.end)
-                - self.abs_byte_offset_for_offset(self.range.start),
-            lines: self.abs_point_for_offset(self.range.end)
-                - &self.abs_point_for_offset(self.range.start),
-            first_line_len: self.line_len(0),
-            rightmost_point: self.rightmost_point(),
-        }
-    }
-
-    fn abs_point_for_offset(&self, offset: usize) -> Point {
-        let mut cursor = self.runs.cursor::<usize, TextSummary>();
-        cursor.seek(&offset, SeekBias::Right, &());
-        let overshoot = (offset - cursor.start().chars) as u32;
-        cursor.start().lines + &Point::new(0, overshoot)
-    }
-
-    fn abs_byte_offset_for_offset(&self, offset: usize) -> usize {
-        let mut cursor = self.runs.cursor::<usize, TextSummary>();
-        cursor.seek(&offset, SeekBias::Right, &());
-        let overshoot = offset - cursor.start().chars;
-        cursor.start().bytes + overshoot * cursor.item().map_or(0, |run| run.char_size()) as usize
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::collections::HashSet;
-    use std::iter::FromIterator;
-
-    #[test]
-    fn test_basic() {
-        let text = Text::from(String::from("ab\ncd€\nfghij\nkl¢m"));
-        assert_eq!(text.len(), 17);
-        assert_eq!(text.as_str(), "ab\ncd€\nfghij\nkl¢m");
-        assert_eq!(text.lines(), Point::new(3, 4));
-        assert_eq!(text.line_len(0), 2);
-        assert_eq!(text.line_len(1), 3);
-        assert_eq!(text.line_len(2), 5);
-        assert_eq!(text.line_len(3), 4);
-        assert_eq!(text.rightmost_point(), Point::new(2, 5));
-
-        let b_to_g = text.slice(1..9);
-        assert_eq!(b_to_g.as_str(), "b\ncd€\nfg");
-        assert_eq!(b_to_g.len(), 8);
-        assert_eq!(b_to_g.lines(), Point::new(2, 2));
-        assert_eq!(b_to_g.line_len(0), 1);
-        assert_eq!(b_to_g.line_len(1), 3);
-        assert_eq!(b_to_g.line_len(2), 2);
-        assert_eq!(b_to_g.line_len(3), 0);
-        assert_eq!(b_to_g.rightmost_point(), Point::new(1, 3));
-
-        let d_to_i = text.slice(4..11);
-        assert_eq!(d_to_i.as_str(), "d€\nfghi");
-        assert_eq!(&d_to_i[1..5], "€\nfg");
-        assert_eq!(d_to_i.len(), 7);
-        assert_eq!(d_to_i.lines(), Point::new(1, 4));
-        assert_eq!(d_to_i.line_len(0), 2);
-        assert_eq!(d_to_i.line_len(1), 4);
-        assert_eq!(d_to_i.line_len(2), 0);
-        assert_eq!(d_to_i.rightmost_point(), Point::new(1, 4));
-
-        let d_to_j = text.slice(4..=11);
-        assert_eq!(d_to_j.as_str(), "d€\nfghij");
-        assert_eq!(&d_to_j[1..], "€\nfghij");
-        assert_eq!(d_to_j.len(), 8);
-    }
-
-    #[test]
-    fn test_random() {
-        use rand::prelude::*;
-
-        for seed in 0..100 {
-            println!("buffer::text seed: {}", seed);
-            let rng = &mut StdRng::seed_from_u64(seed);
-
-            let len = rng.gen_range(0..50);
-            let mut string = String::new();
-            for _ in 0..len {
-                if rng.gen_ratio(1, 5) {
-                    string.push('\n');
-                } else {
-                    string.push(rng.gen());
-                }
-            }
-            let text = Text::from(string.clone());
-
-            for _ in 0..10 {
-                let start = rng.gen_range(0..text.len() + 1);
-                let end = rng.gen_range(start..text.len() + 2);
-
-                let string_slice = string
-                    .chars()
-                    .skip(start)
-                    .take(end - start)
-                    .collect::<String>();
-                let expected_line_endpoints = string_slice
-                    .split('\n')
-                    .enumerate()
-                    .map(|(row, line)| Point::new(row as u32, line.chars().count() as u32))
-                    .collect::<Vec<_>>();
-                let text_slice = text.slice(start..end);
-
-                assert_eq!(text_slice.lines(), lines(&string_slice));
-
-                let mut rightmost_points: HashSet<Point> = HashSet::new();
-                for endpoint in &expected_line_endpoints {
-                    if let Some(rightmost_point) = rightmost_points.iter().next().cloned() {
-                        if endpoint.column > rightmost_point.column {
-                            rightmost_points.clear();
-                        }
-                        if endpoint.column >= rightmost_point.column {
-                            rightmost_points.insert(*endpoint);
-                        }
-                    } else {
-                        rightmost_points.insert(*endpoint);
-                    }
-
-                    assert_eq!(text_slice.line_len(endpoint.row as u32), endpoint.column);
-                }
-
-                assert!(rightmost_points.contains(&text_slice.rightmost_point()));
-
-                for _ in 0..10 {
-                    let offset = rng.gen_range(0..string_slice.chars().count() + 1);
-                    let point = lines(&string_slice.chars().take(offset).collect::<String>());
-                    assert_eq!(text_slice.point_for_offset(offset), point);
-                    assert_eq!(text_slice.offset_for_point(point), offset);
-                    if offset < string_slice.chars().count() {
-                        assert_eq!(
-                            &text_slice[offset..offset + 1],
-                            String::from_iter(string_slice.chars().nth(offset)).as_str()
-                        );
-                    }
-                }
-            }
-        }
-    }
-
-    pub fn lines(s: &str) -> Point {
-        let mut row = 0;
-        let mut column = 0;
-        for ch in s.chars() {
-            if ch == '\n' {
-                row += 1;
-                column = 0;
-            } else {
-                column += 1;
-            }
-        }
-        Point::new(row, column)
-    }
-}

zed/src/editor/display_map/fold_map.rs 🔗

@@ -38,6 +38,7 @@ impl FoldMap {
                     display_text: None,
                 },
                 &(),
+                &(),
             )),
             last_sync: Mutex::new(buffer.version()),
         }
@@ -72,7 +73,8 @@ impl FoldMap {
     }
 
     pub fn rightmost_point(&self, ctx: &AppContext) -> DisplayPoint {
-        DisplayPoint(self.sync(ctx).summary().display.rightmost_point)
+        todo!()
+        // DisplayPoint(self.sync(ctx).summary().display.rightmost_point)
     }
 
     pub fn folds_in_range<'a, T>(
@@ -122,7 +124,7 @@ impl FoldMap {
             let mut cursor = self.folds.cursor::<_, ()>();
             for fold in folds {
                 new_tree.push_tree(cursor.slice(&fold, SeekBias::Right, buffer), buffer);
-                new_tree.push(fold, buffer);
+                new_tree.push(fold, &(), buffer);
             }
             new_tree.push_tree(cursor.suffix(buffer), buffer);
             new_tree
@@ -341,6 +343,7 @@ impl FoldMap {
                             display_text: None,
                         },
                         &(),
+                        &(),
                     );
                 }
 
@@ -352,14 +355,15 @@ impl FoldMap {
                                     chars: 1,
                                     bytes: '…'.len_utf8(),
                                     lines: Point::new(0, 1),
-                                    first_line_len: 1,
-                                    rightmost_point: Point::new(0, 1),
+                                    // first_line_len: 1,
+                                    // rightmost_point: Point::new(0, 1),
                                 },
                                 buffer: buffer.text_summary_for_range(fold.start..fold.end),
                             },
                             display_text: Some('…'),
                         },
                         &(),
+                        &(),
                     );
                 }
             }
@@ -377,6 +381,7 @@ impl FoldMap {
                         display_text: None,
                     },
                     &(),
+                    &(),
                 );
             }
         }
@@ -393,6 +398,7 @@ impl FoldMap {
                     display_text: None,
                 },
                 &(),
+                &(),
             );
         }
 
@@ -465,9 +471,10 @@ struct TransformSummary {
 }
 
 impl sum_tree::Item for Transform {
+    type Context = ();
     type Summary = TransformSummary;
 
-    fn summary(&self) -> Self::Summary {
+    fn summary(&self, _: &()) -> Self::Summary {
         self.summary.clone()
     }
 }
@@ -497,9 +504,10 @@ impl Default for Fold {
 }
 
 impl sum_tree::Item for Fold {
+    type Context = ();
     type Summary = FoldSummary;
 
-    fn summary(&self) -> Self::Summary {
+    fn summary(&self, _: &()) -> Self::Summary {
         FoldSummary {
             start: self.0.start.clone(),
             end: self.0.end.clone(),

zed/src/operation_queue.rs 🔗

@@ -48,9 +48,10 @@ impl<T: Operation> OperationQueue<T> {
 }
 
 impl<T: Operation> Item for T {
+    type Context = ();
     type Summary = OperationSummary;
 
-    fn summary(&self) -> Self::Summary {
+    fn summary(&self, _: &()) -> Self::Summary {
         OperationSummary {
             key: OperationKey(self.timestamp()),
             len: 1,

zed/src/sum_tree/mod.rs 🔗

@@ -11,12 +11,13 @@ const TREE_BASE: usize = 2;
 const TREE_BASE: usize = 6;
 
 pub trait Item: Clone + fmt::Debug {
+    type Context;
     type Summary: Summary;
 
-    fn summary(&self) -> Self::Summary;
+    fn summary(&self, ctx: &Self::Context) -> Self::Summary;
 }
 
-pub trait KeyedItem: Item {
+pub trait KeyedItem: Item<Context = ()> {
     type Key: for<'a> Dimension<'a, Self::Summary> + Ord;
 
     fn key(&self) -> Self::Key;
@@ -64,9 +65,13 @@ impl<T: Item> SumTree<T> {
         }))
     }
 
-    pub fn from_item(item: T, ctx: &<T::Summary as Summary>::Context) -> Self {
+    pub fn from_item(
+        item: T,
+        item_ctx: &T::Context,
+        summary_ctx: &<T::Summary as Summary>::Context,
+    ) -> Self {
         let mut tree = Self::new();
-        tree.push(item, ctx);
+        tree.push(item, item_ctx, summary_ctx);
         tree
     }
 
@@ -125,47 +130,13 @@ impl<T: Item> SumTree<T> {
         }
     }
 
-    pub fn extend<I>(&mut self, iter: I, ctx: &<T::Summary as Summary>::Context)
-    where
-        I: IntoIterator<Item = T>,
-    {
-        let mut leaf: Option<Node<T>> = None;
-
-        for item in iter {
-            if leaf.is_some() && leaf.as_ref().unwrap().items().len() == 2 * TREE_BASE {
-                self.push_tree(SumTree(Arc::new(leaf.take().unwrap())), ctx);
-            }
-
-            if leaf.is_none() {
-                leaf = Some(Node::Leaf::<T> {
-                    summary: T::Summary::default(),
-                    items: ArrayVec::new(),
-                    item_summaries: ArrayVec::new(),
-                });
-            }
-
-            if let Some(Node::Leaf {
-                summary,
-                items,
-                item_summaries,
-            }) = leaf.as_mut()
-            {
-                let item_summary = item.summary();
-                summary.add_summary(&item_summary, ctx);
-                items.push(item);
-                item_summaries.push(item_summary);
-            } else {
-                unreachable!()
-            }
-        }
-
-        if leaf.is_some() {
-            self.push_tree(SumTree(Arc::new(leaf.take().unwrap())), ctx);
-        }
-    }
-
-    pub fn push(&mut self, item: T, ctx: &<T::Summary as Summary>::Context) {
-        let summary = item.summary();
+    pub fn push(
+        &mut self,
+        item: T,
+        item_ctx: &T::Context,
+        summary_ctx: &<T::Summary as Summary>::Context,
+    ) {
+        let summary = item.summary(item_ctx);
         self.push_tree(
             SumTree::from_child_trees(
                 vec![SumTree(Arc::new(Node::Leaf {
@@ -173,9 +144,9 @@ impl<T: Item> SumTree<T> {
                     items: ArrayVec::from_iter(Some(item)),
                     item_summaries: ArrayVec::from_iter(Some(summary)),
                 }))],
-                ctx,
+                summary_ctx,
             ),
-            ctx,
+            summary_ctx,
         )
     }
 
@@ -349,13 +320,54 @@ impl<T: Item> SumTree<T> {
     }
 }
 
+impl<T: Item<Context = ()>> SumTree<T> {
+    pub fn extend<I>(&mut self, iter: I, ctx: &<T::Summary as Summary>::Context)
+    where
+        I: IntoIterator<Item = T>,
+    {
+        let mut leaf: Option<Node<T>> = None;
+
+        for item in iter {
+            if leaf.is_some() && leaf.as_ref().unwrap().items().len() == 2 * TREE_BASE {
+                self.push_tree(SumTree(Arc::new(leaf.take().unwrap())), ctx);
+            }
+
+            if leaf.is_none() {
+                leaf = Some(Node::Leaf::<T> {
+                    summary: T::Summary::default(),
+                    items: ArrayVec::new(),
+                    item_summaries: ArrayVec::new(),
+                });
+            }
+
+            if let Some(Node::Leaf {
+                summary,
+                items,
+                item_summaries,
+            }) = leaf.as_mut()
+            {
+                let item_summary = item.summary(&());
+                summary.add_summary(&item_summary, ctx);
+                items.push(item);
+                item_summaries.push(item_summary);
+            } else {
+                unreachable!()
+            }
+        }
+
+        if leaf.is_some() {
+            self.push_tree(SumTree(Arc::new(leaf.take().unwrap())), ctx);
+        }
+    }
+}
+
 impl<T: KeyedItem> SumTree<T> {
     #[allow(unused)]
     pub fn insert(&mut self, item: T, ctx: &<T::Summary as Summary>::Context) {
         *self = {
             let mut cursor = self.cursor::<T::Key, ()>();
             let mut new_tree = cursor.slice(&item.key(), SeekBias::Left, ctx);
-            new_tree.push(item, ctx);
+            new_tree.push(item, &(), ctx);
             new_tree.push_tree(cursor.suffix(ctx), ctx);
             new_tree
         };
@@ -863,9 +875,10 @@ mod tests {
     struct Sum(usize);
 
     impl Item for u8 {
+        type Context = ();
         type Summary = IntegersSummary;
 
-        fn summary(&self) -> Self::Summary {
+        fn summary(&self, _: &()) -> Self::Summary {
             IntegersSummary {
                 count: Count(1),
                 sum: Sum(*self as usize),

zed/src/worktree.rs 🔗

@@ -538,9 +538,10 @@ impl Entry {
 }
 
 impl sum_tree::Item for Entry {
+    type Context = ();
     type Summary = EntrySummary;
 
-    fn summary(&self) -> Self::Summary {
+    fn summary(&self, _: &()) -> Self::Summary {
         let file_count;
         let visible_file_count;
         if self.is_file() {