summaryrefslogtreecommitdiff
path: root/src/builtins.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/builtins.zig')
-rw-r--r--src/builtins.zig59
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);
}
};