From fda2f546f66d0bb1b30da0ef6aae77fe82803b1c Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 11:22:41 -0300 Subject: DumpConfig prints each setting as the ctl word that sets it, reads unsupported for one the frontend lacks, and includes LocationsConfig The report meant to be feedable back as configuration, but several lines were not. Shell and Font each took three to six lines of "requested", "effective" and "pending". The transition read "Panel transition PanelSlide", with no Panel word. WindowOpacity and TaglineSize carried "(pending)" and "(build-time only)". A GUI-only toggle such as Ligatures was left out on the tty, and LocationsConfig was missing altogether. Now every line up to a blank one is a word and its value: - Shell ; - Font :; - one on/off line for each Panel word; - `unsupported` for a setting this frontend does not have; - LocationsConfig as it would be typed. After the blank line comes what is in effect but set by no word, phrased so that none of it starts with a ctl word. The test checks that each line before the blank one starts with a setting word. The GUI scripts that read the font lines (font_size.py, appearance_gallery.py) now read the new ones. Co-Authored-By: Claude Opus 5.5 --- docs/config.md | 7 +- src/Output.zig | 4 ++ src/builtins.zig | 3 +- src/config.zig | 167 +++++++++++++++++++-------------------------- test/appearance_gallery.py | 2 +- test/font_size.py | 6 +- test/output.zig | 26 ++++--- 7 files changed, 98 insertions(+), 117 deletions(-) diff --git a/docs/config.md b/docs/config.md index bc4acf12..35f0b6e7 100644 --- a/docs/config.md +++ b/docs/config.md @@ -34,8 +34,11 @@ not stop later ones. Text that is no builtin is not run as a shell command `Config` (`SPC f c`) opens `init` itself in a pane, or goes to its pane when it is open. With no file there yet, the pane is named for it, empty, and Save writes it, making its directory first. `DumpConfig` opens a -`+DumpConfig` pane with the startup path (a right click opens it) and every -live setting; a setting the frontend cannot show reads `unsupported`. The +`+DumpConfig` pane with every live setting, each line the word that sets +it and its value (`Shell /bin/bash`, `PanelSlide off`, `LocationsConfig …`) +and `unsupported` for one the frontend cannot show. After a blank line +come what is in effect but set by no word, such as the last shell spawned +and the font in use, and the startup path (a right click opens it). The root `ctl` file reads the settings back in the words a write takes ([fs.md](fs.md#the-root-ctl)). diff --git a/src/Output.zig b/src/Output.zig index 0581be41..65f6bfe4 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -815,7 +815,11 @@ pub fn openConfig(p: *Pardes, id: usize) !void { errdefer out.deinit(); var dir_buf: [1024]u8 = undefined; const host_fs = comptime (pardes.platform != .web and pardes.platform != .esp32p4); + var located: std.Io.Writer.Allocating = .init(p.gpa); + defer located.deinit(); + try p.locations_config.write(&located.writer); try config.Runtime.writeReport(&out.writer, .{ + .locations = located.written(), .default_dump_dir = if (host_fs) (pardes.dump.defaultDirectory(&dir_buf) orelse "") else "", .startup_config_path = p.opts.startup_config_path, .platform = @tagName(pardes.platform), diff --git a/src/builtins.zig b/src/builtins.zig index c40b2e00..8fe49649 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -458,7 +458,8 @@ test "Ligatures is a setting only in the shell that shapes text" { .state = &p.settings, }); try std.testing.expectEqual(exists, std.mem.indexOf(u8, storage[0..out.end], "Ligatures off\n") != null); - try std.testing.expectEqual(exists, std.mem.indexOf(u8, storage[0..out.end], "Ligatures") != null); + // A GUI-only setting reads so elsewhere, as every one the shell lacks. + try std.testing.expectEqual(!exists, std.mem.indexOf(u8, storage[0..out.end], "Ligatures unsupported\n") != null); } comptime { diff --git a/src/config.zig b/src/config.zig index a70ae708..94b32b88 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1275,6 +1275,8 @@ pub const Runtime = struct { native_images: bool, capabilities: Capabilities, state: *const Runtime, + /// The `LocationsConfig` argument in effect, as it would be typed. + locations: []const u8 = "", }; fn onOff(value: bool) []const u8 { @@ -1285,23 +1287,19 @@ pub const Runtime = struct { return if (text.len == 0) "(none)" else text; } - fn transitionSettingName(transition: animation.Transition) []const u8 { - if (transition == .off) return "off"; - 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 DumpConfig prints and what you write back. + /// Each setting line reads as the command that would set it: the word, + /// then the value you would type after it (`unsupported` where this + /// shell has no such setting). No colons, no units: `WindowOpacity 70` + /// is both what DumpConfig prints and what you write back. What is in + /// effect but set by no word follows, after a blank line. 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| { - // A toggle for what this shell does not draw (Ligatures in a - // terminal, whose text its emulator draws) is no setting here: - // it is not a builtin, so the report does not list it either. - if (!setting.enabled(context.capabilities)) continue; + if (!setting.enabled(context.capabilities)) { + try out.print("{s} unsupported\n", .{setting.word}); + continue; + } const value = switch (field) { inline else => |f| @field(state, @tagName(f)), }; @@ -1310,11 +1308,7 @@ pub const Runtime = struct { .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", .{ - setting.word, - state.window_opacity, - if (state.window_opacity_pending) " (pending)" else "", - }) + try out.print("{s} {d}\n", .{ setting.word, state.window_opacity }) else try out.print("{s} unsupported\n", .{setting.word}); }, @@ -1333,64 +1327,28 @@ pub const Runtime = struct { } }), .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", - .{ - setting.word, - if (chosen.len == 0) context.compiled_default_shell else chosen, - if (chosen.len == 0) " (default)" else "", - setting.word, - shown(state.shell.effective.get()), - setting.word, - onOff(state.shell.pending), - }, - ); + try out.print("{s} {s}\n", .{ setting.word, if (chosen.len == 0) context.compiled_default_shell else chosen }); }, .font => if (!setting.enabled(context.capabilities)) 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}) - 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 {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()), - setting.word, - shown(state.font.requested_path.get()), - setting.word, - shown(state.font.effective_name.get()), - setting.word, - onOff(state.font.pending), - setting.word, - state.font.effective_size_hundredths / 100, - state.font.effective_size_hundredths % 100, - @tagName(state.font.effective_size_unit), - }, - ); + // As `Font` takes it, `name:size`: the one asked for, else + // the one in use. + const f = &state.font; + const name = if (f.requested_path.get().len > 0) f.requested_path.get() else if (f.requested_name.get().len > 0) f.requested_name.get() else f.effective_name.get(); + try out.print("{s} {s}", .{ setting.word, name }); + if (f.requested_size_hundredths != 0) try out.print(":{d}.{d:0>2}", .{ f.requested_size_hundredths / 100, f.requested_size_hundredths % 100 }); + try out.writeByte('\n'); }, .tagline_size => if (!context.capabilities.tagline_font_size) 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 }) else 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") - else - try out.print("Panel transition {s}\n", .{transitionSettingName(state.panel_transition)}); - }, + // Each a switch of its own, the one in use on. + .transition => |transition| if (!setting.enabled(context.capabilities)) + try out.print("{s} unsupported\n", .{setting.word}) + else + try out.print("{s} {s}\n", .{ setting.word, onOff(state.panel_transition == transition) }), .scene => |effect| { if (!setting.enabled(context.capabilities)) { try out.print("{s} unsupported\n", .{setting.word}); @@ -1426,7 +1384,21 @@ pub const Runtime = struct { .inactive_dim => try out.print("{s} {d}\n", .{ setting.word, state.inactive_dim }), .grip_width => try out.print("{s} {d}\n", .{ setting.word, state.grip_width }), }; - + if (context.locations.len > 0) try out.print("LocationsConfig {s}\n", .{context.locations}); + + // In effect, and set by no word. + try out.writeByte('\n'); + try out.print("Last shell spawned: {s}\n", .{shown(state.shell.effective.get())}); + try out.print("Shell change pending: {s}\n", .{onOff(state.shell.pending)}); + if (find("Font").?.enabled(context.capabilities)) { + const f = &state.font; + try out.print("Font in use: {s} {d}.{d:0>2} {s}\n", .{ shown(f.effective_name.get()), f.effective_size_hundredths / 100, f.effective_size_hundredths % 100, @tagName(f.effective_size_unit) }); + try out.print("Font change pending: {s}\n", .{onOff(f.pending)}); + } + if (find("WindowOpacity").?.enabled(context.capabilities)) + try out.print("Window opacity change pending: {s}\n", .{onOff(state.window_opacity_pending)}); + if (context.capabilities.tagline_font_size and !find("TaglineSize").?.enabled(context.capabilities)) + try out.writeAll("Tagline size: build-time only\n"); if (context.startup_config_path) |path| try out.print("Startup config: {s}\n", .{path}) else @@ -1726,9 +1698,9 @@ pub const Runtime = struct { .chip_shadow => "ChipShadow on\n", .thumb_flash => "ThumbFlash on\n", }, - .shell => "Shell requested (new panes) fish\n", + .shell => "Shell fish\n", .tagline_size => "TaglineSize 73\n", - .window_opacity => "WindowOpacity 85 (pending)\n", + .window_opacity => "WindowOpacity 85\n", .window_blur => "WindowBlur 40\n", .dump_dir => "DumpDir ~/dumps\n", .message_ms => |which| switch (which) { @@ -1738,17 +1710,17 @@ pub const Runtime = struct { }, .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 => "PanelSlide on\n", + .zoom => "PanelZoom on\n", + .dissolve => "PanelDissolve on\n", + .ascii => "PanelAscii on\n", + .vertical => "PanelVertical on\n", + .edges => "PanelEdges on\n", + .fall => "PanelFall on\n", + .wave => "PanelWave on\n", + .curtain => "PanelCurtain on\n", + .scramble => "PanelScramble on\n", + .typewriter => "PanelType on\n", }, .scene => |effect| switch (effect) { .crt => "Crt 2\n", @@ -1788,12 +1760,10 @@ pub const Runtime = struct { 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", + "Font /fonts/wanted.ttf:18.50\n", + "Font in use: Effective Mono 13.75 points\n", + "Font change pending: on\n", + "Window opacity change pending: off\n", "TaglineSize 82\n", "Startup config: /tmp/pardes/init\n", "Platform: gui\n", @@ -1823,19 +1793,23 @@ pub const Runtime = struct { 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 /bin/sh\n") != null); + try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Last shell spawned: (none)\n") != null); + try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Shell change 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, "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, "Font in use") == null); + try std.testing.expect(std.mem.indexOf(u8, defaults_report, "PanelSlide 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, "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); - // A shell that does not shape text has no Ligatures setting to report. - try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Ligatures") == null); + // A setting this shell does not have reads so, as a line to write. + try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Ligatures unsupported\n") != null); + // Every line before the blank one is a ctl word and its value. + const words = defaults_report[0..std.mem.indexOf(u8, defaults_report, "\n\n").?]; + var lines = std.mem.splitScalar(u8, words, '\n'); + while (lines.next()) |line| try std.testing.expect(find(line[0..std.mem.indexOfScalar(u8, line, ' ').?]) != null); defaults_context.platform = "web"; defaults_context.capabilities.panel_transitions = false; @@ -1843,8 +1817,9 @@ 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, "PanelSlide unsupported\n") != null); + try std.testing.expect(std.mem.indexOf(u8, web_report, "TaglineSize 100\n") != null); + try std.testing.expect(std.mem.indexOf(u8, web_report, "Tagline size: build-time only\n") != null); } }; diff --git a/test/appearance_gallery.py b/test/appearance_gallery.py index c321c7c0..98420a15 100644 --- a/test/appearance_gallery.py +++ b/test/appearance_gallery.py @@ -98,7 +98,7 @@ def run(args): return text settings = config_report() - require('Font effective size 18.00 pixels' in settings, 'configured font size not applied: ' + settings) + require(' 18.00 pixels\n' in settings, 'configured font size not applied: ' + settings) require('TaglineSize 80' in settings, 'configured tag size missing') (root / 'effective-config.txt').write_text(settings) report['font_effective_pixels'] = 18 diff --git a/test/font_size.py b/test/font_size.py index ef685a9e..878d6472 100644 --- a/test/font_size.py +++ b/test/font_size.py @@ -30,16 +30,16 @@ def run(args): for serial, tag in panes(client).items(): if '/+DumpConfig ' in tag: body = client.read(f'/pane/{serial}/body').decode() - if f'Font effective size {size:.2f} pixels' in body: + if f' {size:.2f} pixels\n' in body and 'Font in use: ' in body: return body return None return require(wait_for(ready), f'Font did not become {size} pixels') initial = report(18) - require('Font requested size 18.00' in initial, 'startup size suffix ignored') + require(':18.00\n' in initial, 'startup size suffix ignored') require(args.font in initial, 'requested font missing') execute(client, control, f'Font {args.font}:20.5') - require('Font requested size 20.50' in report(20.5), 'fractional size ignored') + require(':20.50\n' in report(20.5), 'fractional size ignored') execute(client, control, f'Font {args.font}') report(20.5) # no suffix preserves current size execute(client, control, f'Font {args.font}:900') diff --git a/test/output.zig b/test/output.zig index c67c021e..51d620c0 100644 --- a/test/output.zig +++ b/test/output.zig @@ -544,11 +544,12 @@ test "DumpConfig prints the startup path and refreshes its one output" { for ([_][]const u8{ "Startup config: " ++ path ++ "\n", "Theme orchard\n", - "Shell effective (last spawn) (none)\n", - "Shell pending on\n", + "Last shell spawned: (none)\n", + "Shell change pending: on\n", + "LocationsConfig ", }) |line| try std.testing.expect(std.mem.indexOf(u8, out.content, line) != null); var default_line: [256]u8 = undefined; - try std.testing.expect(std.mem.indexOf(u8, out.content, try std.fmt.bufPrint(&default_line, "Shell requested (new panes) {s} (default)\n", .{config.defaultShell()})) != null); + try std.testing.expect(std.mem.indexOf(u8, out.content, try std.fmt.bufPrint(&default_line, "Shell {s}\n", .{config.defaultShell()})) != null); try std.testing.expectEqualStrings(config.dump_config_buffer, std.fs.path.basename(out.path)); try std.testing.expectEqual(panes.Output.Origin{ .cmd = .DumpConfig }, out.output.?.from); @@ -734,14 +735,14 @@ test "every enabled setting builtin mutates the State Config reports" { "Tagbottom on\n", "Debug on\n", "Theme acme\n", - "Shell requested (new panes) fish\n", + "Shell fish\n", }) |line| try std.testing.expect(std.mem.indexOf(u8, report, line) != null); - const transition = if (builtins.capabilities.panel_transitions) - try std.fmt.bufPrint(&buf, "Panel transition {s}\n", .{ - config.Runtime.findAction(.{ .transition = p.settings.panel_transition }).?.word, - }) + const transition = if (!builtins.capabilities.panel_transitions) + "PanelSlide unsupported\n" + else if (p.settings.panel_transition == .off) + "PanelSlide off\n" else - "Panel transition unsupported\n"; + try std.fmt.bufPrint(&buf, "{s} on\n", .{config.Runtime.findAction(.{ .transition = p.settings.panel_transition }).?.word}); try std.testing.expect(std.mem.indexOf(u8, report, transition) != null); // A bare word turns a bundled pass on at its default level. const scene_status = if (builtins.capabilities.scene_shaders) "2" else "unsupported"; @@ -752,14 +753,11 @@ test "every enabled setting builtin mutates the State Config reports" { const tagline = if (!builtins.capabilities.tagline_font_size) "TaglineSize unsupported\n" else - try std.fmt.bufPrint(&buf, "TaglineSize {d}{s}\n", .{ - p.settings.font.tagline_percent, - if (builtins.capabilities.font_picker) "" else " (build-time only)", - }); + try std.fmt.bufPrint(&buf, "TaglineSize {d}\n", .{p.settings.font.tagline_percent}); try std.testing.expect(std.mem.indexOf(u8, report, tagline) != null); if (chosen_font) |name| { try std.testing.expect(std.mem.indexOf(u8, report, name) != null); - try std.testing.expect(std.mem.indexOf(u8, report, "Font pending on\n") != null); + try std.testing.expect(std.mem.indexOf(u8, report, "Font change pending: on\n") != null); } } -- cgit v1.3