···118118 var grapheme_len: usize = 0;
119119 var cp_count: usize = 0;
120120121121- while (grapheme_iter.next()) |result| {
121121+ while (grapheme_iter.nextCodePoint()) |result| {
122122 cp_count += 1;
123123 if (result.is_break) {
124124 // Found the first grapheme boundary
+50-43
src/Vaxis.zig
···33const atomic = std.atomic;
44const base64Encoder = std.base64.standard.Encoder;
55const zigimg = @import("zigimg");
66-const IoWriter = std.io.Writer;
7687const Cell = @import("Cell.zig");
98const Image = @import("Image.zig");
···4948 /// clipboard
5049 system_clipboard_allocator: ?std.mem.Allocator = null,
5150};
5151+5252+io: std.Io,
5353+env_map: *std.process.Environ.Map,
52545355/// the screen we write to
5456screen: Screen,
···103105} = .{},
104106105107/// Initialize Vaxis with runtime options
106106-pub fn init(alloc: std.mem.Allocator, opts: Options) !Vaxis {
108108+pub fn init(io: std.Io, alloc: std.mem.Allocator, env_map: *std.process.Environ.Map, opts: Options) !Vaxis {
107109 return .{
110110+ .io = io,
111111+ .env_map = env_map,
108112 .opts = opts,
109113 .screen = .{},
110114 .screen_last = try .init(alloc, 0, 0),
···115119/// passed, this will free resources associated with Vaxis. This is left as an
116120/// optional so applications can choose to not free resources when the
117121/// application will be exiting anyways
118118-pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator, tty: *IoWriter) void {
122122+pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator, tty: *std.Io.Writer) void {
119123 self.resetState(tty) catch {};
120124121125 if (alloc) |a| {
···128132}
129133130134/// resets enabled features, sends cursor to home and clears below cursor
131131-pub fn resetState(self: *Vaxis, tty: *IoWriter) !void {
135135+pub fn resetState(self: *Vaxis, tty: *std.Io.Writer) !void {
132136 // always show the cursor on state reset
133137 tty.writeAll(ctlseqs.show_cursor) catch {};
134138 tty.writeAll(ctlseqs.sgr_reset) catch {};
···190194pub fn resize(
191195 self: *Vaxis,
192196 alloc: std.mem.Allocator,
193193- tty: *IoWriter,
197197+ tty: *std.Io.Writer,
194198 winsize: Winsize,
195199) !void {
196200 log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
···231235232236/// enter the alternate screen. The alternate screen will automatically
233237/// be exited if calling deinit while in the alt screen.
234234-pub fn enterAltScreen(self: *Vaxis, tty: *IoWriter) !void {
238238+pub fn enterAltScreen(self: *Vaxis, tty: *std.Io.Writer) !void {
235239 try tty.writeAll(ctlseqs.smcup);
236240 try tty.flush();
237241 self.state.alt_screen = true;
238242}
239243240244/// exit the alternate screen. Does not flush the writer.
241241-pub fn exitAltScreen(self: *Vaxis, tty: *IoWriter) !void {
245245+pub fn exitAltScreen(self: *Vaxis, tty: *std.Io.Writer) !void {
242246 try tty.writeAll(ctlseqs.rmcup);
243247 try tty.flush();
244248 self.state.alt_screen = false;
···250254///
251255/// This call will block until Vaxis.query_futex is woken up, or the timeout.
252256/// Event loops can wake up this futex when cap_da1 is received
253253-pub fn queryTerminal(self: *Vaxis, tty: *IoWriter, timeout_ns: u64) !void {
257257+pub fn queryTerminal(self: *Vaxis, tty: *std.Io.Writer, timeout_ns: u64) !void {
254258 try self.queryTerminalSend(tty);
255259 // 1 second timeout
256256- std.Thread.Futex.timedWait(&self.query_futex, 0, timeout_ns) catch {};
260260+ try std.Io.futexWaitTimeout(self.io, atomic.Value(u32), &self.query_futex, .init(0), .{ .duration = .{ .clock = .real, .raw = .fromNanoseconds(timeout_ns) } });
257261 self.queries_done.store(true, .unordered);
258262 try self.enableDetectedFeatures(tty);
259263}
···261265/// write queries to the terminal to determine capabilities. This function
262266/// is only for use with a custom main loop. Call Vaxis.queryTerminal() if
263267/// you are using Loop.run()
264264-pub fn queryTerminalSend(vx: *Vaxis, tty: *IoWriter) !void {
268268+pub fn queryTerminalSend(vx: *Vaxis, tty: *std.Io.Writer) !void {
265269 vx.queries_done.store(false, .unordered);
266270267271 // TODO: re-enable this
···312316/// Enable features detected by responses to queryTerminal. This function
313317/// is only for use with a custom main loop. Call Vaxis.queryTerminal() if
314318/// you are using Loop.run()
315315-pub fn enableDetectedFeatures(self: *Vaxis, tty: *IoWriter) !void {
319319+pub fn enableDetectedFeatures(self: *Vaxis, tty: *std.Io.Writer) !void {
316320 switch (builtin.os.tag) {
317321 .windows => {
318322 // No feature detection on windows. We just hard enable some knowns for ConPTY
···320324 },
321325 else => {
322326 // Apply any environment variables
323323- if (std.posix.getenv("TERMUX_VERSION")) |_|
327327+ if (self.env_map.get("TERMUX_VERSION")) |_|
324328 self.sgr = .legacy;
325325- if (std.posix.getenv("VHS_RECORD")) |_| {
329329+ if (self.env_map.get("VHS_RECORD")) |_| {
326330 self.caps.unicode = .wcwidth;
327331 self.caps.kitty_keyboard = false;
328332 self.sgr = .legacy;
329333 }
330330- if (std.posix.getenv("TERM_PROGRAM")) |prg| {
334334+ if (self.env_map.get("TERM_PROGRAM")) |prg| {
331335 if (std.mem.eql(u8, prg, "vscode"))
332336 self.sgr = .legacy;
333337 }
334334- if (std.posix.getenv("VAXIS_FORCE_LEGACY_SGR")) |_|
338338+ if (self.env_map.get("VAXIS_FORCE_LEGACY_SGR")) |_|
335339 self.sgr = .legacy;
336336- if (std.posix.getenv("VAXIS_FORCE_WCWIDTH")) |_|
340340+ if (self.env_map.get("VAXIS_FORCE_WCWIDTH")) |_|
337341 self.caps.unicode = .wcwidth;
338338- if (std.posix.getenv("VAXIS_FORCE_UNICODE")) |_|
342342+ if (self.env_map.get("VAXIS_FORCE_UNICODE")) |_|
339343 self.caps.unicode = .unicode;
340344341345 // enable detected features
···358362}
359363360364/// draws the screen to the terminal
361361-pub fn render(self: *Vaxis, tty: *IoWriter) !void {
365365+pub fn render(self: *Vaxis, tty: *std.Io.Writer) !void {
362366 defer self.refresh = false;
363367 assert(self.screen.buf.len == @as(usize, @intCast(self.screen.width)) * self.screen.height); // correct size
364368 assert(self.screen.buf.len == self.screen_last.buf.len); // same size
···397401 const startRender = struct {
398402 fn run(
399403 vx: *Vaxis,
400400- io: *IoWriter,
404404+ io: *std.Io.Writer,
401405 cursor_pos_ptr: *CursorPos,
402406 reposition_ptr: *bool,
403407 started_ptr: *bool,
···828832 try tty.flush();
829833}
830834831831-fn enableKittyKeyboard(self: *Vaxis, tty: *IoWriter, flags: Key.KittyFlags) !void {
835835+fn enableKittyKeyboard(self: *Vaxis, tty: *std.Io.Writer, flags: Key.KittyFlags) !void {
832836 const flag_int: u5 = @bitCast(flags);
833837 try tty.print(ctlseqs.csi_u_push, .{flag_int});
834838 try tty.flush();
···836840}
837841838842/// send a system notification
839839-pub fn notify(_: *Vaxis, tty: *IoWriter, title: ?[]const u8, body: []const u8) !void {
843843+pub fn notify(_: *Vaxis, tty: *std.Io.Writer, title: ?[]const u8, body: []const u8) !void {
840844 if (title) |t|
841845 try tty.print(ctlseqs.osc777_notify, .{ t, body })
842846 else
···846850}
847851848852/// sets the window title
849849-pub fn setTitle(_: *Vaxis, tty: *IoWriter, title: []const u8) !void {
853853+pub fn setTitle(_: *Vaxis, tty: *std.Io.Writer, title: []const u8) !void {
850854 try tty.print(ctlseqs.osc2_set_title, .{title});
851855 try tty.flush();
852856}
···854858// turn bracketed paste on or off. An event will be sent at the
855859// beginning and end of a detected paste. All keystrokes between these
856860// events were pasted
857857-pub fn setBracketedPaste(self: *Vaxis, tty: *IoWriter, enable: bool) !void {
861861+pub fn setBracketedPaste(self: *Vaxis, tty: *std.Io.Writer, enable: bool) !void {
858862 const seq = if (enable)
859863 ctlseqs.bp_set
860864 else
···870874}
871875872876/// Change the mouse reporting mode
873873-pub fn setMouseMode(self: *Vaxis, tty: *IoWriter, enable: bool) !void {
877877+pub fn setMouseMode(self: *Vaxis, tty: *std.Io.Writer, enable: bool) !void {
874878 if (enable) {
875879 self.state.mouse = true;
876880 if (self.caps.sgr_pixels) {
···914918pub fn transmitLocalImagePath(
915919 self: *Vaxis,
916920 allocator: std.mem.Allocator,
917917- tty: *IoWriter,
921921+ tty: *std.Io.Writer,
918922 payload: []const u8,
919923 width: u16,
920924 height: u16,
···972976/// Transmit an image which has been pre-base64 encoded
973977pub fn transmitPreEncodedImage(
974978 self: *Vaxis,
975975- tty: *IoWriter,
979979+ tty: *std.Io.Writer,
976980 bytes: []const u8,
977981 width: u16,
978982 height: u16,
···10311035pub fn transmitImage(
10321036 self: *Vaxis,
10331037 alloc: std.mem.Allocator,
10341034- tty: *IoWriter,
10381038+ tty: *std.Io.Writer,
10351039 img: *const zigimg.Image,
10361040 format: Image.TransmitFormat,
10371041) !Image {
···10671071pub fn loadImage(
10681072 self: *Vaxis,
10691073 alloc: std.mem.Allocator,
10701070- tty: *IoWriter,
10741074+ tty: *std.Io.Writer,
10711075 src: Image.Source,
10721076) !Image {
10731077 if (!self.caps.kitty_graphics) return error.NoGraphicsCapability;
···10821086}
1083108710841088/// deletes an image from the terminal's memory
10851085-pub fn freeImage(_: Vaxis, tty: *IoWriter, id: u32) void {
10891089+pub fn freeImage(_: Vaxis, tty: *std.Io.Writer, id: u32) void {
10861090 tty.print("\x1b_Ga=d,d=I,i={d};\x1b\\", .{id}) catch |err| {
10871091 log.err("couldn't delete image {d}: {}", .{ id, err });
10881092 return;
···10901094 tty.flush() catch {};
10911095}
1092109610931093-pub fn copyToSystemClipboard(_: Vaxis, tty: *IoWriter, text: []const u8, encode_allocator: std.mem.Allocator) !void {
10971097+pub fn copyToSystemClipboard(_: Vaxis, tty: *std.Io.Writer, text: []const u8, encode_allocator: std.mem.Allocator) !void {
10941098 const encoder = std.base64.standard.Encoder;
10951099 const size = encoder.calcSize(text.len);
10961100 const buf = try encode_allocator.alloc(u8, size);
···11041108 try tty.flush();
11051109}
1106111011071107-pub fn requestSystemClipboard(self: Vaxis, tty: *IoWriter) !void {
11111111+pub fn requestSystemClipboard(self: Vaxis, tty: *std.Io.Writer) !void {
11081112 if (self.opts.system_clipboard_allocator == null) return error.NoClipboardAllocator;
11091113 try tty.print(
11101114 ctlseqs.osc52_clipboard_request,
···11141118}
1115111911161120/// Set the default terminal foreground color
11171117-pub fn setTerminalForegroundColor(self: *Vaxis, tty: *IoWriter, rgb: [3]u8) !void {
11211121+pub fn setTerminalForegroundColor(self: *Vaxis, tty: *std.Io.Writer, rgb: [3]u8) !void {
11181122 try tty.print(ctlseqs.osc10_set, .{ rgb[0], rgb[0], rgb[1], rgb[1], rgb[2], rgb[2] });
11191123 try tty.flush();
11201124 self.state.changed_default_fg = true;
11211125}
1122112611231127/// Set the default terminal background color
11241124-pub fn setTerminalBackgroundColor(self: *Vaxis, tty: *IoWriter, rgb: [3]u8) !void {
11281128+pub fn setTerminalBackgroundColor(self: *Vaxis, tty: *std.Io.Writer, rgb: [3]u8) !void {
11251129 try tty.print(ctlseqs.osc11_set, .{ rgb[0], rgb[0], rgb[1], rgb[1], rgb[2], rgb[2] });
11261130 try tty.flush();
11271131 self.state.changed_default_bg = true;
11281132}
1129113311301134/// Set the terminal cursor color
11311131-pub fn setTerminalCursorColor(self: *Vaxis, tty: *IoWriter, rgb: [3]u8) !void {
11351135+pub fn setTerminalCursorColor(self: *Vaxis, tty: *std.Io.Writer, rgb: [3]u8) !void {
11321136 try tty.print(ctlseqs.osc12_set, .{ rgb[0], rgb[0], rgb[1], rgb[1], rgb[2], rgb[2] });
11331137 try tty.flush();
11341138 self.state.changed_cursor_color = true;
11351139}
1136114011371141/// Set the terminal secondary cursor color
11381138-pub fn setTerminalCursorSecondaryColor(self: *Vaxis, tty: *IoWriter, rgb: [3]u8) error{WriteFailed}!void {
11421142+pub fn setTerminalCursorSecondaryColor(self: *Vaxis, tty: *std.Io.Writer, rgb: [3]u8) error{WriteFailed}!void {
11391143 if (self.caps.multi_cursor) {
11401144 try tty.print(ctlseqs.secondary_cursors_rgb, .{ rgb[0], rgb[1], rgb[2] });
11411145 try tty.flush();
···11701174/// Request a color report from the terminal. Note: not all terminals support
11711175/// reporting colors. It is always safe to try, but you may not receive a
11721176/// response.
11731173-pub fn queryColor(_: Vaxis, tty: *IoWriter, kind: Cell.Color.Kind) !void {
11771177+pub fn queryColor(_: Vaxis, tty: *std.Io.Writer, kind: Cell.Color.Kind) !void {
11741178 switch (kind) {
11751179 .fg => try tty.writeAll(ctlseqs.osc10_query),
11761180 .bg => try tty.writeAll(ctlseqs.osc11_query),
···11851189/// capability. Support can be detected by checking the value of
11861190/// vaxis.caps.color_scheme_updates. The initial scheme will be reported when
11871191/// subscribing.
11881188-pub fn subscribeToColorSchemeUpdates(self: *Vaxis, tty: *IoWriter) !void {
11921192+pub fn subscribeToColorSchemeUpdates(self: *Vaxis, tty: *std.Io.Writer) !void {
11891193 try tty.writeAll(ctlseqs.color_scheme_request);
11901194 try tty.writeAll(ctlseqs.color_scheme_set);
11911195 try tty.flush();
11921196 self.state.color_scheme_updates = true;
11931197}
1194119811951195-pub fn deviceStatusReport(_: Vaxis, tty: *IoWriter) !void {
11991199+pub fn deviceStatusReport(_: Vaxis, tty: *std.Io.Writer) !void {
11961200 try tty.writeAll(ctlseqs.device_status_report);
11971201 try tty.flush();
11981202}
···12011205/// the cursor will be put on the next line after the last line is printed. This is useful to
12021206/// sequentially print data in a styled format to eg. stdout. This function returns an error if you
12031207/// are not in the alt screen. The cursor is always hidden, and mouse shapes are not available
12041204-pub fn prettyPrint(self: *Vaxis, tty: *IoWriter) !void {
12081208+pub fn prettyPrint(self: *Vaxis, tty: *std.Io.Writer) !void {
12051209 if (self.state.alt_screen) return error.NotInPrimaryScreen;
1206121012071211 try tty.writeAll(ctlseqs.hide_cursor);
···14761480}
1477148114781482/// Set the terminal's current working directory
14791479-pub fn setTerminalWorkingDirectory(_: *Vaxis, tty: *IoWriter, path: []const u8) !void {
14831483+pub fn setTerminalWorkingDirectory(_: *Vaxis, tty: *std.Io.Writer, path: []const u8) !void {
14801484 if (path.len == 0 or path[0] != '/')
14811485 return error.InvalidAbsolutePath;
14821486 const hostname = switch (builtin.os.tag) {
···14941498}
1495149914961500test "render: no output when no changes" {
14971497- var vx = try Vaxis.init(std.testing.allocator, .{});
14981498- var deinit_writer = std.io.Writer.Allocating.init(std.testing.allocator);
15011501+ const io = std.testing.io;
15021502+ var env_map = try std.testing.environ.createMap(std.testing.allocator);
15031503+ defer env_map.deinit();
15041504+ var vx = try Vaxis.init(io, std.testing.allocator, &env_map, .{});
15051505+ var deinit_writer: std.Io.Writer.Allocating = .init(std.testing.allocator);
14991506 defer deinit_writer.deinit();
15001507 defer vx.deinit(std.testing.allocator, &deinit_writer.writer);
1501150815021502- var render_writer = std.io.Writer.Allocating.init(std.testing.allocator);
15091509+ var render_writer: std.Io.Writer.Allocating = .init(std.testing.allocator);
15031510 defer render_writer.deinit();
15041511 try vx.render(&render_writer.writer);
15051512 const output = try render_writer.toOwnedSlice();
+2-2
src/gwidth.zig
···5555 var grapheme_start: usize = 0;
5656 var prev_break: bool = true;
57575858- while (grapheme_iter.next()) |result| {
5858+ while (grapheme_iter.nextCodePoint()) |result| {
5959 if (prev_break and !result.is_break) {
6060 // Start of a new grapheme
6161- const cp_len: usize = std.unicode.utf8CodepointSequenceLength(result.cp) catch 1;
6161+ const cp_len: usize = std.unicode.utf8CodepointSequenceLength(result.code_point) catch 1;
6262 grapheme_start = grapheme_iter.i - cp_len;
6363 }
6464
···223223 // Advance the hard iterator
224224 if (self.index == self.line.len) {
225225 self.line = self.hard_iter.next() orelse return null;
226226- self.line = std.mem.trimRight(u8, self.line, " \t");
226226+ self.line = std.mem.trimEnd(u8, self.line, " \t");
227227 self.index = 0;
228228 }
229229···237237 if (self.ctx.max.width) |max| {
238238 if (cur_width + next_width > max) {
239239 // Trim the word to see if it can fit on a line by itself
240240- const trimmed = std.mem.trimLeft(u8, word, " \t");
240240+ const trimmed = std.mem.trimEnd(u8, word, " \t");
241241 const trimmed_bytes = word.len - trimmed.len;
242242 // The number of bytes we trimmed is equal to the reduction in length
243243 const trimmed_width = next_width - trimmed_bytes;