diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 19:37:05 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 7f31e62b63a64cef3916aac21417b7b1414000aa (patch) | |
| tree | 75ae00e196e7eff22fc4659b512b12ce81f10cf6 /src/panes.zig | |
| parent | 594970f18dee92308fd1554663cc408cda4d102c (diff) | |
| download | pardes-7f31e62b63a64cef3916aac21417b7b1414000aa.tar.gz pardes-7f31e62b63a64cef3916aac21417b7b1414000aa.zip | |
pty/run answers with the command's output; pin cloud9 with 9ns concurrency and E.BUSY
The answer is now the header line (exit N, or exit N cut M when only the
last 64 KiB were kept, or exit N cut when its start scrolled out of the
history), then what the command printed: the screen text between its C and
D marks, which the marks handler pins so scrolling keeps them.
Also from review: a plain /log open honours its offset until follow is
written, so tail -n and less work; whitespace-only run lines are refused.
cloud9 is pinned at 82d8152c: 9ns keeps up to 32 requests in flight per
mount so a waiting read no longer freezes the rest of it, and E.BUSY
replaces the errno pardes spelled locally.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/panes.zig')
| -rw-r--r-- | src/panes.zig | 37 |
1 files changed, 37 insertions, 0 deletions
diff --git a/src/panes.zig b/src/panes.zig index 410492b8..68e03a04 100644 --- a/src/panes.zig +++ b/src/panes.zig @@ -6526,11 +6526,26 @@ pub const Terminal = struct { /// The newest prompt continues an unfinished line (bash's PS2, marked /// `k=c`): the shell still holds the start of the command. continuation: bool = false, + /// Where the newest command's output began (its C) and ended (its + /// D) on the primary screen, pinned so scrolling keeps them. + out_start: ?*ghostty_vt.Pin = null, + out_end: ?*ghostty_vt.Pin = null, + /// The newest command to end: which one, and its status if given. finished: ?struct { seq: u32, status: ?i32 } = null, const Action = ghostty_vt.TerminalStream.Action; + /// Move one of the output pins to the cursor. Only the primary screen + /// is pinned: a command's own full-screen display is not its output. + fn pinCursor(self: *@This(), slot: *?*ghostty_vt.Pin, now: bool) void { + const screen = self.inner.terminal.screens.get(.primary) orelse return; + if (slot.*) |old| screen.pages.untrackPin(old); + slot.* = null; + if (!now or self.inner.terminal.screens.active_key != .primary) return; + slot.* = screen.pages.trackPin(screen.cursor.page_pin.*) catch null; + } + pub fn vt(self: *@This(), comptime action: Action.Tag, value: Action.Value(action)) void { if (comptime action == .semantic_prompt) mark: { const aid = value.readOption(.aid) orelse break :mark; @@ -6549,10 +6564,13 @@ pub const Terminal = struct { .end_input_start_output => if (self.phase == .input) { self.phase = .running; self.started +%= 1; + self.pinCursor(&self.out_start, true); + self.pinCursor(&self.out_end, false); }, .end_command => if (self.phase == .running) { self.phase = .none; self.finished = .{ .seq = self.started, .status = value.readOption(.exit_code) }; + self.pinCursor(&self.out_end, true); } else { self.declined +%= 1; }, @@ -7357,6 +7375,25 @@ pub const Terminal = struct { state.vt.screens.active.cursor.semantic_content == .input; } + /// What the newest finished command printed: the screen text between its + /// C and D marks, owned by the caller. When its start has scrolled out of + /// the history, ghostty moved that pin to the oldest row it kept (and + /// flagged it `garbage`), so this is the part that survived. + pub fn commandOutput(pane: *Pane, gpa: std.mem.Allocator) ![:0]const u8 { + if (comptime enabled) printed: { + const state = pane.terminal orelse break :printed; + const start = state.stream.handler.out_start orelse break :printed; + const end = state.stream.handler.out_end orelse break :printed; + if (end.garbage) break :printed; + const screen = state.vt.screens.get(.primary) orelse break :printed; + // D is where the next prompt begins: the output ends a cell before. + const last = end.leftWrap(1) orelse break :printed; + if (start.eql(end.*) or last.before(start.*)) break :printed; + return try screen.selectionString(gpa, .{ .sel = ghostty_vt.Selection.init(start.*, last, false) }); + } + return try gpa.dupeZ(u8, ""); // it printed nothing + } + /// At a prompt with nothing typed on its line: no input left of the /// cursor and none under it (text typed and then Ctrl-A'd starts at the /// cursor). Nothing further right counts: that is where fish draws a |
