From 692a28aed75e3f115071472999a2f870d8cb6556 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Thu, 25 Jun 2026 16:43:12 -0700 Subject: [PATCH] add placeInline for term_graphics, to be used for kitty charts --- src/commands/history.zig | 4 +--- src/term_graphics.zig | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/commands/history.zig b/src/commands/history.zig index 1de3074..cfae4d7 100644 --- a/src/commands/history.zig +++ b/src/commands/history.zig @@ -617,9 +617,7 @@ fn emitTimelineKitty( const rgb = try rendered.extractRgb(allocator); defer allocator.free(rgb); - try term_graphics.emitKittyRGB(out, allocator, rgb, dims.width, dims.height, cols, rows); - // Kitty leaves the cursor in place; advance below the placed image. - for (0..rows) |_| try out.writeByte('\n'); + try term_graphics.placeInline(out, allocator, rgb, dims.width, dims.height, cols, rows); } fn renderBraille( diff --git a/src/term_graphics.zig b/src/term_graphics.zig index efe01bd..79045be 100644 --- a/src/term_graphics.zig +++ b/src/term_graphics.zig @@ -97,6 +97,25 @@ pub fn emitKittyRGB( } } +/// Emit `rgb` as a kitty image (see `emitKittyRGB`), then advance the +/// cursor `rows` lines so following output lands directly below it. +/// Centralizes the pairing of the `C=1` (no cursor move) emission with +/// its matching vertical advance - callers should use this rather than +/// emitting + advancing by hand. +pub fn placeInline( + writer: *std.Io.Writer, + alloc: std.mem.Allocator, + rgb: []const u8, + width_px: u32, + height_px: u32, + cols: u16, + rows: u16, +) !void { + try emitKittyRGB(writer, alloc, rgb, width_px, height_px, cols, rows); + var i: u16 = 0; + while (i < rows) : (i += 1) try writer.writeByte('\n'); +} + // ── Tests ───────────────────────────────────────────────────────────── const testing = std.testing; @@ -158,3 +177,14 @@ test "emitKittyRGB: large payload is chunked with m=1 then a final m=0" { try testing.expect(std.mem.indexOf(u8, out, "\x1b_Gm=1;") != null); try testing.expect(std.mem.indexOf(u8, out, "\x1b_Gm=0;") != null); } + +test "placeInline appends exactly `rows` newlines after the image" { + const alloc = testing.allocator; + var aw: std.Io.Writer.Allocating = .init(alloc); + defer aw.deinit(); + const rgb = [_]u8{ 0, 0, 0, 0, 0, 0 }; // 2x1, base64 has no newlines + try placeInline(&aw.writer, alloc, &rgb, 2, 1, 4, 3); + const out = aw.written(); + try testing.expect(std.mem.endsWith(u8, out, "\x1b\\\n\n\n")); + try testing.expectEqual(@as(usize, 3), std.mem.count(u8, out, "\n")); +}