summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/builtins.zig44
-rw-r--r--src/exec.zig6
-rw-r--r--src/ninep/ctl.zig30
3 files changed, 78 insertions, 2 deletions
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);
}
diff --git a/src/exec.zig b/src/exec.zig
index 5a421f19..215a4fd1 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -959,6 +959,12 @@ pub fn runBuiltinFrom(p: *Pardes, b: Builtin, id: usize, txt: []const u8, arg: ?
p.fs.unlogged = false;
pane.msg_announcement = true;
}
+ // A word for one kind of pane, on another: refused with why, not a
+ // silent nothing (Palette on a file, Filter on a PDF).
+ if (builtins.registry.needs(b)) |kind| if (p.panes[id] == null or !kind.has(pane)) {
+ var said: [96]u8 = undefined;
+ return p.reportFailure(id, std.fmt.bufPrint(&said, "{s}: only {s} pane takes it", .{ @tagName(b), kind.noun() }) catch "only another kind of pane takes it");
+ };
const c: builtins.Ctx = .{ .p = p, .pane = pane, .id = id, .txt = txt, .arg = arg, .from_body = from_body };
builtins.registry.dispatch(b, c);
}
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index a0a363b5..902a9647 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -1671,6 +1671,36 @@ test "Save and Save path on an image pane are refused with why, never a silent s
}
}
+test "a builtin for one kind of pane is refused on another, and still acts on its own kind" {
+ const gpa = testing.allocator;
+ const p = try withFile(gpa, "text\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const cases = [_]struct { []const u8, []const u8 }{
+ .{ "Petscii\n", "Petscii: only an image 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" },
+ .{ "Togglettymode\n", "Togglettymode: only a terminal pane takes it" },
+ };
+ for (cases) |case| {
+ const r = wr(p, Node.of(serial, .ctl), case[0]);
+ try testing.expectEqual(Status.err, r.reply.status);
+ try testing.expectEqualStrings(case[1], r.reply.ename);
+ }
+ if (pardes.pdf_enabled) for ([_][]const u8{ "PdfFit\n", "PdfTint\n", "PdfSections\n" }) |line| {
+ try testing.expectEqual(Status.err, wr(p, Node.of(serial, .ctl), line).reply.status);
+ };
+ const id = p.freeSlot().?;
+ const doc = try p.newDocPane(id);
+ doc.image = .{ .path = try p.gpa.dupe(u8, "/tmp/pardes-pic.ppm") };
+ const r = wr(p, Node.of(doc.serial, .ctl), "Mode\n");
+ try testing.expectEqualStrings("Mode: only a text or terminal pane has modes", r.reply.ename);
+ const glyphs = doc.image.?.glyph_art;
+ try testing.expectEqual(Status.ok, wr(p, Node.of(doc.serial, .ctl), "Petscii\n").reply.status);
+ try testing.expect(doc.image.?.glyph_art != glyphs);
+}
+
test "exec Save writes the named file through the host" {
const gpa = testing.allocator;
const p = try withFile(gpa, "one\ntwo\n");