Handle BCE + line scroll
Using an additional field for store the current background and compare against it to generate proper color sequence
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
import { IBufferCell } from 'xterm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a dummy buffer cell to hold data from real buffer cell
|
||||||
|
*/
|
||||||
|
export class MyBufferCell implements IBufferCell {
|
||||||
|
constructor (private _cell: IBufferCell) {}
|
||||||
|
private _width: number = this._cell.getWidth();
|
||||||
|
private _chars: string = this._cell.getChars();
|
||||||
|
private _code: number = this._cell.getCode();
|
||||||
|
private _fgColorMode: number = this._cell.getFgColorMode();
|
||||||
|
private _bgColorMode: number = this._cell.getBgColorMode();
|
||||||
|
private _fgColor: number = this._cell.getFgColor();
|
||||||
|
private _bgColor: number = this._cell.getBgColor();
|
||||||
|
private _bold: number = this._cell.isBold();
|
||||||
|
private _italic: number = this._cell.isItalic();
|
||||||
|
private _dim: number = this._cell.isDim();
|
||||||
|
private _underline: number = this._cell.isUnderline();
|
||||||
|
private _blink: number = this._cell.isBlink();
|
||||||
|
private _inverse: number = this._cell.isInverse();
|
||||||
|
private _invisible: number = this._cell.isInvisible();
|
||||||
|
private _fgRGB: boolean = this._cell.isFgRGB();
|
||||||
|
private _bgRGB: boolean = this._cell.isBgRGB();
|
||||||
|
private _fgPalette: boolean = this._cell.isFgPalette();
|
||||||
|
private _bgPallette: boolean = this._cell.isBgPalette();
|
||||||
|
private _fgDefault: boolean = this._cell.isFgDefault();
|
||||||
|
private _bgDefault: boolean = this._cell.isBgDefault();
|
||||||
|
private _attributeDefault: boolean = this._cell.isAttributeDefault();
|
||||||
|
public getWidth(): number {
|
||||||
|
return this._width;
|
||||||
|
}
|
||||||
|
public getChars(): string {
|
||||||
|
return this._chars;
|
||||||
|
}
|
||||||
|
public getCode(): number {
|
||||||
|
return this._code;
|
||||||
|
}
|
||||||
|
public getFgColorMode(): number {
|
||||||
|
return this._fgColorMode;
|
||||||
|
}
|
||||||
|
public getBgColorMode(): number {
|
||||||
|
return this._bgColorMode;
|
||||||
|
}
|
||||||
|
public getFgColor(): number {
|
||||||
|
return this._fgColor;
|
||||||
|
}
|
||||||
|
public getBgColor(): number {
|
||||||
|
return this._bgColor;
|
||||||
|
}
|
||||||
|
public isBold(): number {
|
||||||
|
return this._bold;
|
||||||
|
}
|
||||||
|
public isItalic(): number {
|
||||||
|
return this._italic;
|
||||||
|
}
|
||||||
|
public isDim(): number {
|
||||||
|
return this._dim;
|
||||||
|
}
|
||||||
|
public isUnderline(): number {
|
||||||
|
return this._underline;
|
||||||
|
}
|
||||||
|
public isBlink(): number {
|
||||||
|
return this._blink;
|
||||||
|
}
|
||||||
|
public isInverse(): number {
|
||||||
|
return this._inverse;
|
||||||
|
}
|
||||||
|
public isInvisible(): number {
|
||||||
|
return this._invisible;
|
||||||
|
}
|
||||||
|
public isFgRGB(): boolean {
|
||||||
|
return this._fgRGB;
|
||||||
|
}
|
||||||
|
public isBgRGB(): boolean {
|
||||||
|
return this._bgRGB;
|
||||||
|
}
|
||||||
|
public isFgPalette(): boolean {
|
||||||
|
return this._fgPalette;
|
||||||
|
}
|
||||||
|
public isBgPalette(): boolean {
|
||||||
|
return this._bgPallette;
|
||||||
|
}
|
||||||
|
public isFgDefault(): boolean {
|
||||||
|
return this._fgDefault;
|
||||||
|
}
|
||||||
|
public isBgDefault(): boolean {
|
||||||
|
return this._bgDefault;
|
||||||
|
}
|
||||||
|
public isAttributeDefault(): boolean {
|
||||||
|
return this._attributeDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static from(cell: IBufferCell): MyBufferCell {
|
||||||
|
return new MyBufferCell(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
|
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
|
||||||
|
import { MyBufferCell } from './MyBufferCell';
|
||||||
|
|
||||||
function constrain(value: number, low: number, high: number): number {
|
function constrain(value: number, low: number, high: number): number {
|
||||||
return Math.max(low, Math.min(value, high));
|
return Math.max(low, Math.min(value, high));
|
||||||
@@ -36,7 +37,7 @@ abstract class BaseSerializeHandler {
|
|||||||
oldCell = c;
|
oldCell = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._rowEnd(row);
|
this._rowEnd(row, row === endRow - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._afterSerialize();
|
this._afterSerialize();
|
||||||
@@ -45,7 +46,7 @@ abstract class BaseSerializeHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
|
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
|
||||||
protected _rowEnd(row: number): void { }
|
protected _rowEnd(row: number, isLastRow: boolean): void { }
|
||||||
protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { }
|
protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { }
|
||||||
protected _afterSerialize(): void { }
|
protected _afterSerialize(): void { }
|
||||||
protected _serializeString(): string { return ''; }
|
protected _serializeString(): string { return ''; }
|
||||||
@@ -71,20 +72,23 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
|||||||
&& cell1.isDim() === cell2.isDim();
|
&& cell1.isDim() === cell2.isDim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class StringSerializeHandler extends BaseSerializeHandler {
|
class StringSerializeHandler extends BaseSerializeHandler {
|
||||||
private _rowIndex: number = 0;
|
private _rowIndex: number = 0;
|
||||||
private _allRows: string[] = new Array<string>();
|
private _allRows: string[] = new Array<string>();
|
||||||
private _currentRow: string = '';
|
private _currentRow: string = '';
|
||||||
private _nullCellCount: number = 0;
|
private _nullCellCount: number = 0;
|
||||||
|
|
||||||
// this is a null cell for reference for checking whether background is empty or not
|
|
||||||
private _nullCell: IBufferCell = this._buffer1.getNullCell();
|
|
||||||
|
|
||||||
// we can see a full colored cell and a null cell that only have background the same style
|
// we can see a full colored cell and a null cell that only have background the same style
|
||||||
// but the information isn't preserved by null cell itself
|
// but the information isn't preserved by null cell itself
|
||||||
// so wee need to record it when required.
|
// so wee need to record it when required.
|
||||||
private _cursorStyle: IBufferCell = this._buffer1.getNullCell();
|
private _cursorStyle: IBufferCell = this._buffer1.getNullCell();
|
||||||
|
|
||||||
|
// this is a null cell for reference for checking whether background is empty or not
|
||||||
|
private _backgroundCell: MyBufferCell = MyBufferCell.from(this._cursorStyle);
|
||||||
|
|
||||||
|
private _firstRow: number = 0;
|
||||||
private _lastCursorRow: number = 0;
|
private _lastCursorRow: number = 0;
|
||||||
private _lastCursorCol: number = 0;
|
private _lastCursorCol: number = 0;
|
||||||
|
|
||||||
@@ -95,26 +99,21 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
protected _beforeSerialize(rows: number, start: number, end: number): void {
|
protected _beforeSerialize(rows: number, start: number, end: number): void {
|
||||||
this._allRows = new Array<string>(rows);
|
this._allRows = new Array<string>(rows);
|
||||||
this._lastCursorRow = start;
|
this._lastCursorRow = start;
|
||||||
|
this._firstRow = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _rowEnd(row: number): void {
|
protected _rowEnd(row: number, isLastRow: boolean): void {
|
||||||
// if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing
|
// if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing
|
||||||
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._nullCell)) {
|
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||||
// use clear right to set background.
|
// use clear right to set background.
|
||||||
// use move right to move cursor.
|
// use move right to move cursor.
|
||||||
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
||||||
|
}
|
||||||
|
|
||||||
// set the cursor back because we aren't there
|
if (!isLastRow) {
|
||||||
this._lastCursorRow = row;
|
if (row - this._firstRow >= this._terminal.rows) {
|
||||||
this._lastCursorCol = this._terminal.cols - this._nullCellCount;
|
this._backgroundCell = MyBufferCell.from(this._cursorStyle);
|
||||||
|
}
|
||||||
this._nullCellCount = 0;
|
|
||||||
|
|
||||||
// perform a style reset before next line,
|
|
||||||
// because scroll when having background set will change the whole background of next line.
|
|
||||||
this._currentRow += `\x1b[m`;
|
|
||||||
// FIXME: we just get a new one because we can't reset it.
|
|
||||||
this._cursorStyle = this._buffer1.getNullCell();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._allRows[this._rowIndex++] = this._currentRow;
|
this._allRows[this._rowIndex++] = this._currentRow;
|
||||||
@@ -178,9 +177,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
// this cell don't have content
|
// this cell don't have content
|
||||||
const isEmptyCell = cell.getChars() === '';
|
const isEmptyCell = cell.getChars() === '';
|
||||||
|
|
||||||
// this cell don't have content and style
|
|
||||||
const isNullCell = cell.getWidth() === 1 && cell.getChars() === '' && cell.isAttributeDefault();
|
|
||||||
|
|
||||||
const sgrSeq = this._diffStyle(cell, this._cursorStyle);
|
const sgrSeq = this._diffStyle(cell, this._cursorStyle);
|
||||||
|
|
||||||
// the empty cell style is only assumed to be changed when background changed, because foreground is always 0.
|
// the empty cell style is only assumed to be changed when background changed, because foreground is always 0.
|
||||||
@@ -194,7 +190,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
if (this._nullCellCount > 0) {
|
if (this._nullCellCount > 0) {
|
||||||
// use clear right to set background.
|
// use clear right to set background.
|
||||||
// use move right to move cursor.
|
// use move right to move cursor.
|
||||||
if (equalBg(this._cursorStyle, this._nullCell)) {
|
if (equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||||
} else {
|
} else {
|
||||||
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
||||||
@@ -203,6 +199,9 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
this._nullCellCount = 0;
|
this._nullCellCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._lastCursorRow = row;
|
||||||
|
this._lastCursorCol = col;
|
||||||
|
|
||||||
this._currentRow += `\x1b[${sgrSeq.join(';')}m`;
|
this._currentRow += `\x1b[${sgrSeq.join(';')}m`;
|
||||||
|
|
||||||
// update the last cursor style
|
// update the last cursor style
|
||||||
@@ -219,7 +218,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
// we can just assume we have same style with previous one here
|
// we can just assume we have same style with previous one here
|
||||||
// because style change is handled by previous stage
|
// because style change is handled by previous stage
|
||||||
// use move right when background is empty, use clear right when there is background.
|
// use move right when background is empty, use clear right when there is background.
|
||||||
if (equalBg(this._cursorStyle, this._nullCell)) {
|
if (equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||||
} else {
|
} else {
|
||||||
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
||||||
@@ -227,10 +226,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
}
|
}
|
||||||
this._nullCellCount = 0;
|
this._nullCellCount = 0;
|
||||||
}
|
}
|
||||||
this._currentRow += cell.getChars();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNullCell) {
|
this._currentRow += cell.getChars();
|
||||||
|
|
||||||
|
// update cursor
|
||||||
this._lastCursorRow = row;
|
this._lastCursorRow = row;
|
||||||
this._lastCursorCol = col + cell.getWidth();
|
this._lastCursorCol = col + cell.getWidth();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,6 +355,33 @@ describe('SerializeAddon', () => {
|
|||||||
// and firefox have a bug that output -0 for unknown reason
|
// and firefox have a bug that output -0 for unknown reason
|
||||||
assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer));
|
assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('cause the BCE on scroll', async () => {
|
||||||
|
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||||
|
|
||||||
|
const padLines = newArray<string>(
|
||||||
|
(index: number) => digitsString(10, index),
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
...padLines,
|
||||||
|
`\u001b[44m${CLEAR_RIGHT(5)}1111111111111111`
|
||||||
|
];
|
||||||
|
|
||||||
|
await writeSync(page, lines.join('\\r\\n'));
|
||||||
|
const originalBuffer = await page.evaluate(`SerializeAddon._inspectBuffer(term.buffer.normal);`);
|
||||||
|
|
||||||
|
const result = await page.evaluate(`serializeAddon.serialize();`) as string;
|
||||||
|
|
||||||
|
await page.evaluate(`term.reset();`);
|
||||||
|
await writeRawSync(page, result);
|
||||||
|
const newBuffer = await page.evaluate(`SerializeAddon._inspectBuffer(term.buffer.normal);`);
|
||||||
|
|
||||||
|
// chai decides -0 and 0 are different number...
|
||||||
|
// and firefox have a bug that output -0 for unknown reason
|
||||||
|
assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
|
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user