Skip to content

Commit cce3165

Browse files
committed
Implement Rope accessors that return Option
1 parent 37a0a78 commit cce3165

File tree

1 file changed

+113
-4
lines changed

1 file changed

+113
-4
lines changed

src/rope/rope.rs

+113-4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ impl Rope {
6666

6767
/// Returns the byte at `byte_index`.
6868
///
69+
/// For a checked version, see [`get_byte`](Self::get_byte).
70+
///
6971
/// # Panics
7072
///
7173
/// Panics if the byte index is out of bounds (i.e. greater than or equal
@@ -85,14 +87,40 @@ impl Rope {
8587
#[track_caller]
8688
#[inline]
8789
pub fn byte(&self, byte_index: usize) -> u8 {
90+
self.get_byte(byte_index).unwrap_or_else(|| {
91+
panic::byte_index_out_of_bounds(byte_index, self.line_len())
92+
})
93+
}
94+
95+
/// Returns the byte at `byte_index`, if it exists.
96+
///
97+
/// If `byte_index` is out of bounds, returns `None`.
98+
///
99+
/// For a panic'ing version, see [`byte`](Self::byte).
100+
///
101+
/// # Examples
102+
///
103+
/// ```
104+
/// # use crop::Rope;
105+
/// #
106+
/// let r = Rope::from("bar");
107+
///
108+
/// assert_eq!(r.get_byte(0), Some(b'b'));
109+
/// assert_eq!(r.get_byte(1), Some(b'a'));
110+
/// assert_eq!(r.get_byte(2), Some(b'r'));
111+
/// assert_eq!(r.get_byte(3), None);
112+
/// ```
113+
#[track_caller]
114+
#[inline]
115+
pub fn get_byte(&self, byte_index: usize) -> Option<u8> {
88116
if byte_index >= self.byte_len() {
89-
panic::byte_index_out_of_bounds(byte_index, self.byte_len());
117+
return None;
90118
}
91119

92120
let (chunk, ByteMetric(chunk_byte_offset)) =
93121
self.tree.leaf_at_measure(ByteMetric(byte_index + 1));
94122

95-
chunk.byte(byte_index - chunk_byte_offset)
123+
Some(chunk.byte(byte_index - chunk_byte_offset))
96124
}
97125

98126
/// Returns the length of the `Rope` in bytes.
@@ -446,6 +474,9 @@ impl Rope {
446474
/// [`line_slice()`](Self::line_slice()) in the
447475
/// `line_index..line_index + 1` range.
448476
///
477+
/// For a checked version, see
478+
/// [`get_line_slice()`](Self::get_line_slice()).
479+
///
449480
/// # Panics
450481
///
451482
/// Panics if the line index is out of bounds (i.e. greater than or equal
@@ -465,8 +496,38 @@ impl Rope {
465496
#[track_caller]
466497
#[inline]
467498
pub fn line(&self, line_index: usize) -> RopeSlice<'_> {
499+
self.get_line(line_index).unwrap_or_else(|| {
500+
panic::line_index_out_of_bounds(line_index, self.line_len())
501+
})
502+
}
503+
504+
/// Returns the line at `line_index`, without its line terminator, if it exists.
505+
///
506+
/// If `line_index` is out of bounds, returns `None`.
507+
///
508+
/// If you want to include the line break consider calling
509+
/// [`get_line_slice()`](Self::get_line_slice()) in the
510+
/// `line_index..line_index + 1` range.
511+
///
512+
/// For a panic'ing version, see [`line_slice()`](Self::line_slice()).
513+
///
514+
/// # Examples
515+
///
516+
/// ```
517+
/// # use crop::Rope;
518+
/// #
519+
/// let r = Rope::from("foo\nbar\r\nbaz");
520+
///
521+
/// assert_eq!(r.get_line(0).unwrap(), "foo");
522+
/// assert_eq!(r.get_line(1).unwrap(), "bar");
523+
/// assert_eq!(r.get_line(2).unwrap(), "baz");
524+
/// assert_eq!(r.get_line(3), None);
525+
/// ```
526+
#[track_caller]
527+
#[inline]
528+
pub fn get_line(&self, line_index: usize) -> Option<RopeSlice<'_>> {
468529
if line_index >= self.line_len() {
469-
panic::line_index_out_of_bounds(line_index, self.line_len());
530+
return None;
470531
}
471532

472533
let tree_slice = self
@@ -479,7 +540,7 @@ impl Rope {
479540
line.truncate_trailing_line_break();
480541
}
481542

482-
line
543+
Some(line)
483544
}
484545

485546
/// Returns the number of lines in the `Rope`.
@@ -551,6 +612,10 @@ impl Rope {
551612
/// Returns an immutable slice of the `Rope` in the specified line range,
552613
/// where the start and end of the range are interpreted as offsets.
553614
///
615+
/// If you want a single line, see [`line()`](Self::line).
616+
///
617+
/// If you want a checked version, see [`get_line_slice()`](Self::get_line_slice).
618+
///
554619
/// # Panics
555620
///
556621
/// Panics if the start is greater than the end or if the end is out of
@@ -566,6 +631,7 @@ impl Rope {
566631
/// assert_eq!(r.line_slice(..1), "foo\n");
567632
/// assert_eq!(r.line_slice(1..3), "bar\r\nbaz\n");
568633
/// assert_eq!(r.line_slice(3..), "foobar\n");
634+
/// assert_eq!(r.line_slice(4..), "");
569635
/// ```
570636
#[track_caller]
571637
#[inline]
@@ -587,6 +653,49 @@ impl Rope {
587653
self.tree.slice(RawLineMetric(start)..RawLineMetric(end)).into()
588654
}
589655

656+
/// Returns an immutable slice of the `Rope` in the specified line range,
657+
/// if it exists, where the start and end of the range are interpreted as
658+
/// offsets.
659+
///
660+
/// If `line_range` is out of bounds, returns `None`.
661+
///
662+
/// If you want a single line, see [`get_line()`](Self::get_line).
663+
///
664+
/// For a panic'ing version, see [`line_slice`](Self::line_slice).
665+
///
666+
/// # Examples
667+
///
668+
/// ```
669+
/// # use crop::Rope;
670+
/// #
671+
/// let r = Rope::from("foo\nbar\r\nbaz\nfoobar\n");
672+
///
673+
/// assert_eq!(r.get_line_slice(..1).unwrap(), "foo\n");
674+
/// assert_eq!(r.get_line_slice(1..3).unwrap(), "bar\r\nbaz\n");
675+
/// assert_eq!(r.get_line_slice(3..).unwrap(), "foobar\n");
676+
/// assert_eq!(r.get_line_slice(4..).unwrap(), "");
677+
/// assert_eq!(r.get_line_slice(5..), None);
678+
/// ```
679+
#[track_caller]
680+
#[inline]
681+
pub fn get_line_slice<R>(&self, line_range: R) -> Option<RopeSlice<'_>>
682+
where
683+
R: RangeBounds<usize>,
684+
{
685+
let (start, end) =
686+
range_bounds_to_start_end(line_range, 0, self.line_len());
687+
688+
if start > end {
689+
return None;
690+
}
691+
692+
if end > self.line_len() {
693+
return None;
694+
}
695+
696+
Some(self.tree.slice(RawLineMetric(start)..RawLineMetric(end)).into())
697+
}
698+
590699
/// Returns an iterator over the lines of this `Rope`, not including the
591700
/// line terminators.
592701
///

0 commit comments

Comments
 (0)