diff options
Diffstat (limited to 'src/term_pane.zig')
| -rw-r--r-- | src/term_pane.zig | 76 |
1 files changed, 70 insertions, 6 deletions
diff --git a/src/term_pane.zig b/src/term_pane.zig index 9f13832f..1243dfa1 100644 --- a/src/term_pane.zig +++ b/src/term_pane.zig @@ -18,6 +18,7 @@ const Pane = pardes.Pane; const EditSnap = pardes.EditSnap; const Ovl = pardes.Ovl; const modal = @import("modal.zig"); +const config = @import("config.zig"); /// The memo behind `shellRows`. ONE entry for the editor, because the motion /// surface is built for the pane the cursor is in and a second pane asking @@ -78,6 +79,65 @@ const Rows = struct { rows: [][]const u8, }; +/// What LEAVING raw tty mode does to one prompt row, decided from its cells +/// alone. See config.tty_blank for why any of this happens. +pub const PromptCut = union(enum) { + /// show the row exactly as ghostty dumped it + keep, + /// show nothing at all + blank, + /// drop this many leading COLUMNS — the prompt — and keep the rest, which + /// is what was typed at it + cut: usize, +}; + +/// The prompt and the command typed at it share a grid row, and OSC 133 marks +/// them apart CELL by cell (`Cell.semantic_content` is output / input / +/// prompt). The row flag every caller tests first is only ghostty's "some cell +/// in here is a prompt cell" index; taking the row on that flag alone is what +/// used to throw the command away with the prompt. +pub fn promptCut(pin: ghostty_vt.Pin) PromptCut { + if (config.tty_blank == .prompt_and_input) return .blank; + const cells = pin.cells(.all); + var cols: usize = 0; + while (cols < cells.len and cells[cols].semantic_content == .prompt) cols += 1; + // Flagged, but with no prompt cells at the FRONT: a right-side prompt, or + // a repaint that has moved on. Nothing here is the prompt, so hide nothing. + if (cols == 0) return .keep; + // ...and all prompt, nothing typed yet: the row is chrome end to end. + if (cols >= cells.len) return .blank; + return .{ .cut = cols }; +} + +/// That decision applied to `raw`, the line ghostty dumped for `pin`'s row. +/// Always a slice OF `raw` — dropping the prompt is a left-hug, so the command +/// starts at column 0 with no run of blanks in front of it where the prompt +/// used to be, and there is nothing to allocate or copy anywhere. +/// +/// Walking the dump rather than rebuilding the row out of cells keeps ghostty +/// the single authority on how a cell spells itself — wide glyphs, combining +/// marks and all. One non-spacer cell is one dumped grapheme, and that is what +/// makes the cell walk and the byte walk stay in step. +pub fn promptRow(pin: ghostty_vt.Pin, raw: []const u8) []const u8 { + const cols = switch (promptCut(pin)) { + .keep => return raw, + .blank => return "", + .cut => |n| n, + }; + const cells = pin.cells(.all); + var at: usize = 0; + var col: usize = 0; + while (col < cols and at < raw.len) { + const cell = &cells[col]; + var cps: usize = 1; + if (pin.grapheme(cell)) |extra| cps += extra.len; + for (0..cps) |_| at = modal.nextGrapheme(raw, at); + // the tail cell of a wide glyph spells nothing of its own + col += if (cell.wide == .wide) @as(usize, 2) else 1; + } + return std.mem.trimEnd(u8, raw[at..], " \t"); +} + /// A terminal's shell rows as the surface sees them: the WHOLE /// history+active grid, prompt rows blanked (OSC 133), absolute grid rows /// from 0. The raw material the motion surface is composed from — the @@ -130,8 +190,10 @@ fn buildRows(alloc: std.mem.Allocator, p: *Pardes, pane: *Pane) !Rows { var n: usize = 0; var it = std.mem.splitScalar(u8, full, '\n'); while (it.next()) |raw| { - const is_prompt = if (pit.next()) |pin| pin.rowAndCell().row.semantic_prompt != .none else false; - const shown = if (is_prompt) "" else raw; + const shown = if (pit.next()) |pin| + if (pin.rowAndCell().row.semantic_prompt != .none) promptRow(pin, raw) else raw + else + raw; if (n > 0) { text[at] = '\n'; at += 1; @@ -159,11 +221,13 @@ pub fn bodyText(arena: std.mem.Allocator, pane: *Pane) ![]const u8 { var lines = std.mem.splitScalar(u8, raw, '\n'); var n: usize = 0; while (lines.next()) |ln| { - const is_prompt = if (pane.mode != .tty) - if (prompts.next()) |pin| pin.rowAndCell().row.semantic_prompt != .none else false + vp[n] = if (pane.mode != .tty) + if (prompts.next()) |pin| + if (pin.rowAndCell().row.semantic_prompt != .none) promptRow(pin, ln) else ln + else + ln else - false; - vp[n] = if (is_prompt) "" else ln; + ln; n += 1; } std.debug.assert(n == vp.len); |
