From 881a4a0dbb838231d9ed44450e539bd426d44417 Mon Sep 17 00:00:00 2001 From: mmis1000 Date: Tue, 15 Sep 2020 17:58:56 +0800 Subject: [PATCH] Try implement cursor restortion --- .../src/SerializeAddon.ts | 110 ++++++++++++++---- demo/client.ts | 2 +- 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index ba366c37..e03707e0 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -13,7 +13,7 @@ function constrain(value: number, low: number, high: number): number { interface ISerializeOptions { withAlternate?: boolean; - + withCursor?: boolean; } // TODO: Refine this template class later @@ -26,7 +26,7 @@ abstract class BaseSerializeHandler { const cell2 = this._buffer.getNullCell(); let oldCell = cell1; - this._beforeSerialize(endRow - startRow); + this._beforeSerialize(endRow - startRow, startRow, endRow); for (let row = startRow; row < endRow; row++) { const line = this._buffer.getLine(row); @@ -51,7 +51,7 @@ abstract class BaseSerializeHandler { protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { } protected _rowEnd(row: number): void { } - protected _beforeSerialize(rows: number): void { } + protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { } protected _afterSerialize(): void { } protected _serializeString(): string { return ''; } } @@ -82,12 +82,16 @@ class StringSerializeHandler extends BaseSerializeHandler { private _currentRow: string = ''; private _nullCellCount: number = 0; - constructor(buffer: IBuffer) { - super(buffer); + private _lastContentCellRow: number = 0; + private _lastContentCellCol: number = 0; + + constructor(private _buffer1: IBuffer,private _terminal: Terminal, private _option: ISerializeOptions = {}) { + super(_buffer1); } - protected _beforeSerialize(rows: number): void { + protected _beforeSerialize(rows: number, start: number, end: number): void { this._allRows = new Array(rows); + this._lastContentCellRow = start; } protected _rowEnd(row: number): void { @@ -147,6 +151,9 @@ class StringSerializeHandler extends BaseSerializeHandler { } else if (this._nullCellCount > 0) { this._currentRow += `\x1b[${this._nullCellCount}C`; this._nullCellCount = 0; + } else { + this._lastContentCellRow = row; + this._lastContentCellCol = col + cell.getWidth(); } this._currentRow += cell.getChars(); @@ -154,12 +161,71 @@ class StringSerializeHandler extends BaseSerializeHandler { protected _serializeString(): string { let rowEnd = this._allRows.length; + for (; rowEnd > 0; rowEnd--) { if (this._allRows[rowEnd - 1]) { break; } } - return this._allRows.slice(0, rowEnd).join('\r\n'); + + let content = this._allRows.slice(0, rowEnd).join('\r\n'); + + if (this._option.withCursor) { + const realCursorRow = this._buffer1.baseY + this._buffer1.cursorY; + const realCursorCol = this._buffer1.cursorX; + + const hasScroll = this._buffer1.length > this._terminal.rows!; + const hasEmptyLine = hasScroll ? (this._buffer1.length - 1 > this._lastContentCellRow) : (realCursorRow > this._lastContentCellRow); + const cursorMoved = + hasScroll + ? hasEmptyLine + ? (realCursorCol !== 0 || realCursorRow !== this._buffer1.length - 1) + : (realCursorRow !== this._lastContentCellRow || realCursorCol !== this._lastContentCellCol) + : hasEmptyLine + // we don't need to check the row because empty row count are based on cursor + ? realCursorCol !== 0 + : (realCursorRow !== this._lastContentCellRow || realCursorCol !== this._lastContentCellCol); + + const moveRight = (offset: number): void => { + if (offset > 0) { + content += `\u001b[${offset}C`; + } else if (offset < 0) { + content += `\u001b[${-offset}D`; + } + }; + const moveDown = (offset: number): void => { + if (offset > 0) { + content += `\u001b[${offset}B`; + } else if (offset < 0) { + content += `\u001b[${-offset}A`; + } + }; + + // Fix empty lines + if (hasEmptyLine) { + if (hasScroll) { + content += '\r\n'.repeat(this._buffer1.length - 1 - this._lastContentCellRow); + } else { + content += '\r\n'.repeat(realCursorRow - this._lastContentCellRow); + } + } + + if (cursorMoved) { + if (hasEmptyLine) { + if (hasScroll) { + moveRight(realCursorCol); + moveDown(realCursorRow - (this._buffer1.length - 1)); + } else { + moveRight(realCursorCol); + } + } else { + moveDown(realCursorRow - this._lastContentCellRow); + moveRight(realCursorCol - this._lastContentCellCol); + } + } + } + + return content; } } @@ -172,6 +238,15 @@ export class SerializeAddon implements ITerminalAddon { this._terminal = terminal; } + private _getString(buffer: IBuffer, rows?: number, option?: ISerializeOptions): string { + const maxRows = buffer.length; + const handler = new StringSerializeHandler(buffer, this._terminal!, option); + const correctRows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows); + const result = handler.serialize(maxRows - correctRows, maxRows); + + return result; + } + public serialize(rows?: number, options: ISerializeOptions = {}): string { // TODO: Add re-position cursor support // TODO: Add word wrap mode support @@ -180,25 +255,16 @@ export class SerializeAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } - if (this._terminal.buffer.active.type === 'normal' || !(options?.withAlternate ?? false)) { - const maxRows = this._terminal.buffer.active.length; - const handler = new StringSerializeHandler(this._terminal.buffer.active); - - rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows); - - return handler.serialize(maxRows - rows, maxRows); + if (this._terminal.buffer.active.type === 'normal' || !(options.withAlternate ?? false)) { + return this._getString(this._terminal.buffer.active, rows, options); } - const maxNormalRows = this._terminal.buffer.normal.length; - const maxAltRows = this._terminal.buffer.alternate.length; - const normalHandler = new StringSerializeHandler(this._terminal.buffer.normal); - const altHandler = new StringSerializeHandler(this._terminal.buffer.alternate); - const normalRows = (rows === undefined) ? maxNormalRows : constrain(rows, 0, maxNormalRows); - const altRows = (rows === undefined) ? maxAltRows : constrain(rows, 0, maxAltRows); + const normalScreenContent = this._getString(this._terminal.buffer.normal, rows, options); + const alternativeScreenContent = this._getString(this._terminal.buffer.alternate, rows, options); - return normalHandler.serialize(maxNormalRows - normalRows, maxNormalRows) + return normalScreenContent + '\u001b[?1049h\u001b[H' - + altHandler.serialize(maxAltRows - altRows, maxAltRows); + + alternativeScreenContent; } public dispose(): void { } diff --git a/demo/client.ts b/demo/client.ts index 6efa181a..509b73a9 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -413,7 +413,7 @@ function updateTerminalSize(): void { } function serializeButtonHandler(): void { - const output = addons.serialize.instance.serialize(); + const output = addons.serialize.instance.serialize(undefined, { withAlternate: true, withCursor: true }); const outputString = JSON.stringify(output); document.getElementById('serialize-output').innerText = outputString;