Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["simd", "std"]
chunk-len = []
graphemes = ["unicode-segmentation"]
serde = ["dep:serde"]
simd = ["str_indices/simd"]
utf16-metric = []
std = []
utf16-metric = []

# Private features
small_chunks = []
Expand All @@ -43,6 +44,7 @@ serde = { version = "1", optional = true }
unicode-segmentation = { version = "1.10.0", optional = true }

[dev-dependencies]
bincode = { version = "2", features = ["serde"] }
criterion = "0.7"
rand = "0.9"
rand_chacha = "0.9"
Expand Down
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,13 @@

extern crate alloc;

mod rope;
pub mod iter {
//! Iterators over [`Rope`](crate::Rope)s and
//! [`RopeSlice`](crate::RopeSlice)s.

pub use crate::rope::iterators::*;
}

mod rope;
#[doc(hidden)]
pub mod tree;

Expand Down
23 changes: 21 additions & 2 deletions src/rope/gap_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,14 @@ pub struct GapBuffer {
pub(super) right_summary: StrSummary,
}

#[derive(Copy, Clone, Default, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct GapBufferSummary {
/// The sum of the [`StrSummary`]s of the left and right chunks.
pub(super) chunks_summary: StrSummary,

/// The number of chunks in the buffer.
#[cfg(feature = "chunk-len")]
pub(super) num_chunks: usize,
}

impl core::fmt::Debug for GapBuffer {
Expand Down Expand Up @@ -1221,6 +1225,9 @@ impl Leaf for GapBuffer {
fn summarize(&self) -> Self::Summary {
GapBufferSummary {
chunks_summary: self.left_summary + self.right_summary,
#[cfg(feature = "chunk-len")]
num_chunks: (self.len_left() > 0) as usize
+ (self.len_right() > 0) as usize,
}
}
}
Expand Down Expand Up @@ -1331,7 +1338,11 @@ impl Summary for GapBufferSummary {

#[inline]
fn empty() -> Self {
Self { chunks_summary: StrSummary::empty() }
Self {
chunks_summary: StrSummary::empty(),
#[cfg(feature = "chunk-len")]
num_chunks: 0,
}
}
}

Expand All @@ -1349,6 +1360,10 @@ impl AddAssign for GapBufferSummary {
#[inline]
fn add_assign(&mut self, rhs: Self) {
self.chunks_summary += rhs.chunks_summary;
#[cfg(feature = "chunk-len")]
{
self.num_chunks += rhs.num_chunks;
}
}
}

Expand All @@ -1366,6 +1381,10 @@ impl SubAssign for GapBufferSummary {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
self.chunks_summary -= rhs.chunks_summary;
#[cfg(feature = "chunk-len")]
{
self.num_chunks -= rhs.num_chunks;
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/rope/gap_slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ impl<'a> LeafSlice<'a> for GapSlice<'a> {
fn summarize(&self) -> GapBufferSummary {
GapBufferSummary {
chunks_summary: self.right_summary + self.left_summary,
#[cfg(feature = "chunk-len")]
num_chunks: (self.len_left() > 0) as usize
+ (self.len_right() > 0) as usize,
}
}
}
Expand Down
109 changes: 81 additions & 28 deletions src/rope/iterators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub struct Chunks<'a> {
leaves: Leaves<'a, { Rope::arity() }, RopeChunk>,
forward_extra_right: Option<&'a str>,
backward_extra_left: Option<&'a str>,
#[cfg(feature = "chunk-len")]
len: usize,
}

impl<'a> From<&'a Rope> for Chunks<'a> {
Expand All @@ -21,7 +23,13 @@ impl<'a> From<&'a Rope> for Chunks<'a> {
if rope.is_empty() {
let _ = leaves.next();
}
Self { leaves, forward_extra_right: None, backward_extra_left: None }
Self {
leaves,
forward_extra_right: None,
backward_extra_left: None,
#[cfg(feature = "chunk-len")]
len: rope.chunk_len(),
}
}
}

