+62
bench/bench.zig
+62
bench/bench.zig
···
1
+
const std = @import("std");
2
+
const vaxis = @import("vaxis");
3
+
4
+
fn parseIterations(allocator: std.mem.Allocator) !usize {
5
+
var args = try std.process.argsWithAllocator(allocator);
6
+
defer args.deinit();
7
+
_ = args.next();
8
+
if (args.next()) |val| {
9
+
return std.fmt.parseUnsigned(usize, val, 10);
10
+
}
11
+
return 200;
12
+
}
13
+
14
+
fn printResults(writer: anytype, label: []const u8, iterations: usize, elapsed_ns: u64, total_bytes: usize) !void {
15
+
const ns_per_frame = elapsed_ns / @as(u64, @intCast(iterations));
16
+
const bytes_per_frame = total_bytes / iterations;
17
+
try writer.print(
18
+
"{s}: frames={d} total_ns={d} ns/frame={d} bytes={d} bytes/frame={d}\n",
19
+
.{ label, iterations, elapsed_ns, ns_per_frame, total_bytes, bytes_per_frame },
20
+
);
21
+
}
22
+
23
+
pub fn main() !void {
24
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
25
+
defer _ = gpa.deinit();
26
+
const allocator = gpa.allocator();
27
+
28
+
const iterations = try parseIterations(allocator);
29
+
30
+
var vx = try vaxis.init(allocator, .{});
31
+
var init_writer = std.io.Writer.Allocating.init(allocator);
32
+
defer init_writer.deinit();
33
+
defer vx.deinit(allocator, &init_writer.writer);
34
+
35
+
const winsize = vaxis.Winsize{ .rows = 24, .cols = 80, .x_pixel = 0, .y_pixel = 0 };
36
+
try vx.resize(allocator, &init_writer.writer, winsize);
37
+
38
+
const stdout = std.fs.File.stdout().deprecatedWriter();
39
+
40
+
var idle_writer = std.io.Writer.Allocating.init(allocator);
41
+
defer idle_writer.deinit();
42
+
var timer = try std.time.Timer.start();
43
+
var i: usize = 0;
44
+
while (i < iterations) : (i += 1) {
45
+
try vx.render(&idle_writer.writer);
46
+
}
47
+
const idle_ns = timer.read();
48
+
const idle_bytes: usize = idle_writer.writer.end;
49
+
try printResults(stdout, "idle", iterations, idle_ns, idle_bytes);
50
+
51
+
var dirty_writer = std.io.Writer.Allocating.init(allocator);
52
+
defer dirty_writer.deinit();
53
+
timer.reset();
54
+
i = 0;
55
+
while (i < iterations) : (i += 1) {
56
+
vx.queueRefresh();
57
+
try vx.render(&dirty_writer.writer);
58
+
}
59
+
const dirty_ns = timer.read();
60
+
const dirty_bytes: usize = dirty_writer.writer.end;
61
+
try printResults(stdout, "dirty", iterations, dirty_ns, dirty_bytes);
62
+
}
+21
build.zig
+21
build.zig
···
41
41
split_view,
42
42
table,
43
43
text_input,
44
+
text_view,
45
+
list_view,
44
46
vaxis,
45
47
view,
46
48
vt,
···
63
65
64
66
const example_run = b.addRunArtifact(example);
65
67
example_step.dependOn(&example_run.step);
68
+
69
+
// Benchmarks
70
+
const bench_step = b.step("bench", "Run benchmarks");
71
+
const bench = b.addExecutable(.{
72
+
.name = "bench",
73
+
.root_module = b.createModule(.{
74
+
.root_source_file = b.path("bench/bench.zig"),
75
+
.target = target,
76
+
.optimize = optimize,
77
+
.imports = &.{
78
+
.{ .name = "vaxis", .module = vaxis_mod },
79
+
},
80
+
}),
81
+
});
82
+
const bench_run = b.addRunArtifact(bench);
83
+
if (b.args) |args| {
84
+
bench_run.addArgs(args);
85
+
}
86
+
bench_step.dependOn(&bench_run.step);
66
87
67
88
// Tests
68
89
const tests_step = b.step("test", "Run tests");
+2
-3
build.zig.zon
+2
-3
build.zig.zon
···
5
5
.minimum_zig_version = "0.15.1",
6
6
.dependencies = .{
7
7
.zigimg = .{
8
-
// TODO .url = "git+https://github.com/zigimg/zigimg",
9
-
.url = "https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz",
10
-
.hash = "zigimg-0.1.0-8_eo2vHnEwCIVW34Q14Ec-xUlzIoVg86-7FU2ypPtxms",
8
+
.url = "git+https://github.com/zigimg/zigimg#eab2522c023b9259db8b13f2f90d609b7437e5f6",
9
+
.hash = "zigimg-0.1.0-8_eo2vUZFgAAtN1c6dAO5DdqL0d4cEWHtn6iR5ucZJti",
11
10
},
12
11
.uucode = .{
13
12
.url = "git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732",
+1
-1
examples/image.zig
+1
-1
examples/image.zig
···
36
36
37
37
var read_buffer: [1024 * 1024]u8 = undefined; // 1MB buffer
38
38
var img1 = try vaxis.zigimg.Image.fromFilePath(alloc, "examples/zig.png", &read_buffer);
39
-
defer img1.deinit();
39
+
defer img1.deinit(alloc);
40
40
41
41
const imgs = [_]vaxis.Image{
42
42
try vx.transmitImage(alloc, tty.writer(), &img1, .rgba),
+99
examples/list_view.zig
+99
examples/list_view.zig
···
1
+
const std = @import("std");
2
+
const vaxis = @import("vaxis");
3
+
const vxfw = vaxis.vxfw;
4
+
5
+
const Text = vxfw.Text;
6
+
const ListView = vxfw.ListView;
7
+
const Widget = vxfw.Widget;
8
+
9
+
const Model = struct {
10
+
list_view: ListView,
11
+
12
+
pub fn widget(self: *Model) Widget {
13
+
return .{
14
+
.userdata = self,
15
+
.eventHandler = Model.typeErasedEventHandler,
16
+
.drawFn = Model.typeErasedDrawFn,
17
+
};
18
+
}
19
+
20
+
pub fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void {
21
+
const self: *Model = @ptrCast(@alignCast(ptr));
22
+
try ctx.requestFocus(self.list_view.widget());
23
+
switch (event) {
24
+
.key_press => |key| {
25
+
if (key.matches('q', .{}) or key.matchExact('c', .{ .ctrl = true })) {
26
+
ctx.quit = true;
27
+
return;
28
+
}
29
+
},
30
+
else => {},
31
+
}
32
+
}
33
+
34
+
fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface {
35
+
const self: *Model = @ptrCast(@alignCast(ptr));
36
+
const max = ctx.max.size();
37
+
38
+
const list_view: vxfw.SubSurface = .{
39
+
.origin = .{ .row = 1, .col = 1 },
40
+
.surface = try self.list_view.draw(ctx),
41
+
};
42
+
43
+
const children = try ctx.arena.alloc(vxfw.SubSurface, 1);
44
+
children[0] = list_view;
45
+
46
+
return .{
47
+
.size = max,
48
+
.widget = self.widget(),
49
+
.buffer = &.{},
50
+
.children = children,
51
+
};
52
+
}
53
+
};
54
+
55
+
pub fn main() !void {
56
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
57
+
defer _ = gpa.deinit();
58
+
59
+
const allocator = gpa.allocator();
60
+
61
+
var app = try vxfw.App.init(allocator);
62
+
defer app.deinit();
63
+
64
+
const model = try allocator.create(Model);
65
+
defer allocator.destroy(model);
66
+
67
+
const n = 80;
68
+
var texts = try std.ArrayList(Widget).initCapacity(allocator, n);
69
+
70
+
var allocs = try std.ArrayList(*Text).initCapacity(allocator, n);
71
+
defer {
72
+
for (allocs.items) |tw| {
73
+
allocator.free(tw.text);
74
+
allocator.destroy(tw);
75
+
}
76
+
allocs.deinit(allocator);
77
+
texts.deinit(allocator);
78
+
}
79
+
80
+
for (0..n) |i| {
81
+
const t = std.fmt.allocPrint(allocator, "List Item {d}", .{i}) catch "placeholder";
82
+
const tw = try allocator.create(Text);
83
+
tw.* = .{ .text = t };
84
+
_ = try allocs.append(allocator, tw);
85
+
_ = try texts.append(allocator, tw.widget());
86
+
}
87
+
88
+
model.* = .{
89
+
.list_view = .{
90
+
.wheel_scroll = 3,
91
+
.scroll = .{
92
+
.wants_cursor = true,
93
+
},
94
+
.children = .{ .slice = texts.items },
95
+
},
96
+
};
97
+
98
+
try app.run(model.widget(), .{});
99
+
}
+66
examples/text_view.zig
+66
examples/text_view.zig
···
1
+
const std = @import("std");
2
+
const log = std.log.scoped(.main);
3
+
const vaxis = @import("vaxis");
4
+
5
+
const TextView = vaxis.widgets.TextView;
6
+
7
+
const Event = union(enum) {
8
+
key_press: vaxis.Key,
9
+
winsize: vaxis.Winsize,
10
+
};
11
+
12
+
pub fn main() !void {
13
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
14
+
15
+
defer {
16
+
const deinit_status = gpa.deinit();
17
+
if (deinit_status == .leak) {
18
+
log.err("memory leak", .{});
19
+
}
20
+
}
21
+
22
+
const alloc = gpa.allocator();
23
+
var buffer: [1024]u8 = undefined;
24
+
var tty = try vaxis.Tty.init(&buffer);
25
+
defer tty.deinit();
26
+
var vx = try vaxis.init(alloc, .{});
27
+
defer vx.deinit(alloc, tty.writer());
28
+
var loop: vaxis.Loop(Event) = .{
29
+
.vaxis = &vx,
30
+
.tty = &tty,
31
+
};
32
+
try loop.init();
33
+
try loop.start();
34
+
defer loop.stop();
35
+
try vx.enterAltScreen(tty.writer());
36
+
try vx.queryTerminal(tty.writer(), 20 * std.time.ns_per_s);
37
+
var text_view = TextView{};
38
+
var text_view_buffer = TextView.Buffer{};
39
+
defer text_view_buffer.deinit(alloc);
40
+
try text_view_buffer.append(alloc, .{ .bytes = "Press Enter to add a line, Up/Down to scroll, 'c' to close." });
41
+
42
+
var counter: i32 = 0;
43
+
var lineBuf: [128]u8 = undefined;
44
+
45
+
while (true) {
46
+
const event = loop.nextEvent();
47
+
switch (event) {
48
+
.key_press => |key| {
49
+
// Close demo
50
+
if (key.matches('c', .{})) break;
51
+
if (key.matches(vaxis.Key.enter, .{})) {
52
+
counter += 1;
53
+
const new_content = try std.fmt.bufPrint(&lineBuf, "\nLine {d}", .{counter});
54
+
try text_view_buffer.append(alloc, .{ .bytes = new_content });
55
+
}
56
+
text_view.input(key);
57
+
},
58
+
.winsize => |ws| try vx.resize(alloc, tty.writer(), ws),
59
+
}
60
+
const win = vx.window();
61
+
win.clear();
62
+
text_view.draw(win, text_view_buffer);
63
+
try vx.render(tty.writer());
64
+
try tty.writer.flush();
65
+
}
66
+
}
+18
-4
src/InternalScreen.zig
+18
-4
src/InternalScreen.zig
···
83
83
row: u16,
84
84
cell: Cell,
85
85
) void {
86
-
if (self.width < col) {
86
+
if (self.width <= col) {
87
87
// column out of bounds
88
88
return;
89
89
}
90
-
if (self.height < row) {
90
+
if (self.height <= row) {
91
91
// height out of bounds
92
92
return;
93
93
}
···
110
110
}
111
111
112
112
pub fn readCell(self: *InternalScreen, col: u16, row: u16) ?Cell {
113
-
if (self.width < col) {
113
+
if (self.width <= col) {
114
114
// column out of bounds
115
115
return null;
116
116
}
117
-
if (self.height < row) {
117
+
if (self.height <= row) {
118
118
// height out of bounds
119
119
return null;
120
120
}
···
131
131
.default = cell.default,
132
132
};
133
133
}
134
+
135
+
test "InternalScreen: out-of-bounds read/write are ignored" {
136
+
var screen = try InternalScreen.init(std.testing.allocator, 2, 2);
137
+
defer screen.deinit(std.testing.allocator);
138
+
139
+
const sentinel: Cell = .{ .char = .{ .grapheme = "A", .width = 1 } };
140
+
screen.writeCell(0, 1, sentinel);
141
+
142
+
const oob_cell: Cell = .{ .char = .{ .grapheme = "X", .width = 1 } };
143
+
screen.writeCell(2, 0, oob_cell);
144
+
const read_back = screen.readCell(0, 1) orelse return error.TestUnexpectedResult;
145
+
try std.testing.expect(std.mem.eql(u8, read_back.char.grapheme, "A"));
146
+
try std.testing.expect(screen.readCell(2, 0) == null);
147
+
}
+78
-35
src/Vaxis.zig
+78
-35
src/Vaxis.zig
···
360
360
assert(self.screen.buf.len == @as(usize, @intCast(self.screen.width)) * self.screen.height); // correct size
361
361
assert(self.screen.buf.len == self.screen_last.buf.len); // same size
362
362
363
-
// Set up sync before we write anything
364
-
// TODO: optimize sync so we only sync _when we have changes_. This
365
-
// requires a smarter buffered writer, we'll probably have to write
366
-
// our own
367
-
try tty.writeAll(ctlseqs.sync_set);
368
-
errdefer tty.writeAll(ctlseqs.sync_reset) catch {};
363
+
var started: bool = false;
364
+
var sync_active: bool = false;
365
+
errdefer if (sync_active) tty.writeAll(ctlseqs.sync_reset) catch {};
369
366
370
-
// Send the cursor to 0,0
371
-
// TODO: this needs to move after we optimize writes. We only do
372
-
// this if we have an update to make. We also need to hide cursor
373
-
// and then reshow it if needed
374
-
try tty.writeAll(ctlseqs.hide_cursor);
375
-
if (self.state.alt_screen)
376
-
try tty.writeAll(ctlseqs.home)
377
-
else {
378
-
try tty.writeByte('\r');
379
-
for (0..self.state.cursor.row) |_| {
380
-
try tty.writeAll(ctlseqs.ri);
381
-
}
382
-
}
383
-
try tty.writeAll(ctlseqs.sgr_reset);
367
+
const cursor_vis_changed = self.screen.cursor_vis != self.screen_last.cursor_vis;
368
+
const cursor_shape_changed = self.screen.cursor_shape != self.screen_last.cursor_shape;
369
+
const mouse_shape_changed = self.screen.mouse_shape != self.screen_last.mouse_shape;
370
+
const cursor_pos_changed = self.screen.cursor_vis and
371
+
(self.screen.cursor_row != self.state.cursor.row or
372
+
self.screen.cursor_col != self.state.cursor.col);
373
+
const needs_render = self.refresh or cursor_vis_changed or cursor_shape_changed or mouse_shape_changed or cursor_pos_changed;
384
374
385
375
// initialize some variables
386
376
var reposition: bool = false;
···
388
378
var col: u16 = 0;
389
379
var cursor: Style = .{};
390
380
var link: Hyperlink = .{};
391
-
var cursor_pos: struct {
381
+
const CursorPos = struct {
392
382
row: u16 = 0,
393
383
col: u16 = 0,
394
-
} = .{};
384
+
};
385
+
var cursor_pos: CursorPos = .{};
395
386
396
-
// Clear all images
397
-
if (self.caps.kitty_graphics)
398
-
try tty.writeAll(ctlseqs.kitty_graphics_clear);
387
+
const startRender = struct {
388
+
fn run(
389
+
vx: *Vaxis,
390
+
io: *IoWriter,
391
+
cursor_pos_ptr: *CursorPos,
392
+
reposition_ptr: *bool,
393
+
started_ptr: *bool,
394
+
sync_active_ptr: *bool,
395
+
) !void {
396
+
if (started_ptr.*) return;
397
+
started_ptr.* = true;
398
+
sync_active_ptr.* = true;
399
+
// Set up sync before we write anything
400
+
try io.writeAll(ctlseqs.sync_set);
401
+
// Send the cursor to 0,0
402
+
try io.writeAll(ctlseqs.hide_cursor);
403
+
if (vx.state.alt_screen)
404
+
try io.writeAll(ctlseqs.home)
405
+
else {
406
+
try io.writeByte('\r');
407
+
for (0..vx.state.cursor.row) |_| {
408
+
try io.writeAll(ctlseqs.ri);
409
+
}
410
+
}
411
+
try io.writeAll(ctlseqs.sgr_reset);
412
+
cursor_pos_ptr.* = .{};
413
+
reposition_ptr.* = true;
414
+
// Clear all images
415
+
if (vx.caps.kitty_graphics)
416
+
try io.writeAll(ctlseqs.kitty_graphics_clear);
417
+
}
418
+
};
399
419
400
420
// Reset skip flag on all last_screen cells
401
421
for (self.screen_last.buf) |*last_cell| {
402
422
last_cell.skip = false;
423
+
}
424
+
425
+
if (needs_render) {
426
+
try startRender.run(self, tty, &cursor_pos, &reposition, &started, &sync_active);
403
427
}
404
428
405
429
var i: usize = 0;
···
446
470
try tty.writeAll(ctlseqs.osc8_clear);
447
471
}
448
472
continue;
473
+
}
474
+
if (!started) {
475
+
try startRender.run(self, tty, &cursor_pos, &reposition, &started, &sync_active);
449
476
}
450
477
self.screen_last.buf[i].skipped = false;
451
478
defer {
···
730
757
cursor_pos.col = col + w;
731
758
cursor_pos.row = row;
732
759
}
760
+
if (!started) return;
733
761
if (self.screen.cursor_vis) {
734
762
if (self.state.alt_screen) {
735
763
try tty.print(
···
761
789
self.state.cursor.row = cursor_pos.row;
762
790
self.state.cursor.col = cursor_pos.col;
763
791
}
792
+
self.screen_last.cursor_vis = self.screen.cursor_vis;
764
793
if (self.screen.mouse_shape != self.screen_last.mouse_shape) {
765
794
try tty.print(
766
795
ctlseqs.osc22_mouse_shape,
···
851
880
const ypos = mouse.row;
852
881
const xextra = self.screen.width_pix % self.screen.width;
853
882
const yextra = self.screen.height_pix % self.screen.height;
854
-
const xcell = (self.screen.width_pix - xextra) / self.screen.width;
855
-
const ycell = (self.screen.height_pix - yextra) / self.screen.height;
883
+
const xcell: i16 = @intCast((self.screen.width_pix - xextra) / self.screen.width);
884
+
const ycell: i16 = @intCast((self.screen.height_pix - yextra) / self.screen.height);
856
885
if (xcell == 0 or ycell == 0) return mouse;
857
-
result.col = xpos / xcell;
858
-
result.row = ypos / ycell;
859
-
result.xoffset = xpos % xcell;
860
-
result.yoffset = ypos % ycell;
886
+
result.col = @divFloor(xpos, xcell);
887
+
result.row = @divFloor(ypos, ycell);
888
+
result.xoffset = @intCast(@mod(xpos, xcell));
889
+
result.yoffset = @intCast(@mod(ypos, ycell));
861
890
}
862
891
return result;
863
892
}
···
995
1024
const buf = switch (format) {
996
1025
.png => png: {
997
1026
const png_buf = try arena.allocator().alloc(u8, img.imageByteSize());
998
-
const png = try img.writeToMemory(png_buf, .{ .png = .{} });
1027
+
const png = try img.writeToMemory(arena.allocator(), png_buf, .{ .png = .{} });
999
1028
break :png png;
1000
1029
},
1001
1030
.rgb => rgb: {
1002
-
try img.convert(.rgb24);
1031
+
try img.convert(arena.allocator(), .rgb24);
1003
1032
break :rgb img.rawBytes();
1004
1033
},
1005
1034
.rgba => rgba: {
1006
-
try img.convert(.rgba32);
1035
+
try img.convert(arena.allocator(), .rgba32);
1007
1036
break :rgba img.rawBytes();
1008
1037
},
1009
1038
};
···
1027
1056
.path => |path| try zigimg.Image.fromFilePath(alloc, path, &read_buffer),
1028
1057
.mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes),
1029
1058
};
1030
-
defer img.deinit();
1059
+
defer img.deinit(alloc);
1031
1060
return self.transmitImage(alloc, tty, &img, .png);
1032
1061
}
1033
1062
···
1409
1438
try tty.print(ctlseqs.osc7, .{uri.fmt(.{ .scheme = true, .authority = true, .path = true })});
1410
1439
try tty.flush();
1411
1440
}
1441
+
1442
+
test "render: no output when no changes" {
1443
+
var vx = try Vaxis.init(std.testing.allocator, .{});
1444
+
var deinit_writer = std.io.Writer.Allocating.init(std.testing.allocator);
1445
+
defer deinit_writer.deinit();
1446
+
defer vx.deinit(std.testing.allocator, &deinit_writer.writer);
1447
+
1448
+
var render_writer = std.io.Writer.Allocating.init(std.testing.allocator);
1449
+
defer render_writer.deinit();
1450
+
try vx.render(&render_writer.writer);
1451
+
const output = try render_writer.toOwnedSlice();
1452
+
defer std.testing.allocator.free(output);
1453
+
try std.testing.expectEqual(@as(usize, 0), output.len);
1454
+
}
+26
-28
src/queue.zig
+26
-28
src/queue.zig
···
30
30
self.not_empty.wait(&self.mutex);
31
31
}
32
32
std.debug.assert(!self.isEmptyLH());
33
-
if (self.isFullLH()) {
34
-
// If we are full, wake up a push that might be
35
-
// waiting here.
36
-
self.not_full.signal();
37
-
}
38
-
39
-
return self.popLH();
33
+
return self.popAndSignalLH();
40
34
}
41
35
42
36
/// Push an item into the queue. Blocks until an item has been
···
48
42
self.not_full.wait(&self.mutex);
49
43
}
50
44
std.debug.assert(!self.isFullLH());
51
-
const was_empty = self.isEmptyLH();
52
-
53
-
self.buf[self.mask(self.write_index)] = item;
54
-
self.write_index = self.mask2(self.write_index + 1);
55
-
56
-
// If we were empty, wake up a pop if it was waiting.
57
-
if (was_empty) {
58
-
self.not_empty.signal();
59
-
}
45
+
self.pushAndSignalLH(item);
60
46
}
61
47
62
48
/// Push an item into the queue. Returns true when the item
···
64
50
/// was full.
65
51
pub fn tryPush(self: *Self, item: T) bool {
66
52
self.mutex.lock();
67
-
if (self.isFullLH()) {
68
-
self.mutex.unlock();
69
-
return false;
70
-
}
71
-
self.mutex.unlock();
72
-
self.push(item);
53
+
defer self.mutex.unlock();
54
+
if (self.isFullLH()) return false;
55
+
self.pushAndSignalLH(item);
73
56
return true;
74
57
}
75
58
···
77
60
/// available.
78
61
pub fn tryPop(self: *Self) ?T {
79
62
self.mutex.lock();
80
-
if (self.isEmptyLH()) {
81
-
self.mutex.unlock();
82
-
return null;
83
-
}
84
-
self.mutex.unlock();
85
-
return self.pop();
63
+
defer self.mutex.unlock();
64
+
if (self.isEmptyLH()) return null;
65
+
return self.popAndSignalLH();
86
66
}
87
67
88
68
/// Poll the queue. This call blocks until events are in the queue
···
148
128
/// Returns `index` modulo twice the length of the backing slice.
149
129
fn mask2(self: Self, index: usize) usize {
150
130
return index % (2 * self.buf.len);
131
+
}
132
+
133
+
fn pushAndSignalLH(self: *Self, item: T) void {
134
+
const was_empty = self.isEmptyLH();
135
+
self.buf[self.mask(self.write_index)] = item;
136
+
self.write_index = self.mask2(self.write_index + 1);
137
+
if (was_empty) {
138
+
self.not_empty.signal();
139
+
}
140
+
}
141
+
142
+
fn popAndSignalLH(self: *Self) T {
143
+
const was_full = self.isFullLH();
144
+
const result = self.popLH();
145
+
if (was_full) {
146
+
self.not_full.signal();
147
+
}
148
+
return result;
151
149
}
152
150
153
151
fn popLH(self: *Self) T {
+3
-2
src/tty.zig
+3
-2
src/tty.zig
···
453
453
0xc0 => '`',
454
454
0xdb => '[',
455
455
0xdc => '\\',
456
+
0xdf => '\\',
456
457
0xe2 => '\\',
457
458
0xdd => ']',
458
459
0xde => '\'',
···
575
576
};
576
577
577
578
const mouse: Mouse = .{
578
-
.col = @as(u16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index
579
-
.row = @as(u16, @bitCast(event.dwMousePosition.Y)), // Windows reports with 0 index
579
+
.col = @as(i16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index
580
+
.row = @as(i16, @bitCast(event.dwMousePosition.Y)), // Windows reports with 0 index
580
581
.mods = mods,
581
582
.type = event_type,
582
583
.button = btn,
+2
-1
src/widgets/TextView.zig
+2
-1
src/widgets/TextView.zig
···
85
85
while (iter.next()) |result| {
86
86
if (prev_break and !result.is_break) {
87
87
// Start of a new grapheme
88
-
grapheme_start = iter.i - std.unicode.utf8CodepointSequenceLength(result.cp) catch 1;
88
+
const cp_len: usize = std.unicode.utf8CodepointSequenceLength(result.cp) catch 1;
89
+
grapheme_start = iter.i - cp_len;
89
90
}
90
91
91
92
if (result.is_break) {