summaryrefslogtreecommitdiff
path: root/src/term_pane.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/term_pane.zig')
-rw-r--r--src/term_pane.zig99
1 files changed, 77 insertions, 22 deletions
diff --git a/src/term_pane.zig b/src/term_pane.zig
index cc0cc24e..7662292e 100644
--- a/src/term_pane.zig
+++ b/src/term_pane.zig
@@ -954,14 +954,12 @@ fn fillBody(dst: ?[]u8, pane: *Pane, viewport: []const []const u8) usize {
return written;
}
-/// tty colors: recolor each body cell from the emulator's own style so shell
-/// output keeps its ansi colors (gated on the Colors toggle by the caller).
-/// Rows the edit buffer stands in for are OURS, not the emulator's: they keep
-/// the plain body style, and the rows under them are read from the shell row
-/// the surface actually shows there.
-pub fn recolorAnsi(p: *Pardes, pane: *Pane, r: pardes.Rect, tx: u16, tw: u16, body_h: u16, off: i32) void {
+/// tty colors: recolor each visible body cell from the emulator's own style so
+/// raw output keeps its ansi colors. Runs ONLY in tty mode (the caller gates
+/// it) and reads the live viewport row for row: normal/insert editing shows
+/// plain text, so nothing an edit does can move a shell row's colour.
+pub fn recolorAnsi(p: *Pardes, pane: *Pane, r: pardes.Rect, tx: u16, tw: u16, body_h: u16) void {
const s = &p.surface;
- const goff: i32 = @intCast(pane.vt.screens.active.pages.scrollbar().offset);
const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
var filtered_storage: FilteredColors = undefined;
const filtered: ?*FilteredColors = if (pane.tty_filter) blk: {
@@ -970,25 +968,15 @@ pub fn recolorAnsi(p: *Pardes, pane: *Pane, r: pardes.Rect, tx: u16, tw: u16, bo
} else null;
var vr: u16 = 0;
while (vr < body_h and vr < pane.rows) : (vr += 1) {
- const grid = pane.gridRow(off + @as(i32, vr)) - goff;
- if (pane.mode != .tty) if (pane.ovl) |o| {
- if (off + @as(i32, vr) >= o.row and off + @as(i32, vr) < o.row + @as(i32, @intCast(modal.lineCount(o.text)))) continue;
- };
- if (grid < 0) continue;
var c: u16 = 0;
while (c < tw) : (c += 1) {
- const ci = pane.vt.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(c), .y = @intCast(grid) } }) orelse continue;
- // bodyText already gave a wide glyph's empty surface tail the
- // head's body style. Ordinary ANSI projection historically leaves
- // that alone; Filter must replace it too or one half of the cell
- // can retain a host/default colour (Ghostty gives the tail the
- // same style id as its wide head).
+ const ci = pane.vt.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(c), .y = @intCast(vr) } }) orelse continue;
+ // Ghostty gives a wide glyph's spacer tail the head's style id;
+ // ordinary projection leaves it, a filter must repaint it too.
if (ci.cell.wide == .spacer_tail and filtered == null) continue;
const cell = s.at(tx + c, body_y + vr);
- // In ordinary ANSI mode retain the old sparse projection: bodyText
- // already painted every visible glyph. A filter paints the whole
- // VT viewport so even empty/default and background-only cells are
- // RGB-keyed by the theme rather than leaking the host defaults.
+ // sparse projection: bodyText already painted every glyph, so only
+ // a filter (which theme-keys blank/default cells too) touches these.
if (cell.default and filtered == null) continue;
cell.default = false;
cell.style = cellStyle(p, ci, filtered);
@@ -1340,6 +1328,73 @@ test "terminal Filter preserves exact palette-null light theme default roles" {
try testing.expectEqual(pardes.Color{ .rgb = light.bg.? }, reversed.at(tx, body_y).style.fg);
try testing.expectEqual(pardes.Color{ .rgb = light.fg.? }, reversed.at(tx, body_y).style.bg);
}
+test "tty ansi colors render only in tty mode, never in normal mode" {
+ const testing = std.testing;
+ const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 18, .rows = 6 });
+ defer p.deinit();
+ while (p.nextEffect()) |_| {}
+ const pane = p.panes[0].?;
+ pane.tty_filter = false;
+
+ p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b]133;A\x1b\\\x1b[32mPP\x1b]133;B\x1b\\\x1b[31mR\x1b[34mB\x1b[0m out" } });
+
+ var frame = std.heap.ArenaAllocator.init(testing.allocator);
+ defer frame.deinit();
+ const r = p.rects[0];
+ const tx = r.x + config.GUTTER;
+ const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const red: pardes.Color = .{ .index = 1 };
+ const blue: pardes.Color = .{ .index = 4 };
+
+ // tty mode projects the emulator's ansi colours cell for cell.
+ pane.mode = .tty;
+ p.shell_rows.stale = true;
+ const tty = try p.render(frame.allocator());
+ try testing.expectEqual(red, tty.at(tx + 2, body_y).style.fg);
+ try testing.expectEqual(blue, tty.at(tx + 3, body_y).style.fg);
+
+ // normal mode is a plain editing view: no ansi projection at all, so an
+ // edit made here cannot change what tty mode renders.
+ pane.mode = .normal;
+ p.shell_rows.stale = true;
+ _ = frame.reset(.retain_capacity);
+ const norm = try p.render(frame.allocator());
+ try testing.expectEqualStrings("R", norm.at(tx, body_y).grapheme());
+ try testing.expect(!std.meta.eql(red, norm.at(tx, body_y).style.fg));
+ try testing.expect(!std.meta.eql(blue, norm.at(tx + 1, body_y).style.fg));
+}
+
+test "an edit overlay never changes tty-mode ansi colors" {
+ const testing = std.testing;
+ const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 18, .rows = 6 });
+ defer p.deinit();
+ while (p.nextEffect()) |_| {}
+ const pane = p.panes[0].?;
+ pane.tty_filter = false;
+ pane.mode = .tty;
+
+ p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[31mAAA\x1b[0m\r\n\x1b[32mBBB\x1b[0m\r\n\x1b[34mCCC\x1b[0m" } });
+
+ var frame = std.heap.ArenaAllocator.init(testing.allocator);
+ defer frame.deinit();
+ const r = p.rects[0];
+ const tx = r.x + config.GUTTER;
+ const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const blue: pardes.Color = .{ .index = 4 };
+
+ p.shell_rows.stale = true;
+ const before = try p.render(frame.allocator());
+ try testing.expectEqualStrings("C", before.at(tx, body_y + 2).grapheme());
+ try testing.expectEqual(blue, before.at(tx, body_y + 2).style.fg);
+
+ // A lingering multi-line edit overlay must not move any shell row's colour.
+ pane.ovl = .{ .row = 0, .rows = 1, .text = try p.gpa.dupe(u8, "e\nd\ni\nt") };
+ p.shell_rows.stale = true;
+ _ = frame.reset(.retain_capacity);
+ const after = try p.render(frame.allocator());
+ try testing.expectEqualStrings("C", after.at(tx, body_y + 2).grapheme());
+ try testing.expectEqual(blue, after.at(tx, body_y + 2).style.fg);
+}
pub fn palColor(p: *Pardes, idx: u8) pardes.Color {
if (p.theme().palette) |pal| if (idx < 16) return .{ .rgb = pal[idx] };