summaryrefslogtreecommitdiff
path: root/src/ninep/ctl.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 05:16:33 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commitc27e38eb3e97fd505255ee4b42d32ddc77c58ced (patch)
tree16ca3a42f84d15ed06c6102615947334fad14e4e /src/ninep/ctl.zig
parent32198b59020e295efbc48de343a9de69c952dcfc (diff)
downloadpardes-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]>
Diffstat (limited to 'src/ninep/ctl.zig')
-rw-r--r--src/ninep/ctl.zig62
1 files changed, 58 insertions, 4 deletions
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);
+ }
+}