fix(tui): preserve WezTerm Kitty images on full redraw
Reserve visible Kitty image rows before drawing placements in full redraws, while preserving the first-row path for taller-than-viewport images so #4415 stays fixed. Closes #5618.
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
|
||||
- Added terminal background color query support for OSC 11 replies.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed WezTerm inline Kitty image rendering during full redraw fallbacks so image padding rows are reserved before the placement is drawn without regressing tall-image placement ([#5618](https://github.com/earendil-works/pi/issues/5618), [#4415](https://github.com/earendil-works/pi/issues/4415)).
|
||||
|
||||
## [0.79.3] - 2026-06-13
|
||||
|
||||
## [0.79.2] - 2026-06-12
|
||||
|
||||
@@ -1244,7 +1244,20 @@ export class TUI extends Container {
|
||||
}
|
||||
for (let i = 0; i < newLines.length; i++) {
|
||||
if (i > 0) buffer += "\r\n";
|
||||
buffer += newLines[i];
|
||||
const line = newLines[i];
|
||||
const isImage = isImageLine(line);
|
||||
const imageReservedRows = isImage ? this.getKittyImageReservedRows(newLines, i) : 1;
|
||||
if (imageReservedRows > 1 && imageReservedRows <= height) {
|
||||
for (let row = 1; row < imageReservedRows; row++) {
|
||||
buffer += "\r\n";
|
||||
}
|
||||
buffer += `\x1b[${imageReservedRows - 1}A`;
|
||||
buffer += line;
|
||||
buffer += `\x1b[${imageReservedRows - 1}B`;
|
||||
i += imageReservedRows - 1;
|
||||
continue;
|
||||
}
|
||||
buffer += line;
|
||||
}
|
||||
buffer += "\x1b[?2026l"; // End synchronized output
|
||||
this.terminal.write(buffer);
|
||||
|
||||
@@ -152,6 +152,95 @@ describe("TUI Kitty image cleanup", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("reserves Kitty image rows before drawing during full redraw fallbacks", async () => {
|
||||
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
|
||||
setCellDimensions({ widthPx: 10, heightPx: 10 });
|
||||
try {
|
||||
const terminal = new LoggingVirtualTerminal(40, 5);
|
||||
const tui = new TUI(terminal);
|
||||
const component = new TestComponent();
|
||||
tui.addChild(component);
|
||||
|
||||
component.lines = ["l0", "l1", "l2", "l3", "l4"];
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
const redrawsBeforeImage = tui.fullRedraws;
|
||||
terminal.clearWrites();
|
||||
|
||||
const image = new Image(
|
||||
"AAAA",
|
||||
"image/png",
|
||||
{ fallbackColor: (value) => value },
|
||||
{ maxWidthCells: 3 },
|
||||
{ widthPx: 30, heightPx: 30 },
|
||||
);
|
||||
const imageLines = image.render(40);
|
||||
const imageSequence = imageLines[0];
|
||||
component.lines = ["l0", "l1", "l2", "l3", "l4", ...imageLines, "after"];
|
||||
tui.requestRender();
|
||||
await terminal.waitForRender();
|
||||
|
||||
const writes = terminal.getWrites();
|
||||
assert.ok(tui.fullRedraws > redrawsBeforeImage, "scrolling image append should force a full redraw");
|
||||
assert.ok(
|
||||
writes.includes(`\r\n\r\n\x1b[2A${imageSequence}\x1b[2B`),
|
||||
"full redraw should reserve visible image rows before drawing the placement",
|
||||
);
|
||||
assert.ok(
|
||||
!writes.includes(`${imageSequence}\r\n\x1b[0m`),
|
||||
"full redraw must not write reserved padding rows after drawing the placement",
|
||||
);
|
||||
|
||||
tui.stop();
|
||||
} finally {
|
||||
resetCapabilitiesCache();
|
||||
setCellDimensions({ widthPx: 9, heightPx: 18 });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not use cursor-up placement for Kitty images taller than the viewport", async () => {
|
||||
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
|
||||
setCellDimensions({ widthPx: 10, heightPx: 10 });
|
||||
try {
|
||||
const terminal = new LoggingVirtualTerminal(40, 5);
|
||||
const tui = new TUI(terminal);
|
||||
const component = new TestComponent();
|
||||
tui.addChild(component);
|
||||
|
||||
component.lines = ["before"];
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
terminal.clearWrites();
|
||||
|
||||
const image = new Image(
|
||||
"AAAA",
|
||||
"image/png",
|
||||
{ fallbackColor: (value) => value },
|
||||
{ maxWidthCells: 6 },
|
||||
{ widthPx: 60, heightPx: 60 },
|
||||
);
|
||||
const imageLines = image.render(40);
|
||||
const imageSequence = imageLines[0];
|
||||
assert.ok(imageLines.length > terminal.rows, "test image should exceed the viewport height");
|
||||
|
||||
component.lines = ["before", ...imageLines, "after"];
|
||||
tui.requestRender(true);
|
||||
await terminal.waitForRender();
|
||||
|
||||
const writes = terminal.getWrites();
|
||||
assert.ok(writes.includes(imageSequence), "image placement should be drawn");
|
||||
assert.ok(
|
||||
!writes.includes(`\x1b[${imageLines.length - 1}A${imageSequence}`),
|
||||
"taller-than-viewport images must keep the #4461 first-row placement path",
|
||||
);
|
||||
|
||||
tui.stop();
|
||||
} finally {
|
||||
resetCapabilitiesCache();
|
||||
setCellDimensions({ widthPx: 9, heightPx: 18 });
|
||||
}
|
||||
});
|
||||
|
||||
it("deletes changed image ids before drawing moved placements", async () => {
|
||||
const terminal = new LoggingVirtualTerminal(40, 10);
|
||||
const tui = new TUI(terminal);
|
||||
|
||||
Reference in New Issue
Block a user