summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 12:23:15 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commitd191193a1e707a6bfd8fc8bbe1429dbc9f34676f (patch)
tree083b64ec90e82566c60a7a72bac957e79d2ae4e5
parentc06a666ca4daf0068372e825807007f7bbcf5523 (diff)
downloadpardes-d191193a1e707a6bfd8fc8bbe1429dbc9f34676f.tar.gz
pardes-d191193a1e707a6bfd8fc8bbe1429dbc9f34676f.zip
A Theme hint drops whole names to fit, never cutting one mid-word
The names were written into a fixed room until it ran out, the last one cut, and the click's form ran past the 128 bytes a 9P error carries. Each name now goes in whole or not at all, and both forms are sized to fit. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/colors.zig23
-rw-r--r--src/exec.zig8
2 files changed, 27 insertions, 4 deletions
diff --git a/src/colors.zig b/src/colors.zig
index aa69fe6d..72e25aed 100644
--- a/src/colors.zig
+++ b/src/colors.zig
@@ -718,7 +718,7 @@ pub fn themesNear(buf: []u8, name: []const u8) []const u8 {
var w = std.Io.Writer.fixed(buf);
for (themes) |t| {
if (name.len > 0 and std.ascii.toLower(t.name[0]) != std.ascii.toLower(name[0])) continue;
- w.print("{s}{s}", .{ if (w.buffered().len > 0) " " else "", t.name }) catch break;
+ if (!putName(&w, buf.len, t.name)) break;
} else if (w.buffered().len > 0) return w.buffered();
// The nearest, most alike first: ponytail, a plain score (shared
// prefix, the name inside, its letters in order), no edit distance.
@@ -741,10 +741,29 @@ pub fn themesNear(buf: []u8, name: []const u8) []const u8 {
n = @min(n + 1, best.len);
}
w = std.Io.Writer.fixed(buf);
- for (best[0..n]) |i| w.print("{s}{s}", .{ if (w.buffered().len > 0) " " else "", themes[i].name }) catch break;
+ for (best[0..n]) |i| if (!putName(&w, buf.len, themes[i].name)) break;
return w.buffered();
}
+/// One more name on the line, whole, or none: false when it would not fit.
+fn putName(w: *std.Io.Writer, cap: usize, name: []const u8) bool {
+ const sep: []const u8 = if (w.buffered().len > 0) " " else "";
+ if (w.buffered().len + sep.len + name.len > cap) return false;
+ w.print("{s}{s}", .{ sep, name }) catch return false;
+ return true;
+}
+
+test "a theme hint drops whole names to fit, never cutting one" {
+ var buf: [23]u8 = undefined;
+ const said = themesNear(&buf, "9dracula");
+ var words = std.mem.tokenizeScalar(u8, said, ' ');
+ while (words.next()) |word| {
+ var whole = false;
+ for (themes) |t| whole = whole or std.mem.eql(u8, t.name, word);
+ try std.testing.expect(whole);
+ }
+}
+
fn likeness(theme_name: []const u8, name: []const u8) usize {
var prefix: usize = 0;
while (prefix < @min(theme_name.len, name.len) and std.ascii.toLower(theme_name[prefix]) == std.ascii.toLower(name[prefix])) prefix += 1;
diff --git a/src/exec.zig b/src/exec.zig
index cf4e01ab..88c0705d 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -918,9 +918,13 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[]
// A config line is not answered on screen (applyStartupConfig
// runs with announce off); a click or a ctl write is.
if (!p.announce) return;
- var near: [170]u8 = undefined;
+ // All of it within the 128 bytes a 9P error carries, so a write
+ // failing with it is cut nowhere: the names get what is left.
+ const shown = want[0..@min(want.len, 40)];
+ const room = 128 -| ("Theme: no theme \"\"; like it: ; Themes lists all".len + shown.len);
+ var near: [128]u8 = undefined;
var text: [256]u8 = undefined;
- return p.reportFailure(p.active, std.fmt.bufPrint(&text, "Theme: no theme \"{s}\"; like it: {s}; Themes lists all", .{ want[0..@min(want.len, 40)], colors.themesNear(&near, want) }) catch "Theme: no such theme");
+ return p.reportFailure(p.active, std.fmt.bufPrint(&text, "Theme: no theme \"{s}\"; like it: {s}; Themes lists all", .{ shown, colors.themesNear(near[0..@min(room, near.len)], want) }) catch "Theme: no such theme");
},
.font => {
if (comptime !font_picker) return;