summaryrefslogtreecommitdiff
path: root/src/config.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 23:04:26 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commitf058514afb2f0e62a46158dc8c361c99ecd5149c (patch)
tree16e0e9ed0754bc1361dba9ae8c3589cd1d87ec81 /src/config.zig
parentf88eadfdf2629ff0d1086b6cf9299be2713ee3b6 (diff)
downloadpardes-f058514afb2f0e62a46158dc8c361c99ecd5149c.tar.gz
pardes-f058514afb2f0e62a46158dc8c361c99ecd5149c.zip
The tutor follows the guide's order, points at its sections, and no longer teaches $ as line end or the terminal tag's old words; a test checks the words, paths, chords and tags it names
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/config.zig')
-rw-r--r--src/config.zig124
1 files changed, 123 insertions, 1 deletions
diff --git a/src/config.zig b/src/config.zig
index d3da47c1..53def6a1 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -661,7 +661,7 @@ pub const Runtime = struct {
inactive_dim: u8 = 0,
/// The grip's button and the scrollbar under it, one width, as a percent
/// of the theme's `rail_px` (acme's 12px Scrollwid at a 17px tagline):
- /// a pixel shell's (docs/themes.md).
+ /// a pixel shell's (docs/typ/themes.typ).
grip_width: u16 = 150,
/// How the fx track's animations move (animation.Motion): off for
/// reduced motion.
@@ -2148,3 +2148,125 @@ test "docs/typ names only words, keys and default tags that exist" {
};
}
}
+
+/// The tutor's own spellings, which the docs/typ test cannot read: a
+/// builtin in backticks (`Save`, `Kill make`), a tag on a line of its own
+/// after "| ", a leader path after SPC (`SPC f s`), a chord as Ctrl-, Alt-
+/// or Shift- and one key.
+const doc_tutor = struct {
+ /// Read before the bindings in this file: a raw terminal's paste
+ /// (Pardes.handleKey).
+ const elsewhere = [_]Chord{ .{ .cp = 'V', .ctrl = true }, .{ .cp = 'V', .ctrl = true, .shift = true } };
+
+ /// The first word in each pair of backticks that looks like a builtin:
+ /// a capital, then letters and digits with a lower-case one among them.
+ fn words(gpa: std.mem.Allocator, text: []const u8, out: *std.ArrayList([]const u8)) !void {
+ var it = std.mem.splitScalar(u8, text, '`');
+ _ = it.next(); // before the first backtick
+ while (it.next()) |inside| {
+ _ = it.next() orelse break; // what follows the closing one
+ const first = inside[0 .. std.mem.indexOfAny(u8, inside, " +") orelse inside.len];
+ if (first.len < 2 or !std.ascii.isUpper(first[0])) continue;
+ var lower = false;
+ for (first) |c| {
+ if (!std.ascii.isAlphanumeric(c)) break;
+ lower = lower or std.ascii.isLower(c);
+ } else if (lower) try out.append(gpa, first);
+ }
+ }
+
+ /// The keys pressed after each SPC, up to the first that is no single
+ /// key: a path, or the start of one.
+ fn paths(gpa: std.mem.Allocator, text: []const u8, out: *std.ArrayList([]const u8)) !void {
+ var toks = std.mem.tokenizeAny(u8, text, " \n");
+ while (toks.next()) |t| {
+ if (!std.mem.eql(u8, t, "SPC")) continue;
+ var keys: std.ArrayList(u8) = .empty;
+ while (toks.peek()) |k| {
+ // `o,` and `j)` end a path written in a sentence
+ const ends = k.len == 2 and std.mem.indexOfScalar(u8, ",.;:)", k[1]) != null;
+ if ((k.len != 1 and !ends) or k[0] == '/') break;
+ try keys.append(gpa, k[0]);
+ _ = toks.next();
+ if (ends) break;
+ }
+ if (keys.items.len > 0) try out.append(gpa, keys.items);
+ }
+ }
+
+ fn path(keys: []const u8) bool {
+ for (std.enums.values(Builtin)) |b| if (leader_path.get(b)) |p|
+ if (std.mem.startsWith(u8, p, keys)) return true;
+ return false;
+ }
+
+ /// Every Ctrl-, Alt- or Shift- chord: its modifiers, then one key.
+ fn chords(gpa: std.mem.Allocator, text: []const u8, out: *std.ArrayList(Chord)) !void {
+ const named = [_]struct { []const u8, u21 }{ .{ "Esc", Key.escape }, .{ "Enter", Key.enter }, .{ "Tab", Key.tab } };
+ var i: usize = 0;
+ while (i < text.len) : (i += 1) {
+ if (i > 0 and std.ascii.isAlphanumeric(text[i - 1])) continue;
+ var c: Chord = .{ .cp = 0 };
+ var at = i;
+ while (true) {
+ const rest = text[at..];
+ if (std.mem.startsWith(u8, rest, "Ctrl-")) c.ctrl = true else if (std.mem.startsWith(u8, rest, "Alt-")) c.alt = true else if (std.mem.startsWith(u8, rest, "Shift-")) c.shift = true else break;
+ at += std.mem.indexOfScalar(u8, rest, '-').? + 1;
+ }
+ if (at == i or at >= text.len) continue;
+ c.cp = text[at];
+ for (named) |n| if (std.mem.startsWith(u8, text[at..], n[0])) {
+ c.cp = n[1];
+ };
+ try out.append(gpa, c);
+ i = at;
+ }
+ }
+};
+
+test "the tutor names only words, keys, paths and default tags that exist" {
+ var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
+ defer arena.deinit();
+ const gpa = arena.allocator();
+ const tutor = @embedFile("tutor.txt");
+
+ var found: std.ArrayList([]const u8) = .empty;
+ try doc_tutor.words(gpa, tutor, &found);
+ try std.testing.expect(found.items.len >= 20);
+ for (found.items) |w| if (!doc_typ.word(w)) {
+ std.debug.print("tutor.txt: `{s}` is no builtin\n", .{w});
+ return error.DocDrift;
+ };
+
+ var tags: usize = 0;
+ var lines = std.mem.splitScalar(u8, tutor, '\n');
+ while (lines.next()) |line| if (std.mem.startsWith(u8, line, "| ")) {
+ tags += 1;
+ if (!doc_typ.tag(line[2..])) {
+ std.debug.print("tutor.txt: \"{s}\" is no default tag\n", .{line});
+ return error.DocDrift;
+ }
+ };
+ try std.testing.expect(tags >= 5);
+
+ found.clearRetainingCapacity();
+ try doc_tutor.paths(gpa, tutor, &found);
+ try std.testing.expect(found.items.len >= 10);
+ for (found.items) |p| if (!doc_tutor.path(p)) {
+ std.debug.print("tutor.txt: SPC {s} leads nowhere\n", .{p});
+ return error.DocDrift;
+ };
+
+ var chords: std.ArrayList(Chord) = .empty;
+ try doc_tutor.chords(gpa, tutor, &chords);
+ try std.testing.expect(chords.items.len >= 10);
+ for (chords.items) |c| {
+ const elsewhere = for (doc_tutor.elsewhere) |e| {
+ if (doc_typ.same(e, c)) break true;
+ } else false;
+ if (!elsewhere and !doc_typ.bound(c)) {
+ std.debug.print("tutor.txt: a chord on {u} (ctrl {} alt {} shift {}) is bound nowhere\n", .{ c.cp, c.ctrl, c.alt, c.shift });
+ return error.DocDrift;
+ }
+ }
+}