crop/
lib.rs

1//! crop is an implementation of a UTF-8 text rope, a data structure designed
2//! to be used in applications that need to handle frequent edits to
3//! arbitrarily large buffers, such as text editors.
4//!
5//! crop's `Rope` is backed by a
6//! [B-tree](https://en.wikipedia.org/wiki/B-tree), ensuring that the time
7//! complexity of inserting, deleting or replacing a piece of text is always
8//! logarithmic in the size of the `Rope`.
9//!
10//! The crate has a relatively straightforward API. There are 3 structs to be
11//! aware of:
12//!
13//! - [`Rope`]: the star of the crate;
14//! - [`RopeSlice`]: an immutable slice of a `Rope`;
15//! - [`RopeBuilder`]: an incremental `Rope` builder.
16//!
17//! plus the [`iter`] module which contains iterators over `Rope`s and
18//! `RopeSlice`s. That's it.
19//!
20//! # Example usage
21//!
22//! ```no_run
23//! # use std::fs::{File};
24//! # use std::io::{BufWriter, Write};
25//! # use std::thread;
26//! # use crop::{RopeBuilder, RopeSlice};
27//! // A `Rope` can be created either directly from a string or incrementally
28//! // using the `RopeBuilder`.
29//!
30//! let mut builder = RopeBuilder::new();
31//!
32//! builder
33//!     .append("I am a 🦀\n")
34//!     .append("Who walks the shore\n")
35//!     .append("And pinches toes all day.\n")
36//!     .append("\n")
37//!     .append("If I were you\n")
38//!     .append("I'd wear some 👟\n")
39//!     .append("And not get in my way.\n");
40//!
41//! let mut rope = builder.build();
42//!
43//! // `Rope`s can be sliced to obtain `RopeSlice`s.
44//! //
45//! // A `RopeSlice` is to a `Rope` as a `&str` is to a `String`: the former in
46//! // each pair are borrowed references of the latter.
47//!
48//! // A `Rope` can be sliced using either byte offsets:
49//!
50//! let byte_slice: RopeSlice = rope.byte_slice(..32);
51//!
52//! assert_eq!(byte_slice, "I am a 🦀\nWho walks the shore\n");
53//!
54//! // or line offsets:
55//!
56//! let line_slice: RopeSlice = rope.line_slice(..2);
57//!
58//! assert_eq!(line_slice, byte_slice);
59//!
60//! // We can also get a `RopeSlice` by asking the `Rope` for a specific line
61//! // index:
62//!
63//! assert_eq!(rope.line(5), "I'd wear some 👟");
64//!
65//! // We can modify that line by getting its start/end byte offsets:
66//!
67//! let start: usize = rope.byte_of_line(5);
68//!
69//! let end: usize = rope.byte_of_line(6);
70//!
71//! // and replacing that byte range with some other text:
72//!
73//! rope.replace(start..end, "I'd rock some 👠\n");
74//!
75//! assert_eq!(rope.line(5), "I'd rock some 👠");
76//!
77//! // `Rope`s use `Arc`s to share data between threads, so cloning them is
78//! // extremely cheap.
79//!
80//! let snapshot = rope.clone();
81//!
82//! // This allows to save a `Rope` to disk in a background thread while
83//! // keeping the main thread responsive.
84//!
85//! thread::spawn(move || {
86//!     let mut file =
87//!         BufWriter::new(File::create("my_little_poem.txt").unwrap());
88//!
89//!     // The text content is stored in the leaves of the B-tree, where each
90//!     // chunk can store up to 1KB of data.
91//!     //
92//!     // We can iterate over the leaves using the `Chunks` iterator which
93//!     // yields the chunks of the `Rope` as string slices.
94//!
95//!     for chunk in snapshot.chunks() {
96//!         file.write_all(chunk.as_bytes()).unwrap();
97//!     }
98//! })
99//! .join()
100//! .unwrap();
101//! ```
102//!
103//! # On offsets and indexes
104//!
105//! Some functions like [`Rope::byte()`] or [`Rope::line()`] take byte or line
106//! **indexes** as parameters, while others like [`Rope::insert()`],
107//! [`Rope::replace()`] or [`Rope::is_char_boundary()`] expect byte or line
108//! **offsets**.
109//!
110//! These two terms may sound very similar to each other, but in this context
111//! they mean slightly different things.
112//!
113//! An index is a 0-based number used to target **one specific** byte or line.
114//! For example, in the word `"bar"` the byte representing the letter `'b'` has
115//! an index of 0, `'a'`'s index is 1 and `'r'`'s index is 2. The maximum value
116//! for an index is **one less** than the length of the string.
117//!
118//! Hopefully nothing surprising so far.
119//!
120//! On the other hand, an offset doesn't refer to an item, it refers to the
121//! **boundary** between two adjacent items. For example, if we want to insert
122//! another `'a'` between the `'a'` and the `'r'` of the word `"bar"` we need
123//! to use a byte offset of 2. The maximum value for an offset is **equal to**
124//! the length of the string.
125//!
126//! # Feature flags
127//!
128//! The following feature flags can be used to tweak crop's behavior and
129//! enable additional APIs:
130//!
131//! - `simd` (enabled by default): enables SIMD on supported platforms;
132//!
133//! - `graphemes` (disabled by default): enables a few grapheme-oriented APIs
134//!   on `Rope`s and `RopeSlice`s such as the
135//!   [`Graphemes`](crate::iter::Graphemes) iterator and others;
136//!
137//! - `utf16-metric` (disabled by default): makes the `Rope` and `RopeSlice`
138//!   track the UTF-16 code units they'd have if their content was stored as
139//!   UTF-16 instead of UTF-8, allowing them to efficiently convert UTF-16
140//!   code unit offsets to and from byte offsets in logarithmic time.
141
142#![cfg_attr(not(any(test, feature = "std")), no_std)]
143#![allow(clippy::explicit_auto_deref)]
144#![allow(clippy::module_inception)]
145#![cfg_attr(docsrs, feature(doc_cfg))]
146#![deny(missing_docs)]
147#![deny(rustdoc::broken_intra_doc_links)]
148#![deny(rustdoc::private_intra_doc_links)]
149#![warn(clippy::std_instead_of_core)]
150#![warn(clippy::std_instead_of_alloc)]
151#![warn(clippy::alloc_instead_of_core)]
152
153extern crate alloc;
154
155pub mod iter {
156    //! Iterators over [`Rope`](crate::Rope)s and
157    //! [`RopeSlice`](crate::RopeSlice)s.
158
159    pub use crate::rope::iterators::*;
160}
161
162mod rope;
163
164#[doc(hidden)]
165pub mod tree;
166
167// These are not part of the public API, we only export them to be able to run
168// doctests.
169pub use rope::{Rope, RopeBuilder, RopeSlice};
170#[doc(hidden)]
171pub use rope::{
172    gap_buffer::GapBuffer,
173    gap_slice::GapSlice,
174    metrics::ChunkSummary,
175};
176
177#[inline]
178pub(crate) fn range_bounds_to_start_end<T, B>(
179    range: B,
180    lo: usize,
181    hi: usize,
182) -> (usize, usize)
183where
184    B: core::ops::RangeBounds<T>,
185    T: core::ops::Add<usize, Output = usize> + Into<usize> + Copy,
186{
187    use core::ops::Bound;
188
189    let start = match range.start_bound() {
190        Bound::Included(&n) => n.into(),
191        Bound::Excluded(&n) => n + 1,
192        Bound::Unbounded => lo,
193    };
194
195    let end = match range.end_bound() {
196        Bound::Included(&n) => n + 1,
197        Bound::Excluded(&n) => n.into(),
198        Bound::Unbounded => hi,
199    };
200
201    (start, end)
202}