WIP: Replace ropey with our own Rope

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           | 405 +++++++++++++--------------
zed/src/editor/buffer/rope.rs          |  39 +
zed/src/editor/display_map/fold_map.rs |   3 
zed/src/worktree.rs                    |   5 
6 files changed, 220 insertions(+), 243 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2188,15 +2188,6 @@ 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"
@@ -2987,7 +2978,6 @@ dependencies = [
  "parking_lot",
  "postage",
  "rand 0.8.3",
- "ropey",
  "rust-embed",
  "seahash",
  "serde 1.0.125",

zed/Cargo.toml 🔗

@@ -31,7 +31,6 @@ 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 🔗

@@ -1,11 +1,11 @@
 mod anchor;
 mod point;
-mod rope;
+pub mod rope;
 mod selection;
 
 pub use anchor::*;
 pub use point::*;
-use ropey::{Rope, RopeSlice};
+pub use rope::{Rope, TextSummary};
 use seahash::SeaHasher;
 pub use selection::*;
 use similar::{ChangeTag, TextDiff};
@@ -57,9 +57,9 @@ type HashMap<K, V> = std::collections::HashMap<K, V>;
 type HashSet<T> = std::collections::HashSet<T>;
 
 pub struct Buffer {
+    fragments: SumTree<Fragment>,
     visible_text: Rope,
     deleted_text: Rope,
-    fragments: SumTree<Fragment>,
     insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>,
     pub version: time::Global,
     saved_version: time::Global,
@@ -305,74 +305,6 @@ impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary {
     }
 }
 
-#[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<'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();
-
-        let mut point = Point::default();
-        let mut rightmost_point = point;
-        let mut first_line_len = None;
-        for (i, c) in slice.chars().enumerate() {
-            if c == '\n' {
-                if first_line_len.is_none() {
-                    first_line_len = Some(i as u32);
-                }
-                point.row += 1;
-                point.column = 0;
-            } else {
-                point.column += 1;
-                if point.column > rightmost_point.column {
-                    rightmost_point = point;
-                }
-            }
-        }
-
-        Self {
-            chars: slice.len_chars(),
-            bytes: slice.len_bytes(),
-            lines: Point::new(last_row as u32, last_column as u32),
-            first_line_len: first_line_len.unwrap_or(slice.len_chars() as u32),
-            rightmost_point,
-        }
-    }
-}
-
-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;
-        }
-
-        self.chars += other.chars;
-        self.bytes += other.bytes;
-        self.lines += &other.lines;
-    }
-}
-
-impl std::ops::AddAssign<Self> for TextSummary {
-    fn add_assign(&mut self, other: Self) {
-        *self += &other;
-    }
-}
-
 #[derive(Eq, PartialEq, Clone, Debug)]
 struct InsertionSplit {
     extent: usize,
@@ -510,10 +442,10 @@ impl Buffer {
             &(),
         );
 
-        if base_text.len_chars() > 0 {
+        if base_text.len() > 0 {
             let base_fragment_id =
                 FragmentId::between(&FragmentId::min_value(), &FragmentId::max_value());
-            let range_in_insertion = 0..base_text.len_chars();
+            let range_in_insertion = 0..base_text.len();
 
             visible_text = base_text.clone();
             insertion_splits.get_mut(&base_insertion.id).unwrap().push(
@@ -663,11 +595,12 @@ impl Buffer {
     }
 
     pub fn text_summary(&self) -> TextSummary {
-        TextSummary::from(self.visible_text.slice(..))
+        self.visible_text.summary()
     }
 
     pub fn text_summary_for_range(&self, range: Range<usize>) -> TextSummary {
-        TextSummary::from(self.visible_text.slice(range))
+        // TODO: Use a dedicated ::summarize method in Rope.
+        self.visible_text.slice(range).summary()
     }
 
     pub fn len(&self) -> usize {
@@ -686,7 +619,7 @@ impl Buffer {
     }
 
     pub fn rightmost_point(&self) -> Point {
-        self.rightmost_point_in_range(0..self.len())
+        self.visible_text.summary().rightmost_point
     }
 
     pub fn rightmost_point_in_range(&self, range: Range<usize>) -> Point {
@@ -694,7 +627,7 @@ impl Buffer {
     }
 
     pub fn max_point(&self) -> Point {
-        self.text_summary().lines
+        self.visible_text.max_point()
     }
 
     pub fn line(&self, row: u32) -> Result<String> {
@@ -717,11 +650,11 @@ impl Buffer {
         Ok(self.chars_at(start)?.take(end - start))
     }
 
-    pub fn chars(&self) -> ropey::iter::Chars {
+    pub fn chars(&self) -> rope::Chars {
         self.chars_at(0).unwrap()
     }
 
-    pub fn chars_at<T: ToOffset>(&self, position: T) -> Result<ropey::iter::Chars> {
+    pub fn chars_at<T: ToOffset>(&self, position: T) -> Result<rope::Chars> {
         let offset = position.to_offset(self)?;
         Ok(self.visible_text.chars_at(offset))
     }
@@ -1084,22 +1017,25 @@ impl Buffer {
         let end_fragment_id = self.resolve_fragment_id(end_id, end_offset)?;
 
         let old_fragments = self.fragments.clone();
-        let last_id = old_fragments.extent::<FragmentIdRef>().0.unwrap();
-        let last_id_ref = FragmentIdRef::new(&last_id);
+        let old_visible_text = self.visible_text.clone();
+        let old_deleted_text = self.deleted_text.clone();
+
+        let mut fragments_cursor = old_fragments.cursor::<FragmentIdRef, FragmentTextSummary>();
+        let mut visible_text_cursor = old_visible_text.cursor(0);
+        let mut deleted_text_cursor = old_deleted_text.cursor(0);
 
-        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();
+            fragments_cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
+        let mut new_visible_text = visible_text_cursor.slice(fragments_cursor.start().visible);
+        let mut new_deleted_text = deleted_text_cursor.slice(fragments_cursor.start().deleted);
 
-        let start_fragment = cursor.item().unwrap();
+        let start_fragment = fragments_cursor.item().unwrap();
         if start_offset == start_fragment.range_in_insertion.end {
-            new_fragments.push(cursor.item().unwrap().clone(), &());
-            cursor.next();
+            new_fragments.push(fragments_cursor.item().unwrap().clone(), &());
+            fragments_cursor.next();
         }
 
-        while let Some(fragment) = cursor.item() {
+        while let Some(fragment) = fragments_cursor.item() {
             if new_text.is_none() && fragment.id > end_fragment_id {
                 break;
             }
@@ -1118,13 +1054,14 @@ impl Buffer {
                     fragment.range_in_insertion.end
                 };
                 let (before_range, within_range, after_range) = self.split_fragment(
-                    cursor.prev_item().as_ref().unwrap(),
+                    fragments_cursor.prev_item().as_ref().unwrap(),
                     &fragment,
                     split_start..split_end,
                 );
                 let insertion = if let Some(new_text) = new_text {
+                    let prev_fragment = fragments_cursor.prev_item();
                     Some(self.build_fragment_to_insert(
-                        before_range.as_ref().or(cursor.prev_item()).unwrap(),
+                        before_range.as_ref().or(prev_fragment).unwrap(),
                         within_range.as_ref().or(after_range.as_ref()),
                         new_text,
                         local_timestamp,
@@ -1137,25 +1074,26 @@ impl Buffer {
                     new_fragments.push(fragment, &());
                 }
                 if let Some(fragment) = insertion {
-                    new_visible_text.insert(
-                        new_fragments.summary().text.visible,
-                        new_text.take().unwrap(),
-                    );
+                    new_visible_text
+                        .append(visible_text_cursor.slice(new_fragments.summary().text.visible));
                     new_fragments.push(fragment, &());
+                    new_visible_text.push(new_text.take().unwrap());
                 }
                 if let Some(mut fragment) = within_range {
                     if fragment.was_visible(&version_in_range, &self.undo_map) {
                         fragment.deletions.insert(local_timestamp);
                         if fragment.visible {
                             fragment.visible = false;
-                            // TODO: avoid calling to_string on rope slice.
-                            let deleted_start = new_fragments.summary().text.visible;
-                            let deleted_range = deleted_start..deleted_start + fragment.len();
-                            new_deleted_text.insert(
-                                new_fragments.summary().text.deleted,
-                                &new_visible_text.slice(deleted_range.clone()).to_string(),
+                            new_visible_text.append(
+                                visible_text_cursor.slice(new_fragments.summary().text.visible),
+                            );
+                            new_deleted_text.append(
+                                deleted_text_cursor.slice(new_fragments.summary().text.deleted),
+                            );
+                            new_deleted_text.append(
+                                visible_text_cursor
+                                    .slice(new_fragments.summary().text.visible + fragment.len()),
                             );
-                            new_visible_text.remove(deleted_range);
                         }
                     }
 
@@ -1168,14 +1106,16 @@ impl Buffer {
                 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(),
+                        fragments_cursor.prev_item().as_ref().unwrap(),
                         Some(&fragment),
                         new_text,
                         local_timestamp,
                         lamport_timestamp,
                     );
-                    new_visible_text.insert(new_fragments.summary().text.visible, new_text);
+                    new_visible_text
+                        .append(visible_text_cursor.slice(new_fragments.summary().text.visible));
                     new_fragments.push(fragment, &());
+                    new_visible_text.push(new_text);
                 }
 
                 if fragment.id < end_fragment_id
@@ -1184,39 +1124,46 @@ impl Buffer {
                     fragment.deletions.insert(local_timestamp);
                     if fragment.visible {
                         fragment.visible = false;
-                        // TODO: avoid calling to_string on rope slice.
-                        let deleted_start = new_fragments.summary().text.visible;
-                        let deleted_range = deleted_start..deleted_start + fragment.len();
-                        new_deleted_text.insert(
-                            new_fragments.summary().text.deleted,
-                            &new_visible_text.slice(deleted_range.clone()).to_string(),
+                        new_visible_text.append(
+                            visible_text_cursor.slice(new_fragments.summary().text.visible),
+                        );
+                        new_deleted_text.append(
+                            deleted_text_cursor.slice(new_fragments.summary().text.deleted),
+                        );
+                        new_deleted_text.append(
+                            visible_text_cursor
+                                .slice(new_fragments.summary().text.visible + fragment.len()),
                         );
-                        new_visible_text.remove(deleted_range);
                     }
                 }
 
                 new_fragments.push(fragment, &());
             }
 
-            cursor.next();
+            fragments_cursor.next();
         }
 
         if let Some(new_text) = new_text {
             let fragment = self.build_fragment_to_insert(
-                cursor.prev_item().as_ref().unwrap(),
+                fragments_cursor.prev_item().as_ref().unwrap(),
                 None,
                 new_text,
                 local_timestamp,
                 lamport_timestamp,
             );
-            new_visible_text.insert(new_fragments.summary().text.visible, new_text);
+            new_visible_text
+                .append(visible_text_cursor.slice(new_fragments.summary().text.visible));
             new_fragments.push(fragment, &());
+            new_visible_text.push(new_text);
         }
 
-        new_fragments.push_tree(cursor.slice(&last_id_ref, SeekBias::Right, &()), &());
+        new_fragments.push_tree(fragments_cursor.suffix(&()), &());
+        new_visible_text.append(visible_text_cursor.suffix());
+        new_deleted_text.append(deleted_text_cursor.suffix());
+
+        self.fragments = new_fragments;
         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);
         Ok(())
@@ -1291,52 +1238,60 @@ 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();
+        let mut new_visible_text = Rope::new();
+        let mut new_deleted_text = Rope::new();
 
         self.undo_map.insert(undo);
         let edit = &self.history.ops[&undo.edit_id];
         let start_fragment_id = self.resolve_fragment_id(edit.start_id, edit.start_offset)?;
         let end_fragment_id = self.resolve_fragment_id(edit.end_id, edit.end_offset)?;
-        let mut cursor = self.fragments.cursor::<FragmentIdRef, ()>();
+
+        let mut fragments_cursor = self.fragments.cursor::<FragmentIdRef, ()>();
+        let mut visible_text_cursor = self.visible_text.cursor(0);
+        let mut deleted_text_cursor = self.deleted_text.cursor(0);
 
         if edit.start_id == edit.end_id && edit.start_offset == edit.end_offset {
             let splits = &self.insertion_splits[&undo.edit_id];
             let mut insertion_splits = splits.cursor::<(), ()>().map(|s| &s.fragment_id).peekable();
 
             let first_split_id = insertion_splits.next().unwrap();
-            new_fragments = cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left, &());
+            new_fragments =
+                fragments_cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left, &());
+            new_visible_text
+                .append(visible_text_cursor.slice(new_fragments.summary().text.visible));
+            new_deleted_text
+                .append(deleted_text_cursor.slice(new_fragments.summary().text.deleted));
 
             loop {
-                let mut fragment = cursor.item().unwrap().clone();
+                let mut fragment = fragments_cursor.item().unwrap().clone();
                 let was_visible = fragment.visible;
                 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 {
+                    new_visible_text
+                        .append(visible_text_cursor.slice(new_fragments.summary().text.visible));
+                    new_deleted_text
+                        .append(deleted_text_cursor.slice(new_fragments.summary().text.deleted));
+                }
+
                 if fragment.visible && !was_visible {
-                    let visible_start = new_fragments.summary().text.deleted;
-                    let visible_range = visible_start..visible_start + fragment.len();
-                    new_visible_text.insert(
-                        new_fragments.summary().text.visible,
-                        &new_deleted_text.slice(visible_range.clone()).to_string(),
+                    new_visible_text.append(
+                        deleted_text_cursor
+                            .slice(new_fragments.summary().text.deleted + fragment.len()),
                     );
-                    new_deleted_text.remove(visible_range);
                 } else if !fragment.visible && was_visible {
-                    let deleted_start = new_fragments.summary().text.visible;
-                    let deleted_range = deleted_start..deleted_start + fragment.len();
-                    new_deleted_text.insert(
-                        new_fragments.summary().text.deleted,
-                        &new_visible_text.slice(deleted_range.clone()).to_string(),
+                    new_deleted_text.append(
+                        visible_text_cursor
+                            .slice(new_fragments.summary().text.visible + fragment.len()),
                     );
-                    new_visible_text.remove(deleted_range);
                 }
 
                 new_fragments.push(fragment, &());
-                cursor.next();
+                fragments_cursor.next();
                 if let Some(split_id) = insertion_splits.next() {
                     new_fragments.push_tree(
-                        cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left, &()),
+                        fragments_cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left, &()),
                         &(),
                     );
                 } else {
@@ -1344,9 +1299,12 @@ impl Buffer {
                 }
             }
         } else {
-            new_fragments =
-                cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
-            while let Some(fragment) = cursor.item() {
+            new_fragments = fragments_cursor.slice(
+                &FragmentIdRef::new(&start_fragment_id),
+                SeekBias::Left,
+                &(),
+            );
+            while let Some(fragment) = fragments_cursor.item() {
                 if fragment.id > end_fragment_id {
                     break;
                 } else {
@@ -1358,37 +1316,42 @@ impl Buffer {
                         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 {
+                            new_visible_text.append(
+                                visible_text_cursor.slice(new_fragments.summary().text.visible),
+                            );
+                            new_deleted_text.append(
+                                deleted_text_cursor.slice(new_fragments.summary().text.deleted),
+                            );
+                        }
+
                         if fragment.visible && !was_visible {
-                            let visible_start = new_fragments.summary().text.deleted;
-                            let visible_range = visible_start..visible_start + fragment.len();
-                            new_visible_text.insert(
-                                new_fragments.summary().text.visible,
-                                &new_deleted_text.slice(visible_range.clone()).to_string(),
+                            new_visible_text.append(
+                                deleted_text_cursor
+                                    .slice(new_fragments.summary().text.deleted + fragment.len()),
                             );
-                            new_deleted_text.remove(visible_range);
                         } else if !fragment.visible && was_visible {
-                            let deleted_start = new_fragments.summary().text.visible;
-                            let deleted_range = deleted_start..deleted_start + fragment.len();
-                            new_deleted_text.insert(
-                                new_fragments.summary().text.deleted,
-                                &new_visible_text.slice(deleted_range.clone()).to_string(),
+                            new_deleted_text.append(
+                                visible_text_cursor
+                                    .slice(new_fragments.summary().text.visible + fragment.len()),
                             );
-                            new_visible_text.remove(deleted_range);
                         }
                     }
 
                     new_fragments.push(fragment, &());
-                    cursor.next();
+                    fragments_cursor.next();
                 }
             }
         }
 
-        new_fragments.push_tree(cursor.suffix(&()), &());
-        drop(cursor);
+        new_fragments.push_tree(fragments_cursor.suffix(&()), &());
+        new_visible_text.append(visible_text_cursor.suffix());
+        new_deleted_text.append(deleted_text_cursor.suffix());
+        drop(fragments_cursor);
+
+        self.fragments = new_fragments;
         self.visible_text = new_visible_text;
         self.deleted_text = new_deleted_text;
-        self.fragments = new_fragments;
 
         Ok(())
     }
@@ -1470,15 +1433,23 @@ impl Buffer {
         let mut ops = Vec::with_capacity(old_ranges.size_hint().0);
 
         let old_fragments = self.fragments.clone();
-        let mut cursor = old_fragments.cursor::<usize, usize>();
+        let old_visible_text = self.visible_text.clone();
+        let old_deleted_text = self.deleted_text.clone();
+
+        let mut fragments_cursor = old_fragments.cursor::<usize, usize>();
+        let mut visible_text_cursor = old_visible_text.cursor(0);
+        let mut deleted_text_cursor = old_deleted_text.cursor(0);
+
         let mut new_fragments = SumTree::new();
-        let mut new_visible_text = self.visible_text.clone();
-        let mut new_deleted_text = self.deleted_text.clone();
+        let mut new_visible_text = Rope::new();
+        let mut new_deleted_text = Rope::new();
 
         new_fragments.push_tree(
-            cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
+            fragments_cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
             &(),
         );
+        new_visible_text.append(visible_text_cursor.slice(new_fragments.summary().text.visible));
+        new_deleted_text.append(deleted_text_cursor.slice(new_fragments.summary().text.deleted));
 
         let mut start_id = None;
         let mut start_offset = None;
@@ -1489,10 +1460,10 @@ impl Buffer {
         let mut local_timestamp = self.local_clock.tick();
         let mut lamport_timestamp = self.lamport_clock.tick();
 
-        while cur_range.is_some() && cursor.item().is_some() {
-            let mut fragment = cursor.item().unwrap().clone();
-            let fragment_summary = cursor.item_summary().unwrap();
-            let mut fragment_start = *cursor.start();
+        while cur_range.is_some() && fragments_cursor.item().is_some() {
+            let mut fragment = fragments_cursor.item().unwrap().clone();
+            let fragment_summary = fragments_cursor.item_summary().unwrap();
+            let mut fragment_start = *fragments_cursor.start();
             let mut fragment_end = fragment_start + fragment.visible_len();
 
             let old_split_tree = self
@@ -1546,8 +1517,12 @@ impl Buffer {
                             local_timestamp,
                             lamport_timestamp,
                         );
-                        new_visible_text.insert(new_fragments.summary().text.visible, &new_text);
+
+                        new_visible_text.append(
+                            visible_text_cursor.slice(new_fragments.summary().text.visible),
+                        );
                         new_fragments.push(new_fragment, &());
+                        new_visible_text.push(&new_text);
                     }
                 }
 
@@ -1559,18 +1534,20 @@ impl Buffer {
                         prefix.id =
                             FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
                         version_in_range.observe_all(&fragment_summary.max_version);
-                        if fragment.visible {
+                        if prefix.visible {
                             prefix.deletions.insert(local_timestamp);
                             prefix.visible = false;
 
-                            // TODO: avoid calling to_string on rope slice.
-                            let deleted_start = new_fragments.summary().text.visible;
-                            let deleted_range = deleted_start..deleted_start + prefix.len();
-                            new_deleted_text.insert(
-                                new_fragments.summary().text.deleted,
-                                &new_visible_text.slice(deleted_range.clone()).to_string(),
+                            new_visible_text.append(
+                                visible_text_cursor.slice(new_fragments.summary().text.visible),
+                            );
+                            new_deleted_text.append(
+                                deleted_text_cursor.slice(new_fragments.summary().text.deleted),
+                            );
+                            new_deleted_text.append(
+                                visible_text_cursor
+                                    .slice(new_fragments.summary().text.visible + prefix.len()),
                             );
-                            new_visible_text.remove(deleted_range);
                         }
                         fragment.range_in_insertion.start = prefix.range_in_insertion.end;
                         new_fragments.push(prefix.clone(), &());
@@ -1592,14 +1569,16 @@ 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;
-                        let deleted_range = deleted_start..deleted_start + fragment.len();
-                        new_deleted_text.insert(
-                            new_fragments.summary().text.deleted,
-                            &new_visible_text.slice(deleted_range.clone()).to_string(),
+                        new_visible_text.append(
+                            visible_text_cursor.slice(new_fragments.summary().text.visible),
+                        );
+                        new_deleted_text.append(
+                            deleted_text_cursor.slice(new_fragments.summary().text.deleted),
+                        );
+                        new_deleted_text.append(
+                            visible_text_cursor
+                                .slice(new_fragments.summary().text.visible + fragment.len()),
                         );
-                        new_visible_text.remove(deleted_range);
                     }
                 }
 
@@ -1651,11 +1630,11 @@ impl Buffer {
             new_fragments.push(fragment, &());
 
             // Scan forward until we find a fragment that is not fully contained by the current splice.
-            cursor.next();
+            fragments_cursor.next();
             if let Some(range) = cur_range.clone() {
-                while let Some(fragment) = cursor.item() {
-                    let fragment_summary = cursor.item_summary().unwrap();
-                    fragment_start = *cursor.start();
+                while let Some(fragment) = fragments_cursor.item() {
+                    let fragment_summary = fragments_cursor.item_summary().unwrap();
+                    fragment_start = *fragments_cursor.start();
                     fragment_end = fragment_start + fragment.visible_len();
                     if range.start < fragment_start && range.end >= fragment_end {
                         let mut new_fragment = fragment.clone();
@@ -1664,17 +1643,20 @@ impl Buffer {
                             new_fragment.deletions.insert(local_timestamp);
                             new_fragment.visible = false;
 
-                            // TODO: avoid calling to_string on rope slice.
-                            let deleted_start = new_fragments.summary().text.visible;
-                            let deleted_range = deleted_start..deleted_start + new_fragment.len();
-                            new_deleted_text.insert(
-                                new_fragments.summary().text.deleted,
-                                &new_visible_text.slice(deleted_range.clone()).to_string(),
+                            new_visible_text.append(
+                                visible_text_cursor.slice(new_fragments.summary().text.visible),
+                            );
+                            new_deleted_text.append(
+                                deleted_text_cursor.slice(new_fragments.summary().text.deleted),
+                            );
+                            new_deleted_text.append(
+                                visible_text_cursor.slice(
+                                    new_fragments.summary().text.visible + new_fragment.len(),
+                                ),
                             );
-                            new_visible_text.remove(deleted_range);
                         }
                         new_fragments.push(new_fragment, &());
-                        cursor.next();
+                        fragments_cursor.next();
 
                         if range.end == fragment_end {
                             end_id = Some(fragment.insertion.id);
@@ -1715,7 +1697,11 @@ impl Buffer {
                 // and push all the fragments in between into the new tree.
                 if cur_range.as_ref().map_or(false, |r| r.start > fragment_end) {
                     new_fragments.push_tree(
-                        cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
+                        fragments_cursor.slice(
+                            &cur_range.as_ref().unwrap().start,
+                            SeekBias::Right,
+                            &(),
+                        ),
                         &(),
                     );
                 }
@@ -1749,19 +1735,21 @@ impl Buffer {
                     local_timestamp,
                     lamport_timestamp,
                 );
-                new_visible_text.insert(new_fragments.summary().text.visible, &new_text);
+
+                new_visible_text
+                    .append(visible_text_cursor.slice(new_fragments.summary().text.visible));
                 new_fragments.push(new_fragment, &());
+                new_visible_text.push(&new_text);
             }
-        } else {
-            new_fragments.push_tree(
-                cursor.slice(&old_fragments.extent::<usize>(), SeekBias::Right, &()),
-                &(),
-            );
         }
 
+        new_fragments.push_tree(fragments_cursor.suffix(&()), &());
+        new_visible_text.append(visible_text_cursor.suffix());
+        new_deleted_text.append(deleted_text_cursor.suffix());
+
+        self.fragments = new_fragments;
         self.visible_text = new_visible_text;
         self.deleted_text = new_deleted_text;
-        self.fragments = new_fragments;
         ops
     }
 
@@ -2031,9 +2019,9 @@ impl Buffer {
 impl Clone for Buffer {
     fn clone(&self) -> Self {
         Self {
+            fragments: self.fragments.clone(),
             visible_text: self.visible_text.clone(),
             deleted_text: self.deleted_text.clone(),
-            fragments: self.fragments.clone(),
             insertion_splits: self.insertion_splits.clone(),
             version: self.version.clone(),
             saved_version: self.saved_version.clone(),
@@ -2409,12 +2397,7 @@ pub trait ToOffset {
 
 impl ToOffset for Point {
     fn to_offset(&self, buffer: &Buffer) -> Result<usize> {
-        if *self <= buffer.max_point() {
-            // TODO: return an error if line is shorter than column.
-            Ok(buffer.visible_text.line_to_char(self.row as usize) + self.column as usize)
-        } else {
-            Err(anyhow!("point is out of bounds"))
-        }
+        buffer.visible_text.to_offset(*self)
     }
 }
 
@@ -2448,13 +2431,7 @@ impl ToPoint for Anchor {
 
 impl ToPoint for usize {
     fn to_point(&self, buffer: &Buffer) -> Result<Point> {
-        if *self <= buffer.len() {
-            let row = buffer.visible_text.char_to_line(*self);
-            let column = *self - buffer.visible_text.line_to_char(row);
-            Ok(Point::new(row as u32, column as u32))
-        } else {
-            Err(anyhow!("offset is out of bounds"))
-        }
+        buffer.visible_text.to_point(*self)
     }
 }
 

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

@@ -100,6 +100,14 @@ impl Rope {
         self.chunks.summary()
     }
 
+    pub fn len(&self) -> usize {
+        self.chunks.extent()
+    }
+
+    pub fn max_point(&self) -> Point {
+        self.chunks.extent()
+    }
+
     pub fn cursor(&self, offset: usize) -> Cursor {
         Cursor::new(self, offset)
     }
@@ -116,15 +124,7 @@ impl Rope {
         self.chunks.cursor::<(), ()>().map(|c| c.0.as_str())
     }
 
-    fn text(&self) -> String {
-        let mut text = String::new();
-        for chunk in self.chunks.cursor::<(), ()>() {
-            text.push_str(&chunk.0);
-        }
-        text
-    }
-
-    fn to_point(&self, offset: usize) -> Result<Point> {
+    pub fn to_point(&self, offset: usize) -> Result<Point> {
         if offset <= self.summary().chars {
             let mut cursor = self.chunks.cursor::<usize, TextSummary>();
             cursor.seek(&offset, SeekBias::Left, &());
@@ -135,7 +135,8 @@ impl Rope {
         }
     }
 
-    fn to_offset(&self, point: Point) -> Result<usize> {
+    pub fn to_offset(&self, point: Point) -> Result<usize> {
+        // TODO: Verify the point actually exists.
         if point <= self.summary().lines {
             let mut cursor = self.chunks.cursor::<Point, TextSummary>();
             cursor.seek(&point, SeekBias::Left, &());
@@ -162,7 +163,7 @@ pub struct Cursor<'a> {
 }
 
 impl<'a> Cursor<'a> {
-    fn new(rope: &'a Rope, offset: usize) -> Self {
+    pub fn new(rope: &'a Rope, offset: usize) -> Self {
         let mut chunks = rope.chunks.cursor();
         chunks.seek(&offset, SeekBias::Right, &());
         Self {
@@ -172,14 +173,14 @@ impl<'a> Cursor<'a> {
         }
     }
 
-    fn seek_forward(&mut self, end_offset: usize) {
+    pub fn seek_forward(&mut self, end_offset: usize) {
         debug_assert!(end_offset >= self.offset);
 
         self.chunks.seek_forward(&end_offset, SeekBias::Right, &());
         self.offset = end_offset;
     }
 
-    fn slice(&mut self, end_offset: usize) -> Rope {
+    pub fn slice(&mut self, end_offset: usize) -> Rope {
         debug_assert!(end_offset >= self.offset);
 
         let mut slice = Rope::new();
@@ -203,7 +204,7 @@ impl<'a> Cursor<'a> {
         slice
     }
 
-    fn suffix(mut self) -> Rope {
+    pub fn suffix(mut self) -> Rope {
         self.slice(self.rope.chunks.extent())
     }
 }
@@ -456,4 +457,14 @@ mod tests {
             }
         }
     }
+
+    impl Rope {
+        fn text(&self) -> String {
+            let mut text = String::new();
+            for chunk in self.chunks.cursor::<(), ()>() {
+                text.push_str(&chunk.0);
+            }
+            text
+        }
+    }
 }

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

@@ -3,6 +3,7 @@ use super::{
     Anchor, Buffer, DisplayPoint, Edit, Point, ToOffset,
 };
 use crate::{
+    editor::rope,
     sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
     time,
 };
@@ -607,7 +608,7 @@ pub struct Chars<'a> {
     cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
     offset: usize,
     buffer: &'a Buffer,
-    buffer_chars: Option<Take<ropey::iter::Chars<'a>>>,
+    buffer_chars: Option<Take<rope::Chars<'a>>>,
 }
 
 impl<'a> Iterator for Chars<'a> {

zed/src/worktree.rs 🔗

@@ -3,7 +3,7 @@ mod fuzzy;
 mod ignore;
 
 use crate::{
-    editor::History,
+    editor::{History, Rope},
     sum_tree::{self, Cursor, Edit, SeekBias, SumTree},
 };
 use ::ignore::gitignore::Gitignore;
@@ -16,7 +16,6 @@ use postage::{
     prelude::{Sink, Stream},
     watch,
 };
-use ropey::Rope;
 use smol::channel::Sender;
 use std::{
     cmp,
@@ -204,7 +203,7 @@ impl Worktree {
         let path = path.to_path_buf();
         let abs_path = self.absolutize(&path);
         ctx.background_executor().spawn(async move {
-            let buffer_size = content.len_bytes().min(10 * 1024);
+            let buffer_size = content.summary().bytes.min(10 * 1024);
             let file = fs::File::create(&abs_path)?;
             let mut writer = io::BufWriter::with_capacity(buffer_size, &file);
             for chunk in content.chunks() {