fix(core): detect uninitialized lines (#24646)

This commit is contained in:
Jacob Richman
2026-04-03 17:51:29 -07:00
committed by GitHub
parent 65024d4538
commit ec35ebbe57
9 changed files with 52 additions and 9 deletions

View File

@@ -155,6 +155,7 @@ const createMockSerializeTerminalToObjectReturnValue = (
underline: false,
dim: false,
inverse: false,
isUninitialized: false,
fg: '#ffffff',
bg: '#000000',
},
@@ -173,6 +174,7 @@ const createExpectedAnsiOutput = (text: string | string[]): AnsiOutput => {
underline: false,
dim: false,
inverse: false,
isUninitialized: false,
fg: '',
bg: '',
} as AnsiToken,

View File

@@ -30,11 +30,12 @@ describe('terminalSerializer', () => {
allowProposedApi: true,
});
const result = serializeTerminalToObject(terminal);
expect(result).toHaveLength(24);
expect(result).toHaveLength(1);
result.forEach((line) => {
// Expect each line to be either empty or contain a single token with spaces
// Actually, the first cell will have inverse: true (cursor), so it will have multiple tokens
if (line.length > 0) {
expect(line[0].text.trim()).toBe('');
expect(line[line.length - 1].text.trim()).toBe('');
}
});
});

View File

@@ -12,6 +12,7 @@ export interface AnsiToken {
underline: boolean;
dim: boolean;
inverse: boolean;
isUninitialized: boolean;
fg: string;
bg: string;
}
@@ -126,6 +127,12 @@ class Cell {
return this.cell?.getChars() || ' ';
}
isUninitialized(): boolean {
return this.cell
? this.cell.getCode() === 0 && this.cell.isAttributeDefault()
: true;
}
isAttribute(attribute: Attribute): boolean {
return (this.attributes & attribute) !== 0;
}
@@ -137,7 +144,8 @@ class Cell {
this.bg === other.bg &&
this.fgColorMode === other.fgColorMode &&
this.bgColorMode === other.bgColorMode &&
this.isCursor() === other.isCursor()
this.isCursor() === other.isCursor() &&
this.isUninitialized() === other.isUninitialized()
);
}
}
@@ -149,15 +157,15 @@ export function serializeTerminalToObject(
): AnsiOutput {
const buffer = terminal.buffer.active;
const cursorX = buffer.cursorX;
const cursorY = buffer.cursorY;
const absoluteCursorY = buffer.baseY + buffer.cursorY;
const defaultFg = '';
const defaultBg = '';
const result: AnsiOutput = [];
// Reuse cell instances
const lastCell = new Cell(null, -1, -1, cursorX, cursorY);
const currentCell = new Cell(null, -1, -1, cursorX, cursorY);
const lastCell = new Cell(null, -1, -1, cursorX, absoluteCursorY);
const currentCell = new Cell(null, -1, -1, cursorX, absoluteCursorY);
const effectiveStart = startLine ?? buffer.viewportY;
const effectiveEnd = endLine ?? buffer.viewportY + terminal.rows;
@@ -173,12 +181,12 @@ export function serializeTerminalToObject(
}
// Reset lastCell for new line
lastCell.update(null, -1, -1, cursorX, cursorY);
lastCell.update(null, -1, -1, cursorX, absoluteCursorY);
let currentText = '';
for (let x = 0; x < terminal.cols; x++) {
const cellData = line.getCell(x, cellBuffer);
currentCell.update(cellData || null, x, y, cursorX, cursorY);
currentCell.update(cellData || null, x, y, cursorX, absoluteCursorY);
if (x > 0 && !currentCell.equals(lastCell)) {
if (currentText) {
@@ -190,6 +198,7 @@ export function serializeTerminalToObject(
dim: lastCell.isAttribute(Attribute.dim),
inverse:
lastCell.isAttribute(Attribute.inverse) || lastCell.isCursor(),
isUninitialized: lastCell.isUninitialized(),
fg: convertColorToHex(lastCell.fg, lastCell.fgColorMode, defaultFg),
bg: convertColorToHex(lastCell.bg, lastCell.bgColorMode, defaultBg),
};
@@ -200,7 +209,7 @@ export function serializeTerminalToObject(
currentText += currentCell.getChars();
// Copy state from currentCell to lastCell. Since we can't easily deep copy
// without allocating, we just update lastCell with the same data.
lastCell.update(cellData || null, x, y, cursorX, cursorY);
lastCell.update(cellData || null, x, y, cursorX, absoluteCursorY);
}
if (currentText) {
@@ -211,6 +220,7 @@ export function serializeTerminalToObject(
underline: lastCell.isAttribute(Attribute.underline),
dim: lastCell.isAttribute(Attribute.dim),
inverse: lastCell.isAttribute(Attribute.inverse) || lastCell.isCursor(),
isUninitialized: lastCell.isUninitialized(),
fg: convertColorToHex(lastCell.fg, lastCell.fgColorMode, defaultFg),
bg: convertColorToHex(lastCell.bg, lastCell.bgColorMode, defaultBg),
};
@@ -220,6 +230,23 @@ export function serializeTerminalToObject(
result.push(currentLine);
}
// Remove trailing empty lines
while (result.length > 0) {
const lastLine = result[result.length - 1];
const lineY = effectiveStart + result.length - 1;
// A line is empty if all its tokens are marked as uninitialized and it has no cursor
const isEmpty =
lastLine.every((token) => token.isUninitialized && !token.inverse) &&
lineY !== absoluteCursorY;
if (isEmpty) {
result.pop();
} else {
break;
}
}
return result;
}