diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-03 15:45:52 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-03 15:45:52 -0300 |
| commit | e7cc89761e693833fe3fcabf022408741cf87709 (patch) | |
| tree | 33385b2c96e21e3ef1293d5be9a0a6c4e2e272fb /src/look.zig | |
| parent | d89c0b532df23ed5b48495f83725893d5d82042b (diff) | |
| download | pardes-e7cc89761e693833fe3fcabf022408741cf87709.tar.gz pardes-e7cc89761e693833fe3fcabf022408741cf87709.zip | |
errors: a mistyped flag is a sentence, and a pipe is not a document
Every argv refusal in main.zig was `return error.BadArgs` out of `main`, which
std prints as `error: BadArgs` with a return trace under it — the same shape a
real crash has, for the most ordinary thing a person can do. It also said
`BadArgs` and nothing about which argument, at sites that knew exactly:
pardes: no such option: --hepl
pardes: -n takes 1 or 3, not 'abc'
pardes: one file or directory at a time, and 'b' is the second
pardes: --detach and --attach are opposites: one runs the session, the
other joins one
Try 'pardes --help'.
stderr rather than the `+Errors` pane one function down, because argv is read
before a core exists and the person who mistyped a flag is looking at the
prompt they typed it into. `--attach`'s refusal already answered this way; now
all eleven do. `getcwd` failing is no longer reported as an argument problem,
and a `.url` or `@pN` positional says why a LAUNCH cannot act on it rather
than being swept into the same word as a typo.
AND `Look` ON A FIFO NO LONGER FREEZES THE EDITOR. `readFile` opened with a
plain blocking `open`, so a named pipe with no writer waited forever — inside
the keystroke that asked, with no frame, no message row and, in the tty shell,
no Ctrl-C either, because the terminal is in raw mode. It is `O_NONBLOCK` now,
the read loops answer `EAGAIN` rather than waiting, and `lseek` answering
ESPIPE — a pipe, a socket, a terminal — is refused as `NotAFile`, which the
boot pane spells "that is a pipe or a device, not a document". Without that
last part an unwritten FIFO read as EOF and opened a silent empty pane, which
says less than the hang did. The zero-size files worth streaming (procfs and
its kin) seek fine and are untouched.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
Diffstat (limited to 'src/look.zig')
| -rw-r--r-- | src/look.zig | 30 |
1 files changed, 29 insertions, 1 deletions
diff --git a/src/look.zig b/src/look.zig index f5d1488c..8c7014f5 100644 --- a/src/look.zig +++ b/src/look.zig @@ -857,7 +857,15 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 { } var pathbuf: [4096]u8 = undefined; const path_z = std.fmt.bufPrintSentinel(&pathbuf, "{s}", .{path}, 0) catch return error.PathTooLong; - const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY }); + // NONBLOCK, and it is the difference between an error and a dead editor. + // A plain blocking `open` of a FIFO waits for a writer that may never come, + // and this call runs INSIDE the keystroke that asked for it — no frame, no + // message row, and in the tty shell no Ctrl-C either, because the terminal + // is in raw mode. `Look` on a named pipe (or on a device that blocks until + // carrier) froze the whole program with nothing on screen to say why. The + // flag is cleared again below for the file kinds that are worth reading; + // the ones that are not are refused by name. + const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY, .NONBLOCK = true }); // WHY it would not open, not just that it would not. Every one of these is // an ordinary thing to do by accident — `pardes /root`, a file left at mode // 000, a name that was deleted between resolving and reading — and a caller @@ -872,7 +880,20 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 { else => error.OpenFailed, }; defer _ = libc.close(fd); + + // The flag STAYS SET, and the read loops below answer `EAGAIN` with + // `NotAFile`. A regular file ignores `O_NONBLOCK` entirely — the kernel + // never short-reads one for it — so this costs ordinary opens nothing and + // turns the one case that would have hung into an error with a name. const end = libc.lseek(fd, 0, libc.SEEK.END); + // NOT SEEKABLE IS NOT A DOCUMENT. `lseek` answers `ESPIPE` for a pipe, a + // socket and a terminal, and those are exactly the things whose "contents" + // are a future rather than a file — with `O_NONBLOCK` above they no longer + // hang the editor, but an unwritten FIFO then reads as EOF and opened as a + // silent empty pane, which says even less than the hang did. The zero-size + // files that ARE worth streaming — procfs and its kin — seek fine and + // report 0, so they take the branch below untouched. + if (end < 0 and libc.errno(end) == .SPIPE) return error.NotAFile; const size: usize = if (end < 0) 0 else @intCast(end); if (end >= 0 and libc.lseek(fd, 0, libc.SEEK.SET) < 0) return error.ReadFailed; if (size == 0) { @@ -885,6 +906,9 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 { const n = libc.read(fd, &first, first.len); if (n < 0) { if (libc.errno(n) == .INTR) continue; + // Nothing to read AND nothing that will end: an empty pipe with + // no writer. This is the hang, reported instead of waited on. + if (libc.errno(n) == .AGAIN) return error.NotAFile; return error.ReadFailed; } first_len = @intCast(n); @@ -899,6 +923,9 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 { const n = libc.read(fd, stream[stream_len..].ptr, stream.len - stream_len); if (n < 0) { if (libc.errno(n) == .INTR) continue; + // A stream that has paused is a stream that has ended, as far + // as one keystroke is concerned: keep what came. + if (libc.errno(n) == .AGAIN) break; return error.ReadFailed; } if (n == 0) break; @@ -909,6 +936,7 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 { while (true) { const n = libc.read(fd, &extra, 1); if (n < 0 and libc.errno(n) == .INTR) continue; + if (n < 0 and libc.errno(n) == .AGAIN) break; if (n < 0) return error.ReadFailed; if (n > 0) return error.FileTooLarge; break; |
