summaryrefslogtreecommitdiff
path: root/src/look.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-03 15:45:52 -0300
committerGabriel Schneider <[email protected]>2026-09-03 15:45:52 -0300
commite7cc89761e693833fe3fcabf022408741cf87709 (patch)
tree33385b2c96e21e3ef1293d5be9a0a6c4e2e272fb /src/look.zig
parentd89c0b532df23ed5b48495f83725893d5d82042b (diff)
downloadpardes-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.zig30
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;