Expand All @@ -32,15 +40,46 @@ impl<'a> From<&RopeSlice<'a>> for Chunks<'a> {
if slice.is_empty() {
let _ = leaves.next();
}
Self { leaves, forward_extra_right: None, backward_extra_left: None }
Self {
leaves,
forward_extra_right: None,
backward_extra_left: None,
#[cfg(feature = "chunk-len")]
len: slice.chunk_len(),
}
}
}

impl<'a> Iterator for Chunks<'a> {
type Item = &'a str;
impl<'a> Chunks<'a> {
#[inline]
fn next_back_inner(&mut self) -> Option<&'a str> {
if let Some(extra) = self.backward_extra_left.take() {
Some(extra)
} else {
let Some(gap_slice) = self.leaves.next_back() else {
return self.forward_extra_right.take();
};

if gap_slice.right_chunk().is_empty() {
#[cfg(feature = "small_chunks")]
if gap_slice.left_chunk().is_empty() {
return self.next_back_inner();
}

debug_assert!(!gap_slice.left_chunk().is_empty());

Some(gap_slice.left_chunk())
} else {
if !gap_slice.left_chunk().is_empty() {
self.backward_extra_left = Some(gap_slice.left_chunk());
}
Some(gap_slice.right_chunk())
}
}
}

#[inline]
fn next(&mut self) -> Option<Self::Item> {
fn next_inner(&mut self) -> Option<&'a str> {
if let Some(extra) = self.forward_extra_right.take() {
Some(extra)
} else {
Expand All @@ -51,7 +90,7 @@ impl<'a> Iterator for Chunks<'a> {
if gap_slice.left_chunk().is_empty() {
#[cfg(feature = "small_chunks")]
if gap_slice.right_chunk().is_empty() {
return self.next();
return self.next_inner();
}

debug_assert!(!gap_slice.right_chunk().is_empty());
Expand All @@ -67,37 +106,51 @@ impl<'a> Iterator for Chunks<'a> {
}
}

impl DoubleEndedIterator for Chunks<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if let Some(extra) = self.backward_extra_left.take() {
Some(extra)
} else {
let Some(gap_slice) = self.leaves.next_back() else {
return self.forward_extra_right.take();
};
impl<'a> Iterator for Chunks<'a> {
type Item = &'a str;

if gap_slice.right_chunk().is_empty() {
#[cfg(feature = "small_chunks")]
if gap_slice.left_chunk().is_empty() {
return self.next_back();
}
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let next = self.next_inner();
#[cfg(feature = "chunk-len")]
{
self.len -= next.is_some() as usize;
}
next
}

debug_assert!(!gap_slice.left_chunk().is_empty());
#[cfg_attr(docsrs, doc(cfg(feature = "chunk-len")))]
#[cfg(feature = "chunk-len")]
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let exact = self.len();
(exact, Some(exact))
}
}

Some(gap_slice.left_chunk())
} else {
if !gap_slice.left_chunk().is_empty() {
self.backward_extra_left = Some(gap_slice.left_chunk());
}
Some(gap_slice.right_chunk())
}
impl DoubleEndedIterator for Chunks<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
let next = self.next_back_inner();
#[cfg(feature = "chunk-len")]
{
self.len -= next.is_some() as usize;
}
next
}
}

impl core::iter::FusedIterator for Chunks<'_> {}

#[cfg_attr(docsrs, doc(cfg(feature = "chunk-len")))]
#[cfg(feature = "chunk-len")]
impl core::iter::ExactSizeIterator for Chunks<'_> {
#[inline]
fn len(&self) -> usize {
self.len
}
}

/// An iterator over the bytes of `Rope`s and `RopeSlice`s.
///
/// This struct is created by the `bytes` method on [`Rope`](Rope::bytes())
Expand Down
19 changes: 18 additions & 1 deletion src/rope/rope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,17 @@ impl Rope {
Chars::from(self)
}

