WIP

Nathan Sobo and Max Brunsfeld created

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/language/src/excerpt_list.rs | 147 +++++++++++++++++++++++-------
1 file changed, 110 insertions(+), 37 deletions(-)

Detailed changes

crates/language/src/excerpt_list.rs 🔗

@@ -3,7 +3,10 @@ use collections::HashMap;
 use gpui::{AppContext, Entity, ModelContext, ModelHandle};
 use parking_lot::Mutex;
 use smallvec::{smallvec, SmallVec};
-use std::{cmp, iter, ops::Range};
+use std::{
+    cmp, iter,
+    ops::{Deref, Range},
+};
 use sum_tree::{Bias, Cursor, SumTree};
 use text::{
     subscription::{Subscription, Topic},
@@ -146,15 +149,6 @@ impl ExcerptList {
             new_excerpts.push_tree(cursor.slice(id, Bias::Left, &()), &());
             let old_excerpt = cursor.item().unwrap();
             let buffer = buffer_state.buffer.read(cx);
-            new_excerpts.push(
-                Excerpt::new(
-                    id.clone(),
-                    buffer.snapshot(),
-                    old_excerpt.range.clone(),
-                    old_excerpt.header_height,
-                ),
-                &(),
-            );
 
             edits.extend(
                 buffer
@@ -163,8 +157,10 @@ impl ExcerptList {
                         old_excerpt.range.clone(),
                     )
                     .map(|mut edit| {
-                        let excerpt_old_start = cursor.start().1;
-                        let excerpt_new_start = new_excerpts.summary().text.bytes;
+                        let excerpt_old_start =
+                            cursor.start().1 + old_excerpt.header_height as usize;
+                        let excerpt_new_start =
+                            new_excerpts.summary().text.bytes + old_excerpt.header_height as usize;
                         edit.old.start += excerpt_old_start;
                         edit.old.end += excerpt_old_start;
                         edit.new.start += excerpt_new_start;
@@ -173,6 +169,16 @@ impl ExcerptList {
                     }),
             );
 
+            new_excerpts.push(
+                Excerpt::new(
+                    id.clone(),
+                    buffer.snapshot(),
+                    old_excerpt.range.clone(),
+                    old_excerpt.header_height,
+                ),
+                &(),
+            );
+
             cursor.next(&());
         }
         new_excerpts.push_tree(cursor.suffix(&()), &());
@@ -195,6 +201,13 @@ impl Snapshot {
             .collect()
     }
 
+    pub fn text_for_range<'a, T: ToOffset>(
+        &'a self,
+        range: Range<T>,
+    ) -> impl Iterator<Item = &'a str> {
+        self.chunks(range, None).map(|chunk| chunk.text)
+    }
+
     pub fn len(&self) -> usize {
         self.excerpts.summary().text.bytes
     }
