diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 00:45:14 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:53:56 -0300 |
| commit | 0bb65902ebec104dac954b59f28500812419e1af (patch) | |
| tree | 1c2d756ce89fcf6059bacf62928804da5585d36b /src/terminal.zig | |
| parent | 28c74918cde4092d964daa847c026ed3de282683 (diff) | |
| download | pardes-0bb65902ebec104dac954b59f28500812419e1af.tar.gz pardes-0bb65902ebec104dac954b59f28500812419e1af.zip | |
A terminal's kitty graphics draw as glyph art where pixels cannot, and by choice: Petscii on the pane, TermImages for new ones
Under an outer terminal without kitty graphics a tty pardes cannot pass a
terminal pane's images on, so it draws them itself as an image pane's
Petscii does: each row of an image is matched to C64-palette glyph art
over the cells it covers (GlyphArt.renderRect, the exact-grid half of
GlyphArt.render), cached while it is on screen. yazi is still told the
kitty protocol works, so it keeps sending its previews.
The same art is a choice anywhere: the Petscii builtin now takes a
terminal pane too (PaneKind.picture), flipping its images between pixels
and glyphs, and a terminal holding images, or drawing them as glyphs by
choice, says petscii:on|off in its tag as an image pane does. TermImages
real|petscii (default real) is what a new terminal starts with; it is in
DumpConfig, /ctl and the reference.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/terminal.zig')
| -rw-r--r-- | src/terminal.zig | 105 |
1 files changed, 104 insertions, 1 deletions
diff --git a/src/terminal.zig b/src/terminal.zig index e6304c0a..0850267e 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -9,6 +9,7 @@ const Surface = pardes.Surface; const modal = @import("modal.zig"); const tracy = @import("tracy.zig"); const dump = @import("dump.zig"); +const tagline = @import("tagline.zig"); const Key = pardes.Key; const Pane = panes.Pane; const EditText = panes.EditText; @@ -2797,6 +2798,36 @@ fn syncPixels(p: *const Pardes, pane: *Pane) void { vt.height_px = @as(u32, vt.rows) * p.cell_pixels.h; } +/// The Petscii builtin on a terminal: its kitty graphics as glyph art, +/// or back to pixels where the shell draws pixels. +pub fn togglePetscii(pane: *Pane) void { + if (comptime !images_enabled) return; + const state = pane.terminal orelse return; + state.images.petscii = !state.images.petscii; +} + +/// Whether the pane draws its kitty graphics as glyph art by choice; null +/// for a pane with no emulator. +pub fn petscii(pane: *const Pane) ?bool { + if (comptime !images_enabled) return null; + const state = pane.terminal orelse return null; + return state.images.petscii; +} + +/// A new terminal takes TermImages: pixels, or glyph art. +pub fn adoptSettings(p: *const Pardes, pane: *Pane) void { + if (comptime !images_enabled) return; + const state = pane.terminal orelse return; + state.images.petscii = p.settings.term_images == .petscii; +} + +/// Whether the screen showing holds any kitty graphics image. +pub fn hasImages(pane: *const Pane) bool { + if (comptime !images_enabled) return false; + const state = pane.terminal orelse return false; + return state.vt.screens.active.kitty_images.images.count() > 0; +} + /// Draw the pane's kitty graphics images over its body rows (after the /// rows and their colours: placeholder cells are blanked here). pub fn drawImages(p: *Pardes, s: *Surface, pane: *Pane, id: usize, tx: u16, tw: u16, body_y: u16, body_h: u16) void { @@ -2828,7 +2859,7 @@ pub fn drawImages(p: *Pardes, s: *Surface, pane: *Pane, id: usize, tx: u16, tw: } // Nothing to draw, no placeholder to blank and nothing cached to let // go: the common case costs the rows' flags. - if (!any_placeholder and screen.kitty_images.placements.count() == 0 and state.images.converted.count() == 0) return; + if (!any_placeholder and screen.kitty_images.placements.count() == 0 and state.images.converted.count() == 0 and state.images.glyphs.count() == 0) return; state.images.draw(p, s, &state.vt, @intCast(id), pane.serial, tx, body_y, @min(tw, pane.cols), rows_of[0..vp_rows]); } @@ -2982,6 +3013,78 @@ test "a virtual kitty placement shows through its placeholder cells, which draw try std.testing.expectEqualStrings("X", s.at(tx + 4, y).grapheme()); } +test "without pixels, or by the Petscii builtin, a terminal's image is glyph art in its cells" { + if (comptime !images_enabled) return error.SkipZigTest; + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .cols = 80, .rows = 24, .shells = 1 }); + defer p.deinit(); + p.update(.{ .resize = .{ .cols = 80, .rows = 24, .cell_pixels = .{ .w = 10, .h = 20 } } }); + var frame = std.heap.ArenaAllocator.init(gpa); + defer frame.deinit(); + const pane = p.panes[0].?; + pane.body.mode = .tty; + p.update(.{ .output = .{ .pane = 0, .bytes = kittyTransmit("c=2,r=1") } }); + const r = p.rects[0]; + const tx = r.x + config.GUTTER; + const y = p.bodyTop(pane, r); + const commodore = pardes.image.GlyphArt.commodore; + const painted = struct { + fn check(cell: *const pardes.Cell) bool { + const bg = switch (cell.style.bg) { + .rgb => |rgb| rgb, + else => return false, + }; + for (commodore) |c| if (std.mem.eql(u8, &c, &bg)) return true; + return false; + } + }.check; + + // A shell that draws no pixels (a tty under no kitty graphics): forced. + p.native_images = false; + var s = try p.render(frame.allocator()); + try std.testing.expectEqual(@as(usize, 0), imagePlaces(s).len); + try std.testing.expect(painted(s.at(tx, y)) and painted(s.at(tx + 1, y))); + try std.testing.expect(!painted(s.at(tx + 2, y))); + // The tag says so, as an image pane's does. + try std.testing.expect(std.mem.indexOf(u8, try tagline.tagPrefix(p, pane), " petscii:off") != null); + + // Pixels where they can be drawn; the builtin flips the pane to glyphs + // and back. + p.native_images = true; + _ = frame.reset(.retain_capacity); + s = try p.render(frame.allocator()); + try std.testing.expectEqual(@as(usize, 1), imagePlaces(s).len); + try std.testing.expect(p.executeBuiltinLine(0, "Petscii")); + _ = frame.reset(.retain_capacity); + s = try p.render(frame.allocator()); + try std.testing.expectEqual(@as(usize, 0), imagePlaces(s).len); + try std.testing.expect(painted(s.at(tx, y))); + try std.testing.expect(std.mem.indexOf(u8, try tagline.tagPrefix(p, pane), " petscii:on") != null); + try std.testing.expect(p.executeBuiltinLine(0, "Petscii")); + _ = frame.reset(.retain_capacity); + s = try p.render(frame.allocator()); + try std.testing.expectEqual(@as(usize, 1), imagePlaces(s).len); +} + +test "TermImages is real by default, and petscii makes new terminals draw glyph art" { + if (comptime !images_enabled) return error.SkipZigTest; + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .cols = 80, .rows = 24, .shells = 1 }); + defer p.deinit(); + try std.testing.expectEqual(config.Runtime.TermImages.real, p.settings.term_images); + try std.testing.expectEqual(false, petscii(p.panes[0].?).?); + // A pane without images says nothing of them. + try std.testing.expect(std.mem.indexOf(u8, try tagline.tagPrefix(p, p.panes[0].?), "petscii") == null); + try std.testing.expect(p.executeBuiltinLine(0, "TermImages petscii")); + try std.testing.expectEqual(config.Runtime.TermImages.petscii, p.settings.term_images); + // The setting is for new panes: the one there keeps its choice. + try std.testing.expectEqual(false, petscii(p.panes[0].?).?); + const id = p.freeSlot().?; + const fresh = try p.newShell(id, ""); + try std.testing.expectEqual(true, petscii(fresh).?); + try std.testing.expect(std.mem.indexOf(u8, try tagline.tagPrefix(p, fresh), " petscii:on") != null); +} + test "a program asking the cell size in pixels (CSI 16 t) is told the shell's" { if (comptime !images_enabled) return error.SkipZigTest; const gpa = std.testing.allocator; |
