Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
97a5069
Refactor so isWrapped isn't public writeable.
PerBothner Mar 31, 2026
1c58f1f
Merge branch 'master' into setWrapped
PerBothner Apr 6, 2026
e9ea22f
Fix MockBuffer.setWrapped.
PerBothner Apr 6, 2026
bd79ccc
Move "data" from BufferLine to new LogicalLine class.
PerBothner Apr 7, 2026
45d9f7a
Merge branch 'master' into LogicalLine
PerBothner May 4, 2026
ab5d344
Merge remote-tracking branch 'upstream/master' into LogicalLine
PerBothner Jun 7, 2026
b469616
Un-remove whitespace to match upsteam.
PerBothner Jun 7, 2026
d41cc7d
Merge branch 'master' into LogicalLine
PerBothner Aug 14, 2026
b1edb57
Optimize _resizeData to need fewer (re-)allocations
PerBothner Aug 16, 2026
69bb5a2
Merge remote-tracking branch 'upstream/master' into LogicalLine
PerBothner Aug 22, 2026
ce06560
New LineBuffer.setCellsFromCodepoints
PerBothner Aug 24, 2026
7a3fa48
Allocate sucessive lines in normal buffer from large block.
PerBothner Aug 28, 2026
d86580c
Merge remote-tracking branch 'upstream/master' into LogicalLine
PerBothner Aug 28, 2026
fbc57f6
Minor optimization - don't check for url links on each character
PerBothner Aug 29, 2026
3ce563c
Use recycled lines from CircularList trimming
PerBothner Aug 29, 2026
1493c24
Merge remote-tracking branch 'upstream/master' into LogicalLine
PerBothner Aug 31, 2026
1ccc47e
Use new COLOR_MASK constant when setting LogicalLine's backgroundColor.
PerBothner Sep 1, 2026
a3be9ce
Fix missing addition of LogicalLine._dataStart in setCellFromCodepoint.
PerBothner Sep 1, 2026
b2a36f5
Change getExtended to return undefined when there is no ExtendedAttrs
PerBothner Sep 1, 2026
e53f2d3
Fix issue in setCellFromCodepoint reported by lint
PerBothner Sep 1, 2026
6ce5d6b
Fixes to integration tests.
PerBothner Sep 1, 2026
95b05e8
Move BufferColumn and LogicalColumn to Types.ts.
PerBothner Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/browser/TestUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ export class MockBuffer implements IBuffer {
public clearAllMarkers(): void {
throw new Error('Method not implemented.');
}
public setWrapped(row: number, value: boolean): void {
throw new Error('Method not implemented.');
}
}

