this repo has no description
13
fork

Configure Feed

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

at ea0c083a24251604d46c388e42f6cf8bb3fbcb2b 223 lines 7.1 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 grapheme_start = iter.i - std.unicode.utf8CodepointSequenceLength(result.cp) catch 1; 89 } 90 91 if (result.is_break) { 92 // End of a grapheme 93 const grapheme_end = iter.i; 94 const grapheme_len = grapheme_end - grapheme_start; 95 96 try self.grapheme.append(allocator, .{ 97 .len = @intCast(grapheme_len), 98 .offset = @intCast(self.content.items.len + grapheme_start), 99 }); 100 101 const cluster = content.bytes[grapheme_start..grapheme_end]; 102 if (std.mem.eql(u8, cluster, "\n")) { 103 self.cols = @max(self.cols, cols); 104 cols = 0; 105 } else { 106 // Calculate width using gwidth 107 const w = vaxis.gwidth.gwidth(cluster, .unicode); 108 cols +|= w; 109 } 110 111 grapheme_start = grapheme_end; 112 } 113 prev_break = result.is_break; 114 } 115 116 // Flush the last grapheme if we ended mid-cluster 117 if (!prev_break and grapheme_start < content.bytes.len) { 118 const grapheme_len = content.bytes.len - grapheme_start; 119 120 try self.grapheme.append(allocator, .{ 121 .len = @intCast(grapheme_len), 122 .offset = @intCast(self.content.items.len + grapheme_start), 123 }); 124 125 const cluster = content.bytes[grapheme_start..]; 126 if (!std.mem.eql(u8, cluster, "\n")) { 127 const w = vaxis.gwidth.gwidth(cluster, .unicode); 128 cols +|= w; 129 } 130 } 131 132 try self.content.appendSlice(allocator, content.bytes); 133 self.last_cols = cols; 134 self.cols = @max(self.cols, cols); 135 self.rows +|= std.mem.count(u8, content.bytes, "\n"); 136 } 137 138 /// Clears all styling data. 139 pub fn clearStyle(self: *@This(), allocator: std.mem.Allocator) void { 140 self.style_list.deinit(allocator); 141 self.style_map.deinit(allocator); 142 } 143 144 /// Update style for range of the buffer contents. 145 pub fn updateStyle(self: *@This(), allocator: std.mem.Allocator, style: Style) Error!void { 146 const style_index = blk: { 147 for (self.style_list.items, 0..) |s, i| { 148 if (std.meta.eql(s, style.style)) { 149 break :blk i; 150 } 151 } 152 try self.style_list.append(allocator, style.style); 153 break :blk self.style_list.items.len - 1; 154 }; 155 for (style.begin..style.end) |i| { 156 try self.style_map.put(allocator, i, style_index); 157 } 158 } 159 160 pub fn writer( 161 self: *@This(), 162 allocator: std.mem.Allocator, 163 ) BufferWriter.Writer { 164 return .{ 165 .context = .{ 166 .allocator = allocator, 167 .buffer = self, 168 }, 169 }; 170 } 171}; 172 173scroll_view: ScrollView = .{}, 174 175pub fn input(self: *@This(), key: vaxis.Key) void { 176 self.scroll_view.input(key); 177} 178 179pub fn draw(self: *@This(), win: vaxis.Window, buffer: Buffer) void { 180 self.scroll_view.draw(win, .{ .cols = buffer.cols, .rows = buffer.rows }); 181 const Pos = struct { x: usize = 0, y: usize = 0 }; 182 var pos: Pos = .{}; 183 var byte_index: usize = 0; 184 const bounds = self.scroll_view.bounds(win); 185 for (buffer.grapheme.items(.len), buffer.grapheme.items(.offset), 0..) |g_len, g_offset, index| { 186 if (bounds.above(pos.y)) { 187 break; 188 } 189 190 const cluster = buffer.content.items[g_offset..][0..g_len]; 191 defer byte_index += cluster.len; 192 193 if (std.mem.eql(u8, cluster, "\n")) { 194 if (index == buffer.grapheme.len - 1) { 195 break; 196 } 197 pos.y +|= 1; 198 pos.x = 0; 199 continue; 200 } else if (bounds.below(pos.y)) { 201 continue; 202 } 203 204 const width = win.gwidth(cluster); 205 defer pos.x +|= width; 206 207 if (!bounds.colInside(pos.x)) { 208 continue; 209 } 210 211 const style: vaxis.Style = blk: { 212 if (buffer.style_map.get(byte_index)) |style_index| { 213 break :blk buffer.style_list.items[style_index]; 214 } 215 break :blk .{}; 216 }; 217 218 self.scroll_view.writeCell(win, pos.x, pos.y, .{ 219 .char = .{ .grapheme = cluster, .width = @intCast(width) }, 220 .style = style, 221 }); 222 } 223}