refactor use a nicer IBufferCell design

This commit is contained in:
javacs3
2019-11-21 20:41:47 +08:00
parent 3cba961dad
commit 73f9d45fdc
3 changed files with 188 additions and 156 deletions
@@ -3,8 +3,30 @@
* @license MIT
*/
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
import { Terminal, ITerminalAddon, IBuffer, IBufferCell, Color } from 'xterm';
// TODO: Workaround here, will remove this later
// If I use `import { CellStyle } from 'xterm'` instead, demo page will raise bellow error
//
// ERROR in ./addons/xterm-addon-serialize/out/SerializeAddon.js
// Module not found: Error: Can't resolve 'xterm' in '/Users/javacs3/Lab/Playground/xterm.js/addons/xterm-addon-serialize/out'
// @ ./addons/xterm-addon-serialize/out/SerializeAddon.js 16:14-30
// @ ./demo/client.ts
//
// Looks like typescript generate this line `var xterm_1 = require("xterm");` that leads to the error;
//
enum CellStyle {
default = 0,
// foreground style
inverse = 0x4000000 >>> 24,
bold = 0x8000000 >>> 24,
underline = 0x10000000 >>> 24,
blink = 0x20000000 >>> 24,
invisible = 0x40000000 >>> 24,
// background style
italic = 0x4000000 >>> 16,
dim = 0x8000000 >>> 16
}
function crop(value: number | undefined, low: number, high: number, initial: number): number {
if (value === undefined) {
@@ -14,29 +36,14 @@ function crop(value: number | undefined, low: number, high: number, initial: num
}
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;
foregroundColor: Color = { type: 'default', hash: 0 };
backgroundColor: Color = { type: 'default', hash: 0 }
style: CellStyle = CellStyle.default;
}
class BaseSerializeHandler {
abstract class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
serialize(startRow: number, endRow: number): string {
@@ -57,14 +64,15 @@ class BaseSerializeHandler {
console.warn(`Can't get cell at row=${row}, col=${col}`);
continue;
}
if ((cell.foregroundColor.hash !== oldCell.foregroundColor.hash)
|| (cell.backgroundColor.hash !== oldCell.backgroundColor.hash)) {
this._cellColorChanged(cell, oldCell, row, col);
}
if (cell.style !== oldCell.style) {
this._cellStyleChanged(cell, oldCell, row, col);
}
if (oldCell.fg !== cell.fg) {
this._fgChanged(cell, oldCell, row, col);
}
if (oldCell.bg !== cell.bg) {
this._bgChanged(cell, oldCell, row, col);
}
this._cellChanged(cell, oldCell, row, col);
this._nextCell(cell, oldCell, row, col);
oldCell = cell;
}
@@ -78,11 +86,11 @@ class BaseSerializeHandler {
return this._serializeFinished();
}
protected _cellChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _fgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _cellStyleChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _bgChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _cellColorChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
protected _lineStart(row: number): void { }
@@ -95,10 +103,6 @@ class BaseSerializeHandler {
protected _serializeFinished(): string { return ''; }
}
const FG_FM_MASK = FgFlags.FM_MASK;
const BG_FM_MASK = BgFlags.FM_MASK;
const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
function fgColor256to16(c: number): number {
if (0 <= c && c <= 7) {
return 30 + c;
@@ -117,12 +121,8 @@ 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
];
function isDefaultColorStyle(cell: IBufferCell) {
return (cell.foregroundColor.hash === 0) && (cell.backgroundColor.hash === 0) && (cell.style === CellStyle.default);
}
class StringSerializeHandler extends BaseSerializeHandler {
@@ -144,91 +144,76 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._currentRow = '';
}
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;
protected _cellStyleChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const styleChangedMask = cell.style ^ oldCell.style;
const style = cell.style;
const sgrSeq = this._sgrSeq;
if ((cell.fg === 0) && (cell.bg === 0)) {
// skip if it's default color style, we will use \x1b[0m to clear every color style later
if (isDefaultColorStyle(cell)) {
return;
}
if (fgFlagsChanged) {
if (fgFlagsChanged & FgFlags.INVERSE) {
sgrSeq.push(cell.isInverse ? '7' : '27');
}
if (fgFlagsChanged & FgFlags.BOLD) {
sgrSeq.push(cell.isBold ? '1' : '22');
}
if (fgFlagsChanged & FgFlags.UNDERLINE) {
sgrSeq.push(cell.isUnderline ? '4' : '24');
}
if (fgFlagsChanged & FgFlags.BLINK) {
sgrSeq.push(cell.isBlink ? '5' : '25');
}
if (fgFlagsChanged & FgFlags.INVISIBLE) {
sgrSeq.push(cell.isInvisible ? '8' : '28');
if (styleChangedMask & CellStyle.inverse) {
sgrSeq.push((style & CellStyle.inverse) ? '7' : '27');
}
if (styleChangedMask & CellStyle.bold) {
sgrSeq.push((style & CellStyle.bold) ? '1' : '22');
}
if (styleChangedMask & CellStyle.underline) {
sgrSeq.push((style & CellStyle.underline) ? '4' : '24');
}
if (styleChangedMask & CellStyle.blink) {
sgrSeq.push((style & CellStyle.blink) ? '5' : '25');
}
if (styleChangedMask & CellStyle.invisible) {
sgrSeq.push((style & CellStyle.invisible) ? '8' : '28');
}
if (styleChangedMask & CellStyle.italic) {
sgrSeq.push((style & CellStyle.italic) ? '3' : '23');
}
if (styleChangedMask & CellStyle.dim) {
sgrSeq.push((style & CellStyle.dim) ? '2' : '22');
}
}
protected _cellColorChanged(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const foregroundColorChanged = cell.foregroundColor.hash !== oldCell.foregroundColor.hash;
const backgroundColorChanged = cell.backgroundColor.hash !== oldCell.backgroundColor.hash;
const sgrSeq = this._sgrSeq;
// skip if it's default color style, we will use \x1b[0m to clear every color style later
if (isDefaultColorStyle(cell)) {
return;
}
if (foregroundColorChanged) {
const foregroundColor = cell.foregroundColor;
switch (foregroundColor.type) {
case 'default': sgrSeq.push('39'); break;
case 'palette16': sgrSeq.push(fgColor256to16(foregroundColor.id).toString()); break;
case 'palette256': sgrSeq.push(`38;5;${foregroundColor.id}`); break;
case 'rgb': const { red, green, blue } = foregroundColor; sgrSeq.push(`38;2;${red};${green};${blue}`); break;
}
}
if (fgColorChanged) {
const fgColor = cell.fgColor;
if (cell.isFgDefault) {
sgrSeq.push('39');
} 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] = hex2rgb(fgColor);
sgrSeq.push(`38;2;${r};${g};${b}`);
if (backgroundColorChanged) {
const backgroundColor = cell.backgroundColor;
switch (backgroundColor.type) {
case 'default': sgrSeq.push('49'); break;
case 'palette16': sgrSeq.push(bgColor256to16(backgroundColor.id).toString()); break;
case 'palette256': sgrSeq.push(`48;5;${backgroundColor.id}`); break;
case 'rgb': const { red, green, blue } = backgroundColor; sgrSeq.push(`48;2;${red};${green};${blue}`); break;
}
}
}
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;
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
const foregroundColorChanged = cell.foregroundColor.hash !== oldCell.foregroundColor.hash;
const backgroundColorChanged = cell.backgroundColor.hash !== oldCell.backgroundColor.hash;
const styleChanged = cell.style !== oldCell.style;
if ((cell.bg === 0) && (cell.fg === 0)) {
return;
}
if (bgFlagsChanged) {
if (bgFlagsChanged & BgFlags.ITALIC) {
sgrSeq.push(cell.isItalic ? '3' : '23');
}
if (bgFlagsChanged & BgFlags.DIM) {
sgrSeq.push(cell.isDim ? '2' : '22');
}
}
if (bgColorChanged) {
const bgColor = cell.bgColor;
if (cell.isBgDefault) {
sgrSeq.push('49');
} 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] = hex2rgb(bgColor);
sgrSeq.push(`48;2;${r};${g};${b}`);
}
}
}
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);
if ((fgChanged || bgChanged) && isfgBgNormal) {
if ((foregroundColorChanged || backgroundColorChanged || styleChanged) && isDefaultColorStyle(cell)) {
this._currentRow += '\x1b[0m';
}
+39 -20
View File
@@ -3,10 +3,11 @@
* @license MIT
*/
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, Color, CellStyle} from 'xterm';
import { ITerminal } from '../Types';
import { IBufferLine, ICellData } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../browser/LocalizableStrings';
@@ -220,28 +221,46 @@ class BufferLineApiView implements IBufferLineApi {
}
}
const COLOR_MASK = Attributes.CM_MASK | Attributes.RGB_MASK;
class BufferCellApiView implements IBufferCellApi {
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(); }
public get foregroundColor(): Color {
const cell = this._cell;
const hash = cell.fg & COLOR_MASK;
if (cell.isFgDefault()) {
return { type: 'default', hash: 0 };
} else if (cell.isFgPalette()) {
switch (cell.getFgColorMode()) {
case Attributes.CM_P16: return { type: 'palette16', hash, id: cell.getFgColor() };
case Attributes.CM_P256: return { type: 'palette256', hash, id: cell.getFgColor() };
}
} else if (cell.isFgRGB()) {
const [red, green, blue] = CellData.toColorRGB(cell.fg);
return { type: 'rgb', hash, red, green, blue };
}
throw new Error('Invalid foregroundColor');
}
public get backgroundColor(): Color {
const cell = this._cell;
const hash = cell.bg & COLOR_MASK;
if (cell.isBgDefault()) {
return { type: 'default', hash: 0 };
} else if (cell.isBgPalette()) {
switch (cell.getBgColorMode()) {
case Attributes.CM_P16: return { type: 'palette16', hash, id: cell.getBgColor() };
case Attributes.CM_P256: return { type: 'palette256', hash, id: cell.getBgColor() };
}
} else if (cell.isBgRGB()) {
const [red, green, blue] = CellData.toColorRGB(cell.bg);
return { type: 'rgb', hash, red, green, blue };
}
throw new Error('Invalid backgroundColor');
}
public get style(): CellStyle {
return ((this._cell.bg & BgFlags.FM_MASK) >>> 16) | ((this._cell.fg & FgFlags.FM_MASK) >>> 24);
}
}
+51 -23
View File
@@ -938,9 +938,6 @@ 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.
*/
@@ -955,27 +952,58 @@ declare module 'xterm' {
*/
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;
readonly foregroundColor: Color;
readonly backgroundColor: Color;
readonly style: CellStyle;
}
// 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;
export type Color = IDefaultColor | IPalette16Color | IPalette256Color | IRgbColor;
export enum CellStyle {
default = 0,
// foreground style
inverse = 0x4000000 >>> 24,
bold = 0x8000000 >>> 24,
underline = 0x10000000 >>> 24,
blink = 0x20000000 >>> 24,
invisible = 0x40000000 >>> 24,
// background style
italic = 0x4000000 >>> 16,
dim = 0x8000000 >>> 16
}
// colors
readonly fgColor: number;
readonly bgColor: number;
interface ICellColor {
type: string;
hash: number;
}
interface IDefaultColor extends ICellColor {
type: 'default';
hash: 0;
}
interface IRgbColor extends ICellColor {
type: 'rgb';
hash: number;
// 0-255
red: number;
green: number;
blue: number;
}
interface IPalette16Color extends ICellColor {
type: 'palette16';
hash: number;
// 0-15
id: number;
}
interface IPalette256Color extends ICellColor {
type: 'palette256';
hash: number;
// 0-255
id: number;
}
}