From 42c0524b94d67caebbf71cd8a82d7690380195fa Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 27 Jul 2026 01:03:46 -0300 Subject: trim the taglines: image renderer toggles and Colors/Crt become leader-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The image tag spelled out `Petscii Ascii` and actOnSelection matched those words by hand before the builtin dispatch. They are three real Builtins now (SPC t p/l/a), so the by-word matching is gone, the tag is a plain `img Del`, and with nothing live left in it the image early-returns in curTail, enterTagEdit and the tag-click guard go too — an image tag edits like every other pane's. Colors and Crt leave the topbar for SPC t c / SPC t r: set-once display switches should not spend width in a bar you read every frame. NextColor stays, it is the one you cycle repeatedly. Both remain full builtins. The snap scripts that clicked those words now press the leader instead, and images.snap finally exercises the palette and ascii toggles for real: its old clicks at x=57 landed on the neighbouring shell's tag, so that toggle had never been covered. The test PPM grew to 512x512 with LCG noise so the ASCII bitmaps actually win cells and dropping them visibly changes the art. --- src/pardes.zig | 66 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 27 deletions(-) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index 0725e8ab..61174877 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -41,7 +41,7 @@ const scroll_off = 3; /// them for the vim reading. pub const swap_enter_tab = false; -pub const topbar_str = "Kill Newcol Tutor Debug Colors NextColor Crt Dump"; +pub const topbar_str = "Kill Newcol Tutor Debug NextColor Dump"; const pane_builtins_str = "Del"; const file_pane_builtins_str = "Save Del"; const pane_tail = " " ++ pane_builtins_str; @@ -64,6 +64,16 @@ const Builtin = enum { Colors, NextColor, Crt, + // the image pane's three renderer toggles. They used to be words the + // image tag printed and actOnSelection matched by hand; as builtins they + // are executable anywhere, pressable under SPC and listed by `SPC ?`, + // which is the whole reason the tag no longer carries them. Petscii picks + // glyph art over the host's pixels, Palette swaps the C64 palette for the + // terminal's own 16, Ascii adds the printable ASCII bitmaps to the + // matcher's glyph set. + Petscii, + Palette, + Ascii, Dump, Restore, Save, @@ -104,6 +114,11 @@ const leader_path = std.EnumArray(Builtin, []const u8).init(.{ .Colors = "tc", .NextColor = "tn", .Crt = "tr", + // the image toggles join the same `t` group; Palette takes `l` because + // `p` is Petscii's and `c` is Colors'. + .Petscii = "tp", + .Palette = "tl", + .Ascii = "ta", .Dump = "sd", .Restore = "sr", // the `w` window group `Save` vacated: the four directional focus moves @@ -157,6 +172,10 @@ const leader_rows: [@typeInfo(Builtin).@"enum".fields.len]Leader = blk: { // is where topbar clicks land, so its exact bytes are load-bearing (every // snapshot golden records the column each word starts at). Comptime-checked // against the enum so a rename cannot silently rot it. +// Colors and Crt are NOT here: both are set-once display switches you flip and +// forget, and a bar you read every frame should not spend width on them now +// that `SPC t c` / `SPC t r` press them. NextColor stays — it is the one you +// cycle repeatedly, so a click beats a three-key path. comptime { @setEvalBranchQuota(8000); // the tokenizer walks the string one branch per byte, stringToEnum builds a map per name var it = std.mem.tokenizeScalar(u8, topbar_str, ' '); @@ -1116,13 +1135,11 @@ pub const Pardes = struct { // ---- tag + selection text (chord sources) ---- - /// the live tag prefix: mode + cwd/path (image: the whole toggle tag) + /// the live tag prefix: mode + cwd/path (an image has only its path — the + /// renderer toggles it used to spell out are builtins now, under SPC t) fn tagPrefix(p: *Pardes, pane: *Pane) ![]u8 { const arena = p.scratch.allocator(); - if (pane.image) |iv| { - const mode = if (iv.pmode == .commodore) "C64" else "Term"; - return std.fmt.allocPrint(arena, "img {s} Petscii {s} Ascii {s}", .{ iv.path, mode, pane_builtins_str }); - } + if (pane.image) |iv| return std.fmt.allocPrint(arena, "img {s}", .{iv.path}); if (pane.file) |f| { const fmode = if (pane.mode == .insert) "INS" else "NOR"; return std.fmt.allocPrint(arena, "{s} {s}", .{ fmode, f.path }); @@ -1138,7 +1155,6 @@ pub const Pardes = struct { /// the editable tail: the user's edited buffer once touched, else defaults /// (an output buffer has no file to Save, so it gets the plain tail) fn curTail(pane: *Pane) []const u8 { - if (pane.image != null) return ""; if (pane.tag_init) return pane.tag_tail.items; if (pane.file) |f| if (!f.output) return file_pane_tail; return pane_tail; @@ -1147,7 +1163,6 @@ pub const Pardes = struct { /// focus the tag for editing, seeding the tail on first touch and placing /// the cursor under the click (a column within the text area) fn enterTagEdit(p: *Pardes, pane: *Pane, click_col: i32) void { - if (pane.image != null) return; // images keep a live toggle tag const pfx = p.tagPrefix(pane) catch return; if (!pane.tag_init) { pane.tag_tail.appendSlice(p.gpa, curTail(pane)) catch return; @@ -3908,7 +3923,7 @@ pub const Pardes = struct { // gutter scrollbar: a left click scrolls UP to that row p.active = id; pane.scrollBy(-(@as(i32, mrow) - @as(i32, r.y + BOX_H))); - } else if (mrow < r.y + BOX_H and pane.image == null) { + } else if (mrow < r.y + BOX_H) { // left click on the tag row: edit the tail in place p.active = id; p.enterTagEdit(pane, @as(i32, mcol) - @as(i32, r.x + GUTTER)); @@ -4572,22 +4587,6 @@ pub const Pardes = struct { fn actOnSelection(p: *Pardes, button: Mouse.Button, id: usize, txt: []const u8, arg: ?[]const u8) void { const pane = p.panes[id] orelse return; - // image tagline toggles (execute), before the builtin/run dispatch - if (button == .middle) if (pane.image) |*iv| { - const w = std.mem.trim(u8, txt, " \t\r\n"); - if (std.mem.eql(u8, w, "Petscii")) { - iv.petscii = !iv.petscii; - return; - } - if (std.mem.eql(u8, w, "C64") or std.mem.eql(u8, w, "Term")) { - iv.pmode = if (iv.pmode == .commodore) .terminal else .commodore; - return; - } - if (std.mem.eql(u8, w, "Ascii")) { - iv.ascii = !iv.ascii; - return; - } - }; if (button == .right) { var realbuf: [4096]u8 = undefined; var cwdbuf: [1024]u8 = undefined; @@ -4709,8 +4708,8 @@ pub const Pardes = struct { /// Run a builtin on pane `id`. `txt` is the executed text — only Restore /// reads it (for its optional path argument), so the leader passes "" and /// every builtin runs with zero arguments. The topbar ones are global; the - /// pane-scoped ones (Save, Del, Delcol, and the window group, which moves - /// focus relative to `id`) act on `id`. + /// pane-scoped ones (Save, Del, Delcol, the image toggles, and the window + /// group, which moves focus relative to `id`) act on `id`. fn runBuiltin(p: *Pardes, b: Builtin, id: usize, txt: []const u8) void { const pane = p.panes[id] orelse return; switch (b) { @@ -4718,6 +4717,19 @@ pub const Pardes = struct { .Colors => p.colors_on = !p.colors_on, .NextColor => p.theme_idx = (p.theme_idx + 1) % themes.len, .Crt => p.crt_on = !p.crt_on, + // the image toggles act on the pane they run in and are inert + // anywhere else, the way Save is on a terminal. Flipping the field + // is the whole toggle: drawImage re-matches the glyph grid when it + // sees grid_mode/grid_ascii disagree with the live ones. + .Petscii => if (pane.image) |*iv| { + iv.petscii = !iv.petscii; + }, + .Palette => if (pane.image) |*iv| { + iv.pmode = if (iv.pmode == .commodore) .terminal else .commodore; + }, + .Ascii => if (pane.image) |*iv| { + iv.ascii = !iv.ascii; + }, .Dump => p.dumpState() catch {}, .Help => p.openHelp(id, ""), .Restore => { -- cgit v1.3