This commit is contained in:
Anthony Eid 2025-08-26 19:50:54 +01:00 committed by GitHub
commit e099f6df52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1892 additions and 88 deletions

View file

@ -7723,12 +7723,24 @@ impl<'a> Iterator for MultiBufferChunks<'a> {
let diff_transform_end = diff_transform_end.min(self.range.end);
if diff_transform_end < chunk_end {
let (before, after) =
chunk.text.split_at(diff_transform_end - self.range.start);
let split_idx = diff_transform_end - self.range.start;
let (before, after) = chunk.text.split_at(split_idx);
self.range.start = diff_transform_end;
let mask = (1 << split_idx) - 1;
let chars = chunk.chars & mask;
let tabs = chunk.tabs & mask;
chunk.text = after;
chunk.chars = chunk.chars >> split_idx;
chunk.tabs = chunk.tabs >> split_idx;
// FIXME: We should be handling bitmap for tabs and chars here
// Because we do a split at operation the bitmaps will be off
Some(Chunk {
text: before,
chars,
tabs,
..chunk.clone()
})
} else {
@ -7772,6 +7784,7 @@ impl<'a> Iterator for MultiBufferChunks<'a> {
self.range.start += "\n".len();
Chunk {
text: "\n",
chars: 1u128,
..Default::default()
}
};
@ -7868,9 +7881,11 @@ impl<'a> Iterator for ExcerptChunks<'a> {
if self.footer_height > 0 {
let text = unsafe { str::from_utf8_unchecked(&NEWLINES[..self.footer_height]) };
let chars = (1 << self.footer_height) - 1;
self.footer_height = 0;
return Some(Chunk {
text,
chars,
..Default::default()
});
}

View file

@ -7,6 +7,7 @@ use parking_lot::RwLock;
use rand::prelude::*;
use settings::SettingsStore;
use std::env;
use util::RandomCharIter;
use util::test::sample_text;
#[ctor::ctor]
@ -3712,3 +3713,235 @@ fn test_new_empty_buffers_title_can_be_set(cx: &mut App) {
});
assert_eq!(multibuffer.read(cx).title(cx), "Hey");
}
#[gpui::test(iterations = 100)]
fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
let multibuffer = if rng.r#gen() {
let len = rng.gen_range(0..10000);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
let buffer = cx.new(|cx| Buffer::local(text, cx));
cx.new(|cx| MultiBuffer::singleton(buffer, cx))
} else {
MultiBuffer::build_random(&mut rng, cx)
};
let snapshot = multibuffer.read(cx).snapshot(cx);
let chunks = snapshot.chunks(0..snapshot.len(), false);
for chunk in chunks {
let chunk_text = chunk.text;
let chars_bitmap = chunk.chars;
let tabs_bitmap = chunk.tabs;
if chunk_text.is_empty() {
assert_eq!(
chars_bitmap, 0,
"Empty chunk should have empty chars bitmap"
);
assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
continue;
}
assert!(
chunk_text.len() <= 128,
"Chunk text length {} exceeds 128 bytes",
chunk_text.len()
);
// Verify chars bitmap
let char_indices = chunk_text
.char_indices()
.map(|(i, _)| i)
.collect::<Vec<_>>();
for byte_idx in 0..chunk_text.len() {
let should_have_bit = char_indices.contains(&byte_idx);
let has_bit = chars_bitmap & (1 << byte_idx) != 0;
if has_bit != should_have_bit {
eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
eprintln!("Char indices: {:?}", char_indices);
eprintln!("Chars bitmap: {:#b}", chars_bitmap);
}
assert_eq!(
has_bit, should_have_bit,
"Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
byte_idx, chunk_text, should_have_bit, has_bit
);
}
for (byte_idx, byte) in chunk_text.bytes().enumerate() {
let is_tab = byte == b'\t';
let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
if has_bit != is_tab {
eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
assert_eq!(
has_bit, is_tab,
"Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
byte_idx, chunk_text, byte as char, is_tab, has_bit
);
}
}
}
}
#[gpui::test(iterations = 100)]
fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
use buffer_diff::BufferDiff;
use util::RandomCharIter;
let multibuffer = if rng.r#gen() {
let len = rng.gen_range(100..10000);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
let buffer = cx.new(|cx| Buffer::local(text, cx));
cx.new(|cx| MultiBuffer::singleton(buffer, cx))
} else {
MultiBuffer::build_random(&mut rng, cx)
};
let _diff_count = rng.gen_range(1..5);
let mut diffs = Vec::new();
multibuffer.update(cx, |multibuffer, cx| {
for buffer_id in multibuffer.excerpt_buffer_ids() {
if rng.gen_bool(0.7) {
if let Some(buffer_handle) = multibuffer.buffer(buffer_id) {
let buffer_text = buffer_handle.read(cx).text();
let mut base_text = String::new();
for line in buffer_text.lines() {
if rng.gen_bool(0.3) {
continue;
} else if rng.gen_bool(0.3) {
let line_len = rng.gen_range(0..50);
let modified_line = RandomCharIter::new(&mut rng)
.take(line_len)
.collect::<String>();
base_text.push_str(&modified_line);
base_text.push('\n');
} else {
base_text.push_str(line);
base_text.push('\n');
}
}
if rng.gen_bool(0.5) {
let extra_lines = rng.gen_range(1..5);
for _ in 0..extra_lines {
let line_len = rng.gen_range(0..50);
let extra_line = RandomCharIter::new(&mut rng)
.take(line_len)
.collect::<String>();
base_text.push_str(&extra_line);
base_text.push('\n');
}
}
let diff =
cx.new(|cx| BufferDiff::new_with_base_text(&base_text, &buffer_handle, cx));
diffs.push(diff.clone());
multibuffer.add_diff(diff, cx);
}
}
}
});
multibuffer.update(cx, |multibuffer, cx| {
if rng.gen_bool(0.5) {
multibuffer.set_all_diff_hunks_expanded(cx);
} else {
let snapshot = multibuffer.snapshot(cx);
let text = snapshot.text();
let mut ranges = Vec::new();
for _ in 0..rng.gen_range(1..5) {
if snapshot.len() == 0 {
break;
}
let diff_size = rng.gen_range(5..1000);
let mut start = rng.gen_range(0..snapshot.len());
while !text.is_char_boundary(start) {
start = start.saturating_sub(1);
}
let mut end = rng.gen_range(start..snapshot.len().min(start + diff_size));
while !text.is_char_boundary(end) {
end = end.saturating_add(1);
}
let start_anchor = snapshot.anchor_after(start);
let end_anchor = snapshot.anchor_before(end);
ranges.push(start_anchor..end_anchor);
}
multibuffer.expand_diff_hunks(ranges, cx);
}
});
let snapshot = multibuffer.read(cx).snapshot(cx);
let chunks = snapshot.chunks(0..snapshot.len(), false);
for chunk in chunks {
let chunk_text = chunk.text;
let chars_bitmap = chunk.chars;
let tabs_bitmap = chunk.tabs;
if chunk_text.is_empty() {
assert_eq!(
chars_bitmap, 0,
"Empty chunk should have empty chars bitmap"
);
assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
continue;
}
assert!(
chunk_text.len() <= 128,
"Chunk text length {} exceeds 128 bytes",
chunk_text.len()
);
let char_indices = chunk_text
.char_indices()
.map(|(i, _)| i)
.collect::<Vec<_>>();
for byte_idx in 0..chunk_text.len() {
let should_have_bit = char_indices.contains(&byte_idx);
let has_bit = chars_bitmap & (1 << byte_idx) != 0;
if has_bit != should_have_bit {
eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
eprintln!("Char indices: {:?}", char_indices);
eprintln!("Chars bitmap: {:#b}", chars_bitmap);
}
assert_eq!(
has_bit, should_have_bit,
"Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
byte_idx, chunk_text, should_have_bit, has_bit
);
}
for (byte_idx, byte) in chunk_text.bytes().enumerate() {
let is_tab = byte == b'\t';
let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
if has_bit != is_tab {
eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
assert_eq!(
has_bit, is_tab,
"Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
byte_idx, chunk_text, byte as char, is_tab, has_bit
);
}
}
}
}