mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-12 06:10:42 -07:00
fix(core): detect uninitialized lines (#24646)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user