fix(tui): place image correctly when viewport height < image height (#4461)
fixes #4415
This commit is contained in:
@@ -82,19 +82,28 @@ export class Image implements Component {
|
|||||||
this.imageId = result.imageId;
|
this.imageId = result.imageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return `rows` lines so TUI accounts for image height.
|
if (caps.images === "kitty") {
|
||||||
// First (rows-1) lines are empty and cleared before the image is drawn.
|
// For Kitty: C=1 prevents cursor movement.
|
||||||
// Last line: move cursor back up, draw the image, then move back down
|
// Don't need the cursor movement.
|
||||||
// for Kitty (this component disables Kitty's terminal-side cursor movement)
|
lines = [result.sequence];
|
||||||
// so TUI cursor accounting stays inside the scroll area.
|
|
||||||
lines = [];
|
// Return `rows` lines so TUI accounts for image height.
|
||||||
for (let i = 0; i < result.rows - 1; i++) {
|
for (let i = 0; i < result.rows - 1; i++) {
|
||||||
lines.push("");
|
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 {
|
} else {
|
||||||
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
||||||
lines = [this.theme.fallbackColor(fallback)];
|
lines = [this.theme.fallbackColor(fallback)];
|
||||||
|
|||||||
@@ -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 });
|
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
|
||||||
setCellDimensions({ widthPx: 10, heightPx: 10 });
|
setCellDimensions({ widthPx: 10, heightPx: 10 });
|
||||||
try {
|
try {
|
||||||
@@ -326,11 +326,11 @@ describe("Kitty image cursor movement", () => {
|
|||||||
const lines = image.render(4);
|
const lines = image.render(4);
|
||||||
const imageId = image.getImageId();
|
const imageId = image.getImageId();
|
||||||
assert.strictEqual(typeof imageId, "number");
|
assert.strictEqual(typeof imageId, "number");
|
||||||
assert.deepStrictEqual(lines.slice(0, -1), [""]);
|
assert.ok(lines[0].startsWith("\x1b_G"));
|
||||||
assert.ok(lines[1].startsWith("\x1b[1A\x1b_G"));
|
assert.ok(lines[0].includes(",C=1,"));
|
||||||
assert.ok(lines[1].includes(",C=1,"));
|
assert.ok(lines[0].includes(`,i=${imageId}`));
|
||||||
assert.ok(lines[1].includes(`,i=${imageId}`));
|
assert.ok(lines[0].endsWith("\x1b\\"));
|
||||||
assert.ok(lines[1].endsWith("\x1b[1B"));
|
assert.deepStrictEqual(lines.slice(1, lines.length), [""]);
|
||||||
} finally {
|
} finally {
|
||||||
resetCapabilitiesCache();
|
resetCapabilitiesCache();
|
||||||
setCellDimensions({ widthPx: 9, heightPx: 18 });
|
setCellDimensions({ widthPx: 9, heightPx: 18 });
|
||||||
|
|||||||
Reference in New Issue
Block a user