this repo has no description
13
fork

Configure Feed

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

at b58ae3a2fa16b3b7f11d2a7297c73a1e839d035b 224 lines 7.2 kB view raw
1const std = @import("std"); 2const vaxis = @import("../main.zig"); 3const uucode = @import("uucode"); 4const ScrollView = vaxis.widgets.ScrollView; 5 6/// Simple grapheme representation to replace Graphemes.Grapheme 7const Grapheme = struct { 8 len: u16, 9 offset: u32, 10}; 11 12pub const BufferWriter = struct { 13 pub const Error = error{OutOfMemory}; 14 pub const Writer = std.io.GenericWriter(@This(), Error, write); 15 16 allocator: std.mem.Allocator, 17 buffer: *Buffer, 18 19 pub fn write(self: @This(), bytes: []const u8) Error!usize { 20 try self.buffer.append(self.allocator, .{ 21 .bytes = bytes, 22 }); 23 return bytes.len; 24 } 25 26 pub fn writer(self: @This()) Writer { 27 return .{ .context = self }; 28 } 29}; 30 31pub const Buffer = struct { 32 const StyleList = std.ArrayListUnmanaged(vaxis.Style); 33 const StyleMap = std.HashMapUnmanaged(usize, usize, std.hash_map.AutoContext(usize), std.hash_map.default_max_load_percentage); 34 35 pub const Content = struct { 36 bytes: []const u8, 37 }; 38 39 pub const Style = struct { 40 begin: usize, 41 end: usize, 42 style: vaxis.Style, 43 }; 44 45 pub const Error = error{OutOfMemory}; 46 47 grapheme: std.MultiArrayList(Grapheme) = .{}, 48 content: std.ArrayListUnmanaged(u8) = .{}, 49 style_list: StyleList = .{}, 50 style_map: StyleMap = .{}, 51 rows: usize = 0, 52 cols: usize = 0, 53 // used when appending to a buffer 54 last_cols: usize = 0, 55 56 pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { 57 self.style_map.deinit(allocator); 58 self.style_list.deinit(allocator); 59 self.grapheme.deinit(allocator); 60 self.content.deinit(allocator); 61 self.* = undefined; 62 } 63 64 /// Clears all buffer data. 65 pub fn clear(self: *@This(), allocator: std.mem.Allocator) void { 66 self.deinit(allocator); 67 self.* = .{}; 68 } 69 70 /// Replaces contents of the buffer, all previous buffer data is lost. 71 pub fn update(self: *@This(), allocator: std.mem.Allocator, content: Content) Error!void { 72 self.clear(allocator); 73 errdefer self.clear(allocator); 74 try self.append(allocator, content); 75 } 76 77 /// Appends content to the buffer. 78 pub fn append(self: *@This(), allocator: std.mem.Allocator, content: Content) Error!void { 79 var cols: usize = self.last_cols; 80 var iter = uucode.grapheme.Iterator(uucode.utf8.Iterator).init(.init(content.bytes)); 81 82 var grapheme_start: usize = 0; 83 var prev_break: bool = true; 84 85 while (iter.next()) |result| { 86 if (prev_break and !result.is_break) { 87 // Start of a new grapheme 88 const cp_len: usize = std.unicode.utf8CodepointSequenceLength(result.cp) catch 1; 89 grapheme_start = iter.i - cp_len; 90 } 91 92 if (result.is_break) { 93 // End of a grapheme 94 const grapheme_end = iter.i; 95 const grapheme_len = grapheme_end - grapheme_start; 96 97 try self.grapheme.append(allocator, .{ 98 .len = @intCast(grapheme_len), 99 .offset = @intCast(self.content.items.len + grapheme_start), 100 }); 101 102 const cluster = content.bytes[grapheme_start..grapheme_end]; 103 if (std.mem.eql(u8, cluster, "\n")) { 104 self.cols = @max(self.cols, cols); 105 cols = 0; 106 } else { 107 // Calculate width using gwidth 108 const w = vaxis.gwidth.gwidth(cluster, .unicode); 109 cols +|= w; 110 } 111 112 grapheme_start = grapheme_end; 113 } 114 prev_break = result.is_break; 115 } 116 117 // Flush the last grapheme if we ended mid-cluster 118 if (!prev_break and grapheme_start < content.bytes.len) { 119 const grapheme_len = content.bytes.len - grapheme_start; 120 121 try self.grapheme.append(allocator, .{ 122 .len = @intCast(grapheme_len), 123 .offset = @intCast(self.content.items.len + grapheme_start), 124 }); 125 126 const cluster = content.bytes[grapheme_start..]; 127 if (!std.mem.eql(u8, cluster, "\n")) { 128 const w = vaxis.gwidth.gwidth(cluster, .unicode); 129 cols +|= w; 130 } 131 } 132 133 try self.content.appendSlice(allocator, content.bytes); 134 self.last_cols = cols; 135 self.cols = @max(self.cols, cols); 136 self.rows +|= std.mem.count(u8, content.bytes, "\n"); 137 } 138 139 /// Clears all styling data. 140 pub fn clearStyle(self: *@This(), allocator: std.mem.Allocator) void { 141 self.style_list.deinit(allocator); 142 self.style_map.deinit(allocator); 143 } 144 145 /// Update style for range of the buffer contents. 146 pub fn updateStyle(self: *@This(), allocator: std.mem.Allocator, style: Style) Error!void { 147 const style_index = blk: { 148 for (self.style_list.items, 0..) |s, i| { 149 if (std.meta.eql(s, style.style)) { 150 break :blk i; 151 } 152 } 153 try self.style_list.append(allocator, style.style); 154 break :blk self.style_list.items.len - 1; 155 }; 156 for (style.begin..style.end) |i| { 157 try self.style_map.put(allocator, i, style_index); 158 } 159 } 160 161 pub fn writer( 162 self: *@This(), 163 allocator: std.mem.Allocator, 164 ) BufferWriter.Writer { 165 return .{ 166 .context = .{ 167 .allocator = allocator, 168 .buffer = self, 169 }, 170 }; 171 } 172}; 173 174scroll_view: ScrollView = .{}, 175 176pub fn input(self: *@This(), key: vaxis.Key) void { 177 self.scroll_view.input(key); 178} 179 180pub fn draw(self: *@This(), win: vaxis.Window, buffer: Buffer) void { 181 self.scroll_view.draw(win, .{ .cols = buffer.cols, .rows = buffer.rows }); 182 const Pos = struct { x: usize = 0, y: usize = 0 }; 183 var pos: Pos = .{}; 184 var byte_index: usize = 0; 185 const bounds = self.scroll_view.bounds(win); 186 for (buffer.grapheme.items(.len), buffer.grapheme.items(.offset), 0..) |g_len, g_offset, index| { 187 if (bounds.above(pos.y)) { 188 break; 189 } 190 191 const cluster = buffer.content.items[g_offset..][0..g_len]; 192 defer byte_index += cluster.len; 193 194 if (std.mem.eql(u8, cluster, "\n")) { 195 if (index == buffer.grapheme.len - 1) { 196 break; 197 } 198 pos.y +|= 1; 199 pos.x = 0; 200 continue; 201 } else if (bounds.below(pos.y)) { 202 continue; 203 } 204 205 const width = win.gwidth(cluster); 206 defer pos.x +|= width; 207 208 if (!bounds.colInside(pos.x)) { 209 continue; 210 } 211 212 const style: vaxis.Style = blk: { 213 if (buffer.style_map.get(byte_index)) |style_index| { 214 break :blk buffer.style_list.items[style_index]; 215 } 216 break :blk .{}; 217 }; 218 219 self.scroll_view.writeCell(win, pos.x, pos.y, .{ 220 .char = .{ .grapheme = cluster, .width = @intCast(width) }, 221 .style = style, 222 }); 223 } 224}