editor: Use unbounded shifts for chunk bitmaps (#40879)

This simplifies some code and is also more correct in some others (I
believe some of these might've overflowed causing panics in sentry)

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Lukas Wirth
2025-10-22 11:05:32 +00:00
committed by GitHub
parent bd69124f2b
commit c81ffaffb6
12 changed files with 169 additions and 227 deletions
Generated
+1
View File
@@ -5354,6 +5354,7 @@ dependencies = [
"rand 0.9.2",
"regex",
"release_channel",
"rope",
"rpc",
"schemars 1.0.4",
"serde",
+1
View File
@@ -64,6 +64,7 @@ project.workspace = true
rand.workspace = true
regex.workspace = true
rpc.workspace = true
rope.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
+4 -4
View File
@@ -26,8 +26,8 @@ use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
use text::{BufferId, Edit};
use ui::ElementId;
const NEWLINES: &[u8; u128::BITS as usize] = &[b'\n'; _];
const BULLETS: &[u8; u128::BITS as usize] = &[b'*'; _];
const NEWLINES: &[u8; rope::Chunk::MASK_BITS] = &[b'\n'; _];
const BULLETS: &[u8; rope::Chunk::MASK_BITS] = &[b'*'; _];
/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
///
@@ -1783,11 +1783,11 @@ impl<'a> Iterator for BlockChunks<'a> {
if self.masked {
// Not great for multibyte text because to keep cursor math correct we
// need to have the same number of bytes in the input as output.
// need to have the same number of chars in the input as output.
let chars_count = prefix.chars().count();
let bullet_len = chars_count;
prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
chars = 1u128.unbounded_shl(bullet_len as u32) - 1;
chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
tabs = 0;
}
@@ -132,37 +132,31 @@ impl<'a> Iterator for CustomHighlightsChunks<'a> {
}
}
let chunk = self
.buffer_chunk
.get_or_insert_with(|| self.buffer_chunks.next().unwrap_or_default());
if chunk.text.is_empty() {
let chunk = match &mut self.buffer_chunk {
Some(it) => it,
slot => slot.insert(self.buffer_chunks.next()?),
};
while chunk.text.is_empty() {
*chunk = self.buffer_chunks.next()?;
}
let split_idx = chunk.text.len().min(next_highlight_endpoint - self.offset);
let (prefix, suffix) = chunk.text.split_at(split_idx);
let (chars, tabs) = if split_idx == 128 {
let output = (chunk.chars, chunk.tabs);
chunk.chars = 0;
chunk.tabs = 0;
output
} else {
let mask = (1 << split_idx) - 1;
let output = (chunk.chars & mask, chunk.tabs & mask);
chunk.chars = chunk.chars >> split_idx;
chunk.tabs = chunk.tabs >> split_idx;
output
};
chunk.text = suffix;
self.offset += prefix.len();
let mask = 1u128.unbounded_shl(split_idx as u32).wrapping_sub(1);
let chars = chunk.chars & mask;
let tabs = chunk.tabs & mask;
let mut prefix = Chunk {
text: prefix,
chars,
tabs,
..chunk.clone()
};
chunk.chars = chunk.chars.unbounded_shr(split_idx as u32);
chunk.tabs = chunk.tabs.unbounded_shr(split_idx as u32);
chunk.text = suffix;
if !self.active_highlights.is_empty() {
prefix.highlight_style = self
.active_highlights
+5 -4
View File
@@ -1436,14 +1436,15 @@ impl<'a> Iterator for FoldChunks<'a> {
let transform_end = self.transform_cursor.end().1;
let chunk_end = buffer_chunk_end.min(transform_end);
chunk.text = &chunk.text
[(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
let bit_start = (self.inlay_offset - buffer_chunk_start).0;
let bit_end = (chunk_end - buffer_chunk_start).0;
chunk.text = &chunk.text[bit_start..bit_end];
let bit_end = (chunk_end - buffer_chunk_start).0;
let mask = 1u128.unbounded_shl(bit_end as u32).wrapping_sub(1);
chunk.tabs = (chunk.tabs >> (self.inlay_offset - buffer_chunk_start).0) & mask;
chunk.chars = (chunk.chars >> (self.inlay_offset - buffer_chunk_start).0) & mask;
chunk.tabs = (chunk.tabs >> bit_start) & mask;
chunk.chars = (chunk.chars >> bit_start) & mask;
if chunk_end == transform_end {
self.transform_cursor.next();
+17 -28
View File
@@ -325,21 +325,16 @@ impl<'a> Iterator for InlayChunks<'a> {
};
let (prefix, suffix) = chunk.text.split_at(split_index);
let (chars, tabs) = if split_index == 128 {
let output = (chunk.chars, chunk.tabs);
chunk.chars = 0;
chunk.tabs = 0;
output
} else {
let mask = (1 << split_index) - 1;
let output = (chunk.chars & mask, chunk.tabs & mask);
chunk.chars = chunk.chars >> split_index;
chunk.tabs = chunk.tabs >> split_index;
output
};
chunk.text = suffix;
self.output_offset.0 += prefix.len();
let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
let chars = chunk.chars & mask;
let tabs = chunk.tabs & mask;
chunk.chars = chunk.chars.unbounded_shr(split_index as u32);
chunk.tabs = chunk.tabs.unbounded_shr(split_index as u32);
chunk.text = suffix;
InlayChunk {
chunk: Chunk {
text: prefix,
@@ -457,18 +452,12 @@ impl<'a> Iterator for InlayChunks<'a> {
let (chunk, remainder) = inlay_chunk.split_at(split_index);
*inlay_chunk = remainder;
let (chars, tabs) = if split_index == 128 {
let output = (*chars, *tabs);
*chars = 0;
*tabs = 0;
output
} else {
let mask = (1 << split_index as u32) - 1;
let output = (*chars & mask, *tabs & mask);
*chars = *chars >> split_index;
*tabs = *tabs >> split_index;
output
};
let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
let new_chars = *chars & mask;
let new_tabs = *tabs & mask;
*chars = chars.unbounded_shr(split_index as u32);
*tabs = tabs.unbounded_shr(split_index as u32);
if inlay_chunk.is_empty() {
self.inlay_chunk = None;
@@ -479,8 +468,8 @@ impl<'a> Iterator for InlayChunks<'a> {
InlayChunk {
chunk: Chunk {
text: chunk,
chars,
tabs,
chars: new_chars,
tabs: new_tabs,
highlight_style,
is_inlay: true,
..Chunk::default()
+40 -49
View File
@@ -11,7 +11,7 @@ use sum_tree::Bias;
const MAX_EXPANSION_COLUMN: u32 = 256;
// Handles a tab width <= 128
const SPACES: &[u8; u128::BITS as usize] = &[b' '; _];
const SPACES: &[u8; rope::Chunk::MASK_BITS] = &[b' '; _];
const MAX_TABS: NonZeroU32 = NonZeroU32::new(SPACES.len() as u32).unwrap();
/// Keeps track of hard tabs in a text buffer.
@@ -569,56 +569,47 @@ impl<'a> Iterator for TabChunks<'a> {
//todo(improve performance by using tab cursor)
for (ix, c) in self.chunk.text.char_indices() {
match c {
'\t' if ix > 0 => {
let (prefix, suffix) = self.chunk.text.split_at(ix);
let mask = 1u128.unbounded_shl(ix as u32).wrapping_sub(1);
let chars = self.chunk.chars & mask;
let tabs = self.chunk.tabs & mask;
self.chunk.tabs = self.chunk.tabs.unbounded_shr(ix as u32);
self.chunk.chars = self.chunk.chars.unbounded_shr(ix as u32);
self.chunk.text = suffix;
return Some(Chunk {
text: prefix,
chars,
tabs,
..self.chunk.clone()
});
}
'\t' => {
if ix > 0 {
let (prefix, suffix) = self.chunk.text.split_at(ix);
let (chars, tabs) = if ix == 128 {
let output = (self.chunk.chars, self.chunk.tabs);
self.chunk.chars = 0;
self.chunk.tabs = 0;
output
} else {
let mask = (1 << ix) - 1;
let output = (self.chunk.chars & mask, self.chunk.tabs & mask);
self.chunk.chars = self.chunk.chars >> ix;
self.chunk.tabs = self.chunk.tabs >> ix;
output
};
self.chunk.text = suffix;
return Some(Chunk {
text: prefix,
chars,
tabs,
..self.chunk.clone()
});
self.chunk.text = &self.chunk.text[1..];
self.chunk.tabs >>= 1;
self.chunk.chars >>= 1;
let tab_size = if self.input_column < self.max_expansion_column {
self.tab_size.get()
} else {
self.chunk.text = &self.chunk.text[1..];
self.chunk.tabs >>= 1;
self.chunk.chars >>= 1;
let tab_size = if self.input_column < self.max_expansion_column {
self.tab_size.get()
} else {
1
};
let mut len = tab_size - self.column % tab_size;
let next_output_position = cmp::min(
self.output_position + Point::new(0, len),
self.max_output_position,
);
len = next_output_position.column - self.output_position.column;
self.column += len;
self.input_column += 1;
self.output_position = next_output_position;
return Some(Chunk {
text: unsafe { std::str::from_utf8_unchecked(&SPACES[..len as usize]) },
is_tab: true,
chars: 1u128.unbounded_shl(len) - 1,
tabs: 0,
..self.chunk.clone()
});
}
1
};
let mut len = tab_size - self.column % tab_size;
let next_output_position = cmp::min(
self.output_position + Point::new(0, len),
self.max_output_position,
);
len = next_output_position.column - self.output_position.column;
self.column += len;
self.input_column += 1;
self.output_position = next_output_position;
return Some(Chunk {
text: unsafe { std::str::from_utf8_unchecked(&SPACES[..len as usize]) },
is_tab: true,
chars: 1u128.unbounded_shl(len) - 1,
tabs: 0,
..self.chunk.clone()
});
}
'\n' => {
self.column = 0;
+5 -12
View File
@@ -972,18 +972,11 @@ impl<'a> Iterator for WrapChunks<'a> {
let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
let (chars, tabs) = if input_len == 128 {
let output = (self.input_chunk.chars, self.input_chunk.tabs);
self.input_chunk.chars = 0;
self.input_chunk.tabs = 0;
output
} else {
let mask = (1 << input_len) - 1;
let output = (self.input_chunk.chars & mask, self.input_chunk.tabs & mask);
self.input_chunk.chars = self.input_chunk.chars >> input_len;
self.input_chunk.tabs = self.input_chunk.tabs >> input_len;
output
};
let mask = 1u128.unbounded_shl(input_len as u32).wrapping_sub(1);
let chars = self.input_chunk.chars & mask;
let tabs = self.input_chunk.tabs & mask;
self.input_chunk.tabs = self.input_chunk.tabs.unbounded_shr(input_len as u32);
self.input_chunk.chars = self.input_chunk.chars.unbounded_shr(input_len as u32);
self.input_chunk.text = suffix;
Some(Chunk {
+12 -16
View File
@@ -506,15 +506,15 @@ pub struct Chunk<'a> {
pub highlight_style: Option<HighlightStyle>,
/// The severity of diagnostic associated with this chunk, if any.
pub diagnostic_severity: Option<DiagnosticSeverity>,
/// Whether this chunk of text is marked as unnecessary.
pub is_unnecessary: bool,
/// Whether this chunk of text was originally a tab character.
pub is_tab: bool,
/// A bitset of which characters are tabs in this string.
pub tabs: u128,
/// Bitmap of character indices in this chunk
pub chars: u128,
/// Whether this chunk of text is marked as unnecessary.
pub is_unnecessary: bool,
/// Whether this chunk of text was originally a tab character.
pub is_tab: bool,
/// Whether this chunk of text was originally an inlay.
pub is_inlay: bool,
/// Whether to underline the corresponding text range in the editor.
pub underline: bool,
@@ -4982,7 +4982,7 @@ impl<'a> Iterator for BufferChunks<'a> {
text: chunk,
chars: chars_map,
tabs,
}) = self.chunks.peek_tabs()
}) = self.chunks.peek_with_bitmaps()
{
let chunk_start = self.range.start;
let mut chunk_end = (self.chunks.offset() + chunk.len())
@@ -4995,18 +4995,14 @@ impl<'a> Iterator for BufferChunks<'a> {
chunk_end = chunk_end.min(*parent_capture_end);
highlight_id = Some(*parent_highlight_id);
}
let slice =
&chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()];
let bit_start = chunk_start - self.chunks.offset();
let bit_end = chunk_end - self.chunks.offset();
let mask = if bit_end >= 128 {
u128::MAX
} else {
(1u128 << bit_end) - 1
};
let tabs = (tabs >> (chunk_start - self.chunks.offset())) & mask;
let chars_map = (chars_map >> (chunk_start - self.chunks.offset())) & mask;
let slice = &chunk[bit_start..bit_end];
let mask = 1u128.unbounded_shl(bit_end as u32).wrapping_sub(1);
let tabs = (tabs >> bit_start) & mask;
let chars = (chars_map >> bit_start) & mask;
self.range.start = chunk_end;
if self.range.start == self.chunks.offset() + chunk.len() {
@@ -5020,7 +5016,7 @@ impl<'a> Iterator for BufferChunks<'a> {
diagnostic_severity: self.current_diagnostic_severity(),
is_unnecessary: self.current_code_is_unnecessary(),
tabs,
chars: chars_map,
chars,
..Chunk::default()
})
} else {
+5 -3
View File
@@ -51,7 +51,7 @@ use text::{
use theme::SyntaxTheme;
use util::{post_inc, rel_path::RelPath};
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
const NEWLINES: &[u8] = &[b'\n'; rope::Chunk::MASK_BITS];
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExcerptId(u32);
@@ -7730,7 +7730,7 @@ impl<'a> Iterator for MultiBufferChunks<'a> {
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 mask = 1u128.unbounded_shl(split_idx as u32).wrapping_sub(1);
let chars = chunk.chars & mask;
let tabs = chunk.tabs & mask;
@@ -7882,7 +7882,9 @@ 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;
let chars = 1u128
.unbounded_shl(self.footer_height as u32)
.wrapping_sub(1);
self.footer_height = 0;
return Some(Chunk {
text,
+58 -53
View File
@@ -5,29 +5,36 @@ use sum_tree::Bias;
use unicode_segmentation::GraphemeCursor;
use util::debug_panic;
pub(crate) const MIN_BASE: usize = if cfg!(test) { 6 } else { 64 };
pub(crate) const MAX_BASE: usize = MIN_BASE * 2;
#[cfg(not(all(test, not(rust_analyzer))))]
pub(crate) type Bitmap = u128;
#[cfg(all(test, not(rust_analyzer)))]
pub(crate) type Bitmap = u16;
pub(crate) const MIN_BASE: usize = MAX_BASE / 2;
pub(crate) const MAX_BASE: usize = Bitmap::BITS as usize;
#[derive(Clone, Debug, Default)]
pub struct Chunk {
/// If bit[i] is set, then the character at index i is the start of a UTF-8 character in the
/// text.
chars: u128,
chars: Bitmap,
/// The number of set bits is the number of UTF-16 code units it would take to represent the
/// text.
///
/// Bit[i] is set if text[i] is the start of a UTF-8 character. If the character would
/// take two UTF-16 code units, then bit[i+1] is also set. (Rust chars never take more
/// than two UTF-16 code units.)
chars_utf16: u128,
chars_utf16: Bitmap,
/// If bit[i] is set, then the character at index i is an ascii newline.
newlines: u128,
newlines: Bitmap,
/// If bit[i] is set, then the character at index i is an ascii tab.
pub tabs: u128,
tabs: Bitmap,
pub text: ArrayString<MAX_BASE>,
}
impl Chunk {
pub const MASK_BITS: usize = Bitmap::BITS as usize;
#[inline(always)]
pub fn new(text: &str) -> Self {
let mut this = Chunk::default();
@@ -41,9 +48,9 @@ impl Chunk {
let ix = self.text.len() + char_ix;
self.chars |= 1 << ix;
self.chars_utf16 |= 1 << ix;
self.chars_utf16 |= (c.len_utf16() as u128) << ix;
self.newlines |= ((c == '\n') as u128) << ix;
self.tabs |= ((c == '\t') as u128) << ix;
self.chars_utf16 |= (c.len_utf16() as Bitmap) << ix;
self.newlines |= ((c == '\n') as Bitmap) << ix;
self.tabs |= ((c == '\t') as Bitmap) << ix;
}
self.text.push_str(text);
}
@@ -79,17 +86,21 @@ impl Chunk {
}
#[inline(always)]
pub fn chars(&self) -> u128 {
pub fn chars(&self) -> Bitmap {
self.chars
}
pub fn tabs(&self) -> Bitmap {
self.tabs
}
}
#[derive(Clone, Copy, Debug)]
pub struct ChunkSlice<'a> {
chars: u128,
chars_utf16: u128,
newlines: u128,
tabs: u128,
chars: Bitmap,
chars_utf16: Bitmap,
newlines: Bitmap,
tabs: Bitmap,
text: &'a str,
}
@@ -129,7 +140,7 @@ impl<'a> ChunkSlice<'a> {
};
(left, right)
} else {
let mask = (1u128 << mid) - 1;
let mask = ((1 as Bitmap) << mid) - 1;
let (left_text, right_text) = self.text.split_at(mid);
let left = ChunkSlice {
chars: self.chars & mask,
@@ -151,17 +162,15 @@ impl<'a> ChunkSlice<'a> {
#[inline(always)]
pub fn slice(self, range: Range<usize>) -> Self {
let mask = if range.end == MAX_BASE {
u128::MAX
} else {
debug_assert!(
self.is_char_boundary(range.end),
"Invalid range end {} in {:?}",
range.end,
self
);
(1u128 << range.end) - 1
};
debug_assert!(
self.is_char_boundary(range.end),
"Invalid range end {} in {:?}",
range.end,
self
);
let mask = (1 as Bitmap)
.unbounded_shl(range.end as u32)
.wrapping_sub(1);
if range.start == MAX_BASE {
Self {
chars: 0,
@@ -220,7 +229,7 @@ impl<'a> ChunkSlice<'a> {
#[inline(always)]
pub fn lines(&self) -> Point {
let row = self.newlines.count_ones();
let column = self.newlines.leading_zeros() - (u128::BITS - self.text.len() as u32);
let column = self.newlines.leading_zeros() - (Bitmap::BITS - self.text.len() as u32);
Point::new(row, column)
}
@@ -230,7 +239,7 @@ impl<'a> ChunkSlice<'a> {
if self.newlines == 0 {
self.chars.count_ones()
} else {
let mask = (1u128 << self.newlines.trailing_zeros()) - 1;
let mask = ((1 as Bitmap) << self.newlines.trailing_zeros()) - 1;
(self.chars & mask).count_ones()
}
}
@@ -241,7 +250,7 @@ impl<'a> ChunkSlice<'a> {
if self.newlines == 0 {
self.chars.count_ones()
} else {
let mask = !(u128::MAX >> self.newlines.leading_zeros());
let mask = !(Bitmap::MAX >> self.newlines.leading_zeros());
(self.chars & mask).count_ones()
}
}
@@ -252,7 +261,7 @@ impl<'a> ChunkSlice<'a> {
if self.newlines == 0 {
self.chars_utf16.count_ones()
} else {
let mask = !(u128::MAX >> self.newlines.leading_zeros());
let mask = !(Bitmap::MAX >> self.newlines.leading_zeros());
(self.chars_utf16 & mask).count_ones()
}
}
@@ -295,13 +304,9 @@ impl<'a> ChunkSlice<'a> {
#[inline(always)]
pub fn offset_to_point(&self, offset: usize) -> Point {
let mask = if offset == MAX_BASE {
u128::MAX
} else {
(1u128 << offset) - 1
};
let mask = (1 as Bitmap).unbounded_shl(offset as u32).wrapping_sub(1);
let row = (self.newlines & mask).count_ones();
let newline_ix = u128::BITS - (self.newlines & mask).leading_zeros();
let newline_ix = Bitmap::BITS - (self.newlines & mask).leading_zeros();
let column = (offset - newline_ix as usize) as u32;
Point::new(row, column)
}
@@ -332,11 +337,7 @@ impl<'a> ChunkSlice<'a> {
#[inline(always)]
pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
let mask = if offset == MAX_BASE {
u128::MAX
} else {
(1u128 << offset) - 1
};
let mask = (1 as Bitmap).unbounded_shl(offset as u32).wrapping_sub(1);
OffsetUtf16((self.chars_utf16 & mask).count_ones() as usize)
}
@@ -345,7 +346,11 @@ impl<'a> ChunkSlice<'a> {
if target.0 == 0 {
0
} else {
let ix = nth_set_bit(self.chars_utf16, target.0) + 1;
#[cfg(not(test))]
let chars_utf16 = self.chars_utf16;
#[cfg(test)]
let chars_utf16 = self.chars_utf16 as u128;
let ix = nth_set_bit(chars_utf16, target.0) + 1;
if ix == MAX_BASE {
MAX_BASE
} else {
@@ -360,13 +365,9 @@ impl<'a> ChunkSlice<'a> {
#[inline(always)]
pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
let mask = if offset == MAX_BASE {
u128::MAX
} else {
(1u128 << offset) - 1
};
let mask = (1 as Bitmap).unbounded_shl(offset as u32).wrapping_sub(1);
let row = (self.newlines & mask).count_ones();
let newline_ix = u128::BITS - (self.newlines & mask).leading_zeros();
let newline_ix = Bitmap::BITS - (self.newlines & mask).leading_zeros();
let column = if newline_ix as usize == MAX_BASE {
0
} else {
@@ -520,7 +521,11 @@ impl<'a> ChunkSlice<'a> {
#[inline(always)]
fn offset_range_for_row(&self, row: u32) -> Range<usize> {
let row_start = if row > 0 {
nth_set_bit(self.newlines, row as usize) + 1
#[cfg(not(test))]
let newlines = self.newlines;
#[cfg(test)]
let newlines = self.newlines as u128;
nth_set_bit(newlines, row as usize) + 1
} else {
0
};
@@ -545,8 +550,8 @@ impl<'a> ChunkSlice<'a> {
}
pub struct Tabs {
tabs: u128,
chars: u128,
tabs: Bitmap,
chars: Bitmap,
}
#[derive(Debug, PartialEq, Eq)]
@@ -647,8 +652,8 @@ mod tests {
// Verify Chunk::chars() bitmap
let expected_chars = char_offsets(&text)
.into_iter()
.inspect(|i| assert!(*i < 128))
.fold(0u128, |acc, i| acc | (1 << i));
.inspect(|i| assert!(*i < MAX_BASE))
.fold(0 as Bitmap, |acc, i| acc | (1 << i));
assert_eq!(chunk.chars(), expected_chars);
for _ in 0..10 {
+8 -39
View File
@@ -4,7 +4,6 @@ mod point;
mod point_utf16;
mod unclipped;
use chunk::Chunk;
use rayon::iter::{IntoParallelIterator, ParallelIterator as _};
use smallvec::SmallVec;
use std::{
@@ -14,12 +13,14 @@ use std::{
};
use sum_tree::{Bias, Dimension, Dimensions, SumTree};
pub use chunk::ChunkSlice;
pub use chunk::{Chunk, ChunkSlice};
pub use offset_utf16::OffsetUtf16;
pub use point::Point;
pub use point_utf16::PointUtf16;
pub use unclipped::Unclipped;
use crate::chunk::Bitmap;
#[derive(Clone, Default)]
pub struct Rope {
chunks: SumTree<Chunk>,
@@ -676,9 +677,9 @@ pub struct ChunkBitmaps<'a> {
/// A slice of text up to 128 bytes in size
pub text: &'a str,
/// Bitmap of character locations in text. LSB ordered
pub chars: u128,
pub chars: Bitmap,
/// Bitmap of tab locations in text. LSB ordered
pub tabs: u128,
pub tabs: Bitmap,
}
#[derive(Clone)]
@@ -850,39 +851,6 @@ impl<'a> Chunks<'a> {
self.offset < initial_offset && self.offset == 0
}
/// Returns bitmaps that represent character positions and tab positions
pub fn peek_with_bitmaps(&self) -> Option<ChunkBitmaps<'a>> {
if !self.offset_is_valid() {
return None;
}
let chunk = self.chunks.item()?;
let chunk_start = *self.chunks.start();
let slice_range = if self.reversed {
let slice_start = cmp::max(chunk_start, self.range.start) - chunk_start;
let slice_end = self.offset - chunk_start;
slice_start..slice_end
} else {
let slice_start = self.offset - chunk_start;
let slice_end = cmp::min(self.chunks.end(), self.range.end) - chunk_start;
slice_start..slice_end
};
// slice range has a bounds between 0 and 128 in non test builds
// We use a non wrapping sub because we want to overflow in the case where slice_range.end == 128
// because that represents a full chunk and the bitmask shouldn't remove anything
let bitmask = (1u128.unbounded_shl(slice_range.end as u32)).wrapping_sub(1);
let chars = (chunk.chars() & bitmask) >> slice_range.start;
let tabs = (chunk.tabs & bitmask) >> slice_range.start;
Some(ChunkBitmaps {
text: &chunk.text[slice_range],
chars,
tabs,
})
}
pub fn peek(&self) -> Option<&'a str> {
if !self.offset_is_valid() {
return None;
@@ -903,7 +871,8 @@ impl<'a> Chunks<'a> {
Some(&chunk.text[slice_range])
}
pub fn peek_tabs(&self) -> Option<ChunkBitmaps<'a>> {
/// Returns bitmaps that represent character positions and tab positions
pub fn peek_with_bitmaps(&self) -> Option<ChunkBitmaps<'a>> {
if !self.offset_is_valid() {
return None;
}
@@ -923,7 +892,7 @@ impl<'a> Chunks<'a> {
let slice_text = &chunk.text[slice_range];
// Shift the tabs to align with our slice window
let shifted_tabs = chunk.tabs >> chunk_start_offset;
let shifted_tabs = chunk.tabs() >> chunk_start_offset;
let shifted_chars = chunk.chars() >> chunk_start_offset;
Some(ChunkBitmaps {