mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
apply utf32 buffer layout
This commit is contained in:
+83
-26
@@ -3,7 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
import { CharData, IBufferLine } from './Types';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR } from './Buffer';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Class representing a terminal line.
|
||||
@@ -131,18 +132,75 @@ export class BufferLineJSArray implements IBufferLine {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* buffer memory layout:
|
||||
*
|
||||
* | uint32_t | uint32_t | uint32_t |
|
||||
* | `content` | `FG` | `BG` |
|
||||
* | wcwidth(2) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) |
|
||||
*/
|
||||
|
||||
|
||||
/** typed array slots taken by one cell */
|
||||
const CELL_SIZE = 3;
|
||||
|
||||
/** cell member indices */
|
||||
/**
|
||||
* Cell member indices.
|
||||
*
|
||||
* Direct access:
|
||||
* `content = data[column * CELL_SIZE + Cell.CONTENT];`
|
||||
* `fg = data[column * CELL_SIZE + Cell.FG];`
|
||||
* `bg = data[column * CELL_SIZE + Cell.BG];`
|
||||
*/
|
||||
const enum Cell {
|
||||
FLAGS = 0,
|
||||
STRING = 1,
|
||||
WIDTH = 2
|
||||
CONTENT = 0,
|
||||
FG = 1, // currently simply holds all known attrs
|
||||
BG = 2 // currently unused
|
||||
}
|
||||
|
||||
/** single vs. combined char distinction */
|
||||
const IS_COMBINED_BIT_MASK = 0x80000000;
|
||||
/**
|
||||
* Bitmasks and helper for accessing data in `content`.
|
||||
*/
|
||||
const enum Content {
|
||||
/**
|
||||
* bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
|
||||
* read: `codepoint = content & Content.codepointMask;`
|
||||
* write: `content |= codepoint & Content.codepointMask;`
|
||||
* shortcut if precondition `codepoint <= 0x10FFFF` is met:
|
||||
* `content |= codepoint;`
|
||||
*/
|
||||
CODEPOINT_MASK = 0x1FFFFF,
|
||||
|
||||
/**
|
||||
* bit 22 flag indication whether a cell contains combined content
|
||||
* read: `isCombined = content & Content.isCombined;`
|
||||
* set: `content |= Content.isCombined;`
|
||||
* clear: `content &= ~Content.isCombined;`
|
||||
*/
|
||||
IS_COMBINED = 0x200000, // 1 << 21
|
||||
|
||||
/**
|
||||
* bit 1..22 mask to check whether a cell contains any string data
|
||||
* we need to check for codepoint and isCombined bits to see
|
||||
* whether a cell contains anything
|
||||
* read: `isEmtpy = !(content & Content.hasContent)`
|
||||
*/
|
||||
HAS_CONTENT = 0x2FFFFF,
|
||||
|
||||
/**
|
||||
* bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
|
||||
* read: `width = (content & Content.widthMask) >> Content.widthShift;`
|
||||
* `hasWidth = content & Content.widthMask;`
|
||||
* as long as wcwidth is highest value in `content`:
|
||||
* `width = content >> Content.widthShift;`
|
||||
* write: `content |= (width << Content.widthShift) & Content.widthMask;`
|
||||
* shortcut if precondition `0 <= width <= 3` is met:
|
||||
* `content |= width << Content.widthShift;`
|
||||
*/
|
||||
WIDTH_MASK = 0xC00000, // 3 << 22
|
||||
WIDTH_SHIFT = 22
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed array based bufferline implementation.
|
||||
@@ -166,28 +224,28 @@ export class BufferLine implements IBufferLine {
|
||||
}
|
||||
|
||||
public get(index: number): CharData {
|
||||
const stringData = this._data[index * CELL_SIZE + Cell.STRING];
|
||||
const content = this._data[index * CELL_SIZE + Cell.CONTENT];
|
||||
const cp = content & Content.CODEPOINT_MASK;
|
||||
return [
|
||||
this._data[index * CELL_SIZE + Cell.FLAGS],
|
||||
(stringData & IS_COMBINED_BIT_MASK)
|
||||
this._data[index * CELL_SIZE + Cell.FG],
|
||||
(content & Content.IS_COMBINED)
|
||||
? this._combined[index]
|
||||
: (stringData) ? String.fromCharCode(stringData) : '',
|
||||
this._data[index * CELL_SIZE + Cell.WIDTH],
|
||||
(stringData & IS_COMBINED_BIT_MASK)
|
||||
: (cp) ? String.fromCharCode(cp) : '',
|
||||
content >> Content.WIDTH_SHIFT,
|
||||
(content & Content.IS_COMBINED)
|
||||
? this._combined[index].charCodeAt(this._combined[index].length - 1)
|
||||
: stringData
|
||||
: cp
|
||||
];
|
||||
}
|
||||
|
||||
public set(index: number, value: CharData): void {
|
||||
this._data[index * CELL_SIZE + Cell.FLAGS] = value[0];
|
||||
if (value[1].length > 1) {
|
||||
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.STRING] = index | IS_COMBINED_BIT_MASK;
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = index | Content.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||
} else {
|
||||
this._data[index * CELL_SIZE + Cell.STRING] = value[1].charCodeAt(0);
|
||||
this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||
}
|
||||
this._data[index * CELL_SIZE + Cell.WIDTH] = value[2];
|
||||
}
|
||||
|
||||
public insertCells(pos: number, n: number, fillCharData: CharData): void {
|
||||
@@ -284,8 +342,6 @@ export class BufferLine implements IBufferLine {
|
||||
/** create a new clone */
|
||||
public clone(): IBufferLine {
|
||||
const newLine = new BufferLine(0);
|
||||
// creation of new typed array from another is actually pretty slow :(
|
||||
// still faster than copying values one by one
|
||||
newLine._data = new Uint32Array(this._data);
|
||||
newLine.length = this.length;
|
||||
for (const el in this._combined) {
|
||||
@@ -297,8 +353,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.STRING] !== 0) { // 0 ==> ''.charCodeAt(0) ==> NaN ==> 0
|
||||
return i + this._data[i * CELL_SIZE + Cell.WIDTH];
|
||||
if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT)) {
|
||||
return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -310,9 +366,10 @@ export class BufferLine implements IBufferLine {
|
||||
}
|
||||
let result = '';
|
||||
while (startCol < endCol) {
|
||||
const stringData = this._data[startCol * CELL_SIZE + Cell.STRING];
|
||||
result += (stringData & IS_COMBINED_BIT_MASK) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : WHITESPACE_CELL_CHAR;
|
||||
startCol += this._data[startCol * CELL_SIZE + Cell.WIDTH] || 1;
|
||||
const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];
|
||||
const cp = content & Content.CODEPOINT_MASK;
|
||||
result += (content & Content.IS_COMBINED) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
|
||||
startCol += (content >> Content.WIDTH_SHIFT) || 1; // always advance by 1
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -76,9 +76,6 @@ export class StringToUtf32 {
|
||||
* Polyfill - Convert UTF32 codepoint into JS string.
|
||||
*/
|
||||
export function stringFromCodePoint(codePoint: number): string {
|
||||
if ((String as any).fromCodePoint) {
|
||||
return (String as any).fromCodePoint(codePoint);
|
||||
}
|
||||
if (codePoint > 0xFFFF) {
|
||||
codePoint -= 0x10000;
|
||||
return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00);
|
||||
|
||||
Reference in New Issue
Block a user