chor: use new Writer interface for terminal's Writer; fix test cases
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 3m38s

This commit is contained in:
2025-10-01 10:59:29 +02:00
parent aa17e13b99
commit 832fc45c3e
6 changed files with 46 additions and 44 deletions

View File

@@ -13,7 +13,7 @@ pub fn reset(this: *Cell) void {
this.cp = ' '; 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); 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} } }, .{ .cp = 's', .style = .{ .fg = .light_green, .bg = .black, .emphasis = &.{.underline} } },
}; };
var string = try std.ArrayList(u8).initCapacity(std.testing.allocator, 4); var writer = std.Io.Writer.Allocating.init(std.testing.allocator);
defer string.deinit(std.testing.allocator); defer writer.deinit();
const writer = string.writer(std.testing.allocator);
for (cells) |cell| { for (cells) |cell| {
try cell.value(writer); try cell.value(&writer.writer);
} }
try std.testing.expectEqualSlices( try std.testing.expectEqualSlices(
u8, 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", "\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} } }, .{ .cp = '┘', .style = .{ .fg = .light_green, .bg = .black, .emphasis = &.{.underline} } },
}; };
var string = try std.ArrayList(u8).initCapacity(std.testing.allocator, 4); var writer = std.Io.Writer.Allocating.init(std.testing.allocator);
defer string.deinit(std.testing.allocator); defer writer.deinit();
const writer = string.writer(std.testing.allocator);
for (cells) |cell| { for (cells) |cell| {
try cell.value(writer); try cell.value(&writer.writer);
} }
try std.testing.expectEqualSlices( try std.testing.expectEqualSlices(
u8, 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", "\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],
); );
} }

View File

@@ -20,22 +20,21 @@ pub const Color = enum(u8) {
// TODO might be useful to use the std.ascii stuff! // 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) { if (this == .default) {
switch (coloring) { switch (coloring) {
.fg => try format(writer, "39", .{}), .fg => try writer.printAscii("39", .{}),
.bg => try format(writer, "49", .{}), .bg => try writer.printAscii("49", .{}),
.ul => try format(writer, "59", .{}), .ul => try writer.printAscii("59", .{}),
} }
} else { } else {
switch (coloring) { switch (coloring) {
.fg => try format(writer, "38;5;{d}", .{@intFromEnum(this)}), .fg => try writer.print("38;5;{d}", .{@intFromEnum(this)}),
.bg => try format(writer, "48;5;{d}", .{@intFromEnum(this)}), .bg => try writer.print("48;5;{d}", .{@intFromEnum(this)}),
.ul => try format(writer, "58;5;{d}", .{@intFromEnum(this)}), .ul => try writer.print("58;5;{d}", .{@intFromEnum(this)}),
} }
} }
} }
}; };
const std = @import("std"); const std = @import("std");
const format = std.fmt.format;

View File

