diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index 6143d94f..ae72ce4b 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -3,10 +3,7 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from 'xterm'; -import { ICellData } from 'common/Types'; -import { CellData } from 'common/buffer/CellData'; -import { IBuffer } from 'common/buffer/Types'; +import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm'; import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants'; function crop(value: number | undefined, low: number, high: number, initial: number): number { @@ -16,24 +13,50 @@ function crop(value: number | undefined, low: number, high: number, initial: num return Math.max(low, Math.min(value, high)); } +class NullBufferCell implements IBufferCell { + fg: number = 0; bg: number = 0; + char: string = ''; + width: number = 0; + isInverse: number = 0; + isBold: number = 0; + isUnderline: number = 0; + isBlink: number = 0; + isInvisible: number = 0; + isItalic: number = 0; + isDim: number = 0; + fgColorMode: number = 0; + bgColorMode: number = 0; + isFgRGB: boolean = false; + isBgRGB: boolean = false; + isFgPalette: boolean = false; + isBgPalette: boolean = false; + isFgDefault: boolean = false; + isBgDefault: boolean = false; + fgColor: number = 0; + bgColor: number = 0; +} + class BaseSerializeHandler { constructor(private _buffer: IBuffer) { } serialize(startRow: number, endRow: number): string { - let oldCell = new CellData(); + let oldCell: IBufferCell = new NullBufferCell(); this._serializeStart(endRow - startRow); for (let row = startRow; row < endRow; row++) { - const line = this._buffer.lines.get(row); + const line = this._buffer.getLine(row); this._lineStart(row); if (line) { for (let col = 0; col < line.length; col++) { - const cell = new CellData(); + const cell = line.getCell(col); - line.loadCell(col, cell); + if (!cell) { + console.warn(`Can't get cell at row=${row}, col=${col}`); + continue; + } if (oldCell.fg !== cell.fg) { this._fgChanged(cell, oldCell, row, col); @@ -41,7 +64,6 @@ class BaseSerializeHandler { if (oldCell.bg !== cell.bg) { this._bgChanged(cell, oldCell, row, col); } - this._cellChanged(cell, oldCell, row, col); oldCell = cell; @@ -56,11 +78,11 @@ class BaseSerializeHandler { return this._serializeFinished(); } - protected _cellChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { } + protected _cellChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { } - protected _fgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { } + protected _fgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { } - protected _bgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { } + protected _bgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { } protected _lineStart(row: number): void { } @@ -95,6 +117,14 @@ function bgColor256to16(c: number): number { return -1; } +function hex2rgb(value: number): [number, number, number] { + return [ + value >>> Attributes.RED_SHIFT & 255, + value >>> Attributes.GREEN_SHIFT & 255, + value & 255 + ]; +} + class StringSerializeHandler extends BaseSerializeHandler { private _rowIndex: number = 0; private _allRows: string[] = new Array(); @@ -114,7 +144,7 @@ class StringSerializeHandler extends BaseSerializeHandler { this._currentRow = ''; } - protected _fgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { + protected _fgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { const fgFlagsChanged = (cell.fg ^ oldCell.fg) & FG_FM_MASK; const fgColorChanged = (cell.fg ^ oldCell.fg) & COLOR_MASK; const sgrSeq = this._sgrSeq; @@ -125,40 +155,40 @@ class StringSerializeHandler extends BaseSerializeHandler { if (fgFlagsChanged) { if (fgFlagsChanged & FgFlags.INVERSE) { - sgrSeq.push(cell.isInverse() ? '7' : '27'); + sgrSeq.push(cell.isInverse ? '7' : '27'); } if (fgFlagsChanged & FgFlags.BOLD) { - sgrSeq.push(cell.isBold() ? '1' : '22'); + sgrSeq.push(cell.isBold ? '1' : '22'); } if (fgFlagsChanged & FgFlags.UNDERLINE) { - sgrSeq.push(cell.isUnderline() ? '4' : '24'); + sgrSeq.push(cell.isUnderline ? '4' : '24'); } if (fgFlagsChanged & FgFlags.BLINK) { - sgrSeq.push(cell.isBlink() ? '5' : '25'); + sgrSeq.push(cell.isBlink ? '5' : '25'); } if (fgFlagsChanged & FgFlags.INVISIBLE) { - sgrSeq.push(cell.isInvisible() ? '8' : '28'); + sgrSeq.push(cell.isInvisible ? '8' : '28'); } } if (fgColorChanged) { - const fgColor = cell.getFgColor(); + const fgColor = cell.fgColor; - if (cell.isFgDefault()) { + if (cell.isFgDefault) { sgrSeq.push('39'); - } else if (cell.isFgPalette()) { - switch (cell.getFgColorMode()) { + } else if (cell.isFgPalette) { + switch (cell.fgColorMode) { case Attributes.CM_P16: sgrSeq.push(fgColor256to16(fgColor).toString()); break; case Attributes.CM_P256: sgrSeq.push(`38;5;${fgColor}`); break; } - } else if (cell.isFgRGB()) { - const [r, g, b] = CellData.toColorRGB(fgColor); + } else if (cell.isFgRGB) { + const [r, g, b] = hex2rgb(fgColor); sgrSeq.push(`38;2;${r};${g};${b}`); } } } - protected _bgChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { + protected _bgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { const bgFlagsChanged = (cell.bg ^ oldCell.bg) & BG_FM_MASK; const bgColorChanged = (cell.bg ^ oldCell.bg) & COLOR_MASK; const sgrSeq = this._sgrSeq; @@ -169,31 +199,31 @@ class StringSerializeHandler extends BaseSerializeHandler { if (bgFlagsChanged) { if (bgFlagsChanged & BgFlags.ITALIC) { - sgrSeq.push(cell.isItalic() ? '3' : '23'); + sgrSeq.push(cell.isItalic ? '3' : '23'); } if (bgFlagsChanged & BgFlags.DIM) { - sgrSeq.push(cell.isDim() ? '2' : '22'); + sgrSeq.push(cell.isDim ? '2' : '22'); } } if (bgColorChanged) { - const bgColor = cell.getBgColor(); + const bgColor = cell.bgColor; - if (cell.isBgDefault()) { + if (cell.isBgDefault) { sgrSeq.push('49'); - } else if (cell.isBgPalette()) { - switch (cell.getBgColorMode()) { + } else if (cell.isBgPalette) { + switch (cell.bgColorMode) { case Attributes.CM_P16: sgrSeq.push(bgColor256to16(bgColor).toString()); break; case Attributes.CM_P256: sgrSeq.push(`48;5;${bgColor}`); break; } - } else if (cell.isFgRGB()) { - const [r, g, b] = CellData.toColorRGB(bgColor); + } else if (cell.isFgRGB) { + const [r, g, b] = hex2rgb(bgColor); sgrSeq.push(`48;2;${r};${g};${b}`); } } } - protected _cellChanged(cell: ICellData, oldCell: ICellData, row: number, col: number): void { + protected _cellChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { const fgChanged = cell.fg !== oldCell.fg; const bgChanged = cell.bg !== oldCell.bg; const isfgBgNormal = (cell.fg === 0) && (cell.bg === 0); @@ -207,7 +237,7 @@ class StringSerializeHandler extends BaseSerializeHandler { this._sgrSeq = []; } - this._currentRow += cell.getChars(); + this._currentRow += cell.char; } protected _serializeFinished(): string { @@ -241,7 +271,7 @@ export class SerializeAddon implements ITerminalAddon { } const maxRows = this._terminal.rows; - const handler = new StringSerializeHandler((this._terminal)._core.buffer); + const handler = new StringSerializeHandler(this._terminal.buffer); rows = crop(rows, 0, maxRows, maxRows); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 5d270ca7..20ecf7e9 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -5,8 +5,9 @@ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; import { ITerminal } from '../Types'; -import { IBufferLine } from 'common/Types'; +import { IBufferLine, ICellData } from 'common/Types'; import { IBuffer } from 'common/buffer/Types'; +import { CellData } from 'common/buffer/CellData'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../browser/LocalizableStrings'; import { IEvent } from 'common/EventEmitter'; @@ -204,11 +205,15 @@ class BufferLineApiView implements IBufferLineApi { constructor(private _line: IBufferLine) {} public get isWrapped(): boolean { return this._line.isWrapped; } + public get length(): number { return this._line.length; } public getCell(x: number): IBufferCellApi | undefined { if (x < 0 || x >= this._line.length) { return undefined; } - return new BufferCellApiView(this._line, x); + + const cell = new CellData(); + this._line.loadCell(x, cell); + return new BufferCellApiView(cell); } public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { return this._line.translateToString(trimRight, startColumn, endColumn); @@ -216,7 +221,27 @@ class BufferLineApiView implements IBufferLineApi { } class BufferCellApiView implements IBufferCellApi { - constructor(private _line: IBufferLine, private _x: number) {} - public get char(): string { return this._line.getString(this._x); } - public get width(): number { return this._line.getWidth(this._x); } + constructor(private _cell: ICellData) {} + + public get fg(): number { return this._cell.fg; } + public get bg(): number { return this._cell.bg; } + public get char(): string { return this._cell.getChars(); } + public get width(): number { return this._cell.getWidth(); } + public get isInverse(): number { return this._cell.isInverse(); } + public get isBold(): number { return this._cell.isBold(); } + public get isUnderline(): number { return this._cell.isUnderline(); } + public get isBlink(): number { return this._cell.isBlink(); } + public get isInvisible(): number { return this._cell.isInvisible(); } + public get isItalic(): number { return this._cell.isItalic(); } + public get isDim(): number { return this._cell.isDim(); } + public get fgColorMode(): number { return this._cell.getFgColorMode(); } + public get bgColorMode(): number { return this._cell.getBgColorMode(); } + public get isFgRGB(): boolean { return this._cell.isFgRGB(); } + public get isBgRGB(): boolean { return this._cell.isBgRGB(); } + public get isFgPalette(): boolean { return this._cell.isFgPalette(); } + public get isBgPalette(): boolean { return this._cell.isBgPalette(); } + public get isFgDefault(): boolean { return this._cell.isFgDefault(); } + public get isBgDefault(): boolean { return this._cell.isBgDefault(); } + public get fgColor(): number { return this._cell.getFgColor(); } + public get bgColor(): number { return this._cell.getBgColor(); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d9e28b26..1ec96674 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -910,6 +910,7 @@ declare module 'xterm' { * Whether the line is wrapped from the previous line. */ readonly isWrapped: boolean; + readonly length: number; /** * Gets a cell from the line, or undefined if the line index does not exist. @@ -937,6 +938,9 @@ declare module 'xterm' { * Represents a single cell in the terminal's buffer. */ interface IBufferCell { + readonly fg: number; + readonly bg: number; + /** * The character within the cell. */ @@ -950,5 +954,28 @@ declare module 'xterm' { * - This is `0` for cells immediately following cells with a width of `2`. */ readonly width: number; + + // flags + readonly isInverse: number; + readonly isBold: number; + readonly isUnderline: number; + readonly isBlink: number; + readonly isInvisible: number; + readonly isItalic: number; + readonly isDim: number; + + // color modes + readonly fgColorMode: number; + readonly bgColorMode: number; + readonly isFgRGB: boolean; + readonly isBgRGB: boolean; + readonly isFgPalette: boolean; + readonly isBgPalette: boolean; + readonly isFgDefault: boolean; + readonly isBgDefault: boolean; + + // colors + readonly fgColor: number; + readonly bgColor: number; } }