From 791c020a563b2acbd99e0c6f03f12273179f73c1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Oct 2018 11:03:31 -0700 Subject: [PATCH 01/56] Allow inverse colors to be stored in glyph keys Fixes #1737 --- src/renderer/atlas/StaticCharAtlas.ts | 4 ++-- src/renderer/atlas/Types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts index c0d8a814..5c56f019 100644 --- a/src/renderer/atlas/StaticCharAtlas.ts +++ b/src/renderer/atlas/StaticCharAtlas.ts @@ -41,7 +41,7 @@ export default class StaticCharAtlas extends BaseCharAtlas { const isAscii = glyph.code < 256; // A color is basic if it is one of the 4 bit ANSI colors. const isBasicColor = glyph.fg < 16; - const isDefaultColor = glyph.fg >= 256; + const isDefaultColor = glyph.fg === 256; const isDefaultBackground = glyph.bg >= 256; return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic; } @@ -60,7 +60,7 @@ export default class StaticCharAtlas extends BaseCharAtlas { let colorIndex = 0; if (glyph.fg < 256) { colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0); - } else { + } else if (glyph.fg === 256) { // If default color and bold if (glyph.bold) { colorIndex = 1; diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts index 6fb3c5d1..614a6e92 100644 --- a/src/renderer/atlas/Types.ts +++ b/src/renderer/atlas/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -export const INVERTED_DEFAULT_COLOR = -1; +export const INVERTED_DEFAULT_COLOR = 258; export const DIM_OPACITY = 0.5; export interface IGlyphIdentifier { From 1e9564788ef286ee50fc59261d80b72474d80b7a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Oct 2018 11:12:35 -0700 Subject: [PATCH 02/56] Use constants for default colors --- src/Buffer.ts | 3 ++- src/renderer/BaseRenderLayer.ts | 4 ++-- src/renderer/LinkRenderLayer.ts | 4 ++-- src/renderer/TextRenderLayer.ts | 10 +++++----- src/renderer/atlas/DynamicCharAtlas.ts | 2 +- src/renderer/atlas/StaticCharAtlas.ts | 10 +++++----- src/renderer/atlas/Types.ts | 3 ++- src/renderer/dom/DomRendererRowFactory.test.ts | 11 ++++++----- src/renderer/dom/DomRendererRowFactory.ts | 9 +++++---- 9 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index e54f752b..7b5ee99a 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -8,8 +8,9 @@ import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIt import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; import { BufferLine, BufferLineTypedArray } from './BufferLine'; +import { DEFAULT_COLOR } from './renderer/atlas/Types'; -export const DEFAULT_ATTR = (0 << 18) | (257 << 9) | (256 << 0); +export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); export const CHAR_DATA_ATTR_INDEX = 0; export const CHAR_DATA_CHAR_INDEX = 1; export const CHAR_DATA_WIDTH_INDEX = 2; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 84e290e7..827356af 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -5,7 +5,7 @@ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; import { CharData, ITerminal } from '../Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier, DEFAULT_COLOR } from './atlas/Types'; import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { CHAR_DATA_CHAR_INDEX } from '../Buffer'; @@ -298,7 +298,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; - } else if (fg < 256) { + } else if (fg < DEFAULT_COLOR) { // 256 color support this._ctx.fillStyle = this._colors.ansi[fg].css; } else { diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 8679939a..c0d190ac 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -6,7 +6,7 @@ import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } from '../Types'; import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkHoverEvent = null; @@ -42,7 +42,7 @@ export class LinkRenderLayer extends BaseRenderLayer { private _onLinkHover(e: ILinkHoverEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; - } else if (e.fg < 256) { + } else if (e.fg < DEFAULT_COLOR) { // 256 color support this._ctx.fillStyle = this._colors.ansi[e.fg].css; } else { diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 7f10e7c9..984272f6 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -6,7 +6,7 @@ import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE } from '../Buffer'; import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { CharData, ITerminal } from '../Types'; -import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; @@ -143,10 +143,10 @@ export class TextRenderLayer extends BaseRenderLayer { const temp = bg; bg = fg; fg = temp; - if (fg === 256) { + if (fg === DEFAULT_COLOR) { fg = INVERTED_DEFAULT_COLOR; } - if (bg === 257) { + if (bg === DEFAULT_COLOR) { bg = INVERTED_DEFAULT_COLOR; } } @@ -186,7 +186,7 @@ export class TextRenderLayer extends BaseRenderLayer { let nextFillStyle = null; // null represents default background color if (bg === INVERTED_DEFAULT_COLOR) { nextFillStyle = this._colors.foreground.css; - } else if (bg < 256) { + } else if (bg < DEFAULT_COLOR) { nextFillStyle = this._colors.ansi[bg].css; } @@ -230,7 +230,7 @@ export class TextRenderLayer extends BaseRenderLayer { this._ctx.save(); if (fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; - } else if (fg < 256) { + } else if (fg < DEFAULT_COLOR) { // 256 color support this._ctx.fillStyle = this._colors.ansi[fg].css; } else { diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index e67fe4f0..950fdd1d 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -42,7 +42,7 @@ interface IGlyphCacheValue { inBitmap: boolean; } -function getGlyphCacheKey(glyph: IGlyphIdentifier): number { +export function getGlyphCacheKey(glyph: IGlyphIdentifier): number { // Note that this only returns a valid key when code < 256 // Layout: // 0b00000000000000000000000000000001: italic (1) diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts index 5c56f019..673a1509 100644 --- a/src/renderer/atlas/StaticCharAtlas.ts +++ b/src/renderer/atlas/StaticCharAtlas.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier } from './Types'; +import { DIM_OPACITY, IGlyphIdentifier, DEFAULT_COLOR } from './Types'; import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from '../../shared/atlas/Types'; import { generateStaticCharAtlasTexture } from '../../shared/atlas/CharAtlasGenerator'; import BaseCharAtlas from './BaseCharAtlas'; @@ -41,8 +41,8 @@ export default class StaticCharAtlas extends BaseCharAtlas { const isAscii = glyph.code < 256; // A color is basic if it is one of the 4 bit ANSI colors. const isBasicColor = glyph.fg < 16; - const isDefaultColor = glyph.fg === 256; - const isDefaultBackground = glyph.bg >= 256; + const isDefaultColor = glyph.fg === DEFAULT_COLOR; + const isDefaultBackground = glyph.bg === DEFAULT_COLOR; return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic; } @@ -58,9 +58,9 @@ export default class StaticCharAtlas extends BaseCharAtlas { } let colorIndex = 0; - if (glyph.fg < 256) { + if (glyph.fg < DEFAULT_COLOR) { colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0); - } else if (glyph.fg === 256) { + } else if (glyph.fg === DEFAULT_COLOR) { // If default color and bold if (glyph.bold) { colorIndex = 1; diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts index 614a6e92..76cfd07d 100644 --- a/src/renderer/atlas/Types.ts +++ b/src/renderer/atlas/Types.ts @@ -3,7 +3,8 @@ * @license MIT */ -export const INVERTED_DEFAULT_COLOR = 258; +export const DEFAULT_COLOR = 256; +export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; export interface IGlyphIdentifier { diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 2c46d8cc..a19865cd 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -10,6 +10,7 @@ import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '. import { FLAGS } from '../Types'; import { BufferLine } from '../../BufferLine'; import { IBufferLine } from '../../Types'; +import { DEFAULT_COLOR } from '../atlas/Types'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; @@ -80,7 +81,7 @@ describe('DomRendererRowFactory', () => { }); it('should add classes for 256 foreground colors', () => { - const defaultAttrNoFgColor = (0 << 9) | (256 << 0); + 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); @@ -92,7 +93,7 @@ describe('DomRendererRowFactory', () => { }); it('should add classes for 256 background colors', () => { - const defaultAttrNoBgColor = (257 << 9) | (0 << 0); + 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); @@ -113,7 +114,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default fg color', () => { - lineData.set(0, [(FLAGS.INVERSE << 18) | (257 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]); + 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); assert.equal(getFragmentHtml(fragment), 'a' + @@ -122,7 +123,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default bg color', () => { - lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]); + 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); assert.equal(getFragmentHtml(fragment), 'a' + @@ -132,7 +133,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) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]); + 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); assert.equal(getFragmentHtml(fragment), `a` + diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 4bb59902..877f3fdd 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -6,6 +6,7 @@ import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer'; import { FLAGS } from '../Types'; import { IBufferLine } from '../../Types'; +import { DEFAULT_COLOR } from '../atlas/Types'; export const BOLD_CLASS = 'xterm-bold'; export const ITALIC_CLASS = 'xterm-italic'; @@ -70,10 +71,10 @@ export class DomRendererRowFactory { const temp = bg; bg = fg; fg = temp; - if (fg === 256) { + if (fg === DEFAULT_COLOR) { fg = 0; } - if (bg === 257) { + if (bg === DEFAULT_COLOR) { bg = 15; } } @@ -91,10 +92,10 @@ export class DomRendererRowFactory { } charElement.textContent = char; - if (fg !== 257) { + if (fg < DEFAULT_COLOR) { charElement.classList.add(`xterm-fg-${fg}`); } - if (bg !== 256) { + if (bg < DEFAULT_COLOR) { charElement.classList.add(`xterm-bg-${bg}`); } fragment.appendChild(charElement); From bc16a8b7e747107807d189327f87efd4eaebbee9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Oct 2018 11:35:48 -0700 Subject: [PATCH 03/56] Handle inverted default colors properly in DOM renderer This uses the actual fg for bg and bg to fg, rather than the black/white theme colors Fixes #1738 --- src/renderer/dom/DomRenderer.ts | 4 ++++ src/renderer/dom/DomRendererRowFactory.ts | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 8f51c4ef..069e9a13 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -10,6 +10,7 @@ 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 { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -196,6 +197,9 @@ export class DomRenderer extends EventEmitter implements IRenderer { `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; }); + styles += + `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this.colorManager.colors.background.css}; }` + + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this.colorManager.colors.foreground.css}; }`; this._themeStyleElement.innerHTML = styles; return this.colorManager.colors; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 877f3fdd..9f8ac357 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -6,7 +6,7 @@ import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer'; import { FLAGS } from '../Types'; import { IBufferLine } from '../../Types'; -import { DEFAULT_COLOR } from '../atlas/Types'; +import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types'; export const BOLD_CLASS = 'xterm-bold'; export const ITALIC_CLASS = 'xterm-italic'; @@ -72,15 +72,16 @@ export class DomRendererRowFactory { bg = fg; fg = temp; if (fg === DEFAULT_COLOR) { - fg = 0; + fg = INVERTED_DEFAULT_COLOR; } if (bg === DEFAULT_COLOR) { - bg = 15; + bg = INVERTED_DEFAULT_COLOR; } } if (flags & FLAGS.BOLD) { - // Convert the FG color to the bold variant + // 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; } @@ -92,10 +93,10 @@ export class DomRendererRowFactory { } charElement.textContent = char; - if (fg < DEFAULT_COLOR) { + if (fg !== DEFAULT_COLOR) { charElement.classList.add(`xterm-fg-${fg}`); } - if (bg < DEFAULT_COLOR) { + if (bg !== DEFAULT_COLOR) { charElement.classList.add(`xterm-bg-${bg}`); } fragment.appendChild(charElement); From 18035e239eb93a92b73911514a429cc1122ff47f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Oct 2018 11:56:00 -0700 Subject: [PATCH 04/56] Fix tests --- src/renderer/dom/DomRendererRowFactory.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index a19865cd..5bc849ed 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -117,7 +117,7 @@ describe('DomRendererRowFactory', () => { 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); assert.equal(getFragmentHtml(fragment), - 'a' + + 'a' + ' ' ); }); @@ -126,7 +126,7 @@ describe('DomRendererRowFactory', () => { 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); assert.equal(getFragmentHtml(fragment), - 'a' + + 'a' + ' ' ); }); From 33a358033ca26fd9d7d54f01127446357fac8b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 9 Oct 2018 23:48:58 +0200 Subject: [PATCH 05/56] new approach --- src/BufferLine.ts | 11 +++++------ src/Terminal.ts | 17 ++++++++++++----- src/common/CircularList.ts | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 53f5d4d4..4134673e 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -94,13 +94,11 @@ export class BufferLine implements IBufferLine { } } - public copyFrom(line: IBufferLine): void { - this._data = []; - for (let i = 0; i < line.length; ++i) { - this._push(line.get(i)); - } + public copyFrom(line: BufferLine): IBufferLine { + this._data = line._data.slice(0); this.length = line.length; this.isWrapped = line.isWrapped; + return this; } public clone(): IBufferLine { @@ -249,7 +247,7 @@ export class BufferLineTypedArray implements IBufferLine { } /** alter to a full copy of line */ - public copyFrom(line: BufferLineTypedArray): void { + public copyFrom(line: BufferLineTypedArray): IBufferLine { if (this.length !== line.length) { this._data = new Uint32Array(line._data); } else { @@ -262,6 +260,7 @@ export class BufferLineTypedArray implements IBufferLine { this._combined[el] = line._combined[el]; } this.isWrapped = line.isWrapped; + return this; } /** create a new clone */ diff --git a/src/Terminal.ts b/src/Terminal.ts index c995af62..919b039e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types'; import { IMouseZoneManager } from './ui/Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; @@ -1174,7 +1174,14 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(isWrapped?: boolean): void { - const newLine = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); + // TODO: make blank a member + let blank: IBufferLine = (this as any)._blank; + if (!blank || blank.length !== this.cols) { + blank = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); + (this as any)._blank = blank; + } + blank.isWrapped = !!(isWrapped); + const topRow = this.buffer.ybase + this.buffer.scrollTop; const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; @@ -1184,9 +1191,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Insert the line using the fastest method if (bottomRow === this.buffer.lines.length - 1) { - this.buffer.lines.push(newLine); + (this.buffer.lines as any).pushRecycling((line: IBufferLine | undefined) => (line) ? line.copyFrom(blank) : blank.clone()); } else { - this.buffer.lines.splice(bottomRow + 1, 0, newLine); + this.buffer.lines.splice(bottomRow + 1, 0, blank.clone()); } // Only adjust ybase and ydisp when the buffer is not trimmed @@ -1208,7 +1215,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // scrollback, instead we can just shift them in-place. const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/; this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); - this.buffer.lines.set(bottomRow, newLine); + this.buffer.lines.set(bottomRow, blank.clone()); } // Move the viewport to the bottom of the buffer unless the user is diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 542dbf12..9609960d 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -100,6 +100,24 @@ export class CircularList extends EventEmitter implements ICircularList { } } + /** + * Recycling push variant with a callback. + * The callback gets the value at the current position to be overwritten. + * Return the new value from the callback. + */ + public pushRecycling(callback: (el: T | undefined) => T): void { + this._array[this._getCyclicIndex(this._length)] = callback(this._array[this._getCyclicIndex(this._length)]); + if (this._length === this._maxLength) { + this._startIndex++; + if (this._startIndex === this._maxLength) { + this._startIndex = 0; + } + this.emit('trim', 1); + } else { + this._length++; + } + } + /** * Removes and returns the last value on the list. * @return The popped value. From 751e2efa755d7d5e7b45474725676bf0b2d0100c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 10 Oct 2018 15:00:38 +0200 Subject: [PATCH 06/56] better integration and experimentalPushRecycling option --- src/Terminal.ts | 34 ++++++++++++++++++++++++---------- src/Types.ts | 2 +- src/common/CircularList.ts | 2 +- src/common/Types.ts | 1 + typings/xterm.d.ts | 2 ++ 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 919b039e..ff820982 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -106,7 +106,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = { theme: null, rightClickSelectsWord: Browser.isMac, rendererType: 'canvas', - experimentalBufferLineImpl: 'JsArray' + experimentalBufferLineImpl: 'JsArray', + experimentalPushRecycling: false }; export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { @@ -208,6 +209,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _screenDprMonitor: ScreenDprMonitor; private _theme: ITheme; + // bufferline to clone/copy from for new blank lines + private _blankLine: IBufferLine = null; + public cols: number; public rows: number; @@ -496,6 +500,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II case 'experimentalBufferLineImpl': this.buffers.normal.setBufferLineFactory(value); this.buffers.alt.setBufferLineFactory(value); + this._blankLine = null; break; } // Inform renderer of changes @@ -1174,13 +1179,18 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(isWrapped?: boolean): void { - // TODO: make blank a member - let blank: IBufferLine = (this as any)._blank; - if (!blank || blank.length !== this.cols) { - blank = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); - (this as any)._blank = blank; + let newLine: IBufferLine; + const useRecycling = this.options.experimentalPushRecycling; + if (useRecycling) { + newLine = this._blankLine; + if (!newLine || newLine.length !== this.cols) { + newLine = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); + this._blankLine = newLine; + } + newLine.isWrapped = !!(isWrapped); + } else { + newLine = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); } - blank.isWrapped = !!(isWrapped); const topRow = this.buffer.ybase + this.buffer.scrollTop; const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; @@ -1191,9 +1201,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Insert the line using the fastest method if (bottomRow === this.buffer.lines.length - 1) { - (this.buffer.lines as any).pushRecycling((line: IBufferLine | undefined) => (line) ? line.copyFrom(blank) : blank.clone()); + if (useRecycling) { + this.buffer.lines.pushRecycling((item) => (item) ? item.copyFrom(newLine) : newLine.clone()); + } else { + this.buffer.lines.push(newLine); + } } else { - this.buffer.lines.splice(bottomRow + 1, 0, blank.clone()); + this.buffer.lines.splice(bottomRow + 1, 0, (useRecycling) ? newLine.clone() : newLine); } // Only adjust ybase and ydisp when the buffer is not trimmed @@ -1215,7 +1229,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // scrollback, instead we can just shift them in-place. const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/; this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); - this.buffer.lines.set(bottomRow, blank.clone()); + this.buffer.lines.set(bottomRow, (useRecycling) ? newLine.clone() : newLine); } // Move the viewport to the bottom of the buffer unless the user is diff --git a/src/Types.ts b/src/Types.ts index e8578426..cafe60db 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -522,7 +522,7 @@ export interface IBufferLine { replaceCells(start: number, end: number, fill: CharData): void; resize(cols: number, fill: CharData, shrink?: boolean): void; fill(fillCharData: CharData): void; - copyFrom(line: IBufferLine): void; + copyFrom(line: IBufferLine): IBufferLine; clone(): IBufferLine; } diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 9609960d..cc0809ed 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -105,7 +105,7 @@ export class CircularList extends EventEmitter implements ICircularList { * The callback gets the value at the current position to be overwritten. * Return the new value from the callback. */ - public pushRecycling(callback: (el: T | undefined) => T): void { + public pushRecycling(callback: (item: T | undefined) => T): void { this._array[this._getCyclicIndex(this._length)] = callback(this._array[this._getCyclicIndex(this._length)]); if (this._length === this._maxLength) { this._startIndex++; diff --git a/src/common/Types.ts b/src/common/Types.ts index aabe721e..ec6fcfd5 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -28,6 +28,7 @@ export interface ICircularList extends IEventEmitter { get(index: number): T | undefined; set(index: number, value: T): void; push(value: T): void; + pushRecycling(callback: (item: T | undefined) => T): void; pop(): T | undefined; splice(start: number, deleteCount: number, ...items: T[]): void; trimStart(count: number): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b5a8a109..7fb33a9b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -112,6 +112,8 @@ declare module 'xterm' { */ experimentalBufferLineImpl?: 'JsArray' | 'TypedArray'; + experimentalPushRecycling?: boolean; + /** * The font size used to render text. */ From 3f3263cbb04b1e4669b1e7440979f6069ffb092d Mon Sep 17 00:00:00 2001 From: Vladimir Zeifman Date: Sat, 13 Oct 2018 19:10:56 +0300 Subject: [PATCH 07/56] Fix tslint errors for all addons and un-exclude tests for search addon --- src/addons/attach/attach.test.ts | 2 +- src/addons/fit/fit.test.ts | 2 +- src/addons/fullscreen/fullscreen.test.ts | 2 +- src/addons/fullscreen/fullscreen.ts | 11 ++++++----- src/addons/search/search.test.ts | 1 + src/addons/search/tsconfig.json | 3 --- src/addons/terminado/terminado.test.ts | 2 +- src/addons/webLinks/webLinks.test.ts | 2 +- src/addons/winptyCompat/winptyCompat.test.ts | 2 +- src/addons/zmodem/zmodem.test.ts | 2 +- src/addons/zmodem/zmodem.ts | 4 ++-- 11 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/addons/attach/attach.test.ts b/src/addons/attach/attach.test.ts index 018cfb31..e280b656 100644 --- a/src/addons/attach/attach.test.ts +++ b/src/addons/attach/attach.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import * as attach from './attach'; diff --git a/src/addons/fit/fit.test.ts b/src/addons/fit/fit.test.ts index 9a6d89fd..781b5010 100644 --- a/src/addons/fit/fit.test.ts +++ b/src/addons/fit/fit.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import * as fit from './fit'; diff --git a/src/addons/fullscreen/fullscreen.test.ts b/src/addons/fullscreen/fullscreen.test.ts index bb98bd30..6d41bdfd 100644 --- a/src/addons/fullscreen/fullscreen.test.ts +++ b/src/addons/fullscreen/fullscreen.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import * as fullscreen from './fullscreen'; diff --git a/src/addons/fullscreen/fullscreen.ts b/src/addons/fullscreen/fullscreen.ts index 297a7f5b..ef9b8ffd 100644 --- a/src/addons/fullscreen/fullscreen.ts +++ b/src/addons/fullscreen/fullscreen.ts @@ -11,17 +11,18 @@ import { Terminal } from 'xterm'; * @param fullscreen Toggle fullscreen on (true) or off (false) */ export function toggleFullScreen(term: Terminal, fullscreen: boolean): void { - let fn: string; + let fn: Function; if (typeof fullscreen === 'undefined') { - fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add'; + fn = (term.element.classList.contains('fullscreen')) ? + term.element.classList.remove : term.element.classList.add; } else if (!fullscreen) { - fn = 'remove'; + fn = term.element.classList.remove; } else { - fn = 'add'; + fn = term.element.classList.add; } - term.element.classList[fn]('fullscreen'); + fn('fullscreen'); } export function apply(terminalConstructor: typeof Terminal): void { diff --git a/src/addons/search/search.test.ts b/src/addons/search/search.test.ts index 1fe18b6a..7451d428 100644 --- a/src/addons/search/search.test.ts +++ b/src/addons/search/search.test.ts @@ -2,6 +2,7 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ +declare var require: any; import { assert, expect } from 'chai'; import * as search from './search'; diff --git a/src/addons/search/tsconfig.json b/src/addons/search/tsconfig.json index 9998dc1b..c34a0bc5 100644 --- a/src/addons/search/tsconfig.json +++ b/src/addons/search/tsconfig.json @@ -18,8 +18,5 @@ }, "include": [ "**/*.ts" - ], - "exclude": [ - "**/*.test.ts" ] } diff --git a/src/addons/terminado/terminado.test.ts b/src/addons/terminado/terminado.test.ts index 2e4a53c5..e46eafdf 100644 --- a/src/addons/terminado/terminado.test.ts +++ b/src/addons/terminado/terminado.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import * as terminado from './terminado'; diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts index c84ee1a5..014c25e4 100644 --- a/src/addons/webLinks/webLinks.test.ts +++ b/src/addons/webLinks/webLinks.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import * as webLinks from './webLinks'; diff --git a/src/addons/winptyCompat/winptyCompat.test.ts b/src/addons/winptyCompat/winptyCompat.test.ts index 0c9269ed..c3a7e479 100644 --- a/src/addons/winptyCompat/winptyCompat.test.ts +++ b/src/addons/winptyCompat/winptyCompat.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import * as winptyCompat from './winptyCompat'; diff --git a/src/addons/zmodem/zmodem.test.ts b/src/addons/zmodem/zmodem.test.ts index 682e62c8..d0c7c5fb 100644 --- a/src/addons/zmodem/zmodem.test.ts +++ b/src/addons/zmodem/zmodem.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { assert, expect } from 'chai'; +import { assert } from 'chai'; import * as zmodem from './zmodem'; diff --git a/src/addons/zmodem/zmodem.ts b/src/addons/zmodem/zmodem.ts index 23204f0f..70fc6e98 100644 --- a/src/addons/zmodem/zmodem.ts +++ b/src/addons/zmodem/zmodem.ts @@ -34,7 +34,7 @@ import { Terminal } from 'xterm'; * via `detach()` and a re-`attach()`.) */ -let zmodem; +let zmodem: any; export interface IZmodemOptions { noTerminalWriteOutsideSession?: boolean; @@ -44,7 +44,7 @@ function zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void { const term = this; const senderFunc = (octets: ArrayLike) => ws.send(new Uint8Array(octets)); - let zsentry; + let zsentry: any; function shouldWrite(): boolean { return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession; From 82228ad8913d85faeb442c76a9da29ffd0895917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 22 Oct 2018 18:15:47 +0200 Subject: [PATCH 08/56] trimAndRecycle with external check --- src/Terminal.ts | 7 ++++++- src/common/CircularList.ts | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index ff820982..516aa2a0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1202,7 +1202,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Insert the line using the fastest method if (bottomRow === this.buffer.lines.length - 1) { if (useRecycling) { - this.buffer.lines.pushRecycling((item) => (item) ? item.copyFrom(newLine) : newLine.clone()); + // this.buffer.lines.pushRecycling((item) => (item) ? item.copyFrom(newLine) : newLine.clone()); + if (willBufferBeTrimmed) { + (this.buffer.lines as any).trimAndRecycle().copyFrom(newLine); + } else { + this.buffer.lines.push(newLine.clone()); + } } else { this.buffer.lines.push(newLine); } diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index cc0809ed..682b8775 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -118,6 +118,15 @@ export class CircularList extends EventEmitter implements ICircularList { } } + public trimAndRecycle(): T | undefined { + this._startIndex++; + if (this._startIndex === this._maxLength) { + this._startIndex = 0; + } + this.emit('trim', 1); + return this._array[this._getCyclicIndex(this._length - 1)]; + } + /** * Removes and returns the last value on the list. * @return The popped value. @@ -154,10 +163,10 @@ export class CircularList extends EventEmitter implements ICircularList { } // Adjust length as needed - if (this._length + items.length > this.maxLength) { - const countToTrim = (this._length + items.length) - this.maxLength; + if (this._length + items.length > this._maxLength) { + const countToTrim = (this._length + items.length) - this._maxLength; this._startIndex += countToTrim; - this._length = this.maxLength; + this._length = this._maxLength; this.emit('trim', countToTrim); } else { this._length += items.length; @@ -196,7 +205,7 @@ export class CircularList extends EventEmitter implements ICircularList { const expandListBy = (start + count + offset) - this._length; if (expandListBy > 0) { this._length += expandListBy; - while (this._length > this.maxLength) { + while (this._length > this._maxLength) { this._length--; this._startIndex++; this.emit('trim', 1); @@ -216,6 +225,6 @@ export class CircularList extends EventEmitter implements ICircularList { * @returns The cyclic index. */ private _getCyclicIndex(index: number): number { - return (this._startIndex + index) % this.maxLength; + return (this._startIndex + index) % this._maxLength; } } From 1585aba5b5c174a7f9dc88395df87ac2180c4b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 22 Oct 2018 21:12:51 +0200 Subject: [PATCH 09/56] slightly faster trimAndRecycle --- src/common/CircularList.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 682b8775..48394c85 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -119,10 +119,7 @@ export class CircularList extends EventEmitter implements ICircularList { } public trimAndRecycle(): T | undefined { - this._startIndex++; - if (this._startIndex === this._maxLength) { - this._startIndex = 0; - } + this._startIndex = ++this._startIndex % this._maxLength; this.emit('trim', 1); return this._array[this._getCyclicIndex(this._length - 1)]; } From 1208fc9e106513a4979d054029c49765b8f98ff1 Mon Sep 17 00:00:00 2001 From: jrkong Date: Mon, 22 Oct 2018 16:32:04 -0400 Subject: [PATCH 10/56] updated webLinks regex to fix vscode issue#60401 --- src/addons/webLinks/webLinks.test.ts | 4 ++-- src/addons/webLinks/webLinks.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts index c84ee1a5..1ae62b80 100644 --- a/src/addons/webLinks/webLinks.test.ts +++ b/src/addons/webLinks/webLinks.test.ts @@ -32,11 +32,11 @@ describe('webLinks addon', () => { const term = new MockTerminal(); webLinks.webLinksInit(term); - const row = ' http://foo.com/a~b#c~d?e~f '; + const row = ' http://foo.com/a~b#c~d?e~f: '; const match = row.match(term.regex); const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); + assert.equal(uri, 'http://foo.com/a~b#c~d?e~f:'); }); }); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index a007bbd6..382d5dba 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -14,7 +14,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; const localHostClause = '(localhost)'; const portClause = '(:\\d{1,5})'; const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*'; +const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*'; const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; From 2efd28ad4b32593bf3403f84399c9cf1698c8c15 Mon Sep 17 00:00:00 2001 From: jrkong Date: Thu, 25 Oct 2018 00:14:20 -0400 Subject: [PATCH 11/56] updated webLinks regex to ignore colon at the end of a line and added the corresponding tests --- src/addons/webLinks/webLinks.test.ts | 28 ++++++++++++++++++++++++++-- src/addons/webLinks/webLinks.ts | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts index 1ae62b80..ce9b8b44 100644 --- a/src/addons/webLinks/webLinks.test.ts +++ b/src/addons/webLinks/webLinks.test.ts @@ -32,11 +32,35 @@ describe('webLinks addon', () => { const term = new MockTerminal(); webLinks.webLinksInit(term); - const row = ' http://foo.com/a~b#c~d?e~f: '; + const row = ' http://foo.com/a~b#c~d?e~f '; const match = row.match(term.regex); const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/a~b#c~d?e~f:'); + assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); + }); + + it('should allow : character in URI path', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://foo.com/colon:test '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/colon:test'); + }); + + it('should not allow : character at the end of a URI path', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://foo.com/colon:test: '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/colon:test'); }); }); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index 382d5dba..75d79104 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -14,7 +14,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; const localHostClause = '(localhost)'; const portClause = '(:\\d{1,5})'; const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*'; +const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:\\s])'; const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; From 93d7a3021d22f1940f1e3ecd58ff663854bfcec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 26 Oct 2018 16:11:47 +0200 Subject: [PATCH 12/56] remove pushRecycling, test case for trimAndRecycle, docs --- src/Terminal.ts | 17 +++++++---------- src/common/CircularList.test.ts | 16 ++++++++++++++++ src/common/CircularList.ts | 23 +++++++---------------- src/common/Types.ts | 2 +- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 9e79f3a8..5b79750c 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 } from './Buffer'; +import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './common/EventEmitter'; import { Viewport } from './Viewport'; @@ -1179,23 +1179,19 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(isWrapped?: boolean): void { -<<<<<<< HEAD let newLine: IBufferLine; const useRecycling = this.options.experimentalPushRecycling; if (useRecycling) { newLine = this._blankLine; - if (!newLine || newLine.length !== this.cols) { - newLine = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); + if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) { + newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); this._blankLine = newLine; } newLine.isWrapped = !!(isWrapped); } else { - newLine = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); + newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); } -======= - const newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); ->>>>>>> master const topRow = this.buffer.ybase + this.buffer.scrollTop; const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; @@ -1206,9 +1202,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Insert the line using the fastest method if (bottomRow === this.buffer.lines.length - 1) { if (useRecycling) { - // this.buffer.lines.pushRecycling((item) => (item) ? item.copyFrom(newLine) : newLine.clone()); if (willBufferBeTrimmed) { - (this.buffer.lines as any).trimAndRecycle().copyFrom(newLine); + // Warning: Never call .trimAndRecycle() without the + // willBufferBeTrimmed guard! + this.buffer.lines.trimAndRecycle().copyFrom(newLine); } else { this.buffer.lines.push(newLine.clone()); } diff --git a/src/common/CircularList.test.ts b/src/common/CircularList.test.ts index 4c07b16c..5f0419ab 100644 --- a/src/common/CircularList.test.ts +++ b/src/common/CircularList.test.ts @@ -257,4 +257,20 @@ describe('CircularList', () => { assert.equal(list.get(3), 4); }); }); + describe('trimAndRecycle', function(): void { + it('should return correct element', function(): void { + const list = new CircularList(5); + list.push([0]); + list.push([1]); + list.push([2]); + list.push([3]); + list.push([4]); + assert.equal(list.trimAndRecycle()[0], 0); + assert.equal(list.trimAndRecycle()[0], 1); + assert.equal(list.trimAndRecycle()[0], 2); + assert.equal(list.trimAndRecycle()[0], 3); + assert.equal(list.trimAndRecycle()[0], 4); + assert.equal(list.trimAndRecycle()[0], 0); + }); + }); }); diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 48394c85..345eb106 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -101,23 +101,14 @@ export class CircularList extends EventEmitter implements ICircularList { } /** - * Recycling push variant with a callback. - * The callback gets the value at the current position to be overwritten. - * Return the new value from the callback. + * Recycling trim. + * This is used to recycle buffer lines in Terminal.scroll when + * the list is at maxLength as a push replacement. + * Returns the old line as new one to be recycled. + * Note: There are no bound checks for performance reasons, + * the method is a special optimization for Terminal.scroll, + * do not use it anywhere else. */ - public pushRecycling(callback: (item: T | undefined) => T): void { - this._array[this._getCyclicIndex(this._length)] = callback(this._array[this._getCyclicIndex(this._length)]); - if (this._length === this._maxLength) { - this._startIndex++; - if (this._startIndex === this._maxLength) { - this._startIndex = 0; - } - this.emit('trim', 1); - } else { - this._length++; - } - } - public trimAndRecycle(): T | undefined { this._startIndex = ++this._startIndex % this._maxLength; this.emit('trim', 1); diff --git a/src/common/Types.ts b/src/common/Types.ts index ec6fcfd5..a1c12adf 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -28,7 +28,7 @@ export interface ICircularList extends IEventEmitter { get(index: number): T | undefined; set(index: number, value: T): void; push(value: T): void; - pushRecycling(callback: (item: T | undefined) => T): void; + trimAndRecycle(): T | undefined; pop(): T | undefined; splice(start: number, deleteCount: number, ...items: T[]): void; trimStart(count: number): void; From c7eb727220edc4a48423c49811296c953ca44f08 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Fri, 26 Oct 2018 00:07:13 -0500 Subject: [PATCH 13/56] Only show cross-hairs when terminal has focus Fixes #1767 --- src/Terminal.ts | 4 ++-- src/xterm.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index e8871462..4674f776 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1154,9 +1154,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II */ public updateCursorStyle(ev: KeyboardEvent): void { if (this.selectionManager && this.selectionManager.shouldColumnSelect(ev)) { - this.element.classList.add('xterm-cursor-crosshair'); + this.element.classList.add('column-select'); } else { - this.element.classList.remove('xterm-cursor-crosshair'); + this.element.classList.remove('column-select'); } } diff --git a/src/xterm.css b/src/xterm.css index 8e129f50..24cd475f 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -139,7 +139,7 @@ cursor: pointer; } -.xterm.xterm-cursor-crosshair { +.xterm.column-select.focus { /* Column selection mode */ cursor: crosshair; } From fb64c527a58e9c1ba84c2ed46516010c77b320e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 30 Oct 2018 17:40:11 +0100 Subject: [PATCH 14/56] revert to precheck pushWouldTrim --- src/BufferLine.ts | 6 ++---- src/Terminal.ts | 18 ++++++++++-------- src/Types.ts | 2 +- src/common/CircularList.test.ts | 16 ---------------- src/common/CircularList.ts | 20 +++++--------------- src/common/Types.ts | 2 +- typings/xterm.d.ts | 7 ++++++- 7 files changed, 25 insertions(+), 46 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index c1ec2609..7c697334 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -94,11 +94,10 @@ export class BufferLine implements IBufferLine { } } - public copyFrom(line: BufferLine): IBufferLine { + public copyFrom(line: BufferLine): void { this._data = line._data.slice(0); this.length = line.length; this.isWrapped = line.isWrapped; - return this; } public clone(): IBufferLine { @@ -249,7 +248,7 @@ export class BufferLineTypedArray implements IBufferLine { } /** alter to a full copy of line */ - public copyFrom(line: BufferLineTypedArray): IBufferLine { + public copyFrom(line: BufferLineTypedArray): void { if (this.length !== line.length) { this._data = new Uint32Array(line._data); } else { @@ -262,7 +261,6 @@ export class BufferLineTypedArray implements IBufferLine { this._combined[el] = line._combined[el]; } this.isWrapped = line.isWrapped; - return this; } /** create a new clone */ diff --git a/src/Terminal.ts b/src/Terminal.ts index df73ca58..420db146 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -107,7 +107,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { rightClickSelectsWord: Browser.isMac, rendererType: 'canvas', experimentalBufferLineImpl: 'JsArray', - experimentalPushRecycling: false + experimentalBufferLineRecycling: false }; export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { @@ -1179,16 +1179,16 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * Scroll the terminal down 1 row, creating a blank line. * @param isWrapped Whether the new line is wrapped from the previous line. */ - public scroll(isWrapped?: boolean): void { + public scroll(isWrapped: boolean = false): void { let newLine: IBufferLine; - const useRecycling = this.options.experimentalPushRecycling; + const useRecycling = this.options.experimentalBufferLineRecycling; if (useRecycling) { newLine = this._blankLine; if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) { newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); this._blankLine = newLine; } - newLine.isWrapped = !!(isWrapped); + newLine.isWrapped = isWrapped; } else { newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); } @@ -1198,15 +1198,17 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (this.buffer.scrollTop === 0) { // Determine whether the buffer is going to be trimmed after insertion. - const willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength; + const willBufferBeTrimmed = this.buffer.lines.pushWouldTrim(); // Insert the line using the fastest method if (bottomRow === this.buffer.lines.length - 1) { if (useRecycling) { if (willBufferBeTrimmed) { - // Warning: Never call .trimAndRecycle() without the - // willBufferBeTrimmed guard! - this.buffer.lines.trimAndRecycle().copyFrom(newLine); + // push would trim the oldest line in the ringbuffer + // therefore we can recycle it here as the new line + const recycled = this.buffer.lines.get(0); + recycled.copyFrom(newLine); + this.buffer.lines.push(recycled); } else { this.buffer.lines.push(newLine.clone()); } diff --git a/src/Types.ts b/src/Types.ts index cafe60db..e8578426 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -522,7 +522,7 @@ export interface IBufferLine { replaceCells(start: number, end: number, fill: CharData): void; resize(cols: number, fill: CharData, shrink?: boolean): void; fill(fillCharData: CharData): void; - copyFrom(line: IBufferLine): IBufferLine; + copyFrom(line: IBufferLine): void; clone(): IBufferLine; } diff --git a/src/common/CircularList.test.ts b/src/common/CircularList.test.ts index 5f0419ab..4c07b16c 100644 --- a/src/common/CircularList.test.ts +++ b/src/common/CircularList.test.ts @@ -257,20 +257,4 @@ describe('CircularList', () => { assert.equal(list.get(3), 4); }); }); - describe('trimAndRecycle', function(): void { - it('should return correct element', function(): void { - const list = new CircularList(5); - list.push([0]); - list.push([1]); - list.push([2]); - list.push([3]); - list.push([4]); - assert.equal(list.trimAndRecycle()[0], 0); - assert.equal(list.trimAndRecycle()[0], 1); - assert.equal(list.trimAndRecycle()[0], 2); - assert.equal(list.trimAndRecycle()[0], 3); - assert.equal(list.trimAndRecycle()[0], 4); - assert.equal(list.trimAndRecycle()[0], 0); - }); - }); }); diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 345eb106..00d5a520 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -90,10 +90,7 @@ export class CircularList extends EventEmitter implements ICircularList { public push(value: T): void { this._array[this._getCyclicIndex(this._length)] = value; if (this._length === this._maxLength) { - this._startIndex++; - if (this._startIndex === this._maxLength) { - this._startIndex = 0; - } + this._startIndex = ++this._startIndex % this._maxLength; this.emit('trim', 1); } else { this._length++; @@ -101,18 +98,11 @@ export class CircularList extends EventEmitter implements ICircularList { } /** - * Recycling trim. - * This is used to recycle buffer lines in Terminal.scroll when - * the list is at maxLength as a push replacement. - * Returns the old line as new one to be recycled. - * Note: There are no bound checks for performance reasons, - * the method is a special optimization for Terminal.scroll, - * do not use it anywhere else. + * Whether a push would trim. + * True when the ringbuffer is full. */ - public trimAndRecycle(): T | undefined { - this._startIndex = ++this._startIndex % this._maxLength; - this.emit('trim', 1); - return this._array[this._getCyclicIndex(this._length - 1)]; + public pushWouldTrim(): boolean { + return this._length === this._maxLength; } /** diff --git a/src/common/Types.ts b/src/common/Types.ts index a1c12adf..c866581d 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -28,7 +28,7 @@ export interface ICircularList extends IEventEmitter { get(index: number): T | undefined; set(index: number, value: T): void; push(value: T): void; - trimAndRecycle(): T | undefined; + pushWouldTrim(): boolean; pop(): T | undefined; splice(start: number, deleteCount: number, ...items: T[]): void; trimStart(count: number): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 68e1e188..8b36dd75 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -112,7 +112,12 @@ declare module 'xterm' { */ experimentalBufferLineImpl?: 'JsArray' | 'TypedArray'; - experimentalPushRecycling?: boolean; + /** + * (EXPERIMENTAL) Enable recycling of buffer lines. + * + * This option will be removed in the future. + */ + experimentalBufferLineRecycling?: boolean; /** * The font size used to render text. From e9906f9b9e73f2a8ffefcb6ceccb8dd4c20bd864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 7 Nov 2018 00:56:45 +0100 Subject: [PATCH 15/56] compromise between code safety and speed --- src/Terminal.ts | 8 ++------ src/common/CircularList.ts | 20 +++++++++++++++++--- src/common/Types.ts | 3 ++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 420db146..13be90ef 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1198,17 +1198,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (this.buffer.scrollTop === 0) { // Determine whether the buffer is going to be trimmed after insertion. - const willBufferBeTrimmed = this.buffer.lines.pushWouldTrim(); + const willBufferBeTrimmed = this.buffer.lines.isFull(); // Insert the line using the fastest method if (bottomRow === this.buffer.lines.length - 1) { if (useRecycling) { if (willBufferBeTrimmed) { - // push would trim the oldest line in the ringbuffer - // therefore we can recycle it here as the new line - const recycled = this.buffer.lines.get(0); - recycled.copyFrom(newLine); - this.buffer.lines.push(recycled); + this.buffer.lines.recycle().copyFrom(newLine); } else { this.buffer.lines.push(newLine.clone()); } diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 00d5a520..0a57ece0 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -98,10 +98,24 @@ export class CircularList extends EventEmitter implements ICircularList { } /** - * Whether a push would trim. - * True when the ringbuffer is full. + * Advance ringbuffer index and return current element for recycling. + * Note: If the ringbuffer is not full this method will return undefined, + * Either precheck with isFull() or handle the undefined return value accordingly. */ - public pushWouldTrim(): boolean { + public recycle(): T | undefined { + if (this._length === this._maxLength) { + this._startIndex = ++this._startIndex % this._maxLength; + this.emit('trim', 1); + } else { + this._length++; + } + return this._array[this._getCyclicIndex(this._length - 1)]; + } + + /** + * Ringbuffer is at max length. + */ + public isFull(): boolean { return this._length === this._maxLength; } diff --git a/src/common/Types.ts b/src/common/Types.ts index c866581d..841029ec 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -28,7 +28,8 @@ export interface ICircularList extends IEventEmitter { get(index: number): T | undefined; set(index: number, value: T): void; push(value: T): void; - pushWouldTrim(): boolean; + recycle(): T | undefined; + isFull(): boolean; pop(): T | undefined; splice(start: number, deleteCount: number, ...items: T[]): void; trimStart(count: number): void; From 774793c7ca77093bb9878d654159164ca28aae98 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 8 Nov 2018 19:20:12 -0800 Subject: [PATCH 16/56] Fixing isClickInSelection to cover missing cases The following cases weren't working: - The first character on single line selections - The first row on multi-line selection --- src/SelectionManager.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index bfb57177..47cad7ef 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -290,8 +290,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager } return (coords[1] > start[1] && coords[1] < end[1]) || - (start[1] === end[1] && coords[1] === start[1] && coords[0] > start[0] && coords[0] < end[0]) || - (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]); + (start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) || + (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) || + (start[1] < end[1] && coords[1] === start[1] && coords[0] >= start[0]); } /** From 9faebaf5fe8f7e480c4206a041fc2b67679fb3b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 15 Nov 2018 00:16:52 +0100 Subject: [PATCH 17/56] hold single AudioContext instance --- src/SoundManager.ts | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index 4139c207..ffb01658 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -12,7 +12,19 @@ import { ITerminal, ISoundManager } from './Types'; export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; export class SoundManager implements ISoundManager { - private _audioContext: AudioContext; + private static _audioContext: AudioContext; + + static get audioContext(): AudioContext { + if (!SoundManager._audioContext) { + const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; + if (!audioContextCtor) { + console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version'); + return null; + } + this._audioContext = new audioContextCtor(); + } + return SoundManager._audioContext; + } constructor( private _terminal: ITerminal @@ -20,22 +32,16 @@ export class SoundManager implements ISoundManager { } public playBellSound(): void { - const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; - if (!this._audioContext && audioContextCtor) { - this._audioContext = new audioContextCtor(); - } - - if (this._audioContext) { - const bellAudioSource = this._audioContext.createBufferSource(); - const context = this._audioContext; - this._audioContext.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => { - bellAudioSource.buffer = buffer; - bellAudioSource.connect(context.destination); - bellAudioSource.start(0); - }); - } else { - console.warn('Sorry, but the Web Audio API is not supported by your browser. Please, consider upgrading to the latest version'); + const context = SoundManager.audioContext; + if (!context) { + return; } + const bellAudioSource = context.createBufferSource(); + context.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => { + bellAudioSource.buffer = buffer; + bellAudioSource.connect(context.destination); + bellAudioSource.start(0); + }); } private _base64ToArrayBuffer(base64: string): ArrayBuffer { From e3146961bef54bb8e44206c90e5901c6d76434fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 15 Nov 2018 00:25:23 +0100 Subject: [PATCH 18/56] make linter happy --- src/SoundManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index ffb01658..a31c7574 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -13,7 +13,7 @@ export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBA export class SoundManager implements ISoundManager { private static _audioContext: AudioContext; - + static get audioContext(): AudioContext { if (!SoundManager._audioContext) { const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; From 0e3f9b72d1725f1797992b04e5ce320b69c56e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 15 Nov 2018 00:48:31 +0100 Subject: [PATCH 19/56] allow to set audio context per argument --- src/SoundManager.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index a31c7574..0ba6e0c2 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -14,7 +14,7 @@ export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBA export class SoundManager implements ISoundManager { private static _audioContext: AudioContext; - static get audioContext(): AudioContext { + static get audioContext(): AudioContext | null { if (!SoundManager._audioContext) { const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; if (!audioContextCtor) { @@ -27,19 +27,20 @@ export class SoundManager implements ISoundManager { } constructor( - private _terminal: ITerminal + private _terminal: ITerminal, + private _audioContext?: AudioContext ) { } public playBellSound(): void { - const context = SoundManager.audioContext; - if (!context) { + const ctx = this._audioContext || SoundManager.audioContext; + if (!ctx) { return; } - const bellAudioSource = context.createBufferSource(); - context.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => { + const bellAudioSource = ctx.createBufferSource(); + ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => { bellAudioSource.buffer = buffer; - bellAudioSource.connect(context.destination); + bellAudioSource.connect(ctx.destination); bellAudioSource.start(0); }); } From a5894ee2344337442aafafa34ed9328a8fbf45f1 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Sat, 29 Sep 2018 09:39:49 -0500 Subject: [PATCH 20/56] Fix cursor movement when swapping buffers --- src/BufferSet.test.ts | 26 ++++++++++++++++++++++ src/BufferSet.ts | 4 ++++ src/InputHandler.test.ts | 48 +++++++++++++++++++++++++++++++++++++++- src/InputHandler.ts | 37 ++++++++++++++++++++++--------- 4 files changed, 103 insertions(+), 12 deletions(-) diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index 009ebf2e..38f2ddab 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -48,4 +48,30 @@ describe('BufferSet', () => { assert.equal(bufferSet.active, bufferSet.alt); }); }); + + describe('cursor handling when swapping buffers', () => { + beforeEach(() => { + bufferSet.normal.x = 0; + bufferSet.normal.y = 0; + bufferSet.alt.x = 0; + bufferSet.alt.y = 0; + }); + + it('should keep the cursor stationary when activating alt buffer', () => { + bufferSet.activateNormalBuffer(); + bufferSet.active.x = 30; + bufferSet.active.y = 10; + bufferSet.activateAltBuffer(); + assert.equal(bufferSet.active.x, 30); + assert.equal(bufferSet.active.y, 10); + }); + it('should keep the cursor stationary when activating normal buffer', () => { + bufferSet.activateAltBuffer(); + bufferSet.active.x = 30; + bufferSet.active.y = 10; + bufferSet.activateNormalBuffer(); + assert.equal(bufferSet.active.x, 30); + assert.equal(bufferSet.active.y, 10); + }); + }); }); diff --git a/src/BufferSet.ts b/src/BufferSet.ts index c91ab751..b213db0a 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -61,6 +61,8 @@ export class BufferSet extends EventEmitter implements IBufferSet { if (this._activeBuffer === this._normal) { return; } + this._normal.x = this._alt.x; + this._normal.y = this._alt.y; // The alt buffer should always be cleared when we switch to the normal // buffer. This frees up memory since the alt buffer should always be new // when activated. @@ -82,6 +84,8 @@ export class BufferSet extends EventEmitter implements IBufferSet { // Since the alt buffer is always cleared when the normal buffer is // activated, we want to fill it when switching to it. this._alt.fillViewportRows(); + this._alt.x = this._normal.x; + this._alt.y = this._normal.y; this._activeBuffer = this._alt; this.emit('activate', { activeBuffer: this._alt, diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index aaaf57c3..cd9d7d0b 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -6,7 +6,7 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal } from './utils/TestUtils.test'; -import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, CHAR_DATA_CHAR_INDEX } from './Buffer'; +import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './Types'; @@ -506,4 +506,50 @@ describe('InputHandler', () => { inputHandler.print(String.fromCharCode(0x200B), 0, 1); }); }); + + describe('alt screen', () => { + let term: Terminal; + let handler: InputHandler; + + function lineContent(line: IBufferLine): string { + let content = ''; + for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX]; + return content; + } + + beforeEach(() => { + term = new Terminal(); + handler = new InputHandler(term); + }); + it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { + handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); + expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' ')); + expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' ')); + // Text color of 'TEST' should be red + expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 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(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' ')); + expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' ')); + // Text color of 'TEST' should be red + expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 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(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' ')); + expect(lineContent(term.buffer.lines.get(1))).to.equal('JUNK' + Array(term.cols - 3).join(' ')); + // Text color of 'TEST' should be default + expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).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); + }); + it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { + handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); + expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' ')); + expect(lineContent(term.buffer.lines.get(1))).to.equal(Array(term.cols + 1).join(' ')); + // Text color of 'TEST' should be default + expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a34590ef..3fa9b705 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1280,7 +1280,9 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._terminal.log('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; - this._terminal.viewport.syncScrollArea(); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1329,14 +1331,19 @@ export class InputHandler extends Disposable implements IInputHandler { case 25: // show cursor this._terminal.cursorHidden = false; break; + case 1048: // alt screen cursor + this.saveCursor(params); + break; case 1049: // alt screen buffer cursor - // TODO: Not sure if we need to save/restore after switching the buffer - // this.saveCursor(params); + this.saveCursor(params); // FALL-THROUGH case 47: // alt screen buffer case 1047: // alt screen buffer this._terminal.buffers.activateAltBuffer(); - this._terminal.viewport.syncScrollArea(); + this._terminal.refresh(0, this._terminal.rows - 1); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } this._terminal.showCursor(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) @@ -1469,7 +1476,9 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._terminal.log('Switching back to normal keypad.'); this._terminal.applicationKeypad = false; - this._terminal.viewport.syncScrollArea(); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -1497,18 +1506,22 @@ export class InputHandler extends Disposable implements IInputHandler { case 25: // hide cursor this._terminal.cursorHidden = true; break; + case 1048: // alt screen cursor + this.restoreCursor(params); + break; case 1049: // alt screen buffer cursor // FALL-THROUGH case 47: // normal screen buffer case 1047: // normal screen buffer - clearing it first // Ensure the selection manager has the correct buffer this._terminal.buffers.activateNormalBuffer(); - // TODO: Not sure if we need to save/restore after switching the buffer - // if (params[0] === 1049) { - // this.restoreCursor(params); - // } + if (params[0] === 1049) { + this.restoreCursor(params); + } this._terminal.refresh(0, this._terminal.rows - 1); - this._terminal.viewport.syncScrollArea(); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } this._terminal.showCursor(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) @@ -1787,7 +1800,9 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.originMode = false; this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false this._terminal.applicationKeypad = false; // ? - this._terminal.viewport.syncScrollArea(); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } this._terminal.applicationCursor = false; this._terminal.buffer.scrollTop = 0; this._terminal.buffer.scrollBottom = this._terminal.rows - 1; From a19d93851c191187e5f601ecb6944649800c2fb2 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Sat, 29 Sep 2018 22:36:28 -0500 Subject: [PATCH 21/56] Each buffer needs its own saved cursor attributes --- src/Buffer.ts | 1 + src/InputHandler.test.ts | 10 ++++++++++ src/InputHandler.ts | 4 ++-- src/Terminal.ts | 1 - src/Types.ts | 2 +- src/utils/TestUtils.test.ts | 2 +- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index e54f752b..5eaf880b 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -38,6 +38,7 @@ export class Buffer implements IBuffer { public tabs: any; public savedY: number; public savedX: number; + public savedCurAttr: number; public markers: Marker[] = []; private _bufferLineConstructor: IBufferLineConstructor; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index cd9d7d0b..5fa3b47a 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -551,5 +551,15 @@ describe('InputHandler', () => { // Text color of 'TEST' should be default expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).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(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' ')); + // Text color of 'TEST' should be default + expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); + handler.parse('\x1b[?1049h\x1b[uTEST'); + expect(lineContent(term.buffer.lines.get(1))).to.equal('TEST' + Array(term.cols - 3).join(' ')); + // Text color of 'TEST' should be red + expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); + }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3fa9b705..47e1875b 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1869,7 +1869,7 @@ 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.savedCurAttr = this._terminal.curAttr; + this._terminal.buffer.savedCurAttr = this._terminal.curAttr; } @@ -1881,7 +1881,7 @@ 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.savedCurAttr || DEFAULT_ATTR; + this._terminal.curAttr = this._terminal.buffer.savedCurAttr || DEFAULT_ATTR; } diff --git a/src/Terminal.ts b/src/Terminal.ts index c9bc98ff..6675e444 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -170,7 +170,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public savedCols: number; public curAttr: number; - public savedCurAttr: number; public params: (string | number)[]; public currentParam: string | number; diff --git a/src/Types.ts b/src/Types.ts index e8578426..6a6b8369 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -49,7 +49,6 @@ export interface IInputHandlingTerminal extends IEventEmitter { wraparoundMode: boolean; bracketedPasteMode: boolean; curAttr: number; - savedCurAttr: number; savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; @@ -290,6 +289,7 @@ export interface IBuffer { hasScrollback: boolean; savedY: number; savedX: number; + savedCurAttr: number; isCursorInViewport: boolean; translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; getWrappedRangeForLine(y: number): { first: number, last: number }; diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 353e615f..a5ef4b9f 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -182,7 +182,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { wraparoundMode: boolean; bracketedPasteMode: boolean; curAttr: number; - savedCurAttr: number; savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; @@ -304,6 +303,7 @@ export class MockBuffer implements IBuffer { scrollTop: number; savedY: number; savedX: number; + savedCurAttr: number; translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string { return Buffer.prototype.translateBufferLineToString.apply(this, arguments); } From 2f900352432a3bbc989a1d31d7fdf01cd69d7d24 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Sun, 30 Sep 2018 22:37:17 -0500 Subject: [PATCH 22/56] Clear alt buffer with erase attributes upon entering --- src/Buffer.ts | 7 +++++-- src/BufferSet.ts | 4 ++-- src/InputHandler.test.ts | 5 +++++ src/InputHandler.ts | 2 +- src/Types.ts | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 5eaf880b..6d218645 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -114,11 +114,14 @@ export class Buffer implements IBuffer { /** * Fills the buffer's viewport with blank lines. */ - public fillViewportRows(): void { + public fillViewportRows(fillAttr?: number): void { if (this.lines.length === 0) { + if (fillAttr === undefined) { + fillAttr = DEFAULT_ATTR; + } let i = this._terminal.rows; while (i--) { - this.lines.push(this.getBlankLine(DEFAULT_ATTR)); + this.lines.push(this.getBlankLine(fillAttr)); } } } diff --git a/src/BufferSet.ts b/src/BufferSet.ts index b213db0a..f84757d1 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -77,13 +77,13 @@ export class BufferSet extends EventEmitter implements IBufferSet { /** * Sets the alt Buffer of the BufferSet as its currently active Buffer */ - public activateAltBuffer(): void { + public activateAltBuffer(fillAttr?: number): void { if (this._activeBuffer === this._alt) { return; } // Since the alt buffer is always cleared when the normal buffer is // activated, we want to fill it when switching to it. - this._alt.fillViewportRows(); + this._alt.fillViewportRows(fillAttr); this._alt.x = this._normal.x; this._alt.y = this._normal.y; this._activeBuffer = this._alt; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 5fa3b47a..b2fea06a 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -561,5 +561,10 @@ describe('InputHandler', () => { // Text color of 'TEST' should be red expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 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); + }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 47e1875b..ab75366a 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1339,7 +1339,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.buffers.activateAltBuffer(this._terminal.eraseAttr()); this._terminal.refresh(0, this._terminal.rows - 1); if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); diff --git a/src/Types.ts b/src/Types.ts index 6a6b8369..0f60e6f7 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -306,7 +306,7 @@ export interface IBufferSet extends IEventEmitter { active: IBuffer; activateNormalBuffer(): void; - activateAltBuffer(): void; + activateAltBuffer(fillAttr?: number): void; } export interface ISelectionManager { From a480f483cda4d73edd1f2963a1ae61e32acd973c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Nov 2018 17:54:03 -0800 Subject: [PATCH 23/56] Add tests --- src/SelectionManager.test.ts | 13 +++++++++++++ src/SelectionManager.ts | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index c42735d5..8735e894 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -30,6 +30,7 @@ class TestSelectionManager extends SelectionManager { public selectLineAt(line: number): void { this._selectLineAt(line); } public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); } + public areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean { return this._areCoordsInSelection(coords, start, end); } // Disable DOM interaction public enable(): void {} @@ -478,5 +479,17 @@ describe('SelectionManager', () => { assert.equal(selectionManager.selectionText, 'a\n😁\nc'); }); }); + + describe('_areCoordsInSelection', () => { + it('should return whether coords are in the selection', () => { + assert.isFalse(selectionManager.areCoordsInSelection([0, 0], [2, 0], [2, 1])); + assert.isFalse(selectionManager.areCoordsInSelection([1, 0], [2, 0], [2, 1])); + assert.isTrue(selectionManager.areCoordsInSelection([2, 0], [2, 0], [2, 1])); + assert.isTrue(selectionManager.areCoordsInSelection([10, 0], [2, 0], [2, 1])); + assert.isTrue(selectionManager.areCoordsInSelection([0, 1], [2, 0], [2, 1])); + assert.isTrue(selectionManager.areCoordsInSelection([1, 1], [2, 0], [2, 1])); + assert.isFalse(selectionManager.areCoordsInSelection([2, 1], [2, 0], [2, 1])); + }); + }); }); diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 47cad7ef..86be0c48 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -289,6 +289,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager return false; } + return this._areCoordsInSelection(coords, start, end); + } + + protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean { return (coords[1] > start[1] && coords[1] < end[1]) || (start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) || (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) || From 203ee0f644f833d2dd554a147ec3274349272887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Nov 2018 17:47:49 +0100 Subject: [PATCH 24/56] remove ctx args from ctor --- src/SoundManager.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index 0ba6e0c2..9adbfc4a 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -27,13 +27,12 @@ export class SoundManager implements ISoundManager { } constructor( - private _terminal: ITerminal, - private _audioContext?: AudioContext + private _terminal: ITerminal ) { } public playBellSound(): void { - const ctx = this._audioContext || SoundManager.audioContext; + const ctx = SoundManager.audioContext; if (!ctx) { return; } From 9286ea3eafe61c3ea9170378568d5fe6f46809f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Nov 2018 17:50:29 +0100 Subject: [PATCH 25/56] make statix usage explicit --- src/SoundManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index 9adbfc4a..6084edcb 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -21,7 +21,7 @@ export class SoundManager implements ISoundManager { console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version'); return null; } - this._audioContext = new audioContextCtor(); + SoundManager._audioContext = new audioContextCtor(); } return SoundManager._audioContext; } From 9b762854eb2626d420eeb58090332bc7a9f8a9b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Nov 2018 23:31:45 +0100 Subject: [PATCH 26/56] faster wcwidth init and runtime; fix slightly wrong surrogate handling --- src/CharWidth.ts | 87 +++++++++++++++++++++++---------------------- src/InputHandler.ts | 41 +++++++++++---------- 2 files changed, 67 insertions(+), 61 deletions(-) diff --git a/src/CharWidth.ts b/src/CharWidth.ts index d54e9392..045a796c 100644 --- a/src/CharWidth.ts +++ b/src/CharWidth.ts @@ -120,50 +120,41 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu return 1; } const control = opts.control | 0; - let table: number[] | Uint32Array = null; - function initTable(): number[] | Uint32Array { - // lookup table for BMP - const CODEPOINTS = 65536; // BMP holds 65536 codepoints - const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2 - const ITEMSIZE = 32; // using uint32_t - const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE; - const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH; - table = (typeof Uint32Array === 'undefined') - ? new Array(CONTAINERSIZE) - : new Uint32Array(CONTAINERSIZE); - for (let i = 0; i < CONTAINERSIZE; ++i) { - let num = 0; - let pos = CODEPOINTS_PER_ITEM; - while (pos--) { - num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); - } - table[i] = num; - } - return table; + + // create lookup table for BMP plane + // TODO: make callable/configurable from UnicodeManager + const table = new Uint8Array(65536); + table.fill(1); + table[0] = opts.nul; + // control chars + table.subarray(1, 32).fill(opts.control); + table.subarray(0x7f, 0xa0).fill(opts.control); + // combining 0 + for (let r = 0; r < COMBINING_BMP.length; ++r) { + table.subarray(COMBINING_BMP[r][0], COMBINING_BMP[r][1]).fill(0); } - // get width from lookup table - // position in container : num / CODEPOINTS_PER_ITEM - // ==> n = table[Math.floor(num / 16)] - // ==> n = table[num >> 4] - // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100 - // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH - // ==> m = (n % 16) * 2 - // ==> m = (num & 15) << 1 - // right shift to position m - // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766 - // we are only interested in 2 LSBs, cut off higher bits - // ==> n = n & 3 e.g. 000000000000000000000000000000XX + // wide chars + table.subarray(0x1100, 0x1160).fill(2); + table[0x2329] = 2; + table[0x232a] = 2; + table.subarray(0x2e80, 0xa4d0).fill(2); + table[0x303f] = 1; // wrongly added before + table.subarray(0xac00, 0xd7a4).fill(2); + table.subarray(0xf900, 0xfb00).fill(2); + table.subarray(0xfe10, 0xfe1a).fill(2); + table.subarray(0xfe30, 0xfe70).fill(2); + table.subarray(0xff00, 0xff61).fill(2); + table.subarray(0xffe0, 0xffe7).fill(2); + return function (num: number): number { - num = num | 0; // get asm.js like optimization under V8 if (num < 32) { return control | 0; } if (num < 127) { return 1; } - const t = table || initTable(); if (num < 65536) { - return t[num >> 4] >> ((num & 15) << 1) & 3; + return table[num]; } // do a full search for high codepoints return wcwidthHigh(num); @@ -175,17 +166,27 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu */ export function getStringCellWidth(s: string): number { let result = 0; - for (let i = 0; i < s.length; ++i) { + const length = s.length; + for (let i = 0; i < length; ++i) { let code = s.charCodeAt(i); + // surrogate pair first if (0xD800 <= code && code <= 0xDBFF) { - const low = s.charCodeAt(i + 1); - if (isNaN(low)) { - return result; + if (++i >= length) { + // this should not happen with strings retrieved from + // Buffer.translateToString as it converts from UTF-32 + // and therefore always should contain the second part + // for any other string we still have to handle it somehow: + // simply treat the lonely surrogate first as a single char (UCS-2 behavior) + return result + wcwidth(code); + } + const second = s.charCodeAt(i); + // convert surrogate pair to high codepoint only for valid second part (UTF-16) + // otherwise treat them independently (UCS-2 behavior) + if (0xDC00 <= second && second <= 0xDFFF) { + code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } else { + result += wcwidth(second); } - code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; - } - if (0xDC00 <= code && code <= 0xDFFF) { - continue; } result += wcwidth(code); } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a34590ef..893046bf 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -113,7 +113,7 @@ class DECRQSS implements IDcsHandler { * each function's header comment. */ export class InputHandler extends Disposable implements IInputHandler { - private _surrogateHigh: string; + private _surrogateFirst: string; constructor( protected _terminal: IInputHandlingTerminal, @@ -123,7 +123,7 @@ export class InputHandler extends Disposable implements IInputHandler { this.register(this._parser); - this._surrogateHigh = ''; + this._surrogateFirst = ''; /** * custom fallback handlers @@ -311,9 +311,9 @@ export class InputHandler extends Disposable implements IInputHandler { } // apply leftover surrogate high from last write - if (this._surrogateHigh) { - data = this._surrogateHigh + data; - this._surrogateHigh = ''; + if (this._surrogateFirst) { + data = this._surrogateFirst + data; + this._surrogateFirst = ''; } this._parser.parse(data); @@ -327,7 +327,7 @@ export class InputHandler extends Disposable implements IInputHandler { public print(data: string, start: number, end: number): void { let char: string; let code: number; - let low: number; + let second: number; let chWidth: number; const buffer: IBuffer = this._terminal.buffer; const charset: ICharset = this._terminal.charset; @@ -345,20 +345,25 @@ export class InputHandler extends Disposable implements IInputHandler { // surrogate pair handling if (0xD800 <= code && code <= 0xDBFF) { - // we got a surrogate high - // get surrogate low (next 2 bytes) - low = data.charCodeAt(stringPosition + 1); - if (isNaN(low)) { - // end of data stream, save surrogate high - this._surrogateHigh = char; + if (++stringPosition >= end) { + // end of input: + // handle pairs as true UTF-16 and wait for the second part + // since we expect the input comming from a stream there is + // a small chance that the surrogate pair got split + // therefore we dont process the first char here, instead + // it gets added as first char to the next processed chunk + this._surrogateFirst = char; continue; } - code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; - char += data.charAt(stringPosition + 1); - } - // surrogate low - already handled above - if (0xDC00 <= code && code <= 0xDFFF) { - continue; + second = data.charCodeAt(stringPosition); + // if the second part is in surrogate pair range create the high codepoint + // otherwise fall back to UCS-2 behavior (handle codepoints independently) + if (0xDC00 <= second && second <= 0xDFFF) { + code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + char += data.charAt(stringPosition); + } else { + stringPosition--; + } } // calculate print space From 21f848d0ab21205dfe26f94ca0753ab461e3f591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Nov 2018 23:48:59 +0100 Subject: [PATCH 27/56] regression test --- src/CharWidth.test.ts | 178 +++++++++++++++++++++++++++++++++++++++++- src/CharWidth.ts | 50 +++--------- 2 files changed, 190 insertions(+), 38 deletions(-) diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts index 3242dd77..c2d10c5c 100644 --- a/src/CharWidth.test.ts +++ b/src/CharWidth.test.ts @@ -5,7 +5,7 @@ import { TestTerminal } from './utils/TestUtils.test'; import { assert } from 'chai'; -import { getStringCellWidth } from './CharWidth'; +import { getStringCellWidth, wcwidth } from './CharWidth'; import { IBuffer } from './Types'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; @@ -77,3 +77,179 @@ describe('getStringCellWidth', function(): void { }); // TODO: multiline tests once #1685 is resolved }); + +describe('wcwidth regression', function(): void { + // TODO: remove with one of the next releases + const wcwidthOld = (function(opts: {nul: number, control: number}): (ucs: number) => number { + // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c + // combining characters + const COMBINING_BMP = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] + ]; + const COMBINING_HIGH = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + // binary search + function bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; + } + } + return false; + } + function wcwidthBMP(ucs: number): number { + // test for 8-bit control characters + if (ucs === 0) { + return opts.nul; + } + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) { + return opts.control; + } + // binary search in table of non-spacing characters + if (bisearch(ucs, COMBINING_BMP)) { + return 0; + } + // if we arrive here, ucs is not a combining or C0/C1 control character + if (isWideBMP(ucs)) { + return 2; + } + return 1; + } + function isWideBMP(ucs: number): boolean { + return ( + ucs >= 0x1100 && ( + ucs <= 0x115f || // Hangul Jamo init. consonants + ucs === 0x2329 || + ucs === 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi + (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables + (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs + (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms + (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms + (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms + (ucs >= 0xffe0 && ucs <= 0xffe6))); + } + function wcwidthHigh(ucs: number): 0 | 1 | 2 { + if (bisearch(ucs, COMBINING_HIGH)) { + return 0; + } + if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { + return 2; + } + return 1; + } + const control = opts.control | 0; + let table: number[] | Uint32Array = null; + function initTable(): number[] | Uint32Array { + // lookup table for BMP + const CODEPOINTS = 65536; // BMP holds 65536 codepoints + const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2 + const ITEMSIZE = 32; // using uint32_t + const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE; + const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH; + table = (typeof Uint32Array === 'undefined') + ? new Array(CONTAINERSIZE) + : new Uint32Array(CONTAINERSIZE); + for (let i = 0; i < CONTAINERSIZE; ++i) { + let num = 0; + let pos = CODEPOINTS_PER_ITEM; + while (pos--) { + num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); + } + table[i] = num; + } + return table; + } + // get width from lookup table + // position in container : num / CODEPOINTS_PER_ITEM + // ==> n = table[Math.floor(num / 16)] + // ==> n = table[num >> 4] + // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100 + // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH + // ==> m = (n % 16) * 2 + // ==> m = (num & 15) << 1 + // right shift to position m + // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766 + // we are only interested in 2 LSBs, cut off higher bits + // ==> n = n & 3 e.g. 000000000000000000000000000000XX + return function (num: number): number { + num = num | 0; // get asm.js like optimization under V8 + if (num < 32) { + return control | 0; + } + if (num < 127) { + return 1; + } + const t = table || initTable(); + if (num < 65536) { + return t[num >> 4] >> ((num & 15) << 1) & 3; + } + // do a full search for high codepoints + return wcwidthHigh(num); + }; + })({nul: 0, control: 0}); // configurable options + + it('equality of old and new impl', function(): void { + for (let i = 0; i < 65536; ++i) { + assert.equal(wcwidth(i), wcwidthOld(i), `mismatch for i: ${i}`); + } + }); +}); diff --git a/src/CharWidth.ts b/src/CharWidth.ts index 045a796c..05fb5d37 100644 --- a/src/CharWidth.ts +++ b/src/CharWidth.ts @@ -78,38 +78,6 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu } return false; } - function wcwidthBMP(ucs: number): number { - // test for 8-bit control characters - if (ucs === 0) { - return opts.nul; - } - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) { - return opts.control; - } - // binary search in table of non-spacing characters - if (bisearch(ucs, COMBINING_BMP)) { - return 0; - } - // if we arrive here, ucs is not a combining or C0/C1 control character - if (isWideBMP(ucs)) { - return 2; - } - return 1; - } - function isWideBMP(ucs: number): boolean { - return ( - ucs >= 0x1100 && ( - ucs <= 0x115f || // Hangul Jamo init. consonants - ucs === 0x2329 || - ucs === 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi - (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables - (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs - (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms - (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms - (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms - (ucs >= 0xffe0 && ucs <= 0xffe6))); - } function wcwidthHigh(ucs: number): 0 | 1 | 2 { if (bisearch(ucs, COMBINING_HIGH)) { return 0; @@ -129,16 +97,15 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu // control chars table.subarray(1, 32).fill(opts.control); table.subarray(0x7f, 0xa0).fill(opts.control); - // combining 0 - for (let r = 0; r < COMBINING_BMP.length; ++r) { - table.subarray(COMBINING_BMP[r][0], COMBINING_BMP[r][1]).fill(0); - } + + // apply wide char rules first // wide chars table.subarray(0x1100, 0x1160).fill(2); table[0x2329] = 2; table[0x232a] = 2; table.subarray(0x2e80, 0xa4d0).fill(2); - table[0x303f] = 1; // wrongly added before + table[0x303f] = 1; // wrongly in last line + table.subarray(0xac00, 0xd7a4).fill(2); table.subarray(0xf900, 0xfb00).fill(2); table.subarray(0xfe10, 0xfe1a).fill(2); @@ -146,6 +113,15 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu table.subarray(0xff00, 0xff61).fill(2); table.subarray(0xffe0, 0xffe7).fill(2); + // apply combining last to ensure we overwrite + // wrongly wide set chars: + // the original algo evals combining first and falls + // through to wide check so we simply do here the opposite + // combining 0 + for (let r = 0; r < COMBINING_BMP.length; ++r) { + table.subarray(COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1).fill(0); + } + return function (num: number): number { if (num < 32) { return control | 0; From 006869704b41732cae36822b05e978c8884c29ad Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 20 Nov 2018 09:44:10 -0800 Subject: [PATCH 28/56] Add help wanted/good first issue links --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4b3a9f2..5c389f5b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,8 @@ opening an issue, read these pointers. ## Contributing code -- Make sure you have a [GitHub account](https://github.com/join) +You can find issues to work on my looking at the [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) issues. It's a good idea to comment on the issue saying that you're taking it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute: + - Fork [xterm.js](https://github.com/sourcelair/xterm.js/) ([how to fork a repo](https://help.github.com/articles/fork-a-repo)) - Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running From 3964bb28b5c3fe061f3c4eba43c78635d4737c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 20 Nov 2018 21:49:31 +0100 Subject: [PATCH 29/56] throw error in recycle with buffer not full --- src/common/CircularList.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 0a57ece0..5a6a652f 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -99,17 +99,16 @@ export class CircularList extends EventEmitter implements ICircularList { /** * Advance ringbuffer index and return current element for recycling. - * Note: If the ringbuffer is not full this method will return undefined, - * Either precheck with isFull() or handle the undefined return value accordingly. + * Note: The buffer must be full for this method to work. + * @throws When the buffer is not full. */ - public recycle(): T | undefined { - if (this._length === this._maxLength) { - this._startIndex = ++this._startIndex % this._maxLength; - this.emit('trim', 1); - } else { - this._length++; + public recycle(): T { + if (this._length !== this._maxLength) { + throw new Error('Can only recycle when the buffer is full'); } - return this._array[this._getCyclicIndex(this._length - 1)]; + this._startIndex = ++this._startIndex % this._maxLength; + this.emit('trim', 1); + return this._array[this._getCyclicIndex(this._length - 1)]!; } /** From 15c4cc8a74c89a9aab02d5277ec66c39aa88664e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 20 Nov 2018 22:01:34 +0100 Subject: [PATCH 30/56] remove experimental flag for recycling, merge with TypedArray setting --- src/Terminal.ts | 7 +++---- src/common/CircularList.ts | 2 +- src/common/Types.ts | 2 +- typings/xterm.d.ts | 7 ------- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 70dc15d8..38eb3d5b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -106,8 +106,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { theme: null, rightClickSelectsWord: Browser.isMac, rendererType: 'canvas', - experimentalBufferLineImpl: 'JsArray', - experimentalBufferLineRecycling: false + experimentalBufferLineImpl: 'JsArray' }; export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { @@ -1181,7 +1180,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II */ public scroll(isWrapped: boolean = false): void { let newLine: IBufferLine; - const useRecycling = this.options.experimentalBufferLineRecycling; + const useRecycling = this.options.experimentalBufferLineImpl === 'TypedArray'; if (useRecycling) { newLine = this._blankLine; if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) { @@ -1198,7 +1197,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (this.buffer.scrollTop === 0) { // Determine whether the buffer is going to be trimmed after insertion. - const willBufferBeTrimmed = this.buffer.lines.isFull(); + const willBufferBeTrimmed = this.buffer.lines.isFull; // Insert the line using the fastest method if (bottomRow === this.buffer.lines.length - 1) { diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 5a6a652f..9faf534a 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -114,7 +114,7 @@ export class CircularList extends EventEmitter implements ICircularList { /** * Ringbuffer is at max length. */ - public isFull(): boolean { + public get isFull(): boolean { return this._length === this._maxLength; } diff --git a/src/common/Types.ts b/src/common/Types.ts index 841029ec..8a416bf1 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -24,12 +24,12 @@ export interface IKeyboardEvent { export interface ICircularList extends IEventEmitter { length: number; maxLength: number; + isFull: boolean; get(index: number): T | undefined; set(index: number, value: T): void; push(value: T): void; recycle(): T | undefined; - isFull(): boolean; pop(): T | undefined; splice(start: number, deleteCount: number, ...items: T[]): void; trimStart(count: number): void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 8b36dd75..c6b6b1e5 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -112,13 +112,6 @@ declare module 'xterm' { */ experimentalBufferLineImpl?: 'JsArray' | 'TypedArray'; - /** - * (EXPERIMENTAL) Enable recycling of buffer lines. - * - * This option will be removed in the future. - */ - experimentalBufferLineRecycling?: boolean; - /** * The font size used to render text. */ From 36db03e7133676e4044c94084bd4b7f9ebe8cab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 20 Nov 2018 22:35:51 +0100 Subject: [PATCH 31/56] keep old impl in test as reference; minor changes --- src/CharWidth.test.ts | 13 ++++++------- src/CharWidth.ts | 1 - src/InputHandler.ts | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts index c2d10c5c..d4ddd24c 100644 --- a/src/CharWidth.test.ts +++ b/src/CharWidth.test.ts @@ -78,8 +78,8 @@ describe('getStringCellWidth', function(): void { // TODO: multiline tests once #1685 is resolved }); -describe('wcwidth regression', function(): void { - // TODO: remove with one of the next releases +it('wcwidth should match all values from the old implementation', function(): void { + // old implementation const wcwidthOld = (function(opts: {nul: number, control: number}): (ucs: number) => number { // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c // combining characters @@ -247,9 +247,8 @@ describe('wcwidth regression', function(): void { }; })({nul: 0, control: 0}); // configurable options - it('equality of old and new impl', function(): void { - for (let i = 0; i < 65536; ++i) { - assert.equal(wcwidth(i), wcwidthOld(i), `mismatch for i: ${i}`); - } - }); + // test full BMP range old vs new implmenetation + for (let i = 0; i < 65536; ++i) { + assert.equal(wcwidth(i), wcwidthOld(i), `mismatch for i: ${i}`); + } }); diff --git a/src/CharWidth.ts b/src/CharWidth.ts index 05fb5d37..d099b109 100644 --- a/src/CharWidth.ts +++ b/src/CharWidth.ts @@ -90,7 +90,6 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu const control = opts.control | 0; // create lookup table for BMP plane - // TODO: make callable/configurable from UnicodeManager const table = new Uint8Array(65536); table.fill(1); table[0] = opts.nul; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 893046bf..b4ce054f 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -327,7 +327,6 @@ export class InputHandler extends Disposable implements IInputHandler { public print(data: string, start: number, end: number): void { let char: string; let code: number; - let second: number; let chWidth: number; const buffer: IBuffer = this._terminal.buffer; const charset: ICharset = this._terminal.charset; @@ -355,7 +354,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._surrogateFirst = char; continue; } - second = data.charCodeAt(stringPosition); + const second = data.charCodeAt(stringPosition); // if the second part is in surrogate pair range create the high codepoint // otherwise fall back to UCS-2 behavior (handle codepoints independently) if (0xDC00 <= second && second <= 0xDFFF) { From ef6fb93a928061bb2ef26563e76f0ed7298793fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 21 Nov 2018 00:07:28 +0100 Subject: [PATCH 32/56] polyfill for TypedArray.fill --- src/CharWidth.ts | 26 ++--- src/core/TypedArrayUtils.test.ts | 157 +++++++++++++++++++++++++++++++ src/core/TypedArrayUtils.ts | 40 ++++++++ 3 files changed, 211 insertions(+), 12 deletions(-) create mode 100644 src/core/TypedArrayUtils.test.ts create mode 100644 src/core/TypedArrayUtils.ts diff --git a/src/CharWidth.ts b/src/CharWidth.ts index d099b109..341b347a 100644 --- a/src/CharWidth.ts +++ b/src/CharWidth.ts @@ -1,3 +1,5 @@ +import { fill } from './core/TypedArrayUtils'; + /** * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT @@ -91,26 +93,26 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu // create lookup table for BMP plane const table = new Uint8Array(65536); - table.fill(1); + fill(table, 1); table[0] = opts.nul; // control chars - table.subarray(1, 32).fill(opts.control); - table.subarray(0x7f, 0xa0).fill(opts.control); + fill(table, opts.control, 1, 32); + fill(table, opts.control, 0x7f, 0xa0); // apply wide char rules first // wide chars - table.subarray(0x1100, 0x1160).fill(2); + fill(table, 2, 0x1100, 0x1160); table[0x2329] = 2; table[0x232a] = 2; - table.subarray(0x2e80, 0xa4d0).fill(2); + fill(table, 2, 0x2e80, 0xa4d0); table[0x303f] = 1; // wrongly in last line - table.subarray(0xac00, 0xd7a4).fill(2); - table.subarray(0xf900, 0xfb00).fill(2); - table.subarray(0xfe10, 0xfe1a).fill(2); - table.subarray(0xfe30, 0xfe70).fill(2); - table.subarray(0xff00, 0xff61).fill(2); - table.subarray(0xffe0, 0xffe7).fill(2); + fill(table, 2, 0xac00, 0xd7a4); + fill(table, 2, 0xf900, 0xfb00); + fill(table, 2, 0xfe10, 0xfe1a); + fill(table, 2, 0xfe30, 0xfe70); + fill(table, 2, 0xff00, 0xff61); + fill(table, 2, 0xffe0, 0xffe7); // apply combining last to ensure we overwrite // wrongly wide set chars: @@ -118,7 +120,7 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu // through to wide check so we simply do here the opposite // combining 0 for (let r = 0; r < COMBINING_BMP.length; ++r) { - table.subarray(COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1).fill(0); + fill(table, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1); } return function (num: number): number { diff --git a/src/core/TypedArrayUtils.test.ts b/src/core/TypedArrayUtils.test.ts new file mode 100644 index 00000000..2e45d371 --- /dev/null +++ b/src/core/TypedArrayUtils.test.ts @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { assert } from 'chai'; +import { fill } from './TypedArrayUtils'; + +type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + +function loopFill(array: TypedArray, value: number, start: number = 0, end?: number | undefined): TypedArray { + if (start >= array.length) { + return array; + } + start = (array.length + start) % array.length; + if (end === undefined) { + end = array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + for (let i = start; i < end; ++i) { + array[i] = value; + } + return array; +} + +function deepEquals(a: TypedArray, b: TypedArray) { + assert.equal(a.length, b.length); + for (let i = 0; i < a.length; ++i) { + assert.equal(a[i], b[i]); + } +} + + +describe('polyfill conformance tests', function(): void { + describe('TypedArray.fill', function(): void { + it('should work with all typed array types', function(): void { + const u8_1 = new Uint8Array(5); + const u8_2 = new Uint8Array(5); + deepEquals(fill(u8_1, 2), u8_2.fill(2)); + deepEquals(fill(u8_1, -1), u8_2.fill(-1)); + const u16_1 = new Uint16Array(5); + const u16_2 = new Uint16Array(5); + deepEquals(fill(u16_1, 2), u16_2.fill(2)); + deepEquals(fill(u16_1, 65535), u16_2.fill(65535)); + deepEquals(fill(u16_1, -1), u16_2.fill(-1)); + const u32_1 = new Uint32Array(5); + const u32_2 = new Uint32Array(5); + deepEquals(fill(u32_1, 2), u32_2.fill(2)); + deepEquals(fill(u32_1, 65537), u32_2.fill(65537)); + deepEquals(fill(u32_1, -1), u32_2.fill(-1)); + const i8_1 = new Int8Array(5); + const i8_2 = new Int8Array(5); + deepEquals(fill(i8_1, 2), i8_2.fill(2)); + deepEquals(fill(i8_1, -1), i8_2.fill(-1)); + const i16_1 = new Int16Array(5); + const i16_2 = new Int16Array(5); + deepEquals(fill(i16_1, 2), i16_2.fill(2)); + deepEquals(fill(i16_1, 65535), i16_2.fill(65535)); + deepEquals(fill(i16_1, -1), i16_2.fill(-1)); + const i32_1 = new Int32Array(5); + const i32_2 = new Int32Array(5); + deepEquals(fill(i32_1, 2), i32_2.fill(2)); + deepEquals(fill(i32_1, 65537), i32_2.fill(65537)); + deepEquals(fill(i32_1, -1), i32_2.fill(-1)); + const f32_1 = new Float32Array(5); + const f32_2 = new Float32Array(5); + deepEquals(fill(f32_1, 1.2345), f32_2.fill(1.2345)); + const f64_1 = new Float64Array(5); + const f64_2 = new Float64Array(5); + deepEquals(fill(f64_1, 1.2345), f64_2.fill(1.2345)); + const u8Clamped_1 = new Uint8ClampedArray(5); + const u8Clamped_2 = new Uint8ClampedArray(5); + deepEquals(fill(u8Clamped_1, 2), u8Clamped_2.fill(2)); + deepEquals(fill(u8Clamped_1, 257), u8Clamped_2.fill(257)); + }); + it('should work with all typed array types - explicit looping', function(): void { + const u8_1 = new Uint8Array(5); + const u8_2 = new Uint8Array(5); + deepEquals(loopFill(u8_1, 2), u8_2.fill(2)); + deepEquals(loopFill(u8_1, -1), u8_2.fill(-1)); + const u16_1 = new Uint16Array(5); + const u16_2 = new Uint16Array(5); + deepEquals(loopFill(u16_1, 2), u16_2.fill(2)); + deepEquals(loopFill(u16_1, 65535), u16_2.fill(65535)); + deepEquals(loopFill(u16_1, -1), u16_2.fill(-1)); + const u32_1 = new Uint32Array(5); + const u32_2 = new Uint32Array(5); + deepEquals(loopFill(u32_1, 2), u32_2.fill(2)); + deepEquals(loopFill(u32_1, 65537), u32_2.fill(65537)); + deepEquals(loopFill(u32_1, -1), u32_2.fill(-1)); + const i8_1 = new Int8Array(5); + const i8_2 = new Int8Array(5); + deepEquals(loopFill(i8_1, 2), i8_2.fill(2)); + deepEquals(loopFill(i8_1, -1), i8_2.fill(-1)); + const i16_1 = new Int16Array(5); + const i16_2 = new Int16Array(5); + deepEquals(loopFill(i16_1, 2), i16_2.fill(2)); + deepEquals(loopFill(i16_1, 65535), i16_2.fill(65535)); + deepEquals(loopFill(i16_1, -1), i16_2.fill(-1)); + const i32_1 = new Int32Array(5); + const i32_2 = new Int32Array(5); + deepEquals(loopFill(i32_1, 2), i32_2.fill(2)); + deepEquals(loopFill(i32_1, 65537), i32_2.fill(65537)); + deepEquals(loopFill(i32_1, -1), i32_2.fill(-1)); + const f32_1 = new Float32Array(5); + const f32_2 = new Float32Array(5); + deepEquals(loopFill(f32_1, 1.2345), f32_2.fill(1.2345)); + const f64_1 = new Float64Array(5); + const f64_2 = new Float64Array(5); + deepEquals(loopFill(f64_1, 1.2345), f64_2.fill(1.2345)); + const u8Clamped_1 = new Uint8ClampedArray(5); + const u8Clamped_2 = new Uint8ClampedArray(5); + deepEquals(loopFill(u8Clamped_1, 2), u8Clamped_2.fill(2)); + deepEquals(loopFill(u8Clamped_1, 257), u8Clamped_2.fill(257)); + }); + it('start offset', function(): void { + for (let i = -2; i < 10; ++i) { + const u8_1 = new Uint8Array(5); + const u8_2 = new Uint8Array(5); + const u8_3 = new Uint8Array(5); + deepEquals(fill(u8_1, 2, i), u8_3.fill(2, i)); + deepEquals(fill(u8_1, -1, i), u8_3.fill(-1, i)); + deepEquals(loopFill(u8_2, 2, i), u8_3.fill(2, i)); + deepEquals(loopFill(u8_2, -1, i), u8_3.fill(-1, i)); + } + }); + it('end offset', function(): void { + for (let i = -2; i < 10; ++i) { + const u8_1 = new Uint8Array(5); + const u8_2 = new Uint8Array(5); + const u8_3 = new Uint8Array(5); + deepEquals(fill(u8_1, 2, 0, i), u8_3.fill(2, 0, i)); + deepEquals(fill(u8_1, -1, 0, i), u8_3.fill(-1, 0, i)); + deepEquals(loopFill(u8_2, 2, 0, i), u8_3.fill(2, 0, i)); + deepEquals(loopFill(u8_2, -1, 0, i), u8_3.fill(-1, 0, i)); + } + }); + it('start/end offset', function(): void { + for (let i = -2; i < 10; ++i) { + for (let j = -2; j < 10; ++j) { + const u8_1 = new Uint8Array(5); + const u8_2 = new Uint8Array(5); + const u8_3 = new Uint8Array(5); + deepEquals(fill(u8_1, 2, i, j), u8_3.fill(2, i, j)); + deepEquals(fill(u8_1, -1, i, j), u8_3.fill(-1, i, j)); + deepEquals(loopFill(u8_2, 2, i, j), u8_3.fill(2, i, j)); + deepEquals(loopFill(u8_2, -1, i, j), u8_3.fill(-1, i, j)); + } + } + }); + }); +}); diff --git a/src/core/TypedArrayUtils.ts b/src/core/TypedArrayUtils.ts new file mode 100644 index 00000000..56e9d7b0 --- /dev/null +++ b/src/core/TypedArrayUtils.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * polyfill for TypedArray.fill + * This is needed to support .fill in all safari versions and IE 11. + */ + +type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + +export function fill(array: TypedArray, value: number, start: number = 0, end?: number | undefined): TypedArray { + // all modern engines that support .fill + if (array.fill) { + return array.fill(value, start, end); + } + // safari and IE 11 + // since IE 11 does not support Array.prototype.fill either + // we cannot use the suggested polyfill from MDN + // instead we simply fall back to looping + if (start >= array.length) { + return array; + } + start = (array.length + start) % array.length; + if (end === undefined) { + end = array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + for (let i = start; i < end; ++i) { + array[i] = value; + } + return array; +} From 8c33de395d48fa6ee2ff4c4c3e2de830a82e70a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 21 Nov 2018 00:15:04 +0100 Subject: [PATCH 33/56] fix linter errors --- src/core/TypedArrayUtils.test.ts | 211 ++++++++++++++++--------------- 1 file changed, 106 insertions(+), 105 deletions(-) diff --git a/src/core/TypedArrayUtils.test.ts b/src/core/TypedArrayUtils.test.ts index 2e45d371..ef86d314 100644 --- a/src/core/TypedArrayUtils.test.ts +++ b/src/core/TypedArrayUtils.test.ts @@ -9,6 +9,7 @@ type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array; +// we explicitly test against the looping version in the test cases function loopFill(array: TypedArray, value: number, start: number = 0, end?: number | undefined): TypedArray { if (start >= array.length) { return array; @@ -28,128 +29,128 @@ function loopFill(array: TypedArray, value: number, start: number = 0, end?: num return array; } -function deepEquals(a: TypedArray, b: TypedArray) { - assert.equal(a.length, b.length); - for (let i = 0; i < a.length; ++i) { - assert.equal(a[i], b[i]); - } -} - - describe('polyfill conformance tests', function(): void { + + function deepEquals(a: TypedArray, b: TypedArray): void { + assert.equal(a.length, b.length); + for (let i = 0; i < a.length; ++i) { + assert.equal(a[i], b[i]); + } + } + describe('TypedArray.fill', function(): void { it('should work with all typed array types', function(): void { - const u8_1 = new Uint8Array(5); - const u8_2 = new Uint8Array(5); - deepEquals(fill(u8_1, 2), u8_2.fill(2)); - deepEquals(fill(u8_1, -1), u8_2.fill(-1)); - const u16_1 = new Uint16Array(5); - const u16_2 = new Uint16Array(5); - deepEquals(fill(u16_1, 2), u16_2.fill(2)); - deepEquals(fill(u16_1, 65535), u16_2.fill(65535)); - deepEquals(fill(u16_1, -1), u16_2.fill(-1)); - const u32_1 = new Uint32Array(5); - const u32_2 = new Uint32Array(5); - deepEquals(fill(u32_1, 2), u32_2.fill(2)); - deepEquals(fill(u32_1, 65537), u32_2.fill(65537)); - deepEquals(fill(u32_1, -1), u32_2.fill(-1)); - const i8_1 = new Int8Array(5); - const i8_2 = new Int8Array(5); - deepEquals(fill(i8_1, 2), i8_2.fill(2)); - deepEquals(fill(i8_1, -1), i8_2.fill(-1)); - const i16_1 = new Int16Array(5); - const i16_2 = new Int16Array(5); - deepEquals(fill(i16_1, 2), i16_2.fill(2)); - deepEquals(fill(i16_1, 65535), i16_2.fill(65535)); - deepEquals(fill(i16_1, -1), i16_2.fill(-1)); - const i32_1 = new Int32Array(5); - const i32_2 = new Int32Array(5); - deepEquals(fill(i32_1, 2), i32_2.fill(2)); - deepEquals(fill(i32_1, 65537), i32_2.fill(65537)); - deepEquals(fill(i32_1, -1), i32_2.fill(-1)); - const f32_1 = new Float32Array(5); - const f32_2 = new Float32Array(5); - deepEquals(fill(f32_1, 1.2345), f32_2.fill(1.2345)); - const f64_1 = new Float64Array(5); - const f64_2 = new Float64Array(5); - deepEquals(fill(f64_1, 1.2345), f64_2.fill(1.2345)); - const u8Clamped_1 = new Uint8ClampedArray(5); - const u8Clamped_2 = new Uint8ClampedArray(5); - deepEquals(fill(u8Clamped_1, 2), u8Clamped_2.fill(2)); - deepEquals(fill(u8Clamped_1, 257), u8Clamped_2.fill(257)); + const u81 = new Uint8Array(5); + const u82 = new Uint8Array(5); + deepEquals(fill(u81, 2), u82.fill(2)); + deepEquals(fill(u81, -1), u82.fill(-1)); + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(fill(u161, 2), u162.fill(2)); + deepEquals(fill(u161, 65535), u162.fill(65535)); + deepEquals(fill(u161, -1), u162.fill(-1)); + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(fill(u321, 2), u322.fill(2)); + deepEquals(fill(u321, 65537), u322.fill(65537)); + deepEquals(fill(u321, -1), u322.fill(-1)); + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(fill(i81, 2), i82.fill(2)); + deepEquals(fill(i81, -1), i82.fill(-1)); + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(fill(i161, 2), i162.fill(2)); + deepEquals(fill(i161, 65535), i162.fill(65535)); + deepEquals(fill(i161, -1), i162.fill(-1)); + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(fill(i321, 2), i322.fill(2)); + deepEquals(fill(i321, 65537), i322.fill(65537)); + deepEquals(fill(i321, -1), i322.fill(-1)); + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(fill(f321, 1.2345), f322.fill(1.2345)); + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(fill(f641, 1.2345), f642.fill(1.2345)); + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(fill(u8Clamped1, 2), u8Clamped2.fill(2)); + deepEquals(fill(u8Clamped1, 257), u8Clamped2.fill(257)); }); it('should work with all typed array types - explicit looping', function(): void { - const u8_1 = new Uint8Array(5); - const u8_2 = new Uint8Array(5); - deepEquals(loopFill(u8_1, 2), u8_2.fill(2)); - deepEquals(loopFill(u8_1, -1), u8_2.fill(-1)); - const u16_1 = new Uint16Array(5); - const u16_2 = new Uint16Array(5); - deepEquals(loopFill(u16_1, 2), u16_2.fill(2)); - deepEquals(loopFill(u16_1, 65535), u16_2.fill(65535)); - deepEquals(loopFill(u16_1, -1), u16_2.fill(-1)); - const u32_1 = new Uint32Array(5); - const u32_2 = new Uint32Array(5); - deepEquals(loopFill(u32_1, 2), u32_2.fill(2)); - deepEquals(loopFill(u32_1, 65537), u32_2.fill(65537)); - deepEquals(loopFill(u32_1, -1), u32_2.fill(-1)); - const i8_1 = new Int8Array(5); - const i8_2 = new Int8Array(5); - deepEquals(loopFill(i8_1, 2), i8_2.fill(2)); - deepEquals(loopFill(i8_1, -1), i8_2.fill(-1)); - const i16_1 = new Int16Array(5); - const i16_2 = new Int16Array(5); - deepEquals(loopFill(i16_1, 2), i16_2.fill(2)); - deepEquals(loopFill(i16_1, 65535), i16_2.fill(65535)); - deepEquals(loopFill(i16_1, -1), i16_2.fill(-1)); - const i32_1 = new Int32Array(5); - const i32_2 = new Int32Array(5); - deepEquals(loopFill(i32_1, 2), i32_2.fill(2)); - deepEquals(loopFill(i32_1, 65537), i32_2.fill(65537)); - deepEquals(loopFill(i32_1, -1), i32_2.fill(-1)); - const f32_1 = new Float32Array(5); - const f32_2 = new Float32Array(5); - deepEquals(loopFill(f32_1, 1.2345), f32_2.fill(1.2345)); - const f64_1 = new Float64Array(5); - const f64_2 = new Float64Array(5); - deepEquals(loopFill(f64_1, 1.2345), f64_2.fill(1.2345)); - const u8Clamped_1 = new Uint8ClampedArray(5); - const u8Clamped_2 = new Uint8ClampedArray(5); - deepEquals(loopFill(u8Clamped_1, 2), u8Clamped_2.fill(2)); - deepEquals(loopFill(u8Clamped_1, 257), u8Clamped_2.fill(257)); + const u81 = new Uint8Array(5); + const u82 = new Uint8Array(5); + deepEquals(loopFill(u81, 2), u82.fill(2)); + deepEquals(loopFill(u81, -1), u82.fill(-1)); + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(loopFill(u161, 2), u162.fill(2)); + deepEquals(loopFill(u161, 65535), u162.fill(65535)); + deepEquals(loopFill(u161, -1), u162.fill(-1)); + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(loopFill(u321, 2), u322.fill(2)); + deepEquals(loopFill(u321, 65537), u322.fill(65537)); + deepEquals(loopFill(u321, -1), u322.fill(-1)); + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(loopFill(i81, 2), i82.fill(2)); + deepEquals(loopFill(i81, -1), i82.fill(-1)); + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(loopFill(i161, 2), i162.fill(2)); + deepEquals(loopFill(i161, 65535), i162.fill(65535)); + deepEquals(loopFill(i161, -1), i162.fill(-1)); + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(loopFill(i321, 2), i322.fill(2)); + deepEquals(loopFill(i321, 65537), i322.fill(65537)); + deepEquals(loopFill(i321, -1), i322.fill(-1)); + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(loopFill(f321, 1.2345), f322.fill(1.2345)); + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(loopFill(f641, 1.2345), f642.fill(1.2345)); + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(loopFill(u8Clamped1, 2), u8Clamped2.fill(2)); + deepEquals(loopFill(u8Clamped1, 257), u8Clamped2.fill(257)); }); it('start offset', function(): void { for (let i = -2; i < 10; ++i) { - const u8_1 = new Uint8Array(5); - const u8_2 = new Uint8Array(5); - const u8_3 = new Uint8Array(5); - deepEquals(fill(u8_1, 2, i), u8_3.fill(2, i)); - deepEquals(fill(u8_1, -1, i), u8_3.fill(-1, i)); - deepEquals(loopFill(u8_2, 2, i), u8_3.fill(2, i)); - deepEquals(loopFill(u8_2, -1, i), u8_3.fill(-1, i)); + const u81 = new Uint8Array(5); + const u82 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fill(u81, 2, i), u83.fill(2, i)); + deepEquals(fill(u81, -1, i), u83.fill(-1, i)); + deepEquals(loopFill(u82, 2, i), u83.fill(2, i)); + deepEquals(loopFill(u82, -1, i), u83.fill(-1, i)); } }); it('end offset', function(): void { for (let i = -2; i < 10; ++i) { - const u8_1 = new Uint8Array(5); - const u8_2 = new Uint8Array(5); - const u8_3 = new Uint8Array(5); - deepEquals(fill(u8_1, 2, 0, i), u8_3.fill(2, 0, i)); - deepEquals(fill(u8_1, -1, 0, i), u8_3.fill(-1, 0, i)); - deepEquals(loopFill(u8_2, 2, 0, i), u8_3.fill(2, 0, i)); - deepEquals(loopFill(u8_2, -1, 0, i), u8_3.fill(-1, 0, i)); + const u81 = new Uint8Array(5); + const u82 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fill(u81, 2, 0, i), u83.fill(2, 0, i)); + deepEquals(fill(u81, -1, 0, i), u83.fill(-1, 0, i)); + deepEquals(loopFill(u82, 2, 0, i), u83.fill(2, 0, i)); + deepEquals(loopFill(u82, -1, 0, i), u83.fill(-1, 0, i)); } }); it('start/end offset', function(): void { for (let i = -2; i < 10; ++i) { for (let j = -2; j < 10; ++j) { - const u8_1 = new Uint8Array(5); - const u8_2 = new Uint8Array(5); - const u8_3 = new Uint8Array(5); - deepEquals(fill(u8_1, 2, i, j), u8_3.fill(2, i, j)); - deepEquals(fill(u8_1, -1, i, j), u8_3.fill(-1, i, j)); - deepEquals(loopFill(u8_2, 2, i, j), u8_3.fill(2, i, j)); - deepEquals(loopFill(u8_2, -1, i, j), u8_3.fill(-1, i, j)); + const u81 = new Uint8Array(5); + const u82 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fill(u81, 2, i, j), u83.fill(2, i, j)); + deepEquals(fill(u81, -1, i, j), u83.fill(-1, i, j)); + deepEquals(loopFill(u82, 2, i, j), u83.fill(2, i, j)); + deepEquals(loopFill(u82, -1, i, j), u83.fill(-1, i, j)); } } }); From 98a3892e52408afcbc599dc6c7943b9558e1a060 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 22 Nov 2018 09:44:13 -0800 Subject: [PATCH 34/56] Fix typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c389f5b..e7924027 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ opening an issue, read these pointers. ## Contributing code -You can find issues to work on my looking at the [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) issues. It's a good idea to comment on the issue saying that you're taking it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute: +You can find issues to work on by looking at the [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) issues. It's a good idea to comment on the issue saying that you're taking it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute: - Fork [xterm.js](https://github.com/sourcelair/xterm.js/) ([how to fork a repo](https://help.github.com/articles/fork-a-repo)) From 53fe096455968f8d8158939ea50a929cc3b6e5a2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 22 Nov 2018 10:52:55 -0800 Subject: [PATCH 35/56] Type fn strongly --- src/addons/fullscreen/fullscreen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addons/fullscreen/fullscreen.ts b/src/addons/fullscreen/fullscreen.ts index ef9b8ffd..4d05e904 100644 --- a/src/addons/fullscreen/fullscreen.ts +++ b/src/addons/fullscreen/fullscreen.ts @@ -11,7 +11,7 @@ import { Terminal } from 'xterm'; * @param fullscreen Toggle fullscreen on (true) or off (false) */ export function toggleFullScreen(term: Terminal, fullscreen: boolean): void { - let fn: Function; + let fn: (...tokens: string[]) => void; if (typeof fullscreen === 'undefined') { fn = (term.element.classList.contains('fullscreen')) ? From 692cdfa390943c0133a8fbd1a27e6b1a9ba721a8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 22 Nov 2018 11:32:08 -0800 Subject: [PATCH 36/56] DOM renderer: Don't output empty cells at end Fixes #1609 --- src/renderer/dom/DomRendererRowFactory.ts | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 4bb59902..bcb0cc48 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer'; +import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from '../../Buffer'; import { FLAGS } from '../Types'; import { IBufferLine } from '../../Types'; @@ -23,17 +23,28 @@ export class DomRendererRowFactory { public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); let colCount = 0; + let nonNullCellFound = false; - for (let x = 0; x < lineData.length; x++) { + for (let x = lineData.length - 1; x >= 0; x--) { // Don't allow any buffer to the right to be displayed if (colCount >= cols) { continue; } const charData = lineData.get(x); - const char: string = charData[CHAR_DATA_CHAR_INDEX]; - const attr: number = charData[CHAR_DATA_ATTR_INDEX]; - const width: number = charData[CHAR_DATA_WIDTH_INDEX]; + + if (!nonNullCellFound) { + const code = charData[CHAR_DATA_CODE_INDEX]; + if (code === NULL_CELL_CODE && !(isCursorRow && x === cursorX)) { + continue; + } else { + nonNullCellFound = true; + } + } + + const char = charData[CHAR_DATA_CHAR_INDEX]; + const attr = charData[CHAR_DATA_ATTR_INDEX]; + const width = charData[CHAR_DATA_WIDTH_INDEX]; // The character to the left is a wide character, drawing is owned by the char at x-1 if (width === 0) { @@ -97,7 +108,11 @@ export class DomRendererRowFactory { if (bg !== 256) { charElement.classList.add(`xterm-bg-${bg}`); } - fragment.appendChild(charElement); + if (fragment.childNodes.length === 0) { + fragment.appendChild(charElement); + } else { + fragment.insertBefore(charElement, fragment.firstChild); + } colCount += width; } return fragment; From dc077a181e3b4f34c49f22f9579b1fd9dca653ff Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 22 Nov 2018 12:03:08 -0800 Subject: [PATCH 37/56] Fix tests and the colCount feature --- .../dom/DomRendererRowFactory.test.ts | 32 ++++++----------- src/renderer/dom/DomRendererRowFactory.ts | 35 +++++++++---------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 2c46d8cc..03f41784 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -23,11 +23,10 @@ describe('DomRendererRowFactory', () => { }); describe('createRow', () => { - it('should create an element for every character in the row', () => { + it('should not create anything for an empty row', () => { const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - ' ' + - ' ' + '' ); }); @@ -45,8 +44,7 @@ describe('DomRendererRowFactory', () => { for (const style of ['block', 'bar', 'underline']) { const fragment = rowFactory.createRow(lineData, true, style, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - ` ` + - ' ' + ` ` ); } }); @@ -65,8 +63,7 @@ describe('DomRendererRowFactory', () => { lineData.set(0, [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' + - ' ' + 'a' ); }); @@ -74,8 +71,7 @@ describe('DomRendererRowFactory', () => { lineData.set(0, [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' + - ' ' + 'a' ); }); @@ -85,8 +81,7 @@ describe('DomRendererRowFactory', () => { lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - `a` + - ' ' + `a` ); } }); @@ -97,8 +92,7 @@ describe('DomRendererRowFactory', () => { lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - `a` + - ' ' + `a` ); } }); @@ -107,8 +101,7 @@ describe('DomRendererRowFactory', () => { 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); assert.equal(getFragmentHtml(fragment), - 'a' + - ' ' + 'a' ); }); @@ -116,8 +109,7 @@ describe('DomRendererRowFactory', () => { lineData.set(0, [(FLAGS.INVERSE << 18) | (257 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + - ' ' + 'a' ); }); @@ -125,8 +117,7 @@ describe('DomRendererRowFactory', () => { lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + - ' ' + 'a' ); }); @@ -135,8 +126,7 @@ describe('DomRendererRowFactory', () => { lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]); 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 bcb0cc48..5f6b49fc 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -22,26 +22,29 @@ export class DomRendererRowFactory { public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); - let colCount = 0; - let nonNullCellFound = false; - for (let x = lineData.length - 1; x >= 0; x--) { + // Find the line length first, this prevents the need to output a bunch of + // empty cells at the end. This cannot easily be integrated into the main + // loop below because of the colCount feature (which can be removed after we + // properly support reflow and disallow data to go beyond the right-side of + // the viewport). + let lineLength = 0; + for (let x = 0; x < lineData.length; x++) { + const charData = lineData.get(x); + const code = charData[CHAR_DATA_CODE_INDEX]; + if (code !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { + lineLength = x + 1; + } + } + + let colCount = 0; + for (let x = 0; x < lineLength; x++) { // Don't allow any buffer to the right to be displayed if (colCount >= cols) { continue; } const charData = lineData.get(x); - - if (!nonNullCellFound) { - const code = charData[CHAR_DATA_CODE_INDEX]; - if (code === NULL_CELL_CODE && !(isCursorRow && x === cursorX)) { - continue; - } else { - nonNullCellFound = true; - } - } - const char = charData[CHAR_DATA_CHAR_INDEX]; const attr = charData[CHAR_DATA_ATTR_INDEX]; const width = charData[CHAR_DATA_WIDTH_INDEX]; @@ -108,11 +111,7 @@ export class DomRendererRowFactory { if (bg !== 256) { charElement.classList.add(`xterm-bg-${bg}`); } - if (fragment.childNodes.length === 0) { - fragment.appendChild(charElement); - } else { - fragment.insertBefore(charElement, fragment.firstChild); - } + fragment.appendChild(charElement); colCount += width; } return fragment; From 66272eb3105ff23b84e5a79b622a72e49352419c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 05:39:50 -0800 Subject: [PATCH 38/56] Get the line length by going backwards instead --- src/renderer/dom/DomRendererRowFactory.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 5f6b49fc..490360bf 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -29,11 +29,12 @@ export class DomRendererRowFactory { // properly support reflow and disallow data to go beyond the right-side of // the viewport). let lineLength = 0; - for (let x = 0; x < lineData.length; x++) { + for (let x = lineData.length - 1; x >= 0; x--) { const charData = lineData.get(x); const code = charData[CHAR_DATA_CODE_INDEX]; if (code !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { lineLength = x + 1; + break; } } From dc0ae4a8156bd0cfff6e1884065757c4c9b9098f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 06:24:27 -0800 Subject: [PATCH 39/56] Fix indentation --- src/CharWidth.ts | 248 +++++++++++++++++++++++------------------------ 1 file changed, 124 insertions(+), 124 deletions(-) diff --git a/src/CharWidth.ts b/src/CharWidth.ts index 341b347a..5a484da8 100644 --- a/src/CharWidth.ts +++ b/src/CharWidth.ts @@ -6,136 +6,136 @@ import { fill } from './core/TypedArrayUtils'; */ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number { - // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c - // combining characters - const COMBINING_BMP = [ - [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], - [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], - [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], - [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], - [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], - [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], - [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], - [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], - [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], - [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], - [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], - [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], - [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], - [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], - [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], - [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], - [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], - [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], - [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], - [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], - [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], - [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], - [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], - [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], - [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], - [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], - [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], - [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], - [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], - [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], - [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], - [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], - [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], - [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], - [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], - [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], - [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] - ]; - const COMBINING_HIGH = [ - [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], - [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - // binary search - function bisearch(ucs: number, data: number[][]): boolean { - let min = 0; - let max = data.length - 1; - let mid; - if (ucs < data[0][0] || ucs > data[max][1]) { - return false; - } - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) { - min = mid + 1; - } else if (ucs < data[mid][0]) { - max = mid - 1; - } else { - return true; - } - } + // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c + // combining characters + const COMBINING_BMP = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] + ]; + const COMBINING_HIGH = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + // binary search + function bisearch(ucs: number, data: number[][]): boolean { + let min = 0; + let max = data.length - 1; + let mid; + if (ucs < data[0][0] || ucs > data[max][1]) { return false; } - function wcwidthHigh(ucs: number): 0 | 1 | 2 { - if (bisearch(ucs, COMBINING_HIGH)) { - return 0; - } - if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { - return 2; + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } else if (ucs < data[mid][0]) { + max = mid - 1; + } else { + return true; } + } + return false; + } + function wcwidthHigh(ucs: number): 0 | 1 | 2 { + if (bisearch(ucs, COMBINING_HIGH)) { + return 0; + } + if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { + return 2; + } + return 1; + } + const control = opts.control | 0; + + // create lookup table for BMP plane + const table = new Uint8Array(65536); + fill(table, 1); + table[0] = opts.nul; + // control chars + fill(table, opts.control, 1, 32); + fill(table, opts.control, 0x7f, 0xa0); + + // apply wide char rules first + // wide chars + fill(table, 2, 0x1100, 0x1160); + table[0x2329] = 2; + table[0x232a] = 2; + fill(table, 2, 0x2e80, 0xa4d0); + table[0x303f] = 1; // wrongly in last line + + fill(table, 2, 0xac00, 0xd7a4); + fill(table, 2, 0xf900, 0xfb00); + fill(table, 2, 0xfe10, 0xfe1a); + fill(table, 2, 0xfe30, 0xfe70); + fill(table, 2, 0xff00, 0xff61); + fill(table, 2, 0xffe0, 0xffe7); + + // apply combining last to ensure we overwrite + // wrongly wide set chars: + // the original algo evals combining first and falls + // through to wide check so we simply do here the opposite + // combining 0 + for (let r = 0; r < COMBINING_BMP.length; ++r) { + fill(table, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1); + } + + return function (num: number): number { + if (num < 32) { + return control | 0; + } + if (num < 127) { return 1; } - const control = opts.control | 0; - - // create lookup table for BMP plane - const table = new Uint8Array(65536); - fill(table, 1); - table[0] = opts.nul; - // control chars - fill(table, opts.control, 1, 32); - fill(table, opts.control, 0x7f, 0xa0); - - // apply wide char rules first - // wide chars - fill(table, 2, 0x1100, 0x1160); - table[0x2329] = 2; - table[0x232a] = 2; - fill(table, 2, 0x2e80, 0xa4d0); - table[0x303f] = 1; // wrongly in last line - - fill(table, 2, 0xac00, 0xd7a4); - fill(table, 2, 0xf900, 0xfb00); - fill(table, 2, 0xfe10, 0xfe1a); - fill(table, 2, 0xfe30, 0xfe70); - fill(table, 2, 0xff00, 0xff61); - fill(table, 2, 0xffe0, 0xffe7); - - // apply combining last to ensure we overwrite - // wrongly wide set chars: - // the original algo evals combining first and falls - // through to wide check so we simply do here the opposite - // combining 0 - for (let r = 0; r < COMBINING_BMP.length; ++r) { - fill(table, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1); + if (num < 65536) { + return table[num]; } - - return function (num: number): number { - if (num < 32) { - return control | 0; - } - if (num < 127) { - return 1; - } - if (num < 65536) { - return table[num]; - } - // do a full search for high codepoints - return wcwidthHigh(num); - }; + // do a full search for high codepoints + return wcwidthHigh(num); + }; })({nul: 0, control: 0}); // configurable options /** From 65c5df92d913936a9902acc665229293a41acd27 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 06:26:06 -0800 Subject: [PATCH 40/56] Move import below copyright --- src/CharWidth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CharWidth.ts b/src/CharWidth.ts index 5a484da8..fd6ac55f 100644 --- a/src/CharWidth.ts +++ b/src/CharWidth.ts @@ -1,10 +1,10 @@ -import { fill } from './core/TypedArrayUtils'; - /** * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ +import { fill } from './core/TypedArrayUtils'; + export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number { // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c // combining characters From d9513ce50df9ea1faa8e4ef7da076824df3be171 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 06:48:37 -0800 Subject: [PATCH 41/56] Don't bother checking beyond cols --- src/renderer/dom/DomRendererRowFactory.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 490360bf..07303e24 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -29,7 +29,7 @@ export class DomRendererRowFactory { // properly support reflow and disallow data to go beyond the right-side of // the viewport). let lineLength = 0; - for (let x = lineData.length - 1; x >= 0; x--) { + 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)) { @@ -38,13 +38,7 @@ export class DomRendererRowFactory { } } - let colCount = 0; for (let x = 0; x < lineLength; x++) { - // Don't allow any buffer to the right to be displayed - if (colCount >= cols) { - continue; - } - const charData = lineData.get(x); const char = charData[CHAR_DATA_CHAR_INDEX]; const attr = charData[CHAR_DATA_ATTR_INDEX]; @@ -113,7 +107,6 @@ export class DomRendererRowFactory { charElement.classList.add(`xterm-bg-${bg}`); } fragment.appendChild(charElement); - colCount += width; } return fragment; } From 478dfee6e2988fd81a61b4e85b14125b3e087e50 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 06:51:39 -0800 Subject: [PATCH 42/56] Ensure wide chars don't overflow onto following row --- src/renderer/dom/DomRenderer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index fadd9b72..73c63b46 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -111,6 +111,8 @@ export class DomRenderer extends EventEmitter implements IRenderer { element.style.width = `${this.dimensions.canvasWidth}px`; element.style.height = `${this.dimensions.actualCellHeight}px`; element.style.lineHeight = `${this.dimensions.actualCellHeight}px`; + // Make sure rows don't overflow onto following row + element.style.overflow = 'hidden'; }); if (!this._dimensionsStyleElement) { @@ -330,7 +332,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, terminal.charMeasure.width, terminal.cols)); + rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, this.dimensions.actualCellWidth, terminal.cols)); } this._terminal.emit('refresh', {start, end}); From 266ea38d91c74389ba68a1630fa0d392722592ab Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 07:35:55 -0800 Subject: [PATCH 43/56] Improve types of fill polyfill and test fallback The polyfill will now return the T instead of the TypedArray union type. Tests now target the fallback only so engines that support fill will run tests against the fallback code. --- src/core/TypedArrayUtils.test.ts | 54 ++++++++++++++++---------------- src/core/TypedArrayUtils.ts | 8 +++-- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/core/TypedArrayUtils.test.ts b/src/core/TypedArrayUtils.test.ts index ef86d314..1df06f4c 100644 --- a/src/core/TypedArrayUtils.test.ts +++ b/src/core/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fill } from './TypedArrayUtils'; +import { fillFallback } from './TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -42,42 +42,42 @@ describe('polyfill conformance tests', function(): void { it('should work with all typed array types', function(): void { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); - deepEquals(fill(u81, 2), u82.fill(2)); - deepEquals(fill(u81, -1), u82.fill(-1)); + deepEquals(fillFallback(u81, 2), u82.fill(2)); + deepEquals(fillFallback(u81, -1), u82.fill(-1)); const u161 = new Uint16Array(5); const u162 = new Uint16Array(5); - deepEquals(fill(u161, 2), u162.fill(2)); - deepEquals(fill(u161, 65535), u162.fill(65535)); - deepEquals(fill(u161, -1), u162.fill(-1)); + deepEquals(fillFallback(u161, 2), u162.fill(2)); + deepEquals(fillFallback(u161, 65535), u162.fill(65535)); + deepEquals(fillFallback(u161, -1), u162.fill(-1)); const u321 = new Uint32Array(5); const u322 = new Uint32Array(5); - deepEquals(fill(u321, 2), u322.fill(2)); - deepEquals(fill(u321, 65537), u322.fill(65537)); - deepEquals(fill(u321, -1), u322.fill(-1)); + deepEquals(fillFallback(u321, 2), u322.fill(2)); + deepEquals(fillFallback(u321, 65537), u322.fill(65537)); + deepEquals(fillFallback(u321, -1), u322.fill(-1)); const i81 = new Int8Array(5); const i82 = new Int8Array(5); - deepEquals(fill(i81, 2), i82.fill(2)); - deepEquals(fill(i81, -1), i82.fill(-1)); + deepEquals(fillFallback(i81, 2), i82.fill(2)); + deepEquals(fillFallback(i81, -1), i82.fill(-1)); const i161 = new Int16Array(5); const i162 = new Int16Array(5); - deepEquals(fill(i161, 2), i162.fill(2)); - deepEquals(fill(i161, 65535), i162.fill(65535)); - deepEquals(fill(i161, -1), i162.fill(-1)); + deepEquals(fillFallback(i161, 2), i162.fill(2)); + deepEquals(fillFallback(i161, 65535), i162.fill(65535)); + deepEquals(fillFallback(i161, -1), i162.fill(-1)); const i321 = new Int32Array(5); const i322 = new Int32Array(5); - deepEquals(fill(i321, 2), i322.fill(2)); - deepEquals(fill(i321, 65537), i322.fill(65537)); - deepEquals(fill(i321, -1), i322.fill(-1)); + deepEquals(fillFallback(i321, 2), i322.fill(2)); + deepEquals(fillFallback(i321, 65537), i322.fill(65537)); + deepEquals(fillFallback(i321, -1), i322.fill(-1)); const f321 = new Float32Array(5); const f322 = new Float32Array(5); - deepEquals(fill(f321, 1.2345), f322.fill(1.2345)); + deepEquals(fillFallback(f321, 1.2345), f322.fill(1.2345)); const f641 = new Float64Array(5); const f642 = new Float64Array(5); - deepEquals(fill(f641, 1.2345), f642.fill(1.2345)); + deepEquals(fillFallback(f641, 1.2345), f642.fill(1.2345)); const u8Clamped1 = new Uint8ClampedArray(5); const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(fill(u8Clamped1, 2), u8Clamped2.fill(2)); - deepEquals(fill(u8Clamped1, 257), u8Clamped2.fill(257)); + deepEquals(fillFallback(u8Clamped1, 2), u8Clamped2.fill(2)); + deepEquals(fillFallback(u8Clamped1, 257), u8Clamped2.fill(257)); }); it('should work with all typed array types - explicit looping', function(): void { const u81 = new Uint8Array(5); @@ -124,8 +124,8 @@ describe('polyfill conformance tests', function(): void { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); const u83 = new Uint8Array(5); - deepEquals(fill(u81, 2, i), u83.fill(2, i)); - deepEquals(fill(u81, -1, i), u83.fill(-1, i)); + deepEquals(fillFallback(u81, 2, i), u83.fill(2, i)); + deepEquals(fillFallback(u81, -1, i), u83.fill(-1, i)); deepEquals(loopFill(u82, 2, i), u83.fill(2, i)); deepEquals(loopFill(u82, -1, i), u83.fill(-1, i)); } @@ -135,8 +135,8 @@ describe('polyfill conformance tests', function(): void { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); const u83 = new Uint8Array(5); - deepEquals(fill(u81, 2, 0, i), u83.fill(2, 0, i)); - deepEquals(fill(u81, -1, 0, i), u83.fill(-1, 0, i)); + deepEquals(fillFallback(u81, 2, 0, i), u83.fill(2, 0, i)); + deepEquals(fillFallback(u81, -1, 0, i), u83.fill(-1, 0, i)); deepEquals(loopFill(u82, 2, 0, i), u83.fill(2, 0, i)); deepEquals(loopFill(u82, -1, 0, i), u83.fill(-1, 0, i)); } @@ -147,8 +147,8 @@ describe('polyfill conformance tests', function(): void { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); const u83 = new Uint8Array(5); - deepEquals(fill(u81, 2, i, j), u83.fill(2, i, j)); - deepEquals(fill(u81, -1, i, j), u83.fill(-1, i, j)); + deepEquals(fillFallback(u81, 2, i, j), u83.fill(2, i, j)); + deepEquals(fillFallback(u81, -1, i, j), u83.fill(-1, i, j)); deepEquals(loopFill(u82, 2, i, j), u83.fill(2, i, j)); deepEquals(loopFill(u82, -1, i, j), u83.fill(-1, i, j)); } diff --git a/src/core/TypedArrayUtils.ts b/src/core/TypedArrayUtils.ts index 56e9d7b0..2e85400c 100644 --- a/src/core/TypedArrayUtils.ts +++ b/src/core/TypedArrayUtils.ts @@ -12,11 +12,15 @@ type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array; -export function fill(array: TypedArray, value: number, start: number = 0, end?: number | undefined): TypedArray { +export function fill(array: T, value: number, start: number = 0, end?: number | undefined): T { // all modern engines that support .fill if (array.fill) { - return array.fill(value, start, end); + return array.fill(value, start, end) as T; } + return fillFallback(array, value, start, end); +} + +export function fillFallback(array: T, value: number, start: number = 0, end?: number | undefined): T { // safari and IE 11 // since IE 11 does not support Array.prototype.fill either // we cannot use the suggested polyfill from MDN From 4f18717940e5be633228c1dabd81d0dd78f19a82 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 07:49:35 -0800 Subject: [PATCH 44/56] Improve handling of default values --- src/core/TypedArrayUtils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/TypedArrayUtils.ts b/src/core/TypedArrayUtils.ts index 2e85400c..6e1a3630 100644 --- a/src/core/TypedArrayUtils.ts +++ b/src/core/TypedArrayUtils.ts @@ -12,7 +12,7 @@ type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array; -export function fill(array: T, value: number, start: number = 0, end?: number | undefined): T { +export function fill(array: T, value: number, start?: number, end?: number): T { // all modern engines that support .fill if (array.fill) { return array.fill(value, start, end) as T; @@ -20,7 +20,7 @@ export function fill(array: T, value: number, start: numbe return fillFallback(array, value, start, end); } -export function fillFallback(array: T, value: number, start: number = 0, end?: number | undefined): T { +export function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T { // safari and IE 11 // since IE 11 does not support Array.prototype.fill either // we cannot use the suggested polyfill from MDN @@ -29,9 +29,6 @@ export function fillFallback(array: T, value: number, star return array; } start = (array.length + start) % array.length; - if (end === undefined) { - end = array.length; - } if (end >= array.length) { end = array.length; } else { From 1b5ef5d93a047b346b20beafd68545ea6295d20e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 08:08:30 -0800 Subject: [PATCH 45/56] Move TypedArrayUtils to common It belongs here since the renderers will use it --- src/CharWidth.ts | 2 +- src/{core => common}/TypedArrayUtils.test.ts | 0 src/{core => common}/TypedArrayUtils.ts | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/{core => common}/TypedArrayUtils.test.ts (100%) rename src/{core => common}/TypedArrayUtils.ts (100%) diff --git a/src/CharWidth.ts b/src/CharWidth.ts index fd6ac55f..43cd948e 100644 --- a/src/CharWidth.ts +++ b/src/CharWidth.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { fill } from './core/TypedArrayUtils'; +import { fill } from './common/TypedArrayUtils'; export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number { // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c diff --git a/src/core/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts similarity index 100% rename from src/core/TypedArrayUtils.test.ts rename to src/common/TypedArrayUtils.test.ts diff --git a/src/core/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts similarity index 100% rename from src/core/TypedArrayUtils.ts rename to src/common/TypedArrayUtils.ts From fc6f101e160f071f8d468a3c4a3fc4443256eb37 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 08:59:30 -0800 Subject: [PATCH 46/56] Remove unused lineHeight parameters --- src/SelectionManager.ts | 2 +- src/Terminal.ts | 4 ++-- src/Types.ts | 4 ++-- src/handlers/AltClickHandler.ts | 1 - src/ui/MouseZoneManager.ts | 2 +- src/utils/MouseHelper.test.ts | 16 ++++++++-------- src/utils/MouseHelper.ts | 6 +++--- 7 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 86be0c48..49e21e2c 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -347,7 +347,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true); + const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.cols, this._terminal.rows, true); if (!coords) { return null; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 38eb3d5b..4b60cfab 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -808,7 +808,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II button = getButton(ev); // get mouse coordinates - pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows); if (!pos) return; sendEvent(button, pos); @@ -834,7 +834,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< function sendMove(ev: MouseEvent): void { let button = pressed; - const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows); if (!pos) return; // buttons marked as motions diff --git a/src/Types.ts b/src/Types.ts index e8578426..6d6187f1 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -246,8 +246,8 @@ export interface ILinkifierAccessor { } export interface IMouseHelper { - getCoords(event: { pageX: number, pageY: number }, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number]; - getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number }; + getCoords(event: { pageX: number, pageY: number }, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number]; + getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number }; } export interface ICharMeasure { diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 8226fd96..9286421e 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -33,7 +33,6 @@ export class AltClickHandler { this._mouseEvent, this._terminal.element, this._terminal.charMeasure, - this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, false diff --git a/src/ui/MouseZoneManager.ts b/src/ui/MouseZoneManager.ts index 491e2a05..a232f5b9 100644 --- a/src/ui/MouseZoneManager.ts +++ b/src/ui/MouseZoneManager.ts @@ -180,7 +180,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { } private _findZoneEventAt(e: MouseEvent): IMouseZone { - const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); + const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.cols, this._terminal.rows); if (!coords) { return null; } diff --git a/src/utils/MouseHelper.test.ts b/src/utils/MouseHelper.test.ts index ac3137f4..94d63b2b 100644 --- a/src/utils/MouseHelper.test.ts +++ b/src/utils/MouseHelper.test.ts @@ -37,34 +37,34 @@ describe('MouseHelper.getCoords', () => { describe('when charMeasure is not initialized', () => { it('should return null', () => { charMeasure = new MockCharMeasure(); - assert.equal(mouseHelper.getCoords({ pageX: 0, pageY: 0 }, document.createElement('div'), charMeasure, 1, 10, 10), null); + assert.equal(mouseHelper.getCoords({ pageX: 0, pageY: 0 }, document.createElement('div'), charMeasure, 10, 10), null); }); }); describe('when pageX/pageY are not supported', () => { it('should return null', () => { - assert.equal(mouseHelper.getCoords({ pageX: undefined, pageY: undefined }, document.createElement('div'), charMeasure, 1, 10, 10), null); + assert.equal(mouseHelper.getCoords({ pageX: undefined, pageY: undefined }, document.createElement('div'), charMeasure, 10, 10), null); }); }); it('should return the cell that was clicked', () => { let coords: [number, number]; - coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH / 2, pageY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 1, 10, 10); + coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH / 2, pageY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 10, 10); assert.deepEqual(coords, [1, 1]); - coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 1, 10, 10); + coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10); assert.deepEqual(coords, [1, 1]); - coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 1, 10, 10); + coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 10, 10); assert.deepEqual(coords, [1, 2]); - coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH + 1, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 1, 10, 10); + coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH + 1, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10); assert.deepEqual(coords, [2, 1]); }); it('should ensure the coordinates are returned within the terminal bounds', () => { let coords: [number, number]; - coords = mouseHelper.getCoords({ pageX: -1, pageY: -1 }, document.createElement('div'), charMeasure, 1, 10, 10); + coords = mouseHelper.getCoords({ pageX: -1, pageY: -1 }, document.createElement('div'), charMeasure, 10, 10); assert.deepEqual(coords, [1, 1]); // Event are double the cols/rows - coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH * 20, pageY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 1, 10, 10); + coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH * 20, pageY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 10, 10); assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal'); }); }); diff --git a/src/utils/MouseHelper.ts b/src/utils/MouseHelper.ts index ca1bb27e..e4b3f211 100644 --- a/src/utils/MouseHelper.ts +++ b/src/utils/MouseHelper.ts @@ -52,7 +52,7 @@ export class MouseHelper { * apply an offset to the x value such that the left half of the cell will * select that cell and the right half will select the next cell. */ - public getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { + public getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { // Coordinates cannot be measured if charMeasure has not been initialized if (!charMeasure.width || !charMeasure.height) { return null; @@ -85,8 +85,8 @@ export class MouseHelper { * @param colCount The number of columns in the terminal. * @param rowCount The number of rows in the terminal. */ - public getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } { - const coords = this.getCoords(event, element, charMeasure, lineHeight, colCount, rowCount); + public getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number } { + const coords = this.getCoords(event, element, charMeasure, colCount, rowCount); let x = coords[0]; let y = coords[1]; From 32574a1a09ded40e299cc1c337899535c2b8889b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 09:05:07 -0800 Subject: [PATCH 47/56] Move Clipboard to ui/ Belong here as it interacts with DOM Part of #1507 --- src/Terminal.ts | 2 +- src/{handlers => ui}/Clipboard.test.ts | 0 src/{handlers => ui}/Clipboard.ts | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/{handlers => ui}/Clipboard.test.ts (100%) rename src/{handlers => ui}/Clipboard.ts (100%) diff --git a/src/Terminal.ts b/src/Terminal.ts index 38eb3d5b..fea32c48 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -29,7 +29,7 @@ import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './common/EventEmitter'; import { Viewport } from './Viewport'; -import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard'; +import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './ui/Clipboard'; import { C0 } from './common/data/EscapeSequences'; import { InputHandler } from './InputHandler'; import { Renderer } from './renderer/Renderer'; diff --git a/src/handlers/Clipboard.test.ts b/src/ui/Clipboard.test.ts similarity index 100% rename from src/handlers/Clipboard.test.ts rename to src/ui/Clipboard.test.ts diff --git a/src/handlers/Clipboard.ts b/src/ui/Clipboard.ts similarity index 100% rename from src/handlers/Clipboard.ts rename to src/ui/Clipboard.ts From 41ba20f7499356f8ad45cb9526ed6e84c93f6267 Mon Sep 17 00:00:00 2001 From: jerch Date: Fri, 23 Nov 2018 18:40:52 +0100 Subject: [PATCH 48/56] remove not longer needed test cases Removed the `loopFill` test method as it is not needed anymore with the separation of the looping variant in `fillFallback`. --- src/common/TypedArrayUtils.test.ts | 69 ------------------------------ 1 file changed, 69 deletions(-) diff --git a/src/common/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts index 1df06f4c..69a62abc 100644 --- a/src/common/TypedArrayUtils.test.ts +++ b/src/common/TypedArrayUtils.test.ts @@ -9,26 +9,6 @@ type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array; -// we explicitly test against the looping version in the test cases -function loopFill(array: TypedArray, value: number, start: number = 0, end?: number | undefined): TypedArray { - if (start >= array.length) { - return array; - } - start = (array.length + start) % array.length; - if (end === undefined) { - end = array.length; - } - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - for (let i = start; i < end; ++i) { - array[i] = value; - } - return array; -} - describe('polyfill conformance tests', function(): void { function deepEquals(a: TypedArray, b: TypedArray): void { @@ -79,78 +59,29 @@ describe('polyfill conformance tests', function(): void { deepEquals(fillFallback(u8Clamped1, 2), u8Clamped2.fill(2)); deepEquals(fillFallback(u8Clamped1, 257), u8Clamped2.fill(257)); }); - it('should work with all typed array types - explicit looping', function(): void { - const u81 = new Uint8Array(5); - const u82 = new Uint8Array(5); - deepEquals(loopFill(u81, 2), u82.fill(2)); - deepEquals(loopFill(u81, -1), u82.fill(-1)); - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(loopFill(u161, 2), u162.fill(2)); - deepEquals(loopFill(u161, 65535), u162.fill(65535)); - deepEquals(loopFill(u161, -1), u162.fill(-1)); - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(loopFill(u321, 2), u322.fill(2)); - deepEquals(loopFill(u321, 65537), u322.fill(65537)); - deepEquals(loopFill(u321, -1), u322.fill(-1)); - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(loopFill(i81, 2), i82.fill(2)); - deepEquals(loopFill(i81, -1), i82.fill(-1)); - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(loopFill(i161, 2), i162.fill(2)); - deepEquals(loopFill(i161, 65535), i162.fill(65535)); - deepEquals(loopFill(i161, -1), i162.fill(-1)); - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(loopFill(i321, 2), i322.fill(2)); - deepEquals(loopFill(i321, 65537), i322.fill(65537)); - deepEquals(loopFill(i321, -1), i322.fill(-1)); - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(loopFill(f321, 1.2345), f322.fill(1.2345)); - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(loopFill(f641, 1.2345), f642.fill(1.2345)); - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(loopFill(u8Clamped1, 2), u8Clamped2.fill(2)); - deepEquals(loopFill(u8Clamped1, 257), u8Clamped2.fill(257)); - }); it('start offset', function(): void { for (let i = -2; i < 10; ++i) { const u81 = new Uint8Array(5); - const u82 = new Uint8Array(5); const u83 = new Uint8Array(5); deepEquals(fillFallback(u81, 2, i), u83.fill(2, i)); deepEquals(fillFallback(u81, -1, i), u83.fill(-1, i)); - deepEquals(loopFill(u82, 2, i), u83.fill(2, i)); - deepEquals(loopFill(u82, -1, i), u83.fill(-1, i)); } }); it('end offset', function(): void { for (let i = -2; i < 10; ++i) { const u81 = new Uint8Array(5); - const u82 = new Uint8Array(5); const u83 = new Uint8Array(5); deepEquals(fillFallback(u81, 2, 0, i), u83.fill(2, 0, i)); deepEquals(fillFallback(u81, -1, 0, i), u83.fill(-1, 0, i)); - deepEquals(loopFill(u82, 2, 0, i), u83.fill(2, 0, i)); - deepEquals(loopFill(u82, -1, 0, i), u83.fill(-1, 0, i)); } }); it('start/end offset', function(): void { for (let i = -2; i < 10; ++i) { for (let j = -2; j < 10; ++j) { const u81 = new Uint8Array(5); - const u82 = new Uint8Array(5); const u83 = new Uint8Array(5); deepEquals(fillFallback(u81, 2, i, j), u83.fill(2, i, j)); deepEquals(fillFallback(u81, -1, i, j), u83.fill(-1, i, j)); - deepEquals(loopFill(u82, 2, i, j), u83.fill(2, i, j)); - deepEquals(loopFill(u82, -1, i, j), u83.fill(-1, i, j)); } } }); From c6f1de797086949619981fa642de1ec677ff59e5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 24 Nov 2018 05:24:15 -0800 Subject: [PATCH 49/56] Introduce is256Color helper --- src/renderer/BaseRenderLayer.ts | 5 +++-- src/renderer/LinkRenderLayer.ts | 5 +++-- src/renderer/TextRenderLayer.ts | 5 +++-- src/renderer/atlas/CharAtlasUtils.ts | 5 +++++ src/renderer/atlas/StaticCharAtlas.ts | 3 ++- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 827356af..2afdebb5 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -5,10 +5,11 @@ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; import { CharData, ITerminal } from '../Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier, DEFAULT_COLOR } from './atlas/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'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -298,7 +299,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; - } else if (fg < DEFAULT_COLOR) { + } else if (is256Color(fg)) { // 256 color support this._ctx.fillStyle = this._colors.ansi[fg].css; } else { diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index c0d190ac..855830e4 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -6,7 +6,8 @@ import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } from '../Types'; import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { is256Color } from './atlas/CharAtlasUtils'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkHoverEvent = null; @@ -42,7 +43,7 @@ export class LinkRenderLayer extends BaseRenderLayer { private _onLinkHover(e: ILinkHoverEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; - } else if (e.fg < DEFAULT_COLOR) { + } else if (is256Color(e.fg)) { // 256 color support this._ctx.fillStyle = this._colors.ansi[e.fg].css; } else { diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 984272f6..7b3feed7 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -9,6 +9,7 @@ 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'; /** * This CharData looks like a null character, which will forc a clear and render @@ -186,7 +187,7 @@ export class TextRenderLayer extends BaseRenderLayer { let nextFillStyle = null; // null represents default background color if (bg === INVERTED_DEFAULT_COLOR) { nextFillStyle = this._colors.foreground.css; - } else if (bg < DEFAULT_COLOR) { + } else if (is256Color(bg)) { nextFillStyle = this._colors.ansi[bg].css; } @@ -230,7 +231,7 @@ export class TextRenderLayer extends BaseRenderLayer { this._ctx.save(); if (fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; - } else if (fg < DEFAULT_COLOR) { + } else if (is256Color(fg)) { // 256 color support this._ctx.fillStyle = this._colors.ansi[fg].css; } else { diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 59ac07df..c504f77e 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -6,6 +6,7 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; import { ICharAtlasConfig } from '../../shared/atlas/Types'; +import { DEFAULT_COLOR } from './Types'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter @@ -51,3 +52,7 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.colors.foreground === b.colors.foreground && a.colors.background === b.colors.background; } + +export function is256Color(colorCode: number): boolean { + return colorCode < DEFAULT_COLOR; +} diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts index 673a1509..8dc8be74 100644 --- a/src/renderer/atlas/StaticCharAtlas.ts +++ b/src/renderer/atlas/StaticCharAtlas.ts @@ -7,6 +7,7 @@ import { DIM_OPACITY, IGlyphIdentifier, DEFAULT_COLOR } from './Types'; import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from '../../shared/atlas/Types'; import { generateStaticCharAtlasTexture } from '../../shared/atlas/CharAtlasGenerator'; import BaseCharAtlas from './BaseCharAtlas'; +import { is256Color } from './CharAtlasUtils'; export default class StaticCharAtlas extends BaseCharAtlas { private _texture: HTMLCanvasElement | ImageBitmap; @@ -58,7 +59,7 @@ export default class StaticCharAtlas extends BaseCharAtlas { } let colorIndex = 0; - if (glyph.fg < DEFAULT_COLOR) { + if (is256Color(glyph.fg)) { colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0); } else if (glyph.fg === DEFAULT_COLOR) { // If default color and bold From f4a522516c4f1c766ffd1609b7b13bc11ca33583 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 24 Nov 2018 07:03:52 -0800 Subject: [PATCH 50/56] Remove shared/ folder We changed plans from moving web worker compatible code to shared/ to enforcing layers using core/, common/, ui/ and public/. This change moves files back from the shared/ dir to clean up the codebase. Part of #1507 --- src/AccessibilityManager.ts | 2 +- src/SelectionManager.ts | 2 +- src/Terminal.ts | 2 +- .../utils/Browser.ts => core/Platform.ts} | 0 src/renderer/ColorManager.ts | 3 +-- src/renderer/Types.ts | 18 +++++++++++---- src/renderer/atlas/CharAtlasCache.ts | 2 +- .../atlas/CharAtlasGenerator.ts | 4 ++-- src/renderer/atlas/CharAtlasUtils.ts | 3 +-- src/renderer/atlas/DynamicCharAtlas.ts | 9 ++++---- src/renderer/atlas/NoneCharAtlas.ts | 3 +-- src/renderer/atlas/StaticCharAtlas.ts | 5 ++--- src/renderer/atlas/Types.ts | 18 +++++++++++++++ src/shared/Types.ts | 18 --------------- src/shared/atlas/Types.ts | 22 ------------------- src/utils/TestUtils.test.ts | 2 +- 16 files changed, 48 insertions(+), 65 deletions(-) rename src/{shared/utils/Browser.ts => core/Platform.ts} (100%) rename src/{shared => renderer}/atlas/CharAtlasGenerator.ts (97%) delete mode 100644 src/shared/Types.ts delete mode 100644 src/shared/atlas/Types.ts diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 1a2de1d7..fa0121ad 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 './shared/utils/Browser'; +import { isMac } from './core/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 49e21e2c..3dc50271 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 './utils/MouseHelper'; -import * as Browser from './shared/utils/Browser'; +import * as Browser from './core/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 8e89c9ca..e898f816 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 './shared/utils/Browser'; +import * as Browser from './core/Platform'; import { addDisposableDomListener } from './ui/Lifecycle'; import * as Strings from './Strings'; import { MouseHelper } from './utils/MouseHelper'; diff --git a/src/shared/utils/Browser.ts b/src/core/Platform.ts similarity index 100% rename from src/shared/utils/Browser.ts rename to src/core/Platform.ts diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index d169f266..8a463670 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -3,8 +3,7 @@ * @license MIT */ -import { IColorManager } from './Types'; -import { IColor, IColorSet } from '../shared/Types'; +import { IColorManager, IColor, IColorSet } from './Types'; import { ITheme } from 'xterm'; const DEFAULT_FOREGROUND = fromHex('#ffffff'); diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 824304ed..f2271f95 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -5,7 +5,6 @@ import { ITerminal, CharacterJoinerHandler } from '../Types'; import { IEventEmitter, ITheme, IDisposable } from 'xterm'; -import { IColorSet } from '../shared/Types'; /** * Flags used to render terminal text properly. @@ -48,9 +47,6 @@ export interface IColorManager { colors: IColorSet; } -// TODO: We should probably rewrite the imports for IColorSet, but there's a lot of them -export { IColorSet }; - export interface IRenderDimensions { scaledCharWidth: number; scaledCharHeight: number; @@ -134,3 +130,17 @@ export interface ICharacterJoinerRegistry { deregisterCharacterJoiner(joinerId: number): boolean; getJoinedCharacters(row: number): [number, number][]; } + +export interface IColor { + css: string; + rgba: number; // 32-bit int with rgba in each byte +} + +export interface IColorSet { + foreground: IColor; + background: IColor; + cursor: IColor; + cursorAccent: IColor; + selection: IColor; + ansi: IColor[]; +} diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index eee93d6c..3cb0e1cd 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -5,12 +5,12 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; -import { ICharAtlasConfig } from '../../shared/atlas/Types'; import { generateConfig, configEquals } from './CharAtlasUtils'; import BaseCharAtlas from './BaseCharAtlas'; import DynamicCharAtlas from './DynamicCharAtlas'; import NoneCharAtlas from './NoneCharAtlas'; import StaticCharAtlas from './StaticCharAtlas'; +import { ICharAtlasConfig } from './Types'; const charAtlasImplementations = { 'none': NoneCharAtlas, diff --git a/src/shared/atlas/CharAtlasGenerator.ts b/src/renderer/atlas/CharAtlasGenerator.ts similarity index 97% rename from src/shared/atlas/CharAtlasGenerator.ts rename to src/renderer/atlas/CharAtlasGenerator.ts index f78a6d41..03ab7b95 100644 --- a/src/shared/atlas/CharAtlasGenerator.ts +++ b/src/renderer/atlas/CharAtlasGenerator.ts @@ -4,9 +4,9 @@ */ import { FontWeight } from 'xterm'; -import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from './Types'; +import { isFirefox, isSafari } from '../../core/Platform'; import { IColor } from '../Types'; -import { isFirefox, isSafari } from '../utils/Browser'; +import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; declare const Promise: any; diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index c504f77e..5b1add39 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -5,8 +5,7 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; -import { ICharAtlasConfig } from '../../shared/atlas/Types'; -import { DEFAULT_COLOR } from './Types'; +import { DEFAULT_COLOR, ICharAtlasConfig } from './Types'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index b6b323f0..72010768 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -3,14 +3,13 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR } from './Types'; -import { ICharAtlasConfig } from '../../shared/atlas/Types'; -import { IColor } from '../../shared/Types'; +import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; import BaseCharAtlas from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from '../ColorManager'; -import { clearColor } from '../../shared/atlas/CharAtlasGenerator'; +import { clearColor } from './CharAtlasGenerator'; import LRUMap from './LRUMap'; -import { isFirefox, isSafari } from '../../shared/utils/Browser'; +import { isFirefox, isSafari } from '../../core/Platform'; +import { IColor } from '../Types'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. diff --git a/src/renderer/atlas/NoneCharAtlas.ts b/src/renderer/atlas/NoneCharAtlas.ts index 1cbc9eea..308e3075 100644 --- a/src/renderer/atlas/NoneCharAtlas.ts +++ b/src/renderer/atlas/NoneCharAtlas.ts @@ -5,8 +5,7 @@ * A dummy CharAtlas implementation that always fails to draw characters. */ -import { IGlyphIdentifier } from './Types'; -import { ICharAtlasConfig } from '../../shared/atlas/Types'; +import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; import BaseCharAtlas from './BaseCharAtlas'; export default class NoneCharAtlas extends BaseCharAtlas { diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts index 8dc8be74..b54c833e 100644 --- a/src/renderer/atlas/StaticCharAtlas.ts +++ b/src/renderer/atlas/StaticCharAtlas.ts @@ -3,9 +3,8 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier, DEFAULT_COLOR } from './Types'; -import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from '../../shared/atlas/Types'; -import { generateStaticCharAtlasTexture } from '../../shared/atlas/CharAtlasGenerator'; +import { DIM_OPACITY, IGlyphIdentifier, DEFAULT_COLOR, ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; +import { generateStaticCharAtlasTexture } from './CharAtlasGenerator'; import BaseCharAtlas from './BaseCharAtlas'; import { is256Color } from './CharAtlasUtils'; diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts index 76cfd07d..38923b2f 100644 --- a/src/renderer/atlas/Types.ts +++ b/src/renderer/atlas/Types.ts @@ -3,10 +3,15 @@ * @license MIT */ +import { FontWeight } from 'xterm'; +import { IColorSet } from '../Types'; + export const DEFAULT_COLOR = 256; export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; +export const CHAR_ATLAS_CELL_SPACING = 1; + export interface IGlyphIdentifier { chars: string; code: number; @@ -16,3 +21,16 @@ export interface IGlyphIdentifier { dim: boolean; italic: boolean; } + +export interface ICharAtlasConfig { + type: 'none' | 'static' | 'dynamic'; + devicePixelRatio: number; + fontSize: number; + fontFamily: string; + fontWeight: FontWeight; + fontWeightBold: FontWeight; + scaledCharWidth: number; + scaledCharHeight: number; + allowTransparency: boolean; + colors: IColorSet; +} diff --git a/src/shared/Types.ts b/src/shared/Types.ts deleted file mode 100644 index 0cded8a8..00000000 --- a/src/shared/Types.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -export interface IColor { - css: string; - rgba: number; // 32-bit int with rgba in each byte -} - -export interface IColorSet { - foreground: IColor; - background: IColor; - cursor: IColor; - cursorAccent: IColor; - selection: IColor; - ansi: IColor[]; -} diff --git a/src/shared/atlas/Types.ts b/src/shared/atlas/Types.ts deleted file mode 100644 index 25eaa716..00000000 --- a/src/shared/atlas/Types.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { FontWeight } from 'xterm'; -import { IColorSet } from '../Types'; - -export const CHAR_ATLAS_CELL_SPACING = 1; - -export interface ICharAtlasConfig { - type: 'none' | 'static' | 'dynamic'; - devicePixelRatio: number; - fontSize: number; - fontFamily: string; - fontWeight: FontWeight; - fontWeightBold: FontWeight; - scaledCharWidth: number; - scaledCharHeight: number; - allowTransparency: boolean; - colors: IColorSet; -} diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index a5ef4b9f..10033a33 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/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 '../shared/utils/Browser'; +import * as Browser from '../core/Platform'; import { ITheme, IDisposable, IMarker } from 'xterm'; import { Terminal } from '../Terminal'; From 13175976d02db6695e17c8f60eb54a0142140934 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 24 Nov 2018 07:16:19 -0800 Subject: [PATCH 51/56] Upgrade to typescript 3.1 Bunch of types needed changing in this version, also the IOffscreenCanvas "support" in the canvas renderer was removed as we're moving away from that. --- package.json | 2 +- src/shared/atlas/CharAtlasGenerator.ts | 18 ++---------------- src/ui/ScreenDprMonitor.ts | 2 +- yarn.lock | 8 ++++---- 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index ad763f0b..c84a0164 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "ts-loader": "^4.5.0", "tslint": "^5.9.1", "tslint-consistent-codestyle": "^1.13.0", - "typescript": "3.0", + "typescript": "3.1", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "webpack": "^4.17.1", diff --git a/src/shared/atlas/CharAtlasGenerator.ts b/src/shared/atlas/CharAtlasGenerator.ts index f78a6d41..60138d79 100644 --- a/src/shared/atlas/CharAtlasGenerator.ts +++ b/src/shared/atlas/CharAtlasGenerator.ts @@ -8,22 +8,13 @@ import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from './Types'; import { IColor } from '../Types'; import { isFirefox, isSafari } from '../utils/Browser'; -declare const Promise: any; - -export interface IOffscreenCanvas { - width: number; - height: number; - getContext(type: '2d', config?: Canvas2DContextAttributes): CanvasRenderingContext2D; - transferToImageBitmap(): ImageBitmap; -} - /** * Generates a char atlas. * @param context The window or worker context. * @param canvasFactory A function to generate a canvas with a width or height. * @param config The config for the new char atlas. */ -export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, config: ICharAtlasConfig): HTMLCanvasElement | Promise { +export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement, config: ICharAtlasConfig): HTMLCanvasElement | Promise { const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; const canvas = canvasFactory( @@ -101,12 +92,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: ( // performance (tested on v55). if (!('createImageBitmap' in context) || isFirefox || isSafari) { // Don't attempt to clear background colors if createImageBitmap is not supported - if (canvas instanceof HTMLCanvasElement) { - // Just return the HTMLCanvas if it's a HTMLCanvasElement - return canvas; - } - // Transfer to an ImageBitmap is this is an OffscreenCanvas - return new Promise((r: (bitmap: ImageBitmap) => void) => r(canvas.transferToImageBitmap())); + return canvas; } const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); diff --git a/src/ui/ScreenDprMonitor.ts b/src/ui/ScreenDprMonitor.ts index 9247a032..d66eeb64 100644 --- a/src/ui/ScreenDprMonitor.ts +++ b/src/ui/ScreenDprMonitor.ts @@ -19,7 +19,7 @@ export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRat */ export class ScreenDprMonitor extends Disposable { private _currentDevicePixelRatio: number; - private _outerListener: MediaQueryListListener; + private _outerListener: (this: MediaQueryList, ev: MediaQueryListEvent) => any; private _listener: ScreenDprListener; private _resolutionMediaMatchList: MediaQueryList; diff --git a/yarn.lock b/yarn.lock index 267c3bfc..db31bfe1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6716,10 +6716,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.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.3.tgz#4853b3e275ecdaa27f78fda46dc273a7eb7fc1c8" - integrity sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg== +typescript@3.1: + version "3.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" + integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== uglify-es@^3.3.4: version "3.3.9" From 4d270bf72cc6afa6fde4f2e12e9762506974b4e9 Mon Sep 17 00:00:00 2001 From: Florian Wesch Date: Sun, 2 Dec 2018 15:38:50 +0100 Subject: [PATCH 52/56] Added info-beamer hosted Works pretty well. See https://community.infobeamer.com/t/174 for details. Wouldn't be able to add this feature without Xterm.js. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 551a94f4..c654f288 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,7 @@ computational environment for Jupyter, supporting interactive data science and s - [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere. - [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom. - [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client. +- [**info-beamer hosted**](https://info-beamer.com): Uses Xterm.js to manage digital signage devices from the web dashboard. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) From 6ee9178302aa31d8c9dacf8ecf17681741539c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 4 Dec 2018 18:55:11 +0100 Subject: [PATCH 53/56] make typed array buffer default --- src/Buffer.ts | 8 ++++---- src/BufferLine.ts | 13 +++++++------ src/Terminal.ts | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 0f75bdad..dd657a2a 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -7,7 +7,7 @@ import { CircularList } from './common/CircularList'; import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, IBufferLineConstructor } from './Types'; import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; -import { BufferLine, BufferLineTypedArray } from './BufferLine'; +import { BufferLine, BufferLineJSArray } from './BufferLine'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); @@ -57,9 +57,9 @@ export class Buffer implements IBuffer { } public setBufferLineFactory(type: string): void { - if (type === 'TypedArray') { - if (this._bufferLineConstructor !== BufferLineTypedArray) { - this._bufferLineConstructor = BufferLineTypedArray; + if (type === 'JsArray') { + if (this._bufferLineConstructor !== BufferLineJSArray) { + this._bufferLineConstructor = BufferLineJSArray; this._recreateLines(); } } else { diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 7c697334..d1f1b23a 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -7,8 +7,9 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; /** * Class representing a terminal line. + * @deprecated */ -export class BufferLine implements IBufferLine { +export class BufferLineJSArray implements IBufferLine { protected _data: CharData[]; public isWrapped = false; public length: number; @@ -94,14 +95,14 @@ export class BufferLine implements IBufferLine { } } - public copyFrom(line: BufferLine): void { + public copyFrom(line: BufferLineJSArray): void { this._data = line._data.slice(0); this.length = line.length; this.isWrapped = line.isWrapped; } public clone(): IBufferLine { - const newLine = new BufferLine(0); + const newLine = new BufferLineJSArray(0); newLine.copyFrom(this); return newLine; } @@ -129,7 +130,7 @@ const enum Cell { * TODO: * - provide getData/setData to directly access the data */ -export class BufferLineTypedArray implements IBufferLine { +export class BufferLine implements IBufferLine { protected _data: Uint32Array | null = null; protected _combined: {[index: number]: string} = {}; public length: number; @@ -248,7 +249,7 @@ export class BufferLineTypedArray implements IBufferLine { } /** alter to a full copy of line */ - public copyFrom(line: BufferLineTypedArray): void { + public copyFrom(line: BufferLine): void { if (this.length !== line.length) { this._data = new Uint32Array(line._data); } else { @@ -265,7 +266,7 @@ export class BufferLineTypedArray implements IBufferLine { /** create a new clone */ public clone(): IBufferLine { - const newLine = new BufferLineTypedArray(0); + 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); diff --git a/src/Terminal.ts b/src/Terminal.ts index e374f2cd..2cfc1ca8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -106,7 +106,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { theme: null, rightClickSelectsWord: Browser.isMac, rendererType: 'canvas', - experimentalBufferLineImpl: 'JsArray' + experimentalBufferLineImpl: 'TypedArray' }; export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { @@ -1179,7 +1179,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II */ public scroll(isWrapped: boolean = false): void { let newLine: IBufferLine; - const useRecycling = this.options.experimentalBufferLineImpl === 'TypedArray'; + const useRecycling = this.options.experimentalBufferLineImpl !== 'JsArray'; if (useRecycling) { newLine = this._blankLine; if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) { From 66ec57b431b0205a20a89f9ef9e0a1ff82813255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 4 Dec 2018 19:02:11 +0100 Subject: [PATCH 54/56] cleanup --- src/BufferLine.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index d1f1b23a..c0c81a5c 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -7,7 +7,8 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; /** * Class representing a terminal line. - * @deprecated + * + * @deprecated to be removed with one of the next releases */ export class BufferLineJSArray implements IBufferLine { protected _data: CharData[]; @@ -120,15 +121,6 @@ const enum Cell { /** * Typed array based bufferline implementation. - * Note: Unlike the JS variant the access to the data - * via set/get is always a copy action. - * Sloppy ref style coding will not work anymore: - * line = new BufferLine(10); - * char = line.get(0); // char is a copy - * char[some_index] = 123; // will not update the line - * line.set(0, ch); // do this to update line data - * TODO: - * - provide getData/setData to directly access the data */ export class BufferLine implements IBufferLine { protected _data: Uint32Array | null = null; From 18902dd7894699e656789dd9904199b6ca766e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 4 Dec 2018 19:11:39 +0100 Subject: [PATCH 55/56] make linter happy --- src/BufferLine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index c0c81a5c..a95fe0f4 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -7,7 +7,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; /** * Class representing a terminal line. - * + * * @deprecated to be removed with one of the next releases */ export class BufferLineJSArray implements IBufferLine { From 6f4a6ef3082b424ba69e95db84aab36ccc827967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 4 Dec 2018 20:33:12 +0100 Subject: [PATCH 56/56] deprecate experimentalBufferLineImpl --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c6b6b1e5..7528bb55 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -108,7 +108,7 @@ declare module 'xterm' { * - 'TypedArray': The new experimental implementation based on TypedArrays that is expected to * significantly boost performance and memory consumption. Use at your own risk. * - * This option will be removed in the future. + * @deprecated This option will be removed in the future. */ experimentalBufferLineImpl?: 'JsArray' | 'TypedArray';