diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 05:16:33 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | c27e38eb3e97fd505255ee4b42d32ddc77c58ced (patch) | |
| tree | 16ca3a42f84d15ed06c6102615947334fad14e4e | |
| parent | 32198b59020e295efbc48de343a9de69c952dcfc (diff) | |
| download | pardes-c27e38eb3e97fd505255ee4b42d32ddc77c58ced.tar.gz pardes-c27e38eb3e97fd505255ee4b42d32ddc77c58ced.zip | |
/commands takes each word's own doc comment, whole, without citations
Repl showed Joincol's first line and Joincol another's, because a comment
had drifted above the wrong declaration in builtins.zig; Kill's was cut
mid-sentence and Exit's cited exec.c:892. The drifted line is back on
Joincol, a parenthesis citing source (.c, .zig) is dropped, and the first
sentence is kept whole up to 240 bytes, then cut between words with an
ellipsis. A test checks every description opens its own word's comment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/builtins.zig | 2 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 62 | ||||
| -rw-r--r-- | test/fs.py | 2 |
3 files changed, 60 insertions, 6 deletions
diff --git a/src/builtins.zig b/src/builtins.zig index 3e35ca2f..dfd1e84c 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -1044,7 +1044,6 @@ pub const Tty9p = struct { } }; -/// Fold the active pane's column into the one on its right, keeping its panes. /// Binds this terminal as a language's REPL, by the language's name as the /// syntax table has it: an exec made on the body of a file in that /// language -- a middle click, the execute key, a single word like `ls` @@ -1119,6 +1118,7 @@ pub const Repl = 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; diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 1f0195d0..307921b9 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -563,9 +563,8 @@ fn summaryOf(word: []const u8, buf: []u8) []const u8 { if (w.buffered().len > 0) w.writeByte(' ') catch break; w.writeAll(text) catch break; } - const all = w.buffered(); - // The first sentence, whole: to a `.` that ends one, or the whole cut - // between words. + const all = stripCitations(w.buffered()); + // The first sentence, whole: to a `.` that ends one. var i: usize = 0; while (std.mem.indexOfScalarPos(u8, all, i, '.')) |dot| : (i = dot + 1) { if (dot + 1 == all.len or all[dot + 1] == ' ') return all[0 .. dot + 1]; @@ -573,6 +572,29 @@ fn summaryOf(word: []const u8, buf: []u8) []const u8 { return all; } +/// Drops a parenthesis that cites source (`(editors/acme/exec.c:892)`, +/// `(exec.c, \`kill\`; acme.c's WKill)`): a reader of /commands wants what +/// the word does, not where acme has it. In place; answers the rest. +fn stripCitations(text: []u8) []u8 { + var out: usize = 0; + var i: usize = 0; + while (i < text.len) { + if (text[i] == '(') if (std.mem.indexOfScalarPos(u8, text, i, ')')) |close| { + const inside = text[i + 1 .. close]; + if (std.mem.indexOf(u8, inside, ".c") != null or std.mem.indexOf(u8, inside, ".zig") != null) { + // With the space before it, and a colon after it kept. + if (out > 0 and text[out - 1] == ' ') out -= 1; + i = close + 1; + continue; + } + }; + text[out] = text[i]; + out += 1; + i += 1; + } + return text[0..out]; +} + fn writeCommands(w: *std.Io.Writer) !void { for (std.enums.values(Builtin)) |b| { // Edit is a pane's word the root's ctl takes too, at the active pane. @@ -593,7 +615,11 @@ fn writeCommands(w: *std.Io.Writer) !void { // Then ` -- ` and what it does, when its source says. var summary_buf: [2048]u8 = undefined; const summary = summaryOf(@tagName(b), &summary_buf); - if (summary.len > 0) try w.print(" -- {s}", .{@import("../Messages.zig").clip(summary, 160)}); + if (summary.len > 0) { + // The sentence whole unless it is long, then cut between words. + const kept = @import("../Messages.zig").clip(summary, 240); + try w.print(" -- {s}{s}", .{ kept, if (kept.len < summary.len) "…" else "" }); + } try w.writeByte('\n'); } } @@ -1667,3 +1693,31 @@ test "a looked word is found in its own pane, next place after the dot, wrapping for (p.panes) |slot| after += @intFromBool(slot != null); try testing.expect(after > count); } + +test "each word's /commands description is its own doc comment's first sentence, no citation" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const listed = rd(p, @intFromEnum(tree.TopFile.commands), 0, 1 << 16).bytes; + for ([_][]const u8{ + "\nRepl arg pane -- Binds this terminal as a language's REPL", + "\nJoincol root -- Fold the active pane's column into the one on its right, keeping its panes.\n", + "\nExit root -- acme's Exit: quit, once warnModified has nothing to say.\n", + "\nKill arg root -- acme's Kill: stop the commands pardes started", + }) |row| try testing.expect(std.mem.indexOf(u8, listed, row) != null); + try testing.expect(std.mem.indexOf(u8, listed, ".c:") == null); + // Every description is the first sentence of the doc comment just + // above its word's declaration in builtins.zig. + const source = @embedFile("../builtins.zig"); + var it = std.mem.tokenizeScalar(u8, listed, '\n'); + while (it.next()) |line| { + const dashes = std.mem.indexOf(u8, line, " -- ") orelse continue; + const word = line[0 .. std.mem.indexOfScalar(u8, line, ' ').?]; + var needle: [64]u8 = undefined; + const at = std.mem.indexOf(u8, source, try std.fmt.bufPrint(&needle, "\npub const {s} = struct", .{word})).?; + const said = std.mem.trimEnd(u8, line[dashes + 4 ..], "…"); + // Its opening words are in the comment block right above. + const head = said[0..@min(said.len, 10)]; + const block_start = std.mem.lastIndexOf(u8, source[0..at], "\n\n") orelse 0; + try testing.expect(std.mem.indexOf(u8, source[block_start..at], head) != null); + } +} @@ -276,7 +276,7 @@ def discovery(binary, embedded=False): # word, `arg` if it takes one, its ctl, a setting's values, then `-- ` what it does assert all(r.split()[2 if r.split()[1] == 'arg' else 1] in ('root', 'pane', 'both') for r in commands), commands assert row('Placement arg root acme,pardes'), commands - assert client.stat('/commands')['length'] == len('\n'.join(commands)) + 1 + assert client.stat('/commands')['length'] == len('\n'.join(commands).encode()) + 1 # bytes: a description may end in … client.write(f'/pane/{first}/ctl', b'Msg said through ctl\n') assert 'said through ctl' in ''.join(cell[0] for cell in client.screen()['cells']) assert set(client.list('/pane')) == {'new', str(fixture), str(first), str(second)} |
