diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 18:20:06 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 594970f18dee92308fd1554663cc408cda4d102c (patch) | |
| tree | 91fc75e94713a3de8327f680b01ccc8f53b755d9 /src/panes.zig | |
| parent | 6f7beca8d27e2e00f7b90bb75015637a9aa4c950 (diff) | |
| download | pardes-594970f18dee92308fd1554663cc408cda4d102c.tar.gz pardes-594970f18dee92308fd1554663cc408cda4d102c.zip | |
Make pty/run and /log streams, and take refused lines back cleanly
Review fixes to pty/run and /log:
- Both are streams with a per-open cursor. A shell's exec 3<>file shares
one offset between write and read, so cat <&3 after echo make >&3 asked
for offset 5 and got "0" instead of "exit 0"; log after follow lost its
first bytes the same way.
- A run is accepted only in pardes's own tagged input phase, so a nested
shell's prompt (ssh, a shell with its own integration) is never taken for
this shell's.
- A line the shell refused is taken back so the next finds the prompt
clear: bash's continuation prompt (now tagged k=c) gets Ctrl-C and the
answer waits for the fresh prompt; a bash syntax error (no C, but a D) is
answered at once; fish's kept line gets Ctrl-U (a Ctrl-C sent while fish
redraws is lost). fish's right prompt no longer reads as typed input.
- bash marks a command's start from PS0 on bash 4.4+, not a DEBUG trap, so
a user's own DEBUG trap (bash-preexec, atuin) survives. bash's own job
notices now show as in plain bash (ttyfollow golden).
- 32 run slots; a new line on an open starts a fresh answer.
Checked live in bash, fish, and fish with a right prompt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/panes.zig')
| -rw-r--r-- | src/panes.zig | 23 |
1 files changed, 18 insertions, 5 deletions
diff --git a/src/panes.zig b/src/panes.zig index 3b1bb1b4..410492b8 100644 --- a/src/panes.zig +++ b/src/panes.zig @@ -6520,6 +6520,12 @@ pub const Terminal = struct { /// Counts prompts drawn (B), so a line the shell refused without /// running (a fish syntax error: no C, no D, a new prompt) shows. prompts: u32 = 0, + /// D marks outside a running command: the shell handled a line + /// without starting it (bash on a syntax error prints no C). + declined: u32 = 0, + /// 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, /// The newest command to end: which one, and its status if given. finished: ?struct { seq: u32, status: ?i32 } = null, @@ -6530,7 +6536,10 @@ pub const Terminal = struct { const aid = value.readOption(.aid) orelse break :mark; if (!std.mem.eql(u8, aid, prompt_aid)) break :mark; switch (value.action) { - .fresh_line_new_prompt => self.phase = .prompt, + .fresh_line_new_prompt => { + self.phase = .prompt; + self.continuation = value.readOption(.prompt_kind) == .continuation; + }, .end_prompt_start_input => { self.phase = .input; self.prompts +%= 1; @@ -6544,6 +6553,8 @@ pub const Terminal = struct { .end_command => if (self.phase == .running) { self.phase = .none; self.finished = .{ .seq = self.started, .status = value.readOption(.exit_code) }; + } else { + self.declined +%= 1; }, else => {}, } @@ -7346,14 +7357,16 @@ pub const Terminal = struct { state.vt.screens.active.cursor.semantic_content == .input; } - /// At a prompt with nothing typed on its line, wherever the cursor is - /// (someone may have typed and pressed Ctrl-A). fish suggests nothing on - /// an empty line, so its autosuggestions never make this false. + /// 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 + /// right prompt, and fish suggests nothing on an empty line. pub fn promptInputEmpty(pane: *const Pane) bool { if (comptime !enabled) return false; if (!promptInputReady(pane)) return false; const cursor = &pane.terminal.?.vt.screens.active.cursor; - for (cursor.page_pin.cells(.all)) |cell| + const row = cursor.page_pin.cells(.all); + for (row[0..@min(cursor.x + 1, row.len)]) |cell| if (cell.semantic_content == .input and !cell.isEmpty()) return false; return true; } |
