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 | |
| 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')
| -rw-r--r-- | src/TerminalImages.zig | 100 | ||||
| -rw-r--r-- | src/builtins.zig | 11 | ||||
| -rw-r--r-- | src/config.zig | 11 | ||||
| -rw-r--r-- | src/image.zig | 21 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 2 | ||||
| -rw-r--r-- | src/pardes.zig | 1 | ||||
| -rw-r--r-- | src/tagline.zig | 4 | ||||
| -rw-r--r-- | src/terminal.zig | 105 |
8 files changed, 221 insertions, 34 deletions
diff --git a/src/TerminalImages.zig b/src/TerminalImages.zig index 773297b2..bc1b2ea8 100644 --- a/src/TerminalImages.zig +++ b/src/TerminalImages.zig @@ -2,8 +2,8 @@ //! previews, `kitten icat`) drawn where it placed them. ghostty-vt keeps //! the images and placements; this reads them each frame, cuts each one //! into the cell rows it covers, clipped to the rows the pane shows, and -//! hands them on as pixels (`ImagePlace`, exact geometry) to a shell that -//! draws them. +//! hands them on: as pixels (`ImagePlace`, exact geometry) to a shell that +//! draws them, else as glyph art in the cells, as an image pane's Petscii. //! Both kinds of placement are drawn: one on a cell (`a=T`/`a=p`), which //! moves with the text it was put on, and a virtual one (`U=1`) shown //! through the U+10EEEE placeholder cells the program prints. @@ -14,17 +14,25 @@ const image = @import("image.zig"); const TerminalImages = @This(); const gfx = ghostty_vt.kitty.graphics; +const GlyphArt = image.GlyphArt; const PixelRect = image.PixelRect; /// The bytes ghostty-vt may hold for one screen's images (it evicts the /// oldest past this): a 4K picture in RGBA, with room for a second. pub const storage_limit = 64 * 1024 * 1024; +/// This pane draws its images as glyph art even where pixels could be +/// drawn (the Petscii builtin; TermImages sets it for a new pane). +petscii: bool = false, /// Pixels in RGBA for images ghostty-vt holds in another format (RGB, /// grey): only for images on screen, dropped the frame they are not. converted: std.AutoHashMapUnmanaged(u32, Converted) = .empty, +/// Glyph art for the fragments drawn as cells, kept while on screen. +glyphs: std.AutoHashMapUnmanaged(GlyphKey, Glyphs) = .empty, const Converted = struct { generation: u64, rgba: []u8, used: bool = true }; +const Glyphs = struct { cells: []GlyphArt.Cell, used: bool = true }; +const GlyphKey = struct { id: u32, generation: u64, src: PixelRect, cols: u16 }; /// One image's rectangle in the pane, in pixels from the viewport's top /// left corner (above or left of it is negative). @@ -34,6 +42,9 @@ pub fn deinit(ti: *TerminalImages, gpa: std.mem.Allocator) void { var conv = ti.converted.valueIterator(); while (conv.next()) |c| gpa.free(c.rgba); ti.converted.deinit(gpa); + var gl = ti.glyphs.valueIterator(); + while (gl.next()) |g| gpa.free(g.cells); + ti.glyphs.deinit(gpa); } /// Accept PNG (`f=100`) as well as raw pixels: ghostty-vt built as a @@ -64,6 +75,8 @@ pub fn draw( ) void { var conv = ti.converted.valueIterator(); while (conv.next()) |c| c.used = false; + var gl = ti.glyphs.valueIterator(); + while (gl.next()) |g| g.used = false; defer ti.prune(p.gpa); const screen = vt.screens.active; @@ -73,7 +86,8 @@ pub fn draw( const bottom = pages.getBottomRight(.viewport) orelse return; const cw: u32 = @max(1, p.cell_pixels.w); const ch: u32 = @max(1, p.cell_pixels.h); - const ctx: Ctx = .{ .ti = ti, .p = p, .pane = pane_id, .serial = serial, .tx = tx, .body_y = body_y, .cols = cols, .rows_of = rows_of, .cw = cw, .ch = ch }; + const pixels = p.native_images and !ti.petscii; + const ctx: Ctx = .{ .ti = ti, .p = p, .s = s, .pane = pane_id, .serial = serial, .tx = tx, .body_y = body_y, .cols = cols, .rows_of = rows_of, .cw = cw, .ch = ch, .pixels = pixels }; // Virtual placements: shown through the placeholder cells, one run of // them on a row at a time. The placeholders themselves are not text. @@ -130,6 +144,7 @@ pub fn draw( const Ctx = struct { ti: *TerminalImages, p: *pardes.Pardes, + s: *pardes.Surface, pane: u8, serial: u32, tx: u16, @@ -138,6 +153,7 @@ const Ctx = struct { rows_of: []const ?u16, cw: u32, ch: u32, + pixels: bool, /// Draw `src` of `img` over `dst`, one cell row of it at a time: a row /// is shown, or not, or shown elsewhere, as the body lays it out. @@ -165,27 +181,37 @@ const Ctx = struct { }; const c0: u16 = @intCast(@divFloor(x0, cw)); const c1: u16 = @intCast(@divFloor(x1 + cw - 1, cw)); - _ = ctx.p.appendImagePlace(.{ - .pane = ctx.pane, - .serial = ctx.serial, - .native = .{ - .page = img.id, - .revision = @truncate(img.generation), - .exact = true, - .geometry = .{ .src = piece, .dst = .{ - .x = @intCast(x0 - @as(i64, c0) * cw), - .y = @intCast(y0 - v * ch), - .w = @intCast(x1 - x0), - .h = @intCast(y1 - y0), - } }, - }, - .x = ctx.tx + c0, - .y = ctx.body_y + row, - .w = c1 - c0, - .h = 1, - .rgba = rgba, - .iw = img.width, - .ih = img.height, + if (ctx.pixels) { + _ = ctx.p.appendImagePlace(.{ + .pane = ctx.pane, + .serial = ctx.serial, + .native = .{ + .page = img.id, + .revision = @truncate(img.generation), + .exact = true, + .geometry = .{ .src = piece, .dst = .{ + .x = @intCast(x0 - @as(i64, c0) * cw), + .y = @intCast(y0 - v * ch), + .w = @intCast(x1 - x0), + .h = @intCast(y1 - y0), + } }, + }, + .x = ctx.tx + c0, + .y = ctx.body_y + row, + .w = c1 - c0, + .h = 1, + .rgba = rgba, + .iw = img.width, + .ih = img.height, + }); + continue; + } + // Glyph art fills whole cells: a sliver of a row is left out. + if ((y1 - y0) * 2 < ch) continue; + const cells = ctx.ti.glyphsOf(ctx.p.gpa, img, rgba, piece, c1 - c0) orelse continue; + for (cells, c0..) |*cell, x| ctx.s.set(ctx.tx + @as(u16, @intCast(x)), ctx.body_y + row, cell.glyph[0..cell.glyph_len], .{ + .fg = .{ .rgb = GlyphArt.commodore[cell.fg] }, + .bg = .{ .rgb = GlyphArt.commodore[cell.bg] }, }); } } @@ -234,6 +260,24 @@ fn convert(gpa: std.mem.Allocator, img: gfx.Image, n: usize) ?[]u8 { return out; } +fn glyphsOf(ti: *TerminalImages, gpa: std.mem.Allocator, img: gfx.Image, rgba: []const u8, piece: PixelRect, cols: u16) ?[]const GlyphArt.Cell { + const key: GlyphKey = .{ .id = img.id, .generation = img.generation, .src = piece, .cols = cols }; + if (ti.glyphs.getPtr(key)) |hit| { + hit.used = true; + return hit.cells; + } + const grid = GlyphArt.renderRect(gpa, rgba, img.width, img.height, piece, cols, 1, GlyphArt.commodore, true) catch return null; + if (grid.cells.len != cols) { + gpa.free(grid.cells); + return null; + } + ti.glyphs.put(gpa, key, .{ .cells = grid.cells }) catch { + gpa.free(grid.cells); + return null; + }; + return grid.cells; +} + /// Drop what this frame did not draw: the caches hold only what is shown. fn prune(ti: *TerminalImages, gpa: std.mem.Allocator) void { while (true) { @@ -244,4 +288,12 @@ fn prune(ti: *TerminalImages, gpa: std.mem.Allocator) void { gpa.free(ti.converted.get(stale).?.rgba); _ = ti.converted.remove(stale); } + while (true) { + var gl = ti.glyphs.iterator(); + const stale = while (gl.next()) |entry| { + if (!entry.value_ptr.used) break entry.key_ptr.*; + } else break; + gpa.free(ti.glyphs.get(stale).?.cells); + _ = ti.glyphs.remove(stale); + } } diff --git a/src/builtins.zig b/src/builtins.zig index fb6aa4cf..f51409aa 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -43,12 +43,16 @@ pub const PaneKind = enum { terminal, pdf, image, + /// An image pane, or a terminal, whose program's kitty graphics are + /// pictures too. + picture, pub fn has(kind: PaneKind, pane: *const panes.Pane) bool { return switch (kind) { .terminal => pane.isTerminal(), .pdf => pane.hasPdf(), .image => pane.image != null, + .picture => pane.image != null or pane.isTerminal(), }; } @@ -57,6 +61,7 @@ pub const PaneKind = enum { .terminal => "a terminal", .pdf => "a PDF", .image => "an image", + .picture => "an image or a terminal", }; } }; @@ -981,11 +986,13 @@ pub const PdfSections = struct { } }; -/// glyph art over the host's pixels +/// glyph art over the host's pixels: an image pane's picture, or the +/// kitty graphics a terminal's program placed (yazi's previews) pub const Petscii = struct { - pub const needs: PaneKind = .image; + pub const needs: PaneKind = .picture; pub fn run(c: Ctx) void { if (c.pane.image) |*state| panes.Image.toggleGlyphArt(state); + panes.terminal.togglePetscii(c.pane); } }; diff --git a/src/config.zig b/src/config.zig index 5a9cf070..b6eb4520 100644 --- a/src/config.zig +++ b/src/config.zig @@ -82,6 +82,7 @@ pub const leader_path = paths: { .BootShell = null, .LookWord = null, .Pager = null, + .TermImages = null, .Debug = "td", .Msg = null, .Colors = "tc", @@ -759,14 +760,19 @@ pub const Runtime = struct { /// PAGER and GIT_PAGER set to `pardes -`, a +Pager pane, where the /// user's environment set neither; `off`, the environment as it is. pager: Pager = .pardes, + /// How a new terminal pane draws the kitty graphics its program sends + /// (yazi's previews): `real`, as pixels where the shell can, or + /// `petscii`, as glyph art. The Petscii builtin flips one pane. + term_images: TermImages = .real, pub const ShaderAnimation = enum { off, on, always }; pub const Placement = enum { acme, pardes }; pub const BootShell = enum { keep, replace }; pub const LookWord = enum { search, list }; pub const Pager = enum { pardes, off }; + pub const TermImages = enum { real, petscii }; /// A setting that is one of a few words, each an enum field of Runtime. - pub const Choice = enum { placement, boot_shell, look_word, pager }; + pub const Choice = enum { placement, boot_shell, look_word, pager, term_images }; /// shadow: a soft drop shadow. rim: a light hairline along the top of /// the focused pane's tag rows. auto: a shadow on a light page; on a @@ -1032,6 +1038,7 @@ pub const Runtime = struct { .{ .word = "BootShell", .action = .{ .choice = .boot_shell }, .doc = "A document dropped where a lone untouched shell stands: keep the shell, or replace it." }, .{ .word = "LookWord", .action = .{ .choice = .look_word }, .doc = "A plain word looked at: search selects its next place in the pane, list lists every place in a +Search." }, .{ .word = "Pager", .action = .{ .choice = .pager }, .doc = "What a terminal's commands page through: pardes, a +Pager pane (pardes -), or off, the environment's own; for terminals started after it." }, + .{ .word = "TermImages", .action = .{ .choice = .term_images }, .doc = "How a new terminal draws its program's kitty graphics: real, as pixels where it can, or petscii, as glyph art." }, }; pub fn find(name: []const u8) ?Setting { @@ -1707,6 +1714,7 @@ pub const Runtime = struct { .boot_shell => "replace", .look_word => "list", .pager => "off", + .term_images => "petscii", }, .lift => "rim", .motion => "playful", @@ -1777,6 +1785,7 @@ pub const Runtime = struct { .boot_shell => "BootShell replace\n", .look_word => "LookWord list\n", .pager => "Pager off\n", + .term_images => "TermImages petscii\n", }, .lift => "Lift rim\n", .motion => "Motion playful\n", diff --git a/src/image.zig b/src/image.zig index 936fe19a..3a3f1932 100644 --- a/src/image.zig +++ b/src/image.zig @@ -305,7 +305,6 @@ pub const GlyphArt = struct { pub fn render(gpa: std.mem.Allocator, rgba: []const u8, iw: usize, ih: usize, max_cols: usize, max_rows: usize, pal: Palette, ascii: bool) !Grid { if (iw == 0 or ih == 0 or max_cols == 0 or max_rows == 0) return .{ .cells = try gpa.alloc(Cell, 0), .cols = 0, .rows = 0 }; - const gset: []const Glyph = if (ascii) &glyphs_ascii else &glyphs; // contain-fit; cells are ~twice as tall as wide, so a row spans 2 width-units. var cols = max_cols; var rows = (max_cols * ih) / (2 * iw); @@ -315,16 +314,28 @@ pub const GlyphArt = struct { } cols = std.math.clamp(cols, 1, max_cols); rows = std.math.clamp(rows, 1, max_rows); + return renderRect(gpa, rgba, iw, ih, .{ .w = @intCast(iw), .h = @intCast(ih) }, cols, rows, pal, ascii); + } + /// The pixels of `src` (a rectangle of the `iw` x `ih` image) laid + /// over exactly `cols` x `rows` cells, whatever their shape: a + /// terminal's image fragment is already the size its cells are. + pub fn renderRect(gpa: std.mem.Allocator, rgba: []const u8, iw: usize, ih: usize, src: PixelRect, cols: usize, rows: usize, pal: Palette, ascii: bool) !Grid { + const x0: usize = @min(src.x, iw); + const y0: usize = @min(src.y, ih); + const sw: usize = @min(src.w, iw - x0); + const sh: usize = @min(src.h, ih - y0); + if (sw == 0 or sh == 0 or cols == 0 or rows == 0) return .{ .cells = try gpa.alloc(Cell, 0), .cols = 0, .rows = 0 }; + const gset: []const Glyph = if (ascii) &glyphs_ascii else &glyphs; const cells = try gpa.alloc(Cell, cols * rows); var cy: usize = 0; while (cy < rows) : (cy += 1) { - const ry0 = cy * ih / rows; - const ry1 = @max(ry0 + 1, (cy + 1) * ih / rows); + const ry0 = y0 + cy * sh / rows; + const ry1 = @max(ry0 + 1, y0 + (cy + 1) * sh / rows); var cx: usize = 0; while (cx < cols) : (cx += 1) { - const rx0 = cx * iw / cols; - const rx1 = @max(rx0 + 1, (cx + 1) * iw / cols); + const rx0 = x0 + cx * sw / cols; + const rx1 = @max(rx0 + 1, x0 + (cx + 1) * sw / cols); var cell: [64][3]u8 = undefined; var sy: usize = 0; while (sy < 8) : (sy += 1) { diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 87339b70..cdb6092a 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -1737,7 +1737,7 @@ test "a builtin for one kind of pane is refused on another, and still acts on it defer p.deinit(); const serial = serialOf(p); const cases = [_]struct { []const u8, []const u8 }{ - .{ "Petscii\n", "Petscii: only an image pane takes it" }, + .{ "Petscii\n", "Petscii: only an image or a terminal pane takes it" }, .{ "Palette\n", "Palette: only an image pane takes it" }, .{ "Ascii\n", "Ascii: only an image pane takes it" }, .{ "Filter\n", "Filter: only a terminal pane takes it" }, diff --git a/src/pardes.zig b/src/pardes.zig index aa2c252c..84b891e5 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -4976,6 +4976,7 @@ pub const Pardes = struct { pane.serial = p.next_serial; p.rects[id] = .{}; p.panes[id] = pane; + panes.terminal.adoptSettings(p, pane); // /log hears of it once the update that made it ends, when it has // its file, name and place (`ctlfs.events.announce`). pane.fs.unannounced = true; diff --git a/src/tagline.zig b/src/tagline.zig index 9bd49eef..032fb5a0 100644 --- a/src/tagline.zig +++ b/src/tagline.zig @@ -124,6 +124,10 @@ pub fn pathPrefix(arena: std.mem.Allocator, pane: *Pane) ![]const u8 { // Unsaved text shows on the grip, as acme's modbutton does, not as a // mark in the text: the prefix is the name alone. if (pane.file) |f| return if (pane.prompt == .name) pane.input.own orelse f.path else f.path; + // A terminal showing kitty graphics says how it draws them, as an image + // pane does, and so does one drawing them as glyph art by choice. + if (panes.terminal.petscii(pane)) |on| if (on or panes.terminal.hasImages(pane)) + return std.fmt.allocPrint(arena, "{s} petscii:{s}", .{ (try shownDir(arena, pane)), if (on) "on" else "off" }); return (try shownDir(arena, pane)); } 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; |
