Repository: helix-editor/helix
File: helix-stdx/src/rope.rs (Rust)

Code before:
    fn grapheme_indices_at(self, byte_idx: usize) -> RopeGraphemeIndices<'a>;
    /// Finds the byte index of the next grapheme boundary after `byte_idx`.
    ///
    /// If the byte index lies on the last grapheme cluster in the slice then this function
    /// returns `RopeSlice::len_bytes`.
    ///
    /// # Example
    ///
    /// ```
    /// # use ropey::Rope;
    /// # use helix_stdx::rope::RopeSliceExt;
    /// let text = Rope::from_str("😶‍🌫️🏴‍☠️🖼️");
    /// let slice = text.slice(..);
    /// let mut byte_idx = 0;
    /// assert_eq!(slice.graphemes_at(byte_idx).next(), Some("😶‍🌫️".into()));
    /// byte_idx = slice.next_grapheme_boundary(byte_idx);
    /// assert_eq!(slice.graphemes_at(byte_idx).next(), Some("🏴‍☠️".into()));
    /// byte_idx = slice.next_grapheme_boundary(byte_idx);
    /// assert_eq!(slice.graphemes_at(byte_idx).next(), Some("🖼️".into()));
    /// byte_idx = slice.next_grapheme_boundary(byte_idx);
    /// assert_eq!(slice.graphemes_at(byte_idx).next(), None);
    /// assert_eq!(byte_idx, slice.len_bytes());
    /// ```
    fn next_grapheme_boundary(self, byte_idx: usize) -> usize {
        self.nth_next_grapheme_boundary(byte_idx, 1)
    }
    /// Finds the byte index of the `n`th grapheme cluster after the given `byte_idx`.
    ///
    /// If there are more than `n` grapheme clusters after `byte_idx` in the rope then this
    /// function returns `RopeSlice::len_bytes`.
    ///
    /// This is functionally equivalent to calling `next_grapheme_boundary` `n` times but is more
    /// efficient.
    fn nth_next_grapheme_boundary(self, byte_idx: usize, n: usize) -> usize;
    /// Finds the byte index of the previous grapheme boundary before `byte_idx`.
    ///
    /// If the byte index lies on the first grapheme cluster in the slice then this function
    /// returns zero.
    ///
    /// # Example
    ///
    /// ```
    /// # use ropey::Rope;
    /// # use helix_stdx::rope::RopeSliceExt;
    /// let text = Rope::from_str("😶‍🌫️🏴‍☠️🖼️");
    /// let slice = text.slice(..);
    /// let mut byte_idx = text.len_bytes();
    /// assert_eq!(slice.graphemes_at(byte_idx).prev(), Some("🖼️".into()));
    /// byte_idx = slice.prev_grapheme_boundary(byte_idx);
    /// assert_eq!(slice.graphemes_at(byte_idx).prev(), Some("🏴‍☠️".into()));
    /// byte_idx = slice.prev_grapheme_boundary(byte_idx);

Review comment (refactor):
I suggest adding some tests where the argument is not already aligned with a grapheme boundry. e.g:
```
    /// assert_eq!(slice.next_grapheme_boundary(byte_idx - 1), byte_idx);
    /// assert_eq!(slice.next_grapheme_boundary(byte_idx - 2), byte_idx);
    /// assert_eq!(slice.next_grapheme_boundary(byte_idx + 1), slice.next_grapheme_boundary(byte_idx));
    /// assert_eq!(slice.next_grapheme_boundary(byte_idx + 2), slice.next_grapheme_boundary(byte_idx));
```

Same for prev

Code after:
    fn grapheme_indices_at(self, byte_idx: usize) -> RopeGraphemeIndices<'a>;
    /// Finds the byte index of the next grapheme boundary after `byte_idx`.
    ///
    /// If the byte index lies on the last grapheme cluster in the slice then this function
    /// returns `RopeSlice::len_bytes`.
    ///
    /// # Example
    ///
    /// ```
    /// # use ropey::Rope;
    /// # use helix_stdx::rope::RopeSliceExt;
    /// let text = Rope::from_str("😶‍🌫️🏴‍☠️🖼️");
    /// let slice = text.slice(..);
    /// let mut byte_idx = 0;
    /// assert_eq!(slice.graphemes_at(byte_idx).next(), Some("😶‍🌫️".into()));
    /// byte_idx = slice.next_grapheme_boundary(byte_idx);
    /// assert_eq!(slice.graphemes_at(byte_idx).next(), Some("🏴‍☠️".into()));
    ///
    /// // If `byte_idx` does not lie on a character or grapheme boundary then this function is
    /// // functionally the same as `ceil_grapheme_boundary`.
    /// assert_eq!(slice.next_grapheme_boundary(byte_idx - 1), byte_idx);
    /// assert_eq!(slice.next_grapheme_boundary(byte_idx - 2), byte_idx);
    /// assert_eq!(slice.next_grapheme_boundary(byte_idx + 1), slice.next_grapheme_boundary(byte_idx));
    /// assert_eq!(slice.next_grapheme_boundary(byte_idx + 2), slice.next_grapheme_boundary(byte_idx));
    ///
    /// byte_idx = slice.next_grapheme_boundary(byte_idx);
    /// assert_eq!(slice.graphemes_at(byte_idx).next(), Some("🖼️".into()));
    /// byte_idx = slice.next_grapheme_boundary(byte_idx);
    /// assert_eq!(slice.graphemes_at(byte_idx).next(), None);
    /// assert_eq!(byte_idx, slice.len_bytes());
    /// ```
    fn next_grapheme_boundary(self, byte_idx: usize) -> usize {
        self.nth_next_grapheme_boundary(byte_idx, 1)
    }
    /// Finds the byte index of the `n`th grapheme cluster after the given `byte_idx`.
    ///
    /// If there are fewer than `n` grapheme clusters after `byte_idx` in the rope then this
    /// function returns `RopeSlice::len_bytes`.
    ///
    /// This is functionally equivalent to calling `next_grapheme_boundary` `n` times but is more
    /// efficient.
    fn nth_next_grapheme_boundary(self, byte_idx: usize, n: usize) -> usize;
    /// Finds the byte index of the previous grapheme boundary before `byte_idx`.
    ///
    /// If the byte index lies on the first grapheme cluster in the slice then this function
    /// returns zero.
    ///
    /// # Example
    ///
    /// ```
    /// # use ropey::Rope;