diff options
Diffstat (limited to 'src/Messages.zig')
| -rw-r--r-- | src/Messages.zig | 49 |
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. |
