Merge branch 'master' into 1932_broken_reflow
This commit is contained in:
+162
-46
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { CharData, IBufferLine, ICellData } from './Types';
|
||||
import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './Types';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer';
|
||||
import { stringFromCodePoint } from './core/input/TextDecoder';
|
||||
|
||||
@@ -36,7 +36,7 @@ const enum Cell {
|
||||
/**
|
||||
* Bitmasks for accessing data in `content`.
|
||||
*/
|
||||
export const enum ContentMasks {
|
||||
export const enum Content {
|
||||
/**
|
||||
* bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
|
||||
* read: `codepoint = content & Content.codepointMask;`
|
||||
@@ -44,7 +44,7 @@ export const enum ContentMasks {
|
||||
* shortcut if precondition `codepoint <= 0x10FFFF` is met:
|
||||
* `content |= codepoint;`
|
||||
*/
|
||||
CODEPOINT = 0x1FFFFF,
|
||||
CODEPOINT_MASK = 0x1FFFFF,
|
||||
|
||||
/**
|
||||
* bit 22 flag indication whether a cell contains combined content
|
||||
@@ -52,7 +52,7 @@ export const enum ContentMasks {
|
||||
* set: `content |= Content.isCombined;`
|
||||
* clear: `content &= ~Content.isCombined;`
|
||||
*/
|
||||
IS_COMBINED = 0x200000, // 1 << 21
|
||||
IS_COMBINED_MASK = 0x200000, // 1 << 21
|
||||
|
||||
/**
|
||||
* bit 1..22 mask to check whether a cell contains any string data
|
||||
@@ -60,7 +60,7 @@ export const enum ContentMasks {
|
||||
* whether a cell contains anything
|
||||
* read: `isEmpty = !(content & Content.hasContent)`
|
||||
*/
|
||||
HAS_CONTENT = 0x3FFFFF,
|
||||
HAS_CONTENT_MASK = 0x3FFFFF,
|
||||
|
||||
/**
|
||||
* bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
|
||||
@@ -72,15 +72,131 @@ export const enum ContentMasks {
|
||||
* shortcut if precondition `0 <= width <= 3` is met:
|
||||
* `content |= width << Content.widthShift;`
|
||||
*/
|
||||
WIDTH = 0xC00000 // 3 << 22
|
||||
WIDTH_MASK = 0xC00000, // 3 << 22
|
||||
WIDTH_SHIFT = 22
|
||||
}
|
||||
|
||||
const WIDTH_MASK_SHIFT = 22;
|
||||
|
||||
export const enum Attributes {
|
||||
/**
|
||||
* bit 1..8 blue in RGB, color in P256 and P16
|
||||
*/
|
||||
BLUE_MASK = 0xFF,
|
||||
BLUE_SHIFT = 0,
|
||||
PCOLOR_MASK = 0xFF,
|
||||
PCOLOR_SHIFT = 0,
|
||||
|
||||
/**
|
||||
* bit 9..16 green in RGB
|
||||
*/
|
||||
GREEN_MASK = 0xFF00,
|
||||
GREEN_SHIFT = 8,
|
||||
|
||||
/**
|
||||
* bit 17..24 red in RGB
|
||||
*/
|
||||
RED_MASK = 0xFF0000,
|
||||
RED_SHIFT = 16,
|
||||
|
||||
/**
|
||||
* bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)
|
||||
*/
|
||||
CM_MASK = 0x3000000,
|
||||
CM_DEFAULT = 0,
|
||||
CM_P16 = 0x1000000,
|
||||
CM_P256 = 0x2000000,
|
||||
CM_RGB = 0x3000000,
|
||||
|
||||
/**
|
||||
* bit 1..24 RGB room
|
||||
*/
|
||||
RGB_MASK = 0xFFFFFF
|
||||
}
|
||||
|
||||
export const enum FgFlags {
|
||||
/**
|
||||
* bit 27..31 (32th bit unused)
|
||||
*/
|
||||
INVERSE = 0x4000000,
|
||||
BOLD = 0x8000000,
|
||||
UNDERLINE = 0x10000000,
|
||||
BLINK = 0x20000000,
|
||||
INVISIBLE = 0x40000000
|
||||
}
|
||||
|
||||
export const enum BgFlags {
|
||||
/**
|
||||
* bit 27..32 (upper 4 unused)
|
||||
*/
|
||||
ITALIC = 0x4000000,
|
||||
DIM = 0x8000000
|
||||
}
|
||||
|
||||
export class AttributeData implements IAttributeData {
|
||||
static toColorRGB(value: number): IColorRGB {
|
||||
return [
|
||||
value >>> Attributes.RED_SHIFT & 255,
|
||||
value >>> Attributes.GREEN_SHIFT & 255,
|
||||
value & 255
|
||||
];
|
||||
}
|
||||
static fromColorRGB(value: IColorRGB): number {
|
||||
return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255;
|
||||
}
|
||||
|
||||
public clone(): IAttributeData {
|
||||
const newObj = new AttributeData();
|
||||
newObj.fg = this.fg;
|
||||
newObj.bg = this.bg;
|
||||
return newObj;
|
||||
}
|
||||
|
||||
// data
|
||||
public fg: number = 0;
|
||||
public bg: number = 0;
|
||||
|
||||
// flags
|
||||
public isInverse(): number { return this.fg & FgFlags.INVERSE; }
|
||||
public isBold(): number { return this.fg & FgFlags.BOLD; }
|
||||
public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; }
|
||||
public isBlink(): number { return this.fg & FgFlags.BLINK; }
|
||||
public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; }
|
||||
public isItalic(): number { return this.bg & BgFlags.ITALIC; }
|
||||
public isDim(): number { return this.bg & BgFlags.DIM; }
|
||||
|
||||
// color modes
|
||||
public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; }
|
||||
public getBgColorMode(): number { return this.bg & Attributes.CM_MASK; }
|
||||
public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; }
|
||||
public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; }
|
||||
public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; }
|
||||
public isBgPalette(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; }
|
||||
public isFgDefault(): boolean { return (this.fg & Attributes.CM_MASK) === 0; }
|
||||
public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; }
|
||||
|
||||
// colors
|
||||
public getFgColor(): number {
|
||||
switch (this.fg & Attributes.CM_MASK) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256: return this.fg & Attributes.PCOLOR_MASK;
|
||||
case Attributes.CM_RGB: return this.fg & Attributes.RGB_MASK;
|
||||
default: return -1; // CM_DEFAULT defaults to -1
|
||||
}
|
||||
}
|
||||
public getBgColor(): number {
|
||||
switch (this.bg & Attributes.CM_MASK) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256: return this.bg & Attributes.PCOLOR_MASK;
|
||||
case Attributes.CM_RGB: return this.bg & Attributes.RGB_MASK;
|
||||
default: return -1; // CM_DEFAULT defaults to -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CellData - represents a single Cell in the terminal buffer.
|
||||
*/
|
||||
export class CellData implements ICellData {
|
||||
export class CellData extends AttributeData implements ICellData {
|
||||
|
||||
/** Helper to create CellData from CharData. */
|
||||
public static fromCharData(value: CharData): CellData {
|
||||
@@ -97,21 +213,21 @@ export class CellData implements ICellData {
|
||||
|
||||
/** Whether cell contains a combined string. */
|
||||
public isCombined(): number {
|
||||
return this.content & ContentMasks.IS_COMBINED;
|
||||
return this.content & Content.IS_COMBINED_MASK;
|
||||
}
|
||||
|
||||
/** Width of the cell. */
|
||||
public getWidth(): number {
|
||||
return this.content >> WIDTH_MASK_SHIFT;
|
||||
return this.content >> Content.WIDTH_SHIFT;
|
||||
}
|
||||
|
||||
/** JS string of the content. */
|
||||
public getChars(): string {
|
||||
if (this.content & ContentMasks.IS_COMBINED) {
|
||||
if (this.content & Content.IS_COMBINED_MASK) {
|
||||
return this.combinedData;
|
||||
}
|
||||
if (this.content & ContentMasks.CODEPOINT) {
|
||||
return stringFromCodePoint(this.content & ContentMasks.CODEPOINT);
|
||||
if (this.content & Content.CODEPOINT_MASK) {
|
||||
return stringFromCodePoint(this.content & Content.CODEPOINT_MASK);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -125,7 +241,7 @@ export class CellData implements ICellData {
|
||||
public getCode(): number {
|
||||
return (this.isCombined())
|
||||
? this.combinedData.charCodeAt(this.combinedData.length - 1)
|
||||
: this.content & ContentMasks.CODEPOINT;
|
||||
: this.content & Content.CODEPOINT_MASK;
|
||||
}
|
||||
|
||||
/** Set data from CharData */
|
||||
@@ -144,7 +260,7 @@ export class CellData implements ICellData {
|
||||
if (0xD800 <= code && code <= 0xDBFF) {
|
||||
const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1);
|
||||
if (0xDC00 <= second && second <= 0xDFFF) {
|
||||
this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
|
||||
this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||
} else {
|
||||
combined = true;
|
||||
}
|
||||
@@ -152,11 +268,11 @@ export class CellData implements ICellData {
|
||||
combined = true;
|
||||
}
|
||||
} else {
|
||||
this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
|
||||
this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||
}
|
||||
if (combined) {
|
||||
this.combinedData = value[CHAR_DATA_CHAR_INDEX];
|
||||
this.content = ContentMasks.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
|
||||
this.content = Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,14 +320,14 @@ export class BufferLine implements IBufferLine {
|
||||
*/
|
||||
public get(index: number): CharData {
|
||||
const content = this._data[index * CELL_SIZE + Cell.CONTENT];
|
||||
const cp = content & ContentMasks.CODEPOINT;
|
||||
const cp = content & Content.CODEPOINT_MASK;
|
||||
return [
|
||||
this._data[index * CELL_SIZE + Cell.FG],
|
||||
(content & ContentMasks.IS_COMBINED)
|
||||
(content & Content.IS_COMBINED_MASK)
|
||||
? this._combined[index]
|
||||
: (cp) ? stringFromCodePoint(cp) : '',
|
||||
content >> WIDTH_MASK_SHIFT,
|
||||
(content & ContentMasks.IS_COMBINED)
|
||||
content >> Content.WIDTH_SHIFT,
|
||||
(content & Content.IS_COMBINED_MASK)
|
||||
? this._combined[index].charCodeAt(this._combined[index].length - 1)
|
||||
: cp
|
||||
];
|
||||
@@ -225,9 +341,9 @@ export class BufferLine implements IBufferLine {
|
||||
this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
|
||||
if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
|
||||
this._combined[index] = value[1];
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = index | ContentMasks.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||
} else {
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT);
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,12 +352,12 @@ export class BufferLine implements IBufferLine {
|
||||
* use these when only one value is needed, otherwise use `loadCell`
|
||||
*/
|
||||
public getWidth(index: number): number {
|
||||
return this._data[index * CELL_SIZE + Cell.CONTENT] >> WIDTH_MASK_SHIFT;
|
||||
return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT;
|
||||
}
|
||||
|
||||
/** Test whether content has width. */
|
||||
public hasWidth(index: number): number {
|
||||
return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.WIDTH;
|
||||
return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK;
|
||||
}
|
||||
|
||||
/** Get FG cell component. */
|
||||
@@ -260,7 +376,7 @@ export class BufferLine implements IBufferLine {
|
||||
* from real empty cells.
|
||||
* */
|
||||
public hasContent(index: number): number {
|
||||
return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.HAS_CONTENT;
|
||||
return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,25 +386,25 @@ export class BufferLine implements IBufferLine {
|
||||
*/
|
||||
public getCodePoint(index: number): number {
|
||||
const content = this._data[index * CELL_SIZE + Cell.CONTENT];
|
||||
if (content & ContentMasks.IS_COMBINED) {
|
||||
if (content & Content.IS_COMBINED_MASK) {
|
||||
return this._combined[index].charCodeAt(this._combined[index].length - 1);
|
||||
}
|
||||
return content & ContentMasks.CODEPOINT;
|
||||
return content & Content.CODEPOINT_MASK;
|
||||
}
|
||||
|
||||
/** Test whether the cell contains a combined string. */
|
||||
public isCombined(index: number): number {
|
||||
return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.IS_COMBINED;
|
||||
return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK;
|
||||
}
|
||||
|
||||
/** Returns the string content of the cell. */
|
||||
public getString(index: number): string {
|
||||
const content = this._data[index * CELL_SIZE + Cell.CONTENT];
|
||||
if (content & ContentMasks.IS_COMBINED) {
|
||||
if (content & Content.IS_COMBINED_MASK) {
|
||||
return this._combined[index];
|
||||
}
|
||||
if (content & ContentMasks.CODEPOINT) {
|
||||
return stringFromCodePoint(content & ContentMasks.CODEPOINT);
|
||||
if (content & Content.CODEPOINT_MASK) {
|
||||
return stringFromCodePoint(content & Content.CODEPOINT_MASK);
|
||||
}
|
||||
// return empty string for empty cells
|
||||
return '';
|
||||
@@ -303,7 +419,7 @@ export class BufferLine implements IBufferLine {
|
||||
cell.content = this._data[startIndex + Cell.CONTENT];
|
||||
cell.fg = this._data[startIndex + Cell.FG];
|
||||
cell.bg = this._data[startIndex + Cell.BG];
|
||||
if (cell.content & ContentMasks.IS_COMBINED) {
|
||||
if (cell.content & Content.IS_COMBINED_MASK) {
|
||||
cell.combinedData = this._combined[index];
|
||||
}
|
||||
return cell;
|
||||
@@ -313,7 +429,7 @@ export class BufferLine implements IBufferLine {
|
||||
* Set data at `index` to `cell`.
|
||||
*/
|
||||
public setCell(index: number, cell: ICellData): void {
|
||||
if (cell.content & ContentMasks.IS_COMBINED) {
|
||||
if (cell.content & Content.IS_COMBINED_MASK) {
|
||||
this._combined[index] = cell.combinedData;
|
||||
}
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content;
|
||||
@@ -327,7 +443,7 @@ export class BufferLine implements IBufferLine {
|
||||
* it gets an optimized access method.
|
||||
*/
|
||||
public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void {
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << WIDTH_MASK_SHIFT);
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
|
||||
this._data[index * CELL_SIZE + Cell.FG] = fg;
|
||||
this._data[index * CELL_SIZE + Cell.BG] = bg;
|
||||
}
|
||||
@@ -340,21 +456,21 @@ export class BufferLine implements IBufferLine {
|
||||
*/
|
||||
public addCodepointToCell(index: number, codePoint: number): void {
|
||||
let content = this._data[index * CELL_SIZE + Cell.CONTENT];
|
||||
if (content & ContentMasks.IS_COMBINED) {
|
||||
if (content & Content.IS_COMBINED_MASK) {
|
||||
// we already have a combined string, simply add
|
||||
this._combined[index] += stringFromCodePoint(codePoint);
|
||||
} else {
|
||||
if (content & ContentMasks.CODEPOINT) {
|
||||
if (content & Content.CODEPOINT_MASK) {
|
||||
// normal case for combining chars:
|
||||
// - move current leading char + new one into combined string
|
||||
// - set combined flag
|
||||
this._combined[index] = stringFromCodePoint(content & ContentMasks.CODEPOINT) + stringFromCodePoint(codePoint);
|
||||
content &= ~ContentMasks.CODEPOINT; // set codepoint in buffer to 0
|
||||
content |= ContentMasks.IS_COMBINED;
|
||||
this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint);
|
||||
content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0
|
||||
content |= Content.IS_COMBINED_MASK;
|
||||
} else {
|
||||
// should not happen - we actually have no data in the cell yet
|
||||
// simply set the data in the cell buffer with a width of 1
|
||||
content = codePoint | (1 << WIDTH_MASK_SHIFT);
|
||||
content = codePoint | (1 << Content.WIDTH_SHIFT);
|
||||
}
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = content;
|
||||
}
|
||||
@@ -476,8 +592,8 @@ export class BufferLine implements IBufferLine {
|
||||
|
||||
public getTrimmedLength(): number {
|
||||
for (let i = this.length - 1; i >= 0; --i) {
|
||||
if ((this._data[i * CELL_SIZE + Cell.CONTENT] & ContentMasks.HAS_CONTENT)) {
|
||||
return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> WIDTH_MASK_SHIFT);
|
||||
if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) {
|
||||
return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -516,9 +632,9 @@ export class BufferLine implements IBufferLine {
|
||||
let result = '';
|
||||
while (startCol < endCol) {
|
||||
const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];
|
||||
const cp = content & ContentMasks.CODEPOINT;
|
||||
result += (content & ContentMasks.IS_COMBINED) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
|
||||
startCol += (content >> WIDTH_MASK_SHIFT) || 1; // always advance by 1
|
||||
const cp = content & Content.CODEPOINT_MASK;
|
||||
result += (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
|
||||
startCol += (content >> Content.WIDTH_SHIFT) || 1; // always advance by 1
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user