From 6904dc50092d979f47108d1fd89f2bfbf7fcd4e6 Mon Sep 17 00:00:00 2001 From: Mmis1000 Date: Fri, 18 Sep 2020 00:36:10 +0800 Subject: [PATCH] Handle BCE + line scroll Using an additional field for store the current background and compare against it to generate proper color sequence --- .../xterm-addon-serialize/src/MyBufferCell.ts | 96 +++++++++++++++++++ .../src/SerializeAddon.ts | 51 +++++----- .../test/SerializeAddon.api.ts | 27 ++++++ 3 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 addons/xterm-addon-serialize/src/MyBufferCell.ts diff --git a/addons/xterm-addon-serialize/src/MyBufferCell.ts b/addons/xterm-addon-serialize/src/MyBufferCell.ts new file mode 100644 index 00000000..56efcb3c --- /dev/null +++ b/addons/xterm-addon-serialize/src/MyBufferCell.ts @@ -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); + } +} diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index f4dcf35b..9518fcbf 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -6,6 +6,7 @@ */ import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm'; +import { MyBufferCell } from './MyBufferCell'; function constrain(value: number, low: number, high: number): number { return Math.max(low, Math.min(value, high)); @@ -36,7 +37,7 @@ abstract class BaseSerializeHandler { oldCell = c; } } - this._rowEnd(row); + this._rowEnd(row, row === endRow - 1); } this._afterSerialize(); @@ -45,7 +46,7 @@ abstract class BaseSerializeHandler { } 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 _afterSerialize(): void { } protected _serializeString(): string { return ''; } @@ -71,20 +72,23 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean { && cell1.isDim() === cell2.isDim(); } + + class StringSerializeHandler extends BaseSerializeHandler { private _rowIndex: number = 0; private _allRows: string[] = new Array(); private _currentRow: string = ''; 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 // but the information isn't preserved by null cell itself // so wee need to record it when required. 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 _lastCursorCol: number = 0; @@ -95,26 +99,21 @@ class StringSerializeHandler extends BaseSerializeHandler { protected _beforeSerialize(rows: number, start: number, end: number): void { this._allRows = new Array(rows); 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 (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._nullCell)) { + if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { // use clear right to set background. // use move right to move cursor. this._currentRow += `\x1b[${this._nullCellCount}X`; + } - // set the cursor back because we aren't there - this._lastCursorRow = row; - this._lastCursorCol = this._terminal.cols - this._nullCellCount; - - 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(); + if (!isLastRow) { + if (row - this._firstRow >= this._terminal.rows) { + this._backgroundCell = MyBufferCell.from(this._cursorStyle); + } } this._allRows[this._rowIndex++] = this._currentRow; @@ -178,9 +177,6 @@ class StringSerializeHandler extends BaseSerializeHandler { // this cell don't have content 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); // 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) { // use clear right to set background. // use move right to move cursor. - if (equalBg(this._cursorStyle, this._nullCell)) { + if (equalBg(this._cursorStyle, this._backgroundCell)) { this._currentRow += `\x1b[${this._nullCellCount}C`; } else { this._currentRow += `\x1b[${this._nullCellCount}X`; @@ -203,6 +199,9 @@ class StringSerializeHandler extends BaseSerializeHandler { this._nullCellCount = 0; } + this._lastCursorRow = row; + this._lastCursorCol = col; + this._currentRow += `\x1b[${sgrSeq.join(';')}m`; // 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 // because style change is handled by previous stage // 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`; } else { this._currentRow += `\x1b[${this._nullCellCount}X`; @@ -227,10 +226,10 @@ class StringSerializeHandler extends BaseSerializeHandler { } this._nullCellCount = 0; } - this._currentRow += cell.getChars(); - } - if (!isNullCell) { + this._currentRow += cell.getChars(); + + // update cursor this._lastCursorRow = row; this._lastCursorCol = col + cell.getWidth(); } diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts index 4d90f3c5..afbe3b39 100644 --- a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts +++ b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts @@ -355,6 +355,33 @@ describe('SerializeAddon', () => { // and firefox have a bug that output -0 for unknown reason 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( + (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(initial: T | ((index: number) => T), count: number): T[] {