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/77] 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 3f93af62..c16c3808 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 77e6971c..04407a09 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/77] 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 c16c3808..7c70c93b 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 e270a5f3..bf8149b2 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 60b86de1..5c4b4880 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/77] 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 7963934e..af838144 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 bf8149b2..d19be5a1 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/77] 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 d19be5a1..2c6198d3 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/77] 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 af838144..1700279c 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 ade2dd4c..931c4651 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/77] 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 2c6198d3..7cc2b8b6 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/77] 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 1700279c..827e7c93 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 7cc2b8b6..f837f295 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 b89d5422..180fe000 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/77] remove leftover --- src/InputHandler.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f837f295..b53e9115 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/77] 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 827e7c93..2792d710 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 931c4651..815eef17 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 8bcde39a..83a4651e 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/77] 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 53247c95..3499c87d 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/77] 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 2792d710..b3a94672 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 180fe000..c1a7788a 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/77] 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 625a2497..32548eeb 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 3499c87d..a2b9045b 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 1aea1cb5..d615dda0 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 bc97de29..a467d6dd 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 3e0b8643..f84fbe6b 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 dc9e95dd..4cad7c72 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 08a14739..18ba7ada 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 815eef17..bf7c5616 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/77] 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 fbf8b051..4ccfe96b 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 b3a94672..64cb1e19 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 ad3713e9..72a86e42 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 c1a7788a..216139b7 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/77] 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 32548eeb..463b2451 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 72a86e42..c8f9e7f2 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 216139b7..9a4401b6 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 e6e4aaa3..9d525fbf 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/77] 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 4ccfe96b..68384f35 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 64cb1e19..fb1d40a9 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 c8f9e7f2..2776051d 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 9a4401b6..e991ac42 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/77] 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 68384f35..c2329113 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 fb1d40a9..d9ddae70 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 2776051d..37583602 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 e991ac42..83f4e492 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/77] 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 463b2451..edba479c 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 c2329113..402b1a02 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 d9ddae70..086fe15b 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 83f4e492..644a9b49 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 0c29566a..2f9f45be 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/77] 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 37583602..c41b4165 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/77] 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 edba479c..6295d175 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 402b1a02..210ba851 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 086fe15b..ccea8fe1 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/77] 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 0546dfe8..d1a1d563 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 210ba851..95ec77c3 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 ccea8fe1..4a074441 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 0ba1294a..d40e5069 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 2f74ccda..42591168 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 fdc9678b..2f9ae6a5 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 2f9f45be..0bbb982e 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 67342da0..e06990d7 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/77] 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 d1a1d563..27e0e836 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 95ec77c3..874eb14f 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 4a074441..8b62141e 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 0747fdf1..7cab3882 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 a7963a2c..24eb0884 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 d2a5cd7c..b5165490 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 2f9ae6a5..e06ceb78 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 644a9b49..d176799f 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 0bbb982e..effdbfaa 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/77] some docs --- src/BufferLine.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 8b62141e..b362c007 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/77] 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 874eb14f..110b9b4e 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 b362c007..048bcab6 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/77] 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 110b9b4e..97508ace 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 c444eebd89b448afd64be59f7206cc6febeaeb1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 13 Jan 2019 23:19:56 +0100 Subject: [PATCH 25/77] RGB support in buffer and attributes --- src/Buffer.test.ts | 8 +- src/Buffer.ts | 41 ++- src/BufferLine.test.ts | 16 +- src/BufferLine.ts | 240 +++++++++++++++++- src/BufferSet.ts | 4 +- src/InputHandler.test.ts | 134 ++++++++-- src/InputHandler.ts | 153 +++++------ src/Terminal.test.ts | 8 +- src/Terminal.ts | 95 ++----- src/Types.ts | 60 ++++- src/renderer/TextRenderLayer.ts | 7 +- .../dom/DomRendererRowFactory.test.ts | 48 +++- src/renderer/dom/DomRendererRowFactory.ts | 7 +- src/ui/TestUtils.test.ts | 15 +- 14 files changed, 589 insertions(+), 247 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 27e0e836..f6907864 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 } from './Buffer'; +import { Buffer, DEFAULT_ATTR_DATA } from './Buffer'; import { CircularList } from './common/CircularList'; import { MockTerminal, TestTerminal } from './ui/TestUtils.test'; import { BufferLine, CellData } from './BufferLine'; @@ -37,7 +37,7 @@ 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_DATA).loadCell(0, new CellData()).asCharData; buffer.fillViewportRows(); assert.equal(buffer.lines.length, INIT_ROWS); for (let y = 0; y < INIT_ROWS; y++) { @@ -184,7 +184,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); // Create 10 extra blank lines for (let i = 0; i < 10; i++) { - buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR)); + buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA)); } // Set cursor to the bottom of the buffer buffer.y = INIT_ROWS - 1; @@ -204,7 +204,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); // Create 10 extra blank lines for (let i = 0; i < 10; i++) { - buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR)); + buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA)); } // Set cursor to the bottom of the buffer buffer.y = INIT_ROWS - 1; diff --git a/src/Buffer.ts b/src/Buffer.ts index 6295d175..a5a285b0 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,13 +4,16 @@ */ import { CircularList } from './common/CircularList'; -import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData } from './Types'; +import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData, IAttributeData } from './Types'; import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; -import { BufferLine, CellData } from './BufferLine'; +import { BufferLine, CellData, AttributeData } from './BufferLine'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); + +export const DEFAULT_ATTR_DATA = new AttributeData(); + export const CHAR_DATA_ATTR_INDEX = 0; export const CHAR_DATA_CHAR_INDEX = 1; export const CHAR_DATA_WIDTH_INDEX = 2; @@ -43,7 +46,7 @@ export class Buffer implements IBuffer { public tabs: any; public savedY: number; public savedX: number; - public savedCurAttr: number; + public savedCurAttrData = DEFAULT_ATTR_DATA.clone(); 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]); @@ -61,19 +64,29 @@ export class Buffer implements IBuffer { this.clear(); } - public getNullCell(fg: number = 0, bg: number = 0): ICellData { - this._nullCell.fg = fg; - this._nullCell.bg = bg; + public getNullCell(attr?: IAttributeData): ICellData { + if (attr) { + this._nullCell.fg = attr.fg; + this._nullCell.bg = attr.bg; + } else { + this._nullCell.fg = 0; + this._nullCell.bg = 0; + } return this._nullCell; } - public getWhitespaceCell(fg: number = 0, bg: number = 0): ICellData { - this._whitespaceCell.fg = fg; - this._whitespaceCell.bg = bg; + public getWhitespaceCell(attr?: IAttributeData): ICellData { + if (attr) { + this._whitespaceCell.fg = attr.fg; + this._whitespaceCell.bg = attr.bg; + } else { + this._whitespaceCell.fg = 0; + this._whitespaceCell.bg = 0; + } return this._whitespaceCell; } - public getBlankLine(attr: number, isWrapped?: boolean): IBufferLine { + public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine { return new BufferLine(this._terminal.cols, this.getNullCell(attr), isWrapped); } @@ -105,10 +118,10 @@ export class Buffer implements IBuffer { /** * Fills the buffer's viewport with blank lines. */ - public fillViewportRows(fillAttr?: number): void { + public fillViewportRows(fillAttr?: IAttributeData): void { if (this.lines.length === 0) { if (fillAttr === undefined) { - fillAttr = DEFAULT_ATTR; + fillAttr = DEFAULT_ATTR_DATA; } let i = this._terminal.rows; while (i--) { @@ -149,7 +162,7 @@ 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 cell = this.getNullCell(DEFAULT_ATTR); // does xterm use the default attr? + const cell = this.getNullCell(DEFAULT_ATTR_DATA); // does xterm use the default attr? for (let i = 0; i < this.lines.length; i++) { this.lines.get(i).resize(newCols, cell); } @@ -172,7 +185,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 - this.lines.push(new BufferLine(newCols, this.getNullCell(DEFAULT_ATTR))); + this.lines.push(new BufferLine(newCols, this.getNullCell(DEFAULT_ATTR_DATA))); } } } diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 97508ace..ec03c064 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -24,23 +24,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); }); }); @@ -366,7 +366,7 @@ describe('BufferLine', function(): void { // 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); @@ -379,7 +379,7 @@ describe('BufferLine', function(): void { // 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); @@ -392,7 +392,7 @@ describe('BufferLine', function(): void { // 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 048bcab6..e306346f 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -2,11 +2,67 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ -import { CharData, IBufferLine, ICellData } from './Types'; +import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer'; import { stringFromCodePoint } from './core/input/TextDecoder'; +import { FLAGS } from './renderer/Types'; +import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; +/** + * TODO: + * The below color-related code can be removed when true color is implemented. + * It's only purpose is to match true color requests with the closest matching + * ANSI color code. + */ +const matchColorCache: {[colorRGBHash: number]: number} = {}; + +// http://stackoverflow.com/questions/1633828 +function matchColorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number { + return Math.pow(30 * (r1 - r2), 2) + + Math.pow(59 * (g1 - g2), 2) + + Math.pow(11 * (b1 - b2), 2); +} + +function matchColor(r1: number, g1: number, b1: number): number { + const hash = (r1 << 16) | (g1 << 8) | b1; + + if (matchColorCache[hash] !== null && matchColorCache[hash] !== undefined) { + return matchColorCache[hash]; + } + + let ldiff = Infinity; + let li = -1; + let i = 0; + let c: number; + let r2: number; + let g2: number; + let b2: number; + let diff: number; + + for (; i < DEFAULT_ANSI_COLORS.length; i++) { + c = DEFAULT_ANSI_COLORS[i].rgba; + r2 = c >>> 24; + g2 = c >>> 16 & 0xFF; + b2 = c >>> 8 & 0xFF; + // assume that alpha is 0xFF + + diff = matchColorDistance(r1, g1, b1, r2, g2, b2); + + if (diff === 0) { + li = i; + break; + } + + if (diff < ldiff) { + ldiff = diff; + li = i; + } + } + + return matchColorCache[hash] = li; +} + /** * buffer memory layout: * @@ -76,12 +132,188 @@ export const enum Content { WIDTH_SHIFT = 22 } +export enum Attributes { + /** + * bit 1..8 blue in RGB, color in P256 and P16 + */ + BLUE_MASK = 0xFF, + BLUE_SHIFT = 0, + PCOLOR_MASK = 0xFF, + PCOLOR_SHIFT = 0, + + /** + * bit 9..16 green in RGB + */ + GREEN_MASK = 0xFF00, + GREEN_SHIFT = 8, + + /** + * bit 17..24 red in RGB + */ + RED_MASK = 0xFF0000, + RED_SHIFT = 16, + + /** + * bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3) + */ + CM_MASK = 0x3000000, + CM_DEFAULT = 0, + CM_P16 = 0x1000000, + CM_P256 = 0x2000000, + CM_RGB = 0x3000000, + + /** + * bit 1..24 RGB room + */ + RGB_MASK = 0xFFFFFF +} + +export enum FgFlags { + /** + * bit 27..31 (32th bit unused) + */ + INVERSE = 0x4000000, + BOLD = 0x8000000, + UNDERLINE = 0x10000000, + BLINK = 0x20000000, + INVISIBLE = 0x40000000 +} + +export enum BgFlags { + /** + * bit 27..32 (upper 4 unused) + */ + ITALIC = 0x4000000, + DIM = 0x8000000 +} + +export class AttributeData implements IAttributeData { + static toRGB(value: number): IColorRGB { + return [ + value >>> Attributes.RED_SHIFT & 255, + value >>> Attributes.GREEN_SHIFT & 255, + value & 255 + ]; + } + static fromRGB(value: IColorRGB): number { + return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255; + } + + public clone(): IAttributeData { + const newObj = new AttributeData(); + newObj.fg = this.fg; + newObj.bg = this.bg; + return newObj; + } + + // data + public fg: number = 0; + public bg: number = 0; + + // flags + public isInverse(): number { return this.fg & FgFlags.INVERSE; } + public isBold(): number { return this.fg & FgFlags.BOLD; } + public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; } + public isBlink(): number { return this.fg & FgFlags.BLINK; } + public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; } + public isItalic(): number { return this.bg & BgFlags.ITALIC; } + public isDim(): number { return this.bg & BgFlags.DIM; } + + // color modes + public getColormodeFg(): number { return this.fg & Attributes.CM_MASK; } + public getColormodeBg(): number { return this.bg & Attributes.CM_MASK; } + public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; } + public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; } + public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; } + public isBgPalette(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; } + public isFgDefault(): boolean { return (this.fg & Attributes.CM_MASK) === 0; } + public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; } + + // colors + public getFgColor(channels: boolean = false): number | IColorRGB { + switch (this.fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: return this.fg & Attributes.PCOLOR_MASK; + case Attributes.CM_RGB: return (channels) ? AttributeData.toRGB(this.fg & Attributes.RGB_MASK) : this.fg & Attributes.RGB_MASK; + default: return -1; // CM_DEFAULT defaults to -1 + } + } + public getBgColor(channels: boolean = false): number | IColorRGB { + switch (this.bg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: return this.bg & Attributes.PCOLOR_MASK; + case Attributes.CM_RGB: return (channels) ? AttributeData.toRGB(this.bg & Attributes.RGB_MASK) : this.bg & Attributes.RGB_MASK; + default: return -1; // CM_DEFAULT defaults to -1 + } + } + + public getOldFlags(): number { + let flags = 0; + if (this.isBold()) { + flags |= FLAGS.BOLD; + } + if (this.isUnderline()) { + flags |= FLAGS.UNDERLINE; + } + if (this.isBlink()) { + flags |= FLAGS.BLINK; + } + if (this.isDim()) { + flags |= FLAGS.DIM; + } + if (this.isInvisible()) { + flags |= FLAGS.INVISIBLE; + } + if (this.isInverse()) { + flags |= FLAGS.INVERSE; + } + if (this.isItalic()) { + flags |= FLAGS.ITALIC; + } + return flags; + } + public getOldFgColor(): number { + let color = this.getFgColor() as number; + if (color === -1) { + return 256; + } + if (this.isFgRGB()) { + color = matchColor( + (this.fg & Attributes.RED_MASK) >> Attributes.RED_SHIFT, + (this.fg & Attributes.GREEN_MASK) >> Attributes.GREEN_SHIFT, + (this.fg & Attributes.BLUE_MASK) >> Attributes.BLUE_SHIFT + ); + if (color === -1) { + color = 256; + } + } + return color; + } + public getOldBgColor(): number { + let color = this.getBgColor() as number; + if (color === -1) { + return 256; + } + if (this.isBgRGB()) { + color = matchColor( + (this.bg & Attributes.RED_MASK) >> Attributes.RED_SHIFT, + (this.bg & Attributes.GREEN_MASK) >> Attributes.GREEN_SHIFT, + (this.bg & Attributes.BLUE_MASK) >> Attributes.BLUE_SHIFT + ); + if (color === -1) { + color = 256; + } + } + return color; + } +} + /** * CellData - represents a single Cell in the terminal buffer. * * TODO: attr getter */ -export class CellData implements ICellData { +export class CellData extends AttributeData implements ICellData { /** Helper to create CellData from CharData. */ public static fromCharData(value: CharData): CellData { @@ -97,7 +329,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; } @@ -119,7 +351,7 @@ export class CellData implements ICellData { /** 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); + return ((this.isCombined) ? this.combinedData.charCodeAt(this.combinedData.length - 1) : this.content & Content.CODEPOINT_MASK); } /** Set data from CharData */ diff --git a/src/BufferSet.ts b/src/BufferSet.ts index f84757d1..268c2d88 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal, IBufferSet } from './Types'; +import { ITerminal, IBufferSet, IAttributeData } from './Types'; import { Buffer } from './Buffer'; import { EventEmitter } from './common/EventEmitter'; @@ -77,7 +77,7 @@ export class BufferSet extends EventEmitter implements IBufferSet { /** * Sets the alt Buffer of the BufferSet as its currently active Buffer */ - public activateAltBuffer(fillAttr?: number): void { + public activateAltBuffer(fillAttr?: IAttributeData): void { if (this._activeBuffer === this._alt) { return; } diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 24eb0884..df34cf04 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -5,33 +5,33 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; -import { MockInputHandlingTerminal } from './ui/TestUtils.test'; -import { DEFAULT_ATTR } from './Buffer'; +import { MockInputHandlingTerminal, TestTerminal } from './ui/TestUtils.test'; +import { DEFAULT_ATTR_DATA } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './Types'; -import { CellData } from './BufferLine'; +import { CellData, Attributes } from './BufferLine'; describe('InputHandler', () => { describe('save and restore cursor', () => { const terminal = new MockInputHandlingTerminal(); terminal.buffer.x = 1; terminal.buffer.y = 2; - terminal.curAttr = 3; + terminal.curAttrData.fg = 3; const inputHandler = new InputHandler(terminal); // Save cursor position inputHandler.saveCursor([]); assert.equal(terminal.buffer.x, 1); assert.equal(terminal.buffer.y, 2); - assert.equal(terminal.curAttr, 3); + assert.equal(terminal.curAttrData.fg, 3); // Change cursor position terminal.buffer.x = 10; terminal.buffer.y = 20; - terminal.curAttr = 30; + terminal.curAttrData.fg = 30; // Restore cursor position inputHandler.restoreCursor([]); assert.equal(terminal.buffer.x, 1); assert.equal(terminal.buffer.y, 2); - assert.equal(terminal.curAttr, 3); + assert.equal(terminal.curAttrData.fg, 3); }); describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { @@ -357,45 +357,149 @@ 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).loadCell(4, new CellData()).fg >> 9) & 0x1ff).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(4, new CellData()).getOldFgColor())).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).loadCell(4, new CellData()).fg >> 9) & 0x1ff).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(4, new CellData()).getOldFgColor())).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).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR); + expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); // Text color of 'JUNK' should be red - expect((term.buffer.lines.get(1).loadCell(0, new CellData()).fg >> 9) & 0x1ff).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(0, new CellData()).getOldFgColor())).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).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR); + expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); }); 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).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR); + expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); 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).loadCell(0, new CellData()).fg >> 9) & 0x1ff).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(0, new CellData()).getOldFgColor())).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).loadCell(10, new CellData()).fg & 0x1ff).to.equal(2); + expect(term.buffer.lines.get(20).loadCell(10, new CellData()).getOldBgColor()).to.equal(2); + }); + }); + + describe('text attributes', () => { + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal(); + }); + it('bold', () => { + term.writeSync('\x1b[1m'); + assert.equal(!!term.curAttrData.isBold(), true); + term.writeSync('\x1b[22m'); + assert.equal(!!term.curAttrData.isBold(), false); + }); + it('dim', () => { + term.writeSync('\x1b[2m'); + assert.equal(!!term.curAttrData.isDim(), true); + term.writeSync('\x1b[22m'); + assert.equal(!!term.curAttrData.isDim(), false); + }); + it('italic', () => { + term.writeSync('\x1b[3m'); + assert.equal(!!term.curAttrData.isItalic(), true); + term.writeSync('\x1b[23m'); + assert.equal(!!term.curAttrData.isItalic(), false); + }); + it('underline', () => { + term.writeSync('\x1b[4m'); + assert.equal(!!term.curAttrData.isUnderline(), true); + term.writeSync('\x1b[24m'); + assert.equal(!!term.curAttrData.isUnderline(), false); + }); + it('blink', () => { + term.writeSync('\x1b[5m'); + assert.equal(!!term.curAttrData.isBlink(), true); + term.writeSync('\x1b[25m'); + assert.equal(!!term.curAttrData.isBlink(), false); + }); + it('inverse', () => { + term.writeSync('\x1b[7m'); + assert.equal(!!term.curAttrData.isInverse(), true); + term.writeSync('\x1b[27m'); + assert.equal(!!term.curAttrData.isInverse(), false); + }); + it('invisible', () => { + term.writeSync('\x1b[8m'); + assert.equal(!!term.curAttrData.isInvisible(), true); + term.writeSync('\x1b[28m'); + assert.equal(!!term.curAttrData.isInvisible(), false); + }); + it('colormode palette 16', () => { + assert.equal(term.curAttrData.getColormodeFg(), 0); // DEFAULT + assert.equal(term.curAttrData.getColormodeBg(), 0); // DEFAULT + // lower 8 colors + for (let i = 0; i < 8; ++i) { + term.writeSync(`\x1b[${i + 30};${i + 40}m`); + assert.equal(term.curAttrData.getColormodeFg(), Attributes.CM_P16); + assert.equal(term.curAttrData.getFgColor(), i); + assert.equal(term.curAttrData.getColormodeBg(), Attributes.CM_P16); + assert.equal(term.curAttrData.getBgColor(), i); + } + // reset to DEFAULT + term.writeSync(`\x1b[39;49m`); + assert.equal(term.curAttrData.getColormodeFg(), 0); + assert.equal(term.curAttrData.getColormodeBg(), 0); + }); + it('colormode palette 256', () => { + assert.equal(term.curAttrData.getColormodeFg(), 0); // DEFAULT + assert.equal(term.curAttrData.getColormodeBg(), 0); // DEFAULT + // lower 8 colors + for (let i = 0; i < 256; ++i) { + term.writeSync(`\x1b[38;5;${i};48;5;${i}m`); + assert.equal(term.curAttrData.getColormodeFg(), Attributes.CM_P256); + assert.equal(term.curAttrData.getFgColor(), i); + assert.equal(term.curAttrData.getColormodeBg(), Attributes.CM_P256); + assert.equal(term.curAttrData.getBgColor(), i); + } + // reset to DEFAULT + term.writeSync(`\x1b[39;49m`); + assert.equal(term.curAttrData.getColormodeFg(), 0); + assert.equal(term.curAttrData.getFgColor(), -1); + assert.equal(term.curAttrData.getColormodeBg(), 0); + assert.equal(term.curAttrData.getBgColor(), -1); + }); + it('colormode RGB', () => { + assert.equal(term.curAttrData.getColormodeFg(), 0); // DEFAULT + assert.equal(term.curAttrData.getColormodeBg(), 0); // DEFAULT + term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + assert.equal(term.curAttrData.getColormodeFg(), Attributes.CM_RGB); + assert.equal(term.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); + assert.deepEqual(term.curAttrData.getFgColor(true), [1, 2, 3]); + assert.equal(term.curAttrData.getColormodeBg(), Attributes.CM_RGB); + assert.deepEqual(term.curAttrData.getBgColor(true), [4, 5, 6]); + // reset to DEFAULT + term.writeSync(`\x1b[39;49m`); + assert.equal(term.curAttrData.getColormodeFg(), 0); + assert.equal(term.curAttrData.getFgColor(), -1); + assert.equal(term.curAttrData.getColormodeBg(), 0); + assert.equal(term.curAttrData.getBgColor(), -1); + }); + it('should zero missing RGB values', () => { + term.writeSync(`\x1b[38;2;1;2;3m`); + term.writeSync(`\x1b[38;2;5m`); + assert.deepEqual(term.curAttrData.getFgColor(true), [5, 0, 0]); }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c41b4165..178507fa 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,19 +4,17 @@ * @license MIT */ -import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types'; +import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IInputHandlingTerminal } from './Types'; import { C0, C1 } from './common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; -import { DEFAULT_ATTR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; -import { FLAGS } from './renderer/Types'; +import { NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from './Buffer'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; -import { ICharset } from './core/Types'; 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'; +import { CellData, Attributes, FgFlags, BgFlags } from './BufferLine'; /** * Map collect to glevel. Used in `selectCharset`. @@ -314,13 +312,13 @@ export class InputHandler extends Disposable implements IInputHandler { public print(data: Uint32Array, start: number, end: number): void { let code: number; let chWidth: number; - const buffer: IBuffer = this._terminal.buffer; - const charset: ICharset = this._terminal.charset; - const screenReaderMode: boolean = this._terminal.options.screenReaderMode; - const cols: number = this._terminal.cols; - const wraparoundMode: boolean = this._terminal.wraparoundMode; - const insertMode: boolean = this._terminal.insertMode; - const curAttr: number = this._terminal.curAttr; + const buffer = this._terminal.buffer; + const charset = this._terminal.charset; + const screenReaderMode = this._terminal.options.screenReaderMode; + const cols = this._terminal.cols; + const wraparoundMode = this._terminal.wraparoundMode; + const insertMode = this._terminal.insertMode; + const curAttr = this._terminal.curAttrData; let bufferRow = buffer.lines.get(buffer.y + buffer.ybase); this._terminal.updateRange(buffer.y); @@ -401,12 +399,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.setDataFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg); } } // write current char to buffer and advance cursor - bufferRow.setDataFromCodePoint(buffer.x++, code, chWidth, curAttr, 0); + bufferRow.setDataFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg); // 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 +412,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.setDataFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg); } } } @@ -520,7 +518,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( this._terminal.buffer.x, params[0] || 1, - this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) + this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) ); this._terminal.updateRange(this._terminal.buffer.y); } @@ -694,7 +692,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.replaceCells( start, end, - this._terminal.buffer.getNullCell(this._terminal.eraseAttr()) + this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) ); if (clearWrap) { line.isWrapped = false; @@ -817,7 +815,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test: echo -e '\e[44m\e[1L\e[0m' // blankLine(true) - xterm/linux behavior buffer.lines.splice(scrollBottomAbsolute - 1, 1); - buffer.lines.splice(row, 0, buffer.getBlankLine(this._terminal.eraseAttr())); + buffer.lines.splice(row, 0, buffer.getBlankLine(this._terminal.eraseAttrData())); } // this.maxRange(); @@ -847,7 +845,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior buffer.lines.splice(row, 1); - buffer.lines.splice(j, 0, buffer.getBlankLine(this._terminal.eraseAttr())); + buffer.lines.splice(j, 0, buffer.getBlankLine(this._terminal.eraseAttrData())); } // this.maxRange(); @@ -863,7 +861,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.buffer.getNullCell(this._terminal.eraseAttr()) + this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) ); this._terminal.updateRange(this._terminal.buffer.y); } @@ -879,7 +877,7 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1); - buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR)); + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); } // this.maxRange(); this._terminal.updateRange(buffer.scrollTop); @@ -898,7 +896,7 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1); - buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR)); + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); } // this.maxRange(); this._terminal.updateRange(buffer.scrollTop); @@ -914,7 +912,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.buffer.getNullCell(this._terminal.eraseAttr()) + this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) ); } @@ -973,7 +971,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.loadCell(buffer.x - 1, this._cell); line.replaceCells(buffer.x, buffer.x + (params[0] || 1), - (this._cell.content !== undefined) ? this._cell : buffer.getNullCell(DEFAULT_ATTR) + (this._cell.content !== undefined) ? this._cell : buffer.getNullCell(DEFAULT_ATTR_DATA) ); // FIXME: no updateRange here? } @@ -1307,7 +1305,7 @@ export class InputHandler extends Disposable implements IInputHandler { // FALL-THROUGH case 47: // alt screen buffer case 1047: // alt screen buffer - this._terminal.buffers.activateAltBuffer(this._terminal.eraseAttr()); + this._terminal.buffers.activateAltBuffer(this._terminal.eraseAttrData()); this._terminal.refresh(0, this._terminal.rows - 1); if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); @@ -1567,127 +1565,128 @@ export class InputHandler extends Disposable implements IInputHandler { public charAttributes(params: number[]): void { // Optimize a single SGR0. if (params.length === 1 && params[0] === 0) { - this._terminal.curAttr = DEFAULT_ATTR; + this._terminal.curAttrData.fg = DEFAULT_ATTR_DATA.fg; + this._terminal.curAttrData.bg = DEFAULT_ATTR_DATA.bg; return; } const l = params.length; - let flags = this._terminal.curAttr >> 18; - let fg = (this._terminal.curAttr >> 9) & 0x1ff; - let bg = this._terminal.curAttr & 0x1ff; let p; + const attr = this._terminal.curAttrData; for (let i = 0; i < l; i++) { p = params[i]; if (p >= 30 && p <= 37) { // fg color 8 - fg = p - 30; + attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); + attr.fg |= Attributes.CM_P16 | (p - 30); } else if (p >= 40 && p <= 47) { // bg color 8 - bg = p - 40; + attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); + attr.bg |= Attributes.CM_P16 | (p - 40); } else if (p >= 90 && p <= 97) { // fg color 16 - p += 8; - fg = p - 90; + attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); + attr.fg |= Attributes.CM_P16 | (p - 90) | 8; } else if (p >= 100 && p <= 107) { // bg color 16 - p += 8; - bg = p - 100; + attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); + attr.bg |= Attributes.CM_P16 | (p - 100) | 8; } else if (p === 0) { // default - flags = DEFAULT_ATTR >> 18; - fg = (DEFAULT_ATTR >> 9) & 0x1ff; - bg = DEFAULT_ATTR & 0x1ff; - // flags = 0; - // fg = 0x1ff; - // bg = 0x1ff; + attr.fg = DEFAULT_ATTR_DATA.fg; + attr.bg = DEFAULT_ATTR_DATA.bg; } else if (p === 1) { // bold text - flags |= FLAGS.BOLD; + attr.fg |= FgFlags.BOLD; } else if (p === 3) { // italic text - flags |= FLAGS.ITALIC; + attr.bg |= BgFlags.ITALIC; } else if (p === 4) { // underlined text - flags |= FLAGS.UNDERLINE; + attr.fg |= FgFlags.UNDERLINE; } else if (p === 5) { // blink - flags |= FLAGS.BLINK; + attr.fg |= FgFlags.BLINK; } else if (p === 7) { // inverse and positive // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' - flags |= FLAGS.INVERSE; + attr.fg |= FgFlags.INVERSE; } else if (p === 8) { // invisible - flags |= FLAGS.INVISIBLE; + attr.fg |= FgFlags.INVISIBLE; } else if (p === 2) { // dimmed text - flags |= FLAGS.DIM; + attr.bg |= BgFlags.DIM; } else if (p === 22) { // not bold nor faint - flags &= ~FLAGS.BOLD; - flags &= ~FLAGS.DIM; + attr.fg &= ~FgFlags.BOLD; + attr.bg &= ~BgFlags.DIM; } else if (p === 23) { // not italic - flags &= ~FLAGS.ITALIC; + attr.bg &= ~BgFlags.ITALIC; } else if (p === 24) { // not underlined - flags &= ~FLAGS.UNDERLINE; + attr.fg &= ~FgFlags.UNDERLINE; } else if (p === 25) { // not blink - flags &= ~FLAGS.BLINK; + attr.fg &= ~FgFlags.BLINK; } else if (p === 27) { // not inverse - flags &= ~FLAGS.INVERSE; + attr.fg &= ~FgFlags.INVERSE; } else if (p === 28) { // not invisible - flags &= ~FLAGS.INVISIBLE; + attr.fg &= ~FgFlags.INVISIBLE; } else if (p === 39) { // reset fg - fg = (DEFAULT_ATTR >> 9) & 0x1ff; + attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); + attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); } else if (p === 49) { // reset bg - bg = DEFAULT_ATTR & 0x1ff; + attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); + attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); } else if (p === 38) { // fg color 256 if (params[i + 1] === 2) { i += 2; - fg = this._terminal.matchColor( - params[i] & 0xff, - params[i + 1] & 0xff, - params[i + 2] & 0xff); - if (fg === -1) fg = 0x1ff; + attr.fg |= Attributes.CM_RGB; + attr.fg &= ~Attributes.RGB_MASK; + attr.fg |= (params[i] & 0xFF) << Attributes.RED_SHIFT; + attr.fg |= (params[i + 1] & 0xFF) << Attributes.GREEN_SHIFT; + attr.fg |= (params[i + 2] & 0xFF) << Attributes.BLUE_SHIFT; i += 2; } else if (params[i + 1] === 5) { i += 2; p = params[i] & 0xff; - fg = p; + attr.fg &= ~Attributes.PCOLOR_MASK; + attr.fg |= Attributes.CM_P256 | p; } } else if (p === 48) { // bg color 256 if (params[i + 1] === 2) { i += 2; - bg = this._terminal.matchColor( - params[i] & 0xff, - params[i + 1] & 0xff, - params[i + 2] & 0xff); - if (bg === -1) bg = 0x1ff; + attr.bg |= Attributes.CM_RGB; + attr.bg &= ~Attributes.RGB_MASK; + attr.bg |= (params[i] & 0xFF) << Attributes.RED_SHIFT; + attr.bg |= (params[i + 1] & 0xFF) << Attributes.GREEN_SHIFT; + attr.bg |= (params[i + 2] & 0xFF) << Attributes.BLUE_SHIFT; i += 2; } else if (params[i + 1] === 5) { i += 2; p = params[i] & 0xff; - bg = p; + attr.bg &= ~Attributes.PCOLOR_MASK; + attr.bg |= Attributes.CM_P256 | p; } } else if (p === 100) { // reset fg/bg - fg = (DEFAULT_ATTR >> 9) & 0x1ff; - bg = DEFAULT_ATTR & 0x1ff; + attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); + attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); + attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); + attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); } else { this._terminal.error('Unknown SGR attribute: %d.', p); } } - - this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; } /** @@ -1774,7 +1773,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.applicationCursor = false; this._terminal.buffer.scrollTop = 0; this._terminal.buffer.scrollBottom = this._terminal.rows - 1; - this._terminal.curAttr = DEFAULT_ATTR; + this._terminal.curAttrData = DEFAULT_ATTR_DATA; this._terminal.buffer.x = this._terminal.buffer.y = 0; // ? this._terminal.charset = null; this._terminal.glevel = 0; // ?? @@ -1837,7 +1836,8 @@ export class InputHandler extends Disposable implements IInputHandler { public saveCursor(params: number[]): void { this._terminal.buffer.savedX = this._terminal.buffer.x; this._terminal.buffer.savedY = this._terminal.buffer.y; - this._terminal.buffer.savedCurAttr = this._terminal.curAttr; + this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg; + this._terminal.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg; } @@ -1849,7 +1849,8 @@ export class InputHandler extends Disposable implements IInputHandler { public restoreCursor(params: number[]): void { this._terminal.buffer.x = this._terminal.buffer.savedX || 0; this._terminal.buffer.y = this._terminal.buffer.savedY || 0; - this._terminal.curAttr = this._terminal.buffer.savedCurAttr || DEFAULT_ATTR; + this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg; + this._terminal.curAttrData.bg = this._terminal.buffer.savedCurAttrData.bg; } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index e06ceb78..e6ed7b98 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 { DEFAULT_ATTR } from './Buffer'; +import { DEFAULT_ATTR_DATA } from './Buffer'; import { CellData } from './BufferLine'; const INIT_COLS = 80; @@ -260,7 +260,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.length, term.rows); assert.deepEqual(term.buffer.lines.get(0), promptLine); for (let i = 1; i < term.rows; i++) { - assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR)); + assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } }); it('should clear a buffer larger than rows', () => { @@ -277,7 +277,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.length, term.rows); assert.deepEqual(term.buffer.lines.get(0), promptLine); for (let i = 1; i < term.rows; i++) { - assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR)); + assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } }); it('should not break the prompt when cleared twice', () => { @@ -290,7 +290,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.length, term.rows); assert.deepEqual(term.buffer.lines.get(0), promptLine); for (let i = 1; i < term.rows; i++) { - assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR)); + assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index 33d8e60f..0a488ea2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,11 +21,11 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IBufferLine, IAttributeData } from './Types'; 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 } from './Buffer'; +import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR_DATA } from './Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './common/EventEmitter'; import { Viewport } from './Viewport'; @@ -41,7 +41,6 @@ import { addDisposableDomListener } from './ui/Lifecycle'; import * as Strings from './Strings'; import { MouseHelper } from './ui/MouseHelper'; import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; -import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { MouseZoneManager } from './ui/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; @@ -52,6 +51,7 @@ import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; import { clone } from './common/Clone'; +import { Attributes } from './BufferLine'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -168,7 +168,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _refreshEnd: number; public savedCols: number; - public curAttr: number; + public curAttrData: IAttributeData; + private _eraseAttrData: IAttributeData; public params: (string | number)[]; public currentParam: string | number; @@ -288,7 +289,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // TODO: Can this be just []? this.charsets = [null]; - this.curAttr = DEFAULT_ATTR; + this.curAttrData = DEFAULT_ATTR_DATA.clone(); + this._eraseAttrData = DEFAULT_ATTR_DATA.clone(); this.params = []; this.currentParam = 0; @@ -328,9 +330,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * back_color_erase feature for xterm. */ - public eraseAttr(): number { - // if (this.is('screen')) return DEFAULT_ATTR; - return (DEFAULT_ATTR & ~0x1ff) | (this.curAttr & 0x1ff); + public eraseAttrData(): IAttributeData { + this._eraseAttrData.bg &= ~(Attributes.CM_MASK | 0xFFFFFF); + this._eraseAttrData.bg |= this.curAttrData.bg & ~0xFC000000; + return this._eraseAttrData; } /** @@ -1175,8 +1178,9 @@ 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()) { - newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); + const eraseAttr = this.eraseAttrData(); + if (!newLine || newLine.length !== this.cols || newLine.getFG(0) !== eraseAttr.fg || newLine.getBG(0) !== eraseAttr.bg) { + newLine = this.buffer.getBlankLine(eraseAttr, isWrapped); this._blankLine = newLine; } newLine.isWrapped = isWrapped; @@ -1745,23 +1749,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.buffer.ybase = 0; this.buffer.y = 0; for (let i = 1; i < this.rows; i++) { - this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR)); + this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } this.refresh(0, this.rows - 1); this.emit('scroll', this.buffer.ydisp); } - /** - * If cur return the back color xterm feature attribute. Else return default attribute. - * @param cur - */ - public ch(cur?: boolean): CharData { - if (cur) { - return [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - } - return [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - } - /** * Evaluate if the current terminal is the given argument. * @param term The terminal name to evaluate @@ -1837,7 +1830,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // blankLine(true) is xterm/linux behavior const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop; this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1); - this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.buffer.getBlankLine(this.eraseAttr())); + this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.buffer.getBlankLine(this.eraseAttrData())); this.updateRange(this.buffer.scrollTop); this.updateRange(this.buffer.scrollBottom); } else { @@ -1882,46 +1875,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return false; } - // TODO: Remove when true color is implemented - public matchColor(r1: number, g1: number, b1: number): number { - const hash = (r1 << 16) | (g1 << 8) | b1; - - if (matchColorCache[hash] !== null && matchColorCache[hash] !== undefined) { - return matchColorCache[hash]; - } - - let ldiff = Infinity; - let li = -1; - let i = 0; - let c: number; - let r2: number; - let g2: number; - let b2: number; - let diff: number; - - for (; i < DEFAULT_ANSI_COLORS.length; i++) { - c = DEFAULT_ANSI_COLORS[i].rgba; - r2 = c >>> 24; - g2 = c >>> 16 & 0xFF; - b2 = c >>> 8 & 0xFF; - // assume that alpha is 0xFF - - diff = matchColorDistance(r1, g1, b1, r2, g2, b2); - - if (diff === 0) { - li = i; - break; - } - - if (diff < ldiff) { - ldiff = diff; - li = i; - } - } - - return matchColorCache[hash] = li; - } - private _visualBell(): boolean { return false; // return this.options.bellStyle === 'visual' || @@ -1944,19 +1897,3 @@ function wasModifierKeyOnlyEvent(ev: KeyboardEvent): boolean { ev.keyCode === 17 || // Ctrl ev.keyCode === 18; // Alt } - -/** - * TODO: - * The below color-related code can be removed when true color is implemented. - * It's only purpose is to match true color requests with the closest matching - * ANSI color code. - */ - -const matchColorCache: {[colorRGBHash: number]: number} = {}; - -// http://stackoverflow.com/questions/1633828 -function matchColorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number { - return Math.pow(30 * (r1 - r2), 2) - + Math.pow(59 * (g1 - g2), 2) - + Math.pow(11 * (b1 - b2), 2); -} diff --git a/src/Types.ts b/src/Types.ts index d176799f..b46f858f 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -48,7 +48,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { insertMode: boolean; wraparoundMode: boolean; bracketedPasteMode: boolean; - curAttr: number; + curAttrData: IAttributeData; savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; @@ -70,7 +70,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { updateRange(y: number): void; scroll(isWrapped?: boolean): void; setgLevel(g: number): void; - eraseAttr(): number; + eraseAttrData(): IAttributeData; is(term: string): boolean; setgCharset(g: number, charset: ICharset): void; resize(x: number, y: number): void; @@ -78,7 +78,6 @@ export interface IInputHandlingTerminal extends IEventEmitter { reset(): void; showCursor(): void; refresh(start: number, end: number): void; - matchColor(r1: number, g1: number, b1: number): number; error(text: string, data?: any): void; setOption(key: string, value: any): void; tabSet(): void; @@ -289,17 +288,17 @@ export interface IBuffer { hasScrollback: boolean; savedY: number; savedX: number; - savedCurAttr: number; + savedCurAttrData: IAttributeData; isCursorInViewport: boolean; translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; getWrappedRangeForLine(y: number): { first: number, last: number }; nextStop(x?: number): number; prevStop(x?: number): number; - getBlankLine(attr: number, isWrapped?: boolean): IBufferLine; + getBlankLine(attr: IAttributeData, 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; + getNullCell(attr?: IAttributeData): ICellData; + getWhitespaceCell(attr?: IAttributeData): ICellData; } export interface IBufferSet extends IEventEmitter { @@ -308,7 +307,7 @@ export interface IBufferSet extends IEventEmitter { active: IBuffer; activateNormalBuffer(): void; - activateAltBuffer(fillAttr?: number): void; + activateAltBuffer(fillAttr?: IAttributeData): void; } export interface ISelectionManager { @@ -513,13 +512,50 @@ export interface IEscapeSequenceParser extends IDisposable { clearErrorHandler(): void; } -/** Cell data */ -export interface ICellData { - content: number; +/** RGB color type */ +export type IColorRGB = [number, number, number]; + +/** Attribute data */ +export interface IAttributeData { fg: number; bg: number; + + clone(): IAttributeData; + + // flags + isInverse(): number; + isBold(): number; + isUnderline(): number; + isBlink(): number; + isInvisible(): number; + isItalic(): number; + isDim(): number; + + // color modes + getColormodeFg(): number; + getColormodeBg(): number; + isFgRGB(): boolean; + isBgRGB(): boolean; + isFgPalette(): boolean; + isBgPalette(): boolean; + isFgDefault(): boolean; + isBgDefault(): boolean; + + // colors + getFgColor(channels?: boolean): number | IColorRGB; + getBgColor(channels?: boolean): number | IColorRGB; + + // shim for old API + getOldFlags(): number; + getOldFgColor(): number; + getOldBgColor(): number; +} + +/** Cell data */ +export interface ICellData extends IAttributeData { + content: number; combinedData: string; - combined: number; + isCombined: number; width: number; chars: string; code: number; diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index bf7c5616..2b2ba1bb 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -80,7 +80,6 @@ export class TextRenderLayer extends BaseRenderLayer { // 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; - const attr = this._cell.fg; let width = this._cell.width; // If true, indicates that the current character(s) to draw were joined. @@ -137,9 +136,9 @@ export class TextRenderLayer extends BaseRenderLayer { } } - const flags = attr >> 18; - let bg = attr & 0x1ff; - let fg = (attr >> 9) & 0x1ff; + const flags = this._cell.getOldFlags(); + let bg = this._cell.getOldBgColor(); + let fg = this._cell.getOldFgColor(); // If inverse flag is on, the foreground should become the background. if (flags & FLAGS.INVERSE) { diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index e06990d7..c80cea27 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -6,11 +6,9 @@ import jsdom = require('jsdom'); 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, CellData } from '../../BufferLine'; +import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR_DATA } from '../../Buffer'; +import { BufferLine, CellData, FgFlags, BgFlags, Attributes } from '../../BufferLine'; import { IBufferLine } from '../../Types'; -import { DEFAULT_COLOR } from '../atlas/Types'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; @@ -61,7 +59,9 @@ describe('DomRendererRowFactory', () => { describe('attributes', () => { it('should add class for bold', () => { - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)])); + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD; + lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -69,7 +69,9 @@ describe('DomRendererRowFactory', () => { }); it('should add class for italic', () => { - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)])); + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC; + lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -77,9 +79,12 @@ describe('DomRendererRowFactory', () => { }); it('should add classes for 256 foreground colors', () => { - const defaultAttrNoFgColor = (0 << 9) | (DEFAULT_COLOR << 0); + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg |= Attributes.CM_P256; for (let i = 0; i < 256; i++) { - lineData.setCell(0, CellData.fromCharData([defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)])); + cell.fg &= ~Attributes.PCOLOR_MASK; + cell.fg |= i; + lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` @@ -88,9 +93,12 @@ describe('DomRendererRowFactory', () => { }); it('should add classes for 256 background colors', () => { - const defaultAttrNoBgColor = (DEFAULT_ATTR << 9) | (0 << 0); + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.bg |= Attributes.CM_P256; for (let i = 0; i < 256; i++) { - lineData.setCell(0, CellData.fromCharData([defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)])); + cell.bg &= ~Attributes.PCOLOR_MASK; + cell.bg |= i; + lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` @@ -99,7 +107,10 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert colors', () => { - lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)])); + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE; + cell.bg |= Attributes.CM_P16 | 1; + lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -107,7 +118,10 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default fg color', () => { - lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)])); + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg |= FgFlags.INVERSE; + cell.bg |= Attributes.CM_P16 | 1; + lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -115,7 +129,9 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default bg color', () => { - lineData.setCell(0, CellData.fromCharData([(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)])); + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE; + lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' @@ -123,8 +139,12 @@ describe('DomRendererRowFactory', () => { }); it('should turn bold fg text bright', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg |= FgFlags.BOLD | Attributes.CM_P16; for (let i = 0; i < 8; i++) { - lineData.setCell(0, CellData.fromCharData([(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)])); + cell.fg &= ~Attributes.PCOLOR_MASK; + cell.fg |= i; + lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 83a4651e..41091a63 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -41,7 +41,6 @@ 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; // The character to the left is a wide character, drawing is owned by the char at x-1 @@ -54,9 +53,9 @@ export class DomRendererRowFactory { charElement.style.width = `${cellWidth * width}px`; } - const flags = attr >> 18; - let bg = attr & 0x1ff; - let fg = (attr >> 9) & 0x1ff; + const flags = this._cell.getOldFlags(); + let bg = this._cell.getOldBgColor(); + let fg = this._cell.getOldFgColor(); if (isCursorRow && x === cursorX) { charElement.classList.add(CURSOR_CLASS); diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index 9d525fbf..4772a9d8 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -4,12 +4,13 @@ */ 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, ICellData } from '../Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData, IAttributeData } from '../Types'; import { ICircularList, XtermListener } from '../common/Types'; import { Buffer } from '../Buffer'; import * as Browser from '../core/Platform'; import { ITheme, IDisposable, IMarker } from 'xterm'; import { Terminal } from '../Terminal'; +import { AttributeData } from '../BufferLine'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -187,7 +188,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { insertMode: boolean; wraparoundMode: boolean; bracketedPasteMode: boolean; - curAttr: number; + curAttrData = new AttributeData(); savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; @@ -222,7 +223,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { setgLevel(g: number): void { throw new Error('Method not implemented.'); } - eraseAttr(): number { + eraseAttrData(): IAttributeData { throw new Error('Method not implemented.'); } eraseRight(x: number, y: number): void { @@ -309,7 +310,7 @@ export class MockBuffer implements IBuffer { scrollTop: number; savedY: number; savedX: number; - savedCurAttr: number; + savedCurAttrData = new AttributeData(); translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string { return Buffer.prototype.translateBufferLineToString.apply(this, arguments); } @@ -325,7 +326,7 @@ export class MockBuffer implements IBuffer { setLines(lines: ICircularList): void { this.lines = lines; } - getBlankLine(attr: number, isWrapped?: boolean): IBufferLine { + getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine { return Buffer.prototype.getBlankLine.apply(this, arguments); } stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] { @@ -334,10 +335,10 @@ 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 { + getNullCell(attr?: IAttributeData): ICellData { throw new Error('Method not implemented.'); } - getWhitespaceCell(fg: number = 0, bg: number = 0): ICellData { + getWhitespaceCell(attr?: IAttributeData): ICellData { throw new Error('Method not implemented.'); } } From 70ded42beea95605e57e94fe3e2590f000fc77fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 13 Jan 2019 23:50:58 +0100 Subject: [PATCH 26/77] follow naming scheme --- src/BufferLine.ts | 4 ++-- src/InputHandler.test.ts | 36 ++++++++++++++++++------------------ src/Types.ts | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index e306346f..341e3009 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -220,8 +220,8 @@ export class AttributeData implements IAttributeData { public isDim(): number { return this.bg & BgFlags.DIM; } // color modes - public getColormodeFg(): number { return this.fg & Attributes.CM_MASK; } - public getColormodeBg(): number { return this.bg & Attributes.CM_MASK; } + public getFgColormode(): number { return this.fg & Attributes.CM_MASK; } + public getBgColormode(): number { return this.bg & Attributes.CM_MASK; } public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; } public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; } public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; } diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index df34cf04..7ca2d6fc 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -447,53 +447,53 @@ describe('InputHandler', () => { assert.equal(!!term.curAttrData.isInvisible(), false); }); it('colormode palette 16', () => { - assert.equal(term.curAttrData.getColormodeFg(), 0); // DEFAULT - assert.equal(term.curAttrData.getColormodeBg(), 0); // DEFAULT + assert.equal(term.curAttrData.getFgColormode(), 0); // DEFAULT + assert.equal(term.curAttrData.getBgColormode(), 0); // DEFAULT // lower 8 colors for (let i = 0; i < 8; ++i) { term.writeSync(`\x1b[${i + 30};${i + 40}m`); - assert.equal(term.curAttrData.getColormodeFg(), Attributes.CM_P16); + assert.equal(term.curAttrData.getFgColormode(), Attributes.CM_P16); assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getColormodeBg(), Attributes.CM_P16); + assert.equal(term.curAttrData.getBgColormode(), Attributes.CM_P16); assert.equal(term.curAttrData.getBgColor(), i); } // reset to DEFAULT term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getColormodeFg(), 0); - assert.equal(term.curAttrData.getColormodeBg(), 0); + assert.equal(term.curAttrData.getFgColormode(), 0); + assert.equal(term.curAttrData.getBgColormode(), 0); }); it('colormode palette 256', () => { - assert.equal(term.curAttrData.getColormodeFg(), 0); // DEFAULT - assert.equal(term.curAttrData.getColormodeBg(), 0); // DEFAULT + assert.equal(term.curAttrData.getFgColormode(), 0); // DEFAULT + assert.equal(term.curAttrData.getBgColormode(), 0); // DEFAULT // lower 8 colors for (let i = 0; i < 256; ++i) { term.writeSync(`\x1b[38;5;${i};48;5;${i}m`); - assert.equal(term.curAttrData.getColormodeFg(), Attributes.CM_P256); + assert.equal(term.curAttrData.getFgColormode(), Attributes.CM_P256); assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getColormodeBg(), Attributes.CM_P256); + assert.equal(term.curAttrData.getBgColormode(), Attributes.CM_P256); assert.equal(term.curAttrData.getBgColor(), i); } // reset to DEFAULT term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getColormodeFg(), 0); + assert.equal(term.curAttrData.getFgColormode(), 0); assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getColormodeBg(), 0); + assert.equal(term.curAttrData.getBgColormode(), 0); assert.equal(term.curAttrData.getBgColor(), -1); }); it('colormode RGB', () => { - assert.equal(term.curAttrData.getColormodeFg(), 0); // DEFAULT - assert.equal(term.curAttrData.getColormodeBg(), 0); // DEFAULT + assert.equal(term.curAttrData.getFgColormode(), 0); // DEFAULT + assert.equal(term.curAttrData.getBgColormode(), 0); // DEFAULT term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - assert.equal(term.curAttrData.getColormodeFg(), Attributes.CM_RGB); + assert.equal(term.curAttrData.getFgColormode(), Attributes.CM_RGB); assert.equal(term.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); assert.deepEqual(term.curAttrData.getFgColor(true), [1, 2, 3]); - assert.equal(term.curAttrData.getColormodeBg(), Attributes.CM_RGB); + assert.equal(term.curAttrData.getBgColormode(), Attributes.CM_RGB); assert.deepEqual(term.curAttrData.getBgColor(true), [4, 5, 6]); // reset to DEFAULT term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getColormodeFg(), 0); + assert.equal(term.curAttrData.getFgColormode(), 0); assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getColormodeBg(), 0); + assert.equal(term.curAttrData.getBgColormode(), 0); assert.equal(term.curAttrData.getBgColor(), -1); }); it('should zero missing RGB values', () => { diff --git a/src/Types.ts b/src/Types.ts index b46f858f..0de25e2d 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -532,8 +532,8 @@ export interface IAttributeData { isDim(): number; // color modes - getColormodeFg(): number; - getColormodeBg(): number; + getFgColormode(): number; + getBgColormode(): number; isFgRGB(): boolean; isBgRGB(): boolean; isFgPalette(): boolean; From 3515c0b220e90539b21f9b158b09f88e4400807d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 14 Jan 2019 00:46:04 +0100 Subject: [PATCH 27/77] DOM renderer with RGB support --- .../dom/DomRendererRowFactory.test.ts | 6 +- src/renderer/dom/DomRendererRowFactory.ts | 60 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index c80cea27..6a69a4ba 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -113,7 +113,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -124,7 +124,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -134,7 +134,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 41091a63..7ef26e1f 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -4,9 +4,8 @@ */ 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 { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { CellData } from '../../BufferLine'; export const BOLD_CLASS = 'xterm-bold'; @@ -53,10 +52,6 @@ export class DomRendererRowFactory { charElement.style.width = `${cellWidth * width}px`; } - const flags = this._cell.getOldFlags(); - let bg = this._cell.getOldBgColor(); - let fg = this._cell.getOldFgColor(); - if (isCursorRow && x === cursorX) { charElement.classList.add(CURSOR_CLASS); @@ -73,39 +68,44 @@ export class DomRendererRowFactory { } } - // If inverse flag is on, the foreground should become the background. - if (flags & FLAGS.INVERSE) { - const temp = bg; - bg = fg; - fg = temp; - if (fg === DEFAULT_COLOR) { - fg = INVERTED_DEFAULT_COLOR; - } - if (bg === DEFAULT_COLOR) { - bg = INVERTED_DEFAULT_COLOR; - } - } - - if (flags & FLAGS.BOLD) { - // Convert the FG color to the bold variant. This should not happen when - // the fg is the inverse default color as there is no bold variant. - if (fg < 8) { - fg += 8; - } + if (this._cell.isBold()) { charElement.classList.add(BOLD_CLASS); } - if (flags & FLAGS.ITALIC) { + if (this._cell.isItalic()) { charElement.classList.add(ITALIC_CLASS); } charElement.textContent = this._cell.chars || WHITESPACE_CELL_CHAR; - if (fg !== DEFAULT_COLOR) { - charElement.classList.add(`xterm-fg-${fg}`); + + const swapColor = !!this._cell.isInverse(); + + // fg + if (this._cell.isFgRGB()) { + let style = charElement.getAttribute('style') || ''; + style += `${swapColor ? 'background-' : ''}color: rgb(${(this._cell.getFgColor(true) as number[]).join(',')});`; + charElement.setAttribute('style', style); + } else if (this._cell.isFgPalette()) { + let fg = this._cell.getFgColor() as number; + if (this._cell.isBold() && fg < 8 && !swapColor) { + fg += 8; + } + charElement.classList.add(`xterm-${swapColor ? 'b' : 'f'}g-${fg}`); + } else if (swapColor) { + charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`); } - if (bg !== DEFAULT_COLOR) { - charElement.classList.add(`xterm-bg-${bg}`); + + // bg + if (this._cell.isBgRGB()) { + let style = charElement.getAttribute('style') || ''; + style += `${swapColor ? '' : 'background-'}color: rgb(${(this._cell.getBgColor(true) as number[]).join(',')});`; + charElement.setAttribute('style', style); + } else if (this._cell.isBgPalette()) { + charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._cell.getBgColor()}`); + } else if (swapColor) { + charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); } + fragment.appendChild(charElement); } return fragment; From 0802582c9bbbb62034742affdae97c0fddcec5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 14 Jan 2019 01:00:07 +0100 Subject: [PATCH 28/77] test cases for DOM renderer style changes --- .../dom/DomRendererRowFactory.test.ts | 22 +++++++++++++++++++ src/renderer/dom/DomRendererRowFactory.ts | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 6a69a4ba..d3b2100c 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -151,6 +151,28 @@ describe('DomRendererRowFactory', () => { ); } }); + + it('should set style attribute for RBG', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3; + cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; + lineData.setCell(0, cell); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + assert.equal(getFragmentHtml(fragment), + 'a' + ); + }); + + it('should correctly invert RGB colors', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE; + cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; + lineData.setCell(0, cell); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + assert.equal(getFragmentHtml(fragment), + 'a' + ); + }); }); }); diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 7ef26e1f..39b13d48 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -83,7 +83,7 @@ export class DomRendererRowFactory { // fg if (this._cell.isFgRGB()) { let style = charElement.getAttribute('style') || ''; - style += `${swapColor ? 'background-' : ''}color: rgb(${(this._cell.getFgColor(true) as number[]).join(',')});`; + style += `${swapColor ? 'background-' : ''}color:rgb(${(this._cell.getFgColor(true) as number[]).join(',')});`; charElement.setAttribute('style', style); } else if (this._cell.isFgPalette()) { let fg = this._cell.getFgColor() as number; @@ -98,7 +98,7 @@ export class DomRendererRowFactory { // bg if (this._cell.isBgRGB()) { let style = charElement.getAttribute('style') || ''; - style += `${swapColor ? '' : 'background-'}color: rgb(${(this._cell.getBgColor(true) as number[]).join(',')});`; + style += `${swapColor ? '' : 'background-'}color:rgb(${(this._cell.getBgColor(true) as number[]).join(',')});`; charElement.setAttribute('style', style); } else if (this._cell.isBgPalette()) { charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._cell.getBgColor()}`); From 9cf479bcfa462af3077e8c34578ca83948e2abc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 14 Jan 2019 08:26:14 +0100 Subject: [PATCH 29/77] cleanup rgb channel conversion --- src/BufferLine.ts | 16 ++++++++-------- src/InputHandler.test.ts | 8 ++++---- src/InputHandler.ts | 10 +++------- src/Types.ts | 4 ++-- src/renderer/dom/DomRendererRowFactory.ts | 10 +++++----- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 341e3009..fcb57a4d 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -188,14 +188,14 @@ export enum BgFlags { } export class AttributeData implements IAttributeData { - static toRGB(value: number): IColorRGB { + static toColorRGB(value: number): IColorRGB { return [ value >>> Attributes.RED_SHIFT & 255, value >>> Attributes.GREEN_SHIFT & 255, value & 255 ]; } - static fromRGB(value: IColorRGB): number { + static fromColorRGB(value: IColorRGB): number { return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255; } @@ -230,19 +230,19 @@ export class AttributeData implements IAttributeData { public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; } // colors - public getFgColor(channels: boolean = false): number | IColorRGB { + public getFgColor(): number { switch (this.fg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: return this.fg & Attributes.PCOLOR_MASK; - case Attributes.CM_RGB: return (channels) ? AttributeData.toRGB(this.fg & Attributes.RGB_MASK) : this.fg & Attributes.RGB_MASK; + case Attributes.CM_RGB: return this.fg & Attributes.RGB_MASK; default: return -1; // CM_DEFAULT defaults to -1 } } - public getBgColor(channels: boolean = false): number | IColorRGB { + public getBgColor(): number { switch (this.bg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: return this.bg & Attributes.PCOLOR_MASK; - case Attributes.CM_RGB: return (channels) ? AttributeData.toRGB(this.bg & Attributes.RGB_MASK) : this.bg & Attributes.RGB_MASK; + case Attributes.CM_RGB: return this.bg & Attributes.RGB_MASK; default: return -1; // CM_DEFAULT defaults to -1 } } @@ -273,7 +273,7 @@ export class AttributeData implements IAttributeData { return flags; } public getOldFgColor(): number { - let color = this.getFgColor() as number; + let color = this.getFgColor(); if (color === -1) { return 256; } @@ -290,7 +290,7 @@ export class AttributeData implements IAttributeData { return color; } public getOldBgColor(): number { - let color = this.getBgColor() as number; + let color = this.getBgColor(); if (color === -1) { return 256; } diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 7ca2d6fc..95e41175 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -9,7 +9,7 @@ import { MockInputHandlingTerminal, TestTerminal } from './ui/TestUtils.test'; import { DEFAULT_ATTR_DATA } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './Types'; -import { CellData, Attributes } from './BufferLine'; +import { CellData, Attributes, AttributeData } from './BufferLine'; describe('InputHandler', () => { describe('save and restore cursor', () => { @@ -486,9 +486,9 @@ describe('InputHandler', () => { term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); assert.equal(term.curAttrData.getFgColormode(), Attributes.CM_RGB); assert.equal(term.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); - assert.deepEqual(term.curAttrData.getFgColor(true), [1, 2, 3]); + assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [1, 2, 3]); assert.equal(term.curAttrData.getBgColormode(), Attributes.CM_RGB); - assert.deepEqual(term.curAttrData.getBgColor(true), [4, 5, 6]); + assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getBgColor()), [4, 5, 6]); // reset to DEFAULT term.writeSync(`\x1b[39;49m`); assert.equal(term.curAttrData.getFgColormode(), 0); @@ -499,7 +499,7 @@ describe('InputHandler', () => { it('should zero missing RGB values', () => { term.writeSync(`\x1b[38;2;1;2;3m`); term.writeSync(`\x1b[38;2;5m`); - assert.deepEqual(term.curAttrData.getFgColor(true), [5, 0, 0]); + assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]); }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 178507fa..4ea340bb 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -14,7 +14,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, Attributes, FgFlags, BgFlags } from './BufferLine'; +import { CellData, Attributes, FgFlags, BgFlags, AttributeData } from './BufferLine'; /** * Map collect to glevel. Used in `selectCharset`. @@ -1651,9 +1651,7 @@ export class InputHandler extends Disposable implements IInputHandler { i += 2; attr.fg |= Attributes.CM_RGB; attr.fg &= ~Attributes.RGB_MASK; - attr.fg |= (params[i] & 0xFF) << Attributes.RED_SHIFT; - attr.fg |= (params[i + 1] & 0xFF) << Attributes.GREEN_SHIFT; - attr.fg |= (params[i + 2] & 0xFF) << Attributes.BLUE_SHIFT; + attr.fg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]); i += 2; } else if (params[i + 1] === 5) { i += 2; @@ -1667,9 +1665,7 @@ export class InputHandler extends Disposable implements IInputHandler { i += 2; attr.bg |= Attributes.CM_RGB; attr.bg &= ~Attributes.RGB_MASK; - attr.bg |= (params[i] & 0xFF) << Attributes.RED_SHIFT; - attr.bg |= (params[i + 1] & 0xFF) << Attributes.GREEN_SHIFT; - attr.bg |= (params[i + 2] & 0xFF) << Attributes.BLUE_SHIFT; + attr.bg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]); i += 2; } else if (params[i + 1] === 5) { i += 2; diff --git a/src/Types.ts b/src/Types.ts index 0de25e2d..50ea560a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -542,8 +542,8 @@ export interface IAttributeData { isBgDefault(): boolean; // colors - getFgColor(channels?: boolean): number | IColorRGB; - getBgColor(channels?: boolean): number | IColorRGB; + getFgColor(): number; + getBgColor(): number; // shim for old API getOldFlags(): number; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 39b13d48..b37eb02b 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -6,7 +6,7 @@ import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer'; import { IBufferLine } from '../../Types'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { CellData } from '../../BufferLine'; +import { CellData, AttributeData } from '../../BufferLine'; export const BOLD_CLASS = 'xterm-bold'; export const ITALIC_CLASS = 'xterm-italic'; @@ -78,15 +78,15 @@ export class DomRendererRowFactory { charElement.textContent = this._cell.chars || WHITESPACE_CELL_CHAR; - const swapColor = !!this._cell.isInverse(); + const swapColor = this._cell.isInverse(); // fg if (this._cell.isFgRGB()) { let style = charElement.getAttribute('style') || ''; - style += `${swapColor ? 'background-' : ''}color:rgb(${(this._cell.getFgColor(true) as number[]).join(',')});`; + style += `${swapColor ? 'background-' : ''}color:rgb(${(AttributeData.toColorRGB(this._cell.getFgColor())).join(',')});`; charElement.setAttribute('style', style); } else if (this._cell.isFgPalette()) { - let fg = this._cell.getFgColor() as number; + let fg = this._cell.getFgColor(); if (this._cell.isBold() && fg < 8 && !swapColor) { fg += 8; } @@ -98,7 +98,7 @@ export class DomRendererRowFactory { // bg if (this._cell.isBgRGB()) { let style = charElement.getAttribute('style') || ''; - style += `${swapColor ? '' : 'background-'}color:rgb(${(this._cell.getBgColor(true) as number[]).join(',')});`; + style += `${swapColor ? '' : 'background-'}color:rgb(${(AttributeData.toColorRGB(this._cell.getBgColor())).join(',')});`; charElement.setAttribute('style', style); } else if (this._cell.isBgPalette()) { charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._cell.getBgColor()}`); From 94c19fb92a90ecf843954115cf45a1813035ebda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 15 Jan 2019 03:13:44 +0100 Subject: [PATCH 30/77] preliminarly RGB support in canvas renderer --- src/renderer/BaseRenderLayer.ts | 14 +++++++++++--- src/renderer/TextRenderLayer.ts | 14 ++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index f84fbe6b..4ef6bada 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -9,7 +9,7 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/T import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { is256Color } from './atlas/CharAtlasUtils'; -import { CellData } from '../BufferLine'; +import { CellData, AttributeData } from '../BufferLine'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -258,9 +258,14 @@ export abstract class BaseRenderLayer implements IRenderLayer { * This is used to validate whether a cached image can be used. * @param bold Whether the text is bold. */ - protected drawChars(terminal: ITerminal, chars: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean): void { + protected drawChars(terminal: ITerminal, chars: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean, cell: CellData): void { const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + if (cell.isFgRGB()) { + this._drawUncachedChars(terminal, chars, width, fg, x, y, bold && terminal.options.enableBold, dim, italic, cell); + return; + } + fg += drawInBrightColor ? 8 : 0; this._currentGlyphIdentifier.chars = chars; this._currentGlyphIdentifier.code = code; @@ -292,7 +297,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to draw at. * @param y The row to draw at. */ - private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean): void { + private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean, cell?: CellData): void { this._ctx.save(); this._ctx.font = this._getFont(terminal, bold, italic); this._ctx.textBaseline = 'middle'; @@ -302,6 +307,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { } else if (is256Color(fg)) { // 256 color support this._ctx.fillStyle = this._colors.ansi[fg].css; + if (cell && cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } } else { this._ctx.fillStyle = this._colors.foreground.css; } diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 2b2ba1bb..1e374dd9 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -10,7 +10,7 @@ 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'; +import { CellData, AttributeData } from '../BufferLine'; /** * This CharData looks like a null character, which will forc a clear and render @@ -126,7 +126,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.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) { width = 2; // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the @@ -189,7 +189,12 @@ export class TextRenderLayer extends BaseRenderLayer { if (bg === INVERTED_DEFAULT_COLOR) { nextFillStyle = this._colors.foreground.css; } else if (is256Color(bg)) { - nextFillStyle = this._colors.ansi[bg].css; + if (this._cell.isBgRGB()) { + console.log(`rgb(${AttributeData.toColorRGB(this._cell.getBgColor()).join(',')})`); + nextFillStyle = `rgb(${AttributeData.toColorRGB(this._cell.getBgColor()).join(',')})`; + } else { + nextFillStyle = this._colors.ansi[bg].css; + } } if (prevFillStyle === null) { @@ -245,7 +250,8 @@ export class TextRenderLayer extends BaseRenderLayer { terminal, chars, code, width, x, y, fg, bg, - !!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM), !!(flags & FLAGS.ITALIC) + !!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM), !!(flags & FLAGS.ITALIC), + this._cell ); }); } From 1536e795424ff5499d93199a8772f5cd6654fecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 15 Jan 2019 03:24:47 +0100 Subject: [PATCH 31/77] remove printf remnant --- src/renderer/TextRenderLayer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 1e374dd9..e0ef0f2d 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -190,7 +190,6 @@ export class TextRenderLayer extends BaseRenderLayer { nextFillStyle = this._colors.foreground.css; } else if (is256Color(bg)) { if (this._cell.isBgRGB()) { - console.log(`rgb(${AttributeData.toColorRGB(this._cell.getBgColor()).join(',')})`); nextFillStyle = `rgb(${AttributeData.toColorRGB(this._cell.getBgColor()).join(',')})`; } else { nextFillStyle = this._colors.ansi[bg].css; From ae50d84c5af5805d6b3b289d161a5a64aadf89b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 17 Jan 2019 00:07:25 +0100 Subject: [PATCH 32/77] fix characterjoiner to support AttrData --- src/renderer/CharacterJoinerRegistry.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 4cad7c72..459975c7 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,6 +1,7 @@ import { ITerminal, IBufferLine } from '../Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; import { CellData } from '../BufferLine'; +import { WHITESPACE_CELL_CHAR } from '../Buffer'; export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { @@ -43,7 +44,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { } const ranges: [number, number][] = []; - const lineStr = this._terminal.buffer.translateBufferLineToString(row, true); + const lineStr = line.translateToString(true); // Because some cells can be represented by multiple javascript characters, // we track the cell and the string indexes separately. This allows us to @@ -52,21 +53,19 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { let rangeStartColumn = 0; let currentStringIndex = 0; let rangeStartStringIndex = 0; - let rangeAttr = line.getFG(0) >> 9; + let rangeAttrFG = line.getFG(0); + let rangeAttrBG = line.getBG(0); - for (let x = 0; x < this._terminal.cols; x++) { + for (let x = 0; x < line.getTrimmedLength(); x++) { 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._cell.width === 0) { // If this character is of width 0, skip it. continue; } // End of range - if (attr !== rangeAttr) { + if (this._cell.fg !== rangeAttrFG || this._cell.bg !== rangeAttrBG) { // If we ended up with a sequence of more than one character, // look for ranges to join. if (x - rangeStartColumn > 1) { @@ -85,10 +84,11 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { // Reset our markers for a new range. rangeStartColumn = x; rangeStartStringIndex = currentStringIndex; - rangeAttr = attr; + rangeAttrFG = this._cell.fg; + rangeAttrBG = this._cell.bg; } - currentStringIndex += chars.length; + currentStringIndex += this._cell.chars.length || WHITESPACE_CELL_CHAR.length; } // Process any trailing ranges. @@ -154,7 +154,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { for (let x = startCol; x < this._terminal.cols; x++) { const width = line.getWidth(x); - const length = line.getString(x).length; + const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length; // We skip zero-width characters when creating the string to join the text // so we do the same here From 8d850d6c8b2c0c4d310712b69c51ca51febf3896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 17 Jan 2019 01:25:16 +0100 Subject: [PATCH 33/77] apply CellData to canvas renderer --- src/renderer/BaseRenderLayer.ts | 66 ++++++++------ src/renderer/TextRenderLayer.ts | 149 +++++++++++++------------------- 2 files changed, 102 insertions(+), 113 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 4ef6bada..e266e52a 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -4,12 +4,12 @@ */ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; -import { ITerminal } from '../Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; +import { ITerminal, ICellData } from '../Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier, DEFAULT_COLOR } from './atlas/Types'; import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; -import { is256Color } from './atlas/CharAtlasUtils'; import { CellData, AttributeData } from '../BufferLine'; +import { WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -258,22 +258,34 @@ export abstract class BaseRenderLayer implements IRenderLayer { * This is used to validate whether a cached image can be used. * @param bold Whether the text is bold. */ - protected drawChars(terminal: ITerminal, chars: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean, cell: CellData): void { - const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + protected drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { - if (cell.isFgRGB()) { - this._drawUncachedChars(terminal, chars, width, fg, x, y, bold && terminal.options.enableBold, dim, italic, cell); + // skip cache right away if we draw in RGB + if (cell.isFgRGB() || (cell.isInverse() && cell.isBgRGB())) { + this._drawUncachedChars(terminal, cell, x, y); return; } + let fg; + let bg; + if (cell.isInverse()) { + fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor(); + bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor(); + } else { + bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor(); + fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); + } + + const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + fg += drawInBrightColor ? 8 : 0; - this._currentGlyphIdentifier.chars = chars; - this._currentGlyphIdentifier.code = code; + this._currentGlyphIdentifier.chars = cell.chars || WHITESPACE_CELL_CHAR; + this._currentGlyphIdentifier.code = cell.code || WHITESPACE_CELL_CODE; this._currentGlyphIdentifier.bg = bg; this._currentGlyphIdentifier.fg = fg; - this._currentGlyphIdentifier.bold = bold && terminal.options.enableBold; - this._currentGlyphIdentifier.dim = dim; - this._currentGlyphIdentifier.italic = italic; + this._currentGlyphIdentifier.bold = cell.isBold() && terminal.options.enableBold; + this._currentGlyphIdentifier.dim = !!cell.isDim(); + this._currentGlyphIdentifier.italic = !!cell.isItalic(); const atlasDidDraw = this._charAtlas && this._charAtlas.draw( this._ctx, this._currentGlyphIdentifier, @@ -282,7 +294,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { ); if (!atlasDidDraw) { - this._drawUncachedChars(terminal, chars, width, fg, x, y, bold && terminal.options.enableBold, dim, italic); + this._drawUncachedChars(terminal, cell, x, y); } } @@ -297,32 +309,34 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to draw at. * @param y The row to draw at. */ - private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean, cell?: CellData): void { + private _drawUncachedChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { this._ctx.save(); - this._ctx.font = this._getFont(terminal, bold, italic); + this._ctx.font = this._getFont(terminal, cell.isBold() && terminal.options.enableBold, !!cell.isItalic()); this._ctx.textBaseline = 'middle'; - if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._colors.background.css; - } else if (is256Color(fg)) { - // 256 color support - this._ctx.fillStyle = this._colors.ansi[fg].css; - if (cell && cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + if (cell.isInverse()) { + if (cell.isBgDefault()) { + this._ctx.fillStyle = this._colors.background.css; + } else if (cell.isBgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; + } else { + this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; } - } else { - this._ctx.fillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else if (cell.isFgPalette()) { + this._ctx.fillStyle = this._colors.ansi[cell.getFgColor()].css; } this._clipRow(terminal, y); // Apply alpha to dim the character - if (dim) { + if (cell.isDim()) { this._ctx.globalAlpha = DIM_OPACITY; } // Draw the character this._ctx.fillText( - chars, + cell.chars, x * this._scaledCellWidth + this._scaledCharLeft, (y + 0.5) * this._scaledCellHeight + this._scaledCharTop); this._ctx.restore(); diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index e0ef0f2d..7ff2d002 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -3,14 +3,12 @@ * @license MIT */ -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'; +import { NULL_CELL_CODE } from '../Buffer'; +import { IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; +import { CharData, ITerminal, ICellData } from '../Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { is256Color } from './atlas/CharAtlasUtils'; -import { CellData, AttributeData } from '../BufferLine'; +import { CellData, AttributeData, Content } from '../BufferLine'; /** * This CharData looks like a null character, which will forc a clear and render @@ -59,14 +57,9 @@ export class TextRenderLayer extends BaseRenderLayer { lastRow: number, joinerRegistry: ICharacterJoinerRegistry | null, callback: ( - code: number, - chars: string, - width: number, + cell: ICellData, x: number, - y: number, - fg: number, - bg: number, - flags: number + y: number ) => void ): void { for (let y = firstRow; y <= lastRow; y++) { @@ -74,13 +67,8 @@ 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.code || 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 width = this._cell.width; + line.loadCell(x, this._cell); + let cell = this._cell; // If true, indicates that the current character(s) to draw were joined. let isJoined = false; @@ -88,7 +76,7 @@ export class TextRenderLayer extends BaseRenderLayer { // The character to the left is a wide character, drawing is owned by // the char at x-1 - if (width === 0) { + if (cell.width === 0) { continue; } @@ -101,14 +89,15 @@ export class TextRenderLayer extends BaseRenderLayer { // We already know the exact start and end column of the joined range, // so we get the string and width representing it directly - chars = terminal.buffer.translateBufferLineToString( - row, - true, - range[0], - range[1] - ); - width = range[1] - range[0]; - code = Infinity; + cell = CellData.fromCharData([ + 0, + line.translateToString(true, range[0], range[1]), + range[1] - range[0], + 0xFFFFFF + ]); + // hacky: patch attrs + cell.fg = this._cell.fg; + cell.bg = this._cell.bg; // Skip over the cells occupied by this range in the loop lastCharX = range[1] - 1; @@ -118,7 +107,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(chars, width, code)) { + if (!isJoined && this._isOverlapping(cell)) { // 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 @@ -127,7 +116,9 @@ export class TextRenderLayer extends BaseRenderLayer { // already in the correct state. // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; if (lastCharX < line.length - 1 && line.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) { - width = 2; + // patch width to 2 + cell.content &= ~Content.WIDTH_MASK; + cell.content |= 2 << Content.WIDTH_SHIFT; // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the // overlapping char is no longer to the left of the character and also when @@ -136,32 +127,10 @@ export class TextRenderLayer extends BaseRenderLayer { } } - const flags = this._cell.getOldFlags(); - let bg = this._cell.getOldBgColor(); - let fg = this._cell.getOldFgColor(); - - // If inverse flag is on, the foreground should become the background. - if (flags & FLAGS.INVERSE) { - const temp = bg; - bg = fg; - fg = temp; - if (fg === DEFAULT_COLOR) { - fg = INVERTED_DEFAULT_COLOR; - } - if (bg === DEFAULT_COLOR) { - bg = INVERTED_DEFAULT_COLOR; - } - } - callback( - code, - chars, - width, + cell, x, - y, - fg, - bg, - flags + y ); x = lastCharX; @@ -182,18 +151,23 @@ export class TextRenderLayer extends BaseRenderLayer { ctx.save(); - this._forEachCell(terminal, firstRow, lastRow, null, (code, chars, width, x, y, fg, bg, flags) => { + this._forEachCell(terminal, firstRow, lastRow, null, (cell, x, y) => { // libvte and xterm both draw the background (but not foreground) of invisible characters, // so we should too. let nextFillStyle = null; // null represents default background color - if (bg === INVERTED_DEFAULT_COLOR) { - nextFillStyle = this._colors.foreground.css; - } else if (is256Color(bg)) { - if (this._cell.isBgRGB()) { - nextFillStyle = `rgb(${AttributeData.toColorRGB(this._cell.getBgColor()).join(',')})`; + + if (cell.isInverse()) { + if (cell.isFgDefault()) { + nextFillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; } else { - nextFillStyle = this._colors.ansi[bg].css; + nextFillStyle = this._colors.ansi[cell.getFgColor()].css; } + } else if (cell.isBgRGB()) { + nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; + } else if (cell.isBgPalette()) { + nextFillStyle = this._colors.ansi[cell.getBgColor()].css; } if (prevFillStyle === null) { @@ -228,30 +202,31 @@ export class TextRenderLayer extends BaseRenderLayer { } private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void { - this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, (code, chars, width, x, y, fg, bg, flags) => { - if (flags & FLAGS.INVISIBLE) { + this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, (cell, x, y) => { + if (cell.isInvisible()) { return; } - if (flags & FLAGS.UNDERLINE) { + if (cell.isUnderline()) { this._ctx.save(); - if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._colors.background.css; - } else if (is256Color(fg)) { - // 256 color support - this._ctx.fillStyle = this._colors.ansi[fg].css; - } else { - this._ctx.fillStyle = this._colors.foreground.css; + + if (cell.isInverse()) { + if (cell.isBgDefault()) { + this._ctx.fillStyle = this._colors.background.css; + } else if (cell.isBgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; + } else { + this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; + } + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else if (cell.isFgPalette()) { + this._ctx.fillStyle = this._colors.ansi[cell.getFgColor()].css; } - this.fillBottomLineAtCells(x, y, width); + + this.fillBottomLineAtCells(x, y, cell.width); this._ctx.restore(); } - this.drawChars( - terminal, chars, code, - width, x, y, - fg, bg, - !!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM), !!(flags & FLAGS.ITALIC), - this._cell - ); + this.drawChars(terminal, cell, x, y); }); } @@ -277,21 +252,21 @@ export class TextRenderLayer extends BaseRenderLayer { /** * Whether a character is overlapping to the next cell. */ - private _isOverlapping(char: string, width: number, code: number): boolean { + private _isOverlapping(cell: ICellData): boolean { // Only single cell characters can be overlapping, rendering issues can // occur without this check - if (width !== 1) { + if (cell.width !== 1) { return false; } // We assume that any ascii character will not overlap - if (code < 256) { + if (cell.code < 256) { return false; } // Deliver from cache if available - if (this._characterOverlapCache.hasOwnProperty(char)) { - return this._characterOverlapCache[char]; + if (this._characterOverlapCache.hasOwnProperty(cell.chars)) { + return this._characterOverlapCache[cell.chars]; } // Setup the font @@ -301,13 +276,13 @@ export class TextRenderLayer extends BaseRenderLayer { // Measure the width of the character, but Math.floor it // because that is what the renderer does when it calculates // the character dimensions we are comparing against - const overlaps = Math.floor(this._ctx.measureText(char).width) > this._characterWidth; + const overlaps = Math.floor(this._ctx.measureText(cell.chars).width) > this._characterWidth; // Restore the original context this._ctx.restore(); // Cache and return - this._characterOverlapCache[char] = overlaps; + this._characterOverlapCache[cell.chars] = overlaps; return overlaps; } From df739ab8146fe8e5f3a776641ef7b889f044f414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 17 Jan 2019 01:31:59 +0100 Subject: [PATCH 34/77] remove old color and flag shims --- src/BufferLine.ts | 116 --------------------------------------- src/InputHandler.test.ts | 10 ++-- src/Types.ts | 5 -- 3 files changed, 5 insertions(+), 126 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index fcb57a4d..3d94d5f6 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -5,64 +5,8 @@ import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer'; import { stringFromCodePoint } from './core/input/TextDecoder'; -import { FLAGS } from './renderer/Types'; -import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; -/** - * TODO: - * The below color-related code can be removed when true color is implemented. - * It's only purpose is to match true color requests with the closest matching - * ANSI color code. - */ -const matchColorCache: {[colorRGBHash: number]: number} = {}; - -// http://stackoverflow.com/questions/1633828 -function matchColorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number { - return Math.pow(30 * (r1 - r2), 2) - + Math.pow(59 * (g1 - g2), 2) - + Math.pow(11 * (b1 - b2), 2); -} - -function matchColor(r1: number, g1: number, b1: number): number { - const hash = (r1 << 16) | (g1 << 8) | b1; - - if (matchColorCache[hash] !== null && matchColorCache[hash] !== undefined) { - return matchColorCache[hash]; - } - - let ldiff = Infinity; - let li = -1; - let i = 0; - let c: number; - let r2: number; - let g2: number; - let b2: number; - let diff: number; - - for (; i < DEFAULT_ANSI_COLORS.length; i++) { - c = DEFAULT_ANSI_COLORS[i].rgba; - r2 = c >>> 24; - g2 = c >>> 16 & 0xFF; - b2 = c >>> 8 & 0xFF; - // assume that alpha is 0xFF - - diff = matchColorDistance(r1, g1, b1, r2, g2, b2); - - if (diff === 0) { - li = i; - break; - } - - if (diff < ldiff) { - ldiff = diff; - li = i; - } - } - - return matchColorCache[hash] = li; -} - /** * buffer memory layout: * @@ -246,66 +190,6 @@ export class AttributeData implements IAttributeData { default: return -1; // CM_DEFAULT defaults to -1 } } - - public getOldFlags(): number { - let flags = 0; - if (this.isBold()) { - flags |= FLAGS.BOLD; - } - if (this.isUnderline()) { - flags |= FLAGS.UNDERLINE; - } - if (this.isBlink()) { - flags |= FLAGS.BLINK; - } - if (this.isDim()) { - flags |= FLAGS.DIM; - } - if (this.isInvisible()) { - flags |= FLAGS.INVISIBLE; - } - if (this.isInverse()) { - flags |= FLAGS.INVERSE; - } - if (this.isItalic()) { - flags |= FLAGS.ITALIC; - } - return flags; - } - public getOldFgColor(): number { - let color = this.getFgColor(); - if (color === -1) { - return 256; - } - if (this.isFgRGB()) { - color = matchColor( - (this.fg & Attributes.RED_MASK) >> Attributes.RED_SHIFT, - (this.fg & Attributes.GREEN_MASK) >> Attributes.GREEN_SHIFT, - (this.fg & Attributes.BLUE_MASK) >> Attributes.BLUE_SHIFT - ); - if (color === -1) { - color = 256; - } - } - return color; - } - public getOldBgColor(): number { - let color = this.getBgColor(); - if (color === -1) { - return 256; - } - if (this.isBgRGB()) { - color = matchColor( - (this.bg & Attributes.RED_MASK) >> Attributes.RED_SHIFT, - (this.bg & Attributes.GREEN_MASK) >> Attributes.GREEN_SHIFT, - (this.bg & Attributes.BLUE_MASK) >> Attributes.BLUE_SHIFT - ); - if (color === -1) { - color = 256; - } - } - return color; - } } /** diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 95e41175..0b7f7ad3 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -357,14 +357,14 @@ 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).loadCell(4, new CellData()).getOldFgColor())).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).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).loadCell(4, new CellData()).getOldFgColor())).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1); }); it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); @@ -373,7 +373,7 @@ describe('InputHandler', () => { // Text color of 'TEST' should be default expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); // Text color of 'JUNK' should be red - expect((term.buffer.lines.get(1).loadCell(0, new CellData()).getOldFgColor())).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1); }); it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); @@ -390,12 +390,12 @@ describe('InputHandler', () => { 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).loadCell(0, new CellData()).getOldFgColor())).to.equal(1); + expect((term.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).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).loadCell(10, new CellData()).getOldBgColor()).to.equal(2); + expect(term.buffer.lines.get(20).loadCell(10, new CellData()).getBgColor()).to.equal(2); }); }); diff --git a/src/Types.ts b/src/Types.ts index 50ea560a..6636b04d 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -544,11 +544,6 @@ export interface IAttributeData { // colors getFgColor(): number; getBgColor(): number; - - // shim for old API - getOldFlags(): number; - getOldFgColor(): number; - getOldBgColor(): number; } /** Cell data */ From 2370038523499ab8cf8f6b9566482b6e44530fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 17 Jan 2019 01:41:48 +0100 Subject: [PATCH 35/77] always draw RGB uncached --- src/renderer/BaseRenderLayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index e266e52a..e0d3e731 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -261,7 +261,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { // skip cache right away if we draw in RGB - if (cell.isFgRGB() || (cell.isInverse() && cell.isBgRGB())) { + if (cell.isFgRGB() || cell.isBgRGB()) { this._drawUncachedChars(terminal, cell, x, y); return; } 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 36/77] 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 2b9b2233..f58d0da0 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 ade21435..3f7ff076 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 37/77] 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 3f7ff076..09f12028 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 38/77] code formatting --- src/BufferLine.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 09f12028..84a7bfcd 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 39/77] 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 2a874f9d..894cc204 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 84a7bfcd..120a15fc 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 86483a29..a9555c76 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 dad86fa2..9588505c 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 40/77] 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 57de302a..59475adb 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 894cc204..7dfcbd2c 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 120a15fc..14518454 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 7cab3882..8608c6fa 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 a9555c76..ccd74a2f 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 e2399173..9d49a860 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 b5165490..10043006 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 e06ceb78..08bceb34 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 9588505c..a08bd485 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 f84fbe6b..addd028c 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 4cad7c72..eb44bb58 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 18ba7ada..c3b751fa 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 bf7c5616..be022239 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 83a4651e..5bc5fffa 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 65dbf88460853785ce259131ea788980b25b1b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 1 Feb 2019 21:44:51 +0100 Subject: [PATCH 41/77] apply bright shift for uncached draws --- src/renderer/BaseRenderLayer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 11de5860..2df61521 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -325,7 +325,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { } else if (cell.isFgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; } else if (cell.isFgPalette()) { - this._ctx.fillStyle = this._colors.ansi[cell.getFgColor()].css; + let fg = cell.getFgColor(); + if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + fg += 8; + } + this._ctx.fillStyle = this._colors.ansi[fg].css; } this._clipRow(terminal, y); From 51e2cdfafb697a8076d9122fcf0e7bbc8f1840e8 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Wed, 6 Mar 2019 21:45:53 +0000 Subject: [PATCH 42/77] WIP: First draft --- src/addons/webLinks/webLinks.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index f0d69cc5..b9f2925a 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -36,6 +36,13 @@ function handleLink(event: MouseEvent, uri: string): void { */ export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { options.matchIndex = 1; + + handler = (event, uri) => { + if (!term.hasSelection()) { + window.open(uri, '_blank'); + } + }; + term.registerLinkMatcher(strictUrlRegex, handler, options); } From 5878aa099dcf093a9480bf110cde28f8ee011b69 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Thu, 7 Mar 2019 21:00:07 +0000 Subject: [PATCH 43/77] Cleaner aproach --- src/addons/webLinks/webLinks.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index b9f2925a..19200c90 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -24,9 +24,7 @@ const start = '(?:^|' + negatedDomainCharacterSet + ')('; const end = ')($|' + negatedPathCharacterSet + ')'; const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); -function handleLink(event: MouseEvent, uri: string): void { - window.open(uri, '_blank'); -} +let handleLink: (event: MouseEvent, uri: string) => void; /** * Initialize the web links addon, registering the link matcher. @@ -37,17 +35,17 @@ function handleLink(event: MouseEvent, uri: string): void { export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { options.matchIndex = 1; - handler = (event, uri) => { - if (!term.hasSelection()) { - window.open(uri, '_blank'); - } - }; - term.registerLinkMatcher(strictUrlRegex, handler, options); } export function apply(terminalConstructor: typeof Terminal): void { (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void { + handleLink = (event, uri) => { + if (!this.hasSelection()) { + window.open(uri, '_blank'); + } + }; + webLinksInit(this, handler, options); }; } From 525af9c36a3b57702d6048737d37aeb394464892 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Thu, 7 Mar 2019 21:30:32 +0000 Subject: [PATCH 44/77] Fix #1773 --- src/renderer/dom/DomRenderer.ts | 2 +- src/renderer/dom/DomRendererRowFactory.test.ts | 11 +++++++++-- src/renderer/dom/DomRendererRowFactory.ts | 7 ++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index c5ef212d..5bff3d1c 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -75,7 +75,7 @@ export class DomRenderer extends EventEmitter implements IRenderer { this._updateDimensions(); this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); - this._rowFactory = new DomRendererRowFactory(document); + this._rowFactory = new DomRendererRowFactory(_terminal, document); this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); this._terminal.screenElement.appendChild(this._rowContainer); diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 67342da0..ab308077 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -9,17 +9,24 @@ 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 { IBufferLine } from '../../Types'; +import { IBufferLine, ITerminal } from '../../Types'; import { DEFAULT_COLOR } from '../atlas/Types'; +import { MockTerminal } from '../../ui/TestUtils.test'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; + let term: ITerminal; let rowFactory: DomRendererRowFactory; let lineData: IBufferLine; beforeEach(() => { dom = new jsdom.JSDOM(''); - rowFactory = new DomRendererRowFactory(dom.window.document); + + term = new MockTerminal(); + term.options.enableBold = true; + term.options.drawBoldTextInBrightColors = true; + + rowFactory = new DomRendererRowFactory(term, dom.window.document); lineData = createEmptyLineData(2); }); diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 8bcde39a..f1865175 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -5,7 +5,7 @@ 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 { FLAGS } from '../Types'; -import { IBufferLine } from '../../Types'; +import { IBufferLine, ITerminal } from '../../Types'; import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types'; export const BOLD_CLASS = 'xterm-bold'; @@ -17,6 +17,7 @@ export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'; export class DomRendererRowFactory { constructor( + private _terminal: ITerminal, private _document: Document ) { } @@ -88,10 +89,10 @@ export class DomRendererRowFactory { } } - if (flags & FLAGS.BOLD) { + if (flags & FLAGS.BOLD && this._terminal.options.enableBold) { // Convert the FG color to the bold variant. This should not happen when // the fg is the inverse default color as there is no bold variant. - if (fg < 8) { + if (fg < 8 && this._terminal.options.drawBoldTextInBrightColors) { fg += 8; } charElement.classList.add(BOLD_CLASS); From b4bef0986cb99f2b43e4cb8ab372ec839bdb0422 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Sun, 10 Mar 2019 22:03:38 +0000 Subject: [PATCH 45/77] Remove circular dependency --- src/renderer/dom/DomRenderer.ts | 2 +- src/renderer/dom/DomRendererRowFactory.test.ts | 12 +++++------- src/renderer/dom/DomRendererRowFactory.ts | 8 ++++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 5bff3d1c..3a5f29e6 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -75,7 +75,7 @@ export class DomRenderer extends EventEmitter implements IRenderer { this._updateDimensions(); this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); - this._rowFactory = new DomRendererRowFactory(_terminal, document); + this._rowFactory = new DomRendererRowFactory(_terminal.options, document); this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); this._terminal.screenElement.appendChild(this._rowContainer); diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index ab308077..76c07781 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -9,24 +9,22 @@ 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 { IBufferLine, ITerminal } from '../../Types'; +import { IBufferLine, ITerminalOptions } from '../../Types'; import { DEFAULT_COLOR } from '../atlas/Types'; -import { MockTerminal } from '../../ui/TestUtils.test'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; - let term: ITerminal; + const options: ITerminalOptions = {}; let rowFactory: DomRendererRowFactory; let lineData: IBufferLine; beforeEach(() => { dom = new jsdom.JSDOM(''); - term = new MockTerminal(); - term.options.enableBold = true; - term.options.drawBoldTextInBrightColors = true; + options.enableBold = true; + options.drawBoldTextInBrightColors = true; - rowFactory = new DomRendererRowFactory(term, dom.window.document); + rowFactory = new DomRendererRowFactory(options, dom.window.document); lineData = createEmptyLineData(2); }); diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index f1865175..206c0c81 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -5,7 +5,7 @@ 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 { FLAGS } from '../Types'; -import { IBufferLine, ITerminal } from '../../Types'; +import { IBufferLine, ITerminalOptions } from '../../Types'; import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types'; export const BOLD_CLASS = 'xterm-bold'; @@ -17,7 +17,7 @@ export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'; export class DomRendererRowFactory { constructor( - private _terminal: ITerminal, + private _terminalOptions: ITerminalOptions, private _document: Document ) { } @@ -89,10 +89,10 @@ export class DomRendererRowFactory { } } - if (flags & FLAGS.BOLD && this._terminal.options.enableBold) { + if (flags & FLAGS.BOLD && this._terminalOptions.enableBold) { // Convert the FG color to the bold variant. This should not happen when // the fg is the inverse default color as there is no bold variant. - if (fg < 8 && this._terminal.options.drawBoldTextInBrightColors) { + if (fg < 8 && this._terminalOptions.drawBoldTextInBrightColors) { fg += 8; } charElement.classList.add(BOLD_CLASS); From 62cfa642cbe5b2bce1d8cc0d71c2a8cdc1198bdb Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Mon, 11 Mar 2019 20:54:17 +0000 Subject: [PATCH 46/77] Better aproach, check i a selection is being performed --- src/addons/webLinks/webLinks.ts | 11 +++-------- src/ui/MouseZoneManager.ts | 12 ++++++++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index 19200c90..f0d69cc5 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -24,7 +24,9 @@ const start = '(?:^|' + negatedDomainCharacterSet + ')('; const end = ')($|' + negatedPathCharacterSet + ')'; const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); -let handleLink: (event: MouseEvent, uri: string) => void; +function handleLink(event: MouseEvent, uri: string): void { + window.open(uri, '_blank'); +} /** * Initialize the web links addon, registering the link matcher. @@ -34,18 +36,11 @@ let handleLink: (event: MouseEvent, uri: string) => void; */ export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { options.matchIndex = 1; - term.registerLinkMatcher(strictUrlRegex, handler, options); } export function apply(terminalConstructor: typeof Terminal): void { (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void { - handleLink = (event, uri) => { - if (!this.hasSelection()) { - window.open(uri, '_blank'); - } - }; - webLinksInit(this, handler, options); }; } diff --git a/src/ui/MouseZoneManager.ts b/src/ui/MouseZoneManager.ts index 79022723..3b848795 100644 --- a/src/ui/MouseZoneManager.ts +++ b/src/ui/MouseZoneManager.ts @@ -29,6 +29,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _tooltipTimeout: number = null; private _currentZone: IMouseZone = null; private _lastHoverCoords: [number, number] = [null, null]; + private _initialSelectionLenght: number; constructor( private _terminal: ITerminal @@ -157,6 +158,10 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { } private _onMouseDown(e: MouseEvent): void { + // Store current terminal selection length, to check if we're performing + // a selection operation + this._initialSelectionLenght = this._terminal.getSelection().length; + // Ignore the event if there are no zones active if (!this._areZonesActive) { return; @@ -186,9 +191,12 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { } private _onClick(e: MouseEvent): void { - // Find the active zone and click it if found + // Find the active zone and click it if found and no selection was + // being performed const zone = this._findZoneEventAt(e); - if (zone) { + const currentSelectionLength = this._terminal.getSelection().length; + + if (zone && currentSelectionLength === this._initialSelectionLenght) { zone.clickCallback(e); e.preventDefault(); e.stopImmediatePropagation(); From 27ebe8c353cb8988f71e665ce4765e9c5f02c597 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Mon, 11 Mar 2019 20:58:42 +0000 Subject: [PATCH 47/77] Use new vscode serverReadyAction --- .vscode/launch.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c7bf7381..2ec26fca 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -38,7 +38,11 @@ "run", "start-debug" ], - "port": 9229 + "port": 9229, + "serverReadyAction": { + "action": "openExternally", + "pattern": "App listening to (http://.*?:[0-9]+)" + } } ] } From 0d63cecc6bed2561cae1ca99e6c64d3f35a3cea6 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Tue, 19 Mar 2019 14:04:16 +0800 Subject: [PATCH 48/77] Modify uses Signed-off-by: Jianhui Zhao --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 481590a7..40de04cf 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters. - [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure. - [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace. -- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server. +- [**rtty**](https://github.com/zhaojh329/rtty): Access your terminals from anywhere via the web. - [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS. - [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker. - [**Azure Data Studio**](https://github.com/Microsoft/azuredatastudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux. From 131e4f78bdc1e036feed1a0aee03cddee25f6e63 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 23 Mar 2019 09:08:55 -0700 Subject: [PATCH 49/77] Fix renderer pausing to not full refresh every time Fixes #1975 --- src/renderer/Renderer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index b8ef87aa..2c1b516a 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -85,6 +85,7 @@ export class Renderer extends EventEmitter implements IRenderer { this._isPaused = entry.intersectionRatio === 0; if (!this._isPaused && this._needsFullRefresh) { this._terminal.refresh(0, this._terminal.rows - 1); + this._needsFullRefresh = false; } } From 4dbe8ec1db2d6005186b547cd2fb87f710aeb48c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 23 Mar 2019 10:58:26 -0700 Subject: [PATCH 50/77] Let consumers decide whether winptyCompat should be active --- demo/client.ts | 5 ++++- src/addons/winptyCompat/winptyCompat.ts | 6 ------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index a3a912f6..70996c4a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -30,7 +30,10 @@ Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); Terminal.applyAddon(search); Terminal.applyAddon(webLinks); -Terminal.applyAddon(winptyCompat); +const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; +if (isWindows) { + Terminal.applyAddon(winptyCompat); +} let term; diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index d162f4e9..58f59fd9 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -13,12 +13,6 @@ const WHITESPACE_CELL_CODE = 32; export function winptyCompatInit(terminal: Terminal): void { const addonTerminal = terminal; - // Don't do anything when the platform is not Windows - const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; - if (!isWindows) { - return; - } - (addonTerminal._core as any).isWinptyCompatEnabled = true; // Winpty does not support wraparound mode which means that lines will never From f9df863ee05244f34536ae9c64606d38abd04a61 Mon Sep 17 00:00:00 2001 From: Jesse Stolwijk Date: Mon, 25 Mar 2019 22:17:16 +0100 Subject: [PATCH 51/77] Add blinking cursor to DomRenderer --- src/renderer/dom/DomRenderer.ts | 15 +++++++-- .../dom/DomRendererRowFactory.test.ts | 31 ++++++++++++------- src/renderer/dom/DomRendererRowFactory.ts | 7 ++++- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index c5ef212d..1da20d95 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -9,7 +9,7 @@ import { ITheme } from 'xterm'; import { EventEmitter } from '../../common/EventEmitter'; import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; -import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; +import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; @@ -165,12 +165,22 @@ export class DomRenderer extends EventEmitter implements IRenderer { `${this._terminalSelector} span.${ITALIC_CLASS} {` + ` font-style: italic;` + `}`; + // Blink animation + styles += + `@keyframes blink {` + + ` 0 % { opacity: 1.0; }` + + ` 50% { opacity: 0.0; }` + + ` 100 % { opacity: 1.0; }` + + `}`; // Cursor styles += `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` + ` outline: 1px solid ${this.colorManager.colors.cursor.css};` + ` outline-offset: -1px;` + `}` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS} {` + + ` animation: blink 1s step-end infinite;` + + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + ` background-color: ${this.colorManager.colors.cursor.css};` + ` color: ${this.colorManager.colors.cursorAccent.css};` + @@ -328,6 +338,7 @@ export class DomRenderer extends EventEmitter implements IRenderer { const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y; const cursorX = this._terminal.buffer.x; + const cursorBlink = this._terminal.options.cursorBlink; for (let y = start; y <= end; y++) { const rowElement = this._rowElements[y]; @@ -336,7 +347,7 @@ export class DomRenderer extends EventEmitter implements IRenderer { const row = y + terminal.buffer.ydisp; const lineData = terminal.buffer.lines.get(row); const cursorStyle = terminal.options.cursorStyle; - rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, this.dimensions.actualCellWidth, terminal.cols)); + rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols)); } this._terminal.emit('refresh', {start, end}); diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 67342da0..07e7686d 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -25,7 +25,7 @@ describe('DomRendererRowFactory', () => { describe('createRow', () => { it('should not create anything for an empty row', () => { - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), '' ); @@ -35,7 +35,7 @@ describe('DomRendererRowFactory', () => { lineData.set(0, [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]); // There should be no element for the following "empty" cell lineData.set(1, [DEFAULT_ATTR, '', 0, undefined]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), '' ); @@ -43,17 +43,24 @@ describe('DomRendererRowFactory', () => { it('should add class for cursor and cursor style', () => { for (const style of ['block', 'bar', 'underline']) { - const fragment = rowFactory.createRow(lineData, true, style, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, true, style, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), ` ` ); } }); + it('should add class for cursor blink', () => { + const fragment = rowFactory.createRow(lineData, true, 'block', 0, true, 5, 20); + assert.equal(getFragmentHtml(fragment), + ` ` + ); + }); + 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)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 1); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 1); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -62,7 +69,7 @@ describe('DomRendererRowFactory', () => { describe('attributes', () => { it('should add class for bold', () => { lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -70,7 +77,7 @@ describe('DomRendererRowFactory', () => { it('should add class for italic', () => { lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -80,7 +87,7 @@ describe('DomRendererRowFactory', () => { 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)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), `a` ); @@ -91,7 +98,7 @@ describe('DomRendererRowFactory', () => { 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)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), `a` ); @@ -100,7 +107,7 @@ describe('DomRendererRowFactory', () => { it('should correctly invert colors', () => { lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -108,7 +115,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)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -116,7 +123,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)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' ); @@ -125,7 +132,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)]); - const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), `a` ); diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 8bcde39a..fd263b21 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -11,6 +11,7 @@ import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types'; export const BOLD_CLASS = 'xterm-bold'; export const ITALIC_CLASS = 'xterm-italic'; export const CURSOR_CLASS = 'xterm-cursor'; +export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block'; export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar'; export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'; @@ -21,7 +22,7 @@ export class DomRendererRowFactory { ) { } - public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment { + public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); // Find the line length first, this prevents the need to output a bunch of @@ -62,6 +63,10 @@ export class DomRendererRowFactory { if (isCursorRow && x === cursorX) { charElement.classList.add(CURSOR_CLASS); + if (cursorBlink) { + charElement.classList.add(CURSOR_BLINK_CLASS); + } + switch (cursorStyle) { case 'bar': charElement.classList.add(CURSOR_STYLE_BAR_CLASS); From e346f8bba37ee094f4710cd1c8dc43438fa01a79 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 29 Mar 2019 19:46:24 -0700 Subject: [PATCH 52/77] Prevent scroll on focus Fixes #1981 --- src/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 4a85758f..c2497df4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -343,7 +343,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II */ public focus(): void { if (this.textarea) { - this.textarea.focus(); + this.textarea.focus({ preventScroll: true }); } } From 0b78011fa34e2f79dd3f2cd68c111a171f45c68c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 30 Mar 2019 14:05:24 -0700 Subject: [PATCH 53/77] Adopt project references Recent versions of TypeScript has improved the performance of project references so they are now viable to switch over to. --- demo/index.html | 4 ++-- demo/server.js | 1 + gulpfile.js | 10 +++++----- package.json | 10 +++------- src/common/tsconfig.json | 14 ++------------ src/core/tsconfig.json | 20 ++++++-------------- src/tsconfig-base.json | 15 +++++++++++++++ src/tsconfig-library-base.json | 11 +++++++++++ src/tsconfig.all.json | 16 ++++++++++++++++ tsconfig.json => src/tsconfig.json | 21 ++++++++++++--------- yarn.lock | 8 ++++---- 11 files changed, 77 insertions(+), 53 deletions(-) create mode 100644 src/tsconfig-base.json create mode 100644 src/tsconfig-library-base.json create mode 100644 src/tsconfig.all.json rename tsconfig.json => src/tsconfig.json (53%) diff --git a/demo/index.html b/demo/index.html index 12f7cb98..370a51ed 100644 --- a/demo/index.html +++ b/demo/index.html @@ -2,8 +2,8 @@ xterm.js demo - - + + diff --git a/demo/server.js b/demo/server.js index 5ff9ca61..37df9916 100644 --- a/demo/server.js +++ b/demo/server.js @@ -11,6 +11,7 @@ function startServer() { logs = {}; app.use('/build', express.static(__dirname + '/../build')); + app.use('/src', express.static(__dirname + '/../src')); app.get('/', function(req, res){ res.sendFile(__dirname + '/index.html'); diff --git a/gulpfile.js b/gulpfile.js index 9af8d6e4..bbb4d6e1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,9 +16,9 @@ const ts = require('gulp-typescript'); const util = require('gulp-util'); const buildDir = process.env.BUILD_DIR || 'build'; -const tsProject = ts.createProject('tsconfig.json'); -let srcDir = tsProject.config.compilerOptions.rootDir; -let outDir = tsProject.config.compilerOptions.outDir; +const tsProject = ts.createProject('src/tsconfig.json'); +let srcDir = './src'; +let outDir = './lib'; const addons = fs.readdirSync(`${__dirname}/src/addons`); @@ -61,7 +61,7 @@ gulp.task('browserify', function() { }; let bundleStream = browserify(browserifyOptions) .bundle() - .pipe(source('xterm.js')) + .pipe(source(`xterm.js`)) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true, sourceRoot: '..'})) .pipe(sourcemaps.write('./')) @@ -136,6 +136,6 @@ gulp.task('sorcery-addons', ['browserify-addons'], function () { }) }); -gulp.task('build', ['sorcery', 'sorcery-addons']); +gulp.task('build', ['css', 'sorcery', 'sorcery-addons']); gulp.task('test', ['mocha']); gulp.task('default', ['build']); diff --git a/package.json b/package.json index c5fad515..946db609 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "ts-loader": "^4.5.0", "tslint": "^5.9.1", "tslint-consistent-codestyle": "^1.13.0", - "typescript": "3.1", + "typescript": "3.4", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "webpack": "^4.17.1", @@ -50,20 +50,16 @@ "start-debug": "node --inspect-brk demo/start", "start-zmodem": "node demo/zmodem/app", "lint": "tslint 'src/**/*.ts' './demo/**/*.ts'", - "pretest": "npm run layering", "test": "npm run mocha", "posttest": "npm run lint", "test-debug": "node --inspect-brk node_modules/.bin/gulp test", "test-suite": "gulp mocha-suite --test", "test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha", "mocha": "gulp test", - "tsc": "tsc", - "prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"", + "prebuild": "tsc -b ./src/tsconfig.all.json", "build": "gulp build", "prepublish": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", - "watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"", - "watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"", - "layering": "concurrently --kill-others-on-fail --names \"common,core\" \"tsc -p ./src/common\" \"tsc -p ./src/core\"" + "watch": "tsc -b -w ./src/tsconfig.all.json" } } diff --git a/src/common/tsconfig.json b/src/common/tsconfig.json index 19dd0273..b40bb2f5 100644 --- a/src/common/tsconfig.json +++ b/src/common/tsconfig.json @@ -1,17 +1,7 @@ { + "extends": "../tsconfig-library-base", "compilerOptions": { - "target": "es5", - "lib": [ - "es5" - ], - "rootDir": ".", - "noEmit": true, - "strict": true, - "pretty": true, - "types": [ - "../../node_modules/@types/mocha", - "../../" - ] + "outDir": "../../lib" }, "include": [ "./**/*" diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json index 4f024a28..41e41f0c 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -1,20 +1,12 @@ { + "extends": "../tsconfig-library-base", "compilerOptions": { - "target": "es5", - "lib": [ - "es5" - ], - "rootDir": ".", - "noEmit": true, - "strict": true, - "pretty": true, - "types": [ - "../../node_modules/@types/mocha", - "../../" - ] + "outDir": "../../lib" }, "include": [ - "./**/*", - "../common/**/*" + "./**/*" + ], + "references": [ + { "path": "../common" } ] } diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json new file mode 100644 index 00000000..5c6afcc5 --- /dev/null +++ b/src/tsconfig-base.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ "es5" ], + "rootDir": ".", + + "sourceMap": true, + "removeComments": true, + "pretty": true, + + "incremental": true, + + "skipLibCheck": true + } +} diff --git a/src/tsconfig-library-base.json b/src/tsconfig-library-base.json new file mode 100644 index 00000000..c82e0873 --- /dev/null +++ b/src/tsconfig-library-base.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig-base.json", + "compilerOptions": { + "types": [ + "../../node_modules/@types/mocha", + "../../" + ], + "composite": true, + "strict": true + } +} diff --git a/src/tsconfig.all.json b/src/tsconfig.all.json new file mode 100644 index 00000000..bee5df32 --- /dev/null +++ b/src/tsconfig.all.json @@ -0,0 +1,16 @@ +{ + "files": [], + "include": [], + "references": [ + { "path": "." }, + { "path": "./addons/attach" }, + { "path": "./addons/fit" }, + { "path": "./addons/fullscreen" }, + { "path": "./addons/search" }, + { "path": "./addons/terminado" }, + { "path": "./addons/webLinks" }, + { "path": "./addons/winptyCompat" }, + { "path": "./addons/zmodem" } + ] +} + \ No newline at end of file diff --git a/tsconfig.json b/src/tsconfig.json similarity index 53% rename from tsconfig.json rename to src/tsconfig.json index 2d1d6e35..0aa3abb8 100644 --- a/tsconfig.json +++ b/src/tsconfig.json @@ -1,7 +1,7 @@ { + "extends": "./tsconfig-base", "compilerOptions": { "module": "commonjs", - "target": "es5", "lib": [ "dom", "es5", @@ -9,19 +9,22 @@ "scripthost", "es2015.promise" ], - "rootDir": "src", - "outDir": "lib", - "sourceMap": true, - "removeComments": true, - "preserveWatchOutput": true, + "rootDir": ".", + "outDir": "../lib", + "noUnusedLocals": true, "noImplicitAny": true }, "include": [ - "src/**/*", - "typings/xterm.d.ts" + "./**/*", + "../typings/xterm.d.ts" ], "exclude": [ - "src/addons/**/*" + "./addons/**/*" + ], + "references": [ + { "path": "./common" }, + { "path": "./core" } ] } + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5555321d..896db4bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6633,10 +6633,10 @@ typedarray@^0.0.6, typedarray@~0.0.5: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.1: - version "3.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" - integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== +typescript@3.4: + version "3.4.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6" + integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q== uglify-es@^3.3.4: version "3.3.9" From b400f9ca3b74fbcb0c7506ec6b4456d835f1fc5a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 30 Mar 2019 14:08:24 -0700 Subject: [PATCH 54/77] Don't clear terminal on yarn watch --- package.json | 2 +- src/addons/attach/tsconfig.json | 3 +-- src/addons/fit/tsconfig.json | 1 - src/addons/fullscreen/tsconfig.json | 1 - src/addons/search/tsconfig.json | 1 - src/addons/terminado/tsconfig.json | 1 - src/addons/webLinks/tsconfig.json | 1 - src/addons/winptyCompat/tsconfig.json | 1 - src/addons/zmodem/tsconfig.json | 1 - 9 files changed, 2 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 946db609..d917039b 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,6 @@ "build": "gulp build", "prepublish": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", - "watch": "tsc -b -w ./src/tsconfig.all.json" + "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" } } diff --git a/src/addons/attach/tsconfig.json b/src/addons/attach/tsconfig.json index 359fbd24..2f39102c 100644 --- a/src/addons/attach/tsconfig.json +++ b/src/addons/attach/tsconfig.json @@ -10,8 +10,7 @@ "outDir": "../../../lib/addons/attach/", "sourceMap": true, "removeComments": true, - "declaration": true, - "preserveWatchOutput": true + "declaration": true }, "include": [ "**/*.ts", diff --git a/src/addons/fit/tsconfig.json b/src/addons/fit/tsconfig.json index 489ccdfe..3458d23a 100644 --- a/src/addons/fit/tsconfig.json +++ b/src/addons/fit/tsconfig.json @@ -11,7 +11,6 @@ "sourceMap": true, "removeComments": true, "declaration": true, - "preserveWatchOutput": true, "types": [ "../../node_modules/@types/mocha" ] diff --git a/src/addons/fullscreen/tsconfig.json b/src/addons/fullscreen/tsconfig.json index 05e6df68..0c74c25c 100644 --- a/src/addons/fullscreen/tsconfig.json +++ b/src/addons/fullscreen/tsconfig.json @@ -11,7 +11,6 @@ "sourceMap": true, "removeComments": true, "declaration": true, - "preserveWatchOutput": true, "types": [ "../../node_modules/@types/mocha" ] diff --git a/src/addons/search/tsconfig.json b/src/addons/search/tsconfig.json index 87899cda..6a1611a5 100644 --- a/src/addons/search/tsconfig.json +++ b/src/addons/search/tsconfig.json @@ -11,7 +11,6 @@ "sourceMap": true, "removeComments": true, "declaration": true, - "preserveWatchOutput": true, "types": [ "../../node_modules/@types/mocha" ] diff --git a/src/addons/terminado/tsconfig.json b/src/addons/terminado/tsconfig.json index 91c18314..e2e19445 100644 --- a/src/addons/terminado/tsconfig.json +++ b/src/addons/terminado/tsconfig.json @@ -10,7 +10,6 @@ "sourceMap": true, "removeComments": true, "declaration": true, - "preserveWatchOutput": true, "types": [ "../../node_modules/@types/mocha" ] diff --git a/src/addons/webLinks/tsconfig.json b/src/addons/webLinks/tsconfig.json index 18105aa2..9c4f1176 100644 --- a/src/addons/webLinks/tsconfig.json +++ b/src/addons/webLinks/tsconfig.json @@ -11,7 +11,6 @@ "sourceMap": true, "removeComments": true, "declaration": true, - "preserveWatchOutput": true, "types": [ "../../node_modules/@types/mocha" ] diff --git a/src/addons/winptyCompat/tsconfig.json b/src/addons/winptyCompat/tsconfig.json index 9fc4d25e..fa48c963 100644 --- a/src/addons/winptyCompat/tsconfig.json +++ b/src/addons/winptyCompat/tsconfig.json @@ -10,7 +10,6 @@ "sourceMap": true, "removeComments": true, "declaration": true, - "preserveWatchOutput": true, "types": [ "../../node_modules/@types/mocha" ] diff --git a/src/addons/zmodem/tsconfig.json b/src/addons/zmodem/tsconfig.json index 2b49f537..7d821b7c 100644 --- a/src/addons/zmodem/tsconfig.json +++ b/src/addons/zmodem/tsconfig.json @@ -10,7 +10,6 @@ "sourceMap": true, "removeComments": true, "declaration": true, - "preserveWatchOutput": true, "types": [ "../../node_modules/@types/mocha" ] From 18f4dc6b3eda8432231cc580fa4221c13e1113ec Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 30 Mar 2019 14:09:51 -0700 Subject: [PATCH 55/77] Remove concurrently --- package.json | 1 - yarn.lock | 87 +++------------------------------------------------- 2 files changed, 4 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index d917039b..539b96da 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "@types/webpack": "^4.4.11", "browserify": "^13.3.0", "chai": "3.5.0", - "concurrently": "^3.5.1", "coveralls": "^3.0.1", "express": "4.13.4", "express-ws": "2.0.0-rc.1", diff --git a/yarn.lock b/yarn.lock index 896db4bf..440aa4f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1275,11 +1275,6 @@ combined-stream@1.0.6, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" - integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0= - commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -1338,21 +1333,6 @@ concat-with-sourcemaps@^1.0.0: dependencies: source-map "^0.6.1" -concurrently@^3.5.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.6.0.tgz#c25e34b156a9d5bd4f256a0d85f6192438ae481f" - integrity sha512-6XiIYtYzmGEccNZFkih5JOH92jLA4ulZArAYy5j1uDSdrPLB3KzdE8GW7t2fHPcg9ry2+5LP9IEYzXzxw9lFdA== - dependencies: - chalk "^2.4.1" - commander "2.6.0" - date-fns "^1.23.0" - lodash "^4.5.1" - read-pkg "^3.0.0" - rx "2.3.24" - spawn-command "^0.0.2-1" - supports-color "^3.2.3" - tree-kill "^1.1.0" - configstore@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021" @@ -1595,11 +1575,6 @@ data-urls@^1.0.0: whatwg-mimetype "^2.0.0" whatwg-url "^6.4.0" -date-fns@^1.23.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" - integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw== - date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -1933,7 +1908,7 @@ errno@^0.1.3, errno@~0.1.7: dependencies: prr "~1.0.1" -error-ex@^1.2.0, error-ex@^1.3.1: +error-ex@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== @@ -3625,7 +3600,7 @@ jsesc@^1.3.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== @@ -3815,16 +3790,6 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" @@ -4037,7 +4002,7 @@ lodash.templatesettings@^3.0.0: lodash._reinterpolate "^3.0.0" lodash.escape "^3.0.0" -lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.5.1: +lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== @@ -4984,14 +4949,6 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -5092,13 +5049,6 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - pause-stream@0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" @@ -5384,15 +5334,6 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -5688,11 +5629,6 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rx@2.3.24: - version "2.3.24" - resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" - integrity sha1-FPlQpCF9fjXapxu8vljv9o6ksrc= - rxjs@^6.1.0: version "6.3.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.1.tgz#878a1a8c64b8a5da11dcf74b5033fe944cdafb84" @@ -6025,11 +5961,6 @@ sparkles@^1.0.0: resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== -spawn-command@^0.0.2-1: - version "0.0.2-1" - resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" - integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= - spawn-wrap@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.2.tgz#cff58e73a8224617b6561abdc32586ea0c82248c" @@ -6271,11 +6202,6 @@ strip-bom@^1.0.0: first-chunk-stream "^1.0.0" is-utf8 "^0.2.0" -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -6305,7 +6231,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^3.1.2, supports-color@^3.2.3: +supports-color@^3.1.2: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= @@ -6521,11 +6447,6 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -tree-kill@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" - integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg== - trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" From f80924fb8d5155142a3bd258f64a71176680f7de Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 30 Mar 2019 14:12:15 -0700 Subject: [PATCH 56/77] Remove zmodem demo --- demo/server.js | 1 - demo/zmodem/app.js | 87 --------- demo/zmodem/index.html | 128 -------------- demo/zmodem/main.js | 388 ----------------------------------------- 4 files changed, 604 deletions(-) delete mode 100644 demo/zmodem/app.js delete mode 100644 demo/zmodem/index.html delete mode 100644 demo/zmodem/main.js diff --git a/demo/server.js b/demo/server.js index 37df9916..c41110ff 100644 --- a/demo/server.js +++ b/demo/server.js @@ -10,7 +10,6 @@ function startServer() { var terminals = {}, logs = {}; - app.use('/build', express.static(__dirname + '/../build')); app.use('/src', express.static(__dirname + '/../src')); app.get('/', function(req, res){ diff --git a/demo/zmodem/app.js b/demo/zmodem/app.js deleted file mode 100644 index 7124c222..00000000 --- a/demo/zmodem/app.js +++ /dev/null @@ -1,87 +0,0 @@ -var express = require('express'); -var app = express(); -var expressWs = require('express-ws')(app); -var os = require('os'); -var pty = require('node-pty'); - -var terminals = {}, - logs = {}; - -app.use('/build', express.static(__dirname + '/../../build')); -app.use('/demo', express.static(__dirname + '/../../demo')); -app.use('/zmodemjs', express.static(__dirname + '/../../node_modules/zmodem.js/dist')); - -app.get('/', function(req, res){ - res.sendFile(__dirname + '/index.html'); -}); - -app.get('/style.css', function(req, res){ - res.sendFile(__dirname + '../style.css'); -}); - -app.get('/main.js', function(req, res){ - res.sendFile(__dirname + '/main.js'); -}); - -app.post('/terminals', function (req, res) { - var cols = parseInt(req.query.cols), - rows = parseInt(req.query.rows), - term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], { - encoding: null, - name: 'xterm-color', - cols: cols || 80, - rows: rows || 24, - cwd: process.env.PWD, - env: process.env - }); - - console.log('Created terminal with PID: ' + term.pid); - terminals[term.pid] = term; - logs[term.pid] = ''; - term.on('data', function(data) { - logs[term.pid] += data; - }); - res.send(term.pid.toString()); - res.end(); -}); - -app.post('/terminals/:pid/size', function (req, res) { - var pid = parseInt(req.params.pid), - cols = parseInt(req.query.cols), - rows = parseInt(req.query.rows), - term = terminals[pid]; - - term.resize(cols, rows); - console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.'); - res.end(); -}); - -app.ws('/terminals/:pid', function (ws, req) { - var term = terminals[parseInt(req.params.pid)]; - console.log('Connected to terminal ' + term.pid); - ws.send(logs[term.pid]); - - term.on('data', function(data) { - try { - ws.send(data); - } catch (ex) { - // The WebSocket is not open, ignore - } - }); - ws.on('message', function(msg) { - term.write(msg); - }); - ws.on('close', function () { - term.kill(); - console.log('Closed terminal ' + term.pid); - // Clean things up - delete terminals[term.pid]; - delete logs[term.pid]; - }); -}); - -var port = process.env.PORT || 3000, - host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0'; - -console.log('App listening to http://' + host + ':' + port); -app.listen(port, host); diff --git a/demo/zmodem/index.html b/demo/zmodem/index.html deleted file mode 100644 index aee7742a..00000000 --- a/demo/zmodem/index.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - xterm.js demo - - - - - - - - - - - - - - - - -

