Skip to content

Commit ebd1c97

Browse files
committed
add checked accessors to Rope and RopeSlice
- .get_byte(byte_index) - .get_line(line_index) - .get_line_slice(line_range)
1 parent 37a0a78 commit ebd1c97

File tree

2 files changed

+274
-50
lines changed

2 files changed

+274
-50
lines changed

src/rope/rope.rs

+134-25
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ pub struct Rope {
3434
}
3535

3636
impl Rope {
37+
pub(super) const fn arity() -> usize {
38+
ARITY
39+
}
40+
3741
#[doc(hidden)]
3842
pub fn assert_invariants(&self) {
3943
self.tree.assert_invariants();
@@ -66,6 +70,8 @@ impl Rope {
6670

6771
/// Returns the byte at `byte_index`.
6872
///
73+
/// For a checked version, see [`get_byte`](Self::get_byte).
74+
///
6975
/// # Panics
7076
///
7177
/// Panics if the byte index is out of bounds (i.e. greater than or equal
@@ -85,14 +91,9 @@ impl Rope {
8591
#[track_caller]
8692
#[inline]
8793
pub fn byte(&self, byte_index: usize) -> u8 {
88-
if byte_index >= self.byte_len() {
89-
panic::byte_index_out_of_bounds(byte_index, self.byte_len());
90-
}
91-
92-
let (chunk, ByteMetric(chunk_byte_offset)) =
93-
self.tree.leaf_at_measure(ByteMetric(byte_index + 1));
94-
95-
chunk.byte(byte_index - chunk_byte_offset)
94+
self.get_byte(byte_index).unwrap_or_else(|| {
95+
panic::byte_index_out_of_bounds(byte_index, self.line_len())
96+
})
9697
}
9798

9899
/// Returns the length of the `Rope` in bytes.
@@ -299,8 +300,120 @@ impl Rope {
299300
self.replace(byte_range, "");
300301
}
301302

302-
pub(super) const fn arity() -> usize {
303-
ARITY
303+
/// Returns the byte at `byte_index`, if it exists.
304+
///
305+
/// If `byte_index` is out of bounds, returns `None`.
306+
///
307+
/// For a panic'ing version, see [`byte`](Self::byte).
308+
///
309+
/// # Examples
310+
///
311+
/// ```
312+
/// # use crop::Rope;
313+
/// #
314+
/// let r = Rope::from("bar");
315+
///
316+
/// assert_eq!(r.get_byte(0), Some(b'b'));
317+
/// assert_eq!(r.get_byte(1), Some(b'a'));
318+
/// assert_eq!(r.get_byte(2), Some(b'r'));
319+
/// assert_eq!(r.get_byte(3), None);
320+
/// ```
321+
#[track_caller]
322+
#[inline]
323+
pub fn get_byte(&self, byte_index: usize) -> Option<u8> {
324+
if byte_index >= self.byte_len() {
325+
return None;
326+
}
327+
328+
let (chunk, ByteMetric(chunk_byte_offset)) =
329+
self.tree.leaf_at_measure(ByteMetric(byte_index + 1));
330+
331+
Some(chunk.byte(byte_index - chunk_byte_offset))
332+
}
333+
334+
/// Returns the line at `line_index`, without its line terminator, if it exists.
335+
///
336+
/// If `line_index` is out of bounds, returns `None`.
337+
///
338+
/// If you want to include the line break consider calling
339+
/// [`get_line_slice()`](Self::get_line_slice()) in the
340+
/// `line_index..line_index + 1` range.
341+
///
342+
/// For a panic'ing version, see [`line_slice()`](Self::line_slice()).
343+
///
344+
/// # Examples
345+
///
346+
/// ```
347+
/// # use crop::Rope;
348+
/// #
349+
/// let r = Rope::from("foo\nbar\r\nbaz");
350+
///
351+
/// assert_eq!(r.get_line(0).unwrap(), "foo");
352+
/// assert_eq!(r.get_line(1).unwrap(), "bar");
353+
/// assert_eq!(r.get_line(2).unwrap(), "baz");
354+
/// assert_eq!(r.get_line(3), None);
355+
/// ```
356+
#[track_caller]
357+
#[inline]
358+
pub fn get_line(&self, line_index: usize) -> Option<RopeSlice<'_>> {
359+
if line_index >= self.line_len() {
360+
return None;
361+
}
362+
363+
let tree_slice = self
364+
.tree
365+
.slice(RawLineMetric(line_index)..RawLineMetric(line_index + 1));
366+
367+
let mut line = RopeSlice { tree_slice, has_trailing_newline: false };
368+
369+
if line.tree_slice.summary().line_breaks() == 1 {
370+
line.truncate_trailing_line_break();
371+
}
372+
373+
Some(line)
374+
}
375+
376+
/// Returns an immutable slice of the `Rope` in the specified line range,
377+
/// if it exists, where the start and end of the range are interpreted as
378+
/// offsets.
379+
///
380+
/// If `line_range` is out of bounds, returns `None`.
381+
///
382+
/// If you want a single line, see [`get_line()`](Self::get_line).
383+
///
384+
/// For a panic'ing version, see [`line_slice`](Self::line_slice).
385+
///
386+
/// # Examples
387+
///
388+
/// ```
389+
/// # use crop::Rope;
390+
/// #
391+
/// let r = Rope::from("foo\nbar\r\nbaz\nfoobar\n");
392+
///
393+
/// assert_eq!(r.get_line_slice(..1).unwrap(), "foo\n");
394+
/// assert_eq!(r.get_line_slice(1..3).unwrap(), "bar\r\nbaz\n");
395+
/// assert_eq!(r.get_line_slice(3..).unwrap(), "foobar\n");
396+
/// assert_eq!(r.get_line_slice(4..).unwrap(), "");
397+
/// assert_eq!(r.get_line_slice(5..), None);
398+
/// ```
399+
#[track_caller]
400+
#[inline]
401+
pub fn get_line_slice<R>(&self, line_range: R) -> Option<RopeSlice<'_>>
402+
where
403+
R: RangeBounds<usize>,
404+
{
405+
let (start, end) =
406+
range_bounds_to_start_end(line_range, 0, self.line_len());
407+
408+
if start > end {
409+
return None;
410+
}
411+
412+
if end > self.line_len() {
413+
return None;
414+
}
415+
416+
Some(self.tree.slice(RawLineMetric(start)..RawLineMetric(end)).into())
304417
}
305418

306419
/// Returns an iterator over the extended grapheme clusters of this
@@ -446,6 +559,9 @@ impl Rope {
446559
/// [`line_slice()`](Self::line_slice()) in the
447560
/// `line_index..line_index + 1` range.
448561
///
562+
/// For a checked version, see
563+
/// [`get_line_slice()`](Self::get_line_slice()).
564+
///
449565
/// # Panics
450566
///
451567
/// Panics if the line index is out of bounds (i.e. greater than or equal
@@ -465,21 +581,9 @@ impl Rope {
465581
#[track_caller]
466582
#[inline]
467583
pub fn line(&self, line_index: usize) -> RopeSlice<'_> {
468-
if line_index >= self.line_len() {
469-
panic::line_index_out_of_bounds(line_index, self.line_len());
470-
}
471-
472-
let tree_slice = self
473-
.tree
474-
.slice(RawLineMetric(line_index)..RawLineMetric(line_index + 1));
475-
476-
let mut line = RopeSlice { tree_slice, has_trailing_newline: false };
477-
478-
if line.tree_slice.summary().line_breaks() == 1 {
479-
line.truncate_trailing_line_break();
480-
}
481-
482-
line
584+
self.get_line(line_index).unwrap_or_else(|| {
585+
panic::line_index_out_of_bounds(line_index, self.line_len())
586+
})
483587
}
484588

485589
/// Returns the number of lines in the `Rope`.
@@ -551,6 +655,10 @@ impl Rope {
551655
/// Returns an immutable slice of the `Rope` in the specified line range,
552656
/// where the start and end of the range are interpreted as offsets.
553657
///
658+
/// If you want a single line, see [`line()`](Self::line).
659+
///
660+
/// If you want a checked version, see [`get_line_slice()`](Self::get_line_slice).
661+
///
554662
/// # Panics
555663
///
556664
/// Panics if the start is greater than the end or if the end is out of
@@ -566,6 +674,7 @@ impl Rope {
566674
/// assert_eq!(r.line_slice(..1), "foo\n");
567675
/// assert_eq!(r.line_slice(1..3), "bar\r\nbaz\n");
568676
/// assert_eq!(r.line_slice(3..), "foobar\n");
677+
/// assert_eq!(r.line_slice(4..), "");
569678
/// ```
570679
#[track_caller]
571680
#[inline]

0 commit comments

Comments
 (0)