diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 6c35165eca..deb476d6c7 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -111,6 +111,12 @@ struct ExcerptSummary { text: TextSummary, } +pub struct MultiBufferRows<'a> { + header_height: u32, + buffer_row_range: Range, + excerpts: Cursor<'a, Excerpt, Point>, +} + pub struct MultiBufferChunks<'a> { range: Range, excerpts: Cursor<'a, Excerpt, usize>, @@ -1052,6 +1058,32 @@ impl MultiBufferSnapshot { } } + pub fn buffer_rows<'a>(&'a self, start_row: u32) -> MultiBufferRows<'a> { + let mut excerpts = self.excerpts.cursor::(); + excerpts.seek(&Point::new(start_row, 0), Bias::Right, &()); + if excerpts.item().is_none() { + excerpts.prev(&()); + } + + let mut header_height = 0; + let mut buffer_row_range = 0..0; + if let Some(excerpt) = excerpts.item() { + let overshoot = start_row - excerpts.start().row; + let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer).row; + let excerpt_header_height = excerpt.header_height as u32; + header_height = excerpt_header_height.saturating_sub(overshoot); + buffer_row_range.start = + excerpt_start + overshoot.saturating_sub(excerpt_header_height); + buffer_row_range.end = + excerpt_start + excerpt.text_summary.lines.row + 1 - excerpt_header_height; + } + MultiBufferRows { + header_height, + buffer_row_range, + excerpts, + } + } + pub fn chunks<'a, T: ToOffset>( &'a self, range: Range, @@ -1821,6 +1853,34 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a ExcerptId> { } } +impl<'a> Iterator for MultiBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + loop { + if self.header_height > 0 { + self.header_height -= 1; + return Some(None); + } + if !self.buffer_row_range.is_empty() { + let row = Some(self.buffer_row_range.start); + self.buffer_row_range.start += 1; + return Some(row); + } + self.excerpts.next(&()); + if let Some(excerpt) = self.excerpts.item() { + self.header_height = excerpt.header_height as u32; + self.buffer_row_range.start = excerpt.range.start.to_point(&excerpt.buffer).row; + self.buffer_row_range.end = + self.buffer_row_range.start + excerpt.text_summary.lines.row + 1 + - self.header_height; + } else { + return None; + } + } + } +} + impl<'a> MultiBufferChunks<'a> { pub fn offset(&self) -> usize { self.range.start @@ -2009,15 +2069,26 @@ mod tests { fn test_singleton_multibuffer(cx: &mut MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx)); let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), buffer.read(cx).text()); + assert_eq!( - multibuffer.read(cx).snapshot(cx).text(), - buffer.read(cx).text() + snapshot.buffer_rows(0).collect::>(), + (0..buffer.read(cx).row_count()) + .map(Some) + .collect::>() ); - buffer.update(cx, |buffer, cx| buffer.edit([1..3], "XXX", cx)); + buffer.update(cx, |buffer, cx| buffer.edit([1..3], "XXX\n", cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(snapshot.text(), buffer.read(cx).text()); assert_eq!( - multibuffer.read(cx).snapshot(cx).text(), - buffer.read(cx).text() + snapshot.buffer_rows(0).collect::>(), + (0..buffer.read(cx).row_count()) + .map(Some) + .collect::>() ); } @@ -2075,8 +2146,9 @@ mod tests { subscription }); + let snapshot = multibuffer.read(cx).snapshot(cx); assert_eq!( - multibuffer.read(cx).snapshot(cx).text(), + snapshot.text(), concat!( "\n", // Preserve newlines "\n", // @@ -2091,6 +2163,22 @@ mod tests { "jj" // ) ); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + &[ + None, + None, + Some(1), + Some(2), + None, + Some(3), + Some(4), + None, + None, + None, + Some(3) + ] + ); { let snapshot = multibuffer.read(cx).read(cx); @@ -2342,17 +2430,25 @@ mod tests { let mut excerpt_starts = Vec::new(); let mut expected_text = String::new(); + let mut expected_buffer_rows = Vec::new(); for (buffer, range, header_height) in &expected_excerpts { let buffer = buffer.read(cx); let buffer_range = range.to_offset(buffer); for _ in 0..*header_height { expected_text.push('\n'); + expected_buffer_rows.push(None); } excerpt_starts.push(TextSummary::from(expected_text.as_str())); expected_text.extend(buffer.text_for_range(buffer_range.clone())); expected_text.push('\n'); + + let buffer_row_range = buffer.offset_to_point(buffer_range.start).row + ..=buffer.offset_to_point(buffer_range.end).row; + for row in buffer_row_range { + expected_buffer_rows.push(Some(row)); + } } // Remove final trailing newline. if !expected_excerpts.is_empty() { @@ -2362,6 +2458,21 @@ mod tests { assert_eq!(snapshot.text(), expected_text); log::info!("MultiBuffer text: {:?}", expected_text); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + expected_buffer_rows, + ); + + for _ in 0..5 { + let start_row = rng.gen_range(0..=expected_buffer_rows.len()); + assert_eq!( + snapshot.buffer_rows(start_row as u32).collect::>(), + &expected_buffer_rows[start_row..], + "buffer_rows({})", + start_row + ); + } + let mut excerpt_starts = excerpt_starts.into_iter(); for (buffer, range, _) in &expected_excerpts { let buffer_id = buffer.id();