From 832fc45c3e007c631f90b66094cfd9ee9150ed55 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Wed, 1 Oct 2025 10:59:29 +0200 Subject: [PATCH] chor: use new `Writer` interface for terminal's `Writer`; fix test cases --- src/cell.zig | 22 +++++++++------------- src/color.zig | 15 +++++++-------- src/render.zig | 4 ++-- src/style.zig | 17 ++++++++--------- src/terminal.zig | 24 ++++++++++++++++-------- src/testing.zig | 8 ++++---- 6 files changed, 46 insertions(+), 44 deletions(-) diff --git a/src/cell.zig b/src/cell.zig index 8b52b17..27a77cc 100644 --- a/src/cell.zig +++ b/src/cell.zig @@ -13,7 +13,7 @@ pub fn reset(this: *Cell) void { this.cp = ' '; } -pub fn value(this: Cell, writer: anytype) !void { +pub fn value(this: Cell, writer: *std.Io.Writer) !void { try this.style.value(writer, this.cp); } @@ -29,17 +29,15 @@ test "ascii styled text" { .{ .cp = 's', .style = .{ .fg = .light_green, .bg = .black, .emphasis = &.{.underline} } }, }; - var string = try std.ArrayList(u8).initCapacity(std.testing.allocator, 4); - defer string.deinit(std.testing.allocator); - - const writer = string.writer(std.testing.allocator); + var writer = std.Io.Writer.Allocating.init(std.testing.allocator); + defer writer.deinit(); for (cells) |cell| { - try cell.value(writer); + try cell.value(&writer.writer); } try std.testing.expectEqualSlices( u8, "\x1b[38;5;10;48;5;8;59mY\x1b[0m\x1b[39;49;59;1;4mv\x1b[0m\x1b[39;49;59;3me\x1b[0m\x1b[38;5;2;48;5;16;59;4ms\x1b[0m", - string.items, + writer.writer.buffer[0..writer.writer.end], ); } @@ -51,16 +49,14 @@ test "utf-8 styled text" { .{ .cp = '┘', .style = .{ .fg = .light_green, .bg = .black, .emphasis = &.{.underline} } }, }; - var string = try std.ArrayList(u8).initCapacity(std.testing.allocator, 4); - defer string.deinit(std.testing.allocator); - - const writer = string.writer(std.testing.allocator); + var writer = std.Io.Writer.Allocating.init(std.testing.allocator); + defer writer.deinit(); for (cells) |cell| { - try cell.value(writer); + try cell.value(&writer.writer); } try std.testing.expectEqualSlices( u8, "\x1b[38;5;10;48;5;8;59m╭\x1b[0m\x1b[39;49;59m─\x1b[0m\x1b[39;49;59m┄\x1b[0m\x1b[38;5;2;48;5;16;59;4m┘\x1b[0m", - string.items, + writer.writer.buffer[0..writer.writer.end], ); } diff --git a/src/color.zig b/src/color.zig index c128e57..a050a39 100644 --- a/src/color.zig +++ b/src/color.zig @@ -20,22 +20,21 @@ pub const Color = enum(u8) { // TODO might be useful to use the std.ascii stuff! - pub inline fn write(this: Color, writer: anytype, comptime coloring: enum { fg, bg, ul }) !void { + pub inline fn write(this: Color, writer: *std.Io.Writer, comptime coloring: enum { fg, bg, ul }) !void { if (this == .default) { switch (coloring) { - .fg => try format(writer, "39", .{}), - .bg => try format(writer, "49", .{}), - .ul => try format(writer, "59", .{}), + .fg => try writer.printAscii("39", .{}), + .bg => try writer.printAscii("49", .{}), + .ul => try writer.printAscii("59", .{}), } } else { switch (coloring) { - .fg => try format(writer, "38;5;{d}", .{@intFromEnum(this)}), - .bg => try format(writer, "48;5;{d}", .{@intFromEnum(this)}), - .ul => try format(writer, "58;5;{d}", .{@intFromEnum(this)}), + .fg => try writer.print("38;5;{d}", .{@intFromEnum(this)}), + .bg => try writer.print("48;5;{d}", .{@intFromEnum(this)}), + .ul => try writer.print("58;5;{d}", .{@intFromEnum(this)}), } } } }; const std = @import("std"); -const format = std.fmt.format; diff --git a/src/render.zig b/src/render.zig index d0e56cc..333872e 100644 --- a/src/render.zig +++ b/src/render.zig @@ -88,7 +88,7 @@ pub const Buffered = struct { try terminal.hideCursor(); // TODO measure timings of rendered frames? var cursor_position: ?Point = null; - const writer = terminal.writer(); + var writer = terminal.writer(); const s = this.screen; const vs = this.virtual_screen; for (0..this.size.y) |row| { @@ -110,7 +110,7 @@ pub const Buffered = struct { // render differences found in virtual screen try terminal.setCursorPosition(.{ .y = @truncate(row), .x = @truncate(col) }); - try cvs.value(writer); + try cvs.value(&writer); // update screen to be the virtual screen for the next frame s[idx] = vs[idx]; } diff --git a/src/style.zig b/src/style.zig index d1aefea..a1ec924 100644 --- a/src/style.zig +++ b/src/style.zig @@ -42,27 +42,27 @@ pub fn eql(this: Style, other: Style) bool { // TODO might be useful to use the std.ascii stuff! -pub fn value(this: Style, writer: anytype, cp: u21) !void { +pub fn value(this: Style, writer: *std.Io.Writer, cp: u21) !void { var buffer: [4]u8 = undefined; const bytes = try unicode.utf8Encode(cp, &buffer); assert(bytes > 0); // build ansi sequence for 256 colors ... // foreground - try format(writer, "\x1b[", .{}); + try writer.printAscii("\x1b[", .{}); try this.fg.write(writer, .fg); // background - try format(writer, ";", .{}); + try writer.printAsciiChar(';', .{}); try this.bg.write(writer, .bg); // underline // FIX assert that if the underline property is set that the ul style and the attribute for underlining is available - try format(writer, ";", .{}); + try writer.printAsciiChar(';', .{}); try this.ul.write(writer, .ul); // append styles (aka attributes like bold, italic, strikethrough, etc.) - for (this.emphasis) |attribute| try format(writer, ";{d}", .{@intFromEnum(attribute)}); - try format(writer, "m", .{}); + for (this.emphasis) |attribute| try writer.print(";{d}", .{@intFromEnum(attribute)}); + try writer.printAsciiChar('m', .{}); // content - try format(writer, "{s}", .{buffer[0..bytes]}); - try format(writer, "\x1b[0m", .{}); + try writer.printAscii(buffer[0..bytes], .{}); + try writer.printAscii("\x1b[0m", .{}); } // TODO implement helper functions for terminal capabilities: @@ -73,6 +73,5 @@ const std = @import("std"); const unicode = std.unicode; const meta = std.meta; const assert = std.debug.assert; -const format = std.fmt.format; const Color = @import("color.zig").Color; const Style = @This(); diff --git a/src/terminal.zig b/src/terminal.zig index 297c17b..38c523a 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -62,17 +62,24 @@ pub fn write(buf: []const u8) !usize { return try posix.write(posix.STDIN_FILENO, buf); } -fn contextWrite(context: *const anyopaque, data: []const u8) anyerror!usize { - _ = context; - return try posix.write(posix.STDOUT_FILENO, data); +fn drainFn(w: *std.Io.Writer, data: []const []const u8, splat: usize) error{WriteFailed}!usize { + _ = w; + if (data.len == 0 or splat == 0) return 0; + var len: usize = 0; + + for (data) |bytes| len += posix.write(posix.STDOUT_FILENO, bytes) catch return error.WriteFailed; + + return len; } -const Writer = std.io.AnyWriter; - -pub fn writer() Writer { +// TODO I now need to add that much, for just the one function above? +pub fn writer() std.Io.Writer { return .{ - .context = &.{}, - .writeFn = contextWrite, + .vtable = &.{ + .drain = drainFn, + .flush = std.Io.Writer.noopFlush, + }, + .buffer = &.{}, }; } @@ -231,6 +238,7 @@ const log = std.log.scoped(.terminal); const std = @import("std"); const mem = std.mem; const posix = std.posix; +const assert = std.debug.assert; const ctlseqs = @import("ctlseqs.zig"); const input = @import("input.zig"); const Key = input.Key; diff --git a/src/testing.zig b/src/testing.zig index 6b313a5..651a0e2 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -167,10 +167,10 @@ pub fn expectEqualCells(origin: Point, size: Point, expected: []const Cell, actu var actual_cps = try std.ArrayList(Cell).initCapacity(allocator, size.x); defer actual_cps.deinit(allocator); - var output = try std.ArrayList(u8).initCapacity(allocator, expected_cps.capacity * actual_cps.capacity + 5 * size.y); - defer output.deinit(allocator); + var allocating_writer = std.Io.Writer.Allocating.init(allocator); + defer allocating_writer.deinit(); - const writer = output.writer(allocator); + var writer = &allocating_writer.writer; var differ = false; const expected_centered = try center(allocator, "Expected Screen", size.x, " "); @@ -212,7 +212,7 @@ pub fn expectEqualCells(origin: Point, size: Point, expected: []const Cell, actu var stdout_buffer: [1024]u8 = undefined; var stdout = std.fs.File.stdout().writer(&stdout_buffer); const stdout_writer = &stdout.interface; - try stdout_writer.writeAll(output.items); + try stdout_writer.writeAll(writer.buffer[0..writer.end]); try stdout_writer.flush(); return error.TestExpectEqualCells; }