Overhaul MultiBuffer::chunks

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2021-12-15 17:22:04 +01:00
parent bcdb4ffd88
commit 5118f27a90
2 changed files with 123 additions and 103 deletions

View file

@ -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::<String>();
let buffer = MultiBuffer::build_simple(&text, cx);
let buffer = if rng.gen() {
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
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::<String>();
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::<String>()
.collect::<String>(),
"chunks({:?}..{:?})",
start,
end
);
let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end);

View file

@ -113,10 +113,8 @@ struct ExcerptSummary {
pub struct MultiBufferChunks<'a> {
range: Range<usize>,
cursor: Cursor<'a, Excerpt, usize>,
header_height: u8,
has_trailing_newline: bool,
excerpt_chunks: Option<BufferChunks<'a>>,
excerpts: Cursor<'a, Excerpt, usize>,
excerpt_chunks: Option<ExcerptChunks<'a>>,
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<T>,
theme: Option<&'a SyntaxTheme>,
) -> MultiBufferChunks<'a> {
let mut result = MultiBufferChunks {
range: 0..range.end.to_offset(self),
cursor: self.excerpts.cursor::<usize>(),
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<usize>,
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<usize>) -> 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<Self::Item> {
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<Self::Item> {
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::<String>(),
expected_text[..end_ix].chars().rev().collect::<String>(),
@ -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