@@ -208,16 +221,34 @@ impl Snapshot {
         let mut cursor = self.excerpts.cursor::<usize>();
         cursor.seek(&range.start, Bias::Right, &());
 
-        let entry_chunks = cursor.item().map(|entry| {
-            let buffer_range = entry.range.to_offset(&entry.buffer);
-            let buffer_start = buffer_range.start + (range.start - cursor.start());
-            let buffer_end = cmp::min(
-                buffer_range.end,
-                buffer_range.start + (range.end - cursor.start()),
-            );
-            entry.buffer.chunks(buffer_start..buffer_end, theme)
+        let mut header_height: u8 = 0;
+        let entry_chunks = cursor.item().map(|excerpt| {
+            let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
+            header_height = excerpt.header_height;
+            let start_overshoot = range.start - cursor.start();
+            let buffer_start;
+            if start_overshoot < excerpt.header_height as usize {
+                header_height -= start_overshoot as u8;
+                buffer_start = buffer_range.start;
+            } else {
+                buffer_start = buffer_range.start + start_overshoot - header_height as usize;
+                header_height = 0;
+            }
+
+            let end_overshoot = range.end - cursor.start();
+            let buffer_end;
+            if end_overshoot < excerpt.header_height as usize {
+                header_height -= excerpt.header_height - end_overshoot as u8;
+                buffer_end = buffer_start;
+            } else {
+                buffer_end = cmp::min(
+                    buffer_range.end,
+                    buffer_range.start + end_overshoot - header_height as usize,
+                );
+            }
+
+            excerpt.buffer.chunks(buffer_start..buffer_end, theme)
         });
-        let header_height = cursor.item().map_or(0, |entry| entry.header_height);
 
         Chunks {
             range,
@@ -242,7 +273,15 @@ impl Excerpt {
             text_summary.lines.row += header_height as u32;
             text_summary.lines_utf16.row += header_height as u32;
             text_summary.bytes += header_height as usize;
+            text_summary.longest_row += header_height as u32;
         }
+        text_summary.last_line_chars = 0;
+        text_summary.lines.row += 1;
+        text_summary.lines.column = 0;
+        text_summary.lines_utf16.row += 1;
+        text_summary.lines_utf16.column = 0;
+        text_summary.bytes += 1;
+
         Excerpt {
             id,
             buffer,
@@ -307,6 +346,10 @@ impl<'a> Iterator for Chunks<'a> {
                 return Some(chunk);
             } else {
                 self.entry_chunks.take();
+                return Some(Chunk {
+                    text: "\n",
+                    ..Default::default()
+                });
             }
         }
 
@@ -325,10 +368,7 @@ impl<'a> Iterator for Chunks<'a> {
                 .chunks(buffer_range.start..buffer_end, self.theme),
         );
 
-        Some(Chunk {
-            text: "\n",
-            ..Default::default()
-        })
+        self.next()
     }
 }
 
@@ -399,7 +439,7 @@ mod tests {
                 subscription.consume().into_inner(),
                 [Edit {
                     old: 0..0,
-                    new: 0..12
+                    new: 0..13
                 }]
             );
 
@@ -422,8 +462,8 @@ mod tests {
             assert_eq!(
                 subscription.consume().into_inner(),
                 [Edit {
-                    old: 12..12,
-                    new: 12..26
+                    old: 13..13,
+                    new: 13..29
                 }]
             );
 
@@ -443,7 +483,7 @@ mod tests {
                 "\n",      //
                 "\n",      //
                 "\n",      //
-                "jj"       //
+                "jj\n"     //
             )
         );
 
@@ -472,15 +512,15 @@ mod tests {
                 "\n",     //
                 "\n",     //
                 "\n",     //
-                "jj"      //
+                "jj\n"    //
             )
         );
 
         assert_eq!(
             subscription.consume().into_inner(),
             [Edit {
-                old: 17..19,
-                new: 17..18
+                old: 18..20,
+                new: 18..19
             }]
         );
     }
@@ -495,12 +535,13 @@ mod tests {
         let list = cx.add_model(|_| ExcerptList::new());
         let mut excerpt_ids = Vec::new();
         let mut expected_excerpts = Vec::new();
+        let mut old_versions = Vec::new();
 
         for _ in 0..operations {
             match rng.gen_range(0..100) {
                 0..=19 if !buffers.is_empty() => {
                     let buffer = buffers.choose(&mut rng).unwrap();
-                    buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx));
+                    buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 1, cx));
                 }
                 _ => {
                     let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
@@ -539,21 +580,53 @@ mod tests {
                 }
             }
 
+            if rng.gen_bool(0.3) {
+                list.update(cx, |list, cx| {
+                    old_versions.push((list.snapshot(cx), list.subscribe()));
+                })
+            }
+
             let snapshot = list.read(cx).snapshot(cx);
             let mut expected_text = String::new();
             for (buffer, range, header_height) in &expected_excerpts {
                 let buffer = buffer.read(cx);
-                if !expected_text.is_empty() {
-                    expected_text.push('\n');
-                }
-
                 for _ in 0..*header_height {
                     expected_text.push('\n');
                 }
                 expected_text.extend(buffer.text_for_range(range.clone()));
+                expected_text.push('\n');
             }
             assert_eq!(snapshot.text(), expected_text);
+
+            for i in 0..10 {
+                let end_ix = snapshot.clip_offset(rng.gen_range(0..=snapshot.len()), Bias::Right);
+                let start_ix = snapshot.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
+
+                let actual = snapshot.text_for_range(start_ix..end_ix).collect();
+            }
+            // for i in 0..expected_text.len() {
+            //     assert_eq!(snapshot.text(), expected_text);
+
+            // }
         }
+
+        // let snapshot = list.read(cx).snapshot(cx);
+        // for (old_snapshot, subscription) in old_versions {
+        //     let edits = subscription.consume().into_inner();
+
+        //     log::info!(
+        //         "applying edits since old text: {:?}: {:?}",
+        //         old_snapshot.text(),
+        //         edits,
+        //     );
+
+        //     let mut text = old_snapshot.text();
+        //     for edit in edits {
+        //         let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
+        //         text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
+        //     }
+        //     assert_eq!(text.to_string(), snapshot.text());
+        // }
     }
 
     #[gpui::test(iterations = 100)]