this repo has no description
13
fork

Configure Feed

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

first attempt at 0.16 update

authored by

Jeffrey C. Ollie and committed by
Tim Culverhouse
2dd8fe89 ceb072b4

+238 -188
+2 -2
build.zig.zon
··· 5 5 .minimum_zig_version = "0.15.1", 6 6 .dependencies = .{ 7 7 .zigimg = .{ 8 - .url = "git+https://github.com/zigimg/zigimg#eab2522c023b9259db8b13f2f90d609b7437e5f6", 9 - .hash = "zigimg-0.1.0-8_eo2vUZFgAAtN1c6dAO5DdqL0d4cEWHtn6iR5ucZJti", 8 + .url = "git+https://github.com/GasInfinity-Forks/zigimg?ref=0.16.x#c474a50ce9ec79ae726ee3f86fe08d530ff91e78", 9 + .hash = "zigimg-0.1.0-8_eo2gCZFwA239CedNPHzAkZMxkUxQpLBq3UrataPX1x", 10 10 }, 11 11 .uucode = .{ 12 12 .url = "git+https://github.com/jcollie/uucode#0079a52812bc675eb5f031252391c3fc18f21973",
+9 -4
src/Loop.zig
··· 19 19 tty: *Tty, 20 20 vaxis: *Vaxis, 21 21 22 - queue: Queue(T, 512) = .{}, 22 + queue: Queue(T, 512) = undefined, 23 23 thread: ?std.Thread = null, 24 24 should_quit: bool = false, 25 25 26 26 /// Initialize the event loop. This is an intrusive init so that we have 27 27 /// a stable pointer to register signal callbacks with posix TTYs 28 - pub fn init(self: *Self) !void { 28 + pub fn init(self: *Self, io: std.Io) !void { 29 + self.queue = .init(io); 29 30 switch (builtin.os.tag) { 30 31 .windows => {}, 31 32 else => { ··· 379 380 } 380 381 381 382 test Loop { 383 + const io = std.testing.io; 384 + var env_map = try std.testing.environ.createMap(std.testing.allocator); 385 + defer env_map.deinit(); 386 + 382 387 const Event = union(enum) { 383 388 key_press: vaxis.Key, 384 389 winsize: vaxis.Winsize, ··· 389 394 var tty = try vaxis.Tty.init(&.{}); 390 395 defer tty.deinit(); 391 396 392 - var vx = try vaxis.init(std.testing.allocator, .{}); 397 + var vx = try vaxis.init(io, std.testing.allocator, &env_map, .{}); 393 398 defer vx.deinit(std.testing.allocator, tty.writer()); 394 399 395 400 var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; 396 - try loop.init(); 401 + try loop.init(io); 397 402 398 403 try loop.start(); 399 404 defer loop.stop();
+1 -1
src/Parser.zig
··· 118 118 var grapheme_len: usize = 0; 119 119 var cp_count: usize = 0; 120 120 121 - while (grapheme_iter.next()) |result| { 121 + while (grapheme_iter.nextCodePoint()) |result| { 122 122 cp_count += 1; 123 123 if (result.is_break) { 124 124 // Found the first grapheme boundary
+50 -43
src/Vaxis.zig
··· 3 3 const atomic = std.atomic; 4 4 const base64Encoder = std.base64.standard.Encoder; 5 5 const zigimg = @import("zigimg"); 6 - const IoWriter = std.io.Writer; 7 6 8 7 const Cell = @import("Cell.zig"); 9 8 const Image = @import("Image.zig"); ··· 49 48 /// clipboard 50 49 system_clipboard_allocator: ?std.mem.Allocator = null, 51 50 }; 51 + 52 + io: std.Io, 53 + env_map: *std.process.Environ.Map, 52 54 53 55 /// the screen we write to 54 56 screen: Screen, ··· 103 105 } = .{}, 104 106 105 107 /// Initialize Vaxis with runtime options 106 - pub fn init(alloc: std.mem.Allocator, opts: Options) !Vaxis { 108 + pub fn init(io: std.Io, alloc: std.mem.Allocator, env_map: *std.process.Environ.Map, opts: Options) !Vaxis { 107 109 return .{ 110 + .io = io, 111 + .env_map = env_map, 108 112 .opts = opts, 109 113 .screen = .{}, 110 114 .screen_last = try .init(alloc, 0, 0), ··· 115 119 /// passed, this will free resources associated with Vaxis. This is left as an 116 120 /// optional so applications can choose to not free resources when the 117 121 /// application will be exiting anyways 118 - pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator, tty: *IoWriter) void { 122 + pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator, tty: *std.Io.Writer) void { 119 123 self.resetState(tty) catch {}; 120 124 121 125 if (alloc) |a| { ··· 128 132 } 129 133 130 134 /// resets enabled features, sends cursor to home and clears below cursor 131 - pub fn resetState(self: *Vaxis, tty: *IoWriter) !void { 135 + pub fn resetState(self: *Vaxis, tty: *std.Io.Writer) !void { 132 136 // always show the cursor on state reset 133 137 tty.writeAll(ctlseqs.show_cursor) catch {}; 134 138 tty.writeAll(ctlseqs.sgr_reset) catch {}; ··· 190 194 pub fn resize( 191 195 self: *Vaxis, 192 196 alloc: std.mem.Allocator, 193 - tty: *IoWriter, 197 + tty: *std.Io.Writer, 194 198 winsize: Winsize, 195 199 ) !void { 196 200 log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows }); ··· 231 235 232 236 /// enter the alternate screen. The alternate screen will automatically 233 237 /// be exited if calling deinit while in the alt screen. 234 - pub fn enterAltScreen(self: *Vaxis, tty: *IoWriter) !void { 238 + pub fn enterAltScreen(self: *Vaxis, tty: *std.Io.Writer) !void { 235 239 try tty.writeAll(ctlseqs.smcup); 236 240 try tty.flush(); 237 241 self.state.alt_screen = true; 238 242 } 239 243 240 244 /// exit the alternate screen. Does not flush the writer. 241 - pub fn exitAltScreen(self: *Vaxis, tty: *IoWriter) !void { 245 + pub fn exitAltScreen(self: *Vaxis, tty: *std.Io.Writer) !void { 242 246 try tty.writeAll(ctlseqs.rmcup); 243 247 try tty.flush(); 244 248 self.state.alt_screen = false; ··· 250 254 /// 251 255 /// This call will block until Vaxis.query_futex is woken up, or the timeout. 252 256 /// Event loops can wake up this futex when cap_da1 is received 253 - pub fn queryTerminal(self: *Vaxis, tty: *IoWriter, timeout_ns: u64) !void { 257 + pub fn queryTerminal(self: *Vaxis, tty: *std.Io.Writer, timeout_ns: u64) !void { 254 258 try self.queryTerminalSend(tty); 255 259 // 1 second timeout 256 - std.Thread.Futex.timedWait(&self.query_futex, 0, timeout_ns) catch {}; 260 + try std.Io.futexWaitTimeout(self.io, atomic.Value(u32), &self.query_futex, .init(0), .{ .duration = .{ .clock = .real, .raw = .fromNanoseconds(timeout_ns) } }); 257 261 self.queries_done.store(true, .unordered); 258 262 try self.enableDetectedFeatures(tty); 259 263 } ··· 261 265 /// write queries to the terminal to determine capabilities. This function 262 266 /// is only for use with a custom main loop. Call Vaxis.queryTerminal() if 263 267 /// you are using Loop.run() 264 - pub fn queryTerminalSend(vx: *Vaxis, tty: *IoWriter) !void { 268 + pub fn queryTerminalSend(vx: *Vaxis, tty: *std.Io.Writer) !void { 265 269 vx.queries_done.store(false, .unordered); 266 270 267 271 // TODO: re-enable this ··· 312 316 /// Enable features detected by responses to queryTerminal. This function 313 317 /// is only for use with a custom main loop. Call Vaxis.queryTerminal() if 314 318 /// you are using Loop.run() 315 - pub fn enableDetectedFeatures(self: *Vaxis, tty: *IoWriter) !void { 319 + pub fn enableDetectedFeatures(self: *Vaxis, tty: *std.Io.Writer) !void { 316 320 switch (builtin.os.tag) { 317 321 .windows => { 318 322 // No feature detection on windows. We just hard enable some knowns for ConPTY ··· 320 324 }, 321 325 else => { 322 326 // Apply any environment variables 323 - if (std.posix.getenv("TERMUX_VERSION")) |_| 327 + if (self.env_map.get("TERMUX_VERSION")) |_| 324 328 self.sgr = .legacy; 325 - if (std.posix.getenv("VHS_RECORD")) |_| { 329 + if (self.env_map.get("VHS_RECORD")) |_| { 326 330 self.caps.unicode = .wcwidth; 327 331 self.caps.kitty_keyboard = false; 328 332 self.sgr = .legacy; 329 333 } 330 - if (std.posix.getenv("TERM_PROGRAM")) |prg| { 334 + if (self.env_map.get("TERM_PROGRAM")) |prg| { 331 335 if (std.mem.eql(u8, prg, "vscode")) 332 336 self.sgr = .legacy; 333 337 } 334 - if (std.posix.getenv("VAXIS_FORCE_LEGACY_SGR")) |_| 338 + if (self.env_map.get("VAXIS_FORCE_LEGACY_SGR")) |_| 335 339 self.sgr = .legacy; 336 - if (std.posix.getenv("VAXIS_FORCE_WCWIDTH")) |_| 340 + if (self.env_map.get("VAXIS_FORCE_WCWIDTH")) |_| 337 341 self.caps.unicode = .wcwidth; 338 - if (std.posix.getenv("VAXIS_FORCE_UNICODE")) |_| 342 + if (self.env_map.get("VAXIS_FORCE_UNICODE")) |_| 339 343 self.caps.unicode = .unicode; 340 344 341 345 // enable detected features ··· 358 362 } 359 363 360 364 /// draws the screen to the terminal 361 - pub fn render(self: *Vaxis, tty: *IoWriter) !void { 365 + pub fn render(self: *Vaxis, tty: *std.Io.Writer) !void { 362 366 defer self.refresh = false; 363 367 assert(self.screen.buf.len == @as(usize, @intCast(self.screen.width)) * self.screen.height); // correct size 364 368 assert(self.screen.buf.len == self.screen_last.buf.len); // same size ··· 397 401 const startRender = struct { 398 402 fn run( 399 403 vx: *Vaxis, 400 - io: *IoWriter, 404 + io: *std.Io.Writer, 401 405 cursor_pos_ptr: *CursorPos, 402 406 reposition_ptr: *bool, 403 407 started_ptr: *bool, ··· 828 832 try tty.flush(); 829 833 } 830 834 831 - fn enableKittyKeyboard(self: *Vaxis, tty: *IoWriter, flags: Key.KittyFlags) !void { 835 + fn enableKittyKeyboard(self: *Vaxis, tty: *std.Io.Writer, flags: Key.KittyFlags) !void { 832 836 const flag_int: u5 = @bitCast(flags); 833 837 try tty.print(ctlseqs.csi_u_push, .{flag_int}); 834 838 try tty.flush(); ··· 836 840 } 837 841 838 842 /// send a system notification 839 - pub fn notify(_: *Vaxis, tty: *IoWriter, title: ?[]const u8, body: []const u8) !void { 843 + pub fn notify(_: *Vaxis, tty: *std.Io.Writer, title: ?[]const u8, body: []const u8) !void { 840 844 if (title) |t| 841 845 try tty.print(ctlseqs.osc777_notify, .{ t, body }) 842 846 else ··· 846 850 } 847 851 848 852 /// sets the window title 849 - pub fn setTitle(_: *Vaxis, tty: *IoWriter, title: []const u8) !void { 853 + pub fn setTitle(_: *Vaxis, tty: *std.Io.Writer, title: []const u8) !void { 850 854 try tty.print(ctlseqs.osc2_set_title, .{title}); 851 855 try tty.flush(); 852 856 } ··· 854 858 // turn bracketed paste on or off. An event will be sent at the 855 859 // beginning and end of a detected paste. All keystrokes between these 856 860 // events were pasted 857 - pub fn setBracketedPaste(self: *Vaxis, tty: *IoWriter, enable: bool) !void { 861 + pub fn setBracketedPaste(self: *Vaxis, tty: *std.Io.Writer, enable: bool) !void { 858 862 const seq = if (enable) 859 863 ctlseqs.bp_set 860 864 else ··· 870 874 } 871 875 872 876 /// Change the mouse reporting mode 873 - pub fn setMouseMode(self: *Vaxis, tty: *IoWriter, enable: bool) !void { 877 + pub fn setMouseMode(self: *Vaxis, tty: *std.Io.Writer, enable: bool) !void { 874 878 if (enable) { 875 879 self.state.mouse = true; 876 880 if (self.caps.sgr_pixels) { ··· 914 918 pub fn transmitLocalImagePath( 915 919 self: *Vaxis, 916 920 allocator: std.mem.Allocator, 917 - tty: *IoWriter, 921 + tty: *std.Io.Writer, 918 922 payload: []const u8, 919 923 width: u16, 920 924 height: u16, ··· 972 976 /// Transmit an image which has been pre-base64 encoded 973 977 pub fn transmitPreEncodedImage( 974 978 self: *Vaxis, 975 - tty: *IoWriter, 979 + tty: *std.Io.Writer, 976 980 bytes: []const u8, 977 981 width: u16, 978 982 height: u16, ··· 1031 1035 pub fn transmitImage( 1032 1036 self: *Vaxis, 1033 1037 alloc: std.mem.Allocator, 1034 - tty: *IoWriter, 1038 + tty: *std.Io.Writer, 1035 1039 img: *const zigimg.Image, 1036 1040 format: Image.TransmitFormat, 1037 1041 ) !Image { ··· 1067 1071 pub fn loadImage( 1068 1072 self: *Vaxis, 1069 1073 alloc: std.mem.Allocator, 1070 - tty: *IoWriter, 1074 + tty: *std.Io.Writer, 1071 1075 src: Image.Source, 1072 1076 ) !Image { 1073 1077 if (!self.caps.kitty_graphics) return error.NoGraphicsCapability; ··· 1082 1086 } 1083 1087 1084 1088 /// deletes an image from the terminal's memory 1085 - pub fn freeImage(_: Vaxis, tty: *IoWriter, id: u32) void { 1089 + pub fn freeImage(_: Vaxis, tty: *std.Io.Writer, id: u32) void { 1086 1090 tty.print("\x1b_Ga=d,d=I,i={d};\x1b\\", .{id}) catch |err| { 1087 1091 log.err("couldn't delete image {d}: {}", .{ id, err }); 1088 1092 return; ··· 1090 1094 tty.flush() catch {}; 1091 1095 } 1092 1096 1093 - pub fn copyToSystemClipboard(_: Vaxis, tty: *IoWriter, text: []const u8, encode_allocator: std.mem.Allocator) !void { 1097 + pub fn copyToSystemClipboard(_: Vaxis, tty: *std.Io.Writer, text: []const u8, encode_allocator: std.mem.Allocator) !void { 1094 1098 const encoder = std.base64.standard.Encoder; 1095 1099 const size = encoder.calcSize(text.len); 1096 1100 const buf = try encode_allocator.alloc(u8, size); ··· 1104 1108 try tty.flush(); 1105 1109 } 1106 1110 1107 - pub fn requestSystemClipboard(self: Vaxis, tty: *IoWriter) !void { 1111 + pub fn requestSystemClipboard(self: Vaxis, tty: *std.Io.Writer) !void { 1108 1112 if (self.opts.system_clipboard_allocator == null) return error.NoClipboardAllocator; 1109 1113 try tty.print( 1110 1114 ctlseqs.osc52_clipboard_request, ··· 1114 1118 } 1115 1119 1116 1120 /// Set the default terminal foreground color 1117 - pub fn setTerminalForegroundColor(self: *Vaxis, tty: *IoWriter, rgb: [3]u8) !void { 1121 + pub fn setTerminalForegroundColor(self: *Vaxis, tty: *std.Io.Writer, rgb: [3]u8) !void { 1118 1122 try tty.print(ctlseqs.osc10_set, .{ rgb[0], rgb[0], rgb[1], rgb[1], rgb[2], rgb[2] }); 1119 1123 try tty.flush(); 1120 1124 self.state.changed_default_fg = true; 1121 1125 } 1122 1126 1123 1127 /// Set the default terminal background color 1124 - pub fn setTerminalBackgroundColor(self: *Vaxis, tty: *IoWriter, rgb: [3]u8) !void { 1128 + pub fn setTerminalBackgroundColor(self: *Vaxis, tty: *std.Io.Writer, rgb: [3]u8) !void { 1125 1129 try tty.print(ctlseqs.osc11_set, .{ rgb[0], rgb[0], rgb[1], rgb[1], rgb[2], rgb[2] }); 1126 1130 try tty.flush(); 1127 1131 self.state.changed_default_bg = true; 1128 1132 } 1129 1133 1130 1134 /// Set the terminal cursor color 1131 - pub fn setTerminalCursorColor(self: *Vaxis, tty: *IoWriter, rgb: [3]u8) !void { 1135 + pub fn setTerminalCursorColor(self: *Vaxis, tty: *std.Io.Writer, rgb: [3]u8) !void { 1132 1136 try tty.print(ctlseqs.osc12_set, .{ rgb[0], rgb[0], rgb[1], rgb[1], rgb[2], rgb[2] }); 1133 1137 try tty.flush(); 1134 1138 self.state.changed_cursor_color = true; 1135 1139 } 1136 1140 1137 1141 /// Set the terminal secondary cursor color 1138 - pub fn setTerminalCursorSecondaryColor(self: *Vaxis, tty: *IoWriter, rgb: [3]u8) error{WriteFailed}!void { 1142 + pub fn setTerminalCursorSecondaryColor(self: *Vaxis, tty: *std.Io.Writer, rgb: [3]u8) error{WriteFailed}!void { 1139 1143 if (self.caps.multi_cursor) { 1140 1144 try tty.print(ctlseqs.secondary_cursors_rgb, .{ rgb[0], rgb[1], rgb[2] }); 1141 1145 try tty.flush(); ··· 1170 1174 /// Request a color report from the terminal. Note: not all terminals support 1171 1175 /// reporting colors. It is always safe to try, but you may not receive a 1172 1176 /// response. 1173 - pub fn queryColor(_: Vaxis, tty: *IoWriter, kind: Cell.Color.Kind) !void { 1177 + pub fn queryColor(_: Vaxis, tty: *std.Io.Writer, kind: Cell.Color.Kind) !void { 1174 1178 switch (kind) { 1175 1179 .fg => try tty.writeAll(ctlseqs.osc10_query), 1176 1180 .bg => try tty.writeAll(ctlseqs.osc11_query), ··· 1185 1189 /// capability. Support can be detected by checking the value of 1186 1190 /// vaxis.caps.color_scheme_updates. The initial scheme will be reported when 1187 1191 /// subscribing. 1188 - pub fn subscribeToColorSchemeUpdates(self: *Vaxis, tty: *IoWriter) !void { 1192 + pub fn subscribeToColorSchemeUpdates(self: *Vaxis, tty: *std.Io.Writer) !void { 1189 1193 try tty.writeAll(ctlseqs.color_scheme_request); 1190 1194 try tty.writeAll(ctlseqs.color_scheme_set); 1191 1195 try tty.flush(); 1192 1196 self.state.color_scheme_updates = true; 1193 1197 } 1194 1198 1195 - pub fn deviceStatusReport(_: Vaxis, tty: *IoWriter) !void { 1199 + pub fn deviceStatusReport(_: Vaxis, tty: *std.Io.Writer) !void { 1196 1200 try tty.writeAll(ctlseqs.device_status_report); 1197 1201 try tty.flush(); 1198 1202 } ··· 1201 1205 /// the cursor will be put on the next line after the last line is printed. This is useful to 1202 1206 /// sequentially print data in a styled format to eg. stdout. This function returns an error if you 1203 1207 /// are not in the alt screen. The cursor is always hidden, and mouse shapes are not available 1204 - pub fn prettyPrint(self: *Vaxis, tty: *IoWriter) !void { 1208 + pub fn prettyPrint(self: *Vaxis, tty: *std.Io.Writer) !void { 1205 1209 if (self.state.alt_screen) return error.NotInPrimaryScreen; 1206 1210 1207 1211 try tty.writeAll(ctlseqs.hide_cursor); ··· 1476 1480 } 1477 1481 1478 1482 /// Set the terminal's current working directory 1479 - pub fn setTerminalWorkingDirectory(_: *Vaxis, tty: *IoWriter, path: []const u8) !void { 1483 + pub fn setTerminalWorkingDirectory(_: *Vaxis, tty: *std.Io.Writer, path: []const u8) !void { 1480 1484 if (path.len == 0 or path[0] != '/') 1481 1485 return error.InvalidAbsolutePath; 1482 1486 const hostname = switch (builtin.os.tag) { ··· 1494 1498 } 1495 1499 1496 1500 test "render: no output when no changes" { 1497 - var vx = try Vaxis.init(std.testing.allocator, .{}); 1498 - var deinit_writer = std.io.Writer.Allocating.init(std.testing.allocator); 1501 + const io = std.testing.io; 1502 + var env_map = try std.testing.environ.createMap(std.testing.allocator); 1503 + defer env_map.deinit(); 1504 + var vx = try Vaxis.init(io, std.testing.allocator, &env_map, .{}); 1505 + var deinit_writer: std.Io.Writer.Allocating = .init(std.testing.allocator); 1499 1506 defer deinit_writer.deinit(); 1500 1507 defer vx.deinit(std.testing.allocator, &deinit_writer.writer); 1501 1508 1502 - var render_writer = std.io.Writer.Allocating.init(std.testing.allocator); 1509 + var render_writer: std.Io.Writer.Allocating = .init(std.testing.allocator); 1503 1510 defer render_writer.deinit(); 1504 1511 try vx.render(&render_writer.writer); 1505 1512 const output = try render_writer.toOwnedSlice();
+2 -2
src/gwidth.zig
··· 55 55 var grapheme_start: usize = 0; 56 56 var prev_break: bool = true; 57 57 58 - while (grapheme_iter.next()) |result| { 58 + while (grapheme_iter.nextCodePoint()) |result| { 59 59 if (prev_break and !result.is_break) { 60 60 // Start of a new grapheme 61 - const cp_len: usize = std.unicode.utf8CodepointSequenceLength(result.cp) catch 1; 61 + const cp_len: usize = std.unicode.utf8CodepointSequenceLength(result.code_point) catch 1; 62 62 grapheme_start = grapheme_iter.i - cp_len; 63 63 } 64 64
+2 -2
src/main.zig
··· 43 43 }; 44 44 45 45 /// Initialize a Vaxis application. 46 - pub fn init(alloc: std.mem.Allocator, opts: Vaxis.Options) !Vaxis { 47 - return Vaxis.init(alloc, opts); 46 + pub fn init(io: std.Io, alloc: std.mem.Allocator, env_map: *std.process.Environ.Map, opts: Vaxis.Options) !Vaxis { 47 + return Vaxis.init(io, alloc, env_map, opts); 48 48 } 49 49 50 50 pub const Panic = struct {
+124 -107
src/queue.zig
··· 1 1 const std = @import("std"); 2 2 const assert = std.debug.assert; 3 3 const atomic = std.atomic; 4 - const Condition = std.Thread.Condition; 5 4 6 5 /// Thread safe. Fixed size. Blocking push and pop. 7 6 pub fn Queue( ··· 14 13 read_index: usize = 0, 15 14 write_index: usize = 0, 16 15 17 - mutex: std.Thread.Mutex = .{}, 16 + io: std.Io, 17 + mutex: std.Io.Mutex = .init, 18 18 // blocks when the buffer is full 19 - not_full: Condition = .{}, 19 + not_full: std.Io.Condition = .init, 20 20 // ...or empty 21 - not_empty: Condition = .{}, 21 + not_empty: std.Io.Condition = .init, 22 22 23 23 const Self = @This(); 24 24 25 + pub fn init(io: std.Io) Self { 26 + return .{ .io = io }; 27 + } 28 + 25 29 /// Pop an item from the queue. Blocks until an item is available. 26 - pub fn pop(self: *Self) T { 27 - self.mutex.lock(); 28 - defer self.mutex.unlock(); 30 + pub fn pop(self: *Self) !T { 31 + try self.mutex.lock(self.io); 32 + defer self.mutex.unlock(self.io); 29 33 while (self.isEmptyLH()) { 30 - self.not_empty.wait(&self.mutex); 34 + try self.not_empty.wait(self.io, &self.mutex); 31 35 } 32 36 std.debug.assert(!self.isEmptyLH()); 33 37 return self.popAndSignalLH(); ··· 35 39 36 40 /// Push an item into the queue. Blocks until an item has been 37 41 /// put in the queue. 38 - pub fn push(self: *Self, item: T) void { 39 - self.mutex.lock(); 40 - defer self.mutex.unlock(); 42 + pub fn push(self: *Self, item: T) !void { 43 + try self.mutex.lock(self.io); 44 + defer self.mutex.unlock(self.io); 41 45 while (self.isFullLH()) { 42 - self.not_full.wait(&self.mutex); 46 + try self.not_full.wait(self.io, &self.mutex); 43 47 } 44 48 std.debug.assert(!self.isFullLH()); 45 49 self.pushAndSignalLH(item); ··· 48 52 /// Push an item into the queue. Returns true when the item 49 53 /// was successfully placed in the queue, false if the queue 50 54 /// was full. 51 - pub fn tryPush(self: *Self, item: T) bool { 52 - self.mutex.lock(); 53 - defer self.mutex.unlock(); 55 + pub fn tryPush(self: *Self, item: T) !bool { 56 + try self.mutex.lock(self.io); 57 + defer self.mutex.unlock(self.io); 54 58 if (self.isFullLH()) return false; 55 59 self.pushAndSignalLH(item); 56 60 return true; ··· 58 62 59 63 /// Pop an item from the queue. Returns null when no item is 60 64 /// available. 61 - pub fn tryPop(self: *Self) ?T { 62 - self.mutex.lock(); 63 - defer self.mutex.unlock(); 65 + pub fn tryPop(self: *Self) !?T { 66 + try self.mutex.lock(self.io); 67 + defer self.mutex.unlock(self.io); 64 68 if (self.isEmptyLH()) return null; 65 69 return self.popAndSignalLH(); 66 70 } 67 71 68 72 /// Poll the queue. This call blocks until events are in the queue 69 - pub fn poll(self: *Self) void { 70 - self.mutex.lock(); 71 - defer self.mutex.unlock(); 73 + pub fn poll(self: *Self) !void { 74 + self.mutex.lock(self.io); 75 + defer self.mutex.unlock(self.io); 72 76 while (self.isEmptyLH()) { 73 77 self.not_empty.wait(&self.mutex); 74 78 } 75 79 std.debug.assert(!self.isEmptyLH()); 76 80 } 77 81 78 - pub fn lock(self: *Self) void { 79 - self.mutex.lock(); 82 + pub fn lock(self: *Self) !void { 83 + try self.mutex.lock(self.io); 80 84 } 81 85 82 86 pub fn unlock(self: *Self) void { 83 - self.mutex.unlock(); 87 + self.mutex.unlock(self.io); 84 88 } 85 89 86 90 /// Used to efficiently drain the queue while the lock is externally held ··· 107 111 } 108 112 109 113 /// Returns `true` if the queue is empty and `false` otherwise. 110 - pub fn isEmpty(self: *Self) bool { 111 - self.mutex.lock(); 112 - defer self.mutex.unlock(); 114 + pub fn isEmpty(self: *Self) !bool { 115 + try self.mutex.lock(self.io); 116 + defer self.mutex.unlock(self.io); 113 117 return self.isEmptyLH(); 114 118 } 115 119 116 120 /// Returns `true` if the queue is full and `false` otherwise. 117 - pub fn isFull(self: *Self) bool { 118 - self.mutex.lock(); 119 - defer self.mutex.unlock(); 121 + pub fn isFull(self: *Self) !bool { 122 + try self.mutex.lock(self.io); 123 + defer self.mutex.unlock(self.io); 120 124 return self.isFullLH(); 121 125 } 122 126 ··· 143 147 self.buf[self.mask(self.write_index)] = item; 144 148 self.write_index = self.mask2(self.write_index + 1); 145 149 if (was_empty) { 146 - self.not_empty.signal(); 150 + self.not_empty.signal(self.io); 147 151 } 148 152 } 149 153 ··· 151 155 const was_full = self.isFullLH(); 152 156 const result = self.popLH(); 153 157 if (was_full) { 154 - self.not_full.signal(); 158 + self.not_full.signal(self.io); 155 159 } 156 160 return result; 157 161 } ··· 167 171 const testing = std.testing; 168 172 const cfg = Thread.SpawnConfig{ .allocator = testing.allocator }; 169 173 test "Queue: simple push / pop" { 170 - var queue: Queue(u8, 16) = .{}; 171 - queue.push(1); 172 - queue.push(2); 173 - const pop = queue.pop(); 174 + const io = std.testing.io; 175 + var queue: Queue(u8, 16) = .init(io); 176 + try queue.push(1); 177 + try queue.push(2); 178 + const pop = try queue.pop(); 174 179 try testing.expectEqual(1, pop); 175 - try testing.expectEqual(2, queue.pop()); 180 + try testing.expectEqual(2, try queue.pop()); 176 181 } 177 182 178 183 const Thread = std.Thread; 179 184 fn testPushPop(q: *Queue(u8, 2)) !void { 180 - q.push(3); 181 - try testing.expectEqual(2, q.pop()); 185 + try q.push(3); 186 + try testing.expectEqual(2, try q.pop()); 182 187 } 183 188 184 189 test "Fill, wait to push, pop once in another thread" { 185 - var queue: Queue(u8, 2) = .{}; 186 - queue.push(1); 187 - queue.push(2); 188 - const t = try Thread.spawn(cfg, testPushPop, .{&queue}); 189 - try testing.expectEqual(false, queue.tryPush(3)); 190 - try testing.expectEqual(1, queue.pop()); 191 - t.join(); 192 - try testing.expectEqual(3, queue.pop()); 193 - try testing.expectEqual(null, queue.tryPop()); 190 + const io = std.testing.io; 191 + var queue: Queue(u8, 2) = .init(io); 192 + try queue.push(1); 193 + try queue.push(2); 194 + var t = try io.concurrent(testPushPop, .{&queue}); 195 + try testing.expectEqual(false, try queue.tryPush(3)); 196 + try testing.expectEqual(1, try queue.pop()); 197 + try t.await(io); 198 + try testing.expectEqual(3, try queue.pop()); 199 + try testing.expectEqual(null, try queue.tryPop()); 194 200 } 195 201 196 - fn testPush(q: *Queue(u8, 2)) void { 197 - q.push(0); 198 - q.push(1); 199 - q.push(2); 200 - q.push(3); 201 - q.push(4); 202 + fn testPush(q: *Queue(u8, 2)) !void { 203 + try q.push(0); 204 + try q.push(1); 205 + try q.push(2); 206 + try q.push(3); 207 + try q.push(4); 202 208 } 203 209 204 210 test "Try to pop, fill from another thread" { 205 - var queue: Queue(u8, 2) = .{}; 206 - const thread = try Thread.spawn(cfg, testPush, .{&queue}); 211 + const io = std.testing.io; 212 + var queue: Queue(u8, 2) = .init(io); 213 + var task = try io.concurrent(testPush, .{&queue}); 214 + defer task.cancel(io) catch {}; 207 215 for (0..5) |idx| { 208 - try testing.expectEqual(@as(u8, @intCast(idx)), queue.pop()); 216 + try testing.expectEqual(@as(u8, @intCast(idx)), try queue.pop()); 209 217 } 210 - thread.join(); 218 + try task.await(io); 211 219 } 212 220 213 - fn sleepyPop(q: *Queue(u8, 2), state: *atomic.Value(u8)) !void { 221 + fn sleepyPop(io: std.Io, q: *Queue(u8, 2), state: *atomic.Value(u8)) !void { 214 222 // First we wait for the queue to be full. 215 223 while (state.load(.acquire) < 1) 216 224 try Thread.yield(); 217 225 218 226 // Then we spuriously wake it up, because that's a thing that can 219 227 // happen. 220 - q.not_full.signal(); 221 - q.not_empty.signal(); 228 + q.not_full.signal(io); 229 + q.not_empty.signal(io); 222 230 223 231 // Then give the other thread a good chance of waking up. It's not 224 232 // clear that yield guarantees the other thread will be scheduled, ··· 226 234 // still full and the push in the other thread is still blocked 227 235 // waiting for space. 228 236 try Thread.yield(); 229 - std.Thread.sleep(10 * std.time.ns_per_ms); 237 + try io.sleep(.fromMilliseconds(10), .real); 230 238 // Finally, let that other thread go. 231 239 try std.testing.expectEqual(1, q.pop()); 232 240 ··· 235 243 try Thread.yield(); 236 244 // But we want to ensure that there's a second push waiting, so 237 245 // here's another sleep. 238 - std.Thread.sleep(10 * std.time.ns_per_ms); 246 + try io.sleep(.fromMilliseconds(10), .real); 239 247 240 248 // Another spurious wake... 241 - q.not_full.signal(); 242 - q.not_empty.signal(); 249 + q.not_full.signal(io); 250 + q.not_empty.signal(io); 243 251 // And another chance for the other thread to see that it's 244 252 // spurious and go back to sleep. 245 253 try Thread.yield(); 246 - std.Thread.sleep(10 * std.time.ns_per_ms); 254 + try io.sleep(.fromMilliseconds(10), .real); 247 255 248 256 // Pop that thing and we're done. 249 257 try std.testing.expectEqual(2, q.pop()); 250 258 } 251 259 252 260 test "Fill, block, fill, block" { 261 + const io = std.testing.io; 262 + 253 263 // Fill the queue, block while trying to write another item, have 254 264 // a background thread unblock us, then block while trying to 255 265 // write yet another thing. Have the background thread unblock 256 266 // that too (after some time) then drain the queue. This test 257 267 // fails if the while loop in `push` is turned into an `if`. 258 268 259 - var queue: Queue(u8, 2) = .{}; 269 + var queue: Queue(u8, 2) = .init(io); 260 270 var state = atomic.Value(u8).init(0); 261 - const thread = try Thread.spawn(cfg, sleepyPop, .{ &queue, &state }); 262 - queue.push(1); 263 - queue.push(2); 271 + var task = try io.concurrent(sleepyPop, .{ io, &queue, &state }); 272 + try queue.push(1); 273 + try queue.push(2); 264 274 state.store(1, .release); 265 - const now = std.time.milliTimestamp(); 266 - queue.push(3); // This one should block. 267 - const then = std.time.milliTimestamp(); 275 + const now = std.Io.Timestamp.now(io, .real).toMilliseconds(); 276 + try queue.push(3); // This one should block. 277 + const then = std.Io.Timestamp.now(io, .real).toMilliseconds(); 268 278 269 279 // Just to make sure the sleeps are yielding to this thread, make 270 280 // sure it took at least 5ms to do the push. ··· 272 282 273 283 state.store(2, .release); 274 284 // This should block again, waiting for the other thread. 275 - queue.push(4); 285 + try queue.push(4); 276 286 277 287 // And once that push has gone through, the other thread's done. 278 - thread.join(); 279 - try std.testing.expectEqual(3, queue.pop()); 280 - try std.testing.expectEqual(4, queue.pop()); 288 + try task.await(io); 289 + try std.testing.expectEqual(3, try queue.pop()); 290 + try std.testing.expectEqual(4, try queue.pop()); 281 291 } 282 292 283 - fn sleepyPush(q: *Queue(u8, 1), state: *atomic.Value(u8)) !void { 293 + fn sleepyPush(io: std.Io, q: *Queue(u8, 1), state: *atomic.Value(u8)) !void { 284 294 // Try to ensure the other thread has already started trying to pop. 285 295 try Thread.yield(); 286 - std.Thread.sleep(10 * std.time.ns_per_ms); 296 + try io.sleep(.fromMilliseconds(10), .real); 287 297 288 298 // Spurious wake 289 - q.not_full.signal(); 290 - q.not_empty.signal(); 299 + q.not_full.signal(io); 300 + q.not_empty.signal(io); 291 301 292 302 try Thread.yield(); 293 - std.Thread.sleep(10 * std.time.ns_per_ms); 303 + try io.sleep(.fromMilliseconds(10), .real); 294 304 295 305 // Stick something in the queue so it can be popped. 296 - q.push(1); 306 + try q.push(1); 297 307 // Ensure it's been popped. 298 308 while (state.load(.acquire) < 1) 299 309 try Thread.yield(); 300 310 // Give the other thread time to block again. 301 311 try Thread.yield(); 302 - std.Thread.sleep(10 * std.time.ns_per_ms); 312 + try io.sleep(.fromMilliseconds(10), .real); 303 313 304 314 // Spurious wake 305 - q.not_full.signal(); 306 - q.not_empty.signal(); 315 + q.not_full.signal(io); 316 + q.not_empty.signal(io); 307 317 308 - q.push(2); 318 + try q.push(2); 309 319 } 310 320 311 321 test "Drain, block, drain, block" { 322 + const io = std.testing.io; 323 + 312 324 // This is like fill/block/fill/block, but on the pop end. This 313 325 // test should fail if the `while` loop in `pop` is turned into an 314 326 // `if`. 315 327 316 - var queue: Queue(u8, 1) = .{}; 328 + var queue: Queue(u8, 1) = .init(io); 317 329 var state = atomic.Value(u8).init(0); 318 - const thread = try Thread.spawn(cfg, sleepyPush, .{ &queue, &state }); 330 + var task = try io.concurrent(sleepyPush, .{ io, &queue, &state }); 319 331 try std.testing.expectEqual(1, queue.pop()); 320 332 state.store(1, .release); 321 333 try std.testing.expectEqual(2, queue.pop()); 322 - thread.join(); 334 + try task.await(io); 323 335 } 324 336 325 337 fn readerThread(q: *Queue(u8, 1)) !void { 326 - try testing.expectEqual(1, q.pop()); 338 + try testing.expectEqual(1, try q.pop()); 327 339 } 328 340 329 341 test "2 readers" { 342 + const io = std.testing.io; 330 343 // 2 threads read, one thread writes 331 - var queue: Queue(u8, 1) = .{}; 332 - const t1 = try Thread.spawn(cfg, readerThread, .{&queue}); 333 - const t2 = try Thread.spawn(cfg, readerThread, .{&queue}); 344 + var queue: Queue(u8, 1) = .init(io); 345 + var t1 = try io.concurrent(readerThread, .{&queue}); 346 + defer t1.cancel(io) catch {}; 347 + var t2 = try io.concurrent(readerThread, .{&queue}); 348 + defer t2.cancel(io) catch {}; 334 349 try Thread.yield(); 335 - std.Thread.sleep(10 * std.time.ns_per_ms); 336 - queue.push(1); 337 - queue.push(1); 338 - t1.join(); 339 - t2.join(); 350 + try io.sleep(.fromMilliseconds(10), .real); 351 + try queue.push(1); 352 + try queue.push(1); 353 + try t1.await(io); 354 + try t2.await(io); 340 355 } 341 356 342 357 fn writerThread(q: *Queue(u8, 1)) !void { 343 - q.push(1); 358 + try q.push(1); 344 359 } 345 360 346 361 test "2 writers" { 347 - var queue: Queue(u8, 1) = .{}; 348 - const t1 = try Thread.spawn(cfg, writerThread, .{&queue}); 349 - const t2 = try Thread.spawn(cfg, writerThread, .{&queue}); 362 + const io = std.testing.io; 350 363 351 - try testing.expectEqual(1, queue.pop()); 352 - try testing.expectEqual(1, queue.pop()); 353 - t1.join(); 354 - t2.join(); 364 + var queue: Queue(u8, 1) = .init(io); 365 + var t1 = try io.concurrent(writerThread, .{&queue}); 366 + var t2 = try io.concurrent(writerThread, .{&queue}); 367 + 368 + try testing.expectEqual(1, try queue.pop()); 369 + try testing.expectEqual(1, try queue.pop()); 370 + try t1.await(io); 371 + try t2.await(io); 355 372 }
+21 -14
src/tty.zig
··· 31 31 termios: posix.termios, 32 32 33 33 /// The file descriptor of the tty 34 - fd: posix.fd_t, 34 + fd: std.Io.File, 35 35 36 36 /// File.Writer for efficient buffered writing 37 37 tty_writer: std.fs.File.Writer, ··· 51 51 /// initializes a Tty instance by opening /dev/tty and "making it raw". A 52 52 /// signal handler is installed for SIGWINCH. No callbacks are installed, be 53 53 /// sure to register a callback when initializing the event loop 54 - pub fn init(buffer: []u8) !PosixTty { 54 + pub fn init(io: std.Io, buffer: []u8) !PosixTty { 55 55 // Open our tty 56 - const fd = try posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0); 56 + var f = try std.Io.Dir.openFileAbsolute(io, "/dev/tty", .{ .mode = .read_write }); 57 + // const fd = try posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0); 57 58 58 59 // Set the termios of the tty 59 - const termios = try makeRaw(fd); 60 + const termios = try makeRaw(f.handle); 60 61 61 62 var act = posix.Sigaction{ 62 63 .handler = .{ .handler = PosixTty.handleWinch }, ··· 69 70 posix.sigaction(posix.SIG.WINCH, &act, null); 70 71 handler_installed = true; 71 72 72 - const file = std.fs.File{ .handle = fd }; 73 + // const file = std.fs.File{ .handle = fd }; 73 74 74 75 const self: PosixTty = .{ 75 - .fd = fd, 76 + .fd = f, 76 77 .termios = termios, 77 - .tty_writer = .initStreaming(file, buffer), 78 + .tty_writer = f.readerStreaming(io, &buffer), 78 79 }; 79 80 80 81 global_tty = self; ··· 692 693 }; 693 694 694 695 pub const TestTty = struct { 696 + const linux = std.os.linux; 695 697 /// Used for API compat 696 698 fd: posix.fd_t, 697 699 pipe_read: posix.fd_t, ··· 702 704 pub fn init(buffer: []u8) !TestTty { 703 705 _ = buffer; 704 706 705 - if (builtin.os.tag == .windows) return error.SkipZigTest; 707 + if (builtin.os.tag != .linux) return error.SkipZigTest; 706 708 const list = try std.testing.allocator.create(std.Io.Writer.Allocating); 707 709 list.* = .init(std.testing.allocator); 708 - const r, const w = try posix.pipe(); 710 + var fds: [2]i32 = undefined; 711 + const rc = linux.pipe(&fds); 712 + switch (linux.errno(rc)) { 713 + .SUCCESS => {}, 714 + else => return error.PipeCreateFailed, 715 + } 709 716 return .{ 710 - .fd = r, 711 - .pipe_read = r, 712 - .pipe_write = w, 717 + .fd = fds[0], 718 + .pipe_read = fds[0], 719 + .pipe_write = fds[0], 713 720 .tty_writer = list, 714 721 }; 715 722 } 716 723 717 724 pub fn deinit(self: TestTty) void { 718 - std.posix.close(self.pipe_read); 719 - std.posix.close(self.pipe_write); 725 + _ = linux.close(self.pipe_read); 726 + _ = linux.close(self.pipe_write); 720 727 self.tty_writer.deinit(); 721 728 std.testing.allocator.destroy(self.tty_writer); 722 729 }
+3 -2
src/unicode.zig
··· 26 26 } 27 27 28 28 pub fn next(self: *GraphemeIterator) ?Grapheme { 29 - while (self.inner.next()) |res| { 29 + while (self.inner.nextCodePoint()) |res| { 30 + 30 31 // When leaving a break and entering a non-break, set the start of a cluster 31 32 if (self.prev_break and !res.is_break) { 32 - const cp_len: usize = std.unicode.utf8CodepointSequenceLength(res.cp) catch 1; 33 + const cp_len: usize = std.unicode.utf8CodepointSequenceLength(res.code_point) catch 1; 33 34 self.start = self.inner.i - cp_len; 34 35 } 35 36
+1
src/vxfw/Button.zig
··· 140 140 141 141 // Event handlers need a context 142 142 var ctx: vxfw.EventContext = .{ 143 + .io = std.testing.io, 143 144 .alloc = std.testing.allocator, 144 145 .cmds = .empty, 145 146 };
+2
src/vxfw/ListView.zig
··· 562 562 }; 563 563 // Event handlers need a context 564 564 var ctx: vxfw.EventContext = .{ 565 + .io = std.testing.io, 565 566 .alloc = std.testing.allocator, 566 567 .cmds = .empty, 567 568 }; ··· 729 730 }; 730 731 // Event handlers need a context 731 732 var ctx: vxfw.EventContext = .{ 733 + .io = std.testing.io, 732 734 .alloc = std.testing.allocator, 733 735 .cmds = .empty, 734 736 };
+2
src/vxfw/ScrollView.zig
··· 644 644 }; 645 645 // Event handlers need a context 646 646 var ctx: vxfw.EventContext = .{ 647 + .io = std.testing.io, 647 648 .alloc = std.testing.allocator, 648 649 .cmds = .empty, 649 650 }; ··· 1042 1043 }; 1043 1044 // Event handlers need a context 1044 1045 var ctx: vxfw.EventContext = .{ 1046 + .io = std.testing.io, 1045 1047 .alloc = std.testing.allocator, 1046 1048 .cmds = .empty, 1047 1049 };
+4 -2
src/vxfw/Spinner.zig
··· 10 10 const frames: []const []const u8 = &.{ "⣶", "⣧", "⣏", "⡟", "⠿", "⢻", "⣹", "⣼" }; 11 11 const time_lapse: u32 = std.time.ms_per_s / 12; // 12 fps 12 12 13 + io: std.Io, 13 14 count: std.atomic.Value(u16) = .{ .raw = 0 }, 14 15 style: vaxis.Style = .{}, 15 16 /// The frame index ··· 24 25 self.was_spinning.store(true, .unordered); 25 26 const count = self.count.fetchAdd(1, .monotonic); 26 27 if (count == 0) { 27 - return vxfw.Tick.in(time_lapse, self.widget()); 28 + return vxfw.Tick.in(self.io, time_lapse, self.widget()); 28 29 } 29 30 return null; 30 31 } ··· 104 105 var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 105 106 defer arena.deinit(); 106 107 // Create a spinner 107 - var spinner: Spinner = .{}; 108 + var spinner: Spinner = .{ .io = std.testing.io }; 108 109 // Get our widget interface 109 110 const spinner_widget = spinner.widget(); 110 111 ··· 124 125 // We are about to deliver the tick to the widget. We need an EventContext (the engine will 125 126 // provide this) 126 127 var ctx: vxfw.EventContext = .{ 128 + .io = std.testing.io, 127 129 .alloc = arena.allocator(), 128 130 .cmds = .empty, 129 131 };
+1
src/vxfw/SplitView.zig
··· 227 227 }; 228 228 229 229 var ctx: vxfw.EventContext = .{ 230 + .io = std.testing.io, 230 231 .alloc = arena.allocator(), 231 232 .cmds = .empty, 232 233 };
+2 -2
src/vxfw/Text.zig
··· 223 223 // Advance the hard iterator 224 224 if (self.index == self.line.len) { 225 225 self.line = self.hard_iter.next() orelse return null; 226 - self.line = std.mem.trimRight(u8, self.line, " \t"); 226 + self.line = std.mem.trimEnd(u8, self.line, " \t"); 227 227 self.index = 0; 228 228 } 229 229 ··· 237 237 if (self.ctx.max.width) |max| { 238 238 if (cur_width + next_width > max) { 239 239 // Trim the word to see if it can fit on a line by itself 240 - const trimmed = std.mem.trimLeft(u8, word, " \t"); 240 + const trimmed = std.mem.trimEnd(u8, word, " \t"); 241 241 const trimmed_bytes = word.len - trimmed.len; 242 242 // The number of bytes we trimmed is equal to the reduction in length 243 243 const trimmed_width = next_width - trimmed_bytes;
+3
src/vxfw/TextField.zig
··· 655 655 } 656 656 657 657 test TextField { 658 + const io = std.testing.io; 659 + 658 660 // Boiler plate draw context init 659 661 var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 660 662 defer arena.deinit(); ··· 683 685 _ = draw_ctx; 684 686 685 687 var ctx: vxfw.EventContext = .{ 688 + .io = io, 686 689 .alloc = arena.allocator(), 687 690 .cmds = .empty, 688 691 };
+9 -7
src/vxfw/vxfw.zig
··· 61 61 return lhs.deadline_ms > rhs.deadline_ms; 62 62 } 63 63 64 - pub fn in(ms: u32, widget: Widget) Command { 65 - const now = std.time.milliTimestamp(); 64 + pub fn in(io: std.Io, ms: u32, widget: Widget) Command { 65 + const now = std.Io.Timestamp.now(io, .real).toMilliseconds(); 66 66 return .{ .tick = .{ 67 67 .deadline_ms = now + ms, 68 68 .widget = widget, ··· 99 99 100 100 pub const EventContext = struct { 101 101 phase: Phase = .at_target, 102 + io: std.Io, 102 103 alloc: Allocator, 103 104 cmds: CommandList, 104 105 ··· 120 121 } 121 122 122 123 pub fn tick(self: *EventContext, ms: u32, widget: Widget) Allocator.Error!void { 123 - try self.addCmd(Tick.in(ms, widget)); 124 + try self.addCmd(Tick.in(self.io, ms, widget)); 124 125 } 125 126 126 127 pub fn consumeAndRedraw(self: *EventContext) void { ··· 535 536 } 536 537 537 538 test "All widgets have a doctest and refAllDecls test" { 539 + const io = std.testing.io; 538 540 // This test goes through every file in src/ and checks that it has a doctest (the filename 539 541 // stripped of ".zig" matches a test name) and a test called "refAllDecls". It makes no 540 542 // guarantees about the quality of the test, but it does ensure it exists which at least makes 541 543 // it easy to fail CI early, or spot bad tests vs non-existant tests 542 544 const excludes = &[_][]const u8{ "vxfw.zig", "App.zig" }; 543 545 544 - var cwd = try std.fs.cwd().openDir("./src/vxfw", .{ .iterate = true }); 546 + var cwd = try std.Io.Dir.cwd().openDir(io, "./src/vxfw", .{ .iterate = true }); 545 547 var iter = cwd.iterate(); 546 - defer cwd.close(); 547 - outer: while (try iter.next()) |file| { 548 + defer cwd.close(io); 549 + outer: while (try iter.next(io)) |file| { 548 550 if (file.kind != .file) continue; 549 551 for (excludes) |ex| if (std.mem.eql(u8, ex, file.name)) continue :outer; 550 552 ··· 552 554 file.name[0..idx] 553 555 else 554 556 continue; 555 - const data = try cwd.readFileAllocOptions(std.testing.allocator, file.name, 10_000_000, null, .of(u8), 0x00); 557 + const data = try cwd.readFileAllocOptions(io, file.name, std.testing.allocator, .limited(10_000_000), .of(u8), 0x00); 556 558 defer std.testing.allocator.free(data); 557 559 var ast = try std.zig.Ast.parse(std.testing.allocator, data, .zig); 558 560 defer ast.deinit(std.testing.allocator);