/// Returns the number of chunks in this `Rope`.
///
/// This is equivalent to `self.chunks().len()`, but it doesn't require
/// constructing an intermediate [`Chunks`] iterator.
#[cfg_attr(docsrs, doc(cfg(feature = "chunk-len")))]
#[cfg(feature = "chunk-len")]
#[inline]
pub fn chunk_len(&self) -> usize {
self.tree.summary().num_chunks
}

/// Returns an iterator over the chunks of this [`Rope`].
#[inline]
pub fn chunks(&self) -> Chunks<'_> {
Expand Down Expand Up @@ -982,7 +993,13 @@ mod serde_impls {
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(None)?;
#[cfg(feature = "chunk-len")]
let chunk_len = Some(self.chunk_len());

#[cfg(not(feature = "chunk-len"))]
let chunk_len = None;

let mut seq = serializer.serialize_seq(chunk_len)?;
for chunk in self.chunks() {
seq.serialize_element(chunk)?;
}
Expand Down
11 changes: 11 additions & 0 deletions src/rope/rope_slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,17 @@ impl<'a> RopeSlice<'a> {
Chars::from(self)
}

/// Returns the number of chunks in this `RopeSlice`.
///
/// This is equivalent to `self.chunks().len()`, but it doesn't require
/// constructing an intermediate [`Chunks`] iterator.
#[cfg_attr(docsrs, doc(cfg(feature = "chunk-len")))]
#[cfg(feature = "chunk-len")]
#[inline]
pub fn chunk_len(&self) -> usize {
self.tree_slice.summary().num_chunks
}

/// Returns an iterator over the chunks of this `RopeSlice`.
#[inline]
pub fn chunks(&self) -> Chunks<'a> {
Expand Down
43 changes: 38 additions & 5 deletions tests/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ mod tests {

serde_test::assert_tokens(
&rope,
&[serde_test::Token::Seq { len: None }, serde_test::Token::SeqEnd],
&[
serde_test::Token::Seq { len: chunk_len(&rope) },
serde_test::Token::SeqEnd,
],
);
}

Expand All @@ -22,7 +25,7 @@ mod tests {
serde_test::assert_tokens(
&rope,
&[
serde_test::Token::Seq { len: None },
serde_test::Token::Seq { len: chunk_len(&rope) },
serde_test::Token::Str("lorem ipsum"),
serde_test::Token::SeqEnd,
],
Expand All @@ -40,7 +43,7 @@ mod tests {
serde_test::assert_tokens(
&rope,
&[
serde_test::Token::Seq { len: None },
serde_test::Token::Seq { len: chunk_len(&rope) },
serde_test::Token::Str("lorem ipsum"),
serde_test::Token::Str(" dolor"),
serde_test::Token::SeqEnd,
Expand All @@ -58,7 +61,7 @@ mod tests {
serde_test::assert_tokens(
&rope,
&[
serde_test::Token::Seq { len: None },
serde_test::Token::Seq { len: chunk_len(&rope) },
serde_test::Token::Str("lorem\nipsum"),
serde_test::Token::SeqEnd,
],
Expand All @@ -75,10 +78,40 @@ mod tests {
serde_test::assert_tokens(
&rope,
&[
serde_test::Token::Seq { len: None },
serde_test::Token::Seq { len: chunk_len(&rope) },
serde_test::Token::Str("lorem\r\nipsum"),
serde_test::Token::SeqEnd,
],
);
}

fn chunk_len(_rope: &Rope) -> Option<usize> {
#[cfg(feature = "chunk-len")]
{
Some(_rope.chunk_len())
}
#[cfg(not(feature = "chunk-len"))]
{
None
}
}

#[cfg(feature = "chunk-len")]
#[test]
fn ser_de_bincode() {
let mut rope = Rope::new();
rope.insert(0, "lorem dolor");
rope.insert(6, "ipsuma ");
rope.delete(11..12);

let config = bincode::config::standard();

let serialized = bincode::serde::encode_to_vec(&rope, config).unwrap();

let (deserialized, _) =
bincode::serde::decode_from_slice::<Rope, _>(&serialized, config)
.unwrap();

assert_eq!(rope, deserialized);
}
}