diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-03 13:11:24 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-03 13:11:24 -0300 |
| commit | 7c6165f184e45af30ac697f8e8a584529ac4a287 (patch) | |
| tree | a03b6bc02ba77c7abe503a304370a59fd9a17e9c | |
| parent | e81b63106b83bce66641e8dd27d6aa348252baf1 (diff) | |
| download | pardes-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.md | 10 | ||||
| -rw-r--r-- | src/main.zig | 8 | ||||
| -rw-r--r-- | src/pardes.zig | 53 |
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; |