@@ -88,7 +88,7 @@ pub const Buffered = struct {
try terminal.hideCursor(); try terminal.hideCursor();
// TODO measure timings of rendered frames? // TODO measure timings of rendered frames?
var cursor_position: ?Point = null; var cursor_position: ?Point = null;
const writer = terminal.writer(); var writer = terminal.writer();
const s = this.screen; const s = this.screen;
const vs = this.virtual_screen; const vs = this.virtual_screen;
for (0..this.size.y) |row| { for (0..this.size.y) |row| {
@@ -110,7 +110,7 @@ pub const Buffered = struct {
// render differences found in virtual screen // render differences found in virtual screen
try terminal.setCursorPosition(.{ .y = @truncate(row), .x = @truncate(col) }); 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 // update screen to be the virtual screen for the next frame
s[idx] = vs[idx]; s[idx] = vs[idx];
} }

View File

@@ -42,27 +42,27 @@ pub fn eql(this: Style, other: Style) bool {
// TODO might be useful to use the std.ascii stuff! // 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; var buffer: [4]u8 = undefined;
const bytes = try unicode.utf8Encode(cp, &buffer); const bytes = try unicode.utf8Encode(cp, &buffer);
assert(bytes > 0); assert(bytes > 0);
// build ansi sequence for 256 colors ... // build ansi sequence for 256 colors ...
// foreground // foreground
try format(writer, "\x1b[", .{}); try writer.printAscii("\x1b[", .{});
try this.fg.write(writer, .fg); try this.fg.write(writer, .fg);
// background // background
try format(writer, ";", .{}); try writer.printAsciiChar(';', .{});
try this.bg.write(writer, .bg); try this.bg.write(writer, .bg);
// underline // underline
// FIX assert that if the underline property is set that the ul style and the attribute for underlining is available // 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); try this.ul.write(writer, .ul);
// append styles (aka attributes like bold, italic, strikethrough, etc.) // append styles (aka attributes like bold, italic, strikethrough, etc.)
for (this.emphasis) |attribute| try format(writer, ";{d}", .{@intFromEnum(attribute)}); for (this.emphasis) |attribute| try writer.print(";{d}", .{@intFromEnum(attribute)});
try format(writer, "m", .{}); try writer.printAsciiChar('m', .{});
// content // content
try format(writer, "{s}", .{buffer[0..bytes]}); try writer.printAscii(buffer[0..bytes], .{});
try format(writer, "\x1b[0m", .{}); try writer.printAscii("\x1b[0m", .{});
} }
// TODO implement helper functions for terminal capabilities: // TODO implement helper functions for terminal capabilities:
@@ -73,6 +73,5 @@ const std = @import("std");
const unicode = std.unicode; const unicode = std.unicode;
const meta = std.meta; const meta = std.meta;
const assert = std.debug.assert; const assert = std.debug.assert;
const format = std.fmt.format;
const Color = @import("color.zig").Color; const Color = @import("color.zig").Color;
const Style = @This(); const Style = @This();

View File

@@ -62,17 +62,24 @@ pub fn write(buf: []const u8) !usize {
return try posix.write(posix.STDIN_FILENO, buf); return try posix.write(posix.STDIN_FILENO, buf);
} }
fn contextWrite(context: *const anyopaque, data: []const u8) anyerror!usize { fn drainFn(w: *std.Io.Writer, data: []const []const u8, splat: usize) error{WriteFailed}!usize {
_ = context; _ = w;
return try posix.write(posix.STDOUT_FILENO, data); 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; // TODO I now need to add that much, for just the one function above?
pub fn writer() std.Io.Writer {
pub fn writer() Writer {
return .{ return .{
.context = &.{}, .vtable = &.{
.writeFn = contextWrite, .drain = drainFn,
.flush = std.Io.Writer.noopFlush,
},
.buffer = &.{},
}; };
} }
@@ -231,6 +238,7 @@ const log = std.log.scoped(.terminal);
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const posix = std.posix; const posix = std.posix;
const assert = std.debug.assert;
const ctlseqs = @import("ctlseqs.zig"); const ctlseqs = @import("ctlseqs.zig");
const input = @import("input.zig"); const input = @import("input.zig");
const Key = input.Key; const Key = input.Key;

View File

@@ -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); var actual_cps = try std.ArrayList(Cell).initCapacity(allocator, size.x);
defer actual_cps.deinit(allocator); defer actual_cps.deinit(allocator);
var output = try std.ArrayList(u8).initCapacity(allocator, expected_cps.capacity * actual_cps.capacity + 5 * size.y); var allocating_writer = std.Io.Writer.Allocating.init(allocator);
defer output.deinit(allocator); defer allocating_writer.deinit();
const writer = output.writer(allocator); var writer = &allocating_writer.writer;
var differ = false; var differ = false;
const expected_centered = try center(allocator, "Expected Screen", size.x, " "); 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_buffer: [1024]u8 = undefined;
var stdout = std.fs.File.stdout().writer(&stdout_buffer); var stdout = std.fs.File.stdout().writer(&stdout_buffer);
const stdout_writer = &stdout.interface; const stdout_writer = &stdout.interface;
try stdout_writer.writeAll(output.items); try stdout_writer.writeAll(writer.buffer[0..writer.end]);
try stdout_writer.flush(); try stdout_writer.flush();
return error.TestExpectEqualCells; return error.TestExpectEqualCells;
} }