summaryrefslogtreecommitdiff
path: root/src/config.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-21 20:07:43 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit9070942b29bd10dddcdecdb0e88ba0fb40608467 (patch)
treefa1fc5984c7847c52bf4e78d277586edce2d8325 /src/config.zig
parent0122e94fb37422085325f2dc78ca015051ce5f91 (diff)
downloadpardes-9070942b29bd10dddcdecdb0e88ba0fb40608467.tar.gz
pardes-9070942b29bd10dddcdecdb0e88ba0fb40608467.zip
Plan 9 idiom for the control filesystem, and the regressions a624a56 left
The 9P tree stops being a command language wearing a filesystem. /new created a pane as a side effect of a *read*; it is now Tcreate in /pane, with Tremove to close, which cloud9's engine has always supported and the editor never declared: tree.zig now says `features = .{ .create = true, .remove = true }`. Eleven pane ctl verbs become files that can be read as well as written -- dot, limit, dirty, mark, scroll, look, exec -- leaving ctl with `get`, the one verb no file would say better. Root /ctl splits into a read-only /status and the /look and /exec files whose write IS the click. stat carries real sizes where it used to answer 0, and qid versions track a pane's revision, so a client can poll for change without re-reading the body. Commit a624a56 moved raw-tty keys to an early-return branch that knew only Ctrl-B and bare Escape, and in the same edit deleted the paste branch below it. That cost Shift-Escape (the unconditional way out of tty mode) and both paste chords: Ctrl-V and Ctrl-Shift-V reached the child as keystrokes, so an agent CLI running in a pane took Ctrl-V for its image-paste binding and answered "No image found in clipboard". Both are restored, with tests. Nested detection was not subtly broken but deleted: 60367d8 removed nested.zig's process-ancestry walk and left "am I inside pardes" derived from PARDES_FORWARD_LOOK, which read "0" both for --nested and for "the listener did not come up". PARDES_PID now answers that question on its own, checked with kill(pid, 0); PARDES_9P and PARDES_PANE answer how to reach it; the flag is gone. The posted-9P registry also self-heals now -- a session that aborts cannot unlink its own socket, so posting sweeps entries whose target refuses a connection, symlinks only and on a definite ECONNREFUSED only. Elsewhere: tty scrolling is sticky-bottom, following new output only from the last row, with typing and entering raw mode snapping back to live; the boot layouts are a Boot enum instead of a chain of ifs, and the bare tty startup (Boot.tty, which main.zig names) opens an empty text pane under the shell while tests keep Boot.tty_shell; builtins announce themselves on the message row under a Verbose setting that is on by default; Config prints each setting the way you would type it back, so WindowOpacity 70 rather than "WindowOpacity: 70%"; LocationsConfig opens its window only when called bare; every tagline puts the word that closes the thing last, and a column now outlives its panes -- closing the last one leaves an empty pane, and only Delcol, newly on the column tagline, takes the column away. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'src/config.zig')
-rw-r--r--src/config.zig187
1 files changed, 92 insertions, 95 deletions
diff --git a/src/config.zig b/src/config.zig
index 440549b3..3169b20c 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -71,6 +71,7 @@ pub const leader_path = paths: {
.FocusTint = null,
.ColumnTags = null,
.SyntaxBold = null,
+ .Verbose = null,
.TreeContextTagStyle = null,
.Wrap = "tw",
.Tagbottom = "tb",
@@ -224,10 +225,10 @@ pub const tty_filter_min_contrast: f64 = 1.5;
pub const topbar_str = "Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Kill";
-pub const pane_builtins_str = "Tty Del Collapse";
-pub const pdf_pane_builtins_str = "Tty Del PdfSections PdfTint Collapse";
-pub const file_pane_builtins_str = "Save Tty Del Collapse";
-pub const terminal_pane_builtins_str = "Tty Save Del Mode Filter Collapse";
+pub const pane_builtins_str = "Tty Collapse Del";
+pub const pdf_pane_builtins_str = "Tty PdfSections PdfTint Collapse Del";
+pub const file_pane_builtins_str = "Save Tty Collapse Del";
+pub const terminal_pane_builtins_str = "Tty Save Mode Filter Collapse Del";
// Each mode badge is one codepoint.
pub const tag_normal = " ";
@@ -540,6 +541,8 @@ pub const Runtime = struct {
focus_tint: bool = true,
column_tags: bool = true,
syntax_bold: bool = false,
+ /// Builtins announce themselves on the message row.
+ verbose: bool = true,
window_opacity: u8 = 100,
window_blur: u8 = 0,
window_opacity_pending: bool = true,
@@ -637,7 +640,7 @@ pub const Runtime = struct {
pub const Capability = std.meta.FieldEnum(Capabilities);
- pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, column_tags, syntax_bold };
+ pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, column_tags, syntax_bold, verbose };
pub const Scene = std.meta.FieldEnum(layout.SceneEffect);
pub const Action = union(enum) {
@@ -682,6 +685,7 @@ pub const Runtime = struct {
.{ .word = "FocusTint", .action = .{ .toggle = .focus_tint } },
.{ .word = "ColumnTags", .action = .{ .toggle = .column_tags } },
.{ .word = "SyntaxBold", .action = .{ .toggle = .syntax_bold } },
+ .{ .word = "Verbose", .action = .{ .toggle = .verbose } },
.{ .word = "Theme", .action = .theme },
.{ .word = "Shell", .action = .shell },
.{ .word = "Font", .action = .font, .availability = .font_picker },
@@ -761,15 +765,10 @@ pub const Runtime = struct {
pub fn apply(state: *Runtime, setting: Setting, argument: ?[]const u8) bool {
switch (setting.action) {
+ // Every toggle names a bool field of its own, so one line covers
+ // all of them and a new toggle needs no arm here.
.toggle => |field| switch (field) {
- .colors => state.colors = !state.colors,
- .wrap => state.wrap = !state.wrap,
- .tree_context_tag_style => state.tree_context_tag_style = !state.tree_context_tag_style,
- .tag_bottom => state.tag_bottom = !state.tag_bottom,
- .debug => state.debug = !state.debug,
- .focus_tint => state.focus_tint = !state.focus_tint,
- .column_tags => state.column_tags = !state.column_tags,
- .syntax_bold => state.syntax_bold = !state.syntax_bold,
+ inline else => |f| @field(state, @tagName(f)) = !@field(state, @tagName(f)),
},
.shell => {
const value = std.mem.trim(u8, argument orelse return false, " \t\r\n");
@@ -835,46 +834,42 @@ pub const Runtime = struct {
return findAction(.{ .transition = transition }).?.word;
}
+ /// Each line reads as the command that would set it: the word, then the
+ /// value you would type after it. No colons, no units — `WindowOpacity 70`
+ /// is both what Config prints and what you write back.
pub fn writeReport(out: *std.Io.Writer, context: ReportContext) !void {
const state = context.state;
var wrote_transition = false;
for (settings) |setting| switch (setting.action) {
.toggle => |field| {
const value = switch (field) {
- .colors => state.colors,
- .wrap => state.wrap,
- .tree_context_tag_style => state.tree_context_tag_style,
- .tag_bottom => state.tag_bottom,
- .debug => state.debug,
- .focus_tint => state.focus_tint,
- .column_tags => state.column_tags,
- .syntax_bold => state.syntax_bold,
+ inline else => |f| @field(state, @tagName(f)),
};
- try out.print("{s}: {s}\n", .{ setting.word, onOff(value) });
+ try out.print("{s} {s}\n", .{ setting.word, onOff(value) });
},
- .theme => try out.print("{s}: {s}\n", .{ setting.word, context.theme_name }),
+ .theme => try out.print("{s} {s}\n", .{ setting.word, context.theme_name }),
.window_opacity => {
if (setting.enabled(context.capabilities))
- try out.print("{s}: {d}%{s}\n", .{
+ try out.print("{s} {d}{s}\n", .{
setting.word,
state.window_opacity,
if (state.window_opacity_pending) " (pending)" else "",
})
else
- try out.print("{s}: unsupported\n", .{setting.word});
+ try out.print("{s} unsupported\n", .{setting.word});
},
.window_blur => {
if (setting.enabled(context.capabilities))
- try out.print("{s}: {d}%\n", .{ setting.word, state.window_blur })
+ try out.print("{s} {d}\n", .{ setting.word, state.window_blur })
else
- try out.print("{s}: unsupported\n", .{setting.word});
+ try out.print("{s} unsupported\n", .{setting.word});
},
.shell => {
const chosen = state.shell.requested.get();
try out.print(
- "{s} requested (new panes): {s}{s}\n" ++
- "{s} effective (last spawn): {s}\n" ++
- "{s} pending: {s}\n",
+ "{s} requested (new panes) {s}{s}\n" ++
+ "{s} effective (last spawn) {s}\n" ++
+ "{s} pending {s}\n",
.{
setting.word,
if (chosen.len == 0) context.compiled_default_shell else chosen,
@@ -887,18 +882,18 @@ pub const Runtime = struct {
);
},
.font => if (!setting.enabled(context.capabilities))
- try out.print("{s}: unsupported\n", .{setting.word})
+ try out.print("{s} unsupported\n", .{setting.word})
else {
if (state.font.requested_size_hundredths == 0)
- try out.print("{s} requested size: unchanged\n", .{setting.word})
+ try out.print("{s} requested size unchanged\n", .{setting.word})
else
- try out.print("{s} requested size: {d}.{d:0>2}\n", .{ setting.word, state.font.requested_size_hundredths / 100, state.font.requested_size_hundredths % 100 });
+ try out.print("{s} requested size {d}.{d:0>2}\n", .{ setting.word, state.font.requested_size_hundredths / 100, state.font.requested_size_hundredths % 100 });
try out.print(
- "{s} requested: {s}\n" ++
- "{s} requested path: {s}\n" ++
- "{s} effective: {s}\n" ++
- "{s} pending: {s}\n" ++
- "{s} effective size: {d}.{d:0>2} {s}\n",
+ "{s} requested {s}\n" ++
+ "{s} requested path {s}\n" ++
+ "{s} effective {s}\n" ++
+ "{s} pending {s}\n" ++
+ "{s} effective size {d}.{d:0>2} {s}\n",
.{
setting.word,
shown(state.font.requested_name.get()),
@@ -916,28 +911,28 @@ pub const Runtime = struct {
);
},
.tagline_size => if (!context.capabilities.tagline_font_size)
- try out.print("{s}: unsupported\n", .{setting.word})
+ try out.print("{s} unsupported\n", .{setting.word})
else if (!setting.enabled(context.capabilities))
- try out.print("{s}: {d}% (build-time only)\n", .{ setting.word, state.font.tagline_percent })
+ try out.print("{s} {d} (build-time only)\n", .{ setting.word, state.font.tagline_percent })
else
- try out.print("{s}: {d}%\n", .{ setting.word, state.font.tagline_percent }),
+ try out.print("{s} {d}\n", .{ setting.word, state.font.tagline_percent }),
.transition => {
if (wrote_transition) continue;
wrote_transition = true;
if (!setting.enabled(context.capabilities))
- try out.writeAll("Panel transition: unsupported\n")
+ try out.writeAll("Panel transition unsupported\n")
else
- try out.print("Panel transition: {s}\n", .{transitionSettingName(state.panel_transition)});
+ try out.print("Panel transition {s}\n", .{transitionSettingName(state.panel_transition)});
},
.scene => |effect| {
if (!setting.enabled(context.capabilities)) {
- try out.print("{s}: unsupported\n", .{setting.word});
+ try out.print("{s} unsupported\n", .{setting.word});
continue;
}
const enabled = switch (effect) {
inline else => |field| @field(state.scene_effects, @tagName(field)),
};
- try out.print("{s}: {s}\n", .{ setting.word, onOff(enabled) });
+ try out.print("{s} {s}\n", .{ setting.word, onOff(enabled) });
},
};
@@ -1073,7 +1068,8 @@ pub const Runtime = struct {
test "Config report observes every simple setting and all live context" {
var state: Runtime = .{};
- var storage: [4096]u8 = undefined;
+ // Every setting, every context line: the report outgrows 4K.
+ var storage: [8192]u8 = undefined;
const context: ReportContext = .{
.startup_config_path = "/tmp/pardes/init",
.platform = "gui",
@@ -1112,37 +1108,38 @@ pub const Runtime = struct {
const report = storage[0..out.end];
const expected = switch (setting.action) {
.toggle => |field| switch (field) {
- .colors => "Colors: off\n",
- .wrap => "Wrap: off\n",
- .tree_context_tag_style => "TreeContextTagStyle: off\n",
- .tag_bottom => "Tagbottom: on\n",
- .debug => "Debug: on\n",
- .focus_tint => "FocusTint: off\n",
- .column_tags => "ColumnTags: off\n",
- .syntax_bold => "SyntaxBold: on\n",
+ .colors => "Colors off\n",
+ .wrap => "Wrap off\n",
+ .tree_context_tag_style => "TreeContextTagStyle off\n",
+ .tag_bottom => "Tagbottom on\n",
+ .debug => "Debug on\n",
+ .focus_tint => "FocusTint off\n",
+ .column_tags => "ColumnTags off\n",
+ .syntax_bold => "SyntaxBold on\n",
+ .verbose => "Verbose off\n",
},
- .shell => "Shell requested (new panes): fish\n",
- .tagline_size => "TaglineSize: 73%\n",
- .window_opacity => "WindowOpacity: 85% (pending)\n",
- .window_blur => "WindowBlur: 40%\n",
+ .shell => "Shell requested (new panes) fish\n",
+ .tagline_size => "TaglineSize 73\n",
+ .window_opacity => "WindowOpacity 85 (pending)\n",
+ .window_blur => "WindowBlur 40\n",
.transition => |transition| switch (transition) {
.off => unreachable,
- .slide => "Panel transition: PanelSlide\n",
- .zoom => "Panel transition: PanelZoom\n",
- .dissolve => "Panel transition: PanelDissolve\n",
- .ascii => "Panel transition: PanelAscii\n",
- .vertical => "Panel transition: PanelVertical\n",
- .edges => "Panel transition: PanelEdges\n",
- .fall => "Panel transition: PanelFall\n",
- .wave => "Panel transition: PanelWave\n",
- .curtain => "Panel transition: PanelCurtain\n",
- .scramble => "Panel transition: PanelScramble\n",
- .typewriter => "Panel transition: PanelType\n",
+ .slide => "Panel transition PanelSlide\n",
+ .zoom => "Panel transition PanelZoom\n",
+ .dissolve => "Panel transition PanelDissolve\n",
+ .ascii => "Panel transition PanelAscii\n",
+ .vertical => "Panel transition PanelVertical\n",
+ .edges => "Panel transition PanelEdges\n",
+ .fall => "Panel transition PanelFall\n",
+ .wave => "Panel transition PanelWave\n",
+ .curtain => "Panel transition PanelCurtain\n",
+ .scramble => "Panel transition PanelScramble\n",
+ .typewriter => "Panel transition PanelType\n",
},
.scene => |effect| switch (effect) {
- .crt => "Crt: on\n",
- .ripple => "Ripple: on\n",
- .glitch => "Glitch: on\n",
+ .crt => "Crt on\n",
+ .ripple => "Ripple on\n",
+ .glitch => "Glitch on\n",
},
.theme, .font => unreachable,
};
@@ -1163,15 +1160,15 @@ pub const Runtime = struct {
try writeReport(&out, context);
const report = storage[0..out.end];
for ([_][]const u8{
- "Theme: acme\n",
- "WindowOpacity: 85%\n",
- "Font requested size: 18.50\n",
- "Font requested: Wanted Mono\n",
- "Font requested path: /fonts/wanted.ttf\n",
- "Font effective: Effective Mono\n",
- "Font pending: on\n",
- "Font effective size: 13.75 points\n",
- "TaglineSize: 82%\n",
+ "Theme acme\n",
+ "WindowOpacity 85\n",
+ "Font requested size 18.50\n",
+ "Font requested Wanted Mono\n",
+ "Font requested path /fonts/wanted.ttf\n",
+ "Font effective Effective Mono\n",
+ "Font pending on\n",
+ "Font effective size 13.75 points\n",
+ "TaglineSize 82\n",
"Startup config: /tmp/pardes/init\n",
"Platform: gui\n",
"Compiled default shell: /bin/sh\n",
@@ -1197,23 +1194,23 @@ pub const Runtime = struct {
out = .fixed(&storage);
try writeReport(&out, defaults_context);
const defaults_report = storage[0..out.end];
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "WindowOpacity: unsupported\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "WindowOpacity unsupported\n") != null);
try std.testing.expect(!find("WindowOpacity").?.enabled(defaults_context.capabilities));
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "WindowBlur: unsupported\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "WindowBlur unsupported\n") != null);
try std.testing.expect(!find("WindowBlur").?.enabled(defaults_context.capabilities));
try std.testing.expect(find("WindowOpacity").?.enabled(context.capabilities));
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Shell requested (new panes): /bin/sh (default)\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Shell effective (last spawn): (none)\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Shell pending: on\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Shell requested (new panes) /bin/sh (default)\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Shell effective (last spawn) (none)\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Shell pending on\n") != null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Startup config: no per-user config path\n") != null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "GUI shader source:") == null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Font: unsupported\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Font requested:") == null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Panel transition: off\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Crt: unsupported\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Ripple: unsupported\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Glitch: unsupported\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, defaults_report, "TaglineSize: unsupported\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Font unsupported\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Font requested") == null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Panel transition off\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Crt unsupported\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Ripple unsupported\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Glitch unsupported\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, defaults_report, "TaglineSize unsupported\n") != null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Look hover delay: off\n") != null);
try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Native images: off\n") != null);
@@ -1223,8 +1220,8 @@ pub const Runtime = struct {
out = .fixed(&storage);
try writeReport(&out, defaults_context);
const web_report = storage[0..out.end];
- try std.testing.expect(std.mem.indexOf(u8, web_report, "Panel transition: unsupported\n") != null);
- try std.testing.expect(std.mem.indexOf(u8, web_report, "TaglineSize: 100% (build-time only)\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, web_report, "Panel transition unsupported\n") != null);
+ try std.testing.expect(std.mem.indexOf(u8, web_report, "TaglineSize 100 (build-time only)\n") != null);
}
};