on master help sign in
fix: ensure_grapheme_boundary_next_byte needs to index at valid char
2302869Blaž Hrastnik
helix-core/src/graphemes.rs+31−1
@@ -203,7 +203,12 @@ pub fn ensure_grapheme_boundary_next_byte(slice: RopeSlice, byte_idx: usize) ->
203 if byte_idx == 0 {
203 if byte_idx == 0 {
204 byte_idx
204 byte_idx
205 } else {
205 } else {
206+ // TODO: optimize so we're not constructing grapheme cursor twice
207+ if is_grapheme_boundary_byte(slice, byte_idx) {
208+ byte_idx
209+ } else {
206− next_grapheme_boundary_byte(slice, byte_idx - 1)
210+ next_grapheme_boundary_byte(slice, byte_idx)
211+ }
207 }
212 }
208}
213}
209
214
@@ -235,6 +240,31 @@ pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
235 }
240 }
236}
241}
237
242
243+/// Returns whether the given byte position is a grapheme boundary.
244+#[must_use]
245+pub fn is_grapheme_boundary_byte(slice: RopeSlice, byte_idx: usize) -> bool {
246+ // Bounds check
247+ debug_assert!(byte_idx <= slice.len_bytes());
248+
249+ // Get the chunk with our byte index in it.
250+ let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
251+
252+ // Set up the grapheme cursor.
253+ let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
254+
255+ // Determine if the given position is a grapheme cluster boundary.
256+ loop {
257+ match gc.is_boundary(chunk, chunk_byte_idx) {
258+ Ok(n) => return n,
259+ Err(GraphemeIncomplete::PreContext(n)) => {
260+ let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1);
261+ gc.provide_context(ctx_chunk, ctx_byte_start);
262+ }
263+ Err(_) => unreachable!(),
264+ }
265+ }
266+}
267+
238/// An iterator over the graphemes of a `RopeSlice`.
268/// An iterator over the graphemes of a `RopeSlice`.
239#[derive(Clone)]
269#[derive(Clone)]
240pub struct RopeGraphemes<'a> {
270pub struct RopeGraphemes<'a> {