Compare commits

..
Author SHA1 Message Date
Cursor AgentandDaniel Imms 467eb5a227 fix(addon-image): cast buffer line lookups to IBufferLineExt | undefined
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-06-06 16:26:54 +00:00
Daniel ImmsandCursor a8632435a6 fix(addon-image): skip missing lines in viewportResize
viewportResize walks the full buffer when the terminal widens but
called line.getBg without checking that lines.get(row) returned a
line. During resize or trim gaps that could throw and abort tile
expansion for remaining rows.

Skip missing buffer lines with continue, consistent with render().

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 03:36:42 +00:00
Cursor AgentandDaniel Imms a0919b9004 fix(addon-image): skip missing buffer lines in render
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-06-03 03:36:41 +00:00
Daniel ImmsandCursor b5e0cdb20c fix(addon-image): scan new columns on viewport width increase
viewportResize() should check whether cells in newly exposed columns
(oldCol + 1 through cols - 1) already have content before expanding
image tiles to the right. The loop used `rightCol > metrics.cols`,
which is false when widening (rightCol starts at oldCol + 1), so the
loop never ran, hasData stayed false, and tiles could overwrite cells
that already held text.

Use `rightCol < metrics.cols` so the emptiness check runs over the
intended column range.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 03:36:41 +00:00
3 changed files with 8 additions and 37 deletions
+7 -4
View File
@@ -431,8 +431,8 @@ export class ImageStorage implements IDisposable {
// because text writes clear the BG flag but leave image tile data intact.
// This lets top-layer images survive text overwrites (kitty C=1 behavior).
for (let row = start; row <= end; ++row) {
const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt;
if (!line) return;
const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt | undefined;
if (!line) continue;
for (let col = 0; col < cols; ++col) {
let e: IExtendedAttrsImage;
if (line.getBg(col) & BgFlags.HAS_EXTENDED) {
@@ -513,7 +513,10 @@ export class ImageStorage implements IDisposable {
const rows = buffer.lines.length;
const oldCol = this._viewportMetrics.cols - 1;
for (let row = 0; row < rows; ++row) {
const line = buffer.lines.get(row) as IBufferLineExt;
const line = buffer.lines.get(row) as IBufferLineExt | undefined;
if (!line) {
continue;
}
if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) {
const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] ?? EMPTY_ATTRS;
const imageId = e.imageId;
@@ -531,7 +534,7 @@ export class ImageStorage implements IDisposable {
}
// expand only if right side is empty (nothing got wrapped from below)
let hasData = false;
for (let rightCol = oldCol + 1; rightCol > metrics.cols; ++rightCol) {
for (let rightCol = oldCol + 1; rightCol < metrics.cols; ++rightCol) {
if (line._data[rightCol * Cell.SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) {
hasData = true;
break;
-7
View File
@@ -475,13 +475,6 @@ describe('InputHandler', () => {
inputHandler.eraseInLine(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
// params[1] - full line erase at last column clears wrap on next line
await resetToBaseState();
bufferService.buffer.y = 1;
bufferService.buffer.x = bufferService.cols - 1;
inputHandler.eraseInLine(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
// params[2] - erase complete line
await resetToBaseState();
bufferService.buffer.y = 2;
+1 -26
View File
@@ -1202,21 +1202,6 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
/**
* Scroll-margin column operations clear isWrapped on lines inside the margins.
* Also clear the line below the bottom margin when it still continues a wrap chain.
*/
private _clearWrapBelowScrollMargins(): void {
const rowBelowMargin = this._activeBuffer.scrollBottom + 1;
if (rowBelowMargin >= this._bufferService.rows) {
return;
}
const lineBelow = this._activeBuffer.lines.get(this._activeBuffer.ybase + rowBelowMargin);
if (lineBelow) {
lineBelow.isWrapped = false;
}
}
/**
* CSI Ps J Erase in Display (ED).
* Ps = 0 -> Erase Below (default).
@@ -1261,7 +1246,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true, respectProtect);
if (this._activeBuffer.x + 1 >= this._bufferService.cols) {
// Deleted entire previous line. This next line can no longer be wrapped.
const nextLine = this._activeBuffer.lines.get(this._activeBuffer.ybase + j + 1);
const nextLine = this._activeBuffer.lines.get(j + 1);
if (nextLine) {
nextLine.isWrapped = false;
}
@@ -1339,12 +1324,6 @@ export class InputHandler extends Disposable implements IInputHandler {
break;
case 1:
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false, respectProtect);
if (this._activeBuffer.x + 1 >= this._bufferService.cols) {
const nextLine = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y + 1);
if (nextLine) {
nextLine.isWrapped = false;
}
}
break;
case 2:
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true, respectProtect);
@@ -1536,7 +1515,6 @@ export class InputHandler extends Disposable implements IInputHandler {
line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
this._clearWrapBelowScrollMargins();
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1570,7 +1548,6 @@ export class InputHandler extends Disposable implements IInputHandler {
line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
this._clearWrapBelowScrollMargins();
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1594,7 +1571,6 @@ export class InputHandler extends Disposable implements IInputHandler {
line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
this._clearWrapBelowScrollMargins();
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1618,7 +1594,6 @@ export class InputHandler extends Disposable implements IInputHandler {
line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
this._clearWrapBelowScrollMargins();
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}