From 8b2a55f31169ede82e11a573ab655e72e7831fe7 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 23:11:08 -0300 Subject: A choice setting's refusal names what it takes, and /commands lists them Bare Placement and BootShell flip, which is pardes's convention for a word that chooses (Crt does too), but nothing said so, and "bad value in control message" never said what would do. A refused value now says `takes acme, pardes` (every switch and choice setting), /commands lists each such setting's values in a fourth field, and config.md and fs.md say that bare steps to the next. Motion and ShaderAnimation stepped nowhere bare and now step like the rest. Co-Authored-By: Claude Opus 5.5 --- src/ninep/ctl.zig | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'src/ninep/ctl.zig') diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index c8be3bad..e62ff605 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -281,7 +281,12 @@ fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ? break :probe probe.apply(setting, if (arg.len > 0) arg else null); }, }; - return if (takes) null else refuse(p, req, "bad value in control message", line); + if (takes) return null; + var why: [104]u8 = undefined; + return refuse(p, req, if (config.Runtime.choices(setting.action)) |values| + std.fmt.bufPrint(&why, "bad value in control message; takes {s}", .{values}) catch "bad value in control message" + else + "bad value in control message", line); } /// Runs a checked control message at pane `id`, as a click on its word @@ -407,11 +412,20 @@ pub fn writeRoot(p: *Pardes, req: Req) Reply { /// itself, so a new builtin is listed the moment it exists. fn writeCommands(w: *std.Io.Writer) !void { for (std.enums.values(Builtin)) |b| { - try w.print("{s}{s} {s}\n", .{ + try w.print("{s}{s} {s}", .{ @tagName(b), if (builtins.registry.takesArg(b)) " arg" else "", if (builtins.registry.scope(b) == .session) "root" else "pane", }); + // A setting that chooses among words: the words, comma-joined. Bare, + // it steps to the next of them (a two-valued one flips). + if (config.Runtime.find(@tagName(b))) |setting| if (config.Runtime.choices(setting.action)) |values| { + try w.writeByte(' '); + var parts = std.mem.splitSequence(u8, values, ", "); + var first = true; + while (parts.next()) |v| : (first = false) try w.print("{s}{s}", .{ if (first) "" else ",", v }); + }; + try w.writeByte('\n'); } } @@ -623,10 +637,11 @@ test "commands lists every builtin with its argument and the ctl that takes it" const b = std.meta.stringToEnum(Builtin, words.next().?).?; if (builtins.registry.takesArg(b)) try testing.expectEqualStrings("arg", words.next().?); try testing.expectEqualStrings(if (builtins.registry.scope(b) == .session) "root" else "pane", words.next().?); + if (config.Runtime.find(@tagName(b))) |s| if (config.Runtime.choices(s.action) != null) try testing.expect(words.next() != null); try testing.expect(words.next() == null); } try testing.expectEqual(std.enums.values(Builtin).len, lines); - for ([_][]const u8{ "\nDel arg pane\n", "\nNewcol root\n", "\nSave arg pane\n", "\nVerbose arg root\n", "\nUndo pane\n", "\nRedo pane\n" }) |row| + for ([_][]const u8{ "\nDel arg pane\n", "\nNewcol root\n", "\nSave arg pane\n", "\nVerbose arg root on,off\n", "\nPlacement arg root acme,pardes\n", "\nUndo pane\n", "\nRedo pane\n" }) |row| try testing.expect(std.mem.indexOf(u8, listed.bytes, row) != null); } @@ -752,7 +767,8 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi try testing.expectEqualStrings("not a session control message \"Del\": write it to pane//ctl", wr(p, root_ctl, "Del").reply.ename); try testing.expectEqualStrings("unknown control message \"Nonsense 1\"", wr(p, root_ctl, "Nonsense 1").reply.ename); - try testing.expectEqualStrings("bad value in control message \"Verbose maybe\"", wr(p, root_ctl, "Verbose maybe").reply.ename); + try testing.expectEqualStrings("bad value in control message; takes on, off \"Verbose maybe\"", wr(p, root_ctl, "Verbose maybe").reply.ename); + try testing.expectEqualStrings("bad value in control message; takes acme, pardes \"Placement east\"", wr(p, root_ctl, "Placement east").reply.ename); const no_theme = wr(p, root_ctl, "Theme no-such-theme").reply.ename; try testing.expectStringStartsWith(no_theme, "bad value in control message; like it: n"); try testing.expect(std.mem.endsWith(u8, no_theme, "; ThemeSel lists all \"Theme no-such-theme\"")); -- cgit v1.3