export class MockRenderer implements IRenderer {
Expand Down
12 changes: 6 additions & 6 deletions src/browser/services/SelectionService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ describe('SelectionService', () => {
it('should expand upwards or downards for wrapped lines', () => {
buffer.lines.set(0, stringToRow(' foo'));
buffer.lines.set(1, stringToRow('bar '));
buffer.lines.get(1)!.isWrapped = true;
buffer.setWrapped(1, true);
selectionService.selectWordAt([1, 1]);
assert.equal(selectionService.selectionText, 'foobar');
selectionService.model.clearSelection();
Expand All @@ -207,10 +207,10 @@ describe('SelectionService', () => {
buffer.lines.set(2, stringToRow('bbbbbbbbbbbbbbbbbbbb'));
buffer.lines.set(3, stringToRow('cccccccccccccccccccc'));
buffer.lines.set(4, stringToRow('bar '));
buffer.lines.get(1)!.isWrapped = true;
buffer.lines.get(2)!.isWrapped = true;
buffer.lines.get(3)!.isWrapped = true;
buffer.lines.get(4)!.isWrapped = true;
buffer.setWrapped(1, true);
buffer.setWrapped(2, true);
buffer.setWrapped(3, true);
buffer.setWrapped(4, true);
selectionService.selectWordAt([18, 0]);
assert.equal(selectionService.selectionText, expectedText);
selectionService.model.clearSelection();
Expand Down Expand Up @@ -349,8 +349,8 @@ describe('SelectionService', () => {
it('should select the entire wrapped line', () => {
buffer.lines.set(0, stringToRow('foo'));
const line2 = stringToRow('bar');
line2.isWrapped = true;
buffer.lines.set(1, line2);
buffer.setWrapped(1, true);
selectionService.selectLineAt(0);
assert.equal(selectionService.selectionText, 'foobar', 'The selected text is correct');
assert.deepEqual(selectionService.model.selectionStart, [0, 0]);
Expand Down
14 changes: 0 additions & 14 deletions src/common/CircularList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,6 @@ export class CircularList<T> extends Disposable implements ICircularList<T> {
}
}

/**
* Advance ringbuffer index and return current element for recycling.
* Note: The buffer must be full for this method to work.
* @throws When the buffer is not full.
*/
public recycle(): T {
if (this._length !== this._maxLength) {
throw new Error('Can only recycle when the buffer is full');
}
this._startIndex = ++this._startIndex % this._maxLength;
this.onTrimEmitter.fire(1);
return this._array[this._getCyclicIndex(this._length - 1)]!;
}

/**
* Ringbuffer is at max length.
*/
Expand Down
9 changes: 6 additions & 3 deletions src/common/InputHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,8 +459,10 @@ describe('InputHandler', () => {
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
assert.equal(bufferService.buffer.lines.get(3)!.isWrapped, true);
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);assert.equal(bufferService.buffer.lines.get(3)!.isWrapped, false);
bufferService.buffer.y = 2;
bufferService.buffer.x = 0;
inputHandler.eraseInLine(Params.fromArray([0]));
Expand All @@ -471,14 +473,15 @@ describe('InputHandler', () => {
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
assert.equal(bufferService.buffer.lines.get(3)!.isWrapped, true);

// params[2] - erase complete line
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([2]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);assert.equal(bufferService.buffer.lines.get(3)!.isWrapped, false);
});
it('ED2 with scrollOnEraseInDisplay turned on', async () => {
const inputHandler = new TestInputHandler(
Expand Down
99 changes: 48 additions & 51 deletions src/common/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,8 @@ export class InputHandler extends Disposable implements IInputHandler {
// autowrap - DECAWM
// automatically wraps to the beginning of the next line
if (wraparoundMode) {
const oldRow = bufferRow;
let oldCol = this._activeBuffer.x - oldWidth;
const oldRow = bufferRow as BufferLine;
const oldCol = this._activeBuffer.x - oldWidth;
this._activeBuffer.x = oldWidth;
this._activeBuffer.y++;
if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {
Expand All @@ -609,7 +609,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
// The line already exists (eg. the initial viewport), mark it as a
// wrapped line
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = true;
this._activeBuffer.setWrapped(this._activeBuffer.ybase + this._activeBuffer.y, true);
}
// row changed, get it again
bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);
Expand All @@ -623,9 +623,7 @@ export class InputHandler extends Disposable implements IInputHandler {
oldCol, 0, oldWidth, false);
}
// clear left over cells to the right
while (oldCol < cols) {
oldRow.setCellFromCodepoint(oldCol++, 0, 1, curAttr);
}
oldRow.eraseRight(oldCol);
} else {
this._activeBuffer.x = cols - 1;
if (chWidth === 2) {
Expand Down Expand Up @@ -773,7 +771,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// reprint is common, especially on resize. Note that the windowsMode wrapped line heuristics
// can mess with this so windowsMode should be disabled, which is recommended on Windows build
// 21376 and above.
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
this._activeBuffer.setWrapped(this._activeBuffer.ybase + this._activeBuffer.y, false);
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this._activeBuffer.x >= this._bufferService.cols) {
Expand Down Expand Up @@ -837,7 +835,7 @@ export class InputHandler extends Disposable implements IInputHandler {
&& this._activeBuffer.y > this._activeBuffer.scrollTop
&& this._activeBuffer.y <= this._activeBuffer.scrollBottom
&& this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) {
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
this._activeBuffer.setWrapped(this._activeBuffer.ybase + this._activeBuffer.y, false);
this._activeBuffer.y--;
this._activeBuffer.x = this._bufferService.cols - 1;
// find last taken cell - last cell can have 3 different states:
Expand Down Expand Up @@ -1183,25 +1181,34 @@ export class InputHandler extends Disposable implements IInputHandler {
/**
* Helper method to erase cells in a terminal row.
* The cell gets replaced with the eraseChar of the terminal.
* Clear isWrapped if start===0;
* clear isWrapped of next line if end >= cols.
* @param y The row index relative to the viewport.
* @param start The start x index of the range to be erased.
* @param end The end x index of the range to be erased (exclusive).
* @param clearWrap clear the isWrapped flag
* @param respectProtect Whether to respect the protection attribute (DECSCA).
*/
private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false, respectProtect: boolean = false): void {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y);
if (!line) {
private _eraseInBufferLine(y: number, start: number, end: number, respectProtect: boolean = false): void {
const yAbs = y + this._activeBuffer.ybase;
const line = this._activeBuffer.lines.get(yAbs);
if (! (line instanceof BufferLine)) {
return;
}
line.replaceCells(
start,
end,
this._activeBuffer.getNullCell(this._eraseAttrData()),
respectProtect
);
if (clearWrap) {
line.isWrapped = false;
if (! respectProtect && end >= this._bufferService.cols) {
const next = line.nextBufferLine;
if (next) next.asUnwrapped(line);
line.eraseRight(start);
line.logicalLine.backgroundColor = this._curAttrData.bg & ~0xFC000000;
} else {
line.replaceCells(
start,
end,
this._activeBuffer.getNullCell(this._eraseAttrData()),
respectProtect
);
}
if (start === 0) {
this._activeBuffer.setWrapped(this._activeBuffer.ybase + y, false);
}
}

Expand All @@ -1211,12 +1218,8 @@ export class InputHandler extends Disposable implements IInputHandler {
* @param y row index
*/
private _resetBufferLine(y: number, respectProtect: boolean = false): void {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y);
if (line) {
line.fill(this._activeBuffer.getNullCell(this._eraseAttrData()), respectProtect);
this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase + y);
line.isWrapped = false;
}
this._eraseInBufferLine(y, 0, this._bufferService.cols, respectProtect);
this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase + y);
}

/**
Expand Down Expand Up @@ -1250,7 +1253,7 @@ export class InputHandler extends Disposable implements IInputHandler {
case 0:
j = this._activeBuffer.y;
this._dirtyRowTracker.markDirty(j);
this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect);
this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, respectProtect);
for (; j < this._bufferService.rows; j++) {
this._resetBufferLine(j, respectProtect);
}
Expand All @@ -1260,14 +1263,7 @@ export class InputHandler extends Disposable implements IInputHandler {
j = this._activeBuffer.y;
this._dirtyRowTracker.markDirty(j);
// Deleted front part of line and everything before. This line will no longer be wrapped.
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);
if (nextLine) {
nextLine.isWrapped = false;
}
}
this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, respectProtect);
while (j--) {
this._resetBufferLine(j, respectProtect);
}
Expand Down Expand Up @@ -1337,13 +1333,13 @@ export class InputHandler extends Disposable implements IInputHandler {
this._restrictCursor(this._bufferService.cols);
switch (params.params[0]) {
case 0:
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect);
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, respectProtect);
break;
case 1:
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false, respectProtect);
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, respectProtect);
break;
case 2:
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true, respectProtect);
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, respectProtect);
break;
}
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
Expand Down Expand Up @@ -1528,9 +1524,10 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const param = params.params[0] || 1;
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
const row = this._activeBuffer.ybase + y;
const line = this._activeBuffer.lines.get(row)!;
line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
this._activeBuffer.setWrapped(row, false);
}
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
Expand Down Expand Up @@ -1561,9 +1558,10 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const param = params.params[0] || 1;
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
const row = this._activeBuffer.ybase + y;
const line = this._activeBuffer.lines.get(row)!;
this._activeBuffer.setWrapped(row, false);
line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
Expand All @@ -1584,9 +1582,10 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const param = params.params[0] || 1;
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
const row = this._activeBuffer.ybase + y;
this._activeBuffer.setWrapped(row, false);
const line = this._activeBuffer.lines.get(row)!;
line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
Expand All @@ -1607,9 +1606,10 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const param = params.params[0] || 1;
for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
const row = this._activeBuffer.ybase + y;
const line = this._activeBuffer.lines.get(row)!;
this._activeBuffer.setWrapped(row, false);
line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
Expand Down Expand Up @@ -3494,11 +3494,8 @@ export class InputHandler extends Disposable implements IInputHandler {
this._setCursor(0, 0);
for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) {
const row = this._activeBuffer.ybase + this._activeBuffer.y + yOffset;
const line = this._activeBuffer.lines.get(row);
if (line) {
line.fill(cell);
line.isWrapped = false;
}
this._activeBuffer.setWrapped(row, false);
this._activeBuffer.lines.get(row)?.fill(cell);
}
this._dirtyRowTracker.markAllDirty();
this._setCursor(0, 0);
Expand Down
4 changes: 1 addition & 3 deletions src/common/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ export interface ICircularList<T> {
get(index: number): T | undefined;
set(index: number, value: T): void;
push(value: T): void;
recycle(): T;
pop(): T | undefined;
splice(start: number, deleteCount: number, ...items: T[]): void;
trimStart(count: number): void;
Expand Down Expand Up @@ -224,7 +223,7 @@ export interface ICellData extends IAttributeData {
*/
export interface IBufferLine {
length: number;
isWrapped: boolean;
get isWrapped(): boolean;
get(index: number): CharData;
set(index: number, value: CharData): void;
loadCell(index: number, cell: ICellData): ICellData;
Expand All @@ -238,7 +237,6 @@ export interface IBufferLine {
cleanupMemory(): number;
fill(fillCellData: ICellData, respectProtect?: boolean): void;
copyFrom(line: IBufferLine): void;
clone(): IBufferLine;
getTrimmedLength(): number;
getNoBgTrimmedLength(): number;
translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string;
Expand Down
6 changes: 2 additions & 4 deletions src/common/WindowsMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ export function updateWindowsModeWrappedState(bufferService: IBufferService): vo
// wrapped.
const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1);
const lastChar = line?.get(bufferService.cols - 1);

const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y);
if (nextLine && lastChar) {
nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE);
if (lastChar) {
bufferService.buffer.setWrapped(bufferService.buffer.ybase + bufferService.buffer.y, lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE);
}
}
Loading
Loading