summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 04:57:43 -0300
committerGabriel Schneider <[email protected]>2026-10-01 04:57:43 -0300
commit0c3cf0628adf9e22270b09d9c451b08ac07f558a (patch)
treec8034581e1c465056a5e5e32cb0fd15242178674
parent2488df529cde2994c37f385cefca4db1837ed9a3 (diff)
downloadpardes-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.zig27
-rw-r--r--src/terminal.zig6
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 {