summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-03 13:11:24 -0300
committerGabriel Schneider <[email protected]>2026-09-03 13:11:24 -0300
commit7c6165f184e45af30ac697f8e8a584529ac4a287 (patch)
treea03b6bc02ba77c7abe503a304370a59fd9a17e9c
parente81b63106b83bce66641e8dd27d6aa348252baf1 (diff)
downloadpardes-7c6165f184e45af30ac697f8e8a584529ac4a287.tar.gz
pardes-7c6165f184e45af30ac697f8e8a584529ac4a287.zip
boot: argv naming nothing opens an errors pane, not a stack trace
`pardes nosuchfile` returned error.BadArgs out of nativeMain, which std prints as `error: BadArgs` with a return trace under it — indistinguishable from a crash, for a typo, and it left the human with no editor at all. A launch that names something look.resolve cannot make a target of now boots one +Errors pane filling the window, saying `file or directory not found` and the argument AS TYPED: acme's own vocabulary for output that came from the program rather than from a word somebody clicked, and the word rather than a resolved path because `pardes ~/notes/tdoo.md` wants to see its own typo back. A chdir that fails on a directory that really is one stays BadArgs. That is a permission problem rather than a typo, and the two want different answers. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
-rw-r--r--src/CHANGELOG.md10
-rw-r--r--src/main.zig8
-rw-r--r--src/pardes.zig53
3 files changed, 71 insertions, 0 deletions
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 66c9fb0c..4cef906d 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -50,6 +50,16 @@
is one a v1 daemon panics encoding, so a mixed pair — `zig build` replacing
the binary under a running session — meets a `Refusal.version` instead of
losing every pane shell the daemon owns.
+- `pardes nosuchfile` opens an editor. It used to return `BadArgs` out of
+ `main`, which std prints as `error: BadArgs` with a return trace under it:
+ indistinguishable from a crash, for a typo, and it left the human with no
+ editor at all. A launch that names nothing now boots one `+Errors` pane
+ filling the window — acme's own vocabulary for output that came from the
+ program rather than from a word somebody clicked — saying `file or directory
+ not found` and the argument AS TYPED. A `chdir` that fails on a directory
+ that really is one is still `BadArgs`: that is a permission problem, not a
+ typo, and the two want different answers.
+
- A filtered terminal costs what an unfiltered one does. `Filter`'s second
stage asked `RGB.contrast` for every cell it painted, and that call ends in
`std.math.pow` six times over — a libm round trip per cell, per frame, to
diff --git a/src/main.zig b/src/main.zig
index 41495a48..60491052 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -333,6 +333,14 @@ fn nativeMain(init: std.process.Init) !void {
opts.file_line = t.at.line;
},
.image => |t| opts.file = try arena.dupe(u8, t.path),
+ // Nothing of that name is there. A typo is not a reason to
+ // refuse to start: the session boots with one `+Errors` pane
+ // naming what was asked for (pardes.zig `missing_path`). The
+ // other arms stay `BadArgs` — a `.dir` here means `chdir`
+ // refused a directory that IS one, which is a permission
+ // problem and not a typo, and `.url`/`.pane` are targets no
+ // launch can act on.
+ .none => opts.missing_path = try arena.dupe(u8, a),
else => return error.BadArgs,
}
}
diff --git a/src/pardes.zig b/src/pardes.zig
index b64b62d7..8c643be6 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5841,6 +5841,17 @@ pub const Options = struct {
/// not consulted at all (it never was — the file arm always won).
file: ?[]const u8 = null,
file_line: usize = 0,
+ /// argv NAMED SOMETHING THAT IS NOT THERE — no file, no directory, nothing
+ /// `look.resolve` could make a target of. The word as the human typed it,
+ /// and it boots one `+Errors` pane saying so.
+ ///
+ /// A launch is not a failure worth refusing. `pardes nosuchfile` used to
+ /// return `BadArgs` out of `main`, which std prints as `error: BadArgs`
+ /// with a return trace under it — indistinguishable from a crash for a
+ /// typo, and it left the human with no editor at all. Outranked by `file`
+ /// for the same reason `file` outranks `shells`: only one of them can name
+ /// the boot layout, and they are never both set.
+ missing_path: ?[]const u8 = null,
tty_toggle: u21 = config.tty_toggle_default,
/// load a dump of another instance instead of spawning shells (acme -l)
load_path: ?[]const u8 = null,
@@ -6322,6 +6333,28 @@ pub const Pardes = struct {
p.ncol = 1;
p.col_n[0] = 1;
p.col_terms[0][0] = 0;
+ } else if (opts.missing_path) |word| {
+ // ARGV NAMED NOTHING. `+Errors` and not the message row, because a
+ // launch has no pane to put a message row on yet — and because
+ // this is exactly what acme's `+Errors` is: output from the
+ // program rather than from a word anybody clicked (output_pane
+ // `Origin.errors`). Filling the window with it makes the answer
+ // unmissable, which a one-line message under a shell prompt is
+ // not.
+ //
+ // The word AS TYPED, not a resolved path: there is nothing to
+ // resolve, and `pardes ~/notes/tdoo.md` wants to see its own typo
+ // back rather than an absolute path built around it.
+ const content = try std.fmt.allocPrint(
+ gpa,
+ "file or directory not found\n\n\t{s}\n",
+ .{word},
+ );
+ errdefer gpa.free(content);
+ _ = try output_pane.open(p, 0, "", .errors, "", content);
+ p.ncol = 1;
+ p.col_n[0] = 1;
+ p.col_terms[0][0] = 0;
} else if (comptime platform == .esp32p4) {
// BARE METAL BOOTS AN EMPTY OUTPUT BUFFER, and a shell is not a layout preference
// here but an impossibility: there is no operating system under this, so there is
@@ -16419,6 +16452,26 @@ test "Esc alternates between two panes of the SAME kind" {
}
}
+test "argv naming nothing boots an errors pane rather than failing the launch" {
+ const gpa = std.testing.allocator;
+ const p = try Pardes.init(gpa, .{ .cols = 80, .rows = 24, .missing_path = "notes/tdoo.md" });
+ defer p.deinit();
+ p.update(.{ .resize = .{ .cols = 80, .rows = 24 } });
+
+ // One pane, filling the window, and it is an OUTPUT pane rather than the
+ // shell a bare `pardes` boots: the answer is the whole screen.
+ try std.testing.expectEqual(@as(u8, 1), p.ncol);
+ try std.testing.expectEqual(@as(usize, 1), p.col_n[0]);
+ const pane = p.panes[0].?;
+ try std.testing.expect(!pane.isTerminal());
+ const f = pane.file.?;
+ try std.testing.expectEqual(output_pane.Origin.errors, f.output.?.from);
+ // ...and it says what happened AND what was asked for. The word as typed,
+ // which is the half a bare "not found" leaves out.
+ try std.testing.expect(std.mem.indexOf(u8, f.content, "not found") != null);
+ try std.testing.expect(std.mem.indexOf(u8, f.content, "notes/tdoo.md") != null);
+}
+
test "Esc back into a tty leaves its view at the prompt" {
if (platform == .web) return;
const gpa = std.testing.allocator;