diff options
Diffstat (limited to 'src/builtins.zig')
| -rw-r--r-- | src/builtins.zig | 59 |
1 files changed, 55 insertions, 4 deletions
diff --git a/src/builtins.zig b/src/builtins.zig index 6688d05f..cb588ba3 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); } @@ -986,6 +1024,11 @@ pub const ClipReplace = struct { pub const Save = struct { pub const takes_arg = true; pub fn run(c: Ctx) void { + // A PDF or an image shows a file it does not hold as text: there + // is nothing of its own to write, bare or to a path. Said, never a + // silent success. + if (c.pane.file == null and !c.pane.isTerminal()) + return c.p.reportFailure(c.id, if (c.pane.hasPdf()) "Save: a PDF pane has no text to save" else "Save: an image pane has no text to save"); const path = std.mem.trim(u8, c.arg orelse "", " \t\r\n"); if (path.len > 0) return exec.saveTo(c.p, c.id, path); if (c.pane.file) |file| if (file.output == null) return exec.saveFile(c.p, c.id); @@ -1052,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; } }; @@ -1061,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); } @@ -1095,8 +1140,11 @@ pub const Edit = struct { } if (res.ops.len > 0) { const new = sam.apply(c.p.gpa, f.content, res.ops) catch |err| return c.p.reportError(c.id, "Edit", err); - panes.File.pushUndo(c.p, c.pane); - panes.File.setContent(c.p, f, new); + // `s/a/a/` changes nothing: no undo step, no new revision. + if (std.mem.eql(u8, new, f.content)) c.p.gpa.free(new) else { + panes.File.pushUndo(c.p, c.pane); + panes.File.setContent(c.p, f, new); + } } ninep_pane.setDot(c.pane, sam.moveDot(res.dot, res.ops)); if (res.printed.len > 0) _ = ninep_pane.appendErrors(c.p, c.id, res.printed); @@ -1271,6 +1319,9 @@ pub const Joincol = struct { // The keyboard's column joins the one to its right; the last has none. const col = c.p.keyboardColumn(c.p.active) orelse return; if (col + 1 >= c.p.ncol) return c.p.reportFailure(c.id, "Joincol: no column to the right"); + // As Newcol is refused: a column that could not give each pane its + // tag and 2 rows would leave a layout `size` refuses as it is. + if (!layout.joinFits(c.p, col)) return c.p.reportFailure(c.id, "Joincol: no space: the joined column cannot give each pane its tag and 2 rows"); layout.joinCol(c.p, col); } }; |
