mod chunk;mod offset_utf16;mod point;mod point_utf16;mod unclipped; use chunk::Chunk;use rayon::iter::{IntoParallelIterator, ParallelIterator as _};use smallvec::SmallVec;use std::{ cmp, fmt, io, mem, ops::{self, AddAssign, Range}, str,};use sum_tree::{Bias, Dimension, SumTree}; pub use chunk::ChunkSlice;pub use offset_utf16::OffsetUtf16;pub use point::Point;pub use point_utf16::PointUtf16;pub use unclipped::Unclipped; #[derive(Clone, Default)]pub struct Rope { chunks: SumTree<Chunk>,} impl Rope { pub fn new() -> Self { Self::default() } pub fn append(&mut self, rope: Rope) { if let Some(chunk) = rope.chunks.first() { if self .chunks .last() .map_or(false, |c| c.text.len() < chunk::MIN_BASE) || chunk.text.len() < chunk::MIN_BASE { self.push_chunk(chunk.as_slice()); let mut chunks = rope.chunks.cursor::<()>(&()); chunks.next(&()); chunks.next(&()); self.chunks.append(chunks.suffix(&()), &()); self.check_invariants(); return; } } self.chunks.append(rope.chunks.clone(), &()); self.check_invariants(); } pub fn replace(&mut self, range: Range<usize>, text: &str) { let mut new_rope = Rope::new(); let mut cursor = self.cursor(0); new_rope.append(cursor.slice(range.start)); cursor.seek_forward(range.end); new_rope.push(text); new_rope.append(cursor.suffix()); *self = new_rope; } pub fn slice(&self, range: Range<usize>) -> Rope { let mut cursor = self.cursor(0); cursor.seek_forward(range.start); cursor.slice(range.end) } pub fn slice_rows(&self, range: Range<u32>) -> Rope { // This would be more efficient with a forward advance after the first, but it's fine. let start = self.point_to_offset(Point::new(range.start, 0)); let end = self.point_to_offset(Point::new(range.end, 0)); self.slice(start..end) } pub fn push(&mut self, mut text: &str) { self.chunks.update_last( |last_chunk| { let split_ix = if last_chunk.text.len() + text.len() <= chunk::MAX_BASE { text.len() } else { let mut split_ix = cmp::min( chunk::MIN_BASE.saturating_sub(last_chunk.text.len()), text.len(), ); while !text.is_char_boundary(split_ix) { split_ix += 1; } split_ix }; let (suffix, remainder) = text.split_at(split_ix); last_chunk.push_str(suffix); text = remainder; }, &(), ); if text.len() > 2048 { return self.push_large(text); } let mut new_chunks = SmallVec::<[_; 16]>::new(); while !text.is_empty() { let mut split_ix = cmp::min(chunk::MAX_BASE, text.len()); while !text.is_char_boundary(split_ix) { split_ix -= 1; } let (chunk, remainder) = text.split_at(split_ix); new_chunks.push(chunk); text = remainder; } #[cfg(test)] const PARALLEL_THRESHOLD: usize = 4; #[cfg(not(test))] const PARALLEL_THRESHOLD: usize = 4 * (2 * sum_tree::TREE_BASE); if new_chunks.len() >= PARALLEL_THRESHOLD { self.chunks .par_extend(new_chunks.into_vec().into_par_iter().map(Chunk::new), &()); } else { self.chunks .extend(new_chunks.into_iter().map(Chunk::new), &()); } self.check_invariants(); } /// A copy of `push` specialized for working with large quantities of text. fn push_large(&mut self, mut text: &str) { // To avoid frequent reallocs when loading large swaths of file contents, // we estimate worst-case `new_chunks` capacity; // Chunk is a fixed-capacity buffer. If a character falls on // chunk boundary, we push it off to the following chunk (thus leaving a small bit of capacity unfilled in current chunk). // Worst-case chunk count when loading a file is then a case where every chunk ends up with that unused capacity. // Since we're working with UTF-8, each character is at most 4 bytes wide. It follows then that the worst case is where // a chunk ends with 3 bytes of a 4-byte character. These 3 bytes end up being stored in the following chunk, thus wasting // 3 bytes of storage in current chunk. // For example, a 1024-byte string can occupy between 32 (full ASCII, 1024/32) and 36 (full 4-byte UTF-8, 1024 / 29 rounded up) chunks. const MIN_CHUNK_SIZE: usize = chunk::MAX_BASE - 3; // We also round up the capacity up by one, for a good measure; we *really* don't want to realloc here, as we assume that the # of characters // we're working with there is large. let capacity = text.len().div_ceil(MIN_CHUNK_SIZE); let mut new_chunks = Vec::with_capacity(capacity); while !text.is_empty() { let mut split_ix = cmp::min(chunk::MAX_BASE, text.len()); while !text.is_char_boundary(split_ix) { split_ix -= 1; } let (chunk, remainder) = text.split_at(split_ix); new_chunks.push(chunk); text = remainder; } #[cfg(test)] const PARALLEL_THRESHOLD: usize = 4; #[cfg(not(test))] const PARALLEL_THRESHOLD: usize = 4 * (2 * sum_tree::TREE_BASE); if new_chunks.len() >= PARALLEL_THRESHOLD { self.chunks .par_extend(new_chunks.into_par_iter().map(Chunk::new), &()); } else { self.chunks .extend(new_chunks.into_iter().map(Chunk::new), &()); } self.check_invariants(); } fn push_chunk(&mut self, mut chunk: ChunkSlice) { self.chunks.update_last( |last_chunk| { let split_ix = if last_chunk.text.len() + chunk.len() <= chunk::MAX_BASE { chunk.len() } else { let mut split_ix = cmp::min( chunk::MIN_BASE.saturating_sub(last_chunk.text.len()), chunk.len(), ); while !chunk.is_char_boundary(split_ix) { split_ix += 1; } split_ix }; let (suffix, remainder) = chunk.split_at(split_ix); last_chunk.append(suffix); chunk = remainder; }, &(), ); if !chunk.is_empty() { self.chunks.push(chunk.into(), &()); } } pub fn push_front(&mut self, text: &str) { let suffix = mem::replace(self, Rope::from(text)); self.append(suffix); } fn check_invariants(&self) { #[cfg(test)] { // Ensure all chunks except maybe the last one are not underflowing. // Allow some wiggle room for multibyte characters at chunk boundaries. let mut chunks = self.chunks.cursor::<()>(&()).peekable(); while let Some(chunk) = chunks.next() { if chunks.peek().is_some() { assert!(chunk.text.len() + 3 >= chunk::MIN_BASE); } } } } pub fn summary(&self) -> TextSummary { self.chunks.summary().text } pub fn len(&self) -> usize { self.chunks.extent(&()) } pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn max_point(&self) -> Point { self.chunks.extent(&()) } pub fn max_point_utf16(&self) -> PointUtf16 { self.chunks.extent(&()) } pub fn cursor(&self, offset: usize) -> Cursor { Cursor::new(self, offset) } pub fn chars(&self) -> impl Iterator<Item = char> + '_ { self.chars_at(0) } pub fn chars_at(&self, start: usize) -> impl Iterator<Item = char> + '_ { self.chunks_in_range(start..self.len()).flat_map(str::chars) } pub fn reversed_chars_at(&self, start: usize) -> impl Iterator<Item = char> + '_ { self.reversed_chunks_in_range(0..start) .flat_map(|chunk| chunk.chars().rev()) } pub fn bytes_in_range(&self, range: Range<usize>) -> Bytes { Bytes::new(self, range, false) } pub fn reversed_bytes_in_range(&self, range: Range<usize>) -> Bytes { Bytes::new(self, range, true) } pub fn chunks(&self) -> Chunks { self.chunks_in_range(0..self.len()) } pub fn chunks_in_range(&self, range: Range<usize>) -> Chunks { Chunks::new(self, range, false) } pub fn reversed_chunks_in_range(&self, range: Range<usize>) -> Chunks { Chunks::new(self, range, true) } pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 { if offset >= self.summary().len { return self.summary().len_utf16; } let mut cursor = self.chunks.cursor::<(usize, OffsetUtf16)>(&()); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; cursor.start().1 + cursor.item().map_or(Default::default(), |chunk| { chunk.as_slice().offset_to_offset_utf16(overshoot) }) } pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize { if offset >= self.summary().len_utf16 { return self.summary().len; } let mut cursor = self.chunks.cursor::<(OffsetUtf16, usize)>(&()); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; cursor.start().1 + cursor.item().map_or(Default::default(), |chunk| { chunk.as_slice().offset_utf16_to_offset(overshoot) }) } pub fn offset_to_point(&self, offset: usize) -> Point { if offset >= self.summary().len { return self.summary().lines; } let mut cursor = self.chunks.cursor::<(usize, Point)>(&()); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; cursor.start().1 + cursor.item().map_or(Point::zero(), |chunk| { chunk.as_slice().offset_to_point(overshoot) }) } pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 { if offset >= self.summary().len { return self.summary().lines_utf16(); } let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>(&()); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; cursor.start().1 + cursor.item().map_or(PointUtf16::zero(), |chunk| { chunk.as_slice().offset_to_point_utf16(overshoot) }) } pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 { if point >= self.summary().lines { return self.summary().lines_utf16(); } let mut cursor = self.chunks.cursor::<(Point, PointUtf16)>(&()); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; cursor.start().1 + cursor.item().map_or(PointUtf16::zero(), |chunk| { chunk.as_slice().point_to_point_utf16(overshoot) }) } pub fn point_to_offset(&self, point: Point) -> usize { if point >= self.summary().lines { return self.summary().len; } let mut cursor = self.chunks.cursor::<(Point, usize)>(&()); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; cursor.start().1 + cursor .item() .map_or(0, |chunk| chunk.as_slice().point_to_offset(overshoot)) } pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { self.point_utf16_to_offset_impl(point, false) } pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped<PointUtf16>) -> usize { self.point_utf16_to_offset_impl(point.0, true) } fn point_utf16_to_offset_impl(&self, point: PointUtf16, clip: bool) -> usize { if point >= self.summary().lines_utf16() { return self.summary().len; } let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>(&()); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; cursor.start().1 + cursor.item().map_or(0, |chunk| { chunk.as_slice().point_utf16_to_offset(overshoot, clip) }) } pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> Point { if point.0 >= self.summary().lines_utf16() { return self.summary().lines; } let mut cursor = self.chunks.cursor::<(PointUtf16, Point)>(&()); cursor.seek(&point.0, Bias::Left, &()); let overshoot = Unclipped(point.0 - cursor.start().0); cursor.start().1 + cursor.item().map_or(Point::zero(), |chunk| { chunk.as_slice().unclipped_point_utf16_to_point(overshoot) }) } pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize { let mut cursor = self.chunks.cursor::<usize>(&()); cursor.seek(&offset, Bias::Left, &()); if let Some(chunk) = cursor.item() { let mut ix = offset - cursor.start(); while !chunk.text.is_char_boundary(ix) { match bias { Bias::Left => { ix -= 1; offset -= 1; } Bias::Right => { ix += 1; offset += 1; } } } offset } else { self.summary().len } } pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 { let mut cursor = self.chunks.cursor::<OffsetUtf16>(&()); cursor.seek(&offset, Bias::Right, &()); if let Some(chunk) = cursor.item() { let overshoot = offset - cursor.start(); *cursor.start() + chunk.as_slice().clip_offset_utf16(overshoot, bias) } else { self.summary().len_utf16 } } pub fn clip_point(&self, point: Point, bias: Bias) -> Point { let mut cursor = self.chunks.cursor::<Point>(&()); cursor.seek(&point, Bias::Right, &()); if let Some(chunk) = cursor.item() { let overshoot = point - cursor.start(); *cursor.start() + chunk.as_slice().clip_point(overshoot, bias) } else { self.summary().lines } } pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 { let mut cursor = self.chunks.cursor::<PointUtf16>(&()); cursor.seek(&point.0, Bias::Right, &()); if let Some(chunk) = cursor.item() { let overshoot = Unclipped(point.0 - cursor.start()); *cursor.start() + chunk.as_slice().clip_point_utf16(overshoot, bias) } else { self.summary().lines_utf16() } } pub fn line_len(&self, row: u32) -> u32 { self.clip_point(Point::new(row, u32::MAX), Bias::Left) .column } pub fn ptr_eq(&self, other: &Self) -> bool { self.chunks.ptr_eq(&other.chunks) }} impl<'a> From<&'a str> for Rope { fn from(text: &'a str) -> Self { let mut rope = Self::new(); rope.push(text); rope }} impl<'a> FromIterator<&'a str> for Rope { fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self { let mut rope = Rope::new(); for chunk in iter { rope.push(chunk); } rope }} impl From<String> for Rope { fn from(text: String) -> Self { Rope::from(text.as_str()) }} impl fmt::Display for Rope { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for chunk in self.chunks() { write!(f, "{}", chunk)?; } Ok(()) }} impl fmt::Debug for Rope { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use std::fmt::Write as _; write!(f, "\"")?; let mut format_string = String::new(); for chunk in self.chunks() { write!(&mut format_string, "{:?}", chunk)?; write!(f, "{}", &format_string[1..format_string.len() - 1])?;Lines 1–500 of 1877. Load more
mod chunk; mod offset_utf16; mod point; mod point_utf16; mod unclipped; use chunk::Chunk; use rayon::iter::{IntoParallelIterator, ParallelIterator as _}; use smallvec::SmallVec; use std::{ cmp, fmt, io, mem, ops::{self, AddAssign, Range}, str, }; use sum_tree::{Bias, Dimension, SumTree}; pub use chunk::ChunkSlice; pub use offset_utf16::OffsetUtf16; pub use point::Point; pub use point_utf16::PointUtf16; pub use unclipped::Unclipped; #[derive(Clone, Default)] pub struct Rope { chunks: SumTree<Chunk>, } impl Rope { pub fn new() -> Self { Self::default() } pub fn append(&mut self, rope: Rope) { if let Some(chunk) = rope.chunks.first() { if self .chunks .last() .map_or(false, |c| c.text.len() < chunk::MIN_BASE) || chunk.text.len() < chunk::MIN_BASE { self.push_chunk(chunk.as_slice()); let mut chunks = rope.chunks.cursor::<()>(&()); chunks.next(&()); chunks.next(&()); self.chunks.append(chunks.suffix(&()), &()); self.check_invariants(); return; } } self.chunks.append(rope.chunks.clone(), &()); self.check_invariants(); } pub fn replace(&mut self, range: Range<usize>, text: &str) { let mut new_rope = Rope::new(); let mut cursor = self.cursor(0); new_rope.append(cursor.slice(range.start)); cursor.seek_forward(range.end); new_rope.push(text); new_rope.append(cursor.suffix()); *self = new_rope; } pub fn slice(&self, range: Range<usize>) -> Rope { let mut cursor = self.cursor(0); cursor.seek_forward(range.start); cursor.slice(range.end) } pub fn slice_rows(&self, range: Range<u32>) -> Rope { // This would be more efficient with a forward advance after the first, but it's fine. let start = self.point_to_offset(Point::new(range.start, 0)); let end = self.point_to_offset(Point::new(range.end, 0)); self.slice(start..end) } pub fn push(&mut self, mut text: &str) { self.chunks.update_last( |last_chunk| { let split_ix = if last_chunk.text.len() + text.len() <= chunk::MAX_BASE { text.len() } else { let mut split_ix = cmp::min( chunk::MIN_BASE.saturating_sub(last_chunk.text.len()), text.len(), ); while !text.is_char_boundary(split_ix) { split_ix += 1; } split_ix }; let (suffix, remainder) = text.split_at(split_ix); last_chunk.push_str(suffix); text = remainder; }, &(), ); if text.len() > 2048 { return self.push_large(text); } let mut new_chunks = SmallVec::<[_; 16]>::new(); while !text.is_empty() { let mut split_ix = cmp::min(chunk::MAX_BASE, text.len()); while !text.is_char_boundary(split_ix) { split_ix -= 1; } let (chunk, remainder) = text.split_at(split_ix); new_chunks.push(chunk); text = remainder; } #[cfg(test)] const PARALLEL_THRESHOLD: usize = 4; #[cfg(not(test))] const PARALLEL_THRESHOLD: usize = 4 * (2 * sum_tree::TREE_BASE); if new_chunks.len() >= PARALLEL_THRESHOLD { self.chunks .par_extend(new_chunks.into_vec().into_par_iter().map(Chunk::new), &()); } else { self.chunks .extend(new_chunks.into_iter().map(Chunk::new), &()); } self.check_invariants(); } /// A copy of `push` specialized for working with large quantities of text. fn push_large(&mut self, mut text: &str) { // To avoid frequent reallocs when loading large swaths of file contents, // we estimate worst-case `new_chunks` capacity; // Chunk is a fixed-capacity buffer. If a character falls on // chunk boundary, we push it off to the following chunk (thus leaving a small bit of capacity unfilled in current chunk). // Worst-case chunk count when loading a file is then a case where every chunk ends up with that unused capacity. // Since we're working with UTF-8, each character is at most 4 bytes wide. It follows then that the worst case is where // a chunk ends with 3 bytes of a 4-byte character. These 3 bytes end up being stored in the following chunk, thus wasting // 3 bytes of storage in current chunk. // For example, a 1024-byte string can occupy between 32 (full ASCII, 1024/32) and 36 (full 4-byte UTF-8, 1024 / 29 rounded up) chunks. const MIN_CHUNK_SIZE: usize = chunk::MAX_BASE - 3; // We also round up the capacity up by one, for a good measure; we *really* don't want to realloc here, as we assume that the # of characters // we're working with there is large. let capacity = text.len().div_ceil(MIN_CHUNK_SIZE); let mut new_chunks = Vec::with_capacity(capacity); while !text.is_empty() { let mut split_ix = cmp::min(chunk::MAX_BASE, text.len()); while !text.is_char_boundary(split_ix) { split_ix -= 1; } let (chunk, remainder) = text.split_at(split_ix); new_chunks.push(chunk); text = remainder; } #[cfg(test)] const PARALLEL_THRESHOLD: usize = 4; #[cfg(not(test))] const PARALLEL_THRESHOLD: usize = 4 * (2 * sum_tree::TREE_BASE); if new_chunks.len() >= PARALLEL_THRESHOLD { self.chunks .par_extend(new_chunks.into_par_iter().map(Chunk::new), &()); } else { self.chunks .extend(new_chunks.into_iter().map(Chunk::new), &()); } self.check_invariants(); } fn push_chunk(&mut self, mut chunk: ChunkSlice) { self.chunks.update_last( |last_chunk| { let split_ix = if last_chunk.text.len() + chunk.len() <= chunk::MAX_BASE { chunk.len() } else { let mut split_ix = cmp::min( chunk::MIN_BASE.saturating_sub(last_chunk.text.len()), chunk.len(), ); while !chunk.is_char_boundary(split_ix) { split_ix += 1; } split_ix }; let (suffix, remainder) = chunk.split_at(split_ix); last_chunk.append(suffix); chunk = remainder; }, &(), ); if !chunk.is_empty() { self.chunks.push(chunk.into(), &()); } } pub fn push_front(&mut self, text: &str) { let suffix = mem::replace(self, Rope::from(text)); self.append(suffix); } fn check_invariants(&self) { #[cfg(test)] { // Ensure all chunks except maybe the last one are not underflowing. // Allow some wiggle room for multibyte characters at chunk boundaries. let mut chunks = self.chunks.cursor::<()>(&()).peekable(); while let Some(chunk) = chunks.next() { if chunks.peek().is_some() { assert!(chunk.text.len() + 3 >= chunk::MIN_BASE); } } } } pub fn summary(&self) -> TextSummary { self.chunks.summary().text } pub fn len(&self) -> usize { self.chunks.extent(&()) } pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn max_point(&self) -> Point { self.chunks.extent(&()) } pub fn max_point_utf16(&self) -> PointUtf16 { self.chunks.extent(&()) } pub fn cursor(&self, offset: usize) -> Cursor { Cursor::new(self, offset) } pub fn chars(&self) -> impl Iterator<Item = char> + '_ { self.chars_at(0) } pub fn chars_at(&self, start: usize) -> impl Iterator<Item = char> + '_ { self.chunks_in_range(start..self.len()).flat_map(str::chars) } pub fn reversed_chars_at(&self, start: usize) -> impl Iterator<Item = char> + '_ { self.reversed_chunks_in_range(0..start) .flat_map(|chunk| chunk.chars().rev()) } pub fn bytes_in_range(&self, range: Range<usize>) -> Bytes { Bytes::new(self, range, false) } pub fn reversed_bytes_in_range(&self, range: Range<usize>) -> Bytes { Bytes::new(self, range, true) } pub fn chunks(&self) -> Chunks { self.chunks_in_range(0..self.len()) } pub fn chunks_in_range(&self, range: Range<usize>) -> Chunks { Chunks::new(self, range, false) } pub fn reversed_chunks_in_range(&self, range: Range<usize>) -> Chunks { Chunks::new(self, range, true) } pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 { if offset >= self.summary().len { return self.summary().len_utf16; } let mut cursor = self.chunks.cursor::<(usize, OffsetUtf16)>(&()); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; cursor.start().1 + cursor.item().map_or(Default::default(), |chunk| { chunk.as_slice().offset_to_offset_utf16(overshoot) }) } pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize { if offset >= self.summary().len_utf16 { return self.summary().len; } let mut cursor = self.chunks.cursor::<(OffsetUtf16, usize)>(&()); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; cursor.start().1 + cursor.item().map_or(Default::default(), |chunk| { chunk.as_slice().offset_utf16_to_offset(overshoot) }) } pub fn offset_to_point(&self, offset: usize) -> Point { if offset >= self.summary().len { return self.summary().lines; } let mut cursor = self.chunks.cursor::<(usize, Point)>(&()); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; cursor.start().1 + cursor.item().map_or(Point::zero(), |chunk| { chunk.as_slice().offset_to_point(overshoot) }) } pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 { if offset >= self.summary().len { return self.summary().lines_utf16(); } let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>(&()); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; cursor.start().1 + cursor.item().map_or(PointUtf16::zero(), |chunk| { chunk.as_slice().offset_to_point_utf16(overshoot) }) } pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 { if point >= self.summary().lines { return self.summary().lines_utf16(); } let mut cursor = self.chunks.cursor::<(Point, PointUtf16)>(&()); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; cursor.start().1 + cursor.item().map_or(PointUtf16::zero(), |chunk| { chunk.as_slice().point_to_point_utf16(overshoot) }) } pub fn point_to_offset(&self, point: Point) -> usize { if point >= self.summary().lines { return self.summary().len; } let mut cursor = self.chunks.cursor::<(Point, usize)>(&()); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; cursor.start().1 + cursor .item() .map_or(0, |chunk| chunk.as_slice().point_to_offset(overshoot)) } pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { self.point_utf16_to_offset_impl(point, false) } pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped<PointUtf16>) -> usize { self.point_utf16_to_offset_impl(point.0, true) } fn point_utf16_to_offset_impl(&self, point: PointUtf16, clip: bool) -> usize { if point >= self.summary().lines_utf16() { return self.summary().len; } let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>(&()); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; cursor.start().1 + cursor.item().map_or(0, |chunk| { chunk.as_slice().point_utf16_to_offset(overshoot, clip) }) } pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> Point { if point.0 >= self.summary().lines_utf16() { return self.summary().lines; } let mut cursor = self.chunks.cursor::<(PointUtf16, Point)>(&()); cursor.seek(&point.0, Bias::Left, &()); let overshoot = Unclipped(point.0 - cursor.start().0); cursor.start().1 + cursor.item().map_or(Point::zero(), |chunk| { chunk.as_slice().unclipped_point_utf16_to_point(overshoot) }) } pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize { let mut cursor = self.chunks.cursor::<usize>(&()); cursor.seek(&offset, Bias::Left, &()); if let Some(chunk) = cursor.item() { let mut ix = offset - cursor.start(); while !chunk.text.is_char_boundary(ix) { match bias { Bias::Left => { ix -= 1; offset -= 1; } Bias::Right => { ix += 1; offset += 1; } } } offset } else { self.summary().len } } pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 { let mut cursor = self.chunks.cursor::<OffsetUtf16>(&()); cursor.seek(&offset, Bias::Right, &()); if let Some(chunk) = cursor.item() { let overshoot = offset - cursor.start(); *cursor.start() + chunk.as_slice().clip_offset_utf16(overshoot, bias) } else { self.summary().len_utf16 } } pub fn clip_point(&self, point: Point, bias: Bias) -> Point { let mut cursor = self.chunks.cursor::<Point>(&()); cursor.seek(&point, Bias::Right, &()); if let Some(chunk) = cursor.item() { let overshoot = point - cursor.start(); *cursor.start() + chunk.as_slice().clip_point(overshoot, bias) } else { self.summary().lines } } pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 { let mut cursor = self.chunks.cursor::<PointUtf16>(&()); cursor.seek(&point.0, Bias::Right, &()); if let Some(chunk) = cursor.item() { let overshoot = Unclipped(point.0 - cursor.start()); *cursor.start() + chunk.as_slice().clip_point_utf16(overshoot, bias) } else { self.summary().lines_utf16() } } pub fn line_len(&self, row: u32) -> u32 { self.clip_point(Point::new(row, u32::MAX), Bias::Left) .column } pub fn ptr_eq(&self, other: &Self) -> bool { self.chunks.ptr_eq(&other.chunks) } } impl<'a> From<&'a str> for Rope { fn from(text: &'a str) -> Self { let mut rope = Self::new(); rope.push(text); rope } } impl<'a> FromIterator<&'a str> for Rope { fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self { let mut rope = Rope::new(); for chunk in iter { rope.push(chunk); } rope } } impl From<String> for Rope { fn from(text: String) -> Self { Rope::from(text.as_str()) } } impl fmt::Display for Rope { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for chunk in self.chunks() { write!(f, "{}", chunk)?; } Ok(()) } } impl fmt::Debug for Rope { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use std::fmt::Write as _; write!(f, "\"")?; let mut format_string = String::new(); for chunk in self.chunks() { write!(&mut format_string, "{:?}", chunk)?; write!(f, "{}", &format_string[1..format_string.len() - 1])?;