diff --git a/.gitignore b/.gitignore index 835d42d..dc7fe94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /result* /zig-cache /zig-out +/perf.data* diff --git a/build.zig b/build.zig index a39bab8..73f97be 100644 --- a/build.zig +++ b/build.zig @@ -4,17 +4,17 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - _ = b.addModule( + const anzi = b.addModule( "anzi", .{ .root_source_file = .{ - .path = "src/main.zig", + .path = "src/root.zig", }, }, ); const unit_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = .{ .path = "src/root.zig" }, .target = target, .optimize = optimize, }); @@ -22,4 +22,15 @@ pub fn build(b: *std.Build) void { const run_unit_tests = b.addRunArtifact(unit_tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_unit_tests.step); + + const random_sgr = b.addExecutable(.{ + .name = "random_sgr", + .root_source_file = .{ .path = "examples/random_sgr.zig" }, + .target = target, + .optimize = optimize, + }); + + random_sgr.root_module.addImport("anzi", anzi); + + b.installArtifact(random_sgr); } diff --git a/examples/random_sgr.zig b/examples/random_sgr.zig new file mode 100644 index 0000000..8a5553e --- /dev/null +++ b/examples/random_sgr.zig @@ -0,0 +1,192 @@ +const std = @import("std"); + +const anzi = @import("anzi"); + +var done: bool = false; + +fn signal(_: c_int) callconv(.C) void { + done = true; +} + +var column: u8 = 0; +var line: u8 = 0; + +fn nextColumn() u8 { + const c = column; + column = (column + 7) % 80; + return c + 1; +} + +fn nextLine() u8 { + const l = line; + line = (line + 27) % 24; + return l + 1; +} + +var red: u8 = 0; +var green: u8 = 0; +var blue: u8 = 0; + +inline fn nextRed() u8 { + const r = red; + red +%= 31; + return r; +} + +inline fn nextGreen() u8 { + const g = green; + green +%= 43; + return g; +} + +inline fn nextBlue() u8 { + const b = blue; + blue +%= 67; + return b; +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const alloc = gpa.allocator(); + + const action = std.os.Sigaction{ + .handler = .{ + .handler = signal, + }, + .flags = 0, + .mask = std.os.empty_sigset, + }; + + try std.os.sigaction(std.os.SIG.INT, &action, null); + + var generator = std.rand.DefaultPrng.init(@intCast(std.time.timestamp())); + const random = generator.random(); + + const a = anzi.ANSI(.{}); + + std.debug.print("preparing buffer...\n", .{}); + + const buffer = try alloc.alloc(u8, 1024 * 1024 * 1024); + defer alloc.free(buffer); + + var fbs = std.io.fixedBufferStream(buffer); + + { + const writer = fbs.writer(); + + try writer.print("{}{}", .{ + a.ScreenMode{ .mode = .alternate_screen_save_cursor_clear_enter, .action = .set }, + a.ScreenMode{ .mode = .cursor_visible, .action = .reset }, + }); + errdefer { + writer.print("{}{}", .{ + a.ScreenMode{ .mode = .cursor_visible, .action = .set }, + a.ScreenMode{ .mode = .alternate_screen_save_cursor_clear_enter, .action = .reset }, + }) catch unreachable; + } + + try a.Erase.entireScreen.write(writer); + + var index: usize = 0; + while (!done and index < 10000) : (index += 1) { + // try (a.ScreenMode{ .mode = .synchronized_output, .action = .set }).write(writer); + for (1..80) |_| { + for (1..24) |_| { + try (a.CursorMove{ + .to = .{ + .column = random.intRangeAtMost(u8, 1, 80), + .line = random.intRangeAtMost(u8, 1, 24), + }, + }).write(writer); + try (a.GraphicsRenditions{ + .graphics = &[_]a.GraphicsRenditions.GraphicsRendition{ + .{ + .color = .{ + .layer = .foreground, + .color = .{ + .colorRGB = .{ + .red = random.int(u8), + .green = random.int(u8), + .blue = random.int(u8), + }, + }, + }, + }, + .{ + .color = .{ + .layer = .background, + .color = .{ + .colorRGB = .{ + .red = random.int(u8), + .green = random.int(u8), + .blue = random.int(u8), + }, + }, + }, + }, + }, + }).write(writer); + try writer.writeByte('J'); + } + } + // try (a.ScreenMode{ .mode = .synchronized_output, .action = .reset }).write(writer); + } + try a.reset.write(writer); + try writer.print("{}{}", .{ + a.ScreenMode{ .mode = .cursor_visible, .action = .set }, + a.ScreenMode{ .mode = .alternate_screen_save_cursor_clear_enter, .action = .reset }, + }); + } + + var elapsed: u64 = undefined; + const written = fbs.getWritten(); + + { + const stdout = std.io.getStdOut(); + const writer = stdout.writer(); + + var iter = std.mem.window(u8, written, 4 * 1024 * 1024, 4 * 1024 * 1024); + + std.debug.print("starting to send data...\n", .{}); + + var timer = try std.time.Timer.start(); + + while (iter.next()) |buf| { + try writer.writeAll(buf); + } + + elapsed = timer.read(); + + std.debug.print("finished sending data...\n", .{}); + } + + const units = [_]struct { + name: []const u8, + factor: u64, + }{ + .{ .name = "y", .factor = 365 * std.time.ns_per_day }, + .{ .name = "w", .factor = std.time.ns_per_week }, + .{ .name = "d", .factor = std.time.ns_per_day }, + .{ .name = "h", .factor = std.time.ns_per_hour }, + .{ .name = "m", .factor = std.time.ns_per_min }, + .{ .name = "s", .factor = std.time.ns_per_s }, + .{ .name = "ms", .factor = std.time.ns_per_ms }, + .{ .name = "µs", .factor = std.time.ns_per_us }, + .{ .name = "ns", .factor = 1 }, + }; + + var i: usize = 0; + for (units) |unit| { + if (elapsed > unit.factor) { + if (i > 0) std.debug.print(" ", .{}); + const r = elapsed % unit.factor; + const x = (elapsed - r) / unit.factor; + std.debug.print("{d}{s}", .{ x, unit.name }); + elapsed = r; + i += 1; + } + } + + std.debug.print("\n", .{}); + std.debug.print("{d}\n", .{written.len}); +} diff --git a/src/main.zig b/src/root.zig similarity index 86% rename from src/main.zig rename to src/root.zig index 53705bb..450f280 100644 --- a/src/main.zig +++ b/src/root.zig @@ -174,7 +174,7 @@ pub fn ANSI(comptime options: Options) type { /// moves cursor to column # toColumn: u8, - pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + pub fn write(self: @This(), writer: anytype) !void { try writer.writeAll(Self.RL_PROMPT_START_IGNORE ++ Self.CSI); switch (self) { .home => try writer.writeAll("H"), @@ -194,12 +194,17 @@ pub fn ANSI(comptime options: Options) type { .downBOL => "E", .upBOL => "F", .toColumn => "G", + else => unreachable, }; try writer.writeAll(v); }, } try writer.writeAll(Self.RL_PROMPT_END_IGNORE); } + + pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try self.write(writer); + } }; /// request cursor position (reports as ESC[#;#R) @@ -248,8 +253,8 @@ pub fn ANSI(comptime options: Options) type { fromStartOfLineToCursor, entireLine, - pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try writer.writeAll(Self.RL_PROMPT_START_IGNORE ++ + pub fn write(self: @This(), writer: anytype) !void { + try writer.writeAll(Self.RL_PROMPT_START_IGNORE ++ Self.CSI ++ switch (self) { .fromCursorToEndOfScreen => "0J", .fromStartOfScreenToCursor => "1J", @@ -260,21 +265,26 @@ pub fn ANSI(comptime options: Options) type { .entireLine => "2K", } ++ Self.RL_PROMPT_END_IGNORE); } + + pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try self.write(writer); + } }; - const ColorType = union(enum) { + pub const ColorType = union(enum) { color8: Color8, color256: Color256, colorRGB: ColorRGB, }; - const GraphicsRenditions = struct { - const GraphicsRendition = union(enum) { + pub const GraphicsRenditions = struct { + pub const GraphicsRendition = union(enum) { reset: void, style: struct { mode: enum { enable, disable }, style: Style, - pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + + pub fn write(self: @This(), writer: anytype) !void { switch (self.mode) { .enable => try std.fmt.formatInt(@intFromEnum(self.style), 10, .lower, .{}, writer), .disable => switch (self.style) { @@ -283,11 +293,16 @@ pub fn ANSI(comptime options: Options) type { }, } } + + pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try self.write(writer); + } }, color: struct { layer: Layer, color: ColorType, - pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + + pub fn write(self: @This(), writer: anytype) !void { switch (self.color) { .color8 => |v| { var offset: u8 = 30; @@ -315,29 +330,37 @@ pub fn ANSI(comptime options: Options) type { }, } } + + pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try self.write(writer); + } }, }; graphics: []const GraphicsRendition, - pub fn format(self: @This(), comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { + pub fn write(self: @This(), writer: anytype) !void { try writer.writeAll(Self.RL_PROMPT_START_IGNORE ++ Self.CSI); for (self.graphics, 0..) |graphic, index| { if (index > 0) try writer.writeAll(";"); switch (graphic) { .reset => try writer.writeAll("0"), - .style => |s| try s.format(fmt, opt, writer), - .color => |c| try c.format(fmt, opt, writer), + .style => |s| try s.write(writer), + .color => |c| try c.write(writer), } } try writer.writeAll("m" ++ Self.RL_PROMPT_END_IGNORE); } + + pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try self.write(writer); + } }; /// Reset all styles and colors to the default - const reset = GraphicsRenditions{ + pub const reset = GraphicsRenditions{ .graphics = &.{ .{ .reset = {}, @@ -536,13 +559,83 @@ pub fn ANSI(comptime options: Options) type { HOST_WRITABLE = 2, pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try writer.writeAll(Self.RL_PROMPT_END_IGNORE_START_IGNORE ++ Self.CSI); + try writer.writeAll(Self.RL_PROMPT_START_IGNORE ++ Self.CSI); try std.fmt.formatInt(@intFromEnum(self), 10, .lower, .{}, writer); try writer.writeAll("$~" ++ Self.RL_PROMPT_END_IGNORE); } }; - pub const ScreenMode = struct {}; + pub const ScreenMode = struct { + mode: enum(u16) { + cursor_visible = 25, + alternate_screen = 1047, + alternate_screen_save_cursor_clear_enter = 1049, + bracketed_paste = 2004, + synchronized_output = 2026, + grapheme_cluster = 2027, + report_color_scheme = 2031, + }, + action: enum { + set, + reset, + }, + + pub fn write(self: @This(), writer: anytype) !void { + try writer.writeAll(Self.RL_PROMPT_START_IGNORE ++ Self.CSI ++ "?"); + try std.fmt.formatInt(@intFromEnum(self.mode), 10, .lower, .{}, writer); + try writer.writeAll(switch (self.action) { + .set => "h", + .reset => "l", + }); + try writer.writeAll(Self.RL_PROMPT_END_IGNORE); + } + + pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try self.write(writer); + } + }; + + pub const KittyGraphicsFormat = enum(u8) { + rgb = 24, + rgba = 32, + png = 100, + }; + + pub const KittyGraphicsMedium = + enum { + direct, + simple_file, + temporary_file, + shared_memory_object, + }; + + pub const KittyGraphicsData = struct { + format: KittyGraphicsFormat = .rgba, + medium: KittyGraphicsMedium = .direct, + width: ?u32, + height: ?u32, + compression: enum { + none, + zstd, + }, + size: ?u32, + offset: ?u32, + data: []const u8, + + // pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + // var first = false; + // const encoded_size = std.base64.standard.Encoder.calcSize(self.data); + // } + }; + + pub const TransmitKittyGraphicsData = struct { + image_id: ?u32, + image_number: ?u32, + placement_id: ?u32, + action: union { + transmit: KittyGraphicsData, + }, + }; }; }