From ba1582cf3c5e0c6d57523b8f703bc9fa8eff2526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 3 Jan 2019 19:01:43 +0100 Subject: [PATCH 01/30] apply utf32 buffer layout --- src/BufferLine.ts | 109 ++++++++++++++++++++++++++-------- src/core/input/TextDecoder.ts | 3 - 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 3f93af626a..c16c380836 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -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; } diff --git a/src/core/input/TextDecoder.ts b/src/core/input/TextDecoder.ts index 77e6971c25..04407a09fe 100644 --- a/src/core/input/TextDecoder.ts +++ b/src/core/input/TextDecoder.ts @@ -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); From a2a8c3d47f2e448a9358d9195651db3146565d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 3 Jan 2019 21:08:18 +0100 Subject: [PATCH 02/30] extend buffer line with more direct access methods --- src/BufferLine.ts | 53 +++++++++++++++++++++++++++++++++++++++++++++ src/InputHandler.ts | 13 +++++------ src/Types.ts | 3 +++ 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index c16c380836..7c70c93bc8 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -248,6 +248,59 @@ export class BufferLine implements IBufferLine { } } + /** + * Set cell data from input handler. + * Since the input handler see the incoming chars as UTF32 codepoints, + * it gets an optimized access method. + */ + public setDataFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void { + 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; + } + + /** + * Add a char to a cell from input handler. + * During input stage combining chars with a width of 0 follow and stack + * onto a leading char. Since we already set the attrs + * by the previous `setDataFromCodePoint` call, we can omit it here. + */ + public addCharToCell(index: number, codePoint: number): void { + let content = this._data[index * CELL_SIZE + Cell.CONTENT]; + if (content & Content.IS_COMBINED) { + // we already have a combined string, simply add + this._combined[index] += stringFromCodePoint(codePoint); + } else { + if (content & Content.CODEPOINT_MASK) { + // normal case for combining chars: + // - move current leading char + new one into combined string + // - set codepoint in cell buffer to index + // - set combined flag + this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint); + content &= ~Content.CODEPOINT_MASK; + content |= index | Content.IS_COMBINED; + } 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 << Content.WIDTH_SHIFT); + } + this._data[index * CELL_SIZE + Cell.CONTENT] = content; + } + } + + /** + * Set data from another buffer cell. + * Useful for basic in buffer copy action. + */ + public setDataFromCellData(index: number, content: number, fg: number, bg: number, combined?: string): void { + this._data[index * CELL_SIZE + Cell.CONTENT] = content; + this._data[index * CELL_SIZE + Cell.FG] = fg; + this._data[index * CELL_SIZE + Cell.BG] = bg; + if (content & Content.IS_COMBINED && combined) { + this._combined[index] = combined; + } + } + public insertCells(pos: number, n: number, fillCharData: CharData): void { pos %= this.length; if (n < this.length - pos) { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e270a5f3af..bf8149b24d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -331,7 +331,6 @@ export class InputHandler extends Disposable implements IInputHandler { public print(data: Uint32Array, start: number, end: number): void { let code: number; - let char: string; let chWidth: number; const buffer: IBuffer = this._terminal.buffer; const charset: ICharset = this._terminal.charset; @@ -345,7 +344,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(buffer.y); for (let pos = start; pos < end; ++pos) { code = data[pos]; - char = stringFromCodePoint(code); // calculate print space // expensive call, therefore we save width in line buffer @@ -355,15 +353,14 @@ export class InputHandler extends Disposable implements IInputHandler { // charset are only defined for ASCII, therefore we only // search for an replacement char if code < 127 if (code < 127 && charset) { - const ch = charset[char]; + const ch = charset[String.fromCharCode(code)]; if (ch) { code = ch.charCodeAt(0); - char = ch; } } if (screenReaderMode) { - this._terminal.emit('a11y.char', char); + this._terminal.emit('a11y.char', stringFromCodePoint(code)); } // insert combining char at last cursor position @@ -380,12 +377,12 @@ export class InputHandler extends Disposable implements IInputHandler { // since an empty cell is only set by fullwidth chars const chMinusTwo = bufferRow.get(buffer.x - 2); if (chMinusTwo) { - chMinusTwo[CHAR_DATA_CHAR_INDEX] += char; + chMinusTwo[CHAR_DATA_CHAR_INDEX] += stringFromCodePoint(code); chMinusTwo[CHAR_DATA_CODE_INDEX] = code; bufferRow.set(buffer.x - 2, chMinusTwo); // must be set explicitly now } } else { - chMinusOne[CHAR_DATA_CHAR_INDEX] += char; + chMinusOne[CHAR_DATA_CHAR_INDEX] += stringFromCodePoint(code); chMinusOne[CHAR_DATA_CODE_INDEX] = code; bufferRow.set(buffer.x - 1, chMinusOne); // must be set explicitly now } @@ -438,7 +435,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // write current char to buffer and advance cursor - bufferRow.set(buffer.x++, [curAttr, char, chWidth, code]); + bufferRow.set(buffer.x++, [curAttr, stringFromCodePoint(code), chWidth, code]); // fullwidth char - also set next cell to placeholder stub and advance cursor // for graphemes bigger than fullwidth we can simply loop to zero diff --git a/src/Types.ts b/src/Types.ts index 60b86de15b..5c4b488074 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -519,6 +519,9 @@ export interface IBufferLine { isWrapped: boolean; get(index: number): CharData; set(index: number, value: CharData): void; + setDataFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; + addCharToCell(index: number, codePoint: number): void; + setDataFromCellData(index: number, content: number, fg: number, bg: number, combined?: string): void; insertCells(pos: number, n: number, ch: CharData): void; deleteCells(pos: number, n: number, fill: CharData): void; replaceCells(start: number, end: number, fill: CharData): void; From b0eecea45aa3c13053bed58d3c744067913424fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 3 Jan 2019 21:40:34 +0100 Subject: [PATCH 03/30] partially apply fast data access in InputHandler.print --- src/BufferLine.ts | 2 +- src/InputHandler.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 7963934e51..af838144a2 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -105,7 +105,7 @@ export class BufferLine implements IBufferLine { this._data[index * CELL_SIZE + Cell.FG], (content & Content.IS_COMBINED) ? this._combined[index] - : (cp) ? String.fromCharCode(cp) : '', + : (cp) ? stringFromCodePoint(cp) : '', content >> Content.WIDTH_SHIFT, (content & Content.IS_COMBINED) ? this._combined[index].charCodeAt(this._combined[index].length - 1) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index bf8149b24d..d19be5a122 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -435,14 +435,14 @@ export class InputHandler extends Disposable implements IInputHandler { } // write current char to buffer and advance cursor - bufferRow.set(buffer.x++, [curAttr, stringFromCodePoint(code), chWidth, code]); + bufferRow.setDataFromCodePoint(buffer.x++, code, chWidth, curAttr, 0); // fullwidth char - also set next cell to placeholder stub and advance cursor // for graphemes bigger than fullwidth we can simply loop to zero // we already made sure above, that buffer.x + chWidth will not overflow right if (chWidth > 0) { while (--chWidth) { - bufferRow.set(buffer.x++, [curAttr, '', 0, undefined]); + bufferRow.setDataFromCodePoint(buffer.x++, 0, 0, curAttr, 0); } } } From 75fbda1fe6297f5aeb4fbb0f8493d16e137f3e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Jan 2019 01:32:20 +0100 Subject: [PATCH 04/30] further opt for InputHandler.print --- src/InputHandler.ts | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d19be5a122..2c6198d342 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -350,7 +350,7 @@ export class InputHandler extends Disposable implements IInputHandler { chWidth = wcwidth(code); // get charset replacement character - // charset are only defined for ASCII, therefore we only + // charset is only defined for ASCII, therefore we only // search for an replacement char if code < 127 if (code < 127 && charset) { const ch = charset[String.fromCharCode(code)]; @@ -370,22 +370,13 @@ export class InputHandler extends Disposable implements IInputHandler { // therefore we can test for buffer.x to avoid overflow left if (!chWidth && buffer.x) { const chMinusOne = bufferRow.get(buffer.x - 1); - if (chMinusOne) { - if (!chMinusOne[CHAR_DATA_WIDTH_INDEX]) { - // found empty cell after fullwidth, need to go 2 cells back - // it is save to step 2 cells back here - // since an empty cell is only set by fullwidth chars - const chMinusTwo = bufferRow.get(buffer.x - 2); - if (chMinusTwo) { - chMinusTwo[CHAR_DATA_CHAR_INDEX] += stringFromCodePoint(code); - chMinusTwo[CHAR_DATA_CODE_INDEX] = code; - bufferRow.set(buffer.x - 2, chMinusTwo); // must be set explicitly now - } - } else { - chMinusOne[CHAR_DATA_CHAR_INDEX] += stringFromCodePoint(code); - chMinusOne[CHAR_DATA_CODE_INDEX] = code; - bufferRow.set(buffer.x - 1, chMinusOne); // must be set explicitly now - } + if (!chMinusOne[CHAR_DATA_WIDTH_INDEX]) { + // found empty cell after fullwidth, need to go 2 cells back + // it is save to step 2 cells back here + // since an empty cell is only set by fullwidth chars + bufferRow.addCharToCell(buffer.x - 2, code); + } else { + bufferRow.addCharToCell(buffer.x - 1, code); } continue; } @@ -430,7 +421,7 @@ export class InputHandler extends Disposable implements IInputHandler { // and will be set to eraseChar const lastCell = bufferRow.get(cols - 1); if (lastCell[CHAR_DATA_WIDTH_INDEX] === 2) { - bufferRow.set(cols - 1, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + bufferRow.setDataFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr, 0); } } From 2f66efa49a107cfd4585b3d272b8248dabdec8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Jan 2019 01:32:56 +0100 Subject: [PATCH 05/30] first try to optimize read access --- src/BufferLine.ts | 44 +++++++++++++++++++++++++++++++-- src/renderer/TextRenderLayer.ts | 22 ++++++++--------- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index af838144a2..1700279c08 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -7,7 +7,6 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, import { stringFromCodePoint } from './core/input/TextDecoder'; - /** * buffer memory layout: * @@ -37,7 +36,7 @@ const enum Cell { /** * Bitmasks and helper for accessing data in `content`. */ -const enum Content { +export const enum Content { /** * bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken) * read: `codepoint = content & Content.codepointMask;` @@ -77,6 +76,25 @@ const enum Content { WIDTH_SHIFT = 22 } +export class CellData { + public content: number = 0; + public fg: number = 0; + public bg: number = 0; + public combinedData: string = ''; + public get combined(): number { + return this.content & Content.IS_COMBINED; + } + public get width(): number { + return this.content >> Content.WIDTH_SHIFT; + } + public get chars(): string { + return (this.content & Content.IS_COMBINED) ? this.combinedData : stringFromCodePoint(this.content & Content.CODEPOINT_MASK); + } + public get code(): number { + return ((this.combined) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK); + } +} + /** * Typed array based bufferline implementation. */ @@ -123,6 +141,28 @@ export class BufferLine implements IBufferLine { } } + public loadCell(index: number, cell: CellData): CellData { + cell.content = this._data[index * CELL_SIZE + Cell.CONTENT]; + cell.fg = this._data[index * CELL_SIZE + Cell.FG]; + cell.bg = this._data[index * CELL_SIZE + Cell.BG]; + if (cell.content & Content.IS_COMBINED) { + cell.combinedData = this._combined[index]; + } + return cell; + } + + public setCell(index: number, cell: CellData): void { + if (cell.content & Content.IS_COMBINED) { + this._combined[index] = cell.combinedData; + // we also need to clear and set codepoint to index + cell.content &= ~Content.CODEPOINT_MASK; + cell.content |= index; + } + this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content; + this._data[index * CELL_SIZE + Cell.FG] = cell.fg; + this._data[index * CELL_SIZE + Cell.BG] = cell.bg; + } + /** * Set cell data from input handler. * Since the input handler see the incoming chars as UTF32 codepoints, diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index ade2dd4c51..931c465199 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -3,13 +3,14 @@ * @license MIT */ -import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; +import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { CharData, ITerminal } from '../Types'; import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; import { is256Color } from './atlas/CharAtlasUtils'; +import { CellData } from '../BufferLine'; /** * This CharData looks like a null character, which will forc a clear and render @@ -24,6 +25,7 @@ export class TextRenderLayer extends BaseRenderLayer { private _characterFont: string; private _characterOverlapCache: { [key: string]: boolean } = {}; private _characterJoinerRegistry: ICharacterJoinerRegistry; + private _cell = new CellData(); constructor(container: HTMLElement, zIndex: number, colors: IColorSet, characterJoinerRegistry: ICharacterJoinerRegistry, alpha: boolean) { super(container, 'text', zIndex, alpha, colors); @@ -72,14 +74,14 @@ export class TextRenderLayer extends BaseRenderLayer { const line = terminal.buffer.lines.get(row); const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; for (let x = 0; x < terminal.cols; x++) { - const charData = line.get(x); - let code: number = charData[CHAR_DATA_CODE_INDEX] || WHITESPACE_CELL_CODE; + (line as any).loadCell(x, this._cell); + let code: number = this._cell.code || WHITESPACE_CELL_CODE; // Can either represent character(s) for a single cell or multiple cells // if indicated by a character joiner. - let chars: string = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; - const attr: number = charData[CHAR_DATA_ATTR_INDEX]; - let width: number = charData[CHAR_DATA_WIDTH_INDEX]; + let chars = this._cell.chars || WHITESPACE_CELL_CHAR; + const attr = this._cell.fg; + let width = this._cell.width; // If true, indicates that the current character(s) to draw were joined. let isJoined = false; @@ -117,7 +119,7 @@ export class TextRenderLayer extends BaseRenderLayer { // right is a space, take ownership of the cell to the right. We skip // this check for joined characters because their rendering likely won't // yield the same result as rendering the last character individually. - if (!isJoined && this._isOverlapping(charData)) { + if (!isJoined && this._isOverlapping(chars, width, code)) { // If the character is overlapping, we want to force a re-render on every // frame. This is specifically to work around the case where two // overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a @@ -271,21 +273,19 @@ export class TextRenderLayer extends BaseRenderLayer { /** * Whether a character is overlapping to the next cell. */ - private _isOverlapping(charData: CharData): boolean { + private _isOverlapping(char: string, width: number, code: number): boolean { // Only single cell characters can be overlapping, rendering issues can // occur without this check - if (charData[CHAR_DATA_WIDTH_INDEX] !== 1) { + if (width !== 1) { return false; } // We assume that any ascii character will not overlap - const code = charData[CHAR_DATA_CODE_INDEX]; if (code < 256) { return false; } // Deliver from cache if available - const char = charData[CHAR_DATA_CHAR_INDEX]; if (this._characterOverlapCache.hasOwnProperty(char)) { return this._characterOverlapCache[char]; } From f3619ab61c4aedc2bb74e824be9c6e7184f64ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Jan 2019 01:33:49 +0100 Subject: [PATCH 06/30] fix linter error --- src/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2c6198d342..7cc2b8b61d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types'; import { C0, C1 } from './common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; +import { CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; import { FLAGS } from './renderer/Types'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; From 802543f7cf2a593be079a5d9e782bb07ac6c526b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Jan 2019 02:58:15 +0100 Subject: [PATCH 07/30] replace set(CharData) with setCell --- src/BufferLine.ts | 78 ++++++++++++++++++++++++++++----------------- src/InputHandler.ts | 10 +++--- src/Types.ts | 16 +++++++++- 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 1700279c08..827e7c9397 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -2,7 +2,7 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ -import { CharData, IBufferLine } from './Types'; +import { CharData, IBufferLine, ICellData } 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'; @@ -76,7 +76,7 @@ export const enum Content { WIDTH_SHIFT = 22 } -export class CellData { +export class CellData implements ICellData { public content: number = 0; public fg: number = 0; public bg: number = 0; @@ -93,6 +93,31 @@ export class CellData { public get code(): number { return ((this.combined) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK); } + public setFromCharData(value: CharData): void { + this.fg = value[CHAR_DATA_ATTR_INDEX]; + this.bg = 0; + let combined = false; + if (value[CHAR_DATA_CHAR_INDEX].length > 2) { + combined = true; + } else if (value[CHAR_DATA_CHAR_INDEX].length === 2) { + const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0); + 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] << Content.WIDTH_SHIFT); + } else { + combined = true; + } + } + combined = true; + } else { + 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 = Content.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); + } + } } /** @@ -101,16 +126,15 @@ export class CellData { export class BufferLine implements IBufferLine { protected _data: Uint32Array | null = null; protected _combined: {[index: number]: string} = {}; + protected _cell: CellData = new CellData(); public length: number; constructor(cols: number, fillCharData?: CharData, public isWrapped: boolean = false) { - if (!fillCharData) { - fillCharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - } if (cols) { this._data = new Uint32Array(cols * CELL_SIZE); + this._cell.setFromCharData(fillCharData || [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); for (let i = 0; i < cols; ++i) { - this.set(i, fillCharData); + this.setCell(i, this._cell); } } this.length = cols; @@ -141,7 +165,7 @@ export class BufferLine implements IBufferLine { } } - public loadCell(index: number, cell: CellData): CellData { + public loadCell(index: number, cell: ICellData): ICellData { cell.content = this._data[index * CELL_SIZE + Cell.CONTENT]; cell.fg = this._data[index * CELL_SIZE + Cell.FG]; cell.bg = this._data[index * CELL_SIZE + Cell.BG]; @@ -151,7 +175,7 @@ export class BufferLine implements IBufferLine { return cell; } - public setCell(index: number, cell: CellData): void { + public setCell(index: number, cell: ICellData): void { if (cell.content & Content.IS_COMBINED) { this._combined[index] = cell.combinedData; // we also need to clear and set codepoint to index @@ -203,31 +227,20 @@ export class BufferLine implements IBufferLine { } } - /** - * Set data from another buffer cell. - * Useful for basic in buffer copy action. - */ - public setDataFromCellData(index: number, content: number, fg: number, bg: number, combined?: string): void { - this._data[index * CELL_SIZE + Cell.CONTENT] = content; - this._data[index * CELL_SIZE + Cell.FG] = fg; - this._data[index * CELL_SIZE + Cell.BG] = bg; - if (content & Content.IS_COMBINED && combined) { - this._combined[index] = combined; - } - } - public insertCells(pos: number, n: number, fillCharData: CharData): void { pos %= this.length; if (n < this.length - pos) { for (let i = this.length - pos - n - 1; i >= 0; --i) { - this.set(pos + n + i, this.get(pos + i)); + this.setCell(pos + n + i, this.loadCell(pos + i, this._cell)); } + this._cell.setFromCharData(fillCharData); for (let i = 0; i < n; ++i) { - this.set(pos + i, fillCharData); + this.setCell(pos + i, this._cell); } } else { + this._cell.setFromCharData(fillCharData); for (let i = pos; i < this.length; ++i) { - this.set(i, fillCharData); + this.setCell(i, this._cell); } } } @@ -236,21 +249,24 @@ export class BufferLine implements IBufferLine { pos %= this.length; if (n < this.length - pos) { for (let i = 0; i < this.length - pos - n; ++i) { - this.set(pos + i, this.get(pos + n + i)); + this.setCell(pos + i, this.loadCell(pos + n + i, this._cell)); } + this._cell.setFromCharData(fillCharData); for (let i = this.length - n; i < this.length; ++i) { - this.set(i, fillCharData); + this.setCell(i, this._cell); } } else { + this._cell.setFromCharData(fillCharData); for (let i = pos; i < this.length; ++i) { - this.set(i, fillCharData); + this.setCell(i, this._cell); } } } public replaceCells(start: number, end: number, fillCharData: CharData): void { + this._cell.setFromCharData(fillCharData); while (start < end && start < this.length) { - this.set(start++, fillCharData); + this.setCell(start++, this._cell); } } @@ -268,8 +284,9 @@ export class BufferLine implements IBufferLine { } } this._data = data; + this._cell.setFromCharData(fillCharData); for (let i = this.length; i < cols; ++i) { - this.set(i, fillCharData); + this.setCell(i, this._cell); } } else if (shrink) { if (cols) { @@ -286,8 +303,9 @@ export class BufferLine implements IBufferLine { /** fill a line with fillCharData */ public fill(fillCharData: CharData): void { this._combined = {}; + this._cell.setFromCharData(fillCharData); for (let i = 0; i < this.length; ++i) { - this.set(i, fillCharData); + this.setCell(i, this._cell); } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7cc2b8b61d..f837f29572 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types'; import { C0, C1 } from './common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; -import { CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; +import { DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; import { FLAGS } from './renderer/Types'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; @@ -16,6 +16,7 @@ import { IDisposable } from 'xterm'; import { Disposable } from './common/Lifecycle'; import { concat, utf32ToString } from './common/TypedArrayUtils'; import { StringToUtf32, stringFromCodePoint } from './core/input/TextDecoder'; +import { CellData } from './BufferLine'; /** * Map collect to glevel. Used in `selectCharset`. @@ -121,6 +122,7 @@ class DECRQSS implements IDcsHandler { export class InputHandler extends Disposable implements IInputHandler { private _parseBuffer: Uint32Array = new Uint32Array(4096); private _stringDecoder: StringToUtf32 = new StringToUtf32(); + private _cell: CellData = new CellData(); constructor( protected _terminal: IInputHandlingTerminal, @@ -369,8 +371,7 @@ export class InputHandler extends Disposable implements IInputHandler { // since they always follow a cell consuming char // therefore we can test for buffer.x to avoid overflow left if (!chWidth && buffer.x) { - const chMinusOne = bufferRow.get(buffer.x - 1); - if (!chMinusOne[CHAR_DATA_WIDTH_INDEX]) { + if (!bufferRow.loadCell(buffer.x - 1, this._cell).width) { // found empty cell after fullwidth, need to go 2 cells back // it is save to step 2 cells back here // since an empty cell is only set by fullwidth chars @@ -419,8 +420,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test last cell - since the last cell has only room for // a halfwidth char any fullwidth shifted there is lost // and will be set to eraseChar - const lastCell = bufferRow.get(cols - 1); - if (lastCell[CHAR_DATA_WIDTH_INDEX] === 2) { + if (bufferRow.loadCell(cols - 1, this._cell).width === 2) { bufferRow.setDataFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr, 0); } } diff --git a/src/Types.ts b/src/Types.ts index b89d5422d8..180fe00092 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -511,6 +511,19 @@ export interface IEscapeSequenceParser extends IDisposable { clearErrorHandler(): void; } +/** Cell data */ +export interface ICellData { + content: number; + fg: number; + bg: number; + combinedData: string; + combined: number; + width: number; + chars: string; + code: number; + setFromCharData(value: CharData): void; +} + /** * Interface for a line in the terminal buffer. */ @@ -519,9 +532,10 @@ export interface IBufferLine { isWrapped: boolean; get(index: number): CharData; set(index: number, value: CharData): void; + loadCell(index: number, cell: ICellData): ICellData; + setCell(index: number, cell: ICellData): void; setDataFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; addCharToCell(index: number, codePoint: number): void; - setDataFromCellData(index: number, content: number, fg: number, bg: number, combined?: string): void; insertCells(pos: number, n: number, ch: CharData): void; deleteCells(pos: number, n: number, fill: CharData): void; replaceCells(start: number, end: number, fill: CharData): void; From d7e5977d9ab825c5c9941f7a81371b6df897c69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Jan 2019 16:19:01 +0100 Subject: [PATCH 08/30] remove leftover --- src/InputHandler.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f837f29572..b53e911587 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -320,9 +320,6 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._parseBuffer.length < data.length) { this._parseBuffer = new Uint32Array(data.length); } - for (let i = 0; i < data.length; ++i) { - this._parseBuffer[i] = data.charCodeAt(i); - } this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer)); buffer = this._terminal.buffer; From 13ffed392d5067dc2c0f9aed51b064d2c367cf26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Jan 2019 18:36:07 +0100 Subject: [PATCH 09/30] remove get calls from renderer --- src/BufferLine.ts | 13 ++++++++++++- src/renderer/TextRenderLayer.ts | 2 +- src/renderer/dom/DomRendererRowFactory.ts | 17 ++++++++--------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 827e7c9397..2792d7108f 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -77,6 +77,11 @@ export const enum Content { } export class CellData implements ICellData { + public static fromCharData(value: CharData): CellData { + const obj = new CellData(); + obj.setFromCharData(value); + return obj; + } public content: number = 0; public fg: number = 0; public bg: number = 0; @@ -88,7 +93,13 @@ export class CellData implements ICellData { return this.content >> Content.WIDTH_SHIFT; } public get chars(): string { - return (this.content & Content.IS_COMBINED) ? this.combinedData : stringFromCodePoint(this.content & Content.CODEPOINT_MASK); + if (this.content & Content.IS_COMBINED) { + return this.combinedData; + } + if (this.content & Content.CODEPOINT_MASK) { + return stringFromCodePoint(this.content & Content.CODEPOINT_MASK); + } + return ''; } public get code(): number { return ((this.combined) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK); diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 931c465199..815eef17ce 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -127,7 +127,7 @@ export class TextRenderLayer extends BaseRenderLayer { // get removed, and `a` would not re-render because it thinks it's // already in the correct state. // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; - if (lastCharX < line.length - 1 && line.get(lastCharX + 1)[CHAR_DATA_CODE_INDEX] === NULL_CELL_CODE) { + if (lastCharX < line.length - 1 && line.loadCell(lastCharX + 1, this._cell).code === NULL_CELL_CODE) { width = 2; // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 8bcde39a05..83a4651e25 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -3,10 +3,11 @@ * @license MIT */ -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer'; import { FLAGS } from '../Types'; import { IBufferLine } from '../../Types'; import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { CellData } from '../../BufferLine'; export const BOLD_CLASS = 'xterm-bold'; export const ITALIC_CLASS = 'xterm-italic'; @@ -16,6 +17,7 @@ export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar'; export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'; export class DomRendererRowFactory { + private _cell: CellData = new CellData(); constructor( private _document: Document ) { @@ -31,19 +33,16 @@ export class DomRendererRowFactory { // the viewport). let lineLength = 0; for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) { - const charData = lineData.get(x); - const code = charData[CHAR_DATA_CODE_INDEX]; - if (code !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { + if (lineData.loadCell(x, this._cell).code !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { lineLength = x + 1; break; } } for (let x = 0; x < lineLength; x++) { - const charData = lineData.get(x); - const char = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; - const attr = charData[CHAR_DATA_ATTR_INDEX]; - const width = charData[CHAR_DATA_WIDTH_INDEX]; + lineData.loadCell(x, this._cell); + const attr = this._cell.fg; + const width = this._cell.width; // The character to the left is a wide character, drawing is owned by the char at x-1 if (width === 0) { @@ -101,7 +100,7 @@ export class DomRendererRowFactory { charElement.classList.add(ITALIC_CLASS); } - charElement.textContent = char; + charElement.textContent = this._cell.chars || WHITESPACE_CELL_CHAR; if (fg !== DEFAULT_COLOR) { charElement.classList.add(`xterm-fg-${fg}`); } From 6552a023c168fd94f4b0be70d597a8ed4534b354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Jan 2019 18:46:45 +0100 Subject: [PATCH 10/30] remove get from linkifier --- src/Linkifier.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 53247c95f8..3499c87d68 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -7,8 +7,8 @@ import { IMouseZoneManager } from './ui/Types'; import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types'; import { MouseZone } from './ui/MouseZoneManager'; import { EventEmitter } from './common/EventEmitter'; -import { CHAR_DATA_ATTR_INDEX } from './Buffer'; import { getStringCellWidth } from './CharWidth'; +import { CellData } from './BufferLine'; /** * The Linkifier applies links to rows shortly after they have been refreshed. @@ -34,6 +34,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _rowsTimeoutId: number; private _nextLinkMatcherId = 0; private _rowsToLinkify: { start: number, end: number }; + private _cell: CellData = new CellData(); constructor( protected _terminal: ITerminal @@ -232,11 +233,10 @@ export class Linkifier extends EventEmitter implements ILinkifier { } const line = this._terminal.buffer.lines.get(bufferIndex[0]); - const char = line.get(bufferIndex[1]); + line.loadCell(bufferIndex[1], this._cell); let fg: number | undefined; - if (char) { - const attr: number = char[CHAR_DATA_ATTR_INDEX]; - fg = (attr >> 9) & 0x1ff; + if (this._cell.fg) { + fg = (this._cell.fg >> 9) & 0x1ff; } if (matcher.validationCallback) { From b93b774971a9be93093d6eb553bd2b6c152610da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Jan 2019 19:14:56 +0100 Subject: [PATCH 11/30] direct cell attrs getter --- src/BufferLine.ts | 41 +++++++++++++++++++++++++++++++++++++++++ src/Types.ts | 10 ++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 2792d7108f..b3a946720e 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -176,6 +176,47 @@ export class BufferLine implements IBufferLine { } } + /** + * primitive getters + * use these when only one value is needed, otherwise use `loadCell` + */ + public getWidth(index: number): number { + return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT; + } + public hasWidth(index: number): number { + return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK; + } + public getFG(index: number): number { + return this._data[index * CELL_SIZE + Cell.FG]; + } + public getBG(index: number): number { + return this._data[index * CELL_SIZE + Cell.BG]; + } + public hasContent(index: number): number { + return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT; + } + public getCodePoint(index: number): number { + // returns either the single codepoint or the last charCode in combined + const content = this._data[index * CELL_SIZE + Cell.CONTENT]; + if (content & Content.IS_COMBINED) { + return this._combined[index].charCodeAt(this._combined[index].length - 1); + } + return content & Content.CODEPOINT_MASK; + } + public isCombined(index: number): number { + return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED; + } + public getString(index: number): string { + const content = this._data[index * CELL_SIZE + Cell.CONTENT]; + if (content & Content.IS_COMBINED) { + return this._combined[index]; + } + if (content & Content.CODEPOINT_MASK) { + return stringFromCodePoint(content & Content.CODEPOINT_MASK); + } + return ''; // return empty string for empty cells + } + public loadCell(index: number, cell: ICellData): ICellData { cell.content = this._data[index * CELL_SIZE + Cell.CONTENT]; cell.fg = this._data[index * CELL_SIZE + Cell.FG]; diff --git a/src/Types.ts b/src/Types.ts index 180fe00092..c1a7788a43 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -545,4 +545,14 @@ export interface IBufferLine { clone(): IBufferLine; getTrimmedLength(): number; translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string; + + /* direct access to cell attrs */ + getWidth(index: number): number; + hasWidth(index: number): number; + getFG(index: number): number; + getBG(index: number): number; + hasContent(index: number): number; + getCodePoint(index: number): number; + isCombined(index: number): number; + getString(index: number): string; } From 62dfa84d5d9ce248f955421007243220973aae81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 5 Jan 2019 01:01:10 +0100 Subject: [PATCH 12/30] remove get CharData calls from codebase (beside tests) --- src/Buffer.ts | 2 +- src/Linkifier.ts | 8 ++- src/SelectionManager.ts | 70 +++++++++++++------------ src/Terminal.ts | 4 +- src/renderer/BaseRenderLayer.ts | 10 ++-- src/renderer/CharacterJoinerRegistry.ts | 18 +++---- src/renderer/CursorRenderLayer.ts | 35 +++++++------ src/renderer/TextRenderLayer.ts | 2 +- 8 files changed, 75 insertions(+), 74 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 625a2497e7..32548eeb4d 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -227,7 +227,7 @@ export class Buffer implements IBuffer { return [-1, -1]; } for (let i = 0; i < line.length; ++i) { - stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length; + stringIndex -= line.getString(i).length; if (stringIndex < 0) { return [lineIndex, i]; } diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 3499c87d68..a2b9045b64 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -8,7 +8,6 @@ import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, import { MouseZone } from './ui/MouseZoneManager'; import { EventEmitter } from './common/EventEmitter'; import { getStringCellWidth } from './CharWidth'; -import { CellData } from './BufferLine'; /** * The Linkifier applies links to rows shortly after they have been refreshed. @@ -34,7 +33,6 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _rowsTimeoutId: number; private _nextLinkMatcherId = 0; private _rowsToLinkify: { start: number, end: number }; - private _cell: CellData = new CellData(); constructor( protected _terminal: ITerminal @@ -233,10 +231,10 @@ export class Linkifier extends EventEmitter implements ILinkifier { } const line = this._terminal.buffer.lines.get(bufferIndex[0]); - line.loadCell(bufferIndex[1], this._cell); + const attr = line.getFG(bufferIndex[1]); let fg: number | undefined; - if (this._cell.fg) { - fg = (this._cell.fg >> 9) & 0x1ff; + if (attr) { + fg = (attr >> 9) & 0x1ff; } if (matcher.validationCallback) { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1aea1cb53a..d615dda057 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,15 +3,15 @@ * @license MIT */ -import { ITerminal, ISelectionManager, IBuffer, CharData, IBufferLine } from './Types'; +import { ITerminal, ISelectionManager, IBuffer, IBufferLine } from './Types'; import { XtermListener } from './common/Types'; import { MouseHelper } from './ui/MouseHelper'; import * as Browser from './core/Platform'; import { CharMeasure } from './ui/CharMeasure'; import { EventEmitter } from './common/EventEmitter'; import { SelectionModel } from './SelectionModel'; -import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer'; import { AltClickHandler } from './handlers/AltClickHandler'; +import { CellData } from './BufferLine'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -103,6 +103,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _mouseMoveListener: EventListener; private _mouseUpListener: EventListener; private _trimListener: XtermListener; + private _cell: CellData = new CellData(); private _mouseDownTimeStamp: number; @@ -506,8 +507,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If the mouse is over the second half of a wide character, adjust the // selection to cover the whole character - const char = line.get(this._model.selectionStart[0]); - if (char[CHAR_DATA_WIDTH_INDEX] === 0) { + if (line.hasWidth(this._model.selectionStart[0]) === 0) { this._model.selectionStart[0]++; } } @@ -596,8 +596,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // selection. Note that selections at the very end of the line will never // have a character. if (this._model.selectionEnd[1] < this._buffer.lines.length) { - const char = this._buffer.lines.get(this._model.selectionEnd[1]).get(this._model.selectionEnd[0]); - if (char && char[CHAR_DATA_WIDTH_INDEX] === 0) { + if (this._buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) { this._model.selectionEnd[0]++; } } @@ -670,16 +669,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number { let charIndex = coords[0]; for (let i = 0; coords[0] >= i; i++) { - const char = bufferLine.get(i); - if (char[CHAR_DATA_WIDTH_INDEX] === 0) { + const length = bufferLine.loadCell(i, this._cell).chars.length; + if (this._cell.width === 0) { // Wide characters aren't included in the line string so decrement the // index so the index is back on the wide character. charIndex--; - } else if (char[CHAR_DATA_CHAR_INDEX].length > 1 && coords[0] !== i) { + } else if (length > 1 && coords[0] !== i) { // Emojis take up multiple characters, so adjust accordingly. For these // we don't want ot include the character at the column as we're // returning the start index in the string, not the end index. - charIndex += char[CHAR_DATA_CHAR_INDEX].length - 1; + charIndex += length - 1; } } return charIndex; @@ -739,48 +738,51 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Consider the initial position, skip it and increment the wide char // variable - if (bufferLine.get(startCol)[CHAR_DATA_WIDTH_INDEX] === 0) { + if (bufferLine.getWidth(startCol) === 0) { leftWideCharCount++; startCol--; } - if (bufferLine.get(endCol)[CHAR_DATA_WIDTH_INDEX] === 2) { + if (bufferLine.getWidth(endCol) === 2) { rightWideCharCount++; endCol++; } // Adjust the end index for characters whose length are > 1 (emojis) - if (bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length > 1) { - rightLongCharOffset += bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length - 1; - endIndex += bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length - 1; + const length = bufferLine.getString(endCol).length; + if (length > 1) { + rightLongCharOffset += length - 1; + endIndex += length - 1; } // Expand the string in both directions until a space is hit - while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.get(startCol - 1))) { - const char = bufferLine.get(startCol - 1); - if (char[CHAR_DATA_WIDTH_INDEX] === 0) { + while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._cell))) { + bufferLine.loadCell(startCol - 1, this._cell); + const length = this._cell.chars.length; + if (this._cell.width === 0) { // If the next character is a wide char, record it and skip the column leftWideCharCount++; startCol--; - } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) { + } else if (length > 1) { // If the next character's string is longer than 1 char (eg. emoji), // adjust the index - leftLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1; - startIndex -= char[CHAR_DATA_CHAR_INDEX].length - 1; + leftLongCharOffset += length - 1; + startIndex -= length - 1; } startIndex--; startCol--; } - while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.get(endCol + 1))) { - const char = bufferLine.get(endCol + 1); - if (char[CHAR_DATA_WIDTH_INDEX] === 2) { + while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._cell))) { + bufferLine.loadCell(endCol + 1, this._cell); + const length = this._cell.chars.length; + if (this._cell.width === 2) { // If the next character is a wide char, record it and skip the column rightWideCharCount++; endCol++; - } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) { + } else if (length > 1) { // If the next character's string is longer than 1 char (eg. emoji), // adjust the index - rightLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1; - endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1; + rightLongCharOffset += length - 1; + endIndex += length - 1; } endIndex++; endCol++; @@ -814,9 +816,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Recurse upwards if the line is wrapped and the word wraps to the above line if (followWrappedLinesAbove) { - if (start === 0 && bufferLine.get(0)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + if (start === 0 && bufferLine.getCodePoint(0) !== 32 /*' '*/) { const previousBufferLine = this._buffer.lines.get(coords[1] - 1); - if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.get(this._terminal.cols - 1)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) { const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false); if (previousLineWordPosition) { const offset = this._terminal.cols - previousLineWordPosition.start; @@ -829,9 +831,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Recurse downwards if the line is wrapped and the word wraps to the next line if (followWrappedLinesBelow) { - if (start + length === this._terminal.cols && bufferLine.get(this._terminal.cols - 1)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + if (start + length === this._terminal.cols && bufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) { const nextBufferLine = this._buffer.lines.get(coords[1] + 1); - if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.get(0)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /*' '*/) { const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true); if (nextLineWordPosition) { length += nextLineWordPosition.length; @@ -894,13 +896,13 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * word logic. * @param char The character to check. */ - private _isCharWordSeparator(charData: CharData): boolean { + private _isCharWordSeparator(cell: CellData): boolean { // Zero width characters are never separators as they are always to the // right of wide characters - if (charData[CHAR_DATA_WIDTH_INDEX] === 0) { + if (cell.width === 0) { return false; } - return WORD_SEPARATORS.indexOf(charData[CHAR_DATA_CHAR_INDEX]) >= 0; + return WORD_SEPARATORS.indexOf(cell.chars) >= 0; } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index bc97de29ff..a467d6dd74 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -25,7 +25,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions import { IMouseZoneManager } from './ui/Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; -import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer'; +import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './common/EventEmitter'; import { Viewport } from './Viewport'; @@ -1175,7 +1175,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public scroll(isWrapped: boolean = false): void { let newLine: IBufferLine; newLine = this._blankLine; - if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) { + if (!newLine || newLine.length !== this.cols || newLine.getFG(0) !== this.eraseAttr()) { newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); this._blankLine = newLine; } diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 3e0b864307..f84fbe6bcc 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -4,12 +4,12 @@ */ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; -import { CharData, ITerminal } from '../Types'; +import { ITerminal } from '../Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; -import { CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { is256Color } from './atlas/CharAtlasUtils'; +import { CellData } from '../BufferLine'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -229,17 +229,17 @@ export abstract class BaseRenderLayer implements IRenderLayer { * ensure that it fits with the cell, including the cell to the right if it's * a wide character. This uses the existing fillStyle on the context. * @param terminal The terminal. - * @param charData The char data for the character to draw. + * @param cell The cell data for the character to draw. * @param x The column to draw at. * @param y The row to draw at. * @param color The color of the character. */ - protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void { + protected fillCharTrueColor(terminal: ITerminal, cell: CellData, x: number, y: number): void { this._ctx.font = this._getFont(terminal, false, false); this._ctx.textBaseline = 'middle'; this._clipRow(terminal, y); this._ctx.fillText( - charData[CHAR_DATA_CHAR_INDEX], + cell.chars, x * this._scaledCellWidth + this._scaledCharLeft, (y + 0.5) * this._scaledCellHeight + this._scaledCharTop); } diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index dc9e95dd68..4cad7c72c9 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,11 +1,12 @@ -import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { ITerminal, IBufferLine } from '../Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; +import { CellData } from '../BufferLine'; export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { private _characterJoiners: ICharacterJoiner[] = []; private _nextCharacterJoinerId: number = 0; + private _cell: CellData = new CellData(); constructor(private _terminal: ITerminal) { } @@ -51,13 +52,13 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { let rangeStartColumn = 0; let currentStringIndex = 0; let rangeStartStringIndex = 0; - let rangeAttr = line.get(0)[CHAR_DATA_ATTR_INDEX] >> 9; + let rangeAttr = line.getFG(0) >> 9; for (let x = 0; x < this._terminal.cols; x++) { - const charData = line.get(x); - const chars = charData[CHAR_DATA_CHAR_INDEX]; - const width = charData[CHAR_DATA_WIDTH_INDEX]; - const attr = charData[CHAR_DATA_ATTR_INDEX] >> 9; + line.loadCell(x, this._cell); + const chars = this._cell.chars; + const width = this._cell.width; + const attr = this._cell.fg >> 9; if (width === 0) { // If this character is of width 0, skip it. @@ -152,9 +153,8 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { } for (let x = startCol; x < this._terminal.cols; x++) { - const charData = line.get(x); - const width = charData[CHAR_DATA_WIDTH_INDEX]; - const length = charData[CHAR_DATA_CHAR_INDEX].length; + const width = line.getWidth(x); + const length = line.getString(x).length; // We skip zero-width characters when creating the string to join the text // so we do the same here diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 08a1473910..18ba7ada5c 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { CharData, ITerminal } from '../Types'; +import { ITerminal, ICellData } from '../Types'; +import { CellData } from '../BufferLine'; interface ICursorState { x: number; @@ -23,8 +23,9 @@ const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer { private _state: ICursorState; - private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; + private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, cell: ICellData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; + private _cell: ICellData = new CellData(); constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { super(container, 'cursor', zIndex, true, colors); @@ -127,8 +128,8 @@ export class CursorRenderLayer extends BaseRenderLayer { return; } - const charData = terminal.buffer.lines.get(cursorY).get(terminal.buffer.x); - if (!charData) { + terminal.buffer.lines.get(cursorY).loadCell(terminal.buffer.x, this._cell); + if (this._cell.content === undefined) { return; } @@ -136,13 +137,13 @@ export class CursorRenderLayer extends BaseRenderLayer { this._clearCursor(); this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); + this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell); this._ctx.restore(); this._state.x = terminal.buffer.x; this._state.y = viewportRelativeCursorY; this._state.isFocused = false; this._state.style = terminal.options.cursorStyle; - this._state.width = charData[CHAR_DATA_WIDTH_INDEX]; + this._state.width = this._cell.width; return; } @@ -158,21 +159,21 @@ export class CursorRenderLayer extends BaseRenderLayer { this._state.y === viewportRelativeCursorY && this._state.isFocused === terminal.isFocused && this._state.style === terminal.options.cursorStyle && - this._state.width === charData[CHAR_DATA_WIDTH_INDEX]) { + this._state.width === this._cell.width) { return; } this._clearCursor(); } this._ctx.save(); - this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); + this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell); this._ctx.restore(); this._state.x = terminal.buffer.x; this._state.y = viewportRelativeCursorY; this._state.isFocused = false; this._state.style = terminal.options.cursorStyle; - this._state.width = charData[CHAR_DATA_WIDTH_INDEX]; + this._state.width = this._cell.width; } private _clearCursor(): void { @@ -188,33 +189,33 @@ export class CursorRenderLayer extends BaseRenderLayer { } } - private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + private _renderBarCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; this.fillLeftLineAtCell(x, y); this._ctx.restore(); } - private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + private _renderBlockCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); + this.fillCells(x, y, cell.width, 1); this._ctx.fillStyle = this._colors.cursorAccent.css; - this.fillCharTrueColor(terminal, charData, x, y); + this.fillCharTrueColor(terminal, cell, x, y); this._ctx.restore(); } - private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; this.fillBottomLineAtCells(x, y); this._ctx.restore(); } - private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + private _renderBlurCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.strokeStyle = this._colors.cursor.css; - this.strokeRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); + this.strokeRectAtCell(x, y, cell.width, 1); this._ctx.restore(); } } diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 815eef17ce..bf7c5616d5 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { CharData, ITerminal } from '../Types'; import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types'; From 88a037b3092f4d05862848ba0ee9ba3fbaed9502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 14:34:29 +0100 Subject: [PATCH 13/30] change insertCells to new interface --- src/BufferLine.test.ts | 4 ++-- src/BufferLine.ts | 8 +++----- src/InputHandler.ts | 10 ++++++++-- src/Types.ts | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index fbf8b0517b..4ccfe96b87 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import * as chai from 'chai'; -import { BufferLine } from './BufferLine'; +import { BufferLine, CellData } from './BufferLine'; import { CharData, IBufferLine } from './Types'; import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer'; @@ -41,7 +41,7 @@ describe('BufferLine', function(): void { line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); - line.insertCells(1, 3, [4, 'd', 0, 'd'.charCodeAt(0)]); + line.insertCells(1, 3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); chai.expect(line.toArray()).eql([ [1, 'a', 0, 'a'.charCodeAt(0)], [4, 'd', 0, 'd'.charCodeAt(0)], diff --git a/src/BufferLine.ts b/src/BufferLine.ts index b3a946720e..64cb1e1921 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -279,20 +279,18 @@ export class BufferLine implements IBufferLine { } } - public insertCells(pos: number, n: number, fillCharData: CharData): void { + public insertCells(pos: number, n: number, fillCellData: ICellData): void { pos %= this.length; if (n < this.length - pos) { for (let i = this.length - pos - n - 1; i >= 0; --i) { this.setCell(pos + n + i, this.loadCell(pos + i, this._cell)); } - this._cell.setFromCharData(fillCharData); for (let i = 0; i < n; ++i) { - this.setCell(pos + i, this._cell); + this.setCell(pos + i, fillCellData); } } else { - this._cell.setFromCharData(fillCharData); for (let i = pos; i < this.length; ++i) { - this.setCell(i, this._cell); + this.setCell(i, fillCellData); } } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ad3713e9c2..72a86e4284 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -396,7 +396,10 @@ export class InputHandler extends Disposable implements IInputHandler { // insert mode: move characters to right if (insertMode) { // right shift cells according to the width - bufferRow.insertCells(buffer.x, chWidth, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + this._cell.fg = curAttr; + this._cell.bg = 0; + this._cell.content = 0; + bufferRow.insertCells(buffer.x, chWidth, this._cell); // test last cell - since the last cell has only room for // a halfwidth char any fullwidth shifted there is lost // and will be set to eraseChar @@ -516,10 +519,13 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ public insertChars(params: number[]): void { + this._cell.content = 0; + this._cell.fg = this._terminal.eraseAttr(); + this._cell.bg = 0; this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( this._terminal.buffer.x, params[0] || 1, - [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] + this._cell ); this._terminal.updateRange(this._terminal.buffer.y); } diff --git a/src/Types.ts b/src/Types.ts index c1a7788a43..216139b7a0 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -536,7 +536,7 @@ export interface IBufferLine { setCell(index: number, cell: ICellData): void; setDataFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; addCharToCell(index: number, codePoint: number): void; - insertCells(pos: number, n: number, ch: CharData): void; + insertCells(pos: number, n: number, ch: ICellData): void; deleteCells(pos: number, n: number, fill: CharData): void; replaceCells(start: number, end: number, fill: CharData): void; resize(cols: number, fill: CharData, shrink?: boolean): void; From 523ff6e5fc2e0f981e661ede0551a499983f628c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 14:54:54 +0100 Subject: [PATCH 14/30] add null and whitespace placeholder cells to buffer --- src/Buffer.ts | 18 ++++++++++++++++-- src/InputHandler.ts | 5 +---- src/Types.ts | 2 ++ src/ui/TestUtils.test.ts | 8 +++++++- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 32548eeb4d..463b245172 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,10 +4,10 @@ */ import { CircularList } from './common/CircularList'; -import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; +import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData } from './Types'; import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; -import { BufferLine } from './BufferLine'; +import { BufferLine, CellData } from './BufferLine'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); @@ -45,6 +45,8 @@ export class Buffer implements IBuffer { public savedX: number; public savedCurAttr: number; public markers: Marker[] = []; + private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]); /** * Create a new Buffer. @@ -59,6 +61,18 @@ export class Buffer implements IBuffer { this.clear(); } + public getNullCell(fg: number = 0, bg: number = 0): ICellData { + this._nullCell.fg = fg; + this._nullCell.bg = bg; + return this._nullCell; + } + + public getWhitespaceCell(fg: number = 0, bg: number = 0): ICellData { + this._whitespaceCell.fg = fg; + this._whitespaceCell.bg = bg; + return this._whitespaceCell; + } + public getBlankLine(attr: number, isWrapped?: boolean): IBufferLine { const fillCharData: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; return new BufferLine(this._terminal.cols, fillCharData, isWrapped); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 72a86e4284..c8f9e7f24c 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -519,13 +519,10 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ public insertChars(params: number[]): void { - this._cell.content = 0; - this._cell.fg = this._terminal.eraseAttr(); - this._cell.bg = 0; this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( this._terminal.buffer.x, params[0] || 1, - this._cell + this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) ); this._terminal.updateRange(this._terminal.buffer.y); } diff --git a/src/Types.ts b/src/Types.ts index 216139b7a0..9a4401b620 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -298,6 +298,8 @@ export interface IBuffer { getBlankLine(attr: number, isWrapped?: boolean): IBufferLine; stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[]; iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator; + getNullCell(fg?: number, bg?: number): ICellData; + getWhitespaceCell(fg?: number, bg?: number): ICellData; } export interface IBufferSet extends IEventEmitter { diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index e6e4aaa32f..9d525fbf92 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator } from '../Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData } from '../Types'; import { ICircularList, XtermListener } from '../common/Types'; import { Buffer } from '../Buffer'; import * as Browser from '../core/Platform'; @@ -334,6 +334,12 @@ export class MockBuffer implements IBuffer { iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator { return Buffer.prototype.iterator.apply(this, arguments); } + getNullCell(fg: number = 0, bg: number = 0): ICellData { + throw new Error('Method not implemented.'); + } + getWhitespaceCell(fg: number = 0, bg: number = 0): ICellData { + throw new Error('Method not implemented.'); + } } export class MockRenderer implements IRenderer { From 4bf7f3eb894d4fe98e674370e783ce17f5c07267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 14:58:30 +0100 Subject: [PATCH 15/30] change deleteCells to new interface --- src/BufferLine.test.ts | 2 +- src/BufferLine.ts | 8 +++----- src/InputHandler.ts | 2 +- src/Types.ts | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 4ccfe96b87..68384f35d6 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -55,7 +55,7 @@ describe('BufferLine', function(): void { line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); - line.deleteCells(1, 2, [6, 'f', 0, 'f'.charCodeAt(0)]); + line.deleteCells(1, 2, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)])); chai.expect(line.toArray()).eql([ [1, 'a', 0, 'a'.charCodeAt(0)], [4, 'd', 0, 'd'.charCodeAt(0)], diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 64cb1e1921..fb1d40a9b7 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -295,20 +295,18 @@ export class BufferLine implements IBufferLine { } } - public deleteCells(pos: number, n: number, fillCharData: CharData): void { + public deleteCells(pos: number, n: number, fillCellData: ICellData): void { pos %= this.length; if (n < this.length - pos) { for (let i = 0; i < this.length - pos - n; ++i) { this.setCell(pos + i, this.loadCell(pos + n + i, this._cell)); } - this._cell.setFromCharData(fillCharData); for (let i = this.length - n; i < this.length; ++i) { - this.setCell(i, this._cell); + this.setCell(i, fillCellData); } } else { - this._cell.setFromCharData(fillCharData); for (let i = pos; i < this.length; ++i) { - this.setCell(i, this._cell); + this.setCell(i, fillCellData); } } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c8f9e7f24c..2776051d06 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -865,7 +865,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells( this._terminal.buffer.x, params[0] || 1, - [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] + this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) ); this._terminal.updateRange(this._terminal.buffer.y); } diff --git a/src/Types.ts b/src/Types.ts index 9a4401b620..e991ac4287 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -539,7 +539,7 @@ export interface IBufferLine { setDataFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; addCharToCell(index: number, codePoint: number): void; insertCells(pos: number, n: number, ch: ICellData): void; - deleteCells(pos: number, n: number, fill: CharData): void; + deleteCells(pos: number, n: number, fill: ICellData): void; replaceCells(start: number, end: number, fill: CharData): void; resize(cols: number, fill: CharData, shrink?: boolean): void; fill(fillCharData: CharData): void; From fe6919b52a2d9cd90a6ab59eead49d63d6af640d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 15:05:52 +0100 Subject: [PATCH 16/30] change replaceCells to new interface --- src/BufferLine.test.ts | 2 +- src/BufferLine.ts | 5 ++--- src/InputHandler.ts | 9 +++++---- src/Types.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 68384f35d6..c2329113a7 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -71,7 +71,7 @@ describe('BufferLine', function(): void { line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); - line.replaceCells(2, 4, [6, 'f', 0, 'f'.charCodeAt(0)]); + line.replaceCells(2, 4, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)])); chai.expect(line.toArray()).eql([ [1, 'a', 0, 'a'.charCodeAt(0)], [2, 'b', 0, 'b'.charCodeAt(0)], diff --git a/src/BufferLine.ts b/src/BufferLine.ts index fb1d40a9b7..d9ddae7006 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -311,10 +311,9 @@ export class BufferLine implements IBufferLine { } } - public replaceCells(start: number, end: number, fillCharData: CharData): void { - this._cell.setFromCharData(fillCharData); + public replaceCells(start: number, end: number, fillCellData: ICellData): void { while (start < end && start < this.length) { - this.setCell(start++, this._cell); + this.setCell(start++, fillCellData); } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2776051d06..3758360255 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types'; import { C0, C1 } from './common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; -import { DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; +import { DEFAULT_ATTR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; import { FLAGS } from './renderer/Types'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; @@ -696,7 +696,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.replaceCells( start, end, - [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] + this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) ); if (clearWrap) { line.isWrapped = false; @@ -916,7 +916,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells( this._terminal.buffer.x, this._terminal.buffer.x + (params[0] || 1), - [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] + this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) ); } @@ -972,9 +972,10 @@ export class InputHandler extends Disposable implements IInputHandler { // make buffer local for faster access const buffer = this._terminal.buffer; const line = buffer.lines.get(buffer.ybase + buffer.y); + line.loadCell(buffer.x - 1, this._cell); line.replaceCells(buffer.x, buffer.x + (params[0] || 1), - line.get(buffer.x - 1) || [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] + (this._cell.content !== undefined) ? this._cell : buffer.getNullCell(DEFAULT_ATTR) ); // FIXME: no updateRange here? } diff --git a/src/Types.ts b/src/Types.ts index e991ac4287..83f4e492ad 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -540,7 +540,7 @@ export interface IBufferLine { addCharToCell(index: number, codePoint: number): void; insertCells(pos: number, n: number, ch: ICellData): void; deleteCells(pos: number, n: number, fill: ICellData): void; - replaceCells(start: number, end: number, fill: CharData): void; + replaceCells(start: number, end: number, fill: ICellData): void; resize(cols: number, fill: CharData, shrink?: boolean): void; fill(fillCharData: CharData): void; copyFrom(line: IBufferLine): void; From 56dec2849fe56af428436207f238819e0c47240f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 15:21:55 +0100 Subject: [PATCH 17/30] change resize, fill to new interface, remove _cell on buffer line --- src/Buffer.ts | 4 +-- src/BufferLine.test.ts | 38 ++++++++++---------- src/BufferLine.ts | 21 ++++++----- src/Types.ts | 4 +-- src/renderer/CharacterJoinerRegistry.test.ts | 16 ++++----- 5 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 463b245172..edba479c2d 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -150,9 +150,9 @@ export class Buffer implements IBuffer { if (this.lines.length > 0) { // Deal with columns increasing (we don't do anything when columns reduce) if (this._terminal.cols < newCols) { - const ch: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // does xterm use the default attr? + const cell = this.getNullCell(DEFAULT_ATTR); // does xterm use the default attr? for (let i = 0; i < this.lines.length; i++) { - this.lines.get(i).resize(newCols, ch); + this.lines.get(i).resize(newCols, cell); } } diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index c2329113a7..402b1a022b 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -87,7 +87,7 @@ describe('BufferLine', function(): void { line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); - line.fill([123, 'z', 0, 'z'.charCodeAt(0)]); + line.fill(CellData.fromCharData([123, 'z', 0, 'z'.charCodeAt(0)])); chai.expect(line.toArray()).eql([ [123, 'z', 0, 'z'.charCodeAt(0)], [123, 'z', 0, 'z'.charCodeAt(0)], @@ -136,67 +136,67 @@ describe('BufferLine', function(): void { describe('resize', function(): void { it('enlarge(false)', function(): void { const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)]); + line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('enlarge(true)', function(): void { const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], true); + line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(true) - should apply new size', function(): void { const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], true); + line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) - should not apply new size', function(): void { const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) + shrink(false) - should not apply new size', function(): void { const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); + line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) + enlarge(false) to smaller than before', function(): void { const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(15, [1, 'a', 0, 'a'.charCodeAt(0)]); + line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); + line.resize(15, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) + enlarge(false) to bigger than before', function(): void { const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(25, [1, 'a', 0, 'a'.charCodeAt(0)]); + line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); + line.resize(25, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); chai.expect(line.toArray()).eql(Array(25).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) + resize shrink=true should enforce shrinking', function(): void { const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], true); + line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); + line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('enlarge from 0 length', function(): void { const line = new TestBufferLine(0, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink to 0 length', function(): void { const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)], true); + line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(0).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) to 0 and enlarge to different sizes', function(): void { const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); - line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); - line.resize(7, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(7, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); - line.resize(7, [1, 'a', 0, 'a'.charCodeAt(0)], true); + line.resize(7, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(7).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index d9ddae7006..086fe15b7f 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -137,15 +137,14 @@ export class CellData implements ICellData { export class BufferLine implements IBufferLine { protected _data: Uint32Array | null = null; protected _combined: {[index: number]: string} = {}; - protected _cell: CellData = new CellData(); public length: number; constructor(cols: number, fillCharData?: CharData, public isWrapped: boolean = false) { if (cols) { this._data = new Uint32Array(cols * CELL_SIZE); - this._cell.setFromCharData(fillCharData || [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + const cell = CellData.fromCharData(fillCharData || [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); for (let i = 0; i < cols; ++i) { - this.setCell(i, this._cell); + this.setCell(i, cell); } } this.length = cols; @@ -282,8 +281,9 @@ export class BufferLine implements IBufferLine { public insertCells(pos: number, n: number, fillCellData: ICellData): void { pos %= this.length; if (n < this.length - pos) { + const cell = new CellData(); for (let i = this.length - pos - n - 1; i >= 0; --i) { - this.setCell(pos + n + i, this.loadCell(pos + i, this._cell)); + this.setCell(pos + n + i, this.loadCell(pos + i, cell)); } for (let i = 0; i < n; ++i) { this.setCell(pos + i, fillCellData); @@ -298,8 +298,9 @@ export class BufferLine implements IBufferLine { public deleteCells(pos: number, n: number, fillCellData: ICellData): void { pos %= this.length; if (n < this.length - pos) { + const cell = new CellData(); for (let i = 0; i < this.length - pos - n; ++i) { - this.setCell(pos + i, this.loadCell(pos + n + i, this._cell)); + this.setCell(pos + i, this.loadCell(pos + n + i, cell)); } for (let i = this.length - n; i < this.length; ++i) { this.setCell(i, fillCellData); @@ -317,7 +318,7 @@ export class BufferLine implements IBufferLine { } } - public resize(cols: number, fillCharData: CharData, shrink: boolean = false): void { + public resize(cols: number, fillCellData: ICellData, shrink: boolean = false): void { if (cols === this.length || (!shrink && cols < this.length)) { return; } @@ -331,9 +332,8 @@ export class BufferLine implements IBufferLine { } } this._data = data; - this._cell.setFromCharData(fillCharData); for (let i = this.length; i < cols; ++i) { - this.setCell(i, this._cell); + this.setCell(i, fillCellData); } } else if (shrink) { if (cols) { @@ -348,11 +348,10 @@ export class BufferLine implements IBufferLine { } /** fill a line with fillCharData */ - public fill(fillCharData: CharData): void { + public fill(fillCellData: ICellData): void { this._combined = {}; - this._cell.setFromCharData(fillCharData); for (let i = 0; i < this.length; ++i) { - this.setCell(i, this._cell); + this.setCell(i, fillCellData); } } diff --git a/src/Types.ts b/src/Types.ts index 83f4e492ad..644a9b49cd 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -541,8 +541,8 @@ export interface IBufferLine { insertCells(pos: number, n: number, ch: ICellData): void; deleteCells(pos: number, n: number, fill: ICellData): void; replaceCells(start: number, end: number, fill: ICellData): void; - resize(cols: number, fill: CharData, shrink?: boolean): void; - fill(fillCharData: CharData): void; + resize(cols: number, fill: ICellData, shrink?: boolean): void; + fill(fillCellData: ICellData): void; copyFrom(line: IBufferLine): void; clone(): IBufferLine; getTrimmedLength(): number; diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index 0c29566ac3..2f9f45bef7 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -5,7 +5,7 @@ import { CircularList } from '../common/CircularList'; import { ICharacterJoinerRegistry } from './Types'; import { CharacterJoinerRegistry } from './CharacterJoinerRegistry'; -import { BufferLine } from '../BufferLine'; +import { BufferLine, CellData } from '../BufferLine'; import { IBufferLine } from '../Types'; describe('CharacterJoinerRegistry', () => { @@ -24,17 +24,17 @@ describe('CharacterJoinerRegistry', () => { lines.set(4, new BufferLine(0)); lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]])); const line6 = lineData([['wi']]); - line6.resize(line6.length + 1, [0, '¥', 2, '¥'.charCodeAt(0)]); - line6.resize(line6.length + 1, [0, '', 0, null]); + line6.resize(line6.length + 1, CellData.fromCharData([0, '¥', 2, '¥'.charCodeAt(0)])); + line6.resize(line6.length + 1, CellData.fromCharData([0, '', 0, null])); let sub = lineData([['deemo']]); let oldSize = line6.length; - line6.resize(oldSize + sub.length, [0, '', 0, 0]); + line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0])); for (let i = 0; i < sub.length; ++i) line6.set(i + oldSize, sub.get(i)); - line6.resize(line6.length + 1, [0, '\xf0\x9f\x98\x81', 1, 128513]); - line6.resize(line6.length + 1, [0, ' ', 1, ' '.charCodeAt(0)]); + line6.resize(line6.length + 1, CellData.fromCharData([0, '\xf0\x9f\x98\x81', 1, 128513])); + line6.resize(line6.length + 1, CellData.fromCharData([0, ' ', 1, ' '.charCodeAt(0)])); sub = lineData([['jiabc']]); oldSize = line6.length; - line6.resize(oldSize + sub.length, [0, '', 0, 0]); + line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0])); for (let i = 0; i < sub.length; ++i) line6.set(i + oldSize, sub.get(i)); lines.set(6, line6); @@ -273,7 +273,7 @@ function lineData(data: IPartialLineData[]): IBufferLine { const line = data[i][0]; const attr = (data[i][1] || 0); const offset = tline.length; - tline.resize(tline.length + line.split('').length, [0, '', 0, 0]); + tline.resize(tline.length + line.split('').length, CellData.fromCharData([0, '', 0, 0])); line.split('').map((char, idx) => tline.set(idx + offset, [attr, char, 1, char.charCodeAt(0)])); } return tline; From 60a591e8c8a445ded42746e709bbfb5543ee5e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 15:34:22 +0100 Subject: [PATCH 18/30] use getNullChar in InputHandler.print --- src/InputHandler.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3758360255..c41b4165c9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -396,13 +396,10 @@ export class InputHandler extends Disposable implements IInputHandler { // insert mode: move characters to right if (insertMode) { // right shift cells according to the width - this._cell.fg = curAttr; - this._cell.bg = 0; - this._cell.content = 0; - bufferRow.insertCells(buffer.x, chWidth, this._cell); + bufferRow.insertCells(buffer.x, chWidth, buffer.getNullCell(curAttr)); // test last cell - since the last cell has only room for // a halfwidth char any fullwidth shifted there is lost - // and will be set to eraseChar + // and will be set to empty cell if (bufferRow.loadCell(cols - 1, this._cell).width === 2) { bufferRow.setDataFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr, 0); } @@ -416,6 +413,7 @@ export class InputHandler extends Disposable implements IInputHandler { // we already made sure above, that buffer.x + chWidth will not overflow right if (chWidth > 0) { while (--chWidth) { + // other than a regular empty cell a cell following a wide char has no width bufferRow.setDataFromCodePoint(buffer.x++, 0, 0, curAttr, 0); } } From 3e456971b14e8cc27708ddd8ef31b67db2427812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 15:47:50 +0100 Subject: [PATCH 19/30] change buffer line ctor to new interface --- src/Buffer.ts | 8 +++--- src/BufferLine.test.ts | 56 +++++++++++++++++++++--------------------- src/BufferLine.ts | 4 +-- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index edba479c2d..6295d17571 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,7 +4,7 @@ */ import { CircularList } from './common/CircularList'; -import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData } from './Types'; +import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData } from './Types'; import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; import { BufferLine, CellData } from './BufferLine'; @@ -74,8 +74,7 @@ export class Buffer implements IBuffer { } public getBlankLine(attr: number, isWrapped?: boolean): IBufferLine { - const fillCharData: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - return new BufferLine(this._terminal.cols, fillCharData, isWrapped); + return new BufferLine(this._terminal.cols, this.getNullCell(attr), isWrapped); } public get hasScrollback(): boolean { @@ -173,8 +172,7 @@ export class Buffer implements IBuffer { } else { // Add a blank line if there is no buffer left at the top to scroll to, or if there // are blank lines after the cursor - const fillCharData: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - this.lines.push(new BufferLine(newCols, fillCharData)); + this.lines.push(new BufferLine(newCols, this.getNullCell(DEFAULT_ATTR))); } } } diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 402b1a022b..210ba85180 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -31,7 +31,7 @@ describe('BufferLine', function(): void { chai.expect(line.length).equals(10); chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); chai.expect(line.isWrapped).equals(true); - line = new TestBufferLine(10, [123, 'a', 456, 'a'.charCodeAt(0)], true); + line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true); chai.expect(line.length).equals(10); chai.expect(line.get(0)).eql([123, 'a', 456, 'a'.charCodeAt(0)]); chai.expect(line.isWrapped).equals(true); @@ -115,7 +115,7 @@ describe('BufferLine', function(): void { line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); - const line2 = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], true); + const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); line2.copyFrom(line); chai.expect(line2.toArray()).eql(line.toArray()); chai.expect(line2.length).equals(line.length); @@ -125,9 +125,9 @@ describe('BufferLine', function(): void { // CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print // --> set code to the last charCodeAt value of the string // Note: needs to be fixed once the string pointer is in place - const line = new TestBufferLine(2, [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]); + const line = new TestBufferLine(2, CellData.fromCharData([1, 'e\u0301', 0, '\u0301'.charCodeAt(0)])); chai.expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]); - const line2 = new TestBufferLine(5, [1, 'a', 0, '\u0301'.charCodeAt(0)], true); + const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, '\u0301'.charCodeAt(0)]), true); line2.copyFrom(line); chai.expect(line2.toArray()).eql(line.toArray()); const line3 = line.clone(); @@ -135,61 +135,61 @@ describe('BufferLine', function(): void { }); describe('resize', function(): void { it('enlarge(false)', function(): void { - const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('enlarge(true)', function(): void { - const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(true) - should apply new size', function(): void { - const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) - should not apply new size', function(): void { - const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) + shrink(false) - should not apply new size', function(): void { - const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(20, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) + enlarge(false) to smaller than before', function(): void { - const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(20, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(15, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) + enlarge(false) to bigger than before', function(): void { - const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(20, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(25, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); chai.expect(line.toArray()).eql(Array(25).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) + resize shrink=true should enforce shrinking', function(): void { - const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(20, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('enlarge from 0 length', function(): void { - const line = new TestBufferLine(0, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink to 0 length', function(): void { - const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); chai.expect(line.toArray()).eql(Array(0).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(false) to 0 and enlarge to different sizes', function(): void { - const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); @@ -202,29 +202,29 @@ describe('BufferLine', function(): void { }); describe('getTrimLength', function(): void { it('empty line', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); chai.expect(line.getTrimmedLength()).equal(0); }); it('ASCII', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); chai.expect(line.getTrimmedLength()).equal(3); }); it('surrogate', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); chai.expect(line.getTrimmedLength()).equal(3); }); it('combining', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); chai.expect(line.getTrimmedLength()).equal(3); }); it('fullwidth', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, '1', 2, '1'.charCodeAt(0)]); line.set(3, [0, '', 0, undefined]); @@ -233,12 +233,12 @@ describe('BufferLine', function(): void { }); describe('translateToString with and w\'o trimming', function(): void { it('empty line', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); chai.expect(line.translateToString(false)).equal(' '); chai.expect(line.translateToString(true)).equal(''); }); it('ASCII', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]); @@ -254,7 +254,7 @@ describe('BufferLine', function(): void { }); it('surrogate', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); line.set(4, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); @@ -269,7 +269,7 @@ describe('BufferLine', function(): void { chai.expect(line.translateToString(true, 0, 3)).equal('a 𝄞'); }); it('combining', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); line.set(4, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); @@ -284,7 +284,7 @@ describe('BufferLine', function(): void { chai.expect(line.translateToString(true, 0, 3)).equal('a e\u0301'); }); it('fullwidth', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, '1', 2, '1'.charCodeAt(0)]); line.set(3, [0, '', 0, undefined]); @@ -308,7 +308,7 @@ describe('BufferLine', function(): void { chai.expect(line.translateToString(true, 0, 2)).equal('a '); }); it('space at end', function(): void { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]); @@ -321,12 +321,12 @@ describe('BufferLine', function(): void { // sanity check - broken line with invalid out of bound null width cells // this can atm happen with deleting/inserting chars in inputhandler by "breaking" // fullwidth pairs --> needs to be fixed after settling BufferLine impl - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); chai.expect(line.translateToString(false)).equal(' '); chai.expect(line.translateToString(true)).equal(''); }); it('should work with endCol=0', () => { - const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE], false); + const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); chai.expect(line.translateToString(true, 0, 0)).equal(''); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 086fe15b7f..ccea8fe1fa 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -139,10 +139,10 @@ export class BufferLine implements IBufferLine { protected _combined: {[index: number]: string} = {}; public length: number; - constructor(cols: number, fillCharData?: CharData, public isWrapped: boolean = false) { + constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) { if (cols) { this._data = new Uint32Array(cols * CELL_SIZE); - const cell = CellData.fromCharData(fillCharData || [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + const cell = fillCellData || CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); for (let i = 0; i < cols; ++i) { this.setCell(i, cell); } From ece7192db74859aa146d42bc68912138e946159f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 16:15:24 +0100 Subject: [PATCH 20/30] remove set with CharData from codebase and deprecate method --- src/Buffer.test.ts | 40 +++--- src/BufferLine.test.ts | 124 +++++++++--------- src/BufferLine.ts | 4 + src/Linkifier.test.ts | 4 +- src/SelectionManager.test.ts | 8 +- src/Terminal.test.ts | 65 ++++----- src/renderer/CharacterJoinerRegistry.test.ts | 6 +- .../dom/DomRendererRowFactory.test.ts | 28 ++-- 8 files changed, 142 insertions(+), 137 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 0546dfe809..d1a1d5638b 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -8,7 +8,7 @@ import { ITerminal } from './Types'; import { Buffer, DEFAULT_ATTR, CHAR_DATA_CHAR_INDEX } from './Buffer'; import { CircularList } from './common/CircularList'; import { MockTerminal, TestTerminal } from './ui/TestUtils.test'; -import { BufferLine } from './BufferLine'; +import { BufferLine, CellData } from './BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -157,10 +157,10 @@ describe('Buffer', () => { buffer.fillViewportRows(); let chData = buffer.lines.get(5).get(0); chData[1] = 'a'; - buffer.lines.get(5).set(0, chData); + buffer.lines.get(5).setCell(0, CellData.fromCharData(chData)); chData = buffer.lines.get(INIT_ROWS - 1).get(0); chData[1] = 'b'; - buffer.lines.get(INIT_ROWS - 1).set(0, chData); + buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData(chData)); buffer.resize(INIT_COLS, INIT_ROWS - 5); assert.equal(buffer.lines.get(0).get(0)[1], 'a'); assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).get(0)[1], 'b'); @@ -278,10 +278,10 @@ describe('Buffer', () => { describe ('translateBufferLineToString', () => { it('should handle selecting a section of ascii text', () => { const line = new BufferLine(4); - line.set(0, [ null, 'a', 1, 'a'.charCodeAt(0)]); - line.set(1, [ null, 'b', 1, 'b'.charCodeAt(0)]); - line.set(2, [ null, 'c', 1, 'c'.charCodeAt(0)]); - line.set(3, [ null, 'd', 1, 'd'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(1, CellData.fromCharData([ null, 'b', 1, 'b'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([ null, 'c', 1, 'c'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([ null, 'd', 1, 'd'.charCodeAt(0)])); buffer.lines.set(0, line); const str = buffer.translateBufferLineToString(0, true, 0, 2); @@ -290,9 +290,9 @@ describe('Buffer', () => { it('should handle a cut-off double width character by including it', () => { const line = new BufferLine(3); - line.set(0, [ null, '語', 2, 35486 ]); - line.set(1, [ null, '', 0, null]); - line.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([ null, '語', 2, 35486 ])); + line.setCell(1, CellData.fromCharData([ null, '', 0, null])); + line.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); @@ -301,9 +301,9 @@ describe('Buffer', () => { it('should handle a zero width character in the middle of the string by not including it', () => { const line = new BufferLine(3); - line.set(0, [ null, '語', 2, '語'.charCodeAt(0) ]); - line.set(1, [ null, '', 0, null]); - line.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([ null, '語', 2, '語'.charCodeAt(0) ])); + line.setCell(1, CellData.fromCharData([ null, '', 0, null])); + line.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); buffer.lines.set(0, line); const str0 = buffer.translateBufferLineToString(0, true, 0, 1); @@ -318,8 +318,8 @@ describe('Buffer', () => { it('should handle single width emojis', () => { const line = new BufferLine(2); - line.set(0, [ null, '😁', 1, '😁'.charCodeAt(0) ]); - line.set(1, [ null, 'a', 1, 'a'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([ null, '😁', 1, '😁'.charCodeAt(0) ])); + line.setCell(1, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); @@ -331,8 +331,8 @@ describe('Buffer', () => { it('should handle double width emojis', () => { const line = new BufferLine(2); - line.set(0, [ null, '😁', 2, '😁'.charCodeAt(0) ]); - line.set(1, [ null, '', 0, null]); + line.setCell(0, CellData.fromCharData([ null, '😁', 2, '😁'.charCodeAt(0) ])); + line.setCell(1, CellData.fromCharData([ null, '', 0, null])); buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); @@ -342,9 +342,9 @@ describe('Buffer', () => { assert.equal(str2, '😁'); const line2 = new BufferLine(3); - line2.set(0, [ null, '😁', 2, '😁'.charCodeAt(0) ]); - line2.set(1, [ null, '', 0, null]); - line2.set(2, [ null, 'a', 1, 'a'.charCodeAt(0)]); + line2.setCell(0, CellData.fromCharData([ null, '😁', 2, '😁'.charCodeAt(0) ])); + line2.setCell(1, CellData.fromCharData([ null, '', 0, null])); + line2.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); buffer.lines.set(0, line2); const str3 = buffer.translateBufferLineToString(0, true, 0, 3); diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 210ba85180..95ec77c354 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -38,9 +38,9 @@ describe('BufferLine', function(): void { }); it('insertCells', function(): void { const line = new TestBufferLine(3); - line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); - line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); - line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); + line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); line.insertCells(1, 3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); chai.expect(line.toArray()).eql([ [1, 'a', 0, 'a'.charCodeAt(0)], @@ -50,11 +50,11 @@ describe('BufferLine', function(): void { }); it('deleteCells', function(): void { const line = new TestBufferLine(5); - line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); - line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); - line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); - line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); - line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); + line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); line.deleteCells(1, 2, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)])); chai.expect(line.toArray()).eql([ [1, 'a', 0, 'a'.charCodeAt(0)], @@ -66,11 +66,11 @@ describe('BufferLine', function(): void { }); it('replaceCells', function(): void { const line = new TestBufferLine(5); - line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); - line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); - line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); - line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); - line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); + line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); line.replaceCells(2, 4, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)])); chai.expect(line.toArray()).eql([ [1, 'a', 0, 'a'.charCodeAt(0)], @@ -82,11 +82,11 @@ describe('BufferLine', function(): void { }); it('fill', function(): void { const line = new TestBufferLine(5); - line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); - line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); - line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); - line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); - line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); + line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); line.fill(CellData.fromCharData([123, 'z', 0, 'z'.charCodeAt(0)])); chai.expect(line.toArray()).eql([ [123, 'z', 0, 'z'.charCodeAt(0)], @@ -98,11 +98,11 @@ describe('BufferLine', function(): void { }); it('clone', function(): void { const line = new TestBufferLine(5, null, true); - line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); - line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); - line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); - line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); - line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); + line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); const line2 = line.clone(); chai.expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray()); chai.expect(line2.length).equals(line.length); @@ -110,11 +110,11 @@ describe('BufferLine', function(): void { }); it('copyFrom', function(): void { const line = new TestBufferLine(5); - line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); - line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); - line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); - line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); - line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); + line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); line2.copyFrom(line); chai.expect(line2.toArray()).eql(line.toArray()); @@ -207,27 +207,27 @@ describe('BufferLine', function(): void { }); it('ASCII', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); chai.expect(line.getTrimmedLength()).equal(3); }); it('surrogate', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); chai.expect(line.getTrimmedLength()).equal(3); }); it('combining', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); chai.expect(line.getTrimmedLength()).equal(3); }); it('fullwidth', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, '1', 2, '1'.charCodeAt(0)]); - line.set(3, [0, '', 0, undefined]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([0, '', 0, undefined])); chai.expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth }); }); @@ -239,10 +239,10 @@ describe('BufferLine', function(): void { }); it('ASCII', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(5, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); chai.expect(line.translateToString(false)).equal('a a aa '); chai.expect(line.translateToString(true)).equal('a a aa'); chai.expect(line.translateToString(false, 0, 5)).equal('a a a'); @@ -255,10 +255,10 @@ describe('BufferLine', function(): void { }); it('surrogate', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); - line.set(4, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); - line.set(5, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); + line.setCell(5, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); chai.expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 '); chai.expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞'); chai.expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞'); @@ -270,10 +270,10 @@ describe('BufferLine', function(): void { }); it('combining', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); - line.set(4, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); - line.set(5, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); + line.setCell(5, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); chai.expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 '); chai.expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301'); chai.expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301'); @@ -285,13 +285,13 @@ describe('BufferLine', function(): void { }); it('fullwidth', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, '1', 2, '1'.charCodeAt(0)]); - line.set(3, [0, '', 0, undefined]); - line.set(5, [1, '1', 2, '1'.charCodeAt(0)]); - line.set(6, [0, '', 0, undefined]); - line.set(7, [1, '1', 2, '1'.charCodeAt(0)]); - line.set(8, [0, '', 0, undefined]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([0, '', 0, undefined])); + line.setCell(5, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); + line.setCell(6, CellData.fromCharData([0, '', 0, undefined])); + line.setCell(7, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); + line.setCell(8, CellData.fromCharData([0, '', 0, undefined])); chai.expect(line.translateToString(false)).equal('a 1 11 '); chai.expect(line.translateToString(true)).equal('a 1 11'); chai.expect(line.translateToString(false, 0, 7)).equal('a 1 1'); @@ -309,11 +309,11 @@ describe('BufferLine', function(): void { }); it('space at end', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(5, [1, 'a', 1, 'a'.charCodeAt(0)]); - line.set(6, [1, ' ', 1, ' '.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(6, CellData.fromCharData([1, ' ', 1, ' '.charCodeAt(0)])); chai.expect(line.translateToString(false)).equal('a a aa '); chai.expect(line.translateToString(true)).equal('a a aa '); }); @@ -327,7 +327,7 @@ describe('BufferLine', function(): void { }); it('should work with endCol=0', () => { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); - line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); chai.expect(line.translateToString(true, 0, 0)).equal(''); }); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index ccea8fe1fa..4a0744414c 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -165,6 +165,10 @@ export class BufferLine implements IBufferLine { ]; } + /** + * Set cell data from CharData. + * @deprecated + */ public set(index: number, value: CharData): void { this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX]; if (value[CHAR_DATA_CHAR_INDEX].length > 1) { diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 0ba1294a0e..d40e50698c 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -9,7 +9,7 @@ import { ILinkMatcher, ITerminal, IBufferLine } from './Types'; import { Linkifier } from './Linkifier'; import { MockBuffer, MockTerminal, TestTerminal } from './ui/TestUtils.test'; import { CircularList } from './common/CircularList'; -import { BufferLine } from './BufferLine'; +import { BufferLine, CellData } from './BufferLine'; class TestLinkifier extends Linkifier { constructor(terminal: ITerminal) { @@ -53,7 +53,7 @@ describe('Linkifier', () => { function stringToRow(text: string): IBufferLine { const result = new BufferLine(text.length); for (let i = 0; i < text.length; i++) { - result.set(i, [0, text.charAt(i), 1, text.charCodeAt(i)]); + result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)])); } return result; } diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 2f74ccdab0..425911686d 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,7 +10,7 @@ import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer, IBufferLine } from './Types'; import { MockTerminal } from './ui/TestUtils.test'; -import { BufferLine } from './BufferLine'; +import { BufferLine, CellData } from './BufferLine'; class TestMockTerminal extends MockTerminal { emit(event: string, data: any): void {} @@ -57,14 +57,14 @@ describe('SelectionManager', () => { function stringToRow(text: string): IBufferLine { const result = new BufferLine(text.length); for (let i = 0; i < text.length; i++) { - result.set(i, [0, text.charAt(i), 1, text.charCodeAt(i)]); + result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)])); } return result; } function stringArrayToRow(chars: string[]): IBufferLine { const line = new BufferLine(chars.length); - chars.map((c, idx) => line.set(idx, [0, c, 1, c.charCodeAt(0)])); + chars.map((c, idx) => line.setCell(idx, CellData.fromCharData([0, c, 1, c.charCodeAt(0)]))); return line; } @@ -119,7 +119,7 @@ describe('SelectionManager', () => { [null, 'o', 1, 'o'.charCodeAt(0)] ]; const line = new BufferLine(data.length); - for (let i = 0; i < data.length; ++i) line.set(i, data[i]); + for (let i = 0; i < data.length; ++i) line.setCell(i, CellData.fromCharData(data[i])); buffer.lines.set(0, line); // Ensure wide characters take up 2 columns selectionManager.selectWordAt([0, 0]); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index fdc9678be0..2f9ae6a57e 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -7,6 +7,7 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper, MockRenderer } from './ui/TestUtils.test'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR } from './Buffer'; +import { CellData } from './BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -227,7 +228,7 @@ describe('term.js addons', () => { }); describe('setOption', () => { - it('should set the option correctly', () => { + it('should set option correctly', () => { term.setOption('cursorBlink', true); assert.equal(term.options.cursorBlink, true); term.setOption('cursorBlink', false); @@ -455,8 +456,8 @@ describe('term.js addons', () => { describe('scroll() function', () => { describe('when scrollback > 0', () => { it('should create a new line and scroll', () => { - term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); - term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); + term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); @@ -466,9 +467,9 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); - term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); - term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); + term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(); @@ -478,11 +479,11 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); - term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); - term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); - term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]); - term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]); + term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); + term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = 3; term.buffer.scrollBottom = 3; term.scroll(); @@ -496,11 +497,11 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); - term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); - term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); - term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]); - term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]); + term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); + term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; @@ -521,9 +522,9 @@ describe('term.js addons', () => { }); it('should create a new line and shift everything up', () => { - term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); - term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); - term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); + term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line assert.equal(term.buffer.lines.length, INIT_ROWS); term.scroll(); @@ -536,9 +537,9 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); - term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); - term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); + term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(); @@ -548,11 +549,11 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); - term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); - term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); - term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]); - term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]); + term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); + term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = 3; term.buffer.scrollBottom = 3; term.scroll(); @@ -565,11 +566,11 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); - term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); - term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); - term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]); - term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]); + term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); + term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index 2f9f45bef7..0bbb982ea4 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -29,13 +29,13 @@ describe('CharacterJoinerRegistry', () => { let sub = lineData([['deemo']]); let oldSize = line6.length; line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0])); - for (let i = 0; i < sub.length; ++i) line6.set(i + oldSize, sub.get(i)); + for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, CellData.fromCharData(sub.get(i))); line6.resize(line6.length + 1, CellData.fromCharData([0, '\xf0\x9f\x98\x81', 1, 128513])); line6.resize(line6.length + 1, CellData.fromCharData([0, ' ', 1, ' '.charCodeAt(0)])); sub = lineData([['jiabc']]); oldSize = line6.length; line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0])); - for (let i = 0; i < sub.length; ++i) line6.set(i + oldSize, sub.get(i)); + for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, CellData.fromCharData(sub.get(i))); lines.set(6, line6); (terminal.buffer).setLines(lines); @@ -274,7 +274,7 @@ function lineData(data: IPartialLineData[]): IBufferLine { const attr = (data[i][1] || 0); const offset = tline.length; tline.resize(tline.length + line.split('').length, CellData.fromCharData([0, '', 0, 0])); - line.split('').map((char, idx) => tline.set(idx + offset, [attr, char, 1, char.charCodeAt(0)])); + line.split('').map((char, idx) => tline.setCell(idx + offset, CellData.fromCharData([attr, char, 1, char.charCodeAt(0)]))); } return tline; } diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 67342da01b..e06990d78c 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -8,7 +8,7 @@ import { assert } from 'chai'; import { DomRendererRowFactory } from './DomRendererRowFactory'; import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer'; import { FLAGS } from '../Types'; -import { BufferLine } from '../../BufferLine'; +import { BufferLine, CellData } from '../../BufferLine'; import { IBufferLine } from '../../Types'; import { DEFAULT_COLOR } from '../atlas/Types'; @@ -32,9 +32,9 @@ describe('DomRendererRowFactory', () => { }); it('should set correct attributes for double width characters', () => { - lineData.set(0, [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)])); // There should be no element for the following "empty" cell - lineData.set(1, [DEFAULT_ATTR, '', 0, undefined]); + lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, undefined])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), '' @@ -51,8 +51,8 @@ describe('DomRendererRowFactory', () => { }); it('should not render cells that go beyond the terminal\'s columns', () => { - lineData.set(0, [DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]); - lineData.set(1, [DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); + lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 1); assert.equal(getFragmentHtml(fragment), 'a' @@ -61,7 +61,7 @@ describe('DomRendererRowFactory', () => { describe('attributes', () => { it('should add class for bold', () => { - lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -69,7 +69,7 @@ describe('DomRendererRowFactory', () => { }); it('should add class for italic', () => { - lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -79,7 +79,7 @@ describe('DomRendererRowFactory', () => { it('should add classes for 256 foreground colors', () => { const defaultAttrNoFgColor = (0 << 9) | (DEFAULT_COLOR << 0); for (let i = 0; i < 256; i++) { - lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` @@ -90,7 +90,7 @@ describe('DomRendererRowFactory', () => { it('should add classes for 256 background colors', () => { const defaultAttrNoBgColor = (DEFAULT_ATTR << 9) | (0 << 0); for (let i = 0; i < 256; i++) { - lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` @@ -99,7 +99,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert colors', () => { - lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -107,7 +107,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default fg color', () => { - lineData.set(0, [(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -115,7 +115,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default bg color', () => { - lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -124,7 +124,7 @@ describe('DomRendererRowFactory', () => { it('should turn bold fg text bright', () => { for (let i = 0; i < 8; i++) { - lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]); + lineData.setCell(0, CellData.fromCharData([(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)])); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` @@ -143,7 +143,7 @@ describe('DomRendererRowFactory', () => { function createEmptyLineData(cols: number): IBufferLine { const lineData = new BufferLine(cols); for (let i = 0; i < cols; i++) { - lineData.set(i, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + lineData.setCell(i, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE])); } return lineData; } From 711ae6594780dc19e1d0c1879db1958dce56cc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 17:50:19 +0100 Subject: [PATCH 21/30] remove get CharData from codebase and deprecate method --- src/Buffer.test.ts | 20 +- src/BufferLine.test.ts | 8 +- src/BufferLine.ts | 7 + src/CharWidth.test.ts | 3 +- src/InputHandler.test.ts | 19 +- src/Terminal.integration.ts | 5 +- src/Terminal.test.ts | 353 ++++++++++--------- src/Types.ts | 1 + src/renderer/CharacterJoinerRegistry.test.ts | 4 +- 9 files changed, 221 insertions(+), 199 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index d1a1d5638b..27e0e836d3 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -5,7 +5,7 @@ import { assert, expect } from 'chai'; import { ITerminal } from './Types'; -import { Buffer, DEFAULT_ATTR, CHAR_DATA_CHAR_INDEX } from './Buffer'; +import { Buffer, DEFAULT_ATTR } from './Buffer'; import { CircularList } from './common/CircularList'; import { MockTerminal, TestTerminal } from './ui/TestUtils.test'; import { BufferLine, CellData } from './BufferLine'; @@ -37,13 +37,13 @@ describe('Buffer', () => { describe('fillViewportRows', () => { it('should fill the buffer with blank lines based on the size of the viewport', () => { - const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR).get(0); + const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR).loadCell(0, new CellData()).asCharData; buffer.fillViewportRows(); assert.equal(buffer.lines.length, INIT_ROWS); for (let y = 0; y < INIT_ROWS; y++) { assert.equal(buffer.lines.get(y).length, INIT_COLS); for (let x = 0; x < INIT_COLS; x++) { - assert.deepEqual(buffer.lines.get(y).get(x), blankLineChar); + assert.deepEqual(buffer.lines.get(y).loadCell(x, new CellData()).asCharData, blankLineChar); } } }); @@ -155,15 +155,15 @@ describe('Buffer', () => { assert.equal(buffer.lines.maxLength, INIT_ROWS); buffer.y = INIT_ROWS - 1; buffer.fillViewportRows(); - let chData = buffer.lines.get(5).get(0); + let chData = buffer.lines.get(5).loadCell(0, new CellData()).asCharData; chData[1] = 'a'; buffer.lines.get(5).setCell(0, CellData.fromCharData(chData)); - chData = buffer.lines.get(INIT_ROWS - 1).get(0); + chData = buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).asCharData; chData[1] = 'b'; buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData(chData)); buffer.resize(INIT_COLS, INIT_ROWS - 5); - assert.equal(buffer.lines.get(0).get(0)[1], 'a'); - assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).get(0)[1], 'b'); + assert.equal(buffer.lines.get(0).loadCell(0, new CellData()).asCharData[1], 'a'); + assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).loadCell(0, new CellData()).asCharData[1], 'b'); }); }); }); @@ -497,7 +497,7 @@ describe('Buffer', () => { assert.equal(input, s); const stringIndex = s.match(/😃/).index; const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex); - assert(terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX], '😃'); + assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).chars, '😃'); }); it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => { @@ -524,7 +524,7 @@ describe('Buffer', () => { assert.equal(input, s); for (let i = 0; i < input.length; ++i) { const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX]); + assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).chars); } }); @@ -542,7 +542,7 @@ describe('Buffer', () => { : (i % 3 === 1) ? input.substr(i, 2) : input.substr(i - 1, 2), - terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX]); + terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).chars); } }); }); diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 95ec77c354..874eb14f6c 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -12,7 +12,7 @@ class TestBufferLine extends BufferLine { public toArray(): CharData[] { const result = []; for (let i = 0; i < this.length; ++i) { - result.push(this.get(i)); + result.push(this.loadCell(i, new CellData()).asCharData); } return result; } @@ -25,15 +25,15 @@ describe('BufferLine', function(): void { chai.expect(line.isWrapped).equals(false); line = new TestBufferLine(10); chai.expect(line.length).equals(10); - chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + chai.expect(line.loadCell(0, new CellData()).asCharData).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); chai.expect(line.isWrapped).equals(false); line = new TestBufferLine(10, null, true); chai.expect(line.length).equals(10); - chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + chai.expect(line.loadCell(0, new CellData()).asCharData).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); chai.expect(line.isWrapped).equals(true); line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true); chai.expect(line.length).equals(10); - chai.expect(line.get(0)).eql([123, 'a', 456, 'a'.charCodeAt(0)]); + chai.expect(line.loadCell(0, new CellData()).asCharData).eql([123, 'a', 456, 'a'.charCodeAt(0)]); chai.expect(line.isWrapped).equals(true); }); it('insertCells', function(): void { diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 4a0744414c..8b62141ebb 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -129,6 +129,9 @@ export class CellData implements ICellData { this.content = Content.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } } + public get asCharData(): CharData { + return [this.fg, this.chars, this.width, this.code]; + } } /** @@ -150,6 +153,10 @@ export class BufferLine implements IBufferLine { this.length = cols; } + /** + * Get cell data CharData. + * @deprecated + */ public get(index: number): CharData { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts index 0747fdf12c..7cab38829a 100644 --- a/src/CharWidth.test.ts +++ b/src/CharWidth.test.ts @@ -8,6 +8,7 @@ import { assert } from 'chai'; import { getStringCellWidth, wcwidth } from './CharWidth'; import { IBuffer } from './Types'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; +import { CellData } from './BufferLine'; describe('getStringCellWidth', function(): void { @@ -22,7 +23,7 @@ describe('getStringCellWidth', function(): void { for (let i = start; i < end; ++i) { const line = buffer.lines.get(i); for (let j = 0; j < line.length; ++j) { // TODO: change to trimBorder with multiline - const ch = line.get(j); + const ch = line.loadCell(j, new CellData()).asCharData; result += ch[CHAR_DATA_WIDTH_INDEX]; // return on sentinel if (ch[CHAR_DATA_CHAR_INDEX] === sentinel) { diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index a7963a2c4b..24eb088490 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -6,9 +6,10 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal } from './ui/TestUtils.test'; -import { CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } from './Buffer'; +import { DEFAULT_ATTR } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './Types'; +import { CellData } from './BufferLine'; describe('InputHandler', () => { describe('save and restore cursor', () => { @@ -356,45 +357,45 @@ describe('InputHandler', () => { expect(term.buffer.translateBufferLineToString(0, true)).to.equal(''); expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); // Text color of 'TEST' should be red - expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(4, new CellData()).fg >> 9) & 0x1ff).to.equal(1); }); it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => { handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); expect(term.buffer.translateBufferLineToString(0, true)).to.equal(''); expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); // Text color of 'TEST' should be red - expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(4, new CellData()).fg >> 9) & 0x1ff).to.equal(1); }); it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); expect(term.buffer.translateBufferLineToString(1, true)).to.equal('JUNK'); // Text color of 'TEST' should be default - expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); + expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR); // Text color of 'JUNK' should be red - expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(0, new CellData()).fg >> 9) & 0x1ff).to.equal(1); }); it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); expect(term.buffer.translateBufferLineToString(1, true)).to.equal(''); // Text color of 'TEST' should be default - expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); + expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR); }); it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => { handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); // Text color of 'TEST' should be default - expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); + expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR); handler.parse('\x1b[?1049h\x1b[uTEST'); expect(term.buffer.translateBufferLineToString(1, true)).to.equal('TEST'); // Text color of 'TEST' should be red - expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(0, new CellData()).fg >> 9) & 0x1ff).to.equal(1); }); it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => { handler.parse('\x1b[42m\x1b[?1049h'); // Buffer should be filled with green background - expect(term.buffer.lines.get(20).get(10)[CHAR_DATA_ATTR_INDEX] & 0x1ff).to.equal(2); + expect(term.buffer.lines.get(20).loadCell(10, new CellData()).fg & 0x1ff).to.equal(2); }); }); }); diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index d2a5cd7c5e..b5165490cc 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -13,8 +13,9 @@ import * as path from 'path'; import * as pty from 'node-pty'; import { assert } from 'chai'; import { Terminal } from './Terminal'; -import { CHAR_DATA_CHAR_INDEX, WHITESPACE_CELL_CHAR } from './Buffer'; +import { WHITESPACE_CELL_CHAR } from './Buffer'; import { IViewport } from './Types'; +import { CellData } from './BufferLine'; class TestTerminal extends Terminal { innerWrite(): void { this._innerWrite(); } @@ -67,7 +68,7 @@ function terminalToString(term: Terminal): string { for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) { lineText = ''; for (let cell = 0; cell < term.cols; ++cell) { - lineText += term.buffer.lines.get(line).get(cell)[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; + lineText += term.buffer.lines.get(line).loadCell(cell, new CellData()).chars || WHITESPACE_CELL_CHAR; } // rtrim empty cells as xterm does lineText = lineText.replace(/\s+$/, ''); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 2f9ae6a57e..e06ceb78cd 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -6,7 +6,7 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper, MockRenderer } from './ui/TestUtils.test'; -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR } from './Buffer'; +import { DEFAULT_ATTR } from './Buffer'; import { CellData } from './BufferLine'; const INIT_COLS = 80; @@ -461,9 +461,9 @@ describe('term.js addons', () => { term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); - assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(INIT_ROWS).get(0)[CHAR_DATA_CHAR_INDEX], ''); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).chars, 'b'); + assert.equal(term.buffer.lines.get(INIT_ROWS).loadCell(0, new CellData()).chars, ''); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { @@ -474,8 +474,8 @@ describe('term.js addons', () => { term.buffer.scrollTop = 1; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c'); }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { @@ -488,12 +488,12 @@ describe('term.js addons', () => { term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); - assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a', '\'a\' should be pushed to the scrollback'); - assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'c'); - assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(5).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a', '\'a\' should be pushed to the scrollback'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'b'); + assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).chars, 'c'); + assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).chars, 'd'); + assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).chars, '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(5).loadCell(0, new CellData()).chars, 'e'); }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { @@ -507,11 +507,11 @@ describe('term.js addons', () => { term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer'); - assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c', '\'b\' should be removed from the buffer'); + assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).chars, 'd'); + assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).chars, '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).chars, 'e'); }); }); @@ -530,10 +530,10 @@ describe('term.js addons', () => { term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); // 'a' gets pushed out of buffer - assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], ''); - assert.equal(term.buffer.lines.get(INIT_ROWS - 2).get(0)[CHAR_DATA_CHAR_INDEX], 'c'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], ''); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'b'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, ''); + assert.equal(term.buffer.lines.get(INIT_ROWS - 2).loadCell(0, new CellData()).chars, 'c'); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).chars, ''); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { @@ -544,8 +544,8 @@ describe('term.js addons', () => { term.buffer.scrollTop = 1; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c'); }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { @@ -558,11 +558,11 @@ describe('term.js addons', () => { term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c'); - assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'b'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c'); + assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).chars, 'd'); + assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).chars, '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).chars, 'e'); }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { @@ -576,11 +576,11 @@ describe('term.js addons', () => { term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer'); - assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c', '\'b\' should be removed from the buffer'); + assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).chars, 'd'); + assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).chars, '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).chars, 'e'); }); }); }); @@ -771,116 +771,126 @@ describe('term.js addons', () => { it('2 characters per cell', function (): void { this.timeout(10000); // This is needed because istanbul patches code and slows it down const high = String.fromCharCode(0xD800); + const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.write(high + String.fromCharCode(i)); - const tchar = term.buffer.lines.get(0).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); + const tchar = term.buffer.lines.get(0).loadCell(0, cell); + expect(tchar.chars).eql(high + String.fromCharCode(i)); + expect(tchar.chars.length).eql(2); + expect(tchar.width).eql(1); + expect(term.buffer.lines.get(0).loadCell(1, cell).chars).eql(''); term.reset(); } }); it('2 characters at last cell', () => { const high = String.fromCharCode(0xD800); + const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.write(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).chars).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).chars.length).eql(2); + expect(term.buffer.lines.get(1).loadCell(0, cell).chars).eql(''); term.reset(); } }); it('2 characters per cell over line end with autowrap', () => { const high = String.fromCharCode(0xD800); + const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.wraparoundMode = true; term.write('a' + high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).chars).eql('a'); + expect(term.buffer.lines.get(1).loadCell(0, cell).chars).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(1).loadCell(0, cell).chars.length).eql(2); + expect(term.buffer.lines.get(1).loadCell(1, cell).chars).eql(''); term.reset(); } }); it('2 characters per cell over line end without autowrap', () => { const high = String.fromCharCode(0xD800); + const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.wraparoundMode = false; term.write('a' + high + String.fromCharCode(i)); // auto wraparound mode should cut off the rest of the line - expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).chars).eql('a'); + expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).chars.length).eql(1); + expect(term.buffer.lines.get(1).loadCell(1, cell).chars).eql(''); term.reset(); } }); it('splitted surrogates', () => { const high = String.fromCharCode(0xD800); + const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.write(high); term.write(String.fromCharCode(i)); - const tchar = term.buffer.lines.get(0).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); + const tchar = term.buffer.lines.get(0).loadCell(0, cell); + expect(tchar.chars).eql(high + String.fromCharCode(i)); + expect(tchar.chars.length).eql(2); + expect(tchar.width).eql(1); + expect(term.buffer.lines.get(0).loadCell(1, cell).chars).eql(''); term.reset(); } }); }); describe('unicode - combining characters', () => { + const cell = new CellData(); it('café', () => { term.write('cafe\u0301'); - expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); - expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_WIDTH_INDEX]).eql(1); + term.buffer.lines.get(0).loadCell(3, cell); + expect(cell.chars).eql('e\u0301'); + expect(cell.chars.length).eql(2); + expect(cell.width).eql(1); }); it('café - end of line', () => { term.buffer.x = term.cols - 1 - 3; term.write('cafe\u0301'); - expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); - expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_WIDTH_INDEX]).eql(1); + term.buffer.lines.get(0).loadCell(term.cols - 1, cell); + expect(cell.chars).eql('e\u0301'); + expect(cell.chars.length).eql(2); + expect(cell.width).eql(1); + term.buffer.lines.get(0).loadCell(1, cell); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(1); }); it('multiple combined é', () => { term.wraparoundMode = true; term.write(Array(100).join('e\u0301')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0).get(i); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + term.buffer.lines.get(0).loadCell(i, cell); + expect(cell.chars).eql('e\u0301'); + expect(cell.chars.length).eql(2); + expect(cell.width).eql(1); } - const tchar = term.buffer.lines.get(1).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + term.buffer.lines.get(1).loadCell(0, cell); + expect(cell.chars).eql('e\u0301'); + expect(cell.chars.length).eql(2); + expect(cell.width).eql(1); }); it('multiple surrogate with combined', () => { term.wraparoundMode = true; term.write(Array(100).join('\uD800\uDC00\u0301')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0).get(i); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + term.buffer.lines.get(0).loadCell(i, cell); + expect(cell.chars).eql('\uD800\uDC00\u0301'); + expect(cell.chars.length).eql(3); + expect(cell.width).eql(1); } - const tchar = term.buffer.lines.get(1).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + term.buffer.lines.get(1).loadCell(0, cell); + expect(cell.chars).eql('\uD800\uDC00\u0301'); + expect(cell.chars.length).eql(3); + expect(cell.width).eql(1); }); }); describe('unicode - fullwidth characters', () => { + const cell = new CellData(); it('cursor movement even', () => { expect(term.buffer.x).eql(0); term.write('¥'); @@ -896,140 +906,141 @@ describe('term.js addons', () => { term.wraparoundMode = true; term.write(Array(50).join('¥')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0).get(i); + term.buffer.lines.get(0).loadCell(i, cell); if (i % 2) { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(0); } else { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + expect(cell.chars).eql('¥'); + expect(cell.chars.length).eql(1); + expect(cell.width).eql(2); } } - const tchar = term.buffer.lines.get(1).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + term.buffer.lines.get(1).loadCell(0, cell); + expect(cell.chars).eql('¥'); + expect(cell.chars.length).eql(1); + expect(cell.width).eql(2); }); it('line of ¥ odd', () => { term.wraparoundMode = true; term.buffer.x = 1; term.write(Array(50).join('¥')); for (let i = 1; i < term.cols - 1; ++i) { - const tchar = term.buffer.lines.get(0).get(i); + term.buffer.lines.get(0).loadCell(i, cell); if (!(i % 2)) { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(0); } else { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + expect(cell.chars).eql('¥'); + expect(cell.chars.length).eql(1); + expect(cell.width).eql(2); } } - let tchar = term.buffer.lines.get(0).get(term.cols - 1); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - tchar = term.buffer.lines.get(1).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + term.buffer.lines.get(0).loadCell(term.cols - 1, cell); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(1); + term.buffer.lines.get(1).loadCell(0, cell); + expect(cell.chars).eql('¥'); + expect(cell.chars.length).eql(1); + expect(cell.width).eql(2); }); it('line of ¥ with combining odd', () => { term.wraparoundMode = true; term.buffer.x = 1; term.write(Array(50).join('¥\u0301')); for (let i = 1; i < term.cols - 1; ++i) { - const tchar = term.buffer.lines.get(0).get(i); + term.buffer.lines.get(0).loadCell(i, cell); if (!(i % 2)) { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(0); } else { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + expect(cell.chars).eql('¥\u0301'); + expect(cell.chars.length).eql(2); + expect(cell.width).eql(2); } } - let tchar = term.buffer.lines.get(0).get(term.cols - 1); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - tchar = term.buffer.lines.get(1).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + term.buffer.lines.get(0).loadCell(term.cols - 1, cell); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(1); + term.buffer.lines.get(1).loadCell(0, cell); + expect(cell.chars).eql('¥\u0301'); + expect(cell.chars.length).eql(2); + expect(cell.width).eql(2); }); it('line of ¥ with combining even', () => { term.wraparoundMode = true; term.write(Array(50).join('¥\u0301')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0).get(i); + term.buffer.lines.get(0).loadCell(i, cell); if (i % 2) { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(0); } else { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + expect(cell.chars).eql('¥\u0301'); + expect(cell.chars.length).eql(2); + expect(cell.width).eql(2); } } - const tchar = term.buffer.lines.get(1).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + term.buffer.lines.get(1).loadCell(0, cell); + expect(cell.chars).eql('¥\u0301'); + expect(cell.chars.length).eql(2); + expect(cell.width).eql(2); }); it('line of surrogate fullwidth with combining odd', () => { term.wraparoundMode = true; term.buffer.x = 1; term.write(Array(50).join('\ud843\ude6d\u0301')); for (let i = 1; i < term.cols - 1; ++i) { - const tchar = term.buffer.lines.get(0).get(i); + term.buffer.lines.get(0).loadCell(i, cell); if (!(i % 2)) { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(0); } else { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + expect(cell.chars).eql('\ud843\ude6d\u0301'); + expect(cell.chars.length).eql(3); + expect(cell.width).eql(2); } } - let tchar = term.buffer.lines.get(0).get(term.cols - 1); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - tchar = term.buffer.lines.get(1).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + term.buffer.lines.get(0).loadCell(term.cols - 1, cell); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(1); + term.buffer.lines.get(1).loadCell(0, cell); + expect(cell.chars).eql('\ud843\ude6d\u0301'); + expect(cell.chars.length).eql(3); + expect(cell.width).eql(2); }); it('line of surrogate fullwidth with combining even', () => { term.wraparoundMode = true; term.write(Array(50).join('\ud843\ude6d\u0301')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0).get(i); + term.buffer.lines.get(0).loadCell(i, cell); if (i % 2) { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + expect(cell.chars).eql(''); + expect(cell.chars.length).eql(0); + expect(cell.width).eql(0); } else { - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + expect(cell.chars).eql('\ud843\ude6d\u0301'); + expect(cell.chars.length).eql(3); + expect(cell.width).eql(2); } } - const tchar = term.buffer.lines.get(1).get(0); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + term.buffer.lines.get(1).loadCell(0, cell); + expect(cell.chars).eql('\ud843\ude6d\u0301'); + expect(cell.chars.length).eql(3); + expect(cell.width).eql(2); }); }); describe('insert mode', () => { + const cell = new CellData(); it('halfwidth - all', () => { term.write(Array(9).join('0123456789').slice(-80)); term.buffer.x = 10; @@ -1037,10 +1048,10 @@ describe('term.js addons', () => { term.insertMode = true; term.write('abcde'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(term.buffer.lines.get(0).get(14)[CHAR_DATA_CHAR_INDEX]).eql('e'); - expect(term.buffer.lines.get(0).get(15)[CHAR_DATA_CHAR_INDEX]).eql('0'); - expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql('4'); + expect(term.buffer.lines.get(0).loadCell(10, cell).chars).eql('a'); + expect(term.buffer.lines.get(0).loadCell(14, cell).chars).eql('e'); + expect(term.buffer.lines.get(0).loadCell(15, cell).chars).eql('0'); + expect(term.buffer.lines.get(0).loadCell(79, cell).chars).eql('4'); }); it('fullwidth - insert', () => { term.write(Array(9).join('0123456789').slice(-80)); @@ -1049,11 +1060,11 @@ describe('term.js addons', () => { term.insertMode = true; term.write('¥¥¥'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(term.buffer.lines.get(0).get(14)[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0).get(15)[CHAR_DATA_CHAR_INDEX]).eql(''); - expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql('3'); + expect(term.buffer.lines.get(0).loadCell(10, cell).chars).eql('¥'); + expect(term.buffer.lines.get(0).loadCell(11, cell).chars).eql(''); + expect(term.buffer.lines.get(0).loadCell(14, cell).chars).eql('¥'); + expect(term.buffer.lines.get(0).loadCell(15, cell).chars).eql(''); + expect(term.buffer.lines.get(0).loadCell(79, cell).chars).eql('3'); }); it('fullwidth - right border', () => { term.write(Array(41).join('¥')); @@ -1062,14 +1073,14 @@ describe('term.js addons', () => { term.insertMode = true; term.write('a'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(''); // fullwidth char got replaced + expect(term.buffer.lines.get(0).loadCell(10, cell).chars).eql('a'); + expect(term.buffer.lines.get(0).loadCell(11, cell).chars).eql('¥'); + expect(term.buffer.lines.get(0).loadCell(79, cell).chars).eql(''); // fullwidth char got replaced term.write('b'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('b'); - expect(term.buffer.lines.get(0).get(12)[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(''); // empty cell after fullwidth + expect(term.buffer.lines.get(0).loadCell(11, cell).chars).eql('b'); + expect(term.buffer.lines.get(0).loadCell(12, cell).chars).eql('¥'); + expect(term.buffer.lines.get(0).loadCell(79, cell).chars).eql(''); // empty cell after fullwidth }); }); }); diff --git a/src/Types.ts b/src/Types.ts index 644a9b49cd..d176799f4a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -524,6 +524,7 @@ export interface ICellData { chars: string; code: number; setFromCharData(value: CharData): void; + asCharData: CharData; } /** diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index 0bbb982ea4..effdbfaafa 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -29,13 +29,13 @@ describe('CharacterJoinerRegistry', () => { let sub = lineData([['deemo']]); let oldSize = line6.length; line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0])); - for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, CellData.fromCharData(sub.get(i))); + for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData())); line6.resize(line6.length + 1, CellData.fromCharData([0, '\xf0\x9f\x98\x81', 1, 128513])); line6.resize(line6.length + 1, CellData.fromCharData([0, ' ', 1, ' '.charCodeAt(0)])); sub = lineData([['jiabc']]); oldSize = line6.length; line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0])); - for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, CellData.fromCharData(sub.get(i))); + for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData())); lines.set(6, line6); (terminal.buffer).setLines(lines); From 35a6aa83268b444686f730ffc879b4f46d9cd06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 18:04:52 +0100 Subject: [PATCH 22/30] some docs --- src/BufferLine.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 8b62141ebb..b362c007df 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -76,22 +76,35 @@ export const enum Content { WIDTH_SHIFT = 22 } +/** + * CellData - represents a single Cell in the terminal buffer. + */ export class CellData implements ICellData { + + /** Helper to create CellData from CharData. */ public static fromCharData(value: CharData): CellData { const obj = new CellData(); obj.setFromCharData(value); return obj; } + + /** Primitives from terminal buffer. */ public content: number = 0; public fg: number = 0; public bg: number = 0; public combinedData: string = ''; + + /** Whether cell contains a combined string. */ public get combined(): number { return this.content & Content.IS_COMBINED; } + + /** Width of the cell. */ public get width(): number { return this.content >> Content.WIDTH_SHIFT; } + + /** JS string of the content. */ public get chars(): string { if (this.content & Content.IS_COMBINED) { return this.combinedData; @@ -101,9 +114,13 @@ export class CellData implements ICellData { } return ''; } + + /** Codepoint of cell (or last charCode of combined string) */ public get code(): number { return ((this.combined) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK); } + + /** Set data from CharData */ public setFromCharData(value: CharData): void { this.fg = value[CHAR_DATA_ATTR_INDEX]; this.bg = 0; @@ -129,11 +146,14 @@ export class CellData implements ICellData { this.content = Content.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } } + + /** Get data as CharData. */ public get asCharData(): CharData { return [this.fg, this.chars, this.width, this.code]; } } + /** * Typed array based bufferline implementation. */ @@ -193,18 +213,23 @@ export class BufferLine implements IBufferLine { public getWidth(index: number): number { return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT; } + public hasWidth(index: number): number { return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK; } + public getFG(index: number): number { return this._data[index * CELL_SIZE + Cell.FG]; } + public getBG(index: number): number { return this._data[index * CELL_SIZE + Cell.BG]; } + public hasContent(index: number): number { return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT; } + public getCodePoint(index: number): number { // returns either the single codepoint or the last charCode in combined const content = this._data[index * CELL_SIZE + Cell.CONTENT]; @@ -213,9 +238,11 @@ export class BufferLine implements IBufferLine { } return content & Content.CODEPOINT_MASK; } + public isCombined(index: number): number { return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED; } + public getString(index: number): string { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; if (content & Content.IS_COMBINED) { @@ -227,6 +254,9 @@ export class BufferLine implements IBufferLine { return ''; // return empty string for empty cells } + /** + * Load data at `index` into `cell`. + */ public loadCell(index: number, cell: ICellData): ICellData { cell.content = this._data[index * CELL_SIZE + Cell.CONTENT]; cell.fg = this._data[index * CELL_SIZE + Cell.FG]; @@ -237,6 +267,9 @@ export class BufferLine implements IBufferLine { return cell; } + /** + * Set data at `index` to `cell`. + */ public setCell(index: number, cell: ICellData): void { if (cell.content & Content.IS_COMBINED) { this._combined[index] = cell.combinedData; From 40b231edd1178d82fd88a0d7d49be67b97574a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 18:44:10 +0100 Subject: [PATCH 23/30] tests for CellData --- src/BufferLine.test.ts | 28 +++++++++++++++++++++++++++- src/BufferLine.ts | 5 ++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 874eb14f6c..110b9b4ed4 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import * as chai from 'chai'; -import { BufferLine, CellData } from './BufferLine'; +import { BufferLine, CellData, Content } from './BufferLine'; import { CharData, IBufferLine } from './Types'; import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer'; @@ -18,6 +18,32 @@ class TestBufferLine extends BufferLine { } } +describe('CellData', () => { + it('CharData <--> CellData equality', () => { + const cell = new CellData(); + // ASCII + cell.setFromCharData([123, 'a', 1, 'a'.charCodeAt(0)]); + chai.assert.deepEqual(cell.asCharData, [123, 'a', 1, 'a'.charCodeAt(0)]); + chai.assert.equal(cell.combined, 0); + // combining + cell.setFromCharData([123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + chai.assert.equal(cell.combined, Content.IS_COMBINED); + // surrogate + cell.setFromCharData([123, '𝄞', 1, 0x1D11E]); + chai.assert.deepEqual(cell.asCharData, [123, '𝄞', 1, 0x1D11E]); + chai.assert.equal(cell.combined, 0); + // surrogate + combining + cell.setFromCharData([123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); + chai.assert.deepEqual(cell.asCharData, [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); + chai.assert.equal(cell.combined, Content.IS_COMBINED); + // wide char + cell.setFromCharData([123, '1', 2, '1'.charCodeAt(0)]); + chai.assert.deepEqual(cell.asCharData, [123, '1', 2, '1'.charCodeAt(0)]); + chai.assert.equal(cell.combined, 0); + }); +}); + describe('BufferLine', function(): void { it('ctor', function(): void { let line: IBufferLine = new TestBufferLine(0); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index b362c007df..048bcab6b7 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -78,6 +78,8 @@ export const enum Content { /** * CellData - represents a single Cell in the terminal buffer. + * + * TODO: attr getter */ export class CellData implements ICellData { @@ -136,8 +138,9 @@ export class CellData implements ICellData { } else { combined = true; } + } else { + combined = true; } - combined = true; } else { this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } From 7c6dad5805e26235f25c4ad7b2f51438aaac2e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 12 Jan 2019 19:06:09 +0100 Subject: [PATCH 24/30] test cases --- src/BufferLine.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 110b9b4ed4..97508ace48 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -357,4 +357,42 @@ describe('BufferLine', function(): void { chai.expect(line.translateToString(true, 0, 0)).equal(''); }); }); + describe('addCharToCell', () => { + it('should set width to 1 for empty cell', () => { + const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); + line.addCharToCell(0, '\u0301'.charCodeAt(0)); + const cell = line.loadCell(0, new CellData()); + // chars contains single combining char + // width is set to 1 + chai.assert.deepEqual(cell.asCharData, [DEFAULT_ATTR, '\u0301', 1, 0x0301]); + // do not account a single combining char as combined + chai.assert.equal(cell.combined, 0); + }); + it('should add char to combining string in cell', () => { + const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); + const cell = line .loadCell(0, new CellData()); + cell.setFromCharData([123, 'e\u0301', 1, 'e\u0301'.charCodeAt(1)]); + line.setCell(0, cell); + line.addCharToCell(0, '\u0301'.charCodeAt(0)); + line.loadCell(0, cell); + // chars contains 3 chars + // width is set to 1 + chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301\u0301', 1, 0x0301]); + // do not account a single combining char as combined + chai.assert.equal(cell.combined, Content.IS_COMBINED); + }); + it('should create combining string on taken cell', () => { + const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); + const cell = line .loadCell(0, new CellData()); + cell.setFromCharData([123, 'e', 1, 'e'.charCodeAt(1)]); + line.setCell(0, cell); + line.addCharToCell(0, '\u0301'.charCodeAt(0)); + line.loadCell(0, cell); + // chars contains 2 chars + // width is set to 1 + chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301', 1, 0x0301]); + // do not account a single combining char as combined + chai.assert.equal(cell.combined, Content.IS_COMBINED); + }); + }); }); From b20730ee4d3090c653e7f00e175815641f0bb149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 27 Jan 2019 19:40:28 +0100 Subject: [PATCH 25/30] add more docs --- src/Buffer.ts | 10 ++++++++++ src/BufferLine.ts | 35 +++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 2b9b223336..f58d0da0e7 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -18,10 +18,20 @@ export const CHAR_DATA_WIDTH_INDEX = 2; export const CHAR_DATA_CODE_INDEX = 3; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 +/** + * Null cell - a real empty cell (containing nothing). + * Note that code should always be 0 for a null cell as + * several test condition of the buffer line rely on this. + */ export const NULL_CELL_CHAR = ''; export const NULL_CELL_WIDTH = 1; export const NULL_CELL_CODE = 0; +/** + * Whilespace cell. + * This is meant as a replacement for empty cells when needed + * during rendering lines to preserve correct aligment. + */ export const WHITESPACE_CELL_CHAR = ' '; export const WHITESPACE_CELL_WIDTH = 1; export const WHITESPACE_CELL_CODE = 32; diff --git a/src/BufferLine.ts b/src/BufferLine.ts index ade2143559..3f7ff07667 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -78,8 +78,6 @@ export const enum Content { /** * CellData - represents a single Cell in the terminal buffer. - * - * TODO: attr getter */ export class CellData implements ICellData { @@ -117,7 +115,12 @@ export class CellData implements ICellData { return ''; } - /** Codepoint of cell (or last charCode of combined string) */ + /** + * Codepoint of cell + * Note this returns the UTF32 codepoint of single chars, + * if content is a combined string it returns the codepoint + * of the last char in string to be in line with code in CharData. + * */ public get code(): number { return ((this.combined) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK); } @@ -127,10 +130,14 @@ export class CellData implements ICellData { this.fg = value[CHAR_DATA_ATTR_INDEX]; this.bg = 0; let combined = false; + + // surrogates and combined strings need special treatment if (value[CHAR_DATA_CHAR_INDEX].length > 2) { combined = true; } else if (value[CHAR_DATA_CHAR_INDEX].length === 2) { const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0); + // if the 2-char string is a surrogate create single codepoint + // everything else is combined if (0xD800 <= code && code <= 0xDBFF) { const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1); if (0xDC00 <= second && second <= 0xDFFF) { @@ -217,24 +224,36 @@ export class BufferLine implements IBufferLine { 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] & Content.WIDTH_MASK; } + /** Get FG cell component. */ public getFG(index: number): number { return this._data[index * CELL_SIZE + Cell.FG]; } + /** Get BG cell component. */ public getBG(index: number): number { return this._data[index * CELL_SIZE + Cell.BG]; } + /** + * Test whether contains any chars. + * Basically an empty has no content, but other cells might differ in FG/BG + * from real empty cells. + * */ public hasContent(index: number): number { return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT; } + /** + * Get codepoint of the cell. + * To be in line with `code` in CharData this either returns + * a single UTF32 codepoint or the last codepoint of a combined string. + */ public getCodePoint(index: number): number { - // returns either the single codepoint or the last charCode in combined const content = this._data[index * CELL_SIZE + Cell.CONTENT]; if (content & Content.IS_COMBINED) { return this._combined[index].charCodeAt(this._combined[index].length - 1); @@ -242,10 +261,12 @@ export class BufferLine implements IBufferLine { 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] & Content.IS_COMBINED; } + /** Returns the string content of the cell. */ public getString(index: number): string { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; if (content & Content.IS_COMBINED) { @@ -254,7 +275,8 @@ export class BufferLine implements IBufferLine { if (content & Content.CODEPOINT_MASK) { return stringFromCodePoint(content & Content.CODEPOINT_MASK); } - return ''; // return empty string for empty cells + // return empty string for empty cells + return ''; } /** @@ -276,9 +298,6 @@ export class BufferLine implements IBufferLine { public setCell(index: number, cell: ICellData): void { if (cell.content & Content.IS_COMBINED) { this._combined[index] = cell.combinedData; - // we also need to clear and set codepoint to index - cell.content &= ~Content.CODEPOINT_MASK; - cell.content |= index; } this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content; this._data[index * CELL_SIZE + Cell.FG] = cell.fg; From e77e36cbb724b6e88174551046a0ec71246aa00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 27 Jan 2019 19:59:13 +0100 Subject: [PATCH 26/30] fix typo in HAS_CONTENT --- src/BufferLine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 3f7ff07667..09f12028bf 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -60,7 +60,7 @@ export const enum Content { * whether a cell contains anything * read: `isEmtpy = !(content & Content.hasContent)` */ - HAS_CONTENT = 0x2FFFFF, + HAS_CONTENT = 0x3FFFFF, /** * bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2) From c60f9b14689520a84ddcdc4060e8875c8cd33262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 27 Jan 2019 20:06:58 +0100 Subject: [PATCH 27/30] code formatting --- src/BufferLine.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 09f12028bf..84a7bfcde2 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -122,7 +122,9 @@ export class CellData implements ICellData { * of the last char in string to be in line with code in CharData. * */ public get code(): number { - return ((this.combined) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK); + return (this.combined) + ? this.combinedData.charCodeAt(this.combinedData.length - 1) + : this.content & Content.CODEPOINT_MASK; } /** Set data from CharData */ From 39fd6c427d8042dc2199116be1d7414441eb179e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 28 Jan 2019 10:42:44 +0100 Subject: [PATCH 28/30] name polishing, docs --- src/BufferLine.test.ts | 22 +++++++++++----------- src/BufferLine.ts | 27 +++++++++++++++++++-------- src/InputHandler.ts | 10 +++++----- src/Types.ts | 6 +++--- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 2a874f9d40..894cc204f6 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -28,23 +28,23 @@ describe('CellData', () => { // ASCII cell.setFromCharData([123, 'a', 1, 'a'.charCodeAt(0)]); chai.assert.deepEqual(cell.asCharData, [123, 'a', 1, 'a'.charCodeAt(0)]); - chai.assert.equal(cell.combined, 0); + chai.assert.equal(cell.isCombined, 0); // combining cell.setFromCharData([123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); - chai.assert.equal(cell.combined, Content.IS_COMBINED); + chai.assert.equal(cell.isCombined, Content.IS_COMBINED); // surrogate cell.setFromCharData([123, '𝄞', 1, 0x1D11E]); chai.assert.deepEqual(cell.asCharData, [123, '𝄞', 1, 0x1D11E]); - chai.assert.equal(cell.combined, 0); + chai.assert.equal(cell.isCombined, 0); // surrogate + combining cell.setFromCharData([123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); chai.assert.deepEqual(cell.asCharData, [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); - chai.assert.equal(cell.combined, Content.IS_COMBINED); + chai.assert.equal(cell.isCombined, Content.IS_COMBINED); // wide char cell.setFromCharData([123, '1', 2, '1'.charCodeAt(0)]); chai.assert.deepEqual(cell.asCharData, [123, '1', 2, '1'.charCodeAt(0)]); - chai.assert.equal(cell.combined, 0); + chai.assert.equal(cell.isCombined, 0); }); }); @@ -331,39 +331,39 @@ describe('BufferLine', function(): void { describe('addCharToCell', () => { it('should set width to 1 for empty cell', () => { const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.addCharToCell(0, '\u0301'.charCodeAt(0)); + line.addCodepointToCell(0, '\u0301'.charCodeAt(0)); const cell = line.loadCell(0, new CellData()); // chars contains single combining char // width is set to 1 chai.assert.deepEqual(cell.asCharData, [DEFAULT_ATTR, '\u0301', 1, 0x0301]); // do not account a single combining char as combined - chai.assert.equal(cell.combined, 0); + chai.assert.equal(cell.isCombined, 0); }); it('should add char to combining string in cell', () => { const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); const cell = line .loadCell(0, new CellData()); cell.setFromCharData([123, 'e\u0301', 1, 'e\u0301'.charCodeAt(1)]); line.setCell(0, cell); - line.addCharToCell(0, '\u0301'.charCodeAt(0)); + line.addCodepointToCell(0, '\u0301'.charCodeAt(0)); line.loadCell(0, cell); // chars contains 3 chars // width is set to 1 chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301\u0301', 1, 0x0301]); // do not account a single combining char as combined - chai.assert.equal(cell.combined, Content.IS_COMBINED); + chai.assert.equal(cell.isCombined, Content.IS_COMBINED); }); it('should create combining string on taken cell', () => { const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); const cell = line .loadCell(0, new CellData()); cell.setFromCharData([123, 'e', 1, 'e'.charCodeAt(1)]); line.setCell(0, cell); - line.addCharToCell(0, '\u0301'.charCodeAt(0)); + line.addCodepointToCell(0, '\u0301'.charCodeAt(0)); line.loadCell(0, cell); // chars contains 2 chars // width is set to 1 chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301', 1, 0x0301]); // do not account a single combining char as combined - chai.assert.equal(cell.combined, Content.IS_COMBINED); + chai.assert.equal(cell.isCombined, Content.IS_COMBINED); }); }); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 84a7bfcde2..120a15fc11 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -95,7 +95,7 @@ export class CellData implements ICellData { public combinedData: string = ''; /** Whether cell contains a combined string. */ - public get combined(): number { + public get isCombined(): number { return this.content & Content.IS_COMBINED; } @@ -122,7 +122,7 @@ export class CellData implements ICellData { * of the last char in string to be in line with code in CharData. * */ public get code(): number { - return (this.combined) + return (this.isCombined) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK; } @@ -168,6 +168,18 @@ export class CellData implements ICellData { /** * Typed array based bufferline implementation. + * + * There are 2 ways to insert data into the cell buffer: + * - `setCellFromCodepoint` + `addCodepointToCell` + * Use these for data that is already UTF32. + * Used during normal input in `InputHandler` for faster buffer access. + * - `setCell` + * This method takes a CellData object and stores the data in the buffer. + * Use `CellData.fromCharData` to create the CellData object (e.g. from JS string). + * + * To retrieve data from the buffer use either one of the primitive methods + * (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop + * memory allocs / GC pressure can be greatly reduced by reusing the CellData object. */ export class BufferLine implements IBufferLine { protected _data: Uint32Array | null = null; @@ -311,19 +323,19 @@ export class BufferLine implements IBufferLine { * Since the input handler see the incoming chars as UTF32 codepoints, * it gets an optimized access method. */ - public setDataFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void { + public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void { 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; } /** - * Add a char to a cell from input handler. + * Add a codepoint to a cell from input handler. * During input stage combining chars with a width of 0 follow and stack * onto a leading char. Since we already set the attrs * by the previous `setDataFromCodePoint` call, we can omit it here. */ - public addCharToCell(index: number, codePoint: number): void { + public addCodepointToCell(index: number, codePoint: number): void { let content = this._data[index * CELL_SIZE + Cell.CONTENT]; if (content & Content.IS_COMBINED) { // we already have a combined string, simply add @@ -332,11 +344,10 @@ export class BufferLine implements IBufferLine { if (content & Content.CODEPOINT_MASK) { // normal case for combining chars: // - move current leading char + new one into combined string - // - set codepoint in cell buffer to index // - set combined flag this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint); - content &= ~Content.CODEPOINT_MASK; - content |= index | Content.IS_COMBINED; + content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0 + content |= Content.IS_COMBINED; } 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 diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 86483a2901..a9555c7617 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -355,9 +355,9 @@ export class InputHandler extends Disposable implements IInputHandler { // found empty cell after fullwidth, need to go 2 cells back // it is save to step 2 cells back here // since an empty cell is only set by fullwidth chars - bufferRow.addCharToCell(buffer.x - 2, code); + bufferRow.addCodepointToCell(buffer.x - 2, code); } else { - bufferRow.addCharToCell(buffer.x - 1, code); + bufferRow.addCodepointToCell(buffer.x - 1, code); } continue; } @@ -401,12 +401,12 @@ export class InputHandler extends Disposable implements IInputHandler { // a halfwidth char any fullwidth shifted there is lost // and will be set to empty cell if (bufferRow.loadCell(cols - 1, this._cell).width === 2) { - bufferRow.setDataFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr, 0); + bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr, 0); } } // write current char to buffer and advance cursor - bufferRow.setDataFromCodePoint(buffer.x++, code, chWidth, curAttr, 0); + bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr, 0); // fullwidth char - also set next cell to placeholder stub and advance cursor // for graphemes bigger than fullwidth we can simply loop to zero @@ -414,7 +414,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (chWidth > 0) { while (--chWidth) { // other than a regular empty cell a cell following a wide char has no width - bufferRow.setDataFromCodePoint(buffer.x++, 0, 0, curAttr, 0); + bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr, 0); } } } diff --git a/src/Types.ts b/src/Types.ts index dad86fa21b..9588505c23 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -527,7 +527,7 @@ export interface ICellData { fg: number; bg: number; combinedData: string; - combined: number; + isCombined: number; width: number; chars: string; code: number; @@ -545,8 +545,8 @@ export interface IBufferLine { set(index: number, value: CharData): void; loadCell(index: number, cell: ICellData): ICellData; setCell(index: number, cell: ICellData): void; - setDataFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; - addCharToCell(index: number, codePoint: number): void; + setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; + addCodepointToCell(index: number, codePoint: number): void; insertCells(pos: number, n: number, ch: ICellData): void; deleteCells(pos: number, n: number, fill: ICellData): void; replaceCells(start: number, end: number, fill: ICellData): void; From 079729f1b43a1c85c6afe03cc18e7e20dd6ad60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 1 Feb 2019 01:10:20 +0100 Subject: [PATCH 29/30] change property getter into methods --- src/Buffer.test.ts | 18 +- src/BufferLine.test.ts | 40 +-- src/BufferLine.ts | 14 +- src/CharWidth.test.ts | 2 +- src/InputHandler.ts | 4 +- src/SelectionManager.ts | 16 +- src/Terminal.integration.ts | 2 +- src/Terminal.test.ts | 298 +++++++++++----------- src/Types.ts | 10 +- src/renderer/BaseRenderLayer.ts | 2 +- src/renderer/CharacterJoinerRegistry.ts | 4 +- src/renderer/CursorRenderLayer.ts | 10 +- src/renderer/TextRenderLayer.ts | 8 +- src/renderer/dom/DomRendererRowFactory.ts | 6 +- 14 files changed, 217 insertions(+), 217 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 57de302af4..59475adb72 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -37,13 +37,13 @@ describe('Buffer', () => { describe('fillViewportRows', () => { it('should fill the buffer with blank lines based on the size of the viewport', () => { - const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR).loadCell(0, new CellData()).asCharData; + const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR).loadCell(0, new CellData()).getAsCharData; buffer.fillViewportRows(); assert.equal(buffer.lines.length, INIT_ROWS); for (let y = 0; y < INIT_ROWS; y++) { assert.equal(buffer.lines.get(y).length, INIT_COLS); for (let x = 0; x < INIT_COLS; x++) { - assert.deepEqual(buffer.lines.get(y).loadCell(x, new CellData()).asCharData, blankLineChar); + assert.deepEqual(buffer.lines.get(y).loadCell(x, new CellData()).getAsCharData, blankLineChar); } } }); @@ -155,15 +155,15 @@ describe('Buffer', () => { assert.equal(buffer.lines.maxLength, INIT_ROWS); buffer.y = INIT_ROWS - 1; buffer.fillViewportRows(); - let chData = buffer.lines.get(5).loadCell(0, new CellData()).asCharData; + let chData = buffer.lines.get(5).loadCell(0, new CellData()).getAsCharData(); chData[1] = 'a'; buffer.lines.get(5).setCell(0, CellData.fromCharData(chData)); - chData = buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).asCharData; + chData = buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getAsCharData(); chData[1] = 'b'; buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData(chData)); buffer.resize(INIT_COLS, INIT_ROWS - 5); - assert.equal(buffer.lines.get(0).loadCell(0, new CellData()).asCharData[1], 'a'); - assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).loadCell(0, new CellData()).asCharData[1], 'b'); + assert.equal(buffer.lines.get(0).loadCell(0, new CellData()).getAsCharData()[1], 'a'); + assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).loadCell(0, new CellData()).getAsCharData()[1], 'b'); }); }); }); @@ -1264,7 +1264,7 @@ describe('Buffer', () => { assert.equal(input, s); const stringIndex = s.match(/😃/).index; const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex); - assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).chars, '😃'); + assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars(), '😃'); }); it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => { @@ -1291,7 +1291,7 @@ describe('Buffer', () => { assert.equal(input, s); for (let i = 0; i < input.length; ++i) { const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); - assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).chars); + assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars()); } }); @@ -1309,7 +1309,7 @@ describe('Buffer', () => { : (i % 3 === 1) ? input.substr(i, 2) : input.substr(i - 1, 2), - terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).chars); + terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars()); } }); diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 894cc204f6..7dfcbd2c10 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -16,7 +16,7 @@ class TestBufferLine extends BufferLine { public toArray(): CharData[] { const result = []; for (let i = 0; i < this.length; ++i) { - result.push(this.loadCell(i, new CellData()).asCharData); + result.push(this.loadCell(i, new CellData()).getAsCharData()); } return result; } @@ -27,24 +27,24 @@ describe('CellData', () => { const cell = new CellData(); // ASCII cell.setFromCharData([123, 'a', 1, 'a'.charCodeAt(0)]); - chai.assert.deepEqual(cell.asCharData, [123, 'a', 1, 'a'.charCodeAt(0)]); - chai.assert.equal(cell.isCombined, 0); + chai.assert.deepEqual(cell.getAsCharData(), [123, 'a', 1, 'a'.charCodeAt(0)]); + chai.assert.equal(cell.isCombined(), 0); // combining cell.setFromCharData([123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); - chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); - chai.assert.equal(cell.isCombined, Content.IS_COMBINED); + chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + chai.assert.equal(cell.isCombined(), Content.IS_COMBINED); // surrogate cell.setFromCharData([123, '𝄞', 1, 0x1D11E]); - chai.assert.deepEqual(cell.asCharData, [123, '𝄞', 1, 0x1D11E]); - chai.assert.equal(cell.isCombined, 0); + chai.assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]); + chai.assert.equal(cell.isCombined(), 0); // surrogate + combining cell.setFromCharData([123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); - chai.assert.deepEqual(cell.asCharData, [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); - chai.assert.equal(cell.isCombined, Content.IS_COMBINED); + chai.assert.deepEqual(cell.getAsCharData(), [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); + chai.assert.equal(cell.isCombined(), Content.IS_COMBINED); // wide char cell.setFromCharData([123, '1', 2, '1'.charCodeAt(0)]); - chai.assert.deepEqual(cell.asCharData, [123, '1', 2, '1'.charCodeAt(0)]); - chai.assert.equal(cell.isCombined, 0); + chai.assert.deepEqual(cell.getAsCharData(), [123, '1', 2, '1'.charCodeAt(0)]); + chai.assert.equal(cell.isCombined(), 0); }); }); @@ -55,15 +55,15 @@ describe('BufferLine', function(): void { chai.expect(line.isWrapped).equals(false); line = new TestBufferLine(10); chai.expect(line.length).equals(10); - chai.expect(line.loadCell(0, new CellData()).asCharData).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); chai.expect(line.isWrapped).equals(false); line = new TestBufferLine(10, null, true); chai.expect(line.length).equals(10); - chai.expect(line.loadCell(0, new CellData()).asCharData).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); chai.expect(line.isWrapped).equals(true); line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true); chai.expect(line.length).equals(10); - chai.expect(line.loadCell(0, new CellData()).asCharData).eql([123, 'a', 456, 'a'.charCodeAt(0)]); + chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([123, 'a', 456, 'a'.charCodeAt(0)]); chai.expect(line.isWrapped).equals(true); }); it('insertCells', function(): void { @@ -335,9 +335,9 @@ describe('BufferLine', function(): void { const cell = line.loadCell(0, new CellData()); // chars contains single combining char // width is set to 1 - chai.assert.deepEqual(cell.asCharData, [DEFAULT_ATTR, '\u0301', 1, 0x0301]); + chai.assert.deepEqual(cell.getAsCharData(), [DEFAULT_ATTR, '\u0301', 1, 0x0301]); // do not account a single combining char as combined - chai.assert.equal(cell.isCombined, 0); + chai.assert.equal(cell.isCombined(), 0); }); it('should add char to combining string in cell', () => { const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); @@ -348,9 +348,9 @@ describe('BufferLine', function(): void { line.loadCell(0, cell); // chars contains 3 chars // width is set to 1 - chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301\u0301', 1, 0x0301]); + chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301\u0301', 1, 0x0301]); // do not account a single combining char as combined - chai.assert.equal(cell.isCombined, Content.IS_COMBINED); + chai.assert.equal(cell.isCombined(), Content.IS_COMBINED); }); it('should create combining string on taken cell', () => { const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); @@ -361,9 +361,9 @@ describe('BufferLine', function(): void { line.loadCell(0, cell); // chars contains 2 chars // width is set to 1 - chai.assert.deepEqual(cell.asCharData, [123, 'e\u0301', 1, 0x0301]); + chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, 0x0301]); // do not account a single combining char as combined - chai.assert.equal(cell.isCombined, Content.IS_COMBINED); + chai.assert.equal(cell.isCombined(), Content.IS_COMBINED); }); }); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 120a15fc11..1451845471 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -95,17 +95,17 @@ export class CellData implements ICellData { public combinedData: string = ''; /** Whether cell contains a combined string. */ - public get isCombined(): number { + public isCombined(): number { return this.content & Content.IS_COMBINED; } /** Width of the cell. */ - public get width(): number { + public getWidth(): number { return this.content >> Content.WIDTH_SHIFT; } /** JS string of the content. */ - public get chars(): string { + public getChars(): string { if (this.content & Content.IS_COMBINED) { return this.combinedData; } @@ -121,8 +121,8 @@ export class CellData implements ICellData { * if content is a combined string it returns the codepoint * of the last char in string to be in line with code in CharData. * */ - public get code(): number { - return (this.isCombined) + public getCode(): number { + return (this.isCombined()) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK; } @@ -160,8 +160,8 @@ export class CellData implements ICellData { } /** Get data as CharData. */ - public get asCharData(): CharData { - return [this.fg, this.chars, this.width, this.code]; + public getAsCharData(): CharData { + return [this.fg, this.getChars(), this.getWidth(), this.getCode()]; } } diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts index 7cab38829a..8608c6fa3d 100644 --- a/src/CharWidth.test.ts +++ b/src/CharWidth.test.ts @@ -23,7 +23,7 @@ describe('getStringCellWidth', function(): void { for (let i = start; i < end; ++i) { const line = buffer.lines.get(i); for (let j = 0; j < line.length; ++j) { // TODO: change to trimBorder with multiline - const ch = line.loadCell(j, new CellData()).asCharData; + const ch = line.loadCell(j, new CellData()).getAsCharData(); result += ch[CHAR_DATA_WIDTH_INDEX]; // return on sentinel if (ch[CHAR_DATA_CHAR_INDEX] === sentinel) { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a9555c7617..ccd74a2f41 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -351,7 +351,7 @@ export class InputHandler extends Disposable implements IInputHandler { // since they always follow a cell consuming char // therefore we can test for buffer.x to avoid overflow left if (!chWidth && buffer.x) { - if (!bufferRow.loadCell(buffer.x - 1, this._cell).width) { + if (!bufferRow.loadCell(buffer.x - 1, this._cell).getWidth()) { // found empty cell after fullwidth, need to go 2 cells back // it is save to step 2 cells back here // since an empty cell is only set by fullwidth chars @@ -400,7 +400,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test last cell - since the last cell has only room for // a halfwidth char any fullwidth shifted there is lost // and will be set to empty cell - if (bufferRow.loadCell(cols - 1, this._cell).width === 2) { + if (bufferRow.loadCell(cols - 1, this._cell).getWidth() === 2) { bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr, 0); } } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index e2399173c5..9d49a860a8 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -669,8 +669,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number { let charIndex = coords[0]; for (let i = 0; coords[0] >= i; i++) { - const length = bufferLine.loadCell(i, this._cell).chars.length; - if (this._cell.width === 0) { + const length = bufferLine.loadCell(i, this._cell).getChars().length; + if (this._cell.getWidth() === 0) { // Wide characters aren't included in the line string so decrement the // index so the index is back on the wide character. charIndex--; @@ -757,8 +757,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Expand the string in both directions until a space is hit while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._cell))) { bufferLine.loadCell(startCol - 1, this._cell); - const length = this._cell.chars.length; - if (this._cell.width === 0) { + const length = this._cell.getChars().length; + if (this._cell.getWidth() === 0) { // If the next character is a wide char, record it and skip the column leftWideCharCount++; startCol--; @@ -773,8 +773,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager } while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._cell))) { bufferLine.loadCell(endCol + 1, this._cell); - const length = this._cell.chars.length; - if (this._cell.width === 2) { + const length = this._cell.getChars().length; + if (this._cell.getWidth() === 2) { // If the next character is a wide char, record it and skip the column rightWideCharCount++; endCol++; @@ -899,10 +899,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _isCharWordSeparator(cell: CellData): boolean { // Zero width characters are never separators as they are always to the // right of wide characters - if (cell.width === 0) { + if (cell.getWidth() === 0) { return false; } - return WORD_SEPARATORS.indexOf(cell.chars) >= 0; + return WORD_SEPARATORS.indexOf(cell.getChars()) >= 0; } /** diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index b5165490cc..100430066c 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -68,7 +68,7 @@ function terminalToString(term: Terminal): string { for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) { lineText = ''; for (let cell = 0; cell < term.cols; ++cell) { - lineText += term.buffer.lines.get(line).loadCell(cell, new CellData()).chars || WHITESPACE_CELL_CHAR; + lineText += term.buffer.lines.get(line).loadCell(cell, new CellData()).getChars() || WHITESPACE_CELL_CHAR; } // rtrim empty cells as xterm does lineText = lineText.replace(/\s+$/, ''); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index e06ceb78cd..08bceb344d 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -461,9 +461,9 @@ describe('term.js addons', () => { term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).chars, 'b'); - assert.equal(term.buffer.lines.get(INIT_ROWS).loadCell(0, new CellData()).chars, ''); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), 'b'); + assert.equal(term.buffer.lines.get(INIT_ROWS).loadCell(0, new CellData()).getChars(), ''); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { @@ -474,8 +474,8 @@ describe('term.js addons', () => { term.buffer.scrollTop = 1; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { @@ -488,12 +488,12 @@ describe('term.js addons', () => { term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a', '\'a\' should be pushed to the scrollback'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'b'); - assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).chars, 'c'); - assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).chars, 'd'); - assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).chars, '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(5).loadCell(0, new CellData()).chars, 'e'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'b'); + assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'c'); + assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), 'd'); + assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(5).loadCell(0, new CellData()).getChars(), 'e'); }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { @@ -507,11 +507,11 @@ describe('term.js addons', () => { term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c', '\'b\' should be removed from the buffer'); - assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).chars, 'd'); - assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).chars, '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).chars, 'e'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer'); + assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd'); + assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e'); }); }); @@ -530,10 +530,10 @@ describe('term.js addons', () => { term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); // 'a' gets pushed out of buffer - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'b'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, ''); - assert.equal(term.buffer.lines.get(INIT_ROWS - 2).loadCell(0, new CellData()).chars, 'c'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).chars, ''); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), ''); + assert.equal(term.buffer.lines.get(INIT_ROWS - 2).loadCell(0, new CellData()).getChars(), 'c'); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), ''); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { @@ -544,8 +544,8 @@ describe('term.js addons', () => { term.buffer.scrollTop = 1; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { @@ -558,11 +558,11 @@ describe('term.js addons', () => { term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'b'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c'); - assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).chars, 'd'); - assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).chars, '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).chars, 'e'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); + assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd'); + assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e'); }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { @@ -576,11 +576,11 @@ describe('term.js addons', () => { term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).chars, 'a'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).chars, 'c', '\'b\' should be removed from the buffer'); - assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).chars, 'd'); - assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).chars, '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).chars, 'e'); + assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer'); + assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd'); + assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e'); }); }); }); @@ -775,10 +775,10 @@ describe('term.js addons', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.write(high + String.fromCharCode(i)); const tchar = term.buffer.lines.get(0).loadCell(0, cell); - expect(tchar.chars).eql(high + String.fromCharCode(i)); - expect(tchar.chars.length).eql(2); - expect(tchar.width).eql(1); - expect(term.buffer.lines.get(0).loadCell(1, cell).chars).eql(''); + expect(tchar.getChars()).eql(high + String.fromCharCode(i)); + expect(tchar.getChars().length).eql(2); + expect(tchar.getWidth()).eql(1); + expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql(''); term.reset(); } }); @@ -788,9 +788,9 @@ describe('term.js addons', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.write(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).chars).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).chars.length).eql(2); - expect(term.buffer.lines.get(1).loadCell(0, cell).chars).eql(''); + expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars().length).eql(2); + expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(''); term.reset(); } }); @@ -801,10 +801,10 @@ describe('term.js addons', () => { term.buffer.x = term.cols - 1; term.wraparoundMode = true; term.write('a' + high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).chars).eql('a'); - expect(term.buffer.lines.get(1).loadCell(0, cell).chars).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(1).loadCell(0, cell).chars.length).eql(2); - expect(term.buffer.lines.get(1).loadCell(1, cell).chars).eql(''); + expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a'); + expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(1).loadCell(0, cell).getChars().length).eql(2); + expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql(''); term.reset(); } }); @@ -816,9 +816,9 @@ describe('term.js addons', () => { term.wraparoundMode = false; term.write('a' + high + String.fromCharCode(i)); // auto wraparound mode should cut off the rest of the line - expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).chars).eql('a'); - expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).chars.length).eql(1); - expect(term.buffer.lines.get(1).loadCell(1, cell).chars).eql(''); + expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a'); + expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(1); + expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql(''); term.reset(); } }); @@ -829,10 +829,10 @@ describe('term.js addons', () => { term.write(high); term.write(String.fromCharCode(i)); const tchar = term.buffer.lines.get(0).loadCell(0, cell); - expect(tchar.chars).eql(high + String.fromCharCode(i)); - expect(tchar.chars.length).eql(2); - expect(tchar.width).eql(1); - expect(term.buffer.lines.get(0).loadCell(1, cell).chars).eql(''); + expect(tchar.getChars()).eql(high + String.fromCharCode(i)); + expect(tchar.getChars().length).eql(2); + expect(tchar.getWidth()).eql(1); + expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql(''); term.reset(); } }); @@ -843,49 +843,49 @@ describe('term.js addons', () => { it('café', () => { term.write('cafe\u0301'); term.buffer.lines.get(0).loadCell(3, cell); - expect(cell.chars).eql('e\u0301'); - expect(cell.chars.length).eql(2); - expect(cell.width).eql(1); + expect(cell.getChars()).eql('e\u0301'); + expect(cell.getChars().length).eql(2); + expect(cell.getWidth()).eql(1); }); it('café - end of line', () => { term.buffer.x = term.cols - 1 - 3; term.write('cafe\u0301'); term.buffer.lines.get(0).loadCell(term.cols - 1, cell); - expect(cell.chars).eql('e\u0301'); - expect(cell.chars.length).eql(2); - expect(cell.width).eql(1); + expect(cell.getChars()).eql('e\u0301'); + expect(cell.getChars().length).eql(2); + expect(cell.getWidth()).eql(1); term.buffer.lines.get(0).loadCell(1, cell); - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(1); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(1); }); it('multiple combined é', () => { term.wraparoundMode = true; term.write(Array(100).join('e\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); - expect(cell.chars).eql('e\u0301'); - expect(cell.chars.length).eql(2); - expect(cell.width).eql(1); + expect(cell.getChars()).eql('e\u0301'); + expect(cell.getChars().length).eql(2); + expect(cell.getWidth()).eql(1); } term.buffer.lines.get(1).loadCell(0, cell); - expect(cell.chars).eql('e\u0301'); - expect(cell.chars.length).eql(2); - expect(cell.width).eql(1); + expect(cell.getChars()).eql('e\u0301'); + expect(cell.getChars().length).eql(2); + expect(cell.getWidth()).eql(1); }); it('multiple surrogate with combined', () => { term.wraparoundMode = true; term.write(Array(100).join('\uD800\uDC00\u0301')); for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); - expect(cell.chars).eql('\uD800\uDC00\u0301'); - expect(cell.chars.length).eql(3); - expect(cell.width).eql(1); + expect(cell.getChars()).eql('\uD800\uDC00\u0301'); + expect(cell.getChars().length).eql(3); + expect(cell.getWidth()).eql(1); } term.buffer.lines.get(1).loadCell(0, cell); - expect(cell.chars).eql('\uD800\uDC00\u0301'); - expect(cell.chars.length).eql(3); - expect(cell.width).eql(1); + expect(cell.getChars()).eql('\uD800\uDC00\u0301'); + expect(cell.getChars().length).eql(3); + expect(cell.getWidth()).eql(1); }); }); @@ -908,19 +908,19 @@ describe('term.js addons', () => { for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); if (i % 2) { - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(0); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(0); } else { - expect(cell.chars).eql('¥'); - expect(cell.chars.length).eql(1); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('¥'); + expect(cell.getChars().length).eql(1); + expect(cell.getWidth()).eql(2); } } term.buffer.lines.get(1).loadCell(0, cell); - expect(cell.chars).eql('¥'); - expect(cell.chars.length).eql(1); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('¥'); + expect(cell.getChars().length).eql(1); + expect(cell.getWidth()).eql(2); }); it('line of ¥ odd', () => { term.wraparoundMode = true; @@ -929,23 +929,23 @@ describe('term.js addons', () => { for (let i = 1; i < term.cols - 1; ++i) { term.buffer.lines.get(0).loadCell(i, cell); if (!(i % 2)) { - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(0); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(0); } else { - expect(cell.chars).eql('¥'); - expect(cell.chars.length).eql(1); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('¥'); + expect(cell.getChars().length).eql(1); + expect(cell.getWidth()).eql(2); } } term.buffer.lines.get(0).loadCell(term.cols - 1, cell); - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(1); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(1); term.buffer.lines.get(1).loadCell(0, cell); - expect(cell.chars).eql('¥'); - expect(cell.chars.length).eql(1); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('¥'); + expect(cell.getChars().length).eql(1); + expect(cell.getWidth()).eql(2); }); it('line of ¥ with combining odd', () => { term.wraparoundMode = true; @@ -954,23 +954,23 @@ describe('term.js addons', () => { for (let i = 1; i < term.cols - 1; ++i) { term.buffer.lines.get(0).loadCell(i, cell); if (!(i % 2)) { - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(0); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(0); } else { - expect(cell.chars).eql('¥\u0301'); - expect(cell.chars.length).eql(2); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('¥\u0301'); + expect(cell.getChars().length).eql(2); + expect(cell.getWidth()).eql(2); } } term.buffer.lines.get(0).loadCell(term.cols - 1, cell); - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(1); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(1); term.buffer.lines.get(1).loadCell(0, cell); - expect(cell.chars).eql('¥\u0301'); - expect(cell.chars.length).eql(2); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('¥\u0301'); + expect(cell.getChars().length).eql(2); + expect(cell.getWidth()).eql(2); }); it('line of ¥ with combining even', () => { term.wraparoundMode = true; @@ -978,19 +978,19 @@ describe('term.js addons', () => { for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); if (i % 2) { - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(0); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(0); } else { - expect(cell.chars).eql('¥\u0301'); - expect(cell.chars.length).eql(2); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('¥\u0301'); + expect(cell.getChars().length).eql(2); + expect(cell.getWidth()).eql(2); } } term.buffer.lines.get(1).loadCell(0, cell); - expect(cell.chars).eql('¥\u0301'); - expect(cell.chars.length).eql(2); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('¥\u0301'); + expect(cell.getChars().length).eql(2); + expect(cell.getWidth()).eql(2); }); it('line of surrogate fullwidth with combining odd', () => { term.wraparoundMode = true; @@ -999,23 +999,23 @@ describe('term.js addons', () => { for (let i = 1; i < term.cols - 1; ++i) { term.buffer.lines.get(0).loadCell(i, cell); if (!(i % 2)) { - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(0); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(0); } else { - expect(cell.chars).eql('\ud843\ude6d\u0301'); - expect(cell.chars.length).eql(3); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('\ud843\ude6d\u0301'); + expect(cell.getChars().length).eql(3); + expect(cell.getWidth()).eql(2); } } term.buffer.lines.get(0).loadCell(term.cols - 1, cell); - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(1); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(1); term.buffer.lines.get(1).loadCell(0, cell); - expect(cell.chars).eql('\ud843\ude6d\u0301'); - expect(cell.chars.length).eql(3); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('\ud843\ude6d\u0301'); + expect(cell.getChars().length).eql(3); + expect(cell.getWidth()).eql(2); }); it('line of surrogate fullwidth with combining even', () => { term.wraparoundMode = true; @@ -1023,19 +1023,19 @@ describe('term.js addons', () => { for (let i = 0; i < term.cols; ++i) { term.buffer.lines.get(0).loadCell(i, cell); if (i % 2) { - expect(cell.chars).eql(''); - expect(cell.chars.length).eql(0); - expect(cell.width).eql(0); + expect(cell.getChars()).eql(''); + expect(cell.getChars().length).eql(0); + expect(cell.getWidth()).eql(0); } else { - expect(cell.chars).eql('\ud843\ude6d\u0301'); - expect(cell.chars.length).eql(3); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('\ud843\ude6d\u0301'); + expect(cell.getChars().length).eql(3); + expect(cell.getWidth()).eql(2); } } term.buffer.lines.get(1).loadCell(0, cell); - expect(cell.chars).eql('\ud843\ude6d\u0301'); - expect(cell.chars.length).eql(3); - expect(cell.width).eql(2); + expect(cell.getChars()).eql('\ud843\ude6d\u0301'); + expect(cell.getChars().length).eql(3); + expect(cell.getWidth()).eql(2); }); }); @@ -1048,10 +1048,10 @@ describe('term.js addons', () => { term.insertMode = true; term.write('abcde'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).loadCell(10, cell).chars).eql('a'); - expect(term.buffer.lines.get(0).loadCell(14, cell).chars).eql('e'); - expect(term.buffer.lines.get(0).loadCell(15, cell).chars).eql('0'); - expect(term.buffer.lines.get(0).loadCell(79, cell).chars).eql('4'); + expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); + expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('e'); + expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql('0'); + expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('4'); }); it('fullwidth - insert', () => { term.write(Array(9).join('0123456789').slice(-80)); @@ -1060,11 +1060,11 @@ describe('term.js addons', () => { term.insertMode = true; term.write('¥¥¥'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).loadCell(10, cell).chars).eql('¥'); - expect(term.buffer.lines.get(0).loadCell(11, cell).chars).eql(''); - expect(term.buffer.lines.get(0).loadCell(14, cell).chars).eql('¥'); - expect(term.buffer.lines.get(0).loadCell(15, cell).chars).eql(''); - expect(term.buffer.lines.get(0).loadCell(79, cell).chars).eql('3'); + expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥'); + expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('¥'); + expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('3'); }); it('fullwidth - right border', () => { term.write(Array(41).join('¥')); @@ -1073,14 +1073,14 @@ describe('term.js addons', () => { term.insertMode = true; term.write('a'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).loadCell(10, cell).chars).eql('a'); - expect(term.buffer.lines.get(0).loadCell(11, cell).chars).eql('¥'); - expect(term.buffer.lines.get(0).loadCell(79, cell).chars).eql(''); // fullwidth char got replaced + expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); + expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('¥'); + expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced term.write('b'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).loadCell(11, cell).chars).eql('b'); - expect(term.buffer.lines.get(0).loadCell(12, cell).chars).eql('¥'); - expect(term.buffer.lines.get(0).loadCell(79, cell).chars).eql(''); // empty cell after fullwidth + expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('b'); + expect(term.buffer.lines.get(0).loadCell(12, cell).getChars()).eql('¥'); + expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth }); }); }); diff --git a/src/Types.ts b/src/Types.ts index 9588505c23..a08bd48530 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -527,12 +527,12 @@ export interface ICellData { fg: number; bg: number; combinedData: string; - isCombined: number; - width: number; - chars: string; - code: number; + isCombined(): number; + getWidth(): number; + getChars(): string; + getCode(): number; setFromCharData(value: CharData): void; - asCharData: CharData; + getAsCharData(): CharData; } /** diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index f84fbe6bcc..addd028cec 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -239,7 +239,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.textBaseline = 'middle'; this._clipRow(terminal, y); this._ctx.fillText( - cell.chars, + cell.getChars(), x * this._scaledCellWidth + this._scaledCharLeft, (y + 0.5) * this._scaledCellHeight + this._scaledCharTop); } diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 4cad7c72c9..eb44bb58db 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -56,8 +56,8 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { for (let x = 0; x < this._terminal.cols; x++) { line.loadCell(x, this._cell); - const chars = this._cell.chars; - const width = this._cell.width; + const chars = this._cell.getChars(); + const width = this._cell.getWidth(); const attr = this._cell.fg >> 9; if (width === 0) { diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 18ba7ada5c..c3b751fa99 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -143,7 +143,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._state.y = viewportRelativeCursorY; this._state.isFocused = false; this._state.style = terminal.options.cursorStyle; - this._state.width = this._cell.width; + this._state.width = this._cell.getWidth(); return; } @@ -159,7 +159,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._state.y === viewportRelativeCursorY && this._state.isFocused === terminal.isFocused && this._state.style === terminal.options.cursorStyle && - this._state.width === this._cell.width) { + this._state.width === this._cell.getWidth()) { return; } this._clearCursor(); @@ -173,7 +173,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._state.y = viewportRelativeCursorY; this._state.isFocused = false; this._state.style = terminal.options.cursorStyle; - this._state.width = this._cell.width; + this._state.width = this._cell.getWidth(); } private _clearCursor(): void { @@ -199,7 +199,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBlockCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this.fillCells(x, y, cell.width, 1); + this.fillCells(x, y, cell.getWidth(), 1); this._ctx.fillStyle = this._colors.cursorAccent.css; this.fillCharTrueColor(terminal, cell, x, y); this._ctx.restore(); @@ -215,7 +215,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBlurCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.strokeStyle = this._colors.cursor.css; - this.strokeRectAtCell(x, y, cell.width, 1); + this.strokeRectAtCell(x, y, cell.getWidth(), 1); this._ctx.restore(); } } diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index bf7c5616d5..be022239a3 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -75,13 +75,13 @@ export class TextRenderLayer extends BaseRenderLayer { const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; for (let x = 0; x < terminal.cols; x++) { (line as any).loadCell(x, this._cell); - let code: number = this._cell.code || WHITESPACE_CELL_CODE; + let code: number = this._cell.getCode() || WHITESPACE_CELL_CODE; // Can either represent character(s) for a single cell or multiple cells // if indicated by a character joiner. - let chars = this._cell.chars || WHITESPACE_CELL_CHAR; + let chars = this._cell.getChars() || WHITESPACE_CELL_CHAR; const attr = this._cell.fg; - let width = this._cell.width; + let width = this._cell.getWidth(); // If true, indicates that the current character(s) to draw were joined. let isJoined = false; @@ -127,7 +127,7 @@ export class TextRenderLayer extends BaseRenderLayer { // get removed, and `a` would not re-render because it thinks it's // already in the correct state. // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; - if (lastCharX < line.length - 1 && line.loadCell(lastCharX + 1, this._cell).code === NULL_CELL_CODE) { + if (lastCharX < line.length - 1 && line.loadCell(lastCharX + 1, this._cell).getCode() === NULL_CELL_CODE) { width = 2; // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 83a4651e25..5bc5fffa57 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -33,7 +33,7 @@ export class DomRendererRowFactory { // the viewport). let lineLength = 0; for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) { - if (lineData.loadCell(x, this._cell).code !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { + if (lineData.loadCell(x, this._cell).getCode() !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { lineLength = x + 1; break; } @@ -42,7 +42,7 @@ export class DomRendererRowFactory { for (let x = 0; x < lineLength; x++) { lineData.loadCell(x, this._cell); const attr = this._cell.fg; - const width = this._cell.width; + const width = this._cell.getWidth(); // The character to the left is a wide character, drawing is owned by the char at x-1 if (width === 0) { @@ -100,7 +100,7 @@ export class DomRendererRowFactory { charElement.classList.add(ITALIC_CLASS); } - charElement.textContent = this._cell.chars || WHITESPACE_CELL_CHAR; + charElement.textContent = this._cell.getChars() || WHITESPACE_CELL_CHAR; if (fg !== DEFAULT_COLOR) { charElement.classList.add(`xterm-fg-${fg}`); } From 5c8c680dac3717e927069699168272ebd23ce54e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 1 Apr 2019 00:01:41 -0700 Subject: [PATCH 30/30] Fix feedback --- src/Buffer.ts | 2 +- src/BufferLine.test.ts | 10 +-- src/BufferLine.ts | 101 +++++++++++----------- src/InputHandler.ts | 10 +-- src/Linkifier.ts | 2 +- src/SelectionManager.ts | 22 ++--- src/Terminal.ts | 2 +- src/Types.ts | 4 +- src/renderer/CharacterJoinerRegistry.ts | 12 +-- src/renderer/TextRenderLayer.ts | 14 +-- src/renderer/dom/DomRendererRowFactory.ts | 13 +-- 11 files changed, 98 insertions(+), 94 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 69a36e439f..9cc1adba88 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -29,7 +29,7 @@ export const NULL_CELL_WIDTH = 1; export const NULL_CELL_CODE = 0; /** - * Whilespace cell. + * Whitespace cell. * This is meant as a replacement for empty cells when needed * during rendering lines to preserve correct aligment. */ diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 7dfcbd2c10..29a783aecf 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import * as chai from 'chai'; -import { BufferLine, CellData, Content } from './BufferLine'; +import { BufferLine, CellData, ContentMasks } from './BufferLine'; import { CharData, IBufferLine } from './Types'; import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer'; @@ -32,7 +32,7 @@ describe('CellData', () => { // combining cell.setFromCharData([123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); - chai.assert.equal(cell.isCombined(), Content.IS_COMBINED); + chai.assert.equal(cell.isCombined(), ContentMasks.IS_COMBINED); // surrogate cell.setFromCharData([123, '𝄞', 1, 0x1D11E]); chai.assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]); @@ -40,7 +40,7 @@ describe('CellData', () => { // surrogate + combining cell.setFromCharData([123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); chai.assert.deepEqual(cell.getAsCharData(), [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]); - chai.assert.equal(cell.isCombined(), Content.IS_COMBINED); + chai.assert.equal(cell.isCombined(), ContentMasks.IS_COMBINED); // wide char cell.setFromCharData([123, '1', 2, '1'.charCodeAt(0)]); chai.assert.deepEqual(cell.getAsCharData(), [123, '1', 2, '1'.charCodeAt(0)]); @@ -350,7 +350,7 @@ describe('BufferLine', function(): void { // width is set to 1 chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301\u0301', 1, 0x0301]); // do not account a single combining char as combined - chai.assert.equal(cell.isCombined(), Content.IS_COMBINED); + chai.assert.equal(cell.isCombined(), ContentMasks.IS_COMBINED); }); it('should create combining string on taken cell', () => { const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); @@ -363,7 +363,7 @@ describe('BufferLine', function(): void { // width is set to 1 chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, 0x0301]); // do not account a single combining char as combined - chai.assert.equal(cell.isCombined(), Content.IS_COMBINED); + chai.assert.equal(cell.isCombined(), ContentMasks.IS_COMBINED); }); }); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 1451845471..c4aa55be0f 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -34,9 +34,9 @@ const enum Cell { } /** - * Bitmasks and helper for accessing data in `content`. + * Bitmasks for accessing data in `content`. */ -export const enum Content { +export const enum ContentMasks { /** * 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 Content { * shortcut if precondition `codepoint <= 0x10FFFF` is met: * `content |= codepoint;` */ - CODEPOINT_MASK = 0x1FFFFF, + CODEPOINT = 0x1FFFFF, /** * bit 22 flag indication whether a cell contains combined content @@ -72,10 +72,11 @@ export const enum Content { * shortcut if precondition `0 <= width <= 3` is met: * `content |= width << Content.widthShift;` */ - WIDTH_MASK = 0xC00000, // 3 << 22 - WIDTH_SHIFT = 22 + WIDTH = 0xC00000 // 3 << 22 } +const WIDTH_MASK_SHIFT = 22; + /** * CellData - represents a single Cell in the terminal buffer. */ @@ -96,21 +97,21 @@ export class CellData implements ICellData { /** Whether cell contains a combined string. */ public isCombined(): number { - return this.content & Content.IS_COMBINED; + return this.content & ContentMasks.IS_COMBINED; } /** Width of the cell. */ public getWidth(): number { - return this.content >> Content.WIDTH_SHIFT; + return this.content >> WIDTH_MASK_SHIFT; } /** JS string of the content. */ public getChars(): string { - if (this.content & Content.IS_COMBINED) { + if (this.content & ContentMasks.IS_COMBINED) { return this.combinedData; } - if (this.content & Content.CODEPOINT_MASK) { - return stringFromCodePoint(this.content & Content.CODEPOINT_MASK); + if (this.content & ContentMasks.CODEPOINT) { + return stringFromCodePoint(this.content & ContentMasks.CODEPOINT); } return ''; } @@ -124,7 +125,7 @@ export class CellData implements ICellData { public getCode(): number { return (this.isCombined()) ? this.combinedData.charCodeAt(this.combinedData.length - 1) - : this.content & Content.CODEPOINT_MASK; + : this.content & ContentMasks.CODEPOINT; } /** Set data from CharData */ @@ -143,7 +144,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] << Content.WIDTH_SHIFT); + this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); } else { combined = true; } @@ -151,11 +152,11 @@ export class CellData implements ICellData { combined = true; } } else { - this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); + this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); } if (combined) { this.combinedData = value[CHAR_DATA_CHAR_INDEX]; - this.content = Content.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); + this.content = ContentMasks.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); } } @@ -203,14 +204,14 @@ export class BufferLine implements IBufferLine { */ public get(index: number): CharData { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; - const cp = content & Content.CODEPOINT_MASK; + const cp = content & ContentMasks.CODEPOINT; return [ this._data[index * CELL_SIZE + Cell.FG], - (content & Content.IS_COMBINED) + (content & ContentMasks.IS_COMBINED) ? this._combined[index] : (cp) ? stringFromCodePoint(cp) : '', - content >> Content.WIDTH_SHIFT, - (content & Content.IS_COMBINED) + content >> WIDTH_MASK_SHIFT, + (content & ContentMasks.IS_COMBINED) ? this._combined[index].charCodeAt(this._combined[index].length - 1) : cp ]; @@ -224,9 +225,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 | Content.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); + this._data[index * CELL_SIZE + Cell.CONTENT] = index | ContentMasks.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); } else { - 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.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); } } @@ -235,21 +236,21 @@ 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] >> Content.WIDTH_SHIFT; + return this._data[index * CELL_SIZE + Cell.CONTENT] >> WIDTH_MASK_SHIFT; } /** Test whether content has width. */ public hasWidth(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK; + return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.WIDTH; } /** Get FG cell component. */ - public getFG(index: number): number { + public getFg(index: number): number { return this._data[index * CELL_SIZE + Cell.FG]; } /** Get BG cell component. */ - public getBG(index: number): number { + public getBg(index: number): number { return this._data[index * CELL_SIZE + Cell.BG]; } @@ -259,7 +260,7 @@ export class BufferLine implements IBufferLine { * from real empty cells. * */ public hasContent(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT; + return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.HAS_CONTENT; } /** @@ -269,38 +270,40 @@ export class BufferLine implements IBufferLine { */ public getCodePoint(index: number): number { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; - if (content & Content.IS_COMBINED) { + if (content & ContentMasks.IS_COMBINED) { return this._combined[index].charCodeAt(this._combined[index].length - 1); } - return content & Content.CODEPOINT_MASK; + return content & ContentMasks.CODEPOINT; } /** Test whether the cell contains a combined string. */ public isCombined(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED; + return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.IS_COMBINED; } /** Returns the string content of the cell. */ public getString(index: number): string { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; - if (content & Content.IS_COMBINED) { + if (content & ContentMasks.IS_COMBINED) { return this._combined[index]; } - if (content & Content.CODEPOINT_MASK) { - return stringFromCodePoint(content & Content.CODEPOINT_MASK); + if (content & ContentMasks.CODEPOINT) { + return stringFromCodePoint(content & ContentMasks.CODEPOINT); } // return empty string for empty cells return ''; } /** - * Load data at `index` into `cell`. + * Load data at `index` into `cell`. This is used to access cells in a way that's more friendly + * to GC as it significantly reduced the amount of new objects/references needed. */ public loadCell(index: number, cell: ICellData): ICellData { - cell.content = this._data[index * CELL_SIZE + Cell.CONTENT]; - cell.fg = this._data[index * CELL_SIZE + Cell.FG]; - cell.bg = this._data[index * CELL_SIZE + Cell.BG]; - if (cell.content & Content.IS_COMBINED) { + const startIndex = index * CELL_SIZE; + 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) { cell.combinedData = this._combined[index]; } return cell; @@ -310,7 +313,7 @@ export class BufferLine implements IBufferLine { * Set data at `index` to `cell`. */ public setCell(index: number, cell: ICellData): void { - if (cell.content & Content.IS_COMBINED) { + if (cell.content & ContentMasks.IS_COMBINED) { this._combined[index] = cell.combinedData; } this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content; @@ -324,7 +327,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 << Content.WIDTH_SHIFT); + this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << WIDTH_MASK_SHIFT); this._data[index * CELL_SIZE + Cell.FG] = fg; this._data[index * CELL_SIZE + Cell.BG] = bg; } @@ -337,21 +340,21 @@ export class BufferLine implements IBufferLine { */ public addCodepointToCell(index: number, codePoint: number): void { let content = this._data[index * CELL_SIZE + Cell.CONTENT]; - if (content & Content.IS_COMBINED) { + if (content & ContentMasks.IS_COMBINED) { // we already have a combined string, simply add this._combined[index] += stringFromCodePoint(codePoint); } else { - if (content & Content.CODEPOINT_MASK) { + if (content & ContentMasks.CODEPOINT) { // normal case for combining chars: // - move current leading char + new one into combined string // - set combined flag - this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint); - content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0 - content |= Content.IS_COMBINED; + this._combined[index] = stringFromCodePoint(content & ContentMasks.CODEPOINT) + stringFromCodePoint(codePoint); + content &= ~ContentMasks.CODEPOINT; // set codepoint in buffer to 0 + content |= ContentMasks.IS_COMBINED; } 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 << Content.WIDTH_SHIFT); + content = codePoint | (1 << WIDTH_MASK_SHIFT); } this._data[index * CELL_SIZE + Cell.CONTENT] = content; } @@ -473,8 +476,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] & Content.HAS_CONTENT)) { - return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT); + if ((this._data[i * CELL_SIZE + Cell.CONTENT] & ContentMasks.HAS_CONTENT)) { + return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> WIDTH_MASK_SHIFT); } } return 0; @@ -513,9 +516,9 @@ export class BufferLine implements IBufferLine { let result = ''; while (startCol < endCol) { 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 + 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 } return result; } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 65f4aaaffd..37c9bbe570 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -106,7 +106,7 @@ class DECRQSS implements IDcsHandler { export class InputHandler extends Disposable implements IInputHandler { private _parseBuffer: Uint32Array = new Uint32Array(4096); private _stringDecoder: StringToUtf32 = new StringToUtf32(); - private _cell: CellData = new CellData(); + private _workCell: CellData = new CellData(); constructor( protected _terminal: IInputHandlingTerminal, @@ -351,7 +351,7 @@ export class InputHandler extends Disposable implements IInputHandler { // since they always follow a cell consuming char // therefore we can test for buffer.x to avoid overflow left if (!chWidth && buffer.x) { - if (!bufferRow.loadCell(buffer.x - 1, this._cell).getWidth()) { + if (!bufferRow.getWidth(buffer.x - 1)) { // found empty cell after fullwidth, need to go 2 cells back // it is save to step 2 cells back here // since an empty cell is only set by fullwidth chars @@ -400,7 +400,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test last cell - since the last cell has only room for // a halfwidth char any fullwidth shifted there is lost // and will be set to empty cell - if (bufferRow.loadCell(cols - 1, this._cell).getWidth() === 2) { + if (bufferRow.getWidth(cols - 1) === 2) { bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr, 0); } } @@ -970,10 +970,10 @@ export class InputHandler extends Disposable implements IInputHandler { // make buffer local for faster access const buffer = this._terminal.buffer; const line = buffer.lines.get(buffer.ybase + buffer.y); - line.loadCell(buffer.x - 1, this._cell); + line.loadCell(buffer.x - 1, this._workCell); line.replaceCells(buffer.x, buffer.x + (params[0] || 1), - (this._cell.content !== undefined) ? this._cell : buffer.getNullCell(DEFAULT_ATTR) + (this._workCell.content !== undefined) ? this._workCell : buffer.getNullCell(DEFAULT_ATTR) ); // FIXME: no updateRange here? } diff --git a/src/Linkifier.ts b/src/Linkifier.ts index a2b9045b64..80399904cb 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -231,7 +231,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { } const line = this._terminal.buffer.lines.get(bufferIndex[0]); - const attr = line.getFG(bufferIndex[1]); + const attr = line.getFg(bufferIndex[1]); let fg: number | undefined; if (attr) { fg = (attr >> 9) & 0x1ff; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 9d49a860a8..361b11235d 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -103,7 +103,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _mouseMoveListener: EventListener; private _mouseUpListener: EventListener; private _trimListener: XtermListener; - private _cell: CellData = new CellData(); + private _workCell: CellData = new CellData(); private _mouseDownTimeStamp: number; @@ -669,8 +669,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number { let charIndex = coords[0]; for (let i = 0; coords[0] >= i; i++) { - const length = bufferLine.loadCell(i, this._cell).getChars().length; - if (this._cell.getWidth() === 0) { + const length = bufferLine.loadCell(i, this._workCell).getChars().length; + if (this._workCell.getWidth() === 0) { // Wide characters aren't included in the line string so decrement the // index so the index is back on the wide character. charIndex--; @@ -755,10 +755,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager } // Expand the string in both directions until a space is hit - while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._cell))) { - bufferLine.loadCell(startCol - 1, this._cell); - const length = this._cell.getChars().length; - if (this._cell.getWidth() === 0) { + while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._workCell))) { + bufferLine.loadCell(startCol - 1, this._workCell); + const length = this._workCell.getChars().length; + if (this._workCell.getWidth() === 0) { // If the next character is a wide char, record it and skip the column leftWideCharCount++; startCol--; @@ -771,10 +771,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager startIndex--; startCol--; } - while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._cell))) { - bufferLine.loadCell(endCol + 1, this._cell); - const length = this._cell.getChars().length; - if (this._cell.getWidth() === 2) { + while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._workCell))) { + bufferLine.loadCell(endCol + 1, this._workCell); + const length = this._workCell.getChars().length; + if (this._workCell.getWidth() === 2) { // If the next character is a wide char, record it and skip the column rightWideCharCount++; endCol++; diff --git a/src/Terminal.ts b/src/Terminal.ts index f3ec6e31de..db696bd6f6 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1181,7 +1181,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public scroll(isWrapped: boolean = false): void { let newLine: IBufferLine; newLine = this._blankLine; - if (!newLine || newLine.length !== this.cols || newLine.getFG(0) !== this.eraseAttr()) { + if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== this.eraseAttr()) { newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); this._blankLine = newLine; } diff --git a/src/Types.ts b/src/Types.ts index a08bd48530..10665f2586 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -560,8 +560,8 @@ export interface IBufferLine { /* direct access to cell attrs */ getWidth(index: number): number; hasWidth(index: number): number; - getFG(index: number): number; - getBG(index: number): number; + getFg(index: number): number; + getBg(index: number): number; hasContent(index: number): number; getCodePoint(index: number): number; isCombined(index: number): number; diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index eb44bb58db..4a899d7218 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -6,7 +6,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { private _characterJoiners: ICharacterJoiner[] = []; private _nextCharacterJoinerId: number = 0; - private _cell: CellData = new CellData(); + private _workCell: CellData = new CellData(); constructor(private _terminal: ITerminal) { } @@ -52,13 +52,13 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { let rangeStartColumn = 0; let currentStringIndex = 0; let rangeStartStringIndex = 0; - let rangeAttr = line.getFG(0) >> 9; + let rangeAttr = line.getFg(0) >> 9; for (let x = 0; x < this._terminal.cols; x++) { - line.loadCell(x, this._cell); - const chars = this._cell.getChars(); - const width = this._cell.getWidth(); - const attr = this._cell.fg >> 9; + line.loadCell(x, this._workCell); + const chars = this._workCell.getChars(); + const width = this._workCell.getWidth(); + const attr = this._workCell.fg >> 9; if (width === 0) { // If this character is of width 0, skip it. diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index be022239a3..f56ccf3a98 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -25,7 +25,7 @@ export class TextRenderLayer extends BaseRenderLayer { private _characterFont: string; private _characterOverlapCache: { [key: string]: boolean } = {}; private _characterJoinerRegistry: ICharacterJoinerRegistry; - private _cell = new CellData(); + private _workCell = new CellData(); constructor(container: HTMLElement, zIndex: number, colors: IColorSet, characterJoinerRegistry: ICharacterJoinerRegistry, alpha: boolean) { super(container, 'text', zIndex, alpha, colors); @@ -74,14 +74,14 @@ export class TextRenderLayer extends BaseRenderLayer { const line = terminal.buffer.lines.get(row); const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; for (let x = 0; x < terminal.cols; x++) { - (line as any).loadCell(x, this._cell); - let code: number = this._cell.getCode() || WHITESPACE_CELL_CODE; + line.loadCell(x, this._workCell); + let code: number = this._workCell.getCode() || WHITESPACE_CELL_CODE; // Can either represent character(s) for a single cell or multiple cells // if indicated by a character joiner. - let chars = this._cell.getChars() || WHITESPACE_CELL_CHAR; - const attr = this._cell.fg; - let width = this._cell.getWidth(); + let chars = this._workCell.getChars() || WHITESPACE_CELL_CHAR; + const attr = this._workCell.fg; + let width = this._workCell.getWidth(); // If true, indicates that the current character(s) to draw were joined. let isJoined = false; @@ -127,7 +127,7 @@ export class TextRenderLayer extends BaseRenderLayer { // get removed, and `a` would not re-render because it thinks it's // already in the correct state. // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; - if (lastCharX < line.length - 1 && line.loadCell(lastCharX + 1, this._cell).getCode() === NULL_CELL_CODE) { + if (lastCharX < line.length - 1 && line.loadCell(lastCharX + 1, this._workCell).getCode() === NULL_CELL_CODE) { width = 2; // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index dfd3a15428..4723298147 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -18,7 +18,8 @@ export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar'; export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'; export class DomRendererRowFactory { - private _cell: CellData = new CellData(); + private _workCell: CellData = new CellData(); + constructor( private _terminalOptions: ITerminalOptions, private _document: Document @@ -35,16 +36,16 @@ export class DomRendererRowFactory { // the viewport). let lineLength = 0; for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) { - if (lineData.loadCell(x, this._cell).getCode() !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { + if (lineData.loadCell(x, this._workCell).getCode() !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { lineLength = x + 1; break; } } for (let x = 0; x < lineLength; x++) { - lineData.loadCell(x, this._cell); - const attr = this._cell.fg; - const width = this._cell.getWidth(); + lineData.loadCell(x, this._workCell); + const attr = this._workCell.fg; + const width = this._workCell.getWidth(); // The character to the left is a wide character, drawing is owned by the char at x-1 if (width === 0) { @@ -106,7 +107,7 @@ export class DomRendererRowFactory { charElement.classList.add(ITALIC_CLASS); } - charElement.textContent = this._cell.getChars() || WHITESPACE_CELL_CHAR; + charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; if (fg !== DEFAULT_COLOR) { charElement.classList.add(`xterm-fg-${fg}`); }