mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 11:29:25 +00:00
Overhaul MultiBuffer::chunks
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
bcdb4ffd88
commit
5118f27a90
2 changed files with 123 additions and 103 deletions
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue