summaryrefslogtreecommitdiff
path: root/src/modal.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/modal.zig')
-rw-r--r--src/modal.zig22
1 files changed, 15 insertions, 7 deletions
diff --git a/src/modal.zig b/src/modal.zig
index dd7d2b57..11eda7b9 100644
--- a/src/modal.zig
+++ b/src/modal.zig
@@ -1327,18 +1327,26 @@ pub fn lineEndOffset(text: []const u8, line: usize) usize {
/// acme's do, not grapheme clusters: a lone combining mark or a `\r` is a
/// place of its own, whatever a terminal draws in one cell.
pub fn runeStart(text: []const u8, off: usize) usize {
- var o = @min(off, text.len);
+ const o = @min(off, text.len);
+ if (o == text.len or text[o] & 0xC0 != 0x80) return o;
+ var s = o;
var steps: u8 = 0;
- while (o > 0 and o < text.len and text[o] & 0xC0 == 0x80 and steps < 3) : (steps += 1) o -= 1;
- return o;
+ while (s > 0 and text[s] & 0xC0 == 0x80 and steps < 3) : (steps += 1) s -= 1;
+ // Only a lead byte whose sequence reaches `o`, its bytes all
+ // continuations, makes `o` the inside of a rune; a stray continuation
+ // byte (invalid UTF-8) is a place of its own.
+ const n = std.unicode.utf8ByteSequenceLength(text[s]) catch return o;
+ if (s + n <= o or s + n > text.len) return o;
+ for (text[s + 1 .. s + n]) |c| if (c & 0xC0 != 0x80) return o;
+ return s;
}
/// `off`, or the end of the rune it falls inside.
pub fn runeEnd(text: []const u8, off: usize) usize {
- var o = @min(off, text.len);
- var steps: u8 = 0;
- while (o < text.len and text[o] & 0xC0 == 0x80 and steps < 3) : (steps += 1) o += 1;
- return o;
+ const o = @min(off, text.len);
+ const s = runeStart(text, o);
+ if (s == o) return o;
+ return s + (std.unicode.utf8ByteSequenceLength(text[s]) catch 1);
}
/// The rune boundary after the one at `off`.