summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Messages.zig48
-rw-r--r--src/builtins.zig11
-rw-r--r--src/colors.zig18
-rw-r--r--src/dump.zig13
-rw-r--r--src/ninep/ctl.zig2
5 files changed, 78 insertions, 14 deletions
diff --git a/src/Messages.zig b/src/Messages.zig
index a316377a..366445c7 100644
--- a/src/Messages.zig
+++ b/src/Messages.zig
@@ -486,13 +486,53 @@ pub fn reportError(p: *Pardes, id: usize, operation: []const u8, err: anyerror)
p.fs.no_pane_slot_len = @intCast(w.buffered().len);
return reportFailure(p, id, w.buffered());
}
- 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;
- }
+ var words: [128]u8 = undefined;
+ w.writeAll(errorWords(err, &words)) catch {};
reportFailure(p, id, w.buffered());
}
+/// An error in a person's words, never its Zig name: the few that read
+/// badly spaced out said plainly, the rest spaced and lower case
+/// (`FileNotFound`, "file not found").
+pub fn errorWords(err: anyerror, buf: []u8) []const u8 {
+ return switch (err) {
+ error.FileTooLarge => "too large",
+ error.NotAFile => "not a regular file",
+ error.ReadFailed => "could not be read",
+ error.ParseZon => "not valid ZON",
+ error.ThemeFileTooLarge => "too large: a theme file is at most 1 MiB",
+ error.InvalidThemeName => "its name is empty or over 255 bytes",
+ else => {
+ var w = std.Io.Writer.fixed(buf);
+ 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;
+ }
+ return w.buffered();
+ },
+ };
+}
+
+/// A ZON parse failure in a person's words: the first error the parser
+/// found and its line, `line 3: expected ','`.
+pub fn zonWhy(diag: *const std.zon.parse.Diagnostics, buf: []u8) []const u8 {
+ var errors = diag.iterateErrors();
+ const first = errors.next() orelse return "not valid ZON";
+ const loc = first.getLocation(diag);
+ return std.fmt.bufPrint(buf, "line {d}: {f}", .{ loc.line + 1, first.fmtMessage(diag) }) catch "not valid ZON";
+}
+
+test "errors are said in words, and a ZON failure names its line" {
+ var buf: [128]u8 = undefined;
+ try std.testing.expectEqualStrings("file not found", errorWords(error.FileNotFound, &buf));
+ try std.testing.expectEqualStrings("too large", errorWords(error.FileTooLarge, &buf));
+ var diag: std.zon.parse.Diagnostics = .{};
+ defer diag.deinit(std.testing.allocator);
+ const T = struct { a: u8 };
+ try std.testing.expectError(error.ParseZon, std.zon.parse.fromSliceAlloc(T, std.testing.allocator, ".{\n .a = 1,\n .b = 2 }", &diag, .{}));
+ try std.testing.expect(std.mem.startsWith(u8, zonWhy(&diag, &buf), "line 3: "));
+}
+
/// 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 {
diff --git a/src/builtins.zig b/src/builtins.zig
index f51409aa..55779c8a 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -715,19 +715,22 @@ pub const Restore = struct {
if (comptime pardes.hosted) {
const bytes = @import("fs.zig").readRestore(c.p.gpa, path, c.p.settings.dump_dir.get()) catch |err| {
var buf: [limits.host_path_cap + 64]u8 = undefined;
+ var words: [128]u8 = undefined;
const why = switch (err) {
error.FileNotFound => "no such file",
error.PermissionDenied => "permission denied",
error.IsDirectory => "a directory, not a dump",
- else => @errorName(err),
+ else => pardes.Messages.errorWords(err, &words),
};
return c.p.reportFailure(c.id, std.fmt.bufPrint(&buf, "Restore: {s}: {s}", .{ path, why }) catch "Restore: cannot read the dump");
};
defer c.p.gpa.free(bytes);
// Nor does a file that is not a dump, before any warning.
- pardes.dump.check(c.p.gpa, bytes) catch |err| {
- var buf: [limits.host_path_cap + 64]u8 = undefined;
- return c.p.reportFailure(c.id, std.fmt.bufPrint(&buf, "Restore: {s}: not a pardes dump ({s})", .{ path, @errorName(err) }) catch "Restore: not a pardes dump");
+ var why_buf: [256]u8 = undefined;
+ pardes.dump.check(c.p.gpa, bytes, &why_buf) catch |err| {
+ var buf: [limits.host_path_cap + 320]u8 = undefined;
+ const why = if (err == error.ParseZon) why_buf[0..std.mem.indexOfScalar(u8, &why_buf, 0).?] else pardes.Messages.errorWords(err, &why_buf);
+ return c.p.reportFailure(c.id, std.fmt.bufPrint(&buf, "Restore: {s}: not a pardes dump: {s}", .{ path, why }) catch "Restore: not a pardes dump");
};
}
// acme's Load adds a dump's windows to the ones there; a Restore
diff --git a/src/colors.zig b/src/colors.zig
index d04c009e..a45184c8 100644
--- a/src/colors.zig
+++ b/src/colors.zig
@@ -766,10 +766,15 @@ pub fn failThemeFile(p: *Pardes, generation: u32, err: anyerror) void {
/// A theme file's failure, said with its path: `theme file x.zon: file
/// not found`, not which file left to guess.
fn themeFileError(p: *Pardes, request: ThemeFileRequest, err: anyerror) void {
- var said: [300]u8 = undefined;
+ var words: [128]u8 = undefined;
+ themeFileFailure(p, request, pardes.Messages.errorWords(err, &words));
+}
+
+fn themeFileFailure(p: *Pardes, request: ThemeFileRequest, why: []const u8) void {
+ var said: [520]u8 = undefined;
var cut: [200]u8 = undefined;
const path = @import("ninep/tree.zig").fitErr(request.path, &cut);
- p.reportError(request.pane, std.fmt.bufPrint(&said, "theme file {s}", .{path}) catch "theme file", err);
+ p.reportFailure(request.pane, std.fmt.bufPrint(&said, "theme file {s}: {s}", .{ path, why }) catch "theme file: not loaded");
revertThemeFile(p);
}
@@ -802,7 +807,11 @@ pub fn loadThemeFile(p: *Pardes, generation: u32, bytes: []const u8, animate: bo
var diagnostics: std.zon.parse.Diagnostics = .{};
defer diagnostics.deinit(p.gpa);
const parsed = std.zon.parse.fromSliceAlloc(Theme, p.gpa, source, &diagnostics, .{}) catch |err| {
- themeFileError(p, request, err);
+ if (err != error.ParseZon) themeFileError(p, request, err) else {
+ // Which line, and what the parser wanted there.
+ var why: [200]u8 = undefined;
+ themeFileFailure(p, request, pardes.Messages.zonWhy(&diagnostics, &why));
+ }
return false;
};
if (parsed.name.len == 0 or parsed.name.len > 255) {
@@ -983,6 +992,9 @@ test "ThemeFile parses ZON atomically and Theme returns to the compiled ring" {
// A half-written save is rejected without disturbing the last good value.
try std.testing.expect(!loadThemeFile(p, request_effect.generation, ".{ .name = ", true));
+ // Said with its line, in words: never a bare `parse zon`.
+ const said = p.panes[0].?.msg[0..p.panes[0].?.msg_len];
+ try std.testing.expect(std.mem.indexOf(u8, said, ": line 1: ") != null and std.mem.indexOf(u8, said, "parse zon") == null);
try std.testing.expectEqualStrings("mine", p.theme().name);
try std.testing.expectEqual(@as(?[3]u8, .{ 1, 2, 3 }), p.theme().bg);
diff --git a/src/dump.zig b/src/dump.zig
index f8b99ef4..e76bb1a2 100644
--- a/src/dump.zig
+++ b/src/dump.zig
@@ -396,11 +396,20 @@ pub const Parsed = struct {
/// Whether `bytes` are a dump a Restore could load, saying nothing: a
/// Restore asks before it warns about unsaved text or replaces anything.
-pub fn check(gpa: std.mem.Allocator, bytes: []const u8) !void {
+/// Whether `bytes` are a dump; a ZON failure's line and reason are left in
+/// `why`, ended by a 0.
+pub fn check(gpa: std.mem.Allocator, bytes: []const u8, why: []u8) !void {
var arena: std.heap.ArenaAllocator = .init(gpa);
defer arena.deinit();
const source = try arena.allocator().dupeZ(u8, bytes);
- try validate(try std.zon.parse.fromSliceAlloc(State, arena.allocator(), source, null, .{ .free_on_error = false }));
+ var diag: std.zon.parse.Diagnostics = .{};
+ const state = std.zon.parse.fromSliceAlloc(State, arena.allocator(), source, &diag, .{ .free_on_error = false }) catch |err| {
+ const said = pardes.Messages.zonWhy(&diag, why[0 .. why.len - 1]);
+ if (said.ptr != why.ptr) @memcpy(why[0..said.len], said);
+ why[said.len] = 0;
+ return err;
+ };
+ try validate(state);
}
pub fn readZon(gpa: std.mem.Allocator, bytes: []const u8, label: []const u8) !Parsed {
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index d6d7a6ee..7d8a370d 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -2287,7 +2287,7 @@ test "a Restore of a file that is no dump fails the write before any warning, an
const root_ctl = @intFromEnum(tree.TopFile.ctl);
const refused = wr(p, root_ctl, line);
try testing.expectEqual(E.IO, refused.errno());
- try testing.expect(std.mem.indexOf(u8, refused.reply.ename, "not a pardes dump") != null);
+ try testing.expect(std.mem.indexOf(u8, refused.reply.ename, "not a pardes dump: line 1: ") != null);
try testing.expect(p.restore_req == null);
try testing.expect(p.panes[0].?.discard_warned == null);