this repo has no description
1const std = @import("std");
2const assert = std.debug.assert;
3const Style = @import("Cell.zig").Style;
4const Cell = @import("Cell.zig");
5const MouseShape = @import("Mouse.zig").Shape;
6const CursorShape = Cell.CursorShape;
7
8const log = std.log.scoped(.vaxis);
9
10const InternalScreen = @This();
11
12pub const InternalCell = struct {
13 char: std.ArrayList(u8) = .empty,
14 style: Style = .{},
15 uri: std.ArrayList(u8) = .empty,
16 uri_id: std.ArrayList(u8) = .empty,
17 // if we got skipped because of a wide character
18 skipped: bool = false,
19 default: bool = true,
20
21 // If we should skip rendering *this* round due to being printed over previously (from a scaled
22 // cell, for example)
23 skip: bool = false,
24
25 scale: Cell.Scale = .{},
26
27 pub fn eql(self: InternalCell, cell: Cell) bool {
28
29 // fastpath when both cells are default
30 if (self.default and cell.default) return true;
31
32 return std.mem.eql(u8, self.char.items, cell.char.grapheme) and
33 Style.eql(self.style, cell.style) and
34 std.mem.eql(u8, self.uri.items, cell.link.uri) and
35 std.mem.eql(u8, self.uri_id.items, cell.link.params);
36 }
37};
38
39arena: *std.heap.ArenaAllocator,
40width: u16 = 0,
41height: u16 = 0,
42
43buf: []InternalCell,
44
45cursor_vis: bool = false,
46cursor_shape: CursorShape = .default,
47
48mouse_shape: MouseShape = .default,
49
50/// sets each cell to the default cell
51pub fn init(gpa: std.mem.Allocator, w: u16, h: u16) !InternalScreen {
52 const arena = try gpa.create(std.heap.ArenaAllocator);
53 arena.* = .init(gpa);
54 var screen = InternalScreen{
55 .arena = arena,
56 .buf = try arena.allocator().alloc(InternalCell, @as(usize, @intCast(w)) * h),
57 };
58 for (screen.buf, 0..) |_, i| {
59 screen.buf[i] = .{
60 .char = try .initCapacity(arena.allocator(), 1),
61 .uri = .empty,
62 .uri_id = .empty,
63 };
64 screen.buf[i].char.appendAssumeCapacity(' ');
65 }
66 screen.width = w;
67 screen.height = h;
68 return screen;
69}
70
71pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void {
72 self.arena.deinit();
73 alloc.destroy(self.arena);
74 self.* = undefined;
75}
76
77/// writes a cell to a location. 0 indexed
78pub fn writeCell(
79 self: *InternalScreen,
80 col: u16,
81 row: u16,
82 cell: Cell,
83) void {
84 if (self.width <= col) {
85 // column out of bounds
86 return;
87 }
88 if (self.height <= row) {
89 // height out of bounds
90 return;
91 }
92 const i = (@as(usize, @intCast(row)) * self.width) + col;
93 assert(i < self.buf.len);
94 self.buf[i].char.clearRetainingCapacity();
95 self.buf[i].char.appendSlice(self.arena.allocator(), cell.char.grapheme) catch |err| {
96 log.warn("couldn't write grapheme: {t}", .{err});
97 };
98 self.buf[i].uri.clearRetainingCapacity();
99 self.buf[i].uri.appendSlice(self.arena.allocator(), cell.link.uri) catch |err| {
100 log.warn("couldn't write uri: {t}", .{err});
101 };
102 self.buf[i].uri_id.clearRetainingCapacity();
103 self.buf[i].uri_id.appendSlice(self.arena.allocator(), cell.link.params) catch |err| {
104 log.warn("couldn't write uri_id: {t}", .{err});
105 };
106 self.buf[i].style = cell.style;
107 self.buf[i].default = cell.default;
108}
109
110pub fn readCell(self: *InternalScreen, col: u16, row: u16) ?Cell {
111 if (self.width <= col) {
112 // column out of bounds
113 return null;
114 }
115 if (self.height <= row) {
116 // height out of bounds
117 return null;
118 }
119 const i = (row * self.width) + col;
120 assert(i < self.buf.len);
121 const cell = self.buf[i];
122 return .{
123 .char = .{ .grapheme = cell.char.items },
124 .style = cell.style,
125 .link = .{
126 .uri = cell.uri.items,
127 .params = cell.uri_id.items,
128 },
129 .default = cell.default,
130 };
131}
132
133test "InternalScreen: out-of-bounds read/write are ignored" {
134 var screen = try InternalScreen.init(std.testing.allocator, 2, 2);
135 defer screen.deinit(std.testing.allocator);
136
137 const sentinel: Cell = .{ .char = .{ .grapheme = "A", .width = 1 } };
138 screen.writeCell(0, 1, sentinel);
139
140 const oob_cell: Cell = .{ .char = .{ .grapheme = "X", .width = 1 } };
141 screen.writeCell(2, 0, oob_cell);
142 const read_back = screen.readCell(0, 1) orelse return error.TestUnexpectedResult;
143 try std.testing.expect(std.mem.eql(u8, read_back.char.grapheme, "A"));
144 try std.testing.expect(screen.readCell(2, 0) == null);
145}
146
147test {
148 std.testing.refAllDecls(@This());
149}