diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 04:57:43 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 04:57:43 -0300 |
| commit | 0c3cf0628adf9e22270b09d9c451b08ac07f558a (patch) | |
| tree | c8034581e1c465056a5e5e32cb0fd15242178674 | |
| parent | 2488df529cde2994c37f385cefca4db1837ed9a3 (diff) | |
| download | pardes-0c3cf0628adf9e22270b09d9c451b08ac07f558a.tar.gz pardes-0c3cf0628adf9e22270b09d9c451b08ac07f558a.zip | |
A shell's prompt drawn on the alternate screen is a prompt: a terminal whose program was killed there is no longer busy for ever
htop killed with `sig KILL` leaves the emulator on its alternate screen;
bash comes back and draws its prompt there, marks and all, but
promptInputReady refused any prompt on the alternate screen, so
pty/status said busy 1 and pty/run answered a bare `busy` for good.
A prompt mark there now counts. A run sent at such a prompt leaves the
alternate screen in the emulator first, as a reused command pane does,
so its output lands on the primary screen and is read whole, not
`exit 0 cut` with nothing. Busy there with no prompt drawn, and no
program name to give, a run is told `busy alternate screen`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/ninep/pty.zig | 27 | ||||
| -rw-r--r-- | src/terminal.zig | 6 |
2 files changed, 31 insertions, 2 deletions
diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig index fc8eb570..c58cf676 100644 --- a/src/ninep/pty.zig +++ b/src/ninep/pty.zig @@ -258,6 +258,8 @@ pub fn writeRun(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { var name: [64]u8 = undefined; if (p.hostForegroundName(id, &name)) |program| answer(p, slot, "busy: {s} is running", .{program}) + else if (pardes.panes.terminal.onAlternateScreen(pane)) + answer(p, slot, "busy alternate screen", .{}) else answer(p, slot, "busy", .{}); } else { @@ -315,6 +317,10 @@ pub fn noteMarks(p: *Pardes, id: usize, pane: *Pane) void { slot.want = marks.started +% 1; slot.prompts = marks.prompts; slot.declined = marks.declined; + // A prompt on the alternate screen (its program killed there): the + // emulator leaves it first, as a reused command pane does, so the + // run's output lands on the primary screen and is read whole. + if (pardes.panes.terminal.onAlternateScreen(pane)) pardes.panes.terminal.feedOutput(p, pane, "\x1b[?1049l"); p.emitWrite(id, slot.line); p.emitWrite(id, "\r"); pardes.panes.terminal.noteCommand(pane, slot.line); @@ -993,6 +999,27 @@ test "a run's answer says cut when its output's start is gone, and reads a bound sh.put(p, c ++ "\x1b[?1049hwhole screen\x1b[?1049l\x1b]133;D;aid=pardes\x07" ++ prompt); try testing.expectEqualStrings("exit ?\n", sh.answered(p, node, h, into)); + // One killed there leaves the emulator on the alternate screen, and the + // shell draws its prompt there: not busy, and the next run is read + // whole from the primary screen, never `exit 0 cut` with nothing. + h = sh.send(p, node); + sh.put(p, c ++ "\x1b[?1049hhtop's screen\r\n"); + _ = call(p, .{ .tag = 4, .op = .release, .node = node, .handle = h }); + sh.put(p, "\x1b]133;D;137;aid=pardes\x07" ++ prompt); + try testing.expect(pardes.panes.terminal.onAlternateScreen(pane)); + try testing.expect(std.mem.endsWith(u8, rd(p, Node.of(serialOf(p), .pty_status), 0, 64).bytes, " 0\n")); + h = sh.send(p, node); + sh.put(p, c ++ "after htop\r\n" ++ d ++ prompt); + try testing.expectEqualStrings("exit 0\nafter htop\n", sh.answered(p, node, h, into)); + // Busy there with no prompt drawn, it says where it is. + h = sh.send(p, node); + sh.put(p, c ++ "\x1b[?1049hfull screen"); + const busy = call(p, .{ .tag = 1, .op = .open, .node = node }).reply.handle; + _ = call(p, .{ .tag = 2, .op = .write, .node = node, .handle = busy, .data = "y\n" }); + try testing.expectEqualStrings("busy alternate screen\n", sh.answered(p, node, busy, into)); + sh.put(p, "\x1b[?1049l" ++ d ++ prompt); + _ = sh.answered(p, node, h, into); + // A megabyte of output: read from at most `output_rows` rows above its // end (the history still holds its start), the last 64 KiB kept. h = sh.send(p, node); diff --git a/src/terminal.zig b/src/terminal.zig index a0337ce7..b7872d4e 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -1149,11 +1149,13 @@ pub fn greeting(pane: *const Pane) enum { wait, now, never } { return if (promptInputEmpty(pane)) .now else .never; } +/// At a prompt's input: on the alternate screen too, where a program killed +/// there (htop, `sig KILL`) left the emulator and the shell then drew its +/// prompt, marks and all. A full-screen program draws no prompt marks. pub fn promptInputReady(pane: *const Pane) bool { if (comptime !enabled) return false; const state = pane.terminal orelse return false; - return state.vt.screens.active_key != .alternate and - state.vt.screens.active.cursor.semantic_content == .input; + return state.vt.screens.active.cursor.semantic_content == .input; } pub const CommandOutput = struct { |
