diff --git a/Cargo.toml b/Cargo.toml index aa515ca..67c7ce4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = [] @@ -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" diff --git a/src/lib.rs b/src/lib.rs index 52350ad..d956468 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/rope/gap_buffer.rs b/src/rope/gap_buffer.rs index 7cb4184..6d6527b 100644 --- a/src/rope/gap_buffer.rs +++ b/src/rope/gap_buffer.rs @@ -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 { @@ -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, } } } @@ -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, + } } } @@ -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; + } } } @@ -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; + } } } diff --git a/src/rope/gap_slice.rs b/src/rope/gap_slice.rs index 71a5c97..078fa12 100644 --- a/src/rope/gap_slice.rs +++ b/src/rope/gap_slice.rs @@ -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, } } } diff --git a/src/rope/iterators.rs b/src/rope/iterators.rs index da22a26..e770bcb 100644 --- a/src/rope/iterators.rs +++ b/src/rope/iterators.rs @@ -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> { @@ -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(), + } } } @@ -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 { + fn next_inner(&mut self) -> Option<&'a str> { if let Some(extra) = self.forward_extra_right.take() { Some(extra) } else { @@ -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()); @@ -67,37 +106,51 @@ impl<'a> Iterator for Chunks<'a> { } } -impl DoubleEndedIterator for Chunks<'_> { - #[inline] - fn next_back(&mut self) -> Option { - 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 { + 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) { + 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 { + 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()) diff --git a/src/rope/rope.rs b/src/rope/rope.rs index 6bb778e..3211974 100644 --- a/src/rope/rope.rs +++ b/src/rope/rope.rs @@ -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<'_> { @@ -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)?; } diff --git a/src/rope/rope_slice.rs b/src/rope/rope_slice.rs index c7c921c..e816325 100644 --- a/src/rope/rope_slice.rs +++ b/src/rope/rope_slice.rs @@ -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> { diff --git a/tests/serde.rs b/tests/serde.rs index 0968d7f..b9e6f0e 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -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, + ], ); } @@ -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, ], @@ -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, @@ -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, ], @@ -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 { + #[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::(&serialized, config) + .unwrap(); + + assert_eq!(rope, deserialized); + } }