summaryrefslogtreecommitdiff
path: root/src/main.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/main.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/main.zig')
-rw-r--r--src/main.zig64
1 files changed, 51 insertions, 13 deletions
diff --git a/src/main.zig b/src/main.zig
index 00eff433..b08a64da 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -136,6 +136,26 @@ const help_text =
\\
;
+/// A MISTAKE AT THE SHELL PROMPT, said in words and nothing else.
+///
+/// Every one of these used to be `return error.BadArgs` out of `main`, which
+/// std prints as `error: BadArgs` with a RETURN TRACE under it. That reads as a
+/// crash — it is the same shape a real panic has — for the most ordinary thing
+/// a person can do, which is mistype a flag. It also said `BadArgs` and nothing
+/// about WHICH argument, when the site that returned it knew exactly.
+///
+/// stderr and not an `+Errors` pane, which is the answer one line down in
+/// `Pardes.init`: argv is read before any core exists, and a person who typed
+/// a bad flag is looking at the prompt they typed it into rather than at an
+/// editor. `--attach`'s refusal three functions down already answers this way.
+fn badArgs(io: std.Io, comptime fmt: []const u8, args: anytype) noreturn {
+ var buf: [1024]u8 = undefined;
+ const line = std.fmt.bufPrint(&buf, "pardes: " ++ fmt ++ "\nTry 'pardes --help'.\n", args) catch
+ "pardes: bad arguments\nTry 'pardes --help'.\n";
+ std.Io.File.stderr().writeStreamingAll(io, line) catch {};
+ std.process.exit(1);
+}
+
/// Built at COMPTIME, because both halves are: `version` comes out of
/// `build.zig.zon` through the options module and `commit` out of `git` at
/// configure time, so there is nothing here to format at runtime and no buffer
@@ -240,18 +260,21 @@ fn nativeMain(init: std.process.Init) !void {
if (std.mem.eql(u8, a, "--tty")) {
opts.tty_only = true;
} else if (std.mem.startsWith(u8, a, "--tty-toggle=")) {
- opts.tty_toggle = parseCtrlKey(a["--tty-toggle=".len..]) orelse return error.BadArgs;
+ opts.tty_toggle = parseCtrlKey(a["--tty-toggle=".len..]) orelse
+ badArgs(init.io, "--tty-toggle wants one letter, not '{s}'", .{a["--tty-toggle=".len..]});
} else if (std.mem.eql(u8, a, "--tty-toggle")) {
i += 1;
- if (i >= args.len) return error.BadArgs;
- opts.tty_toggle = parseCtrlKey(args[i]) orelse return error.BadArgs;
+ if (i >= args.len) badArgs(init.io, "--tty-toggle needs a letter after it", .{});
+ opts.tty_toggle = parseCtrlKey(args[i]) orelse
+ badArgs(init.io, "--tty-toggle wants one letter, not '{s}'", .{args[i]});
} else if (std.mem.eql(u8, a, "-n")) {
i += 1;
- if (i >= args.len) return error.BadArgs;
- opts.shells = std.fmt.parseInt(u8, args[i], 10) catch return error.BadArgs;
+ if (i >= args.len) badArgs(init.io, "-n needs a count after it: 1 or 3", .{});
+ opts.shells = std.fmt.parseInt(u8, args[i], 10) catch
+ badArgs(init.io, "-n takes 1 or 3, not '{s}'", .{args[i]});
} else if (std.mem.eql(u8, a, "-l")) {
i += 1;
- if (i >= args.len) return error.BadArgs;
+ if (i >= args.len) badArgs(init.io, "-l needs the path of a dump to load", .{});
opts.load_path = args[i];
} else if (std.mem.eql(u8, a, "--fs")) {
opts.fs = "";
@@ -297,8 +320,12 @@ fn nativeMain(init: std.process.Init) !void {
return;
} else if (a.len > 0 and a[0] != '-' and positional == null) {
positional = a;
+ } else if (a.len == 0) {
+ badArgs(init.io, "an empty argument names nothing", .{});
+ } else if (a[0] == '-') {
+ badArgs(init.io, "no such option: {s}", .{a});
} else {
- return error.BadArgs;
+ badArgs(init.io, "one file or directory at a time, and '{s}' is the second", .{a});
}
}
// Started INSIDE another pardes: hand it the file and get out of the way
@@ -328,7 +355,8 @@ fn nativeMain(init: std.process.Init) !void {
std.process.exit(1);
};
var cwdbuf: [4096]u8 = undefined;
- const cwd = std.c.getcwd(&cwdbuf, cwdbuf.len) orelse return error.BadArgs;
+ const cwd = std.c.getcwd(&cwdbuf, cwdbuf.len) orelse
+ badArgs(init.io, "this shell's working directory is gone; cd somewhere that exists", .{});
var realbuf: [4096]u8 = undefined;
const sent = switch (@import("look.zig").resolve(word, std.mem.span(@as([*:0]u8, @ptrCast(cwd))), &realbuf)) {
.dir => |d| nested.sendLook(outer_pid, d, 0),
@@ -356,7 +384,8 @@ fn nativeMain(init: std.process.Init) !void {
// dirs); anything else resolves as a file
if (std.c.chdir(a.ptr) != 0) {
var cwdbuf: [4096]u8 = undefined;
- const cwd = std.c.getcwd(&cwdbuf, cwdbuf.len) orelse return error.BadArgs;
+ const cwd = std.c.getcwd(&cwdbuf, cwdbuf.len) orelse
+ badArgs(init.io, "this shell's working directory is gone; cd somewhere that exists", .{});
var realbuf: [4096]u8 = undefined;
switch (@import("look.zig").resolve(a, std.mem.span(@as([*:0]u8, @ptrCast(cwd))), &realbuf)) {
.file => |t| {
@@ -390,7 +419,13 @@ fn nativeMain(init: std.process.Init) !void {
.word = try arena.dupe(u8, a),
.dir = try arena.dupe(u8, std.mem.span(@as([*:0]u8, @ptrCast(cwd)))),
},
- else => return error.BadArgs,
+ // A URL, an `@pN` pane address, or a directory that resolves
+ // and `chdir` refused. None is a typo, and none is something a
+ // LAUNCH can act on — the first two are words to click once
+ // pardes is open, and the third is a permission problem.
+ .url => badArgs(init.io, "a URL is not something a launch can open; start pardes and click it", .{}),
+ .pane => badArgs(init.io, "@pN addresses a pane of a running pardes, so there is none yet", .{}),
+ else => badArgs(init.io, "cannot enter that directory: {s}", .{a}),
}
}
}
@@ -410,7 +445,8 @@ fn nativeMain(init: std.process.Init) !void {
// with no core, so the two together are a contradiction with no useful
// reading. Refused rather than resolved by declaration order, which would
// silently drop whichever flag lost.
- if (detach != null and attach != null) return error.BadArgs;
+ if (detach != null and attach != null)
+ badArgs(init.io, "--detach and --attach are opposites: one runs the session, the other joins one", .{});
// `--fs` mounts the acme control filesystem, and it needs a CORE to serve.
// `--attach` has none — it is a terminal whose state lives in another
// process — so the flag there would be parsed, stored, and served by
@@ -427,13 +463,15 @@ fn nativeMain(init: std.process.Init) !void {
// than the desktop shells pay. `--detach --fs` is now the configuration
// that most wants a control filesystem, because it is the one whose panes
// outlive every terminal that could otherwise have scripted them.
- if (opts.fs != null and attach != null) return error.BadArgs;
+ if (opts.fs != null and attach != null)
+ badArgs(init.io, "--fs serves a session's own core, and --attach has none of its own", .{});
// ...and `--fs9` for exactly that reason and no other: it is the same tree
// over a different transport, and an `--attach` has no core to serve it
// from either. Checked separately rather than folded into the line above
// so that neither flag's refusal is a side effect of the other's — they
// are independent everywhere else.
- if (opts.fs9 != null and attach != null) return error.BadArgs;
+ if (opts.fs9 != null and attach != null)
+ badArgs(init.io, "--fs9 serves a session's own core, and --attach has none of its own", .{});
// `--detach` replaces the frontend rather than choosing among them: the
// core runs here, with no terminal, and the frontends are elsewhere on a
// socket (src/detached/). It is checked before `platform` because it is not