summaryrefslogtreecommitdiff
path: root/src/builtins.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 09:41:55 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commitf5eaddb2cf28a9b72d5dd5b3b10502a316d4de85 (patch)
tree3d043946453c8da589ef17eef1ebc66f2d2c1b24 /src/builtins.zig
parent0ee8d748c028ad5257f95e15db38646aecd2851b (diff)
downloadpardes-f5eaddb2cf28a9b72d5dd5b3b10502a316d4de85.tar.gz
pardes-f5eaddb2cf28a9b72d5dd5b3b10502a316d4de85.zip
Split control messages by scope: the root ctl takes the session's builtins and reads the settings, a pane's ctl its own
Every builtin could only be clicked, or written to exec, and the settings could be read only as the Config window's prose. acme keeps window verbs on a window's ctl, and webfs and upas/fs keep session settings on a root ctl. Each builtin now declares its scope (scope = .session; settings are all session, the rest pane), read by the registry. The root /ctl takes session builtins and reads every setting in the words a write takes, so its read written back changes nothing (panel and scene effects now take on/off like the toggles, to make that true); a pane's ctl takes the pane's builtins beside get, lock and unlock. Writes are checked whole and refused in Plan 9's ctl words (unknown control message "X", wrong #args ...), which 9ns now maps to EINVAL (cloud9 re-pinned at a8c7a715). A builtin that would prompt for its argument fails the write instead, and a refusal is answered at once, not after the frame. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/builtins.zig')
-rw-r--r--src/builtins.zig59
1 files changed, 56 insertions, 3 deletions
diff --git a/src/builtins.zig b/src/builtins.zig
index ffb00ff6..ed0a07dd 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -30,6 +30,14 @@ pub const capabilities: config.Runtime.Capabilities = .{
.ligatures = pardes.platform == .gui,
};
+/// Where a builtin acts: at the pane it runs from, or on the editor as a
+/// whole. acme keeps its window verbs on a window's ctl and its session
+/// commands elsewhere, as webfs and upas/fs keep session settings on a root
+/// 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.
+pub const Scope = enum { pane, session };
+
pub const Ctx = struct {
p: *Pardes,
pane: *Pane,
@@ -133,9 +141,14 @@ pub const registry = struct {
pub fn takesArg(b: Builtin()) bool {
inline for (manualBuiltinList(), 0..) |T, i|
if (@intFromEnum(b) == i) return @hasDecl(T, "takes_arg") and T.takes_arg;
- inline for (comptime settingList(), manualBuiltinCount()..) |setting, i|
- if (@intFromEnum(b) == i) return setting.takesArg();
- unreachable;
+ // Every setting takes its value, and a switch flips when bare.
+ return true;
+ }
+
+ pub fn scope(b: Builtin()) Scope {
+ inline for (manualBuiltinList(), 0..) |T, i|
+ if (@intFromEnum(b) == i) return if (@hasDecl(T, "scope")) T.scope else .pane;
+ return .session; // a setting
}
pub fn outputTraits(b: Builtin()) ?OutputTraits {
@@ -173,6 +186,17 @@ test "capabilities exactly gate setting and effect-source builtins" {
try std.testing.expectEqual(EffectCode.enabled, effect_code_registered);
}
+test "a builtin's scope is the pane unless it says session, and every setting is the session's" {
+ const Builtin = registry.Builtin();
+ try std.testing.expectEqual(Scope.pane, registry.scope(Builtin.Del));
+ try std.testing.expectEqual(Scope.pane, registry.scope(Builtin.Save));
+ try std.testing.expectEqual(Scope.session, registry.scope(Builtin.Newcol));
+ try std.testing.expectEqual(Scope.session, registry.scope(Builtin.Kill));
+ try std.testing.expectEqual(Scope.session, registry.scope(Builtin.Verbose));
+ for (comptime settingList()) |setting|
+ try std.testing.expectEqual(Scope.session, registry.scope(std.meta.stringToEnum(Builtin, setting.word).?));
+}
+
test "Look does not announce itself on the message row; Del still does" {
const Builtin = registry.Builtin();
try std.testing.expect(!registry.announces(Builtin.Look));
@@ -233,6 +257,7 @@ pub const Exec = struct {
// ---- session ----
pub const Kill = struct {
+ pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
c.p.quit = true;
c.p.emit(.quit);
@@ -240,12 +265,14 @@ pub const Kill = struct {
};
pub const Dump = struct {
+ pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
dump.dumpState(c.p) catch {};
}
};
pub const Restore = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub fn run(c: Ctx) void {
const path = c.arg orelse (c.p.last_dump orelse return);
@@ -256,6 +283,7 @@ pub const Restore = struct {
};
pub const Attach = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = pardes.can_attach;
pub fn run(c: Ctx) void {
@@ -270,6 +298,7 @@ pub const Attach = struct {
};
pub const Detach = struct {
+ pub const scope: Scope = .session;
pub const enabled = pardes.can_attach;
pub fn run(c: Ctx) void {
if (comptime enabled)
@@ -280,6 +309,7 @@ pub const Detach = struct {
};
pub const Mount = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = pardes.hosted;
pub fn run(c: Ctx) void {
@@ -293,6 +323,7 @@ pub const Mount = struct {
};
pub const Unmount = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = pardes.hosted;
pub fn run(c: Ctx) void {
@@ -318,12 +349,14 @@ pub const Msg = struct {
};
pub const NextColor = struct {
+ pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
colors.setThemeIndex(c.p, (@as(usize, c.p.settings.theme) + 1) % pardes.themes.len);
}
};
pub const ThemeSel = struct {
+ pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.themes_buffer, .steps = true, .commands = true };
pub fn run(c: Ctx) void {
panes.Output.openThemes(c.p, c.id) catch |err| c.p.reportError(c.id, "themes", err);
@@ -331,6 +364,7 @@ pub const ThemeSel = struct {
};
pub const ThemeFile = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = pardes.hosted;
pub fn run(c: Ctx) void {
@@ -342,6 +376,7 @@ pub const ThemeFile = struct {
};
pub const DumpThemes = struct {
+ pub const scope: Scope = .session;
pub const enabled = pardes.hosted;
pub fn run(c: Ctx) void {
if (comptime enabled) {
@@ -355,6 +390,7 @@ pub const DumpThemes = struct {
};
pub const FontSel = struct {
+ pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.fonts_buffer, .steps = true, .commands = true };
pub const enabled = capabilities.font_picker;
pub fn run(c: Ctx) void {
@@ -496,6 +532,7 @@ pub const New = struct {
/// The same empty scratch, opened in a fresh column beside the calling pane.
pub const Newcol = struct {
+ pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
c.p.newScratchColumn(c.id);
}
@@ -603,12 +640,14 @@ pub const Tty9p = struct {
/// Fold the active pane's column into the one on its right, keeping its panes.
/// The horizontal mirror of the vertical stacking `New` does.
pub const Joincol = struct {
+ pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
layout.joinCol(c.p);
}
};
pub const Tutor = struct {
+ pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
const free = c.p.freeSlot() orelse return;
const nt = c.p.openTutorView(free) catch return;
@@ -617,6 +656,7 @@ pub const Tutor = struct {
};
pub const Help = struct {
+ pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.help_buffer };
pub fn run(c: Ctx) void {
panes.Output.openHelp(c.p, c.id, "") catch |err| c.p.reportError(c.id, "help", err);
@@ -624,6 +664,7 @@ pub const Help = struct {
};
pub const Config = struct {
+ pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.config_buffer };
pub fn run(c: Ctx) void {
panes.Output.openConfig(c.p, c.id) catch |err| c.p.reportError(c.id, "config", err);
@@ -631,6 +672,7 @@ pub const Config = struct {
};
pub const LocationsConfig = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const output: OutputTraits = .{ .name = "+LocationsConfig" };
@@ -660,6 +702,7 @@ pub const LocationsConfig = struct {
};
pub const Messages = struct {
+ pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.messages_buffer };
pub fn run(c: Ctx) void {
panes.Output.openMessages(c.p, c.id) catch |err| c.p.reportError(c.id, "messages", err);
@@ -669,6 +712,7 @@ pub const Messages = struct {
/// This build's version and what changed to reach it, printed into an output
/// buffer the same way Config prints the live settings.
pub const Changelog = struct {
+ pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.changelog_buffer };
pub fn run(c: Ctx) void {
panes.Output.openChangelog(c.p, c.id) catch |err| c.p.reportError(c.id, "changelog", err);
@@ -676,6 +720,7 @@ pub const Changelog = struct {
};
pub const EffectCode = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = capabilities.panel_transitions or capabilities.scene_shaders;
pub const output: OutputTraits = .{ .name = config.effect_code_buffer };
@@ -690,6 +735,7 @@ pub const EffectCode = struct {
/// An optional SDL workspace-tag companion; never consumes input or text cells.
pub const Pet = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = pardes.platform == .gui;
pub fn run(c: Ctx) void {
@@ -793,6 +839,7 @@ pub const Right = struct {
/// Ctrl-o: one step back into the history.
pub const Back = struct {
+ pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
c.p.jumpBy(-1);
}
@@ -800,6 +847,7 @@ pub const Back = struct {
/// Ctrl-i: one step forward again, up to wherever Back started.
pub const Forward = struct {
+ pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
c.p.jumpBy(1);
}
@@ -821,6 +869,7 @@ pub const Last = struct {
};
pub const Jumplist = struct {
+ pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.jumps_buffer, .steps = true, .locations = true };
pub fn run(c: Ctx) void {
panes.Output.openJumps(c.p, c.id) catch |err| c.p.reportError(c.id, "jumplist", err);
@@ -920,6 +969,7 @@ pub const Lspwhy = struct {
};
pub const Peek = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = Board.enabled;
pub const output: OutputTraits = .{ .name = config.peek_buffer };
@@ -932,6 +982,7 @@ pub const Peek = struct {
};
pub const Poke = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = Board.enabled;
pub fn run(c: Ctx) void {
@@ -943,6 +994,7 @@ pub const Poke = struct {
};
pub const Hexdump = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = Board.enabled;
pub const output: OutputTraits = .{ .name = config.hexdump_buffer };
@@ -955,6 +1007,7 @@ pub const Hexdump = struct {
};
pub const Gpio = struct {
+ pub const scope: Scope = .session;
pub const takes_arg = true;
pub const enabled = Board.enabled;
pub const output: OutputTraits = .{ .name = config.gpio_buffer };