From 109a558a0488f3269f1864985e9b3db5f58b9552 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 19:23:38 -0300 Subject: A builtin for one kind of pane is refused on another with why: Petscii, Palette, Ascii, Filter, Togglettymode, the Pdf words, Mode and TreeContext Each of these did nothing, silently, on the wrong kind of pane, so a ctl or exec write of Palette to a text file answered ok as if it had acted. A builtin now declares the one kind it is for (needs), the registry reads it, and runBuiltinFrom refuses the rest before dispatch, as Repl and Edit already refused theirs. Mode and TreeContext, which take more than one kind, say their own refusal. Co-Authored-By: Claude Opus 5.5 --- src/builtins.zig | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) (limited to 'src/builtins.zig') diff --git a/src/builtins.zig b/src/builtins.zig index 4d257a9e..edeb7ac8 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -38,6 +38,29 @@ pub const capabilities: config.Runtime.Capabilities = .{ /// ctl; pardes's root `ctl` takes the session builtins and a pane's `ctl` /// the pane ones. A builtin says `pub const scope: Scope = .session;`, /// and every setting is one; the rest act on their pane. +/// The kinds of pane a builtin may be for alone (registry.needs). +pub const PaneKind = enum { + terminal, + pdf, + image, + + pub fn has(kind: PaneKind, pane: *const panes.Pane) bool { + return switch (kind) { + .terminal => pane.isTerminal(), + .pdf => pane.hasPdf(), + .image => pane.image != null, + }; + } + + pub fn noun(kind: PaneKind) []const u8 { + return switch (kind) { + .terminal => "a terminal", + .pdf => "a PDF", + .image => "an image", + }; + } +}; + pub const Scope = enum { pane, session }; pub const Ctx = struct { @@ -181,6 +204,15 @@ pub const registry = struct { /// `Msg`, a setting's value), or would ask for it at a prompt (`Find`): /// a ctl refuses it bare before any line of the write runs. A builtin /// says `pub const requires_arg = true;` beside `takes_arg`. + /// The one kind of pane a builtin means something on, or null for any: + /// it says `pub const needs: PaneKind = .terminal;`, and on any other + /// pane it is refused (exec.runBuiltinFrom), as Repl and Edit are. + pub fn needs(b: Builtin()) ?PaneKind { + inline for (manualBuiltinList(), 0..) |T, i| + if (@intFromEnum(b) == i) return if (@hasDecl(T, "needs")) T.needs else null; + return null; + } + pub fn requiresArg(b: Builtin()) bool { inline for (manualBuiltinList(), 0..) |T, i| if (@intFromEnum(b) == i) return @hasDecl(T, "requires_arg") and T.requires_arg; @@ -869,7 +901,7 @@ pub const TreeContext = struct { pub fn run(c: Ctx) void { if (comptime !enabled) unreachable; - if (!panes.File.supportsContext(c.pane)) return; + if (!panes.File.supportsContext(c.pane)) return c.p.reportFailure(c.id, "TreeContext: only a source file the parser knows has context"); const file = &c.pane.file.?; const arg = std.mem.trim(u8, c.arg orelse "", " \t\r\n"); const on = if (arg.len == 0) !file.tree_context else if (std.mem.eql(u8, arg, "on")) true else if (std.mem.eql(u8, arg, "off")) false else { @@ -889,6 +921,7 @@ pub const TreeContext = struct { /// Fit the PDF page to the pane by width or by height, flipping between them. pub const PdfFit = struct { + pub const needs: PaneKind = .pdf; pub const enabled = pardes.pdf_enabled; pub fn run(c: Ctx) void { if (comptime enabled) apply(c) else unreachable; @@ -900,6 +933,7 @@ pub const PdfFit = struct { /// Tint the PDF page to the theme, or show it as printed, flipping between them. pub const PdfTint = struct { + pub const needs: PaneKind = .pdf; pub const enabled = pardes.pdf_enabled; pub fn run(c: Ctx) void { if (comptime enabled) apply(c) else unreachable; @@ -911,6 +945,7 @@ pub const PdfTint = struct { /// List the PDF's sections in a pane, each a place to go. pub const PdfSections = struct { + pub const needs: PaneKind = .pdf; pub const output: OutputTraits = .{ .name = config.pdf_sections_buffer, .steps = true, .locations = true }; pub const enabled = pardes.pdf_enabled; pub fn run(c: Ctx) void { @@ -923,6 +958,7 @@ pub const PdfSections = struct { /// glyph art over the host's pixels pub const Petscii = struct { + pub const needs: PaneKind = .image; pub fn run(c: Ctx) void { if (c.pane.image) |*state| panes.Image.toggleGlyphArt(state); } @@ -930,6 +966,7 @@ pub const Petscii = struct { /// the C64 palette or the terminal's own 16 pub const Palette = struct { + pub const needs: PaneKind = .image; pub fn run(c: Ctx) void { if (c.pane.image) |*state| panes.Image.togglePalette(state); } @@ -937,6 +974,7 @@ pub const Palette = struct { /// add the printable ASCII bitmaps to the matcher's glyph set pub const Ascii = struct { + pub const needs: PaneKind = .image; pub fn run(c: Ctx) void { if (c.pane.image) |*state| panes.Image.toggleAscii(state); } @@ -1057,8 +1095,8 @@ pub const Del = struct { /// Recolour a terminal's program colours to the theme, or show them as sent, flipping between them. pub const Filter = struct { + pub const needs: PaneKind = .terminal; pub fn run(c: Ctx) void { - if (!c.pane.isTerminal()) return; c.pane.tty_filter = !c.pane.tty_filter; } }; @@ -1066,12 +1104,14 @@ pub const Filter = struct { /// Cycle the pane's input modes: terminals add raw tty mode to normal/insert. pub const Mode = struct { pub fn run(c: Ctx) void { + if (c.pane.file == null and !c.pane.isTerminal()) return c.p.reportFailure(c.id, "Mode: only a text or terminal pane has modes"); c.p.cycleMode(c.id); } }; /// Switch this terminal between raw program input and normal editor mode. pub const Togglettymode = struct { + pub const needs: PaneKind = .terminal; pub fn run(c: Ctx) void { c.p.toggleTty(c.id); } -- cgit v1.3