diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 8ba6a2929a..0291471c6f 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -7729,12 +7729,12 @@ impl<'a> Iterator for MultiBufferChunks<'a> { // 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, + text: dbg!(before), ..chunk.clone() }) } else { self.range.start = chunk_end; - self.buffer_chunk.take() + dbg!(self.buffer_chunk.take()) } } DiffTransform::DeletedHunk { @@ -7767,12 +7767,13 @@ impl<'a> Iterator for MultiBufferChunks<'a> { let chunk = if let Some(chunk) = chunks.next() { self.range.start += chunk.text.len(); self.diff_base_chunks = Some((*buffer_id, chunks)); - chunk + dbg!(chunk) } else { debug_assert!(has_trailing_newline); self.range.start += "\n".len(); Chunk { text: "\n", + chars: 1u128, ..Default::default() } }; @@ -7868,9 +7869,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() }); } diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index 75c9d0cfc8..22395b97d5 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -3721,7 +3721,6 @@ fn test_new_empty_buffers_title_can_be_set(cx: &mut App) { #[gpui::test(iterations = 100)] fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) { - // Generate random multibuffer using existing test infrastructure let multibuffer = if rng.r#gen() { let len = rng.gen_range(0..10000); let text = RandomCharIter::new(&mut rng).take(len).collect::(); @@ -3733,7 +3732,6 @@ fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) { let snapshot = multibuffer.read(cx).snapshot(cx); - // Get all chunks and verify their bitmaps let chunks = snapshot.chunks(0..snapshot.len(), false); for chunk in chunks { @@ -3741,7 +3739,6 @@ fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) { let chars_bitmap = chunk.chars; let tabs_bitmap = chunk.tabs; - // Check empty chunks have empty bitmaps if chunk_text.is_empty() { assert_eq!( chars_bitmap, 0, @@ -3751,7 +3748,6 @@ fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) { continue; } - // Verify that chunk text doesn't exceed 128 bytes assert!( chunk_text.len() <= 128, "Chunk text length {} exceeds 128 bytes", @@ -3781,7 +3777,147 @@ fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) { ); } - // Verify tabs bitmap + 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::(); + 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::(); + 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::(); + 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 mut ranges = Vec::new(); + for _ in 0..rng.gen_range(1..5) { + let start = rng.gen_range(0..snapshot.len()); + let end = rng.gen_range(start..snapshot.len().min(start + 1000)); + 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::>(); + + 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;