this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

parser: use a ring buffer to store raw text

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>

+49 -104
+6 -63
src/GraphemeCache.zig
··· 9 9 // the start index of the next grapheme 10 10 idx: usize = 0, 11 11 12 - /// the cache of graphemes. This allows up to 2048 graphemes with 4 codepoints 13 - /// each 14 - grapheme_buf: [1024 * 8 / 4]Grapheme = undefined, 15 - 16 - // index of our next grapheme 17 - g_idx: u21 = 0, 18 - 19 - pub const UNICODE_MAX = 1_114_112; 20 - 21 - const Grapheme = struct { 22 - // codepoint is an index into the internal storage 23 - codepoint: u21, 24 - start: usize, 25 - end: usize, 26 - }; 27 - 28 12 /// put a slice of bytes in the cache as a grapheme 29 - pub fn put(self: *GraphemeCache, bytes: []const u8) !u21 { 30 - // See if we already have these bytes. It's a likely case that if we get one 31 - // grapheme, we'll get it again. So this will save a lot of storage and is 32 - // most likely worth the cost as it's pretty rare 33 - for (self.grapheme_buf) |grapheme| { 34 - const g_bytes = self.buf[grapheme.start..grapheme.end]; 35 - if (std.mem.eql(u8, g_bytes, bytes)) { 36 - return grapheme.codepoint; 37 - } 38 - } 39 - if (self.idx + bytes.len > self.buf.len) return error.OutOfGraphemeBufferMemory; 40 - if (self.g_idx + 1 > self.grapheme_buf.len) return error.OutOfGraphemeMemory; 41 - 13 + pub fn put(self: *GraphemeCache, bytes: []const u8) []u8 { 14 + // reset the idx to 0 if we would overflow 15 + if (self.idx + bytes.len > self.buf.len) self.idx = 0; 16 + defer self.idx += bytes.len; 42 17 // copy the grapheme to our storage 43 18 @memcpy(self.buf[self.idx .. self.idx + bytes.len], bytes); 44 - 45 - const g = Grapheme{ 46 - // assign a codepoint that is always outside of valid unicode 47 - .codepoint = self.g_idx + UNICODE_MAX + 1, 48 - .start = self.idx, 49 - .end = self.idx + bytes.len, 50 - }; 51 - self.grapheme_buf[self.g_idx] = g; 52 - self.g_idx += 1; 53 - self.idx += bytes.len; 54 - 55 - return g.codepoint; 56 - } 57 - 58 - /// get the slice of bytes for a given grapheme 59 - pub fn get(self: *GraphemeCache, cp: u21) ![]const u8 { 60 - if (cp < (UNICODE_MAX + 1)) return error.InvalidGraphemeIndex; 61 - const idx: usize = cp - UNICODE_MAX - 1; 62 - if (idx > self.g_idx) return error.InvalidGraphemeIndex; 63 - const g = self.grapheme_buf[idx]; 64 - return self.buf[g.start..g.end]; 65 - } 66 - 67 - test "GraphemeCache: roundtrip" { 68 - var cache: GraphemeCache = .{}; 69 - const cp = try cache.put("abc"); 70 - const bytes = try cache.get(cp); 71 - try testing.expectEqualStrings("abc", bytes); 72 - 73 - const cp_2 = try cache.put("abc"); 74 - try testing.expectEqual(cp, cp_2); 75 - 76 - const cp_3 = try cache.put("def"); 77 - try testing.expectEqual(cp + 1, cp_3); 19 + // return the slice 20 + return self.buf[self.idx .. self.idx + bytes.len]; 78 21 }
+8 -3
src/Key.zig
··· 14 14 /// the unicode codepoint of the key event. 15 15 codepoint: u21, 16 16 17 - /// the text generated from the key event. This will only contain a value if the 18 - /// event generated a multi-codepoint grapheme. If there was only a single 19 - /// codepoint, library users can encode the codepoint directly 17 + /// the text generated from the key event. The underlying slice has a limited 18 + /// lifetime. Vaxis maintains an internal ring buffer to temporarily store text. 19 + /// If the application needs these values longer than the lifetime of the event 20 + /// it must copy the data. 20 21 text: ?[]const u8 = null, 21 22 22 23 /// the shifted codepoint of this key event. This will only be present if the ··· 35 36 pub const escape: u21 = 0x1B; 36 37 pub const space: u21 = 0x20; 37 38 pub const backspace: u21 = 0x7F; 39 + 40 + // multicodepoint is a key which generated text but cannot be expressed as a 41 + // single codepoint. The value is the maximum unicode codepoint + 1 42 + pub const multicodepoint: u21 = 1_114_112 + 1; 38 43 39 44 // kitty encodes these keys directly in the private use area. We reuse those 40 45 // mappings
+1 -6
src/Tty.zig
··· 143 143 switch (event) { 144 144 .key_press => |key| { 145 145 if (@hasField(EventType, "key_press")) { 146 - // HACK: yuck. there has to be a better way 147 - var mut_key = key; 148 - if (key.text) |text| { 149 - mut_key.codepoint = try vx.g_cache.put(text); 150 - } 151 - vx.postEvent(.{ .key_press = mut_key }); 146 + vx.postEvent(.{ .key_press = key }); 152 147 } 153 148 }, 154 149 .focus_in => {
+17 -17
src/parser.zig
··· 4 4 const Key = @import("Key.zig"); 5 5 const CodePointIterator = @import("ziglyph").CodePointIterator; 6 6 const graphemeBreak = @import("ziglyph").graphemeBreak; 7 - const UNICODE_MAX = @import("GraphemeCache.zig").UNICODE_MAX; 8 7 9 8 const log = std.log.scoped(.parser); 10 9 ··· 88 87 var cp = iter.next() orelse return .{ .event = null, .n = 0 }; 89 88 90 89 var code = cp.code; 91 - const g_start = i; 92 90 i += cp.len - 1; // subtract one for the loop iter 93 91 var g_state: u3 = 0; 94 92 while (iter.next()) |next_cp| { 95 93 if (graphemeBreak(cp.code, next_cp.code, &g_state)) { 96 94 break; 97 95 } 98 - code = UNICODE_MAX + 1; 96 + code = Key.multicodepoint; 99 97 i += next_cp.len; 100 98 cp = next_cp; 101 99 } 102 - const text: ?[]const u8 = multi: { 103 - if (code > UNICODE_MAX) { 104 - break :multi input[g_start .. i + 1]; 105 - } else { 106 - break :multi null; 107 - } 108 - }; 109 100 110 - break :blk .{ .codepoint = code, .text = text }; 101 + break :blk .{ .codepoint = code, .text = input[start .. i + 1] }; 111 102 }, 112 103 }; 113 104 return .{ ··· 366 357 test "parse: single xterm keypress" { 367 358 const input = "a"; 368 359 const result = try parse(input); 369 - const expected_key: Key = .{ .codepoint = 'a' }; 360 + const expected_key: Key = .{ 361 + .codepoint = 'a', 362 + .text = "a", 363 + }; 370 364 const expected_event: Event = .{ .key_press = expected_key }; 371 365 372 366 try testing.expectEqual(1, result.n); ··· 376 370 test "parse: single xterm keypress with more buffer" { 377 371 const input = "ab"; 378 372 const result = try parse(input); 379 - const expected_key: Key = .{ .codepoint = 'a' }; 373 + const expected_key: Key = .{ 374 + .codepoint = 'a', 375 + .text = "a", 376 + }; 380 377 const expected_event: Event = .{ .key_press = expected_key }; 381 378 382 379 try testing.expectEqual(1, result.n); 383 - try testing.expectEqual(expected_event, result.event); 380 + try testing.expectEqualStrings(expected_key.text.?, result.event.?.key_press.text.?); 381 + try testing.expectEqualDeep(expected_event, result.event); 384 382 } 385 383 386 384 test "parse: xterm escape keypress" { ··· 546 544 const result = try parse(input); 547 545 const expected_key: Key = .{ 548 546 .codepoint = 0x1F642, 547 + .text = input, 549 548 }; 550 549 const expected_event: Event = .{ .key_press = expected_key }; 551 550 ··· 558 557 const result = try parse(input); 559 558 const expected_key: Key = .{ 560 559 .codepoint = 0x1F642, 560 + .text = "🙂", 561 561 }; 562 562 const expected_event: Event = .{ .key_press = expected_key }; 563 563 564 564 try testing.expectEqual(4, result.n); 565 - try testing.expectEqual(expected_event, result.event); 565 + try testing.expectEqualDeep(expected_event, result.event); 566 566 } 567 567 568 568 test "parse: multiple codepoint grapheme" { ··· 571 571 const input = "👩‍🚀"; 572 572 const result = try parse(input); 573 573 const expected_key: Key = .{ 574 - .codepoint = UNICODE_MAX + 1, 574 + .codepoint = Key.multicodepoint, 575 575 .text = input, 576 576 }; 577 577 const expected_event: Event = .{ .key_press = expected_key }; ··· 586 586 const input = "👩‍🚀abc"; 587 587 const result = try parse(input); 588 588 const expected_key: Key = .{ 589 - .codepoint = UNICODE_MAX + 1, 589 + .codepoint = Key.multicodepoint, 590 590 .text = "👩‍🚀", 591 591 }; 592 592
-6
src/vaxis.zig
··· 82 82 const tpr = @divTrunc(self.render_dur, self.renders); 83 83 log.info("total renders = {d}", .{self.renders}); 84 84 log.info("microseconds per render = {d}", .{tpr}); 85 - log.info("cached graphemes n = {d} / {d}, bytes = {d} / {d}", .{ 86 - self.g_cache.g_idx, 87 - self.g_cache.grapheme_buf.len, 88 - self.g_cache.idx, 89 - self.g_cache.buf.len, 90 - }); 91 85 } 92 86 } 93 87
+17 -9
src/widgets/TextInput.zig
··· 2 2 const Cell = @import("../cell.zig").Cell; 3 3 const Key = @import("../Key.zig"); 4 4 const Window = @import("../Window.zig"); 5 + const GraphemeIterator = @import("ziglyph").GraphemeIterator; 6 + const strWidth = @import("ziglyph").display_width.strWidth; 5 7 6 8 const log = std.log.scoped(.text_input); 7 9 ··· 22 24 pub fn update(self: *TextInput, event: Event) void { 23 25 switch (event) { 24 26 .key_press => |key| { 27 + log.info("key : {}", .{key}); 28 + if (key.text) |text| { 29 + @memcpy(self.buffer[self.buffer_idx .. self.buffer_idx + text.len], text); 30 + self.buffer_idx += text.len; 31 + } 25 32 switch (key.codepoint) { 26 - 0x20...0x7E => { 27 - self.buffer[self.buffer_idx] = @truncate(key.codepoint); 28 - self.buffer_idx += 1; 29 - self.cursor_idx += 1; 30 - }, 31 33 Key.backspace => { 32 34 // TODO: this only works at the end of the array. Then 33 35 // again, we don't have any means to move the cursor yet ··· 41 43 } 42 44 43 45 pub fn draw(self: *TextInput, win: Window) void { 44 - for (0.., self.buffer[0..self.buffer_idx]) |i, b| { 45 - win.writeCell(i, 0, .{ 46 + const input = self.buffer[0..self.buffer_idx]; 47 + var iter = GraphemeIterator.init(input); 48 + var col: usize = 0; 49 + while (iter.next()) |grapheme| { 50 + const g = grapheme.slice(input); 51 + const w = strWidth(g, .full) catch 1; 52 + win.writeCell(col, 0, .{ 46 53 .char = .{ 47 - .grapheme = &[_]u8{b}, 48 - .width = 1, 54 + .grapheme = g, 55 + .width = w, 49 56 }, 50 57 }); 58 + col += w; 51 59 } 52 60 }