fix(tui): place image correctly when viewport height < image height (#4461)

fixes #4415
This commit is contained in:
xu0o0
2026-05-13 20:37:48 +08:00
committed by GitHub
parent e0b5d27af2
commit c08c462461
2 changed files with 27 additions and 18 deletions

View File

@@ -82,19 +82,28 @@ export class Image implements Component {
this.imageId = result.imageId;
}
// Return `rows` lines so TUI accounts for image height.
// First (rows-1) lines are empty and cleared before the image is drawn.
// Last line: move cursor back up, draw the image, then move back down
// for Kitty (this component disables Kitty's terminal-side cursor movement)
// so TUI cursor accounting stays inside the scroll area.
lines = [];
for (let i = 0; i < result.rows - 1; i++) {
lines.push("");
if (caps.images === "kitty") {
// For Kitty: C=1 prevents cursor movement.
// Don't need the cursor movement.
lines = [result.sequence];
// Return `rows` lines so TUI accounts for image height.
for (let i = 0; i < result.rows - 1; i++) {
lines.push("");
}
} else {
// Return `rows` lines so TUI accounts for image height.
// First (rows-1) lines are empty and cleared before the image is drawn.
// Last line: move cursor back up, draw the image, then move back down
// so TUI cursor accounting stays inside the scroll area.
lines = [];
for (let i = 0; i < result.rows - 1; i++) {
lines.push("");
}
const rowOffset = result.rows - 1;
const moveUp = rowOffset > 0 ? `\x1b[${rowOffset}A` : "";
lines.push(moveUp + result.sequence);
}
const rowOffset = result.rows - 1;
const moveUp = rowOffset > 0 ? `\x1b[${rowOffset}A` : "";
const moveDown = caps.images === "kitty" && rowOffset > 0 ? `\x1b[${rowOffset}B` : "";
lines.push(moveUp + result.sequence + moveDown);
} else {
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
lines = [this.theme.fallbackColor(fallback)];

View File

@@ -312,7 +312,7 @@ describe("Kitty image cursor movement", () => {
}
});
it("restores the cursor to the reserved image row after Kitty rendering", () => {
it("places image sequence on first line with empty padding rows", () => {
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
setCellDimensions({ widthPx: 10, heightPx: 10 });
try {
@@ -326,11 +326,11 @@ describe("Kitty image cursor movement", () => {
const lines = image.render(4);
const imageId = image.getImageId();
assert.strictEqual(typeof imageId, "number");
assert.deepStrictEqual(lines.slice(0, -1), [""]);
assert.ok(lines[1].startsWith("\x1b[1A\x1b_G"));
assert.ok(lines[1].includes(",C=1,"));
assert.ok(lines[1].includes(`,i=${imageId}`));
assert.ok(lines[1].endsWith("\x1b[1B"));
assert.ok(lines[0].startsWith("\x1b_G"));
assert.ok(lines[0].includes(",C=1,"));
assert.ok(lines[0].includes(`,i=${imageId}`));
assert.ok(lines[0].endsWith("\x1b\\"));
assert.deepStrictEqual(lines.slice(1, lines.length), [""]);
} finally {
resetCapabilitiesCache();
setCellDimensions({ widthPx: 9, heightPx: 18 });