Compare commits

...
Author SHA1 Message Date
Daniel ImmsandCursor c3d35fcea9 fix(InputHandler): clear wrap below scroll margins after column ops
scrollLeft, scrollRight, insertColumns, and deleteColumns set
isWrapped=false on every line inside the scroll margins but left
the first line below scrollBottom unchanged. A soft wrap spanning
the bottom margin and the next row could leave a stale isWrapped
flag on the line below, breaking wrapped string translation.

Clear isWrapped on the buffer line below the bottom margin after
each of these column operations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 03:36:34 +00:00
Daniel ImmsandCursor b1ecd216f2 fix(InputHandler): clear wrap after EL1 full-line erase
CSI EL Ps=1 erases from the start of the row through the cursor.
When the cursor is in the last column, that clears the entire row but
did not clear isWrapped on the following line. eraseInDisplay Ps=1
already handled this case with ybase-aware indexing.

Mirror that behavior in eraseInLine so soft-wrap chains stay
consistent after erase-to-left at end of line.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 03:36:34 +00:00
Daniel ImmsandCursor ade9a5a091 fix(InputHandler): use ybase when clearing wrap after ED 1
eraseInDisplay Ps=1 clears from the start of the viewport through the
cursor. When the cursor is in the last column, the following line's
isWrapped flag must be cleared because the erased line can no longer
continue a wrapped row.

_eraseInBufferLine already addresses buffer lines with ybase + y, but
the next-line lookup used lines.get(j + 1) without ybase. With
scrollback (ybase > 0), that touched the wrong line and left incorrect
wrap state on the line below the cursor.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 03:36:34 +00:00
2 changed files with 33 additions and 1 deletions
+7
View File
@@ -475,6 +475,13 @@ 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;
+26 -1
View File
@@ -1202,6 +1202,21 @@ 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).
@@ -1246,7 +1261,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(j + 1);
const nextLine = this._activeBuffer.lines.get(this._activeBuffer.ybase + j + 1);
if (nextLine) {
nextLine.isWrapped = false;
}
@@ -1324,6 +1339,12 @@ 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);
@@ -1515,6 +1536,7 @@ 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;
}
@@ -1548,6 +1570,7 @@ 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;
}
@@ -1571,6 +1594,7 @@ 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;
}
@@ -1594,6 +1618,7 @@ 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;
}