diff options
Diffstat (limited to 'src/panes.zig')
| -rw-r--r-- | src/panes.zig | 74 |
1 files changed, 69 insertions, 5 deletions
diff --git a/src/panes.zig b/src/panes.zig index 156d443a..3b1bb1b4 100644 --- a/src/panes.zig +++ b/src/panes.zig @@ -6502,7 +6502,59 @@ pub const Terminal = struct { pub const history_max = if (enabled) 64 else 8; pub const VtSlot = if (enabled) ghostty_vt.Terminal else void; - pub const StreamSlot = if (enabled) ghostty_vt.TerminalStream else void; + pub const StreamSlot = if (enabled) ghostty_vt.Stream(MarkedHandler) else void; + + /// The `aid` pardes's injected bash and fish marks carry (host_io.zig). + pub const prompt_aid = "pardes"; + + /// ghostty's handler, plus where the shell is in its command cycle as + /// its OSC 133 marks tell it: ghostty parses D's exit status and drops + /// it, and /pane/<n>/pty/run waits on exactly that. Only marks carrying + /// pardes's own `aid` move it, so fish's native marks, a nested shell's + /// and a stray `133;D` in some file being printed all pass it by. + pub const MarkedHandler = if (enabled) struct { + inner: ghostty_vt.TerminalStream.Handler, + phase: enum { none, prompt, input, running } = .none, + /// Counts commands started (C at the input phase). + started: u32 = 0, + /// 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, + /// 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; + + 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; + if (!std.mem.eql(u8, aid, prompt_aid)) break :mark; + switch (value.action) { + .fresh_line_new_prompt => self.phase = .prompt, + .end_prompt_start_input => { + self.phase = .input; + self.prompts +%= 1; + }, + // bash's DEBUG trap also fires on its own PROMPT_COMMAND, + // outside the input phase; only a C that ends input counts + .end_input_start_output => if (self.phase == .input) { + self.phase = .running; + self.started +%= 1; + }, + .end_command => if (self.phase == .running) { + self.phase = .none; + self.finished = .{ .seq = self.started, .status = value.readOption(.exit_code) }; + }, + else => {}, + } + } + self.inner.vt(action, value); + } + + pub fn deinit(self: *@This()) void { + self.inner.deinit(); + } + } else void; pub const Replay = struct { bytes: [1024 * 1024]u8 = undefined, @@ -6784,12 +6836,12 @@ pub const Terminal = struct { .rows = rows, .max_scrollback = 16 * 1024 * 1024, }); - state.stream = state.vt.vtStream(); + state.stream = .initAlloc(gpa, .{ .inner = state.vt.vtHandler() }); state.replay.head = 0; state.replay.len = 0; state.reply_len = 0; - state.stream.handler.effects.write_pty = ptyReport; - state.stream.handler.effects.device_attributes = ptyDeviceAttrs; + state.stream.handler.inner.effects.write_pty = ptyReport; + state.stream.handler.inner.effects.device_attributes = ptyDeviceAttrs; pane.terminal = state; pane.tty_filter = true; return pane; @@ -6938,7 +6990,7 @@ pub const Terminal = struct { while (p.nextEffect()) |_| {} const pane = p.panes[0].?; const state = pane.terminal.?; - try std.testing.expect(state.stream.handler.terminal == &state.vt); + try std.testing.expect(state.stream.handler.inner.terminal == &state.vt); p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[" } }); while (p.nextEffect()) |effect| { if (effect == .write) return error.PrematureTerminalReply; @@ -7294,6 +7346,18 @@ 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. + 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| + if (cell.semantic_content == .input and !cell.isEmpty()) return false; + return true; + } + /// Encode one key for the program that owns a raw terminal and queue its pty /// write, respecting the application's keyboard protocol and terminal modes. pub fn forwardKey(p: *Pardes, id: usize, key: Key) void { |
