79da83da
test: the grid's wide flags are pinned against ghostty's page cells
a73x 2026-09-04 18:04
Commit message
src/engine/engine.zig
| Old | New | ||
|---|---|---|---|
| @@ -1704,3 +1704,59 @@ test "grid oracle: the grid's dumpPlain and cursor agree with the engine's for e | |||
| 1704 | try std.testing.expectEqual(e.cursorPos().y, g.cursor.y); | 1704 | try std.testing.expectEqual(e.cursorPos().y, g.cursor.y); |
| 1705 | } | 1705 | } |
| 1706 | } | 1706 | } |
| 1707 | |||
| 1708 | // The wide flag is the half of a cell that `dumpPlain` cannot grade: a spacer | ||
| 1709 | // mis-tagged as narrow carries the same text and shifts every clip and every | ||
| 1710 | // highlight past it by one column. So this reads ghostty's own `cell.wide` | ||
| 1711 | // through the page, the way `encodeRowAt` reads it, and asks the mirrored grid | ||
| 1712 | // for the same answer at every column of every row. | ||
| 1713 | test "grid oracle: every cell's wide flag matches ghostty's page cell" { | ||
| 1714 | const alloc = std.testing.allocator; | ||
| 1715 | const cols: u16 = 12; | ||
| 1716 | const rows: u16 = 3; | ||
| 1717 | var e = try Engine.init(alloc, .{ .cols = cols, .rows = rows }); | ||
| 1718 | defer e.deinit(); | ||
| 1719 | // Row 0 puts wide glyphs at two different columns, so a rule that only | ||
| 1720 | // works at an even column fails here. Row 1 ends with a wide glyph that | ||
| 1721 | // does not fit its last column: ghostty leaves a spacer_head there and | ||
| 1722 | // wraps the glyph itself onto row 2. | ||
| 1723 | e.feed("ab\u{6f22}cd\u{1F600}z\r\n0123456789a\u{6f22}"); | ||
| 1724 | const g = try Grid.init(alloc, 1, 1); | ||
| 1725 | defer g.deinit(); | ||
| 1726 | try e.mirrorInto(g); | ||
| 1727 | |||
| 1728 | var seen = [_]bool{false} ** 4; | ||
| 1729 | var y: u16 = 0; | ||
| 1730 | while (y < rows) : (y += 1) { | ||
| 1731 | const screen = e.term.screens.active; | ||
| 1732 | const pin = screen.pages.pin(.{ .viewport = .{ .x = 0, .y = y } }).?; | ||
| 1733 | const page = &pin.node.data; | ||
| 1734 | const cells = page.getCells(pin.rowAndCell().row); | ||
| 1735 | var x: u16 = 0; | ||
| 1736 | while (x < cols) : (x += 1) { | ||
| 1737 | const want: proto.Wide = switch (cells[x].wide) { | ||
| 1738 | .narrow => .narrow, | ||
| 1739 | .wide => .wide, | ||
| 1740 | .spacer_tail => .spacer_tail, | ||
| 1741 | .spacer_head => .spacer_head, | ||
| 1742 | }; | ||
| 1743 | seen[@intFromEnum(want)] = true; | ||
| 1744 | const got = g.lines[y].cells[x].wide; | ||
| 1745 | if (got != want) { | ||
| 1746 | std.debug.print( | ||
| 1747 | "wide flag diverged at col {d} row {d}: engine says {s}, grid says {s}\n", | ||
| 1748 | .{ x, y, @tagName(want), @tagName(got) }, | ||
| 1749 | ); | ||
| 1750 | return error.WideFlagDiverged; | ||
| 1751 | } | ||
| 1752 | } | ||
| 1753 | } | ||
| 1754 | // Without this the loop above could be twelve narrow cells three times over | ||
| 1755 | // and grade nothing: the screen has to hold every shape it claims to. | ||
| 1756 | for (seen, 0..) |s, i| { | ||
| 1757 | if (!s) { | ||
| 1758 | std.debug.print("screen never held a {s} cell\n", .{@tagName(@as(proto.Wide, @enumFromInt(i)))}); | ||
| 1759 | return error.ScreenMissesAWideShape; | ||
| 1760 | } | ||
| 1761 | } | ||
| 1762 | } | ||