xterm.js: xterm, in the browser

- -
- -
- - - - - - - - - -
- -
-

Actions

-

- - -

-
-
-

Options

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-
-

Size

-
-
- - -
-
- - -
-
-
-
-

Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.

-

* ZMODEM file transfers are supported via an addon. To try it out, install lrzsz onto the remote peer, then run rz to send from your browser or sz <file> to send from the remote peer.

- - - diff --git a/demo/zmodem/main.js b/demo/zmodem/main.js deleted file mode 100644 index 619ef2b8..00000000 --- a/demo/zmodem/main.js +++ /dev/null @@ -1,388 +0,0 @@ -"use strict"; - -var term, - protocol, - socketURL, - socket, - pid; - -Terminal.applyAddon(fit); -Terminal.applyAddon(attach); -Terminal.applyAddon(zmodem); -Terminal.applyAddon(search); - -var terminalContainer = document.getElementById('terminal-container'), - actionElements = { - findNext: document.querySelector('#find-next'), - findPrevious: document.querySelector('#find-previous') - }, - optionElements = { - cursorBlink: document.querySelector('#option-cursor-blink'), - cursorStyle: document.querySelector('#option-cursor-style'), - scrollback: document.querySelector('#option-scrollback'), - tabstopwidth: document.querySelector('#option-tabstopwidth'), - bellStyle: document.querySelector('#option-bell-style') - }, - colsElement = document.getElementById('cols'), - rowsElement = document.getElementById('rows'); - -function setTerminalSize() { - var cols = parseInt(colsElement.value, 10); - var rows = parseInt(rowsElement.value, 10); - var viewportElement = document.querySelector('.xterm-viewport'); - var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth; - var width = (cols * term.charMeasure.width + 20 /*room for scrollbar*/).toString() + 'px'; - var height = (rows * term.charMeasure.height).toString() + 'px'; - - terminalContainer.style.width = width; - terminalContainer.style.height = height; - term.resize(cols, rows); -} - -colsElement.addEventListener('change', setTerminalSize); -rowsElement.addEventListener('change', setTerminalSize); - -actionElements.findNext.addEventListener('keypress', function (e) { - if (e.key === "Enter") { - e.preventDefault(); - term.findNext(actionElements.findNext.value); - } -}); -actionElements.findPrevious.addEventListener('keypress', function (e) { - if (e.key === "Enter") { - e.preventDefault(); - term.findPrevious(actionElements.findPrevious.value); - } -}); - -optionElements.cursorBlink.addEventListener('change', function () { - term.setOption('cursorBlink', optionElements.cursorBlink.checked); -}); -optionElements.cursorStyle.addEventListener('change', function () { - term.setOption('cursorStyle', optionElements.cursorStyle.value); -}); -optionElements.bellStyle.addEventListener('change', function () { - term.setOption('bellStyle', optionElements.bellStyle.value); -}); -optionElements.scrollback.addEventListener('change', function () { - term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10)); -}); -optionElements.tabstopwidth.addEventListener('change', function () { - term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10)); -}); - -createTerminal(); - -function createTerminal() { - // Clean terminal - while (terminalContainer.children.length) { - terminalContainer.removeChild(terminalContainer.children[0]); - } - term = new Terminal({ - cursorBlink: optionElements.cursorBlink.checked, - scrollback: parseInt(optionElements.scrollback.value, 10), - tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10) - }); - term.on('resize', function (size) { - if (!pid) { - return; - } - var cols = size.cols, - rows = size.rows, - url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows; - - fetch(url, {method: 'POST'}); - }); - protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://'; - socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; - - term.open(terminalContainer); - term.fit(); - - // fit is called within a setTimeout, cols and rows need this. - setTimeout(function () { - colsElement.value = term.cols; - rowsElement.value = term.rows; - - // Set terminal size again to set the specific dimensions on the demo - setTerminalSize(); - - fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) { - - res.text().then(function (pid) { - window.pid = pid; - socketURL += pid; - socket = new WebSocket(socketURL); - socket.onopen = runRealTerminal; - socket.onclose = runFakeTerminal; - socket.onerror = runFakeTerminal; - - term.zmodemAttach(socket, { - noTerminalWriteOutsideSession: true, - } ); - - term.on("zmodemRetract", () => { - start_form.style.display = "none"; - start_form.onsubmit = null; - }); - - term.on("zmodemDetect", (detection) => { - function do_zmodem() { - term.detach(); - let zsession = detection.confirm(); - - var promise; - - if (zsession.type === "receive") { - promise = _handle_receive_session(zsession); - } - else { - promise = _handle_send_session(zsession); - } - - promise.catch( console.error.bind(console) ).then( () => { - term.attach(socket); - } ); - } - - if (_auto_zmodem()) { - do_zmodem(); - } - else { - start_form.style.display = ""; - start_form.onsubmit = function(e) { - start_form.style.display = "none"; - - if (document.getElementById("zmstart_yes").checked) { - do_zmodem(); - } - else { - detection.deny(); - } - }; - } - }); - }); - }); - }, 0); -} - -//---------------------------------------------------------------------- -// UI STUFF - -function _show_file_info(xfer) { - var file_info = xfer.get_details(); - - document.getElementById("name").textContent = file_info.name; - document.getElementById("size").textContent = file_info.size; - document.getElementById("mtime").textContent = file_info.mtime; - document.getElementById("files_remaining").textContent = file_info.files_remaining; - document.getElementById("bytes_remaining").textContent = file_info.bytes_remaining; - - document.getElementById("mode").textContent = "0" + file_info.mode.toString(8); - - var xfer_opts = xfer.get_options(); - ["conversion", "management", "transport", "sparse"].forEach( (lbl) => { - document.getElementById(`zfile_${lbl}`).textContent = xfer_opts[lbl]; - } ); - - document.getElementById("zm_file").style.display = ""; -} -function _hide_file_info() { - document.getElementById("zm_file").style.display = "none"; -} - -function _save_to_disk(xfer, buffer) { - return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name); -} - -var skipper_button = document.getElementById("zm_progress_skipper"); -var skipper_button_orig_text = skipper_button.textContent; - -function _show_progress() { - skipper_button.disabled = false; - skipper_button.textContent = skipper_button_orig_text; - - document.getElementById("bytes_received").textContent = 0; - document.getElementById("percent_received").textContent = 0; - - document.getElementById("zm_progress").style.display = ""; -} - -function _update_progress(xfer) { - var total_in = xfer.get_offset(); - - document.getElementById("bytes_received").textContent = total_in; - - var percent_received = 100 * total_in / xfer.get_details().size; - document.getElementById("percent_received").textContent = percent_received.toFixed(2); -} - -function _hide_progress() { - document.getElementById("zm_progress").style.display = "none"; -} - -var start_form = document.getElementById("zm_start"); - -function _auto_zmodem() { - return document.getElementById("zmodem-auto").checked; -} - -// END UI STUFF -//---------------------------------------------------------------------- - -function _handle_receive_session(zsession) { - zsession.on("offer", function(xfer) { - current_receive_xfer = xfer; - - _show_file_info(xfer); - - var offer_form = document.getElementById("zm_offer"); - - function on_form_submit() { - offer_form.style.display = "none"; - - //START - //if (offer_form.zmaccept.value) { - if (_auto_zmodem() || document.getElementById("zmaccept_yes").checked) { - _show_progress(); - - var FILE_BUFFER = []; - xfer.on("input", (payload) => { - _update_progress(xfer); - FILE_BUFFER.push( new Uint8Array(payload) ); - }); - xfer.accept().then( - () => { - _save_to_disk(xfer, FILE_BUFFER); - }, - console.error.bind(console) - ); - } - else { - xfer.skip(); - } - //END - } - - if (_auto_zmodem()) { - on_form_submit(); - } - else { - offer_form.onsubmit = on_form_submit; - offer_form.style.display = ""; - } - } ); - - var promise = new Promise( (res) => { - zsession.on("session_end", () => { - _hide_file_info(); - _hide_progress(); - res(); - } ); - } ); - - zsession.start(); - - return promise; -} - -function _handle_send_session(zsession) { - var choose_form = document.getElementById("zm_choose"); - choose_form.style.display = ""; - - var file_el = document.getElementById("zm_files"); - - var promise = new Promise( (res) => { - file_el.onchange = function(e) { - choose_form.style.display = "none"; - - var files_obj = file_el.files; - - Zmodem.Browser.send_files( - zsession, - files_obj, - { - on_offer_response(obj, xfer) { - if (xfer) _show_progress(); - //console.log("offer", xfer ? "accepted" : "skipped"); - }, - on_progress(obj, xfer) { - _update_progress(xfer); - }, - on_file_complete(obj) { - //console.log("COMPLETE", obj); - _hide_progress(); - }, - } - ).then(_hide_progress).then( - zsession.close.bind(zsession), - console.error.bind(console) - ).then( () => { - _hide_file_info(); - _hide_progress(); - res(); - } ); - }; - } ); - - return promise; -} - -//This is here to allow canceling of an in-progress ZMODEM transfer. -var current_receive_xfer; - -//Called from HTML directly. -function skip_current_file() { - current_receive_xfer.skip(); - - skipper_button.disabled = true; - skipper_button.textContent = "Waiting for server to acknowledge skip …"; -} - -function runRealTerminal() { - term.attach(socket); - - term._initialized = true; -} - -function runFakeTerminal() { - if (term._initialized) { - return; - } - - term._initialized = true; - - var shellprompt = '$ '; - - term.prompt = function () { - term.write('\r\n' + shellprompt); - }; - - term.writeln('Welcome to xterm.js'); - term.writeln('This is a local terminal emulation, without a real terminal in the back-end.'); - term.writeln('Type some keys and commands to play around.'); - term.writeln(''); - term.prompt(); - - term.on('key', function (key, ev) { - var printable = ( - !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey - ); - - if (ev.keyCode == 13) { - term.prompt(); - } else if (ev.keyCode == 8) { - // Do not delete the prompt - if (term.x > 2) { - term.write('\b \b'); - } - } else if (printable) { - term.write(key); - } - }); - - term.on('paste', function (data, ev) { - term.write(data); - }); -} From e182e3d4e43af884a61d0fadf0d5f34792a76cd5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 30 Mar 2019 14:52:48 -0700 Subject: [PATCH 57/77] Fix demo on non-Windows Broke in #1978 --- demo/client.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index 70996c4a..aaaf2829 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -102,7 +102,9 @@ function createTerminal(): void { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - term.winptyCompatInit(); + if (isWindows) { + term.winptyCompatInit(); + } term.webLinksInit(); term.fit(); term.focus(); From bf9d879efa9897827fbeb9f58022cbc0960ad439 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 30 Mar 2019 14:59:31 -0700 Subject: [PATCH 58/77] Fix typo --- src/ui/MouseZoneManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/MouseZoneManager.ts b/src/ui/MouseZoneManager.ts index 3b848795..372dccc5 100644 --- a/src/ui/MouseZoneManager.ts +++ b/src/ui/MouseZoneManager.ts @@ -29,7 +29,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _tooltipTimeout: number = null; private _currentZone: IMouseZone = null; private _lastHoverCoords: [number, number] = [null, null]; - private _initialSelectionLenght: number; + private _initialSelectionLength: number; constructor( private _terminal: ITerminal @@ -160,7 +160,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _onMouseDown(e: MouseEvent): void { // Store current terminal selection length, to check if we're performing // a selection operation - this._initialSelectionLenght = this._terminal.getSelection().length; + this._initialSelectionLength = this._terminal.getSelection().length; // Ignore the event if there are no zones active if (!this._areZonesActive) { @@ -196,7 +196,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { const zone = this._findZoneEventAt(e); const currentSelectionLength = this._terminal.getSelection().length; - if (zone && currentSelectionLength === this._initialSelectionLenght) { + if (zone && currentSelectionLength === this._initialSelectionLength) { zone.clickCallback(e); e.preventDefault(); e.stopImmediatePropagation(); From 4d008c66f11ed06835e6b2270305397033c81ebe Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 30 Mar 2019 15:06:25 -0700 Subject: [PATCH 59/77] Recommend 127.0.0.1:3000 on mac and linux too Fixes #1986 --- .vscode/launch.json | 5 +---- demo/server.js | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2ec26fca..e5bad7b3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,10 +23,7 @@ "type": "chrome", "request": "launch", "name": "Demo Client", - "url": "http://0.0.0.0:3000", - "windows": { - "url": "http://127.0.0.1:3000" - }, + "url": "http://127.0.0.1:3000", "webRoot": "${workspaceFolder}/" }, { diff --git a/demo/server.js b/demo/server.js index 5ff9ca61..0587977c 100644 --- a/demo/server.js +++ b/demo/server.js @@ -99,7 +99,7 @@ function startServer() { var port = process.env.PORT || 3000, host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0'; - console.log('App listening to http://' + host + ':' + port); + console.log('App listening to http://127.0.0.1:' + port); app.listen(port, host); } From 4a3609f8051f19bf3f53c65cfdfcead9116e2f2e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 31 Mar 2019 23:21:11 -0700 Subject: [PATCH 60/77] Move platform to common and remove dom dependence in common Because common/ imports 'xterm', it also imported dom accidentally. Fixes #1990 --- src/AccessibilityManager.ts | 2 +- src/SelectionManager.ts | 2 +- src/Terminal.ts | 2 +- src/common/EventEmitter.ts | 3 +-- src/common/Lifecycle.ts | 2 +- src/{core => common}/Platform.ts | 7 +++++++ src/common/Types.ts | 11 ++++++++++- src/common/tsconfig.json | 5 ++++- src/core/tsconfig.json | 2 +- src/renderer/atlas/CharAtlasGenerator.ts | 2 +- src/renderer/atlas/DynamicCharAtlas.ts | 2 +- src/tsconfig-base.json | 4 +--- src/tsconfig-library-base.json | 4 ---- src/tsconfig.json | 3 +-- src/ui/TestUtils.test.ts | 2 +- 15 files changed, 32 insertions(+), 21 deletions(-) rename src/{core => common}/Platform.ts (90%) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index fa0121ad..877676c9 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -5,7 +5,7 @@ import * as Strings from './Strings'; import { ITerminal, IBuffer } from './Types'; -import { isMac } from './core/Platform'; +import { isMac } from './common/Platform'; import { RenderDebouncer } from './ui/RenderDebouncer'; import { addDisposableDomListener } from './ui/Lifecycle'; import { Disposable } from './common/Lifecycle'; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index f93328fe..e4bf87ea 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -6,7 +6,7 @@ import { ITerminal, ISelectionManager, IBuffer, CharData, IBufferLine } from './Types'; import { XtermListener } from './common/Types'; import { MouseHelper } from './ui/MouseHelper'; -import * as Browser from './core/Platform'; +import * as Browser from './common/Platform'; import { CharMeasure } from './ui/CharMeasure'; import { EventEmitter } from './common/EventEmitter'; import { SelectionModel } from './SelectionModel'; diff --git a/src/Terminal.ts b/src/Terminal.ts index c2497df4..ebdb8a89 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -36,7 +36,7 @@ import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; import { CharMeasure } from './ui/CharMeasure'; -import * as Browser from './core/Platform'; +import * as Browser from './common/Platform'; import { addDisposableDomListener } from './ui/Lifecycle'; import * as Strings from './Strings'; import { MouseHelper } from './ui/MouseHelper'; diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 68eb60f7..74a794cd 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -3,8 +3,7 @@ * @license MIT */ -import { XtermListener } from './Types'; -import { IEventEmitter, IDisposable } from 'xterm'; +import { IDisposable, IEventEmitter, XtermListener } from './Types'; import { Disposable } from './Lifecycle'; export class EventEmitter extends Disposable implements IEventEmitter, IDisposable { diff --git a/src/common/Lifecycle.ts b/src/common/Lifecycle.ts index 209a3e2a..5fac6e82 100644 --- a/src/common/Lifecycle.ts +++ b/src/common/Lifecycle.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDisposable } from 'xterm'; +import { IDisposable } from './Types'; /** * A base class that can be extended to provide convenience methods for managing the lifecycle of an diff --git a/src/core/Platform.ts b/src/common/Platform.ts similarity index 90% rename from src/core/Platform.ts rename to src/common/Platform.ts index 42c20d9d..bb0ad54b 100644 --- a/src/core/Platform.ts +++ b/src/common/Platform.ts @@ -3,6 +3,13 @@ * @license MIT */ +interface INavigator { + userAgent: string; + language: string; + platform: string; +} +declare const navigator: INavigator; + const isNode = (typeof navigator === 'undefined') ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; diff --git a/src/common/Types.ts b/src/common/Types.ts index 8a416bf1..8ad98d99 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -3,7 +3,16 @@ * @license MIT */ -import { IEventEmitter } from 'xterm'; +export interface IDisposable { + dispose(): void; +} + +export interface IEventEmitter { + on(type: string, listener: (...args: any[]) => void): void; + off(type: string, listener: (...args: any[]) => void): void; + emit(type: string, data?: any): void; + addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable; +} export type XtermListener = (...args: any[]) => void; diff --git a/src/common/tsconfig.json b/src/common/tsconfig.json index b40bb2f5..6d8d1a56 100644 --- a/src/common/tsconfig.json +++ b/src/common/tsconfig.json @@ -1,7 +1,10 @@ { "extends": "../tsconfig-library-base", "compilerOptions": { - "outDir": "../../lib" + "outDir": "../../lib", + "types": [ + "../../node_modules/@types/mocha" + ] }, "include": [ "./**/*" diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json index 41e41f0c..1fcf9e47 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "../../lib" }, "include": [ - "./**/*" + "./**/*", "../common/Platform.ts" ], "references": [ { "path": "../common" } diff --git a/src/renderer/atlas/CharAtlasGenerator.ts b/src/renderer/atlas/CharAtlasGenerator.ts index cadcce2e..38950766 100644 --- a/src/renderer/atlas/CharAtlasGenerator.ts +++ b/src/renderer/atlas/CharAtlasGenerator.ts @@ -4,7 +4,7 @@ */ import { FontWeight } from 'xterm'; -import { isFirefox, isSafari } from '../../core/Platform'; +import { isFirefox, isSafari } from '../../common/Platform'; import { IColor } from '../Types'; import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index e311c369..cb03a48f 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -8,7 +8,7 @@ import BaseCharAtlas from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from '../ColorManager'; import { clearColor } from './CharAtlasGenerator'; import LRUMap from './LRUMap'; -import { isFirefox, isSafari } from '../../core/Platform'; +import { isFirefox, isSafari } from '../../common/Platform'; import { IColor } from '../Types'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json index 5c6afcc5..84d0c924 100644 --- a/src/tsconfig-base.json +++ b/src/tsconfig-base.json @@ -8,8 +8,6 @@ "removeComments": true, "pretty": true, - "incremental": true, - - "skipLibCheck": true + "incremental": true } } diff --git a/src/tsconfig-library-base.json b/src/tsconfig-library-base.json index c82e0873..66b61f09 100644 --- a/src/tsconfig-library-base.json +++ b/src/tsconfig-library-base.json @@ -1,10 +1,6 @@ { "extends": "./tsconfig-base.json", "compilerOptions": { - "types": [ - "../../node_modules/@types/mocha", - "../../" - ], "composite": true, "strict": true } diff --git a/src/tsconfig.json b/src/tsconfig.json index 0aa3abb8..f0b1c749 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -11,7 +11,7 @@ ], "rootDir": ".", "outDir": "../lib", - + "noUnusedLocals": true, "noImplicitAny": true }, @@ -27,4 +27,3 @@ { "path": "./core" } ] } - \ No newline at end of file diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index e6e4aaa3..b418ee6c 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../rende import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator } from '../Types'; import { ICircularList, XtermListener } from '../common/Types'; import { Buffer } from '../Buffer'; -import * as Browser from '../core/Platform'; +import * as Browser from '../common/Platform'; import { ITheme, IDisposable, IMarker } from 'xterm'; import { Terminal } from '../Terminal'; From 5c8c680dac3717e927069699168272ebd23ce54e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 1 Apr 2019 00:01:41 -0700 Subject: [PATCH 61/77] 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 69a36e43..9cc1adba 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 7dfcbd2c..29a783ae 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 14518454..c4aa55be 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 65f4aaaf..37c9bbe5 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 a2b9045b..80399904 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 9d49a860..361b1123 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 f3ec6e31..db696bd6 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 a08bd485..10665f25 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 eb44bb58..4a899d72 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 be022239..f56ccf3a 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 dfd3a154..47232981 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}`); } From 2e10c8c25e9823dfa96a98bc12475f1b36bdf594 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 08:04:39 -0700 Subject: [PATCH 62/77] Clean up --- src/common/Platform.ts | 3 +++ src/common/tsconfig.json | 4 +--- src/core/tsconfig.json | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/common/Platform.ts b/src/common/Platform.ts index bb0ad54b..ee82cff4 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -8,6 +8,9 @@ interface INavigator { language: string; platform: string; } + +// We're declaring a navigator global here as we expect it in all runtimes (node and browser), but +// we want this module to live in common. declare const navigator: INavigator; const isNode = (typeof navigator === 'undefined') ? true : false; diff --git a/src/common/tsconfig.json b/src/common/tsconfig.json index 6d8d1a56..ccf742e5 100644 --- a/src/common/tsconfig.json +++ b/src/common/tsconfig.json @@ -6,7 +6,5 @@ "../../node_modules/@types/mocha" ] }, - "include": [ - "./**/*" - ] + "include": [ "./**/*" ] } diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json index 1fcf9e47..99bf48ca 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -1,11 +1,12 @@ { "extends": "../tsconfig-library-base", "compilerOptions": { - "outDir": "../../lib" + "outDir": "../../lib", + "types": [ + "../../node_modules/@types/mocha" + ] }, - "include": [ - "./**/*", "../common/Platform.ts" - ], + "include": [ "./**/*" ], "references": [ { "path": "../common" } ] From fa505111a210333b3aa6887ab1bc4f9831aeefb3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 08:51:23 -0700 Subject: [PATCH 63/77] Replace winptyCompat addon with windowsMode option --- demo/client.ts | 16 +++----- src/Buffer.ts | 2 +- src/Terminal.ts | 25 +++++++++++- src/WindowsMode.ts | 30 ++++++++++++++ src/addons/winptyCompat/Interfaces.ts | 14 ------- src/addons/winptyCompat/package.json | 5 --- src/addons/winptyCompat/tsconfig.json | 21 ---------- src/addons/winptyCompat/winptyCompat.test.ts | 19 --------- src/addons/winptyCompat/winptyCompat.ts | 43 -------------------- typings/xterm.d.ts | 12 ++++++ 10 files changed, 73 insertions(+), 114 deletions(-) create mode 100644 src/WindowsMode.ts delete mode 100644 src/addons/winptyCompat/Interfaces.ts delete mode 100644 src/addons/winptyCompat/package.json delete mode 100644 src/addons/winptyCompat/tsconfig.json delete mode 100644 src/addons/winptyCompat/winptyCompat.test.ts delete mode 100644 src/addons/winptyCompat/winptyCompat.ts diff --git a/demo/client.ts b/demo/client.ts index aaaf2829..fa98ee4e 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -13,12 +13,11 @@ import * as fit from '../lib/addons/fit/fit'; import * as fullscreen from '../lib/addons/fullscreen/fullscreen'; import * as search from '../lib/addons/search/search'; import * as webLinks from '../lib/addons/webLinks/webLinks'; -import * as winptyCompat from '../lib/addons/winptyCompat/winptyCompat'; import { ISearchOptions } from '../lib/addons/search/Interfaces'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module -import { Terminal as TerminalType } from 'xterm'; +import { Terminal as TerminalType, ITerminalOptions } from 'xterm'; export interface IWindowWithTerminal extends Window { term: TerminalType; @@ -30,10 +29,6 @@ Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); Terminal.applyAddon(search); Terminal.applyAddon(webLinks); -const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; -if (isWindows) { - Terminal.applyAddon(winptyCompat); -} let term; @@ -86,7 +81,10 @@ function createTerminal(): void { while (terminalContainer.children.length) { terminalContainer.removeChild(terminalContainer.children[0]); } - term = new Terminal({}); + const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; + term = new Terminal({ + windowsMode: isWindows + } as ITerminalOptions); window.term = term; // Expose `term` to window for debugging purposes term.on('resize', (size: { cols: number, rows: number }) => { if (!pid) { @@ -102,9 +100,7 @@ function createTerminal(): void { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - if (isWindows) { - term.winptyCompatInit(); - } + term.webLinksInit(); term.fit(); term.focus(); diff --git a/src/Buffer.ts b/src/Buffer.ts index 9cc1adba..4d844ea1 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -252,7 +252,7 @@ export class Buffer implements IBuffer { } private get _isReflowEnabled(): boolean { - return this._hasScrollback && !(this._terminal as any).isWinptyCompatEnabled; + return this._hasScrollback && !this._terminal.options.windowsMode; } private _reflow(newCols: number, newRows: number): void { diff --git a/src/Terminal.ts b/src/Terminal.ts index db696bd6..5ed99729 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; import { clone } from './common/Clone'; +import { applyWindowsMode } from './WindowsMode'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -110,7 +111,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = { tabStopWidth: 8, theme: null, rightClickSelectsWord: Browser.isMac, - rendererType: 'canvas' + rendererType: 'canvas', + windowsMode: false }; export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { @@ -210,6 +212,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _accessibilityManager: AccessibilityManager; private _screenDprMonitor: ScreenDprMonitor; private _theme: ITheme; + private _windowsMode: IDisposable | undefined; // bufferline to clone/copy from for new blank lines private _blankLine: IBufferLine = null; @@ -239,6 +242,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public dispose(): void { super.dispose(); + if (this._windowsMode) { + this._windowsMode.dispose(); + this._windowsMode = undefined; + } this._customKeyEventHandler = null; removeTerminalFromCache(this); this.handler = () => {}; @@ -321,6 +328,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.selectionManager.clearSelection(); this.selectionManager.initBuffersListeners(); } + + if (this.options.windowsMode) { + this._windowsMode = applyWindowsMode(this); + } } /** @@ -501,6 +512,18 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } break; case 'tabStopWidth': this.buffers.setupTabStops(); break; + case 'windowsMode': + if (value) { + if (!this._windowsMode) { + this._windowsMode = applyWindowsMode(this); + } + } else { + if (this._windowsMode) { + this._windowsMode.dispose(); + this._windowsMode = undefined; + } + } + break; } // Inform renderer of changes if (this.renderer) { diff --git a/src/WindowsMode.ts b/src/WindowsMode.ts new file mode 100644 index 00000000..33a9bed5 --- /dev/null +++ b/src/WindowsMode.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from 'xterm'; +import { ITerminal } from './Types'; +import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from './Buffer'; + +export function applyWindowsMode(terminal: ITerminal): IDisposable { + // Winpty does not support wraparound mode which means that lines will never + // be marked as wrapped. This causes issues for things like copying a line + // retaining the wrapped new line characters or if consumers are listening + // in on the data stream. + // + // The workaround for this is to listen to every incoming line feed and mark + // the line as wrapped if the last character in the previous line is not a + // space. This is certainly not without its problems, but generally on + // Windows when text reaches the end of the terminal it's likely going to be + // wrapped. + return terminal.addDisposableListener('linefeed', () => { + const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1); + const lastChar = line.get(terminal.cols - 1); + + if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) { + const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y); + nextLine.isWrapped = true; + } + }); +} diff --git a/src/addons/winptyCompat/Interfaces.ts b/src/addons/winptyCompat/Interfaces.ts deleted file mode 100644 index 6217c860..00000000 --- a/src/addons/winptyCompat/Interfaces.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Terminal } from 'xterm'; - -export interface ITerminalCore { - buffer: any; -} - -export interface IWinptyCompatAddonTerminal extends Terminal { - _core: ITerminalCore; -} diff --git a/src/addons/winptyCompat/package.json b/src/addons/winptyCompat/package.json deleted file mode 100644 index fc929497..00000000 --- a/src/addons/winptyCompat/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "xterm.winptycompat", - "main": "winptyCompat.js", - "private": true -} diff --git a/src/addons/winptyCompat/tsconfig.json b/src/addons/winptyCompat/tsconfig.json deleted file mode 100644 index fa48c963..00000000 --- a/src/addons/winptyCompat/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "lib": [ - "es5" - ], - "rootDir": ".", - "outDir": "../../../lib/addons/winptyCompat/", - "sourceMap": true, - "removeComments": true, - "declaration": true, - "types": [ - "../../node_modules/@types/mocha" - ] - }, - "include": [ - "**/*.ts", - "../../../typings/xterm.d.ts" - ] -} diff --git a/src/addons/winptyCompat/winptyCompat.test.ts b/src/addons/winptyCompat/winptyCompat.test.ts deleted file mode 100644 index c3a7e479..00000000 --- a/src/addons/winptyCompat/winptyCompat.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; - -import * as winptyCompat from './winptyCompat'; - -class MockTerminal {} - -describe('winptyCompat addon', () => { - describe('apply', () => { - it('should do register the `winptyCompatInit` method', () => { - winptyCompat.apply(MockTerminal); - assert.equal(typeof (MockTerminal).prototype.winptyCompatInit, 'function'); - }); - }); -}); diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts deleted file mode 100644 index 58f59fd9..00000000 --- a/src/addons/winptyCompat/winptyCompat.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Terminal } from 'xterm'; -import { IWinptyCompatAddonTerminal } from './Interfaces'; - -const CHAR_DATA_CODE_INDEX = 3; -const NULL_CELL_CODE = 0; -const WHITESPACE_CELL_CODE = 32; - -export function winptyCompatInit(terminal: Terminal): void { - const addonTerminal = terminal; - - (addonTerminal._core as any).isWinptyCompatEnabled = true; - - // Winpty does not support wraparound mode which means that lines will never - // be marked as wrapped. This causes issues for things like copying a line - // retaining the wrapped new line characters or if consumers are listening - // in on the data stream. - // - // The workaround for this is to listen to every incoming line feed and mark - // the line as wrapped if the last character in the previous line is not a - // space. This is certainly not without its problems, but generally on - // Windows when text reaches the end of the terminal it's likely going to be - // wrapped. - addonTerminal.on('linefeed', () => { - const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1); - const lastChar = line.get(addonTerminal.cols - 1); - - if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) { - const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y); - nextLine.isWrapped = true; - } - }); -} - -export function apply(terminalConstructor: typeof Terminal): void { - (terminalConstructor.prototype).winptyCompatInit = function (): void { - winptyCompatInit(this); - }; -} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d813bc5f..ec647a10 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -188,6 +188,18 @@ declare module 'xterm' { * The color theme of the terminal. */ theme?: ITheme; + + /** + * Whether "Windows mode" is enabled. Because Windows backends winpty and + * conpty operate by doing line wrapping on their side, xterm.js does not + * have access to wrapped lines. When Windows mode is enabled the following + * changes will be in effect: + * + * - Reflow is disabled. + * - Lines are assumed to be wrapped if the last character of the line is + * not whitespace. + */ + windowsMode?: boolean; } /** From b12d2c218eab24ecb981be861fd086c6379f7b84 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:03:24 -0700 Subject: [PATCH 64/77] Remove reference to winptyCompat tsconfig --- src/tsconfig.all.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tsconfig.all.json b/src/tsconfig.all.json index bee5df32..2a53ab89 100644 --- a/src/tsconfig.all.json +++ b/src/tsconfig.all.json @@ -9,8 +9,6 @@ { "path": "./addons/search" }, { "path": "./addons/terminado" }, { "path": "./addons/webLinks" }, - { "path": "./addons/winptyCompat" }, { "path": "./addons/zmodem" } ] } - \ No newline at end of file From 68e3d7fa164bfff08ac143d8ef3f9f62e7b14ed0 Mon Sep 17 00:00:00 2001 From: Vadim Zakondyrin Date: Wed, 3 Apr 2019 12:10:32 +0600 Subject: [PATCH 65/77] Fix CSI scroll down handler The issue has been introduced in cd8477a942e1fac85de167eab7e8c09c85f55255 --- src/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 37c9bbe5..237e0849 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -898,7 +898,7 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1); - buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR)); + buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR)); } // this.maxRange(); this._terminal.updateRange(buffer.scrollTop); From b18829963cd7f7ff4598f00fd8eedf2dc035793c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:01:43 -0400 Subject: [PATCH 66/77] Fix backspace on demo buffer.x will probably be public API soon (#1994) Fixes #1989 --- demo/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index fa98ee4e..7a601898 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -172,7 +172,7 @@ function runFakeTerminal(): void { term.prompt(); } else if (ev.keyCode === 8) { // Do not delete the prompt - if (term.x > 2) { + if (term._core.buffer.x > 2) { term.write('\b \b'); } } else if (printable) { From e8a99945830de257a62f0554183b71ee613776fc Mon Sep 17 00:00:00 2001 From: Asad Memon Date: Thu, 4 Apr 2019 11:58:19 -0700 Subject: [PATCH 67/77] add CodeInterview.io in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 40de04cf..5083a9e3 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard. - [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation. - [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD +- [**CodeInterview.io**](https://codeinterview.io): A coding interview platform in 25+ languages and many web frameworks. Uses xterm.js to provide shell access. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) From bb324ba9c18816539c1120e91d66fe9c8a2f4965 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 6 Apr 2019 14:55:43 -0400 Subject: [PATCH 68/77] Move width shift back into enum --- src/BufferLine.test.ts | 10 ++-- src/BufferLine.ts | 88 ++++++++++++++++----------------- src/renderer/TextRenderLayer.ts | 6 +-- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 29a783ae..5b029cb8 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, ContentMasks } 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'; @@ -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(), ContentMasks.IS_COMBINED); + chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK); // 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(), ContentMasks.IS_COMBINED); + chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK); // 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(), ContentMasks.IS_COMBINED); + chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK); }); 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(), ContentMasks.IS_COMBINED); + chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK); }); }); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index f0bfe858..6ce5e498 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -36,7 +36,7 @@ const enum Cell { /** * Bitmasks for accessing data in `content`. */ -export const enum ContentMasks { +export const enum Content { /** * bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken) * read: `codepoint = content & Content.codepointMask;` @@ -44,7 +44,7 @@ export const enum ContentMasks { * shortcut if precondition `codepoint <= 0x10FFFF` is met: * `content |= codepoint;` */ - CODEPOINT = 0x1FFFFF, + CODEPOINT_MASK = 0x1FFFFF, /** * bit 22 flag indication whether a cell contains combined content @@ -52,7 +52,7 @@ export const enum ContentMasks { * set: `content |= Content.isCombined;` * clear: `content &= ~Content.isCombined;` */ - IS_COMBINED = 0x200000, // 1 << 21 + IS_COMBINED_MASK = 0x200000, // 1 << 21 /** * bit 1..22 mask to check whether a cell contains any string data @@ -60,7 +60,7 @@ export const enum ContentMasks { * whether a cell contains anything * read: `isEmtpy = !(content & Content.hasContent)` */ - HAS_CONTENT = 0x3FFFFF, + HAS_CONTENT_MASK = 0x3FFFFF, /** * bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2) @@ -72,10 +72,10 @@ export const enum ContentMasks { * shortcut if precondition `0 <= width <= 3` is met: * `content |= width << Content.widthShift;` */ - WIDTH = 0xC00000 // 3 << 22 + WIDTH_MASK = 0xC00000, // 3 << 22 + WIDTH_SHIFT = 22 } -export const WIDTH_MASK_SHIFT = 22; export enum Attributes { /** @@ -213,21 +213,21 @@ export class CellData extends AttributeData implements ICellData { /** Whether cell contains a combined string. */ public isCombined(): number { - return this.content & ContentMasks.IS_COMBINED; + return this.content & Content.IS_COMBINED_MASK; } /** Width of the cell. */ public getWidth(): number { - return this.content >> WIDTH_MASK_SHIFT; + return this.content >> Content.WIDTH_SHIFT; } /** JS string of the content. */ public getChars(): string { - if (this.content & ContentMasks.IS_COMBINED) { + if (this.content & Content.IS_COMBINED_MASK) { return this.combinedData; } - if (this.content & ContentMasks.CODEPOINT) { - return stringFromCodePoint(this.content & ContentMasks.CODEPOINT); + if (this.content & Content.CODEPOINT_MASK) { + return stringFromCodePoint(this.content & Content.CODEPOINT_MASK); } return ''; } @@ -241,7 +241,7 @@ export class CellData extends AttributeData implements ICellData { public getCode(): number { return (this.isCombined()) ? this.combinedData.charCodeAt(this.combinedData.length - 1) - : this.content & ContentMasks.CODEPOINT; + : this.content & Content.CODEPOINT_MASK; } /** Set data from CharData */ @@ -260,7 +260,7 @@ export class CellData extends AttributeData implements ICellData { if (0xD800 <= code && code <= 0xDBFF) { const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1); if (0xDC00 <= second && second <= 0xDFFF) { - this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); + this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } else { combined = true; } @@ -268,11 +268,11 @@ export class CellData extends AttributeData implements ICellData { combined = true; } } else { - this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); + this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } if (combined) { this.combinedData = value[CHAR_DATA_CHAR_INDEX]; - this.content = ContentMasks.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); + this.content = Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } } @@ -320,14 +320,14 @@ export class BufferLine implements IBufferLine { */ public get(index: number): CharData { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; - const cp = content & ContentMasks.CODEPOINT; + const cp = content & Content.CODEPOINT_MASK; return [ this._data[index * CELL_SIZE + Cell.FG], - (content & ContentMasks.IS_COMBINED) + (content & Content.IS_COMBINED_MASK) ? this._combined[index] : (cp) ? stringFromCodePoint(cp) : '', - content >> WIDTH_MASK_SHIFT, - (content & ContentMasks.IS_COMBINED) + content >> Content.WIDTH_SHIFT, + (content & Content.IS_COMBINED_MASK) ? this._combined[index].charCodeAt(this._combined[index].length - 1) : cp ]; @@ -341,9 +341,9 @@ export class BufferLine implements IBufferLine { this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX]; if (value[CHAR_DATA_CHAR_INDEX].length > 1) { this._combined[index] = value[1]; - this._data[index * CELL_SIZE + Cell.CONTENT] = index | ContentMasks.IS_COMBINED | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); + this._data[index * CELL_SIZE + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } else { - this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << WIDTH_MASK_SHIFT); + this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT); } } @@ -352,12 +352,12 @@ export class BufferLine implements IBufferLine { * use these when only one value is needed, otherwise use `loadCell` */ public getWidth(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] >> WIDTH_MASK_SHIFT; + return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT; } /** Test whether content has width. */ public hasWidth(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.WIDTH; + return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK; } /** Get FG cell component. */ @@ -376,7 +376,7 @@ export class BufferLine implements IBufferLine { * from real empty cells. * */ public hasContent(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.HAS_CONTENT; + return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK; } /** @@ -386,25 +386,25 @@ export class BufferLine implements IBufferLine { */ public getCodePoint(index: number): number { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; - if (content & ContentMasks.IS_COMBINED) { + if (content & Content.IS_COMBINED_MASK) { return this._combined[index].charCodeAt(this._combined[index].length - 1); } - return content & ContentMasks.CODEPOINT; + return content & Content.CODEPOINT_MASK; } /** Test whether the cell contains a combined string. */ public isCombined(index: number): number { - return this._data[index * CELL_SIZE + Cell.CONTENT] & ContentMasks.IS_COMBINED; + return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK; } /** Returns the string content of the cell. */ public getString(index: number): string { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; - if (content & ContentMasks.IS_COMBINED) { + if (content & Content.IS_COMBINED_MASK) { return this._combined[index]; } - if (content & ContentMasks.CODEPOINT) { - return stringFromCodePoint(content & ContentMasks.CODEPOINT); + if (content & Content.CODEPOINT_MASK) { + return stringFromCodePoint(content & Content.CODEPOINT_MASK); } // return empty string for empty cells return ''; @@ -419,7 +419,7 @@ export class BufferLine implements IBufferLine { cell.content = this._data[startIndex + Cell.CONTENT]; cell.fg = this._data[startIndex + Cell.FG]; cell.bg = this._data[startIndex + Cell.BG]; - if (cell.content & ContentMasks.IS_COMBINED) { + if (cell.content & Content.IS_COMBINED_MASK) { cell.combinedData = this._combined[index]; } return cell; @@ -429,7 +429,7 @@ export class BufferLine implements IBufferLine { * Set data at `index` to `cell`. */ public setCell(index: number, cell: ICellData): void { - if (cell.content & ContentMasks.IS_COMBINED) { + if (cell.content & Content.IS_COMBINED_MASK) { this._combined[index] = cell.combinedData; } this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content; @@ -443,7 +443,7 @@ export class BufferLine implements IBufferLine { * it gets an optimized access method. */ public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void { - this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << WIDTH_MASK_SHIFT); + this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT); this._data[index * CELL_SIZE + Cell.FG] = fg; this._data[index * CELL_SIZE + Cell.BG] = bg; } @@ -456,21 +456,21 @@ export class BufferLine implements IBufferLine { */ public addCodepointToCell(index: number, codePoint: number): void { let content = this._data[index * CELL_SIZE + Cell.CONTENT]; - if (content & ContentMasks.IS_COMBINED) { + if (content & Content.IS_COMBINED_MASK) { // we already have a combined string, simply add this._combined[index] += stringFromCodePoint(codePoint); } else { - if (content & ContentMasks.CODEPOINT) { + if (content & Content.CODEPOINT_MASK) { // normal case for combining chars: // - move current leading char + new one into combined string // - set combined flag - this._combined[index] = stringFromCodePoint(content & ContentMasks.CODEPOINT) + stringFromCodePoint(codePoint); - content &= ~ContentMasks.CODEPOINT; // set codepoint in buffer to 0 - content |= ContentMasks.IS_COMBINED; + this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint); + content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0 + content |= Content.IS_COMBINED_MASK; } else { // should not happen - we actually have no data in the cell yet // simply set the data in the cell buffer with a width of 1 - content = codePoint | (1 << WIDTH_MASK_SHIFT); + content = codePoint | (1 << Content.WIDTH_SHIFT); } this._data[index * CELL_SIZE + Cell.CONTENT] = content; } @@ -592,8 +592,8 @@ export class BufferLine implements IBufferLine { public getTrimmedLength(): number { for (let i = this.length - 1; i >= 0; --i) { - if ((this._data[i * CELL_SIZE + Cell.CONTENT] & ContentMasks.HAS_CONTENT)) { - return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> WIDTH_MASK_SHIFT); + if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) { + return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT); } } return 0; @@ -632,9 +632,9 @@ export class BufferLine implements IBufferLine { let result = ''; while (startCol < endCol) { const content = this._data[startCol * CELL_SIZE + Cell.CONTENT]; - const cp = content & ContentMasks.CODEPOINT; - result += (content & ContentMasks.IS_COMBINED) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR; - startCol += (content >> WIDTH_MASK_SHIFT) || 1; // always advance by 1 + const cp = content & Content.CODEPOINT_MASK; + result += (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR; + startCol += (content >> Content.WIDTH_SHIFT) || 1; // always advance by 1 } return result; } diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index f9821e4a..2547ecb2 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -8,7 +8,7 @@ import { IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types' import { CharData, ITerminal, ICellData } from '../Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { CellData, AttributeData, ContentMasks, WIDTH_MASK_SHIFT } from '../BufferLine'; +import { CellData, AttributeData, Content } from '../BufferLine'; /** * This CharData looks like a null character, which will forc a clear and render @@ -117,8 +117,8 @@ export class TextRenderLayer extends BaseRenderLayer { // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; if (lastCharX < line.length - 1 && line.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) { // patch width to 2 - cell.content &= ~ContentMasks.WIDTH; - cell.content |= 2 << WIDTH_MASK_SHIFT; + cell.content &= ~Content.WIDTH_MASK; + cell.content |= 2 << Content.WIDTH_SHIFT; // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the // overlapping char is no longer to the left of the character and also when From 171b9337cb544417b052486634ce9fc36a8bcbaf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 6 Apr 2019 14:56:15 -0400 Subject: [PATCH 69/77] Make BufferLine enums const --- src/BufferLine.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 6ce5e498..bf0683d0 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -77,7 +77,7 @@ export const enum Content { } -export enum Attributes { +export const enum Attributes { /** * bit 1..8 blue in RGB, color in P256 and P16 */ @@ -113,7 +113,7 @@ export enum Attributes { RGB_MASK = 0xFFFFFF } -export enum FgFlags { +export const enum FgFlags { /** * bit 27..31 (32th bit unused) */ @@ -124,7 +124,7 @@ export enum FgFlags { INVISIBLE = 0x40000000 } -export enum BgFlags { +export const enum BgFlags { /** * bit 27..32 (upper 4 unused) */ From 09a5fd4e61e45479b64e29a1e639cb5e53d1c735 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 6 Apr 2019 15:06:42 -0400 Subject: [PATCH 70/77] Fix capitalization in color mode --- src/BufferLine.ts | 4 ++-- src/InputHandler.test.ts | 36 ++++++++++++++++++------------------ src/Types.ts | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index bf0683d0..2bd5a113 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -165,8 +165,8 @@ export class AttributeData implements IAttributeData { public isDim(): number { return this.bg & BgFlags.DIM; } // color modes - public getFgColormode(): number { return this.fg & Attributes.CM_MASK; } - public getBgColormode(): number { return this.bg & Attributes.CM_MASK; } + public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; } + public getBgColorMode(): number { return this.bg & Attributes.CM_MASK; } public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; } public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; } public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; } diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 0b7f7ad3..01596f80 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -447,53 +447,53 @@ describe('InputHandler', () => { assert.equal(!!term.curAttrData.isInvisible(), false); }); it('colormode palette 16', () => { - assert.equal(term.curAttrData.getFgColormode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColormode(), 0); // DEFAULT + assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT // lower 8 colors for (let i = 0; i < 8; ++i) { term.writeSync(`\x1b[${i + 30};${i + 40}m`); - assert.equal(term.curAttrData.getFgColormode(), Attributes.CM_P16); + assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getBgColormode(), Attributes.CM_P16); + assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); assert.equal(term.curAttrData.getBgColor(), i); } // reset to DEFAULT term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColormode(), 0); - assert.equal(term.curAttrData.getBgColormode(), 0); + assert.equal(term.curAttrData.getFgColorMode(), 0); + assert.equal(term.curAttrData.getBgColorMode(), 0); }); it('colormode palette 256', () => { - assert.equal(term.curAttrData.getFgColormode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColormode(), 0); // DEFAULT + assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT // lower 8 colors for (let i = 0; i < 256; ++i) { term.writeSync(`\x1b[38;5;${i};48;5;${i}m`); - assert.equal(term.curAttrData.getFgColormode(), Attributes.CM_P256); + assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getBgColormode(), Attributes.CM_P256); + assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); assert.equal(term.curAttrData.getBgColor(), i); } // reset to DEFAULT term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColormode(), 0); + assert.equal(term.curAttrData.getFgColorMode(), 0); assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getBgColormode(), 0); + assert.equal(term.curAttrData.getBgColorMode(), 0); assert.equal(term.curAttrData.getBgColor(), -1); }); it('colormode RGB', () => { - assert.equal(term.curAttrData.getFgColormode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColormode(), 0); // DEFAULT + assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - assert.equal(term.curAttrData.getFgColormode(), Attributes.CM_RGB); + assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_RGB); assert.equal(term.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [1, 2, 3]); - assert.equal(term.curAttrData.getBgColormode(), Attributes.CM_RGB); + assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_RGB); assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getBgColor()), [4, 5, 6]); // reset to DEFAULT term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColormode(), 0); + assert.equal(term.curAttrData.getFgColorMode(), 0); assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getBgColormode(), 0); + assert.equal(term.curAttrData.getBgColorMode(), 0); assert.equal(term.curAttrData.getBgColor(), -1); }); it('should zero missing RGB values', () => { diff --git a/src/Types.ts b/src/Types.ts index c05a3479..6e0108ae 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -540,8 +540,8 @@ export interface IAttributeData { isDim(): number; // color modes - getFgColormode(): number; - getBgColormode(): number; + getFgColorMode(): number; + getBgColorMode(): number; isFgRGB(): boolean; isBgRGB(): boolean; isFgPalette(): boolean; From ddd02b49683a357f41f4c8577ef9b2555be7584b Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Sat, 6 Apr 2019 16:04:50 -0400 Subject: [PATCH 71/77] feat: add DIM support to DomRenderer part of #1896 --- src/renderer/dom/DomRendererRowFactory.ts | 5 +++++ src/xterm.css | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 47232981..707d3466 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -10,6 +10,7 @@ import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { CellData } from '../../BufferLine'; export const BOLD_CLASS = 'xterm-bold'; +export const DIM_CLASS = 'xterm-dim'; export const ITALIC_CLASS = 'xterm-italic'; export const CURSOR_CLASS = 'xterm-cursor'; export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; @@ -107,6 +108,10 @@ export class DomRendererRowFactory { charElement.classList.add(ITALIC_CLASS); } + if (flags & FLAGS.DIM) { + charElement.classList.add(exports.DIM_CLASS); + } + charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; if (fg !== DEFAULT_COLOR) { charElement.classList.add(`xterm-fg-${fg}`); diff --git a/src/xterm.css b/src/xterm.css index 2e47b1a1..e0319f87 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -161,3 +161,7 @@ height: 1px; overflow: hidden; } + +.xterm-dim { + opacity: 0.6; +} From 92c435a16dfa6d7ae366db66985d2d6b6a2247fd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 09:45:54 -0400 Subject: [PATCH 72/77] Use same dim opacity as canvas renderer --- src/renderer/dom/DomRendererRowFactory.ts | 2 +- src/xterm.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 707d3466..4d2143ee 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -109,7 +109,7 @@ export class DomRendererRowFactory { } if (flags & FLAGS.DIM) { - charElement.classList.add(exports.DIM_CLASS); + charElement.classList.add(DIM_CLASS); } charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; diff --git a/src/xterm.css b/src/xterm.css index e0319f87..e80c2524 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -163,5 +163,5 @@ } .xterm-dim { - opacity: 0.6; + opacity: 0.5; } From ab44650d87a4d701171d891feeee60796dc56f48 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 13:16:19 -0400 Subject: [PATCH 73/77] Fix reflow smaller case related to ended \t chars Part of #1932 --- src/Buffer.test.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++ src/Buffer.ts | 8 ++++---- src/BufferLine.ts | 2 +- src/BufferReflow.ts | 26 +++++++++++++++++++----- 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 59475adb..e2ef35fe 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -518,6 +518,29 @@ describe('Buffer', () => { assert.equal(secondMarker.line, 1, 'second marker should be restored'); assert.equal(thirdMarker.line, 2, 'third marker should be restored'); }); + it('should correctly reflow wrapped lines that end in null space (via tab char)', () => { + buffer.fillViewportRows(); + buffer.resize(4, 10); + buffer.y = 2; + buffer.lines.get(0).set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); + buffer.lines.get(0).set(1, [null, 'b', 1, 'b'.charCodeAt(0)]); + buffer.lines.get(1).set(0, [null, 'c', 1, 'c'.charCodeAt(0)]); + buffer.lines.get(1).set(1, [null, 'd', 1, 'd'.charCodeAt(0)]); + buffer.lines.get(1).isWrapped = true; + // Buffer: + // "ab " (wrapped) + // "cd" + buffer.resize(5, 10); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0).translateToString(true), 'ab c'); + assert.equal(buffer.lines.get(1).translateToString(false), 'd '); + buffer.resize(6, 10); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0).translateToString(true), 'ab cd'); + assert.equal(buffer.lines.get(1).translateToString(false), ' '); + }); it('should wrap wide characters correctly when reflowing larger', () => { buffer.fillViewportRows(); buffer.resize(12, 10); @@ -553,6 +576,32 @@ describe('Buffer', () => { assert.equal(buffer.lines.get(1).translateToString(true), '语汉语汉语'); assert.equal(buffer.lines.get(1).translateToString(false), '语汉语汉语 '); }); + it('should correctly reflow wrapped lines that end in null space (via tab char)', () => { + buffer.fillViewportRows(); + buffer.resize(4, 10); + buffer.y = 2; + buffer.lines.get(0).set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); + buffer.lines.get(0).set(1, [null, 'b', 1, 'b'.charCodeAt(0)]); + buffer.lines.get(1).set(0, [null, 'c', 1, 'c'.charCodeAt(0)]); + buffer.lines.get(1).set(1, [null, 'd', 1, 'd'.charCodeAt(0)]); + buffer.lines.get(1).isWrapped = true; + // Buffer: + // "ab " (wrapped) + // "cd" + buffer.resize(3, 10); + assert.equal(buffer.y, 2); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0).translateToString(false), 'ab '); + assert.equal(buffer.lines.get(1).translateToString(false), ' cd'); + buffer.resize(2, 10); + assert.equal(buffer.y, 3); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0).translateToString(false), 'ab'); + assert.equal(buffer.lines.get(1).translateToString(false), ' '); + assert.equal(buffer.lines.get(2).translateToString(false), 'cd'); + }); it('should wrap wide characters correctly when reflowing smaller', () => { buffer.fillViewportRows(); buffer.resize(12, 10); diff --git a/src/Buffer.ts b/src/Buffer.ts index 4d844ea1..770b027c 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -8,7 +8,7 @@ import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IB import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; import { BufferLine, CellData } from './BufferLine'; -import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow'; +import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './BufferReflow'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; @@ -269,7 +269,7 @@ export class Buffer implements IBuffer { } private _reflowLarger(newCols: number, newRows: number): void { - const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, newCols, this.ybase + this.y); + const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y); if (toRemove.length > 0) { const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove); reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout); @@ -375,8 +375,8 @@ export class Buffer implements IBuffer { srcCol -= cellsToCopy; if (srcCol === 0) { srcLineIndex--; - // TODO: srcCol shoudl take trimmed length into account - srcCol = wrappedLines[Math.max(srcLineIndex, 0)].getTrimmedLength(); // this._cols; + const wrappedLinesIndex = Math.max(srcLineIndex, 0); + srcCol = getWrappedLineTrimmedLength(wrappedLines, wrappedLinesIndex, this._cols); } } diff --git a/src/BufferLine.ts b/src/BufferLine.ts index c4aa55be..07207248 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -58,7 +58,7 @@ export const enum ContentMasks { * 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)` + * read: `isEmpty = !(content & Content.hasContent)` */ HAS_CONTENT = 0x3FFFFF, diff --git a/src/BufferReflow.ts b/src/BufferReflow.ts index d27d7c48..d3adfc6d 100644 --- a/src/BufferReflow.ts +++ b/src/BufferReflow.ts @@ -19,7 +19,7 @@ export interface INewLayoutResult { * @param lines The buffer lines. * @param newCols The columns after resize. */ -export function reflowLargerGetLinesToRemove(lines: CircularList, newCols: number, bufferAbsoluteY: number): number[] { +export function reflowLargerGetLinesToRemove(lines: CircularList, oldCols: number, newCols: number, bufferAbsoluteY: number): number[] { const nullCell = CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); // Gather all BufferLines that need to be removed from the Buffer here so that they can be // batched up and only committed once @@ -49,11 +49,11 @@ export function reflowLargerGetLinesToRemove(lines: CircularList, n // Copy buffer data to new locations let destLineIndex = 0; - let destCol = wrappedLines[destLineIndex].getTrimmedLength(); + let destCol = wrappedLines.length === 1 ? wrappedLines[destLineIndex].getTrimmedLength() : oldCols; let srcLineIndex = 1; let srcCol = 0; while (srcLineIndex < wrappedLines.length) { - const srcTrimmedTineLength = wrappedLines[srcLineIndex].getTrimmedLength(); + const srcTrimmedTineLength = srcLineIndex === wrappedLines.length - 1 ? wrappedLines[srcLineIndex].getTrimmedLength() : oldCols; const srcRemainingCells = srcTrimmedTineLength - srcCol; const destRemainingCells = newCols - destCol; const cellsToCopy = Math.min(srcRemainingCells, destRemainingCells); @@ -174,7 +174,7 @@ export function reflowLargerApplyNewLayout(lines: CircularList, new */ export function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCols: number, newCols: number): number[] { const newLineLengths: number[] = []; - const cellsNeeded = wrappedLines.map(l => l.getTrimmedLength()).reduce((p, c) => p + c); + const cellsNeeded = wrappedLines.map((l, i) => getWrappedLineTrimmedLength(wrappedLines, i, oldCols)).reduce((p, c) => p + c); // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and // linesNeeded @@ -188,7 +188,7 @@ export function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCo break; } srcCol += newCols; - const oldTrimmedLength = wrappedLines[srcLine].getTrimmedLength(); + const oldTrimmedLength = getWrappedLineTrimmedLength(wrappedLines, srcLine, oldCols); if (srcCol > oldTrimmedLength) { srcCol -= oldTrimmedLength; srcLine++; @@ -204,3 +204,19 @@ export function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCo return newLineLengths; } + +export function getWrappedLineTrimmedLength(lines: BufferLine[], i: number, cols: number): number { + // If this is the last row in the wrapped line, get the actual trimmed length + if (i === lines.length - 1) { + return lines[i].getTrimmedLength(); + } + // Detect whether the following line starts with a wide character and the end of the current line + // is null, if so then we can be pretty sure the null character should be excluded from the line + // length] + const endsInNull = !(lines[i].hasContent(cols - 1)) && lines[i].getWidth(cols - 1) === 1; + const followingLineStartsWithWide = lines[i + 1].getWidth(0) === 2; + if (endsInNull && followingLineStartsWithWide) { + return cols - 1; + } + return cols; +} From 3ac313f8e12ef7ded12e60e60ceb41d1f6105f37 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 13:18:05 -0400 Subject: [PATCH 74/77] Fix reflow larger case Fixes #1932 --- src/BufferReflow.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BufferReflow.ts b/src/BufferReflow.ts index d3adfc6d..c090b0d7 100644 --- a/src/BufferReflow.ts +++ b/src/BufferReflow.ts @@ -49,11 +49,11 @@ export function reflowLargerGetLinesToRemove(lines: CircularList, o // Copy buffer data to new locations let destLineIndex = 0; - let destCol = wrappedLines.length === 1 ? wrappedLines[destLineIndex].getTrimmedLength() : oldCols; + let destCol = getWrappedLineTrimmedLength(wrappedLines, destLineIndex, oldCols); let srcLineIndex = 1; let srcCol = 0; while (srcLineIndex < wrappedLines.length) { - const srcTrimmedTineLength = srcLineIndex === wrappedLines.length - 1 ? wrappedLines[srcLineIndex].getTrimmedLength() : oldCols; + const srcTrimmedTineLength = getWrappedLineTrimmedLength(wrappedLines, srcLineIndex, oldCols); const srcRemainingCells = srcTrimmedTineLength - srcCol; const destRemainingCells = newCols - destCol; const cellsToCopy = Math.min(srcRemainingCells, destRemainingCells); From 74b68ac1c786ed8121fa1457f134dfe913672b54 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 13:49:56 -0400 Subject: [PATCH 75/77] Fix compile error from conflicting PRs --- src/renderer/dom/DomRendererRowFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index a6728873..a6cbeb78 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -84,7 +84,7 @@ export class DomRendererRowFactory { charElement.classList.add(ITALIC_CLASS); } - if (flags & FLAGS.DIM) { + if (this._workCell.isDim()) { charElement.classList.add(DIM_CLASS); } From a0b4ee56f66fde4e20baa82a08c6b88cc8781ada Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 14:17:38 -0400 Subject: [PATCH 76/77] Add a test for dim flag in dom renderer --- src/renderer/dom/DomRendererRowFactory.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 026a947d..4402943c 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -90,6 +90,16 @@ describe('DomRendererRowFactory', () => { ); }); + it('should add class for dim', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM; + lineData.setCell(0, cell); + const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); + assert.equal(getFragmentHtml(fragment), + 'a' + ); + }); + it('should add classes for 256 foreground colors', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg |= Attributes.CM_P256; From e5f94391ae5985cbbb7a6f52d4005473236745b5 Mon Sep 17 00:00:00 2001 From: Sean Kavanagh Date: Tue, 9 Apr 2019 09:10:29 -0400 Subject: [PATCH 77/77] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5083a9e3..1fa8ad41 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation. - [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD - [**CodeInterview.io**](https://codeinterview.io): A coding interview platform in 25+ languages and many web frameworks. Uses xterm.js to provide shell access. +- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents)