Impelement cursor movement after image placement

This commit is contained in:
Anthony Kim
2026-02-09 14:30:35 -08:00
parent c11a8f43f4
commit a4502a6d00
4 changed files with 81 additions and 94 deletions
@@ -373,7 +373,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler {
const id = cmd.id ?? this._nextImageId - 1;
const image = this._images.get(id);
if (image) {
const result = this._displayImage(image, cmd.columns, cmd.rows);
const result = this._displayImage(image, cmd);
if (cmd.id !== undefined) {
return (result as Promise<boolean>).then(r => {
this._sendResponse(id, 'OK', cmd.quiet ?? 0);
@@ -471,31 +471,39 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler {
// Image display
private _displayImage(image: IKittyImageData, columns?: number, rows?: number): boolean | Promise<boolean> {
return this._decodeAndDisplay(image, columns, rows)
private _displayImage(image: IKittyImageData, cmd: IKittyCommand): boolean | Promise<boolean> {
return this._decodeAndDisplay(image, cmd)
.then(() => true)
.catch(() => true);
}
private async _decodeAndDisplay(image: IKittyImageData, columns?: number, rows?: number): Promise<void> {
private async _decodeAndDisplay(image: IKittyImageData, cmd: IKittyCommand): Promise<void> {
const bitmap = await this._createBitmap(image);
const cw = this._renderer.dimensions?.css.cell.width || CELL_SIZE_DEFAULT.width;
const ch = this._renderer.dimensions?.css.cell.height || CELL_SIZE_DEFAULT.height;
// Per spec: c/r default to image's natural cell dimensions
const imgCols = cmd.columns ?? Math.ceil(bitmap.width / cw);
const imgRows = cmd.rows ?? Math.ceil(bitmap.height / ch);
let w = bitmap.width;
let h = bitmap.height;
if (columns || rows) {
const cw = this._renderer.dimensions?.css.cell.width || CELL_SIZE_DEFAULT.width;
const ch = this._renderer.dimensions?.css.cell.height || CELL_SIZE_DEFAULT.height;
if (columns) w = columns * cw;
if (rows) h = rows * ch;
if (columns && !rows) h = Math.round(w * (bitmap.height / bitmap.width));
else if (rows && !columns) w = Math.round(h * (bitmap.width / bitmap.height));
// Scale bitmap to fit placement rectangle when c/r are specified
if (cmd.columns !== undefined || cmd.rows !== undefined) {
w = Math.round(imgCols * cw);
h = Math.round(imgRows * ch);
}
if (w * h > this._opts.pixelLimit) return;
// Save cursor position before addImage modifies it
const buffer = this._coreTerminal._core.buffer;
const savedX = buffer.x;
const savedY = buffer.y;
const savedYbase = buffer.ybase;
let storageId: number;
if (w !== bitmap.width || h !== bitmap.height) {
const resized = await createImageBitmap(bitmap, { resizeWidth: w, resizeHeight: h });
@@ -505,21 +513,19 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler {
}
this._kittyIdToStorageId.set(image.id, storageId);
// TODO: Implement cursor movement per Kitty graphics protocol spec
// Per spec: "After placing an image on the screen the cursor must be moved to the
// right by the number of cols in the image placement rectangle and down by the
// number of rows in the image placement rectangle."
//
// Default behavior (C=0 or unspecified): Move cursor by cols/rows
// With C=1: Don't move cursor at all
//
// Implementation would need:
// 1. Get placement.C value (cursor movement policy)
// 2. Calculate cols = placement.columns || Math.ceil(w / cellWidth)
// 3. Calculate rows = placement.rows || Math.ceil(h / cellHeight)
// 4. If C !== 1: Move cursor right by cols and down by rows
// this._bufferService.buffer.x += cols;
// this._bufferService.buffer.y += rows;
// Kitty cursor movement
// Per spec: cursor placed at first column after last image column,
// on the last row of the image. C=1 means don't move cursor.
if (cmd.cursorMovement === 1) {
// C=1: restore cursor to position before image was placed
const scrolled = buffer.ybase - savedYbase;
buffer.x = savedX;
buffer.y = Math.max(savedY - scrolled, 0);
} else {
// Default (C=0): advance cursor horizontally past the image
// addImage already positioned cursor on the last row via lineFeeds
buffer.x = Math.min(savedX + imgCols, this._coreTerminal.cols);
}
}
/**