Implement `MultiBuffer::reversed_chars_at`

Antonio Scandurra created

Change summary

crates/editor/src/multi_buffer.rs | 55 ++++++++++++++++++++++++++++++--
crates/text/src/text.rs           |  5 +++
2 files changed, 56 insertions(+), 4 deletions(-)

Detailed changes

crates/editor/src/multi_buffer.rs 🔗

@@ -14,6 +14,7 @@ use std::{
     cmp, io,
     iter::{self, FromIterator, Peekable},
     ops::{Range, Sub},
+    str,
     sync::Arc,
     time::{Duration, Instant, SystemTime},
 };
@@ -754,9 +755,47 @@ impl MultiBufferSnapshot {
         &'a self,
         position: T,
     ) -> impl Iterator<Item = char> + 'a {
-        // TODO
-        let offset = position.to_offset(self);
-        self.as_singleton().unwrap().reversed_chars_at(offset)
+        let mut offset = position.to_offset(self);
+        let mut cursor = self.excerpts.cursor::<usize>();
+        cursor.seek(&offset, Bias::Left, &());
+        let mut excerpt_chunks = cursor.item().map(|excerpt| {
+            let start_after_header = cursor.start() + excerpt.header_height as usize;
+            let mut end_before_footer = cursor.start() + excerpt.text_summary.bytes;
+            if excerpt.has_trailing_newline {
+                end_before_footer -= 1;
+            }
+
+            let start = excerpt.range.start.to_offset(&excerpt.buffer);
+            let end =
+                start + (cmp::min(offset, end_before_footer).saturating_sub(start_after_header));
+            excerpt.buffer.reversed_chunks_in_range(start..end)
+        });
+        iter::from_fn(move || {
+            if offset == *cursor.start() {
+                cursor.prev(&());
+                let excerpt = cursor.item()?;
+                excerpt_chunks = Some(
+                    excerpt
+                        .buffer
+                        .reversed_chunks_in_range(excerpt.range.clone()),
+                );
+            }
+
+            let excerpt = cursor.item().unwrap();
+            if offset <= cursor.start() + excerpt.header_height as usize {
+                let header_height = offset - cursor.start();
+                offset -= header_height;
+                Some(unsafe { str::from_utf8_unchecked(&NEWLINES[..header_height]) })
+            } else if offset == cursor.end(&()) && excerpt.has_trailing_newline {
+                offset -= 1;
+                Some("\n")
+            } else {
+                let chunk = excerpt_chunks.as_mut().unwrap().next().unwrap();
+                offset -= chunk.len();
+                Some(chunk)
+            }
+        })
+        .flat_map(|c| c.chars().rev())
     }
 
     pub fn chars_at<'a, T: ToOffset>(&'a self, position: T) -> impl Iterator<Item = char> + 'a {
@@ -1593,7 +1632,7 @@ impl<'a> Iterator for MultiBufferChunks<'a> {
             if self.header_height > 0 {
                 let chunk = Chunk {
                     text: unsafe {
-                        std::str::from_utf8_unchecked(&NEWLINES[..self.header_height as usize])
+                        str::from_utf8_unchecked(&NEWLINES[..self.header_height as usize])
                     },
                     ..Default::default()
                 };
@@ -2152,6 +2191,14 @@ mod tests {
                     start_ix..end_ix
                 );
             }
+
+            for _ in 0..10 {
+                let end_ix = snapshot.clip_offset(rng.gen_range(0..=snapshot.len()), Bias::Right);
+                assert_eq!(
+                    expected_text[..end_ix].chars().rev().collect::<String>(),
+                    snapshot.reversed_chars_at(end_ix).collect::<String>()
+                );
+            }
         }
 
         let snapshot = list.read(cx).snapshot(cx);

crates/text/src/text.rs 🔗

@@ -1332,6 +1332,11 @@ impl BufferSnapshot {
         self.visible_text.reversed_chars_at(offset)
     }
 
+    pub fn reversed_chunks_in_range<T: ToOffset>(&self, range: Range<T>) -> rope::Chunks {
+        let range = range.start.to_offset(self)..range.end.to_offset(self);
+        self.visible_text.reversed_chunks_in_range(range)
+    }
+
     pub fn bytes_in_range<'a, T: ToOffset>(&'a self, range: Range<T>) -> rope::Bytes<'a> {
         let start = range.start.to_offset(self);
         let end = range.end.to_offset(self);