summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/builtins.zig2
-rw-r--r--src/ninep/ctl.zig62
-rw-r--r--test/fs.py2
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);
+ }
+}
diff --git a/test/fs.py b/test/fs.py
index 9fb560e0..b7e8adcc 100644
--- a/test/fs.py
+++ b/test/fs.py
@@ -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)}