const std = @import("std"); const vaxis = @import("../main.zig"); const grapheme = @import("grapheme"); const DisplayWidth = @import("DisplayWidth"); const ScrollView = vaxis.widgets.ScrollView; pub const BufferWriter = struct { pub const Error = error{OutOfMemory}; pub const Writer = std.io.GenericWriter(@This(), Error, write); allocator: std.mem.Allocator, buffer: *Buffer, gd: *const grapheme.GraphemeData, wd: *const DisplayWidth.DisplayWidthData, pub fn write(self: @This(), bytes: []const u8) Error!usize { try self.buffer.append(self.allocator, .{ .bytes = bytes, .gd = self.gd, .wd = self.wd, }); return bytes.len; } pub fn writer(self: @This()) Writer { return .{ .context = self }; } }; pub const Buffer = struct { const StyleList = std.ArrayListUnmanaged(vaxis.Style); const StyleMap = std.HashMapUnmanaged(usize, usize, std.hash_map.AutoContext(usize), std.hash_map.default_max_load_percentage); pub const Content = struct { bytes: []const u8, gd: *const grapheme.GraphemeData, wd: *const DisplayWidth.DisplayWidthData, }; pub const Style = struct { begin: usize, end: usize, style: vaxis.Style, }; pub const Error = error{OutOfMemory}; grapheme: std.MultiArrayList(grapheme.Grapheme) = .{}, content: std.ArrayListUnmanaged(u8) = .{}, style_list: StyleList = .{}, style_map: StyleMap = .{}, rows: usize = 0, cols: usize = 0, // used when appending to a buffer last_cols: usize = 0, pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { self.style_map.deinit(allocator); self.style_list.deinit(allocator); self.grapheme.deinit(allocator); self.content.deinit(allocator); self.* = undefined; } /// Clears all buffer data. pub fn clear(self: *@This(), allocator: std.mem.Allocator) void { self.deinit(allocator); self.* = .{}; } /// Replaces contents of the buffer, all previous buffer data is lost. pub fn update(self: *@This(), allocator: std.mem.Allocator, content: Content) Error!void { self.clear(allocator); errdefer self.clear(allocator); try self.append(allocator, content); } /// Appends content to the buffer. pub fn append(self: *@This(), allocator: std.mem.Allocator, content: Content) Error!void { var cols: usize = self.last_cols; var iter = grapheme.Iterator.init(content.bytes, content.gd); const dw: DisplayWidth = .{ .data = content.wd }; while (iter.next()) |g| { try self.grapheme.append(allocator, .{ .len = g.len, .offset = @as(u32, @intCast(self.content.items.len)) + g.offset, }); const cluster = g.bytes(content.bytes); if (std.mem.eql(u8, cluster, "\n")) { self.cols = @max(self.cols, cols); cols = 0; continue; } cols +|= dw.strWidth(cluster); } try self.content.appendSlice(allocator, content.bytes); self.last_cols = cols; self.cols = @max(self.cols, cols); self.rows +|= std.mem.count(u8, content.bytes, "\n"); } /// Clears all styling data. pub fn clearStyle(self: *@This(), allocator: std.mem.Allocator) void { self.style_list.deinit(allocator); self.style_map.deinit(allocator); } /// Update style for range of the buffer contents. pub fn updateStyle(self: *@This(), allocator: std.mem.Allocator, style: Style) Error!void { const style_index = blk: { for (self.style_list.items, 0..) |s, i| { if (std.meta.eql(s, style.style)) { break :blk i; } } try self.style_list.append(allocator, style.style); break :blk self.style_list.items.len - 1; }; for (style.begin..style.end) |i| { try self.style_map.put(allocator, i, style_index); } } pub fn writer( self: *@This(), allocator: std.mem.Allocator, gd: *const grapheme.GraphemeData, wd: *const DisplayWidth.DisplayWidthData, ) BufferWriter.Writer { return .{ .context = .{ .allocator = allocator, .buffer = self, .gd = gd, .wd = wd, }, }; } }; scroll_view: ScrollView = .{}, pub fn input(self: *@This(), key: vaxis.Key) void { self.scroll_view.input(key); } pub fn draw(self: *@This(), win: vaxis.Window, buffer: Buffer) void { self.scroll_view.draw(win, .{ .cols = buffer.cols, .rows = buffer.rows }); const Pos = struct { x: usize = 0, y: usize = 0 }; var pos: Pos = .{}; var byte_index: usize = 0; const bounds = self.scroll_view.bounds(win); for (buffer.grapheme.items(.len), buffer.grapheme.items(.offset), 0..) |g_len, g_offset, index| { if (bounds.above(pos.y)) { break; } const cluster = buffer.content.items[g_offset..][0..g_len]; defer byte_index += cluster.len; if (std.mem.eql(u8, cluster, "\n")) { if (index == buffer.grapheme.len - 1) { break; } pos.y +|= 1; pos.x = 0; continue; } else if (bounds.below(pos.y)) { continue; } const width = win.gwidth(cluster); defer pos.x +|= width; if (!bounds.colInside(pos.x)) { continue; } const style: vaxis.Style = blk: { if (buffer.style_map.get(byte_index)) |style_index| { break :blk buffer.style_list.items[style_index]; } break :blk .{}; }; self.scroll_view.writeCell(win, pos.x, pos.y, .{ .char = .{ .grapheme = cluster, .width = width }, .style = style, }); } }