this repo has no description
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}