summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-03 15:39:43 -0300
committerGabriel Schneider <[email protected]>2026-09-03 15:39:43 -0300
commitd89c0b532df23ed5b48495f83725893d5d82042b (patch)
treea4a2eff66096fb90414bf5cd84fe016c930e0d68 /src/gui
parent3d8d4425c969d3df21915c9c14b144460a1c0086 (diff)
downloadpardes-d89c0b532df23ed5b48495f83725893d5d82042b.tar.gz
pardes-d89c0b532df23ed5b48495f83725893d5d82042b.zip
errors: a save that could not happen, and two panics on an ordinary click
A review of what this program does when the environment says no. The finding that reframes it: there were almost NO panics on ordinary paths — the rule already held — but there was a great deal of silence, and one case worse than any panic. SILENT DATA LOSS ON SAVE. `saveFile` marked the pane saved the moment it QUEUED the effect, before any host had tried; `host_io.writeFd` returned void, so a short or failed write was indistinguishable from a complete one; and `writeFileBytes` returned true regardless. A save to a read-only file, or into a directory removed under the pane, therefore cleared the tag's ` *` and posted nothing — and `Del` makes no dirty check, so the next click threw the edits away with the screen saying they were safe. On a full disk it was worse: the file is already `O_TRUNC`'d when `write` fails, so the message row said `saved` over a file that had just been emptied. Now: `writeFd` reports, `writeFileBytes` returns WHY (`PermissionDenied`, `NoSpaceLeft`, `ReadOnlyFilesystem`, …) including a failed `close`, which is where write-back filesystems report at all; the core marks the pane saved around `perform` rather than at emit, which is also where the bytes are read; and a host that could not write calls `Pardes.saveFailed`, which puts the reason on the message row and takes the clean mark back. That is a CALL and not a return value because host.zig enforces, at comptime, that a `push_` method reaching every host in a fan-out cannot have one answer — the first attempt at this changed the signature and the compiler was right to refuse it. TWO PANICS ON AN ORDINARY KEYSTROKE, in look.zig's number scans. `v = v * 10 + d` over caller-supplied digits, reached from `parsePathLine` and the `@pN` scan — which every Look, every right-click and every n/N motion runs on whatever word is under the pointer. A hash in a log, a CSV column, any output shaped `foo:99999999999999999999`, and the editor died with "integer overflow". Both saturate now, the same way acmefs.zig's address parser already did; a saturated line is refused by `file_pane.open`'s `line <= total` and a saturated pane id by `focusPaneLine`'s `id < MAX_PANES`, so nothing addressable changes. A BOOT FILE THAT WILL NOT OPEN joins the missing-name case in the `+Errors` pane instead of taking the launch down: `pardes /root` resolves as a `.file`, could not be read, and left `error: PermissionDenied` and a return trace. `look.readFile` now says which errno it was, so the pane can say "permission denied" rather than a word from the source code. The tag-marker test drained no effects and passed anyway, which is exactly the defect; it drains now. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.zig13
1 files changed, 7 insertions, 6 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 44e28125..a87eebeb 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -2806,7 +2806,7 @@ fn dumpGrid(gpa: std.mem.Allocator, surface: *pardes.Surface) !void {
@memcpy(frame[at..][0..footer.len], footer);
at += footer.len;
std.debug.assert(at == frame.len);
- host_io.writeFd(1, frame);
+ _ = host_io.writeFd(1, frame);
}
// =====================================================================
@@ -3890,7 +3890,7 @@ fn spawnPane(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
fn ptyWrite(ctx: ?*anyopaque, pane: u8, bytes: []const u8) void {
const s = shellOf(ctx);
- if (s.ptys[pane]) |pt| host_io.writeFd(pt.fd, bytes);
+ if (s.ptys[pane]) |pt| _ = host_io.writeFd(pt.fd, bytes);
}
fn ptyResize(ctx: ?*anyopaque, pane: u8, cols: u16, rows: u16) void {
@@ -3920,7 +3920,8 @@ fn ttyTaken(ctx: ?*anyopaque, pane: u8) bool {
fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) void {
const s = shellOf(ctx);
- if (!host_io.writeFileBytes(path, bytes)) return;
+ host_io.writeFileBytes(path, bytes) catch |err|
+ return s.core.saveFailed(pane, "save", err);
// our own write is about to come back as a watch event: restamp from the
// bytes we just put there so it reads as "no change". Only for the pane's
// OWN file — a `Put` elsewhere is a change like any other.
@@ -3941,7 +3942,7 @@ fn writeDump(ctx: ?*anyopaque, bytes: []const u8) void {
const s = shellOf(ctx);
var pbuf: [1024:0]u8 = undefined;
const path = pardes.dump.outPath(&pbuf) orelse return;
- if (!host_io.writeFileBytes(path, bytes)) return;
+ host_io.writeFileBytes(path, bytes) catch |err| return s.core.reportError(0, "dump", err);
s.core.setLastDump(path);
}
@@ -6048,7 +6049,7 @@ fn writeCapturePpm(g: *Gui, gpa: std.mem.Allocator, pixels: []const u8, width: u
var header: [64]u8 = undefined;
const hdr = std.fmt.bufPrint(&header, "P6\n{d} {d}\n255\n", .{ width, height }) catch return error.CaptureWriteFailed;
- host_io.writeFd(fd, hdr);
+ _ = host_io.writeFd(fd, hdr);
const row_rgb = try gpa.alloc(u8, @as(usize, width) * 3);
defer gpa.free(row_rgb);
@@ -6061,7 +6062,7 @@ fn writeCapturePpm(g: *Gui, gpa: std.mem.Allocator, pixels: []const u8, width: u
row_rgb[di + 1] = src[si + 1];
row_rgb[di + 2] = src[si + if (bgr) @as(usize, 0) else 2];
}
- host_io.writeFd(fd, row_rgb);
+ _ = host_io.writeFd(fd, row_rgb);
}
if (libc.rename(tmp_path, final_path) != 0) return error.CaptureWriteFailed;
}