From 5118f27a9062101a2022333acd2bb2201138853e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 15 Dec 2021 17:22:04 +0100 Subject: [PATCH] Overhaul `MultiBuffer::chunks` Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/tab_map.rs | 16 +- crates/editor/src/multi_buffer.rs | 210 ++++++++++++----------- 2 files changed, 123 insertions(+), 103 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 5f62582581..14e54c9523 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -451,11 +451,15 @@ mod tests { } #[gpui::test(iterations = 100)] - fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + fn test_random_tabs(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { let tab_size = rng.gen_range(1..=4); let len = rng.gen_range(0..30); - let text = RandomCharIter::new(&mut rng).take(len).collect::(); - let buffer = MultiBuffer::build_simple(&text, cx); + let buffer = if rng.gen() { + let text = RandomCharIter::new(&mut rng).take(len).collect::(); + MultiBuffer::build_simple(&text, cx) + } else { + MultiBuffer::build_random(rng.gen_range(1..=5), &mut rng, cx) + }; let buffer_snapshot = buffer.read(cx).snapshot(cx); log::info!("Buffer text: {:?}", buffer_snapshot.text()); @@ -488,13 +492,15 @@ mod tests { .chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0)) .collect::(); let expected_summary = TextSummary::from(expected_text.as_str()); - log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text); assert_eq!( expected_text, tabs_snapshot .chunks(start..end, None) .map(|c| c.text) - .collect::() + .collect::(), + "chunks({:?}..{:?})", + start, + end ); let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end); diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index bc15424ae9..59f6db3ced 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -113,10 +113,8 @@ struct ExcerptSummary { pub struct MultiBufferChunks<'a> { range: Range, - cursor: Cursor<'a, Excerpt, usize>, - header_height: u8, - has_trailing_newline: bool, - excerpt_chunks: Option>, + excerpts: Cursor<'a, Excerpt, usize>, + excerpt_chunks: Option>, theme: Option<&'a SyntaxTheme>, } @@ -127,6 +125,12 @@ pub struct MultiBufferBytes<'a> { chunk: &'a [u8], } +struct ExcerptChunks<'a> { + header_height: usize, + content_chunks: BufferChunks<'a>, + footer_height: usize, +} + struct ExcerptBytes<'a> { header_height: usize, content_bytes: language::rope::Bytes<'a>, @@ -1039,16 +1043,15 @@ impl MultiBufferSnapshot { range: Range, theme: Option<&'a SyntaxTheme>, ) -> MultiBufferChunks<'a> { - let mut result = MultiBufferChunks { - range: 0..range.end.to_offset(self), - cursor: self.excerpts.cursor::(), - header_height: 0, + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut chunks = MultiBufferChunks { + range: range.clone(), + excerpts: self.excerpts.cursor(), excerpt_chunks: None, - has_trailing_newline: false, theme, }; - result.seek(range.start.to_offset(self)); - result + chunks.seek(range.start); + chunks } pub fn offset_to_point(&self, offset: usize) -> Point { @@ -1626,6 +1629,38 @@ impl Excerpt { } } + fn chunks_in_range<'a>( + &'a self, + range: Range, + theme: Option<&'a SyntaxTheme>, + ) -> ExcerptChunks<'a> { + let content_start = self.range.start.to_offset(&self.buffer); + let chunks_start = content_start + range.start.saturating_sub(self.header_height as usize); + let mut chunks_end = content_start + + cmp::min(range.end, self.text_summary.bytes) + .saturating_sub(self.header_height as usize); + + let header_height = cmp::min( + (self.header_height as usize).saturating_sub(range.start), + range.len(), + ); + let mut footer_height = 0; + if self.has_trailing_newline && range.end == self.text_summary.bytes { + chunks_end -= 1; + if !range.is_empty() { + footer_height = 1; + } + } + + let content_chunks = self.buffer.chunks(chunks_start..chunks_end, theme); + + ExcerptChunks { + header_height, + content_chunks, + footer_height, + } + } + fn bytes_in_range(&self, range: Range) -> ExcerptBytes { let content_start = self.range.start.to_offset(&self.buffer); let bytes_start = content_start + range.start.saturating_sub(self.header_height as usize); @@ -1738,38 +1773,18 @@ impl<'a> MultiBufferChunks<'a> { pub fn seek(&mut self, offset: usize) { self.range.start = offset; - self.cursor.seek_forward(&offset, Bias::Right, &()); - self.header_height = 0; - self.excerpt_chunks = None; - if let Some(excerpt) = self.cursor.item() { - let buffer_range = excerpt.range.to_offset(&excerpt.buffer); - self.header_height = excerpt.header_height; - self.has_trailing_newline = excerpt.has_trailing_newline; - - let buffer_start; - let start_overshoot = self.range.start - self.cursor.start(); - if start_overshoot < excerpt.header_height as usize { - self.header_height -= start_overshoot as u8; - buffer_start = buffer_range.start; - } else { - buffer_start = - buffer_range.start + start_overshoot - excerpt.header_height as usize; - self.header_height = 0; - } - - let buffer_end; - let end_overshoot = self.range.end - self.cursor.start(); - if end_overshoot < excerpt.header_height as usize { - self.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 - excerpt.header_height as usize, - ); - } - - self.excerpt_chunks = Some(excerpt.buffer.chunks(buffer_start..buffer_end, self.theme)); + self.excerpts.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = self.excerpts.item() { + self.excerpt_chunks = Some(excerpt.chunks_in_range( + self.range.start - self.excerpts.start() + ..cmp::min( + self.range.end - self.excerpts.start(), + excerpt.text_summary.bytes, + ), + self.theme, + )); + } else { + self.excerpt_chunks = None; } } } @@ -1778,56 +1793,22 @@ impl<'a> Iterator for MultiBufferChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { - loop { - if self.header_height > 0 { - let chunk = Chunk { - text: unsafe { - str::from_utf8_unchecked(&NEWLINES[..self.header_height as usize]) - }, - ..Default::default() - }; - self.range.start += self.header_height as usize; - self.header_height = 0; - return Some(chunk); - } - - if let Some(excerpt_chunks) = self.excerpt_chunks.as_mut() { - if let Some(chunk) = excerpt_chunks.next() { - self.range.start += chunk.text.len(); - return Some(chunk); - } - self.excerpt_chunks.take(); - if self.has_trailing_newline && self.cursor.end(&()) <= self.range.end { - self.range.start += 1; - return Some(Chunk { - text: "\n", - ..Default::default() - }); - } - } - - self.cursor.next(&()); - if *self.cursor.start() >= self.range.end { - return None; - } - - let excerpt = self.cursor.item()?; - let buffer_range = excerpt.range.to_offset(&excerpt.buffer); - - let buffer_end = cmp::min( - buffer_range.end, - buffer_range.start + self.range.end - - excerpt.header_height as usize - - self.cursor.start(), - ); - - self.header_height = excerpt.header_height; - self.has_trailing_newline = excerpt.has_trailing_newline; - self.excerpt_chunks = Some( - excerpt - .buffer - .chunks(buffer_range.start..buffer_end, self.theme), - ); + if self.range.is_empty() { + None + } else if let Some(chunk) = self.excerpt_chunks.as_mut()?.next() { + self.range.start += chunk.text.len(); + Some(chunk) + } else { + self.excerpts.next(&()); + let excerpt = self.excerpts.item()?; + self.excerpt_chunks = Some(excerpt.chunks_in_range( + 0..cmp::min( + self.range.end - self.excerpts.start(), + excerpt.text_summary.bytes, + ), + self.theme, + )); + self.next() } } } @@ -1908,6 +1889,38 @@ impl<'a> Iterator for ExcerptBytes<'a> { } } +impl<'a> Iterator for ExcerptChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.header_height > 0 { + let text = unsafe { str::from_utf8_unchecked(&NEWLINES[..self.header_height]) }; + self.header_height = 0; + return Some(Chunk { + text, + ..Default::default() + }); + } + + if let Some(chunk) = self.content_chunks.next() { + if !chunk.text.is_empty() { + return Some(chunk); + } + } + + if self.footer_height > 0 { + let text = unsafe { str::from_utf8_unchecked(&NEWLINES[..self.footer_height]) }; + self.footer_height = 0; + return Some(Chunk { + text, + ..Default::default() + }); + } + + None + } +} + impl ToOffset for Point { fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { snapshot.point_to_offset(*self) @@ -1943,7 +1956,7 @@ impl ToPoint for Point { mod tests { use super::*; use gpui::{elements::Empty, Element, MutableAppContext}; - use language::Buffer; + use language::{Buffer, Rope}; use rand::prelude::*; use std::env; use text::{Point, RandomCharIter}; @@ -2386,9 +2399,10 @@ mod tests { ); } + let text_rope = Rope::from(expected_text.as_str()); for _ 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 end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right); + let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); assert_eq!( snapshot @@ -2409,7 +2423,7 @@ mod tests { } for _ in 0..10 { - let end_ix = snapshot.clip_offset(rng.gen_range(0..=snapshot.len()), Bias::Right); + let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right); assert_eq!( snapshot.reversed_chars_at(end_ix).collect::(), expected_text[..end_ix].chars().rev().collect::(), @@ -2417,7 +2431,7 @@ mod tests { } for _ in 0..10 { - let end_ix = rng.gen_range(0..=snapshot.len()); + let end_ix = rng.gen_range(0..=text_rope.len()); let start_ix = rng.gen_range(0..=end_ix); assert_eq!( snapshot