summaryrefslogtreecommitdiff
path: root/src/Messages.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Messages.zig')
-rw-r--r--src/Messages.zig49
1 files changed, 41 insertions, 8 deletions
diff --git a/src/Messages.zig b/src/Messages.zig
index f8178629..66261eb4 100644
--- a/src/Messages.zig
+++ b/src/Messages.zig
@@ -416,23 +416,51 @@ pub fn messageLog(m: *const Messages, i: usize) ?*const LoggedMessage {
return &m.log[(first + i) % limits.message_log];
}
+/// `<operation>: <the error in words>`: FileNotFound reads "file not found".
pub fn reportError(p: *Pardes, id: usize, operation: []const u8, err: anyerror) void {
var buf: [256]u8 = undefined;
- const text = std.fmt.bufPrint(&buf, "{s}: {s}", .{ operation, @errorName(err) }) catch operation;
- reportFailure(p, id, text);
+ var w = std.Io.Writer.fixed(&buf);
+ w.print("{s}: ", .{operation}) catch {};
+ for (@errorName(err), 0..) |c, i| {
+ if (std.ascii.isUpper(c) and i > 0) w.writeByte(' ') catch break;
+ w.writeByte(std.ascii.toLower(c)) catch break;
+ }
+ reportFailure(p, id, w.buffered());
+}
+
+/// The longest start of `text` that fits `max` bytes and ends between
+/// words (or, with no space to cut at, between characters).
+pub fn clip(text: []const u8, max: usize) []const u8 {
+ if (text.len <= max) return text;
+ if (std.mem.lastIndexOfScalar(u8, text[0 .. max + 1], ' ')) |sp| return text[0..sp];
+ var n = max;
+ while (n > 0 and text[n] & 0xC0 == 0x80) n -= 1;
+ return text[0..n];
}
/// reportError with the words already chosen.
pub fn reportFailure(p: *Pardes, id: usize, text: []const u8) void {
- // A builtin a ctl write runs: its first error is also the write's.
+ // A builtin a ctl write runs: its first error is also the write's, cut
+ // between words.
if (p.fs.no_prompt and p.fs.failure_len == 0) {
- const n = @min(text.len, p.fs.failure.len);
- @memcpy(p.fs.failure[0..n], text[0..n]);
+ const kept = if (text.len > p.fs.failure.len) clip(text, p.fs.failure.len - 3) else text;
+ @memcpy(p.fs.failure[0..kept.len], kept);
+ var n = kept.len;
+ if (kept.len < text.len) {
+ @memcpy(p.fs.failure[n..][0..3], "...");
+ n += 3;
+ }
p.fs.failure_len = @intCast(n);
}
setMessage(p, id, text);
}
+test "an error reads as words and a failure is cut between them" {
+ try std.testing.expectEqualStrings("go", clip("go haskell", 4));
+ try std.testing.expectEqualStrings("go haskell", clip("go haskell", 10));
+ try std.testing.expectEqualStrings("\u{e9}", clip("\u{e9}\u{e9}", 3));
+}
+
/// The one place that decides which transient lines a pane is showing and
/// in what order. Everything downstream reads the answer: the body layer
/// so it does not paint under them, and the paint pass so it can stack
@@ -534,14 +562,14 @@ test "Msg writes the transient row by hand, bare or with text, and input ends it
// ...on the pane that ran it, and nowhere else
try std.testing.expectEqualStrings("", row(p, 0));
- // Bare, it reports ITSELF through reportError — the `<operation>: <Error>`
+ // Bare, it reports ITSELF through reportError — the `<operation>: <error in words>`
// every failed save and refused Look arrives in.
try std.testing.expect(p.executeBuiltinLine(2, "Msg"));
- try std.testing.expectEqualStrings("Msg: NoMessage", row(p, 2));
+ try std.testing.expectEqualStrings("Msg: no message", row(p, 2));
// Input starts its exit: it lingers, dissolves, and is gone.
p.update(.{ .key = .{ .cp = Key.escape } });
- try std.testing.expectEqualStrings("Msg: NoMessage", row(p, 2));
+ try std.testing.expectEqualStrings("Msg: no message", row(p, 2));
for (0..200) |_| p.update(.tick);
try std.testing.expectEqualStrings("", row(p, 2));
// ...and with neither linger nor animation it is as old as your last input.
@@ -764,6 +792,11 @@ test "a builtin announces itself on the message row, and Verbose silences it" {
// for its announcement, which is correct and not what this is testing.
try std.testing.expect(p.executeBuiltinLine(p.active, "Wrap"));
try std.testing.expectEqualStrings("Wrap", pane.msg[0..pane.msg_len]);
+ // A value it does not take is said, in place of the announcement.
+ const wrapped = p.settings.wrap;
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Wrap maybe"));
+ try std.testing.expectEqualStrings("Wrap: takes on or off", pane.msg[0..pane.msg_len]);
+ try std.testing.expectEqual(wrapped, p.settings.wrap);
// The notice list is what the paint pass reads, so the announcement has to
// reach it, not just the buffer.