a modern tui library written in zig
1const std = @import("std");
2const uucode = @import("uucode");
3
4// Old API-compatible Grapheme value
5pub const Grapheme = struct {
6 start: usize,
7 len: usize,
8
9 pub fn bytes(self: Grapheme, str: []const u8) []const u8 {
10 return str[self.start .. self.start + self.len];
11 }
12};
13
14// Old API-compatible iterator that yields Grapheme with .len and .bytes()
15pub const GraphemeIterator = struct {
16 str: []const u8,
17 inner: uucode.grapheme.Iterator(uucode.utf8.Iterator),
18 start: usize = 0,
19 prev_break: bool = true,
20
21 pub fn init(str: []const u8) GraphemeIterator {
22 return .{
23 .str = str,
24 .inner = uucode.grapheme.Iterator(uucode.utf8.Iterator).init(.init(str)),
25 };
26 }
27
28 pub fn next(self: *GraphemeIterator) ?Grapheme {
29 while (self.inner.next()) |res| {
30 // When leaving a break and entering a non-break, set the start of a cluster
31 if (self.prev_break and !res.is_break) {
32 const cp_len: usize = std.unicode.utf8CodepointSequenceLength(res.cp) catch 1;
33 self.start = self.inner.i - cp_len;
34 }
35
36 // A break marks the end of the current grapheme
37 if (res.is_break) {
38 const end = self.inner.i;
39 const s = self.start;
40 self.start = end;
41 self.prev_break = true;
42 return .{ .start = s, .len = end - s };
43 }
44
45 self.prev_break = false;
46 }
47
48 // Flush the last grapheme if we ended mid-cluster
49 if (!self.prev_break and self.start < self.str.len) {
50 const s = self.start;
51 const len = self.str.len - s;
52 self.start = self.str.len;
53 self.prev_break = true;
54 return .{ .start = s, .len = len };
55 }
56
57 return null;
58 }
59};
60
61/// creates a grapheme iterator based on str
62pub fn graphemeIterator(str: []const u8) GraphemeIterator {
63 return GraphemeIterator.init(str);
64}