From 4858bdc4646a0e03348e666333c63eec0411d856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 19 Jul 2019 01:45:27 +0200 Subject: [PATCH 001/338] CoreMouseService --- src/InputHandler.test.ts | 20 +- src/InputHandler.ts | 11 +- src/Terminal.ts | 249 ++++------------ src/common/TestUtils.test.ts | 14 +- src/common/Types.d.ts | 61 ++++ src/common/services/CoreMouseService.test.ts | 214 ++++++++++++++ src/common/services/CoreMouseService.ts | 294 +++++++++++++++++++ src/common/services/Services.ts | 28 +- 8 files changed, 684 insertions(+), 207 deletions(-) create mode 100644 src/common/services/CoreMouseService.test.ts create mode 100644 src/common/services/CoreMouseService.ts diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 38645a0b..4a27c55e 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -13,7 +13,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService } from 'common/TestUtils.test'; +import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; @@ -33,7 +33,7 @@ describe('InputHandler', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // Save cursor position inputHandler.saveCursor(); assert.equal(bufferService.buffer.x, 1); @@ -52,7 +52,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService); + const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService()); const collect = ' '; inputHandler.setCursorStyle(Params.fromArray([0]), collect); @@ -95,7 +95,7 @@ describe('InputHandler', () => { const terminal = new MockInputHandlingTerminal(); const collect = '?'; terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // Set bracketed paste mode inputHandler.setMode(Params.fromArray([2004]), collect); assert.equal(terminal.bracketedPasteMode, true); @@ -114,7 +114,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -152,7 +152,7 @@ describe('InputHandler', () => { it('deleteChars', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -193,7 +193,7 @@ describe('InputHandler', () => { it('eraseInLine', function(): void { const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // fill 6 lines to test 3 different states inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -222,7 +222,7 @@ describe('InputHandler', () => { it('eraseInDisplay', function(): void { const term = new Terminal({cols: 80, rows: 7}); const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); // fill display with a's for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -357,7 +357,7 @@ describe('InputHandler', () => { describe('print', () => { it('should not cause an infinite loop (regression test)', () => { const term = new Terminal(); - const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -372,7 +372,7 @@ describe('InputHandler', () => { beforeEach(() => { term = new Terminal(); bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService()); + handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index cc767a16..6d486592 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -19,7 +19,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { IAttributeData, IDisposable } from 'common/Types'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; import { ISelectionService } from 'browser/services/Services'; /** @@ -134,6 +134,7 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _dirtyRowService: IDirtyRowService, private readonly _logService: ILogService, private readonly _optionsService: IOptionsService, + private readonly _coreMouseService: ICoreMouseService, private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); @@ -1285,6 +1286,7 @@ export class InputHandler extends Disposable implements IInputHandler { // even if there is no button held down. // TODO: Why are params[0] compares nested within a switch for params[0]? + this._coreMouseService.activeProtocol = param === 9 ? 'X10' : param === 1000 ? 'VT200' : param === 1002 ? 'DRAG' : 'ANY'; this._terminal.x10Mouse = param === 9; this._terminal.vt200Mouse = param === 1000; @@ -1305,11 +1307,13 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 1005: // utf8 ext mode mouse this._terminal.utfMouse = true; + this._coreMouseService.activeEncoding = 'UTF8'; // for wide terminals // simply encodes large values as utf8 characters break; case 1006: // sgr ext mode mouse this._terminal.sgrMouse = true; + this._coreMouseService.activeEncoding = 'SGR'; // for wide terminals // does not add 32 to fields // press: ^[[ this.scrollToBottom()); this._instantiationService.setService(ICoreService, this._coreService); this._coreService.onData(e => this._onData.fire(e)); + this._coreMouseService = this._instantiationService.createInstance(CoreMouseService); + this._instantiationService.setService(ICoreMouseService, this._coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); this._logService = this._instantiationService.createInstance(LogService); @@ -309,7 +313,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._userScrolling = false; // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService); + this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); @@ -709,223 +713,85 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } /** - * XTerm mouse events - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking - * To better understand these - * the xterm code is very helpful: - * Relevant files: - * button.c, charproc.c, misc.c - * Relevant functions in xterm/button.c: - * BtnCode, EmitButtonCode, EditorButton, SendMousePosition + * mouse events + * FIXME: move event handler registration into browser MouseService */ public bindMouse(): void { const el = this.element; const self = this; - let pressed = 32; - // mouseup, mousedown, wheel - // left click: ^[[M 3<^[[M#3< - // wheel up: ^[[M`3> function sendButton(ev: MouseEvent | WheelEvent): void { - let button; let pos; - // get the xterm-style button - button = getButton(ev); - // get mouse coordinates pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); if (!pos) return; - sendEvent(button, pos); - + let but: ICoreMouseEvent['button']; + let action: ICoreMouseEvent['action']; + let code: number; switch ((ev).overrideType || ev.type) { - case 'mousedown': - pressed = button; - break; case 'mouseup': - // keep it at the left - // button, just in case. - pressed = 32; - break; - case 'wheel': - // nothing. don't - // interfere with - // `pressed`. - break; - } - } - - // motion example of a left click: - // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< - function sendMove(ev: MouseEvent): void { - let button = pressed; - const pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); - if (!pos) return; - - // buttons marked as motions - // are incremented by 32 - button += 32; - - sendEvent(button, pos); - } - - // encode button and - // position to characters - function encode(data: number[], ch: number): void { - if (!self.utfMouse) { - if (ch === 255) { - data.push(0); - return; - } - if (ch > 127) ch = 127; - data.push(ch); - } else { - if (ch > 2047) { - data.push(2047); - return; - } - data.push(ch); - } - } - - // send a mouse event: - // regular/utf8: ^[[M Cb Cx Cy - // urxvt: ^[[ Cb ; Cx ; Cy M - // sgr: ^[[ Cb ; Cx ; Cy M/m - // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r - // locator: CSI P e ; P b ; P r ; P c ; P p & w - function sendEvent(button: number, pos: {x: number, y: number}): void { - if (self._vt300Mouse) { - // NOTE: Unstable. - // http://www.vt100.net/docs/vt3xx-gp/chapter15.html - button &= 3; - pos.x -= 32; - pos.y -= 32; - let data = C0.ESC + '[24'; - if (button === 0) data += '1'; - else if (button === 1) data += '3'; - else if (button === 2) data += '5'; - else if (button === 3) return; - else data += '0'; - data += '~[' + pos.x + ',' + pos.y + ']\r'; - self._coreService.triggerDataEvent(data, true); - return; - } - - if (self._decLocator) { - // NOTE: Unstable. - button &= 3; - pos.x -= 32; - pos.y -= 32; - if (button === 0) button = 2; - else if (button === 1) button = 4; - else if (button === 2) button = 6; - else if (button === 3) button = 3; - self._coreService.triggerDataEvent(C0.ESC + '[' - + button - + ';' - + (button === 3 ? 4 : 0) - + ';' - + pos.y - + ';' - + pos.x - + ';' - // Not sure what page is meant to be - + (pos).page || 0 - + '&w', true); - return; - } - - if (self.urxvtMouse) { - pos.x -= 32; - pos.y -= 32; - pos.x++; - pos.y++; - self._coreService.triggerDataEvent(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M', true); - return; - } - - if (self.sgrMouse) { - pos.x -= 32; - pos.y -= 32; - self._coreService.triggerDataEvent(C0.ESC + '[<' - + (((button & 3) === 3 ? button & ~3 : button) - 32) - + ';' - + pos.x - + ';' - + pos.y - + ((button & 3) === 3 ? 'm' : 'M'), true); - return; - } - - const data: number[] = []; - - encode(data, button); - encode(data, pos.x); - encode(data, pos.y); - - self._coreService.triggerDataEvent(C0.ESC + '[M' + String.fromCharCode.apply(String, data), true); - } - - function getButton(ev: MouseEvent): number { - let button; - let shift; - let meta; - let ctrl; - let mod; - - // two low bits: - // 0 = left - // 1 = middle - // 2 = right - // 3 = release - // wheel up/down: - // 1, and 2 - with 64 added - switch ((ev).overrideType || ev.type) { - case 'mousedown': - button = ev.button !== null && ev.button !== undefined + action = 'up'; + code = ev.button !== null && ev.button !== undefined ? +ev.button : ev.which !== null && ev.which !== undefined ? ev.which - 1 : null; + but = code === 0 ? 'left' : code === 1 ? 'middle' : 'right'; break; - case 'mouseup': - button = 3; + case 'mousedown': + action = 'down'; + code = ev.button !== null && ev.button !== undefined + ? +ev.button + : ev.which !== null && ev.which !== undefined + ? ev.which - 1 + : null; + but = code === 0 ? 'left' : code === 1 ? 'middle' : 'right'; break; case 'DOMMouseScroll': - button = ev.detail < 0 - ? 64 - : 65; + but = 'wheel'; + action = ev.detail < 0 ? 'up' : 'down'; break; case 'wheel': - button = (ev).deltaY < 0 - ? 64 - : 65; + but = 'wheel'; + action = (ev).deltaY < 0 ? 'up' : 'down'; break; } + self._coreMouseService.triggerMouseEvent({ + col: pos.x - 33, // FIXME: why -33 here? + row: pos.y - 33, + button: but, + action, + ctrl: ev.ctrlKey, + alt: ev.altKey, + shift: ev.shiftKey + }); + return; + } - // next three bits are the modifiers: - // 4 = shift, 8 = meta, 16 = control - shift = ev.shiftKey ? 4 : 0; - meta = ev.metaKey ? 8 : 0; - ctrl = ev.ctrlKey ? 16 : 0; - mod = shift | meta | ctrl; + function sendMove(ev: MouseEvent): void { + const pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); + if (!pos) return; - // no mods - if (self.vt200Mouse) { - // ctrl only - mod &= ctrl; - } else if (!self.normalMouse) { - mod = 0; + let but: ICoreMouseEvent['button'] = 'none'; + if (ev.buttons !== undefined) { + but = ev.buttons & 1 ? 'left' : ev.buttons & 2 ? 'right' : ev.buttons & 4 ? 'middle' : 'none'; } - // increment to SP - button = (32 + (mod << 2)) + button; - - return button; + self._coreMouseService.triggerMouseEvent({ + col: pos.x - 33, + row: pos.y - 33, + button: but, + action: 'move', + ctrl: ev.ctrlKey, + alt: ev.altKey, + shift: ev.shiftKey + }); + return; } + this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => { // Prevent the focus on the textarea from getting lost @@ -988,10 +854,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return this.cancel(ev); })); - // if (this.normalMouse) { - // on(this.document, 'mousemove', sendMove); - // } - this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => { if (!this.mouseEvents) { // Convert wheel events into up/down events when the buffer does not have scrollback, this @@ -1790,6 +1652,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._setup(); this._bufferService.reset(); this._coreService.reset(); + this._coreMouseService.reset(); if (this._selectionService) { this._selectionService.reset(); } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index ec36be2f..bbeb4de8 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,13 +3,13 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; -import { IDecPrivateModes } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; export class MockBufferService implements IBufferService { serviceBrand: any; @@ -29,6 +29,16 @@ export class MockBufferService implements IBufferService { reset(): void {} } +export class MockCoreMouseService implements ICoreMouseService { + activeEncoding: string = ''; + activeProtocol: string = ''; + addEncoding(name: string): void {} + addProtocol(name: string): void {} + reset(): void {} + triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } + onProtocolChange: IEvent = new EventEmitter().event; +} + export class MockCoreService implements ICoreService { serviceBrand: any; decPrivateModes: IDecPrivateModes = {} as any; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index b25b50a5..598a6a9e 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -158,3 +158,64 @@ export interface IRowRange { start: number; end: number; } + +/** + * Interface for mouse events in the core. + */ +export interface ICoreMouseEvent { + /** column (zero based). */ + col: number; + /** row (zero based). */ + row: number; + /** + * Button the action occured. Due to restrictions of the tracking protocols + * it is not possible to report multiple buttons at once. + * Wheel is treated as a button. + * There are invalid combinations of buttons and actions possible + * (like move + wheel), those are silently ignored by the CoreMouseService. + */ + button: 'left' | 'middle' | 'right' | 'wheel' | 'none'; + action: 'up' | 'down' | 'move'; + /** + * Modifier states. + * Protocols will add/ignore those based on specific restrictions. + */ + ctrl?: boolean; + alt?: boolean; + shift?: boolean; +} + +/** + * CoreMouseEventType + * To be reported to the browser component which events a mouse + * protocol wants to be catched and forwarded as an ICoreMouseEvent + * to CoreMouseService. + * Known types: + * - mousedown: any mousedown event + * - mouseup: any mouseup event + * - wheel: any wheel event + * - mousedrag: any mousemove event while a button is pressed + * - mousemove: any mousemove event + */ +export type CoreMouseEventType = 'mousedown' | 'mouseup' | 'wheel' | 'mousemove' | 'mousedrag'; + +/** + * Mouse protocol interface. + * A mouse protocol can be registered and activated at the CoreMouseService. + * `events` should contain a list of needed events as a hint for the browser component + * to install/remove the appropriate event handlers. + * `restrict` applies further protocol specific restrictions like not allowed + * modifiers or filtering invalid event types. + */ +export interface ICoreMouseProtocol { + events: CoreMouseEventType[]; + restrict: (e: ICoreMouseEvent) => boolean; +} + +/** + * CoreMouseEncoding + * The tracking encoding can be registered and activated at the CoreMouseService. + * If a ICoreMouseEvent passes all procotol restrictions it will be encoded + * with the active encoding and sent out. + */ +export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts new file mode 100644 index 00000000..b4131f00 --- /dev/null +++ b/src/common/services/CoreMouseService.test.ts @@ -0,0 +1,214 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { CoreMouseService } from 'common/services/CoreMouseService'; +import { MockCoreService, MockBufferService } from 'common/TestUtils.test'; +import { assert } from 'chai'; +import { ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; + +// needed mock services +const bufferService = new MockBufferService(300, 100); +const coreService = new MockCoreService(); + +function toBytes(s: string | undefined): number[] { + if (!s) { + return []; + } + const res: number[] = []; + for (let i = 0; i < s.length; ++i) { + res.push(s.charCodeAt(i)); + } + return res; +} + +describe('CoreMouseService', () => { + it('init', () => { + const cms = new CoreMouseService(bufferService, coreService); + assert.equal(cms.activeEncoding, 'DEFAULT'); + assert.equal(cms.activeProtocol, 'NONE'); + }); + it('default protocols - NONE, X10, VT200, DRAG, ANY', () => { + const cms = new CoreMouseService(bufferService, coreService); + assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']); + }); + it('default encodings - DEFAULT, UTF8, SGR, URXVT', () => { + const cms = new CoreMouseService(bufferService, coreService); + assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'UTF8', 'SGR', 'URXVT']); + }); + it('protocol/encoding setter, reset', () => { + const cms = new CoreMouseService(bufferService, coreService); + cms.activeEncoding = 'SGR'; + cms.activeProtocol = 'ANY'; + assert.equal(cms.activeEncoding, 'SGR'); + assert.equal(cms.activeProtocol, 'ANY'); + cms.reset(); + assert.equal(cms.activeEncoding, 'DEFAULT'); + assert.equal(cms.activeProtocol, 'NONE'); + assert.throws(() => { cms.activeEncoding = 'xyz'; }, 'unknown encoding "xyz"'); + assert.throws(() => { cms.activeProtocol = 'xyz'; }, 'unknown protocol "xyz"'); + }); + it('addEncoding', () => { + const cms = new CoreMouseService(bufferService, coreService); + cms.addEncoding('XYZ', (e: ICoreMouseEvent) => ''); + cms.activeEncoding = 'XYZ'; + assert.equal(cms.activeEncoding, 'XYZ'); + }); + it('addProtocol', () => { + const cms = new CoreMouseService(bufferService, coreService); + cms.addProtocol('XYZ', { events: [], restrict: (e: ICoreMouseEvent) => false }); + cms.activeProtocol = 'XYZ'; + assert.equal(cms.activeProtocol, 'XYZ'); + }); + it('onProtocolChange', () => { + const cms = new CoreMouseService(bufferService, coreService); + const wantedEvents: CoreMouseEventType[][] = []; + cms.onProtocolChange(events => wantedEvents.push(events)); + cms.activeProtocol = 'NONE'; + assert.deepEqual(wantedEvents, [[]]); + cms.activeProtocol = 'ANY'; + assert.deepEqual(wantedEvents, [[], ['mousedown', 'mouseup', 'wheel', 'mousemove']]); + }); + describe('triggerMouseEvent', () => { + let cms: CoreMouseService; + let reports: string[]; + beforeEach(() => { + cms = new CoreMouseService(bufferService, coreService); + reports = []; + coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data); + }); + it('NONE', () => { + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + }); + it('X10', () => { + cms.activeProtocol = 'X10'; + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + }); + it('VT200', () => { + cms.activeProtocol = 'VT200'; + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + }); + it('DRAG', () => { + cms.activeProtocol = 'DRAG'; + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); + }); + it('ANY', () => { + cms.activeProtocol = 'ANY'; + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), true); + // should not report in any case + // invalid button + action combinations + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'move' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'down' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'up' }), false); + // invalid coords + assert.equal(cms.triggerMouseEvent({ col: -1, row: 0, button: 'left', action: 'down' }), false); + assert.equal(cms.triggerMouseEvent({ col: 500, row: 0, button: 'left', action: 'down' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: -1, button: 'left', action: 'down' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 500, button: 'left', action: 'down' }), false); + }); + describe('coords', () => { + it('DEFAULT encoding', () => { + cms.activeProtocol = 'ANY'; + for (let i = 0; i < bufferService.cols; ++i) { + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + // capped at 95 + if (i < 95) { + assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); + } else { + assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, 0x7f, 0x21]); + } + } + }); + it('UTF8 encoding', () => { + cms.activeProtocol = 'ANY'; + cms.activeEncoding = 'UTF8'; + for (let i = 0; i < bufferService.cols; ++i) { + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); + } + }); + it('SGR encoding', () => { + cms.activeProtocol = 'ANY'; + cms.activeEncoding = 'SGR'; + for (let i = 0; i < bufferService.cols; ++i) { + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`); + } + }); + it('URXVT', () => { + cms.activeProtocol = 'ANY'; + cms.activeEncoding = 'URXVT'; + for (let i = 0; i < bufferService.cols; ++i) { + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.deepEqual(reports.pop(), `\x1b[32;${i + 1};1M`); + } + }); + }); + it('eventCodes with modifiers (DEFAULT encoding)', () => { + cms.activeProtocol = 'ANY'; + cms.activeEncoding = 'DEFAULT'; + // all buttons + down + no modifer + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'down', ctrl: false, alt: false, shift: false }), true); + assert.deepEqual(reports, ['\x1b[M !!', '\x1b[M!!!', '\x1b[M"!!', '\x1b[Ma!!']); + while (reports.pop()) { } + + // all buttons + up + no modifier + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'up', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'up', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up', ctrl: false, alt: false, shift: false }), true); + assert.deepEqual(reports, ['\x1b[M#!!', '\x1b[M#!!', '\x1b[M#!!', '\x1b[M`!!']); + while (reports.pop()) { } + + // all buttons + move + no modifier + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'move', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'move', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: false, shift: false }), true); + assert.deepEqual(reports, ['\x1b[M@!!', '\x1b[MA!!', '\x1b[MB!!', '\x1b[MC!!']); + while (reports.pop()) { } + + // button none + move + modifiers + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: true, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: true, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: false, shift: true }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: true, alt: true, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: true, shift: true }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: true, alt: true, shift: true }), true); + assert.deepEqual(reports, ['\x1b[MS!!', '\x1b[MK!!', '\x1b[MG!!', '\x1b[M[!!', '\x1b[MO!!', '\x1b[M_!!']); + while (reports.pop()) { } + }); + }); +}); diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts new file mode 100644 index 00000000..6549c002 --- /dev/null +++ b/src/common/services/CoreMouseService.ts @@ -0,0 +1,294 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType } from 'common/Types'; + +/** + * Supported default protocols. + */ +const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { + /** + * NONE + * Events: none + * Modifiers: none + */ + NONE: { + events: [], + restrict: () => false + }, + /** + * X10 + * Events: mousedown + * Modifiers: none (TBD) + */ + X10: { + events: ['mousedown'], + restrict: (e: ICoreMouseEvent) => { + // no wheel (TBD), no move, no up + if (e.button === 'wheel' || e.action !== 'down') { + return false; + } + // no modifiers (TDB) + e.ctrl = false; + e.alt = false; + e.shift = false; + return true; + } + }, + /** + * VT200 + * Events: mousedown / mouseup / wheel + * Modifiers: CTRL (TBD) + */ + VT200: { + events: ['mousedown', 'mouseup', 'wheel'], + restrict: (e: ICoreMouseEvent) => { + // no move + if (e.action === 'move') { + return false; + } + // modifiers - only ctrl? + e.alt = false; + e.shift = false; + return true; + } + }, + /** + * DRAG + * Events: mousedown / mouseup / wheel / mousedrag + * Modifiers: CTRL | ALT | SHIFT + */ + DRAG: { + events: ['mousedown', 'mouseup', 'wheel', 'mousedrag'], + restrict: (e: ICoreMouseEvent) => { + // no move without button + if (e.action === 'move' && e.button === 'none') { + return false; + } + // modifiers unclear - let all pass for now + return true; + } + }, + /** + * ANY + * Events: all mouse related events + * Modifiers: CTRL | ALT | SHIFT + */ + ANY: { + events: ['mousedown', 'mouseup', 'wheel', 'mousemove'], + restrict: (e: ICoreMouseEvent) => true + } +}; + +/** + * Mapping of buttons and actions to event codes. (taken from xterm spec) + * More than 3 buttons are not supported. + */ +enum CODEMAP { + // buttons + left = 0, + middle = 1, + right = 2, + none = 3, + wheel = 64, + // actions + up = 0, + down = 1, + move = 32, + // modifiers + shift = 4, + alt = 8, + ctrl = 16 +} + +// helper for default encoders to generate the event code. +function eventCode(e: ICoreMouseEvent, isSGR: boolean): number { + const button = CODEMAP[e.button]; + const action = CODEMAP[e.action]; + const modifier = (e.ctrl ? CODEMAP.ctrl : 0) | (e.shift ? CODEMAP.shift : 0) | (e.alt ? CODEMAP.alt : 0); + let code = button | modifier; + if (e.button === 'wheel') { + code |= action; + } else { + if (e.action === 'move') { + code |= CODEMAP.move; + } else if (e.action === 'up' && !isSGR) { + // special case - only SGR can report button on release + // all others have to go with NONE + code |= CODEMAP.none; + } + } + return code; +} + +const S = String.fromCharCode; + +/** + * Supported default encodings. + */ +const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = { + /** + * DEFAULT - CSI M Pb Px Py + * Single byte encoding for coords and event code. + * Can encode values up to 223. The Encoding of higher + * values is not UTF-8 compatible (and currently limited + * to 95 in xterm.js). + */ + DEFAULT: (e: ICoreMouseEvent) => { + let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; + // FIXME: we are currently limited to ASCII range + params = params.map(v => (v > 127) ? 127 : v); + // FIXED: params = params.map(v => (v > 255) ? 0 : value); + return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`; + }, + /** + * UTF8 - CSI M Pb Px Py + * Same as DEFAULT, but with optional 2-byte UTF8 + * encoding for values > 223 (can encode up to 2015). + */ + UTF8: (e: ICoreMouseEvent) => { + let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; + // limit to 2-byte UTF8 + params = params.map(v => (v > 2047) ? 0 : v); + return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`; + }, + /** + * SGR - CSI < Pb ; Px ; Py M|m + * No encoding limitation. + * Can report button on release and works with a well formed sequence. + */ + SGR: (e: ICoreMouseEvent) => { + const final = (e.action === 'up' && e.button !== 'wheel') ? 'm' : 'M'; + return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`; + }, + /** + * URXVT - CSI Pb ; Px ; Py M + * Same button encoding as default, decimal encoding for coords. + * Ambiguity with other sequences, should not be used. + */ + URXVT: (e: ICoreMouseEvent) => { + return `\x1b[${eventCode(e, false) + 32};${e.col};${e.row}M`; + } +}; + +/** + * CoreMouseService + * + * Provides mouse tracking reports with different protocols and encodings. + * - protocols: NONE (default), X10, VT200, DRAG, ANY + * - encodings: DEFAULT, SGR, UTF8, URXVT + * + * Custom protocols/encodings can be added by `addProtocol` / `addEncoding`. + * To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`. + * Switching a protocol will send a notification event `onProtocolChange` + * with a list of needed events to track. + * + * The service handles the mouse tracking state and decides whether to send + * a tracking report to the backend based on protocol and encoding limitations. + * To send a mouse event call `triggerMouseEvent`. + */ +export class CoreMouseService implements ICoreMouseService { + private _protocols: {[name: string]: ICoreMouseProtocol} = {}; + private _encodings: {[name: string]: CoreMouseEncoding} = {}; + private _activeProtocol: string = ''; + private _activeEncoding: string = ''; + private _onProtocolChange = new EventEmitter(); + + constructor( + @IBufferService private readonly _bufferService: IBufferService, + @ICoreService private readonly _coreService: ICoreService + ) { + // register default protocols and encodings + Object.keys(DEFAULT_PROCOTOLS).forEach(name => this.addProtocol(name, DEFAULT_PROCOTOLS[name])); + Object.keys(DEFAULT_ENCODINGS).forEach(name => this.addEncoding(name, DEFAULT_ENCODINGS[name])); + // call reset to set defaults + this.reset(); + } + + public addProtocol(name: string, protocol: ICoreMouseProtocol): void { + this._protocols[name] = protocol; + } + + public addEncoding(name: string, encoding: CoreMouseEncoding): void { + this._encodings[name] = encoding; + } + + public get activeProtocol(): string { + return this._activeProtocol; + } + + public set activeProtocol(name: string) { + if (!this._protocols[name]) { + throw new Error(`unknown protocol "${name}"`); + } + this._activeProtocol = name; + this._onProtocolChange.fire(this._protocols[name].events); + } + + public get activeEncoding(): string { + return this._activeEncoding; + } + + public set activeEncoding(name: string) { + if (!this._encodings[name]) { + throw new Error(`unknown encoding "${name}"`); + } + this._activeEncoding = name; + } + + public reset(): void { + this.activeProtocol = 'NONE'; + this.activeEncoding = 'DEFAULT'; + } + + /** + * Event to announce changes in mouse tracking. + */ + public get onProtocolChange(): IEvent { + return this._onProtocolChange.event; + } + + /** + * Triggers a mouse event to be sent. + * + * Returns true if the event passed all protocol restrictions and a report + * was sent, otherwise false. The return value may be used to decide whether + * the default event action in the bowser component should be omitted. + * + * Note: The method will change values of the given event object + * to fullfill protocol and encoding restrictions. + */ + public triggerMouseEvent(event: ICoreMouseEvent): boolean { + // range check for col/row + if (event.col < 0 || event.col >= this._bufferService.cols + || event.row < 0 || event.row >= this._bufferService.rows) { + return false; + } + + // filter nonsense combinations of button + action + if (event.button === 'wheel' && event.action === 'move') { + return false; + } + if (event.button === 'none' && event.action !== 'move') { + return false; + } + + // report 1-based coords + event.col++; + event.row++; + + // apply protocol restrictions + if (!this._protocols[this._activeProtocol].restrict(event)) { + return false; + } + + // encode report and send + const report = this._encodings[this._activeEncoding](event); + this._coreService.triggerDataEvent(report, true); + + return true; + } +} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 1af55e9a..8c3a7eb8 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; export const IBufferService = createDecorator('BufferService'); @@ -23,6 +23,32 @@ export interface IBufferService { reset(): void; } +export const ICoreMouseService = createDecorator('CoreMouseService'); +export interface ICoreMouseService { + activeProtocol: string; + activeEncoding: string; + addProtocol(name: string, protocol: ICoreMouseProtocol): void; + addEncoding(name: string, encoding: CoreMouseEncoding): void; + reset(): void; + + /** + * Triggers a mouse event to be sent. + * + * Returns true if the event passed all protocol restrictions and a report + * was sent, otherwise false. The return value may be used to decide whether + * the default event action in the bowser component should be omitted. + * + * Note: The method will change values of the given event object + * to fullfill protocol and encoding restrictions. + */ + triggerMouseEvent(event: ICoreMouseEvent): boolean; + + /** + * Event to announce changes in mouse tracking. + */ + onProtocolChange: IEvent; +} + export const ICoreService = createDecorator('CoreService'); export interface ICoreService { serviceBrand: any; From e38a68e3965fc08e8447fb6daca53d9a2e085500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 19 Jul 2019 02:46:03 +0200 Subject: [PATCH 002/338] debounce events at grid level --- src/common/services/CoreMouseService.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 6549c002..36b562ad 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -196,6 +196,7 @@ export class CoreMouseService implements ICoreMouseService { private _activeProtocol: string = ''; private _activeEncoding: string = ''; private _onProtocolChange = new EventEmitter(); + private _lastEvent: ICoreMouseEvent | null = null; constructor( @IBufferService private readonly _bufferService: IBufferService, @@ -242,6 +243,7 @@ export class CoreMouseService implements ICoreMouseService { public reset(): void { this.activeProtocol = 'NONE'; this.activeEncoding = 'DEFAULT'; + this._lastEvent = null; } /** @@ -280,6 +282,11 @@ export class CoreMouseService implements ICoreMouseService { event.col++; event.row++; + // debounce at grid level + if (this._lastEvent && this._compareEvents(this._lastEvent, event)) { + return false; + } + // apply protocol restrictions if (!this._protocols[this._activeProtocol].restrict(event)) { return false; @@ -289,6 +296,19 @@ export class CoreMouseService implements ICoreMouseService { const report = this._encodings[this._activeEncoding](event); this._coreService.triggerDataEvent(report, true); + this._lastEvent = event; + + return true; + } + + private _compareEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent): boolean { + if (e1.col !== e2.col) return false; + if (e1.row !== e2.row) return false; + if (e1.button !== e2.button) return false; + if (e1.action !== e2.action) return false; + if (e1.ctrl !== e2.ctrl) return false; + if (e1.alt !== e2.alt) return false; + if (e1.shift !== e2.shift) return false; return true; } } From df0cf253acb0b6ffd35999796b252f5ceef93b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 19 Jul 2019 02:56:24 +0200 Subject: [PATCH 003/338] debounce only move events --- src/common/services/CoreMouseService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 36b562ad..cddc058d 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -282,8 +282,8 @@ export class CoreMouseService implements ICoreMouseService { event.col++; event.row++; - // debounce at grid level - if (this._lastEvent && this._compareEvents(this._lastEvent, event)) { + // debounce move at grid level + if (event.action === 'move' && this._lastEvent && this._compareEvents(this._lastEvent, event)) { return false; } From cbddbc6e831cf0a4f3992038579b31d8641e525a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 19 Jul 2019 16:21:30 +0200 Subject: [PATCH 004/338] some changes in mouse event handling: - use onProtocolChange - remove mouse states from Terminal.ts - remove selectionService from InputHandler - fix up events in VT200 mode - fix move event with no buttons in ANY mode --- src/InputHandler.ts | 48 +--- src/Terminal.ts | 235 +++++++++++-------- src/TestUtils.test.ts | 4 +- src/Types.d.ts | 10 +- src/common/services/CoreMouseService.test.ts | 2 +- src/common/services/CoreMouseService.ts | 2 +- 6 files changed, 155 insertions(+), 146 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 6d486592..a7d1b1fd 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -20,7 +20,6 @@ import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { IAttributeData, IDisposable } from 'common/Types'; import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService } from 'common/services/Services'; -import { ISelectionService } from 'browser/services/Services'; /** * Map collect to glevel. Used in `selectCharset`. @@ -118,8 +117,6 @@ export class InputHandler extends Disposable implements IInputHandler { private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); - private _selectionService: ISelectionService | undefined; - private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter(); @@ -310,11 +307,6 @@ export class InputHandler extends Disposable implements IInputHandler { super.dispose(); } - // TODO: When InputHandler moves into common, browser dependencies need to move out - public setBrowserServices(selectionService: ISelectionService): void { - this._selectionService = selectionService; - } - public parse(data: string): void { let buffer = this._bufferService.buffer; const cursorStartX = buffer.x; @@ -1277,28 +1269,20 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. + this._coreMouseService.activeProtocol = 'X10'; + break; case 1000: // vt200 mouse // no motion. // no modifiers, except control on the wheel. + this._coreMouseService.activeProtocol = 'VT200'; + break; case 1002: // button event mouse + this._coreMouseService.activeProtocol = 'DRAG'; + break; case 1003: // any event mouse // any event - sends motion events, // even if there is no button held down. - - // TODO: Why are params[0] compares nested within a switch for params[0]? - this._coreMouseService.activeProtocol = param === 9 ? 'X10' : param === 1000 ? 'VT200' : param === 1002 ? 'DRAG' : 'ANY'; - - this._terminal.x10Mouse = param === 9; - this._terminal.vt200Mouse = param === 1000; - this._terminal.normalMouse = param > 1000; - this._terminal.mouseEvents = true; - if (this._terminal.element) { - this._terminal.element.classList.add('enable-mouse-events'); - } - if (this._selectionService) { - this._selectionService.disable(); - } - this._logService.info('Binding to mouse events.'); + this._coreMouseService.activeProtocol = 'ANY'; break; case 1004: // send focusin/focusout events // focusin: ^[[I @@ -1306,13 +1290,11 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.sendFocus = true; break; case 1005: // utf8 ext mode mouse - this._terminal.utfMouse = true; this._coreMouseService.activeEncoding = 'UTF8'; // for wide terminals // simply encodes large values as utf8 characters break; case 1006: // sgr ext mode mouse - this._terminal.sgrMouse = true; this._coreMouseService.activeEncoding = 'SGR'; // for wide terminals // does not add 32 to fields @@ -1320,7 +1302,6 @@ export class InputHandler extends Disposable implements IInputHandler { // release: ^[[ev).overrideType || ev.type) { + case 'mousemove': + action = 'move'; + if (ev.buttons !== undefined) { + but = ev.buttons & 1 ? 'left' : ev.buttons & 2 ? 'right' : ev.buttons & 4 ? 'middle' : 'none'; + } else { + but = 'none'; + } + break; case 'mouseup': action = 'up'; code = ev.button !== null && ev.button !== undefined @@ -758,7 +755,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp action = (ev).deltaY < 0 ? 'up' : 'down'; break; } - self._coreMouseService.triggerMouseEvent({ + return self._coreMouseService.triggerMouseEvent({ col: pos.x - 33, // FIXME: why -33 here? row: pos.y - 33, button: but, @@ -767,95 +764,147 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp alt: ev.altKey, shift: ev.shiftKey }); - return; } - function sendMove(ev: MouseEvent): void { - const pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); - if (!pos) return; - - let but: ICoreMouseEvent['button'] = 'none'; - if (ev.buttons !== undefined) { - but = ev.buttons & 1 ? 'left' : ev.buttons & 2 ? 'right' : ev.buttons & 4 ? 'middle' : 'none'; + /** + * Event handler state handling. + * We listen to the onProtocolChange event of CoreMouseService and put + * requested handlers in `requestedEvents`. With this the handlers have + * all bits to do the event handler juggling. + * Note: 'mousedown' currently is an "always on" handler and not managed + * by onProtocolChange. + */ + const requestedEvents: {[key: string]: any} = { + mouseup: null, + wheel: null, + mousedrag: null, + mousemove: null + }; + const eventHandlers: {[key: string]: any} = { + mouseup: (ev: MouseEvent) => { + sendEvent(ev); + if (!ev.buttons) { + // if no other button is held remove global handlers + this._document.removeEventListener('mouseup', requestedEvents.mouseup); + if (requestedEvents.mousedrag) { + this._document.removeEventListener('mousemove', requestedEvents.mousedrag); + } + } + return this.cancel(ev); + }, + wheel: (ev: WheelEvent) => { + sendEvent(ev); + ev.preventDefault(); + return this.cancel(ev); + }, + mousedrag: (ev: MouseEvent) => { + // deal only with move while a button is held + if (ev.buttons) { + sendEvent(ev); + } + }, + mousemove: (ev: MouseEvent) => { + // deal only with move eithout any button + if (!ev.buttons) { + sendEvent(ev); + } + } + }; + this._coreMouseService.onProtocolChange(events => { + // apply global changes on events + if (events.length) { + this._logService.info('Binding to mouse events:', events); + this.mouseEvents = events; + if (this.element) { + this.element.classList.add('enable-mouse-events'); + } + if (this._selectionService) { + this._selectionService.disable(); + } + } else { + this._logService.info('Unbinding from mouse events.'); + this.mouseEvents = events; + if (this.element) { + this.element.classList.remove('enable-mouse-events'); + } + if (this._selectionService) { + this._selectionService.enable(); + } } - self._coreMouseService.triggerMouseEvent({ - col: pos.x - 33, - row: pos.y - 33, - button: but, - action: 'move', - ctrl: ev.ctrlKey, - alt: ev.altKey, - shift: ev.shiftKey - }); - return; - } + if (events.indexOf('mousemove') === -1) { + el.removeEventListener('mousemove', requestedEvents.mousemove); + requestedEvents.mousemove = null; + } else if (!requestedEvents.mousemove) { + el.addEventListener('mousemove', eventHandlers.mousemove); + requestedEvents.mousemove = eventHandlers.mousemove; + } + if (events.indexOf('wheel') === -1) { + el.removeEventListener('wheel', requestedEvents.wheel); + requestedEvents.wheel = null; + } else if (!requestedEvents.wheel) { + el.addEventListener('wheel', eventHandlers.wheel); + requestedEvents.wheel = eventHandlers.wheel; + } + if (events.indexOf('mouseup') === -1) { + // always remove possible leftover handler + this._document.removeEventListener('mouseup', requestedEvents.mouseup); + requestedEvents.mouseup = null; + } else if (!requestedEvents.mouseup) { + requestedEvents.mouseup = eventHandlers.mouseup; + } + + if (events.indexOf('mousedrag') === -1) { + // always remove possible leftover handler + this._document.removeEventListener('mousemove', requestedEvents.mousedrag); + requestedEvents.mousedrag = null; + } else if (!requestedEvents.mousedrag) { + requestedEvents.mousedrag = eventHandlers.mousedrag; + } + }); + // force initial onProtocolChange so we dont miss early mouse requests + this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol; + + // DEBUG: get rid of annoying popup during testing + this.register(addDisposableDomListener(el, 'contextmenu', (e: any) => { + e.preventDefault(); + return false; + })); + + /** + * "Always on" event handlers. + */ this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => { - - // Prevent the focus on the textarea from getting lost - // and make sure we get focused on mousedown ev.preventDefault(); this.focus(); // Don't send the mouse button to the pty if mouse events are disabled or // if the selection manager is having selection forced (ie. a modifier is // held). - if (!this.mouseEvents || this._selectionService.shouldForceSelection(ev)) { + if (!this.mouseEvents.length || this._selectionService.shouldForceSelection(ev)) { return; } - // send the button - sendButton(ev); + sendEvent(ev); - // fix for odd bug - // if (this.vt200Mouse && !this.normalMouse) { - if (this.vt200Mouse) { - (ev).overrideType = 'mouseup'; - sendButton(ev); - return this.cancel(ev); + // Register additional global handlers which should keep reporting outside + // of the terminal element. + // Note: Other emulators also do this for 'mousedown' while a button + // is held, we currently limit 'mousedown' to the terminal only. + if (requestedEvents.mouseup) { + this._document.addEventListener('mouseup', requestedEvents.mouseup); } - - // TODO: All mouse handling should be pulled into its own file. - - // bind events - let moveHandler: (event: MouseEvent) => void; - if (this.normalMouse) { - moveHandler = (event: MouseEvent) => { - // Do nothing if normal mouse mode is on. This can happen if the mouse is held down when the - // terminal exits normalMouse mode. - if (!this.normalMouse) { - return; - } - sendMove(event); - }; - // TODO: these event listeners should be managed by the disposable, the Terminal reference may - // be kept aroud if Terminal.dispose is fired when the mouse is down - this._document.addEventListener('mousemove', moveHandler); + if (requestedEvents.mousedrag) { + this._document.addEventListener('mousemove', requestedEvents.mousedrag); } - // x10 compatibility mode can't send button releases - const handler = (ev: MouseEvent) => { - if (this.normalMouse && !this.x10Mouse) { - sendButton(ev); - } - if (moveHandler) { - // Even though this should only be attached when this.normalMouse is true, holding the - // mouse button down when normalMouse changes can happen. Just always try to remove it. - this._document.removeEventListener('mousemove', moveHandler); - moveHandler = null; - } - this._document.removeEventListener('mouseup', handler); - return this.cancel(ev); - }; - this._document.addEventListener('mouseup', handler); - return this.cancel(ev); })); this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => { - if (!this.mouseEvents) { + if (!requestedEvents.wheel) { // Convert wheel events into up/down events when the buffer does not have scrollback, this // enables scrolling in apps hosted in the alt buffer such as vim or tmux. if (!this.buffer.hasScrollback) { @@ -876,32 +925,30 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } return; } - if (this.x10Mouse || this._vt300Mouse || this._decLocator) return; - sendButton(ev); - ev.preventDefault(); })); // allow wheel scrolling in // the shell for example this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => { - if (this.mouseEvents) return; + if (requestedEvents.wheel) return; this.viewport.onWheel(ev); return this.cancel(ev); })); this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => { - if (this.mouseEvents) return; + if (this.mouseEvents.length) return; this.viewport.onTouchStart(ev); return this.cancel(ev); })); this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => { - if (this.mouseEvents) return; + if (this.mouseEvents.length) return; this.viewport.onTouchMove(ev); return this.cancel(ev); })); } + /** * Tells the renderer to refresh terminal content between two rows (inclusive) at the next * opportunity. diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 0ee948d5..5805ba3d 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -6,7 +6,7 @@ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; @@ -210,7 +210,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { x10Mouse: boolean; vt200Mouse: boolean; normalMouse: boolean; - mouseEvents: boolean; + mouseEvents: CoreMouseEventType[]; sendFocus: boolean; utfMouse: boolean; sgrMouse: boolean; diff --git a/src/Types.d.ts b/src/Types.d.ts index f96ba07d..80e8848c 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -4,7 +4,7 @@ */ import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm'; -import { ICharset, IAttributeData, CharData } from 'common/Types'; +import { ICharset, IAttributeData, CharData, CoreMouseEventType } from 'common/Types'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; @@ -36,14 +36,8 @@ export interface IInputHandlingTerminal { bracketedPasteMode: boolean; curAttrData: IAttributeData; savedCols: number; - x10Mouse: boolean; - vt200Mouse: boolean; - normalMouse: boolean; - mouseEvents: boolean; + mouseEvents: CoreMouseEventType[]; sendFocus: boolean; - utfMouse: boolean; - sgrMouse: boolean; - urxvtMouse: boolean; cursorHidden: boolean; buffers: IBufferSet; diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts index b4131f00..4f4392f1 100644 --- a/src/common/services/CoreMouseService.test.ts +++ b/src/common/services/CoreMouseService.test.ts @@ -67,7 +67,7 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'NONE'; assert.deepEqual(wantedEvents, [[]]); cms.activeProtocol = 'ANY'; - assert.deepEqual(wantedEvents, [[], ['mousedown', 'mouseup', 'wheel', 'mousemove']]); + assert.deepEqual(wantedEvents, [[], ['mousedown', 'mouseup', 'wheel', 'mousedrag', 'mousemove']]); }); describe('triggerMouseEvent', () => { let cms: CoreMouseService; diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index cddc058d..bafb9b12 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -78,7 +78,7 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: CTRL | ALT | SHIFT */ ANY: { - events: ['mousedown', 'mouseup', 'wheel', 'mousemove'], + events: ['mousedown', 'mouseup', 'wheel', 'mousedrag', 'mousemove'], restrict: (e: ICoreMouseEvent) => true } }; From b64ed4f540edf8e5eeb1df38297cdae4af6ee053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 19 Jul 2019 17:04:12 +0200 Subject: [PATCH 005/338] cleanup sendEvent, switch reported button during move --- src/Terminal.ts | 58 ++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 0c72dad0..88712677 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -713,46 +713,41 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // get mouse coordinates pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); - if (!pos) return; + if (!pos) { + return false; + } let but: ICoreMouseEvent['button']; let action: ICoreMouseEvent['action']; - let code: number; - // FIXME: cleanup the switch mess switch ((ev).overrideType || ev.type) { case 'mousemove': action = 'move'; - if (ev.buttons !== undefined) { - but = ev.buttons & 1 ? 'left' : ev.buttons & 2 ? 'right' : ev.buttons & 4 ? 'middle' : 'none'; - } else { + but = 'none'; + if (!ev.buttons) { but = 'none'; + // buttons is not supported on macOS, try to get a value from button instead + if (ev.button !== undefined) { + but = ev.button === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; + } + } else { + but = ev.buttons & 1 ? 'left' : ev.buttons & 4 ? 'middle' : ev.buttons & 2 ? 'right' : 'none'; } break; case 'mouseup': action = 'up'; - code = ev.button !== null && ev.button !== undefined - ? +ev.button - : ev.which !== null && ev.which !== undefined - ? ev.which - 1 - : null; - but = code === 0 ? 'left' : code === 1 ? 'middle' : 'right'; + but = ev.button === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; break; case 'mousedown': action = 'down'; - code = ev.button !== null && ev.button !== undefined - ? +ev.button - : ev.which !== null && ev.which !== undefined - ? ev.which - 1 - : null; - but = code === 0 ? 'left' : code === 1 ? 'middle' : 'right'; + but = ev.button === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; break; case 'DOMMouseScroll': - but = 'wheel'; action = ev.detail < 0 ? 'up' : 'down'; + but = 'wheel'; break; case 'wheel': - but = 'wheel'; action = (ev).deltaY < 0 ? 'up' : 'down'; + but = 'wheel'; break; } return self._coreMouseService.triggerMouseEvent({ @@ -804,7 +799,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } }, mousemove: (ev: MouseEvent) => { - // deal only with move eithout any button + // deal only with move without any button if (!ev.buttons) { sendEvent(ev); } @@ -812,26 +807,19 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp }; this._coreMouseService.onProtocolChange(events => { // apply global changes on events + this.mouseEvents = events; if (events.length) { this._logService.info('Binding to mouse events:', events); - this.mouseEvents = events; - if (this.element) { - this.element.classList.add('enable-mouse-events'); - } - if (this._selectionService) { - this._selectionService.disable(); - } + this.element.classList.add('enable-mouse-events'); + this._selectionService.disable(); } else { this._logService.info('Unbinding from mouse events.'); - this.mouseEvents = events; - if (this.element) { - this.element.classList.remove('enable-mouse-events'); - } - if (this._selectionService) { - this._selectionService.enable(); - } + this.element.classList.remove('enable-mouse-events'); + this._selectionService.enable(); } + // add/remove handlers from requestedEvents + if (events.indexOf('mousemove') === -1) { el.removeEventListener('mousemove', requestedEvents.mousemove); requestedEvents.mousemove = null; From f239e10ed67ca4b139239ead3a217ad5450d4952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 19 Jul 2019 23:55:18 +0200 Subject: [PATCH 006/338] use enums instead --- src/Terminal.ts | 94 ++++++------- src/TestUtils.test.ts | 2 +- src/Types.d.ts | 2 +- src/common/TestUtils.test.ts | 2 +- src/common/Types.d.ts | 40 ++++-- src/common/services/CoreMouseService.test.ts | 139 ++++++++++--------- src/common/services/CoreMouseService.ts | 77 +++++----- src/common/services/Services.ts | 2 +- 8 files changed, 183 insertions(+), 175 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 88712677..230e7a92 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -40,7 +40,7 @@ import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -142,7 +142,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public charsets: ICharset[]; // mouse properties - public mouseEvents: CoreMouseEventType[] = []; + public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; public sendFocus: boolean; // misc @@ -650,8 +650,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.linkifier.attachToDom(this.element, this._mouseZoneManager); // apply mouse event classes set by escape codes before terminal was attached - this.element.classList.toggle('enable-mouse-events', !!this.mouseEvents.length); - if (this.mouseEvents.length) { + this.element.classList.toggle('enable-mouse-events', !!this.mouseEvents); + if (this.mouseEvents) { this._selectionService.disable(); } else { this._selectionService.enable(); @@ -703,11 +703,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } } - + // TODO: Move mouse event code into its own file. public bindMouse(): void { const self = this; const el = this.element; + // send event to CoreMouseService function sendEvent(ev: MouseEvent | WheelEvent): boolean { let pos; @@ -717,38 +718,43 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return false; } - let but: ICoreMouseEvent['button']; - let action: ICoreMouseEvent['action']; + let but: CoreMouseButton; + let action: CoreMouseAction; switch ((ev).overrideType || ev.type) { case 'mousemove': - action = 'move'; - but = 'none'; - if (!ev.buttons) { - but = 'none'; + action = CoreMouseAction.MOVE; + if (ev.buttons === undefined) { // buttons is not supported on macOS, try to get a value from button instead + but = CoreMouseButton.NONE; if (ev.button !== undefined) { - but = ev.button === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; } } else { - but = ev.buttons & 1 ? 'left' : ev.buttons & 4 ? 'middle' : ev.buttons & 2 ? 'right' : 'none'; + but = ev.buttons & 1 ? CoreMouseButton.LEFT : + ev.buttons & 4 ? CoreMouseButton.MIDDLE : + ev.buttons & 2 ? CoreMouseButton.RIGHT : + CoreMouseButton.NONE; } break; case 'mouseup': - action = 'up'; - but = ev.button === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; + action = CoreMouseAction.UP; + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; break; case 'mousedown': - action = 'down'; - but = ev.button === 0 ? 'left' : ev.button === 1 ? 'middle' : ev.button === 2 ? 'right' : 'none'; + action = CoreMouseAction.DOWN; + but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; break; case 'DOMMouseScroll': - action = ev.detail < 0 ? 'up' : 'down'; - but = 'wheel'; + action = ev.detail < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN; + but = CoreMouseButton.WHEEL; break; case 'wheel': - action = (ev).deltaY < 0 ? 'up' : 'down'; - but = 'wheel'; + action = (ev).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN; + but = CoreMouseButton.WHEEL; break; + default: + // dont handle other event types by accident + return false; } return self._coreMouseService.triggerMouseEvent({ col: pos.x - 33, // FIXME: why -33 here? @@ -762,11 +768,11 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } /** - * Event handler state handling. + * Event listener state handling. * We listen to the onProtocolChange event of CoreMouseService and put - * requested handlers in `requestedEvents`. With this the handlers have - * all bits to do the event handler juggling. - * Note: 'mousedown' currently is an "always on" handler and not managed + * requested listeners in `requestedEvents`. With this the listeners + * have all bits to do the event listener juggling. + * Note: 'mousedown' currently is "always on" and not managed * by onProtocolChange. */ const requestedEvents: {[key: string]: any} = { @@ -775,7 +781,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp mousedrag: null, mousemove: null }; - const eventHandlers: {[key: string]: any} = { + const eventListeners: {[key: string]: any} = { mouseup: (ev: MouseEvent) => { sendEvent(ev); if (!ev.buttons) { @@ -808,7 +814,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._coreMouseService.onProtocolChange(events => { // apply global changes on events this.mouseEvents = events; - if (events.length) { + if (events) { this._logService.info('Binding to mouse events:', events); this.element.classList.add('enable-mouse-events'); this._selectionService.disable(); @@ -820,49 +826,43 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // add/remove handlers from requestedEvents - if (events.indexOf('mousemove') === -1) { + if (!(events & CoreMouseEventType.MOVE)) { el.removeEventListener('mousemove', requestedEvents.mousemove); requestedEvents.mousemove = null; } else if (!requestedEvents.mousemove) { - el.addEventListener('mousemove', eventHandlers.mousemove); - requestedEvents.mousemove = eventHandlers.mousemove; + el.addEventListener('mousemove', eventListeners.mousemove); + requestedEvents.mousemove = eventListeners.mousemove; } - if (events.indexOf('wheel') === -1) { + if (!(events & CoreMouseEventType.WHEEL)) { el.removeEventListener('wheel', requestedEvents.wheel); requestedEvents.wheel = null; } else if (!requestedEvents.wheel) { - el.addEventListener('wheel', eventHandlers.wheel); - requestedEvents.wheel = eventHandlers.wheel; + el.addEventListener('wheel', eventListeners.wheel); + requestedEvents.wheel = eventListeners.wheel; } - if (events.indexOf('mouseup') === -1) { + if (!(events & CoreMouseEventType.UP)) { // always remove possible leftover handler this._document.removeEventListener('mouseup', requestedEvents.mouseup); requestedEvents.mouseup = null; } else if (!requestedEvents.mouseup) { - requestedEvents.mouseup = eventHandlers.mouseup; + requestedEvents.mouseup = eventListeners.mouseup; } - if (events.indexOf('mousedrag') === -1) { + if (!(events & CoreMouseEventType.DRAG)) { // always remove possible leftover handler this._document.removeEventListener('mousemove', requestedEvents.mousedrag); requestedEvents.mousedrag = null; } else if (!requestedEvents.mousedrag) { - requestedEvents.mousedrag = eventHandlers.mousedrag; + requestedEvents.mousedrag = eventListeners.mousedrag; } }); // force initial onProtocolChange so we dont miss early mouse requests this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol; - // DEBUG: get rid of annoying popup during testing - this.register(addDisposableDomListener(el, 'contextmenu', (e: any) => { - e.preventDefault(); - return false; - })); - /** - * "Always on" event handlers. + * "Always on" event listeners. */ this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => { ev.preventDefault(); @@ -871,7 +871,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Don't send the mouse button to the pty if mouse events are disabled or // if the selection manager is having selection forced (ie. a modifier is // held). - if (!this.mouseEvents.length || this._selectionService.shouldForceSelection(ev)) { + if (!this.mouseEvents || this._selectionService.shouldForceSelection(ev)) { return; } @@ -924,13 +924,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp })); this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => { - if (this.mouseEvents.length) return; + if (this.mouseEvents) return; this.viewport.onTouchStart(ev); return this.cancel(ev); })); this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => { - if (this.mouseEvents.length) return; + if (this.mouseEvents) return; this.viewport.onTouchMove(ev); return this.cancel(ev); })); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 5805ba3d..f719aa88 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -210,7 +210,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { x10Mouse: boolean; vt200Mouse: boolean; normalMouse: boolean; - mouseEvents: CoreMouseEventType[]; + mouseEvents: CoreMouseEventType; sendFocus: boolean; utfMouse: boolean; sgrMouse: boolean; diff --git a/src/Types.d.ts b/src/Types.d.ts index 80e8848c..d74d29b9 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -36,7 +36,7 @@ export interface IInputHandlingTerminal { bracketedPasteMode: boolean; curAttrData: IAttributeData; savedCols: number; - mouseEvents: CoreMouseEventType[]; + mouseEvents: CoreMouseEventType; sendFocus: boolean; cursorHidden: boolean; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index bbeb4de8..62b5402d 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -36,7 +36,7 @@ export class MockCoreMouseService implements ICoreMouseService { addProtocol(name: string): void {} reset(): void {} triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } - onProtocolChange: IEvent = new EventEmitter().event; + onProtocolChange: IEvent = new EventEmitter().event; } export class MockCoreService implements ICoreService { diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 598a6a9e..7f86b7ec 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -162,6 +162,20 @@ export interface IRowRange { /** * Interface for mouse events in the core. */ +export const enum CoreMouseButton { + LEFT = 0, + MIDDLE = 1, + RIGHT = 2, + NONE = 3, + WHEEL = 64 +} + +export const enum CoreMouseAction { + UP = 0, + DOWN = 1, + MOVE = 32 +} + export interface ICoreMouseEvent { /** column (zero based). */ col: number; @@ -174,8 +188,8 @@ export interface ICoreMouseEvent { * There are invalid combinations of buttons and actions possible * (like move + wheel), those are silently ignored by the CoreMouseService. */ - button: 'left' | 'middle' | 'right' | 'wheel' | 'none'; - action: 'up' | 'down' | 'move'; + button: CoreMouseButton; + action: CoreMouseAction; /** * Modifier states. * Protocols will add/ignore those based on specific restrictions. @@ -190,14 +204,20 @@ export interface ICoreMouseEvent { * To be reported to the browser component which events a mouse * protocol wants to be catched and forwarded as an ICoreMouseEvent * to CoreMouseService. - * Known types: - * - mousedown: any mousedown event - * - mouseup: any mouseup event - * - wheel: any wheel event - * - mousedrag: any mousemove event while a button is pressed - * - mousemove: any mousemove event */ -export type CoreMouseEventType = 'mousedown' | 'mouseup' | 'wheel' | 'mousemove' | 'mousedrag'; +export const enum CoreMouseEventType { + NONE = 0, + /** any mousedown event */ + DOWN = 1, + /** any mouseup event */ + UP = 2, + /** any mousemove event while a button is held */ + DRAG = 4, + /** any mousemove event without a button */ + MOVE = 8, + /** any wheel event */ + WHEEL = 16 +} /** * Mouse protocol interface. @@ -208,7 +228,7 @@ export type CoreMouseEventType = 'mousedown' | 'mouseup' | 'wheel' | 'mousemove' * modifiers or filtering invalid event types. */ export interface ICoreMouseProtocol { - events: CoreMouseEventType[]; + events: CoreMouseEventType; restrict: (e: ICoreMouseEvent) => boolean; } diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts index 4f4392f1..63f39f5d 100644 --- a/src/common/services/CoreMouseService.test.ts +++ b/src/common/services/CoreMouseService.test.ts @@ -5,7 +5,7 @@ import { CoreMouseService } from 'common/services/CoreMouseService'; import { MockCoreService, MockBufferService } from 'common/TestUtils.test'; import { assert } from 'chai'; -import { ICoreMouseEvent, CoreMouseEventType } from 'common/Types'; +import { ICoreMouseEvent, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; // needed mock services const bufferService = new MockBufferService(300, 100); @@ -56,18 +56,21 @@ describe('CoreMouseService', () => { }); it('addProtocol', () => { const cms = new CoreMouseService(bufferService, coreService); - cms.addProtocol('XYZ', { events: [], restrict: (e: ICoreMouseEvent) => false }); + cms.addProtocol('XYZ', { events: CoreMouseEventType.NONE, restrict: (e: ICoreMouseEvent) => false }); cms.activeProtocol = 'XYZ'; assert.equal(cms.activeProtocol, 'XYZ'); }); it('onProtocolChange', () => { const cms = new CoreMouseService(bufferService, coreService); - const wantedEvents: CoreMouseEventType[][] = []; + const wantedEvents: CoreMouseEventType[] = []; cms.onProtocolChange(events => wantedEvents.push(events)); cms.activeProtocol = 'NONE'; - assert.deepEqual(wantedEvents, [[]]); + assert.deepEqual(wantedEvents, [CoreMouseEventType.NONE]); cms.activeProtocol = 'ANY'; - assert.deepEqual(wantedEvents, [[], ['mousedown', 'mouseup', 'wheel', 'mousedrag', 'mousemove']]); + assert.deepEqual(wantedEvents, [ + CoreMouseEventType.NONE, + CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE + ]); }); describe('triggerMouseEvent', () => { let cms: CoreMouseService; @@ -78,68 +81,68 @@ describe('CoreMouseService', () => { coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data); }); it('NONE', () => { - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false); }); it('X10', () => { cms.activeProtocol = 'X10'; - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false); }); it('VT200', () => { cms.activeProtocol = 'VT200'; - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), false); }); it('DRAG', () => { cms.activeProtocol = 'DRAG'; - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true); }); it('ANY', () => { cms.activeProtocol = 'ANY'; - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up' }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move' }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), true); // should not report in any case // invalid button + action combinations - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'move' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'up' }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.MOVE }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.UP }), false); // invalid coords - assert.equal(cms.triggerMouseEvent({ col: -1, row: 0, button: 'left', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 500, row: 0, button: 'left', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: -1, button: 'left', action: 'down' }), false); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 500, button: 'left', action: 'down' }), false); + assert.equal(cms.triggerMouseEvent({ col: -1, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 500, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: -1, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 500, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); }); describe('coords', () => { it('DEFAULT encoding', () => { cms.activeProtocol = 'ANY'; for (let i = 0; i < bufferService.cols; ++i) { - assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); // capped at 95 if (i < 95) { assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); @@ -152,7 +155,7 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; cms.activeEncoding = 'UTF8'; for (let i = 0; i < bufferService.cols; ++i) { - assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); } }); @@ -160,7 +163,7 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; cms.activeEncoding = 'SGR'; for (let i = 0; i < bufferService.cols; ++i) { - assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`); } }); @@ -168,7 +171,7 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; cms.activeEncoding = 'URXVT'; for (let i = 0; i < bufferService.cols; ++i) { - assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: 'left', action: 'down' }), true); + assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); assert.deepEqual(reports.pop(), `\x1b[32;${i + 1};1M`); } }); @@ -177,36 +180,36 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; cms.activeEncoding = 'DEFAULT'; // all buttons + down + no modifer - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'down', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'down', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'down', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'down', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.DOWN, ctrl: false, alt: false, shift: false }), true); assert.deepEqual(reports, ['\x1b[M !!', '\x1b[M!!!', '\x1b[M"!!', '\x1b[Ma!!']); while (reports.pop()) { } // all buttons + up + no modifier - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'up', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'up', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'up', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'wheel', action: 'up', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP, ctrl: false, alt: false, shift: false }), true); assert.deepEqual(reports, ['\x1b[M#!!', '\x1b[M#!!', '\x1b[M#!!', '\x1b[M`!!']); while (reports.pop()) { } // all buttons + move + no modifier - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'left', action: 'move', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'middle', action: 'move', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'right', action: 'move', ctrl: false, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.MIDDLE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: false }), true); assert.deepEqual(reports, ['\x1b[M@!!', '\x1b[MA!!', '\x1b[MB!!', '\x1b[MC!!']); while (reports.pop()) { } // button none + move + modifiers - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: true, alt: false, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: true, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: false, shift: true }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: true, alt: true, shift: false }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: false, alt: true, shift: true }), true); - assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: 'none', action: 'move', ctrl: true, alt: true, shift: true }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: false, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: false, shift: true }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: false }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: false, alt: true, shift: true }), true); + assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE, ctrl: true, alt: true, shift: true }), true); assert.deepEqual(reports, ['\x1b[MS!!', '\x1b[MK!!', '\x1b[MG!!', '\x1b[M[!!', '\x1b[MO!!', '\x1b[M_!!']); while (reports.pop()) { } }); diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index bafb9b12..22a56553 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -4,19 +4,19 @@ */ import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType } from 'common/Types'; +import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; /** * Supported default protocols. */ -const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { +const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = { /** * NONE * Events: none * Modifiers: none */ NONE: { - events: [], + events: CoreMouseEventType.NONE, restrict: () => false }, /** @@ -25,10 +25,10 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: none (TBD) */ X10: { - events: ['mousedown'], + events: CoreMouseEventType.DOWN, restrict: (e: ICoreMouseEvent) => { // no wheel (TBD), no move, no up - if (e.button === 'wheel' || e.action !== 'down') { + if (e.button === CoreMouseButton.WHEEL || e.action !== CoreMouseAction.DOWN) { return false; } // no modifiers (TDB) @@ -44,10 +44,10 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: CTRL (TBD) */ VT200: { - events: ['mousedown', 'mouseup', 'wheel'], + events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL, restrict: (e: ICoreMouseEvent) => { // no move - if (e.action === 'move') { + if (e.action === CoreMouseAction.MOVE) { return false; } // modifiers - only ctrl? @@ -62,10 +62,10 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: CTRL | ALT | SHIFT */ DRAG: { - events: ['mousedown', 'mouseup', 'wheel', 'mousedrag'], + events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG, restrict: (e: ICoreMouseEvent) => { // no move without button - if (e.action === 'move' && e.button === 'none') { + if (e.action === CoreMouseAction.MOVE && e.button === CoreMouseButton.NONE) { return false; } // modifiers unclear - let all pass for now @@ -78,47 +78,32 @@ const DEFAULT_PROCOTOLS: {[key: string]: ICoreMouseProtocol} = { * Modifiers: CTRL | ALT | SHIFT */ ANY: { - events: ['mousedown', 'mouseup', 'wheel', 'mousedrag', 'mousemove'], + events: + CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL + | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE, restrict: (e: ICoreMouseEvent) => true } }; -/** - * Mapping of buttons and actions to event codes. (taken from xterm spec) - * More than 3 buttons are not supported. - */ -enum CODEMAP { - // buttons - left = 0, - middle = 1, - right = 2, - none = 3, - wheel = 64, - // actions - up = 0, - down = 1, - move = 32, - // modifiers - shift = 4, - alt = 8, - ctrl = 16 +const enum Modifiers { + SHIFT = 4, + ALT = 8, + CTRL = 16 } // helper for default encoders to generate the event code. function eventCode(e: ICoreMouseEvent, isSGR: boolean): number { - const button = CODEMAP[e.button]; - const action = CODEMAP[e.action]; - const modifier = (e.ctrl ? CODEMAP.ctrl : 0) | (e.shift ? CODEMAP.shift : 0) | (e.alt ? CODEMAP.alt : 0); - let code = button | modifier; - if (e.button === 'wheel') { - code |= action; + const modifier = (e.ctrl ? Modifiers.CTRL : 0) | (e.shift ? Modifiers.SHIFT : 0) | (e.alt ? Modifiers.ALT : 0); + let code = e.button | modifier; + if (e.button === CoreMouseButton.WHEEL) { + code |= e.action; } else { - if (e.action === 'move') { - code |= CODEMAP.move; - } else if (e.action === 'up' && !isSGR) { + if (e.action === CoreMouseAction.MOVE) { + code |= CoreMouseAction.MOVE; + } else if (e.action === CoreMouseAction.UP && !isSGR) { // special case - only SGR can report button on release // all others have to go with NONE - code |= CODEMAP.none; + code |= CoreMouseButton.NONE; } } return code; @@ -161,7 +146,7 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = { * Can report button on release and works with a well formed sequence. */ SGR: (e: ICoreMouseEvent) => { - const final = (e.action === 'up' && e.button !== 'wheel') ? 'm' : 'M'; + const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M'; return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`; }, /** @@ -195,7 +180,7 @@ export class CoreMouseService implements ICoreMouseService { private _encodings: {[name: string]: CoreMouseEncoding} = {}; private _activeProtocol: string = ''; private _activeEncoding: string = ''; - private _onProtocolChange = new EventEmitter(); + private _onProtocolChange = new EventEmitter(); private _lastEvent: ICoreMouseEvent | null = null; constructor( @@ -203,7 +188,7 @@ export class CoreMouseService implements ICoreMouseService { @ICoreService private readonly _coreService: ICoreService ) { // register default protocols and encodings - Object.keys(DEFAULT_PROCOTOLS).forEach(name => this.addProtocol(name, DEFAULT_PROCOTOLS[name])); + Object.keys(DEFAULT_PROTOCOLS).forEach(name => this.addProtocol(name, DEFAULT_PROTOCOLS[name])); Object.keys(DEFAULT_ENCODINGS).forEach(name => this.addEncoding(name, DEFAULT_ENCODINGS[name])); // call reset to set defaults this.reset(); @@ -249,7 +234,7 @@ export class CoreMouseService implements ICoreMouseService { /** * Event to announce changes in mouse tracking. */ - public get onProtocolChange(): IEvent { + public get onProtocolChange(): IEvent { return this._onProtocolChange.event; } @@ -271,10 +256,10 @@ export class CoreMouseService implements ICoreMouseService { } // filter nonsense combinations of button + action - if (event.button === 'wheel' && event.action === 'move') { + if (event.button === CoreMouseButton.WHEEL && event.action === CoreMouseAction.MOVE) { return false; } - if (event.button === 'none' && event.action !== 'move') { + if (event.button === CoreMouseButton.NONE && event.action !== CoreMouseAction.MOVE) { return false; } @@ -283,7 +268,7 @@ export class CoreMouseService implements ICoreMouseService { event.row++; // debounce move at grid level - if (event.action === 'move' && this._lastEvent && this._compareEvents(this._lastEvent, event)) { + if (event.action === CoreMouseAction.MOVE && this._lastEvent && this._compareEvents(this._lastEvent, event)) { return false; } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 8c3a7eb8..61414656 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -46,7 +46,7 @@ export interface ICoreMouseService { /** * Event to announce changes in mouse tracking. */ - onProtocolChange: IEvent; + onProtocolChange: IEvent; } export const ICoreService = createDecorator('CoreService'); From 3e5b075a0ac846cc2a35a414d03bc6de95f522ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 22 Jul 2019 16:32:24 +0200 Subject: [PATCH 007/338] remove modifier restriction for 1000 --- src/common/services/CoreMouseService.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 22a56553..659a80ff 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -22,16 +22,16 @@ const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = { /** * X10 * Events: mousedown - * Modifiers: none (TBD) + * Modifiers: none */ X10: { events: CoreMouseEventType.DOWN, restrict: (e: ICoreMouseEvent) => { - // no wheel (TBD), no move, no up + // no wheel, no move, no up if (e.button === CoreMouseButton.WHEEL || e.action !== CoreMouseAction.DOWN) { return false; } - // no modifiers (TDB) + // no modifiers e.ctrl = false; e.alt = false; e.shift = false; @@ -41,7 +41,7 @@ const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = { /** * VT200 * Events: mousedown / mouseup / wheel - * Modifiers: CTRL (TBD) + * Modifiers: all */ VT200: { events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL, @@ -50,16 +50,13 @@ const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = { if (e.action === CoreMouseAction.MOVE) { return false; } - // modifiers - only ctrl? - e.alt = false; - e.shift = false; return true; } }, /** * DRAG * Events: mousedown / mouseup / wheel / mousedrag - * Modifiers: CTRL | ALT | SHIFT + * Modifiers: all */ DRAG: { events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG, @@ -68,14 +65,13 @@ const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = { if (e.action === CoreMouseAction.MOVE && e.button === CoreMouseButton.NONE) { return false; } - // modifiers unclear - let all pass for now return true; } }, /** * ANY * Events: all mouse related events - * Modifiers: CTRL | ALT | SHIFT + * Modifiers: all */ ANY: { events: From 776c6dffea382bdace43919faf8fc8707e4ec826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 23 Jul 2019 20:16:42 +0200 Subject: [PATCH 008/338] refactor buttons, wheel with left right --- src/Terminal.ts | 27 ++++++++++----- src/common/Types.d.ts | 20 ++++++++--- src/common/services/CoreMouseService.test.ts | 1 + src/common/services/CoreMouseService.ts | 36 +++++++++++++------- 4 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index e19a4516..92d904e9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -734,35 +734,44 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // buttons is not supported on macOS, try to get a value from button instead but = CoreMouseButton.NONE; if (ev.button !== undefined) { - but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; + but = ev.button < 3 ? ev.button : undefined; } } else { but = ev.buttons & 1 ? CoreMouseButton.LEFT : ev.buttons & 4 ? CoreMouseButton.MIDDLE : ev.buttons & 2 ? CoreMouseButton.RIGHT : - CoreMouseButton.NONE; + CoreMouseButton.NONE; // fallback to NONE } break; case 'mouseup': action = CoreMouseAction.UP; - but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; + // AUX buttons are currently not supported + but = ev.button < 3 ? ev.button : undefined; break; case 'mousedown': action = CoreMouseAction.DOWN; - but = ev.button < 3 ? ev.button : CoreMouseButton.NONE; - break; - case 'DOMMouseScroll': - action = ev.detail < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN; - but = CoreMouseButton.WHEEL; + but = ev.button < 3 ? ev.button : undefined; break; case 'wheel': - action = (ev).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN; + // precedence: up/down over left/right + // FIXME: Can we have both in one event? Need to send another wheel event? + if ((ev as WheelEvent).deltaY !== 0) { + action = (ev as WheelEvent).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN; + } else if ((ev as WheelEvent).deltaX !== 0) { + action = (ev as WheelEvent).deltaX < 0 ? CoreMouseAction.LEFT : CoreMouseAction.RIGHT; + } but = CoreMouseButton.WHEEL; break; default: // dont handle other event types by accident return false; } + + // exit if we cannot determine button/action values + if (action === undefined || but === undefined) { + return false; + } + return self._coreMouseService.triggerMouseEvent({ col: pos.x - 33, // FIXME: why -33 here? row: pos.y - 33, diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 7f86b7ec..2f742038 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -167,13 +167,25 @@ export const enum CoreMouseButton { MIDDLE = 1, RIGHT = 2, NONE = 3, - WHEEL = 64 + WHEEL = 4, + // additional buttons 1..8 + // untested! + AUX1 = 8, + AUX2 = 9, + AUX3 = 10, + AUX4 = 11, + AUX5 = 12, + AUX6 = 13, + AUX7 = 14, + AUX8 = 15 } export const enum CoreMouseAction { - UP = 0, - DOWN = 1, - MOVE = 32 + UP = 0, // buttons, wheel + DOWN = 1, // buttons, wheel + LEFT = 2, // wheel only + RIGHT = 3, // wheel only + MOVE = 32 // buttons only } export interface ICoreMouseEvent { diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts index 63f39f5d..2063a496 100644 --- a/src/common/services/CoreMouseService.test.ts +++ b/src/common/services/CoreMouseService.test.ts @@ -177,6 +177,7 @@ describe('CoreMouseService', () => { }); }); it('eventCodes with modifiers (DEFAULT encoding)', () => { + // TODO: implement AUX button tests cms.activeProtocol = 'ANY'; cms.activeEncoding = 'DEFAULT'; // all buttons + down + no modifer diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 659a80ff..957537e4 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -89,11 +89,18 @@ const enum Modifiers { // helper for default encoders to generate the event code. function eventCode(e: ICoreMouseEvent, isSGR: boolean): number { - const modifier = (e.ctrl ? Modifiers.CTRL : 0) | (e.shift ? Modifiers.SHIFT : 0) | (e.alt ? Modifiers.ALT : 0); - let code = e.button | modifier; + let code = (e.ctrl ? Modifiers.CTRL : 0) | (e.shift ? Modifiers.SHIFT : 0) | (e.alt ? Modifiers.ALT : 0); if (e.button === CoreMouseButton.WHEEL) { + code |= 64; code |= e.action; } else { + code |= e.button & 3; + if (e.button & 4) { + code |= 64; + } + if (e.button & 8) { + code |= 128; + } if (e.action === CoreMouseAction.MOVE) { code |= CoreMouseAction.MOVE; } else if (e.action === CoreMouseAction.UP && !isSGR) { @@ -244,40 +251,43 @@ export class CoreMouseService implements ICoreMouseService { * Note: The method will change values of the given event object * to fullfill protocol and encoding restrictions. */ - public triggerMouseEvent(event: ICoreMouseEvent): boolean { + public triggerMouseEvent(e: ICoreMouseEvent): boolean { // range check for col/row - if (event.col < 0 || event.col >= this._bufferService.cols - || event.row < 0 || event.row >= this._bufferService.rows) { + if (e.col < 0 || e.col >= this._bufferService.cols + || e.row < 0 || e.row >= this._bufferService.rows) { return false; } // filter nonsense combinations of button + action - if (event.button === CoreMouseButton.WHEEL && event.action === CoreMouseAction.MOVE) { + if (e.button === CoreMouseButton.WHEEL && e.action === CoreMouseAction.MOVE) { return false; } - if (event.button === CoreMouseButton.NONE && event.action !== CoreMouseAction.MOVE) { + if (e.button === CoreMouseButton.NONE && e.action !== CoreMouseAction.MOVE) { + return false; + } + if (e.button !== CoreMouseButton.WHEEL && (e.action === CoreMouseAction.LEFT || e.action === CoreMouseAction.RIGHT)) { return false; } // report 1-based coords - event.col++; - event.row++; + e.col++; + e.row++; // debounce move at grid level - if (event.action === CoreMouseAction.MOVE && this._lastEvent && this._compareEvents(this._lastEvent, event)) { + if (e.action === CoreMouseAction.MOVE && this._lastEvent && this._compareEvents(this._lastEvent, e)) { return false; } // apply protocol restrictions - if (!this._protocols[this._activeProtocol].restrict(event)) { + if (!this._protocols[this._activeProtocol].restrict(e)) { return false; } // encode report and send - const report = this._encodings[this._activeEncoding](event); + const report = this._encodings[this._activeEncoding](e); this._coreService.triggerDataEvent(report, true); - this._lastEvent = event; + this._lastEvent = e; return true; } From 4fba080d34dd7dc89293ec3c8e90147ee6e207c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 24 Jul 2019 15:14:37 +0200 Subject: [PATCH 009/338] deactivate wheel left/right, docs --- bin/test_mousemodes.js | 91 ++++++++++++++++++++++++++---------------- src/Terminal.ts | 34 +++++++++++----- 2 files changed, 80 insertions(+), 45 deletions(-) diff --git a/bin/test_mousemodes.js b/bin/test_mousemodes.js index 12ee8a0c..976a38e0 100644 --- a/bin/test_mousemodes.js +++ b/bin/test_mousemodes.js @@ -48,43 +48,66 @@ stdin.addListener('data', function(data) { } }); -// basic button codes (modifier keys are added on top) -const BUTTONS = { - 0: ['left', 'press'], - 1: ['middle', 'press'], - 2: ['right', 'press'], - 3: ['', 'release'], - 32: ['left', 'move'], - 33: ['middle', 'move'], - 34: ['right', 'move'], - 35: ['', 'move'], - 64: ['wheel', 'up'], - 65: ['wheel', 'down'] +// button definitions +const buttons = { + '': -1, + left: 0, + middle: 1, + right: 2, + released: 3, + wheelUp: 4, + wheelDown: 5, + wheelLeft: 6, + wheelRight: 7, + aux1: 8, + aux2: 9, + aux3: 10, + aux4: 11, + aux5: 12, + aux6: 13, + aux7: 14, + aux8: 15 }; +const reverseButtons = {}; +for (const el in buttons) { + reverseButtons[buttons[el]] = el; +} +// extract button data from buttonCode function evalButtonCode(code) { - // 2 bits: 0 - left, 1 - middle, 2 - right, 3 - release - // higher bits: 4 - shift, 8 - meta, 16 - control - const modifier = {shift: !!(code & 4), meta: !!(code & 8), control: !!(code & 16)}; - const wheel = code & 64; - let action; - let button; - if (wheel) { - action = (code & 1) ? 'down' : 'up'; - button = 'wheel'; - } else { - action = code & 32 ? 'move' : code === 3 ? 'release' : 'press'; - code &= 3; // TODO: more than 3 buttons + wheel - button = code === 0 ? 'left' : code === 1 ? 'middle' : code === 2 ? 'right' : ''; + // more than 15 buttons are not supported + if (code > 255) { + return {button: 'invalid', action: 'invalid', modifier: {}}; } - return {button, action, modifier}; + const modifier = {shift: !!(code & 4), meta: !!(code & 8), control: !!(code & 16)}; + const move = code & 32; + let button = code & 3; + if (code & 128) { + button |= 8; + } + if (code & 64) { + button |= 4 + } + let actionS = 'press'; + let buttonS = reverseButtons[button]; + if (button === 3) { + buttonS = ''; + actionS = 'release'; + } + if (move) { + actionS = 'move'; + } else if (4 <= button && button <= 7) { + buttonS = 'wheel'; + actionS = button === 4 ? 'up' : button === 5 ? 'down' : button === 6 ? 'left' : 'right'; + } + return {button: buttonS, action: actionS, modifier}; } // protocols const PROTOCOLS = { '9 (X10: press only)': '\x1b[?9h', '1000 (VT200: press, release, wheel)': '\x1b[?1000h', - // '1001 (VT200 highlight)': '\x1b[?1001h', // handle of backreport not implemented + // '1001 (VT200 highlight)': '\x1b[?1001h', // handle of backreport - not implemented '1002 (press, release, move on pressed, wheel)': '\x1b[?1002h', '1003 (press, relase, move, wheel)': '\x1b[?1003h' } @@ -96,8 +119,8 @@ const ENC = { // format: CSI M - From d0e05eb795c6ab4a22b3dc00ffb3f2e6e8de3e80 Mon Sep 17 00:00:00 2001 From: Revanth Mahesh Date: Sun, 27 Oct 2019 10:11:43 +0530 Subject: [PATCH 196/338] Support disposing of EventEmitters --- src/common/EventEmitter.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 34ac190f..ea9df48b 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -16,11 +16,13 @@ export interface IEvent { export interface IEventEmitter { event: IEvent; fire(data: T): void; + dispose(): void; } export class EventEmitter implements IEventEmitter { private _listeners: IListener[] = []; private _event?: IEvent; + private _disposed: boolean = false; public get event(): IEvent { if (!this._event) { @@ -28,10 +30,12 @@ export class EventEmitter implements IEventEmitter { this._listeners.push(listener); const disposable = { dispose: () => { - for (let i = 0; i < this._listeners.length; i++) { - if (this._listeners[i] === listener) { - this._listeners.splice(i, 1); - return; + if (!this._disposed) { + for (let i = 0; i < this._listeners.length; i++) { + if (this._listeners[i] === listener) { + this._listeners.splice(i, 1); + return; + } } } } @@ -51,4 +55,12 @@ export class EventEmitter implements IEventEmitter { queue[i].call(undefined, data); } } + +public dispose(): void { + if (this._listeners) { + this._listeners.splice(0, this._listeners.length); + } + this._disposed = true; +} + } From f3bbcb35e4421c112d81f66f0ea1f3286e8e5ed4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 26 Oct 2019 21:52:11 -0700 Subject: [PATCH 197/338] Clean up --- src/common/EventEmitter.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index ea9df48b..5991e338 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -56,11 +56,10 @@ export class EventEmitter implements IEventEmitter { } } -public dispose(): void { - if (this._listeners) { - this._listeners.splice(0, this._listeners.length); + public dispose(): void { + if (this._listeners) { + this._listeners.length = 0; + } + this._disposed = true; } - this._disposed = true; -} - } From 56d33668310397948dcfeb6324ac1d518ebb11bf Mon Sep 17 00:00:00 2001 From: alberto Date: Sun, 27 Oct 2019 22:08:42 +0100 Subject: [PATCH 198/338] Fix typo --- addons/xterm-addon-search/typings/xterm-addon-search.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 94bc5297..913f33df 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -27,7 +27,7 @@ declare module 'xterm-addon-search' { caseSensitive?: boolean; /** - * Whether to do an indcremental search, this will expand the selection if it + * Whether to do an incremental search, this will expand the selection if it * still matches the term the user typed. Note that this only affects * `findNext`, not `findPrevious`. */ From dca1e0b93549a155fef5b6e82532f6be6552957f Mon Sep 17 00:00:00 2001 From: alberto Date: Sun, 27 Oct 2019 22:18:11 +0100 Subject: [PATCH 199/338] Fix another typo --- addons/xterm-addon-search/typings/xterm-addon-search.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 913f33df..f27aba78 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -17,7 +17,7 @@ declare module 'xterm-addon-search' { /** * Whether to search for a whole word, the result is only valid if it's - * suppounded in "non-word" characters such as `_`, `(`, `)` or space. + * surrounded in "non-word" characters such as `_`, `(`, `)` or space. */ wholeWord?: boolean; From 8d0fdc8731b823968e991eb8b98588f5577407ab Mon Sep 17 00:00:00 2001 From: alberto Date: Sun, 27 Oct 2019 22:29:36 +0100 Subject: [PATCH 200/338] Fix grammar --- 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 295a03b2..27f39177 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -49,7 +49,7 @@ declare module 'xterm' { /** * When enabled the cursor will be set to the beginning of the next line - * with every new line. This equivalent to sending '\r\n' for each '\n'. + * with every new line. This is equivalent to sending '\r\n' for each '\n'. * Normally the termios settings of the underlying PTY deals with the * translation of '\n' to '\r\n' and this setting should not be used. If you * deal with data from a non-PTY related source, this settings might be From fe2ac04b857e730f62780e55ebf1d672890510b7 Mon Sep 17 00:00:00 2001 From: alberto Date: Sun, 27 Oct 2019 22:34:54 +0100 Subject: [PATCH 201/338] Fix grammar --- 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 27f39177..eb9904e8 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -437,7 +437,7 @@ declare module 'xterm' { onData: IEvent; /** - * Adds an event listener for a key is pressed. The event value contains the + * Adds an event listener for when a key is pressed. The event value contains the * string that will be sent in the data event as well as the DOM event that * triggered it. * @returns an `IDisposable` to stop listening. From ffe2d66affe663c5bb861a92c28517f19e90c5be Mon Sep 17 00:00:00 2001 From: alberto Date: Sun, 27 Oct 2019 22:38:55 +0100 Subject: [PATCH 202/338] Remove duplicated punctuation --- 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 eb9904e8..9ec407c7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -609,7 +609,7 @@ declare module 'xterm' { /** * Selects text within the terminal. - * @param column The column the selection starts at.. + * @param column The column the selection starts at. * @param row The row the selection starts at. * @param length The length of the selection. */ From b880cd3ad3d171023bcb11caf708f6ec7d7c1f6c Mon Sep 17 00:00:00 2001 From: alberto Date: Sun, 27 Oct 2019 22:43:26 +0100 Subject: [PATCH 203/338] Fix punctuation mark --- 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 9ec407c7..eb2b3a7f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -918,7 +918,7 @@ declare module 'xterm' { /** * The line within the buffer where the top of the bottom page is (when - * fully scrolled down); + * fully scrolled down). */ readonly baseY: number; From cb1cf3cd119d500d5ac099548a5d5394fc061e03 Mon Sep 17 00:00:00 2001 From: alberto Date: Sun, 27 Oct 2019 22:50:22 +0100 Subject: [PATCH 204/338] Fix grammar --- typings/xterm.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index eb2b3a7f..9728a72c 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1047,7 +1047,7 @@ declare module 'xterm' { * array will contain subarrays with their numercial values. * Return true if the sequence was handled; false if we should try * a previous handler (set by addCsiHandler or setCsiHandler). - * The most recently-added handler is tried first. + * The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; @@ -1066,7 +1066,7 @@ declare module 'xterm' { * The function gets the payload and numerical parameters as arguments. * Return true if the sequence was handled; false if we should try * a previous handler (set by addDcsHandler or setDcsHandler). - * The most recently-added handler is tried first. + * The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; @@ -1079,7 +1079,7 @@ declare module 'xterm' { * @param callback The function to handle the sequence. * Return true if the sequence was handled; false if we should try * a previous handler (set by addEscHandler or setEscHandler). - * The most recently-added handler is tried first. + * The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; @@ -1097,7 +1097,7 @@ declare module 'xterm' { * The callback is called with OSC data string. * Return true if the sequence was handled; false if we should try * a previous handler (set by addOscHandler or setOscHandler). - * The most recently-added handler is tried first. + * The most recently added handler is tried first. * @return An IDisposable you can call to remove this handler. */ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; From 6c2b34a5c8b421f3b29aeddac07108ea4bcf8404 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 30 Oct 2019 20:03:53 -0700 Subject: [PATCH 205/338] Use YarnInstaller --- azure-pipelines.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a58f7fe0..cec85ba5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,9 +12,9 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + - task: YarnInstaller@3 inputs: - versionSpec: "1.9.4" + versionSpec: '1.x' displayName: 'Install Yarn' - script: | yarn @@ -70,9 +70,9 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + - task: YarnInstaller@3 inputs: - versionSpec: "1.9.4" + versionSpec: '1.x' displayName: 'Install Yarn' - script: | yarn @@ -115,9 +115,9 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + - task: YarnInstaller@3 inputs: - versionSpec: "1.9.4" + versionSpec: '1.x' displayName: 'Install Yarn' - script: | yarn From 79f3a7343cf71de92e18157ab6f7a90931181d9b Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 30 Oct 2019 20:07:41 -0700 Subject: [PATCH 206/338] Don't wrap for single line scripts --- azure-pipelines.yml | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cec85ba5..c6ee9ac0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,14 +16,11 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: | - yarn + - script: yarn displayName: 'Install dependencies and build' - - script: | - yarn test-unit --forbid-only + - script: yarn test-unit --forbid-only displayName: 'Unit tests' - - script: | - yarn lint + - script: yarn lint displayName: 'Lint' - job: macOS @@ -34,14 +31,11 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: | - yarn + - script: yarn displayName: 'Install dependencies and build' - - script: | - yarn test-unit --forbid-only + - script: yarn test-unit --forbid-only displayName: 'Unit tests' - - script: | - yarn lint + - script: yarn lint displayName: 'Lint' - job: Windows @@ -52,14 +46,11 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: | - yarn + - script: yarn displayName: 'Install dependencies and build' - - script: | - yarn test-unit --forbid-only + - script: yarn test-unit --forbid-only displayName: 'Unit tests' - - script: | - yarn lint + - script: yarn lint displayName: 'Lint' - job: Linux_IntegrationTests @@ -74,8 +65,7 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: | - yarn + - script: yarn displayName: 'Install dependencies and build' - script: | yarn start & @@ -91,8 +81,7 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: | - yarn + - script: yarn displayName: 'Install dependencies and build' - script: | yarn start & @@ -119,9 +108,7 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: | - yarn + - script: yarn displayName: 'Install dependencies and build' - - script: | - NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js + - script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js displayName: 'Package and publish to npm' From a450bd52d7a5c12e9ce6767774c653c0b0f0547b Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 30 Oct 2019 20:18:32 -0700 Subject: [PATCH 207/338] Generate and publish coverage report to pipelines --- .gitignore | 1 + azure-pipelines.yml | 17 ++ package.json | 1 + yarn.lock | 522 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 530 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 64273dda..8aea04b2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules/ lib/ out/ out-test/ +.nyc_output/ Makefile.gyp *.Makefile *.target.gyp.mk diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c6ee9ac0..cab8fa51 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,6 +22,23 @@ jobs: displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' + - script: | + NODE_PATH=$(pwd)/out ./node_modules/.bin/nyc ./node_modules/.bin/mocha './out/*test.js' './out/**/*test.js' + ./node_modules/.bin/nyc report --reporter=cobertura + displayName: 'Coverage report' + - task: PublishCodeCoverageResults@1 + inputs: + codeCoverageTool: Cobertura + summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml' + reportDirectory: '$(System.DefaultWorkingDirectory)/**/coverage' + displayName: 'Publish coverage' + - task: mspremier.BuildQualityChecks.QualityChecks-task.BuildQualityChecks@6 + displayName: 'Check build quality' + inputs: + checkCoverage: true + coverageType: branches + coverageThreshold: 55 + coverageFailOption: fixed - job: macOS pool: diff --git a/package.json b/package.json index fb9592c7..fa29486a 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "jsdom": "^11.11.0", "mocha": "^6.1.4", "node-pty": "0.7.6", + "nyc": "13", "puppeteer": "^1.15.0", "source-map-loader": "^0.2.4", "ts-loader": "^6.0.4", diff --git a/yarn.lock b/yarn.lock index 0019809a..c96c390c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,6 +9,46 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" + integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/generator@^7.4.0", "@babel/generator@^7.6.3": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671" + integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w== + dependencies: + "@babel/types" "^7.6.3" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-split-export-declaration@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" + integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== + dependencies: + "@babel/types" "^7.4.4" + "@babel/highlight@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" @@ -18,6 +58,44 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.3": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81" + integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A== + +"@babel/template@^7.1.0", "@babel/template@^7.4.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6" + integrity sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.6.0" + "@babel/types" "^7.6.0" + +"@babel/traverse@^7.4.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9" + integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw== + dependencies: + "@babel/code-frame" "^7.5.5" + "@babel/generator" "^7.6.3" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/parser" "^7.6.3" + "@babel/types" "^7.6.3" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.0.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.6.0", "@babel/types@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09" + integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + "@fimbul/bifrost@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@fimbul/bifrost/-/bifrost-0.11.0.tgz#83cacc21464198b12e3cc1c2204ae6c6d7afd158" @@ -439,11 +517,23 @@ app-root-path@^2.2.1: resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== +append-transform@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" + integrity sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw== + dependencies: + default-require-extensions "^2.0.0" + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -489,6 +579,11 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -816,6 +911,16 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +caching-transform@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-3.0.2.tgz#601d46b91eca87687a281e71cef99791b0efca70" + integrity sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w== + dependencies: + hasha "^3.0.0" + make-dir "^2.0.0" + package-hash "^3.0.0" + write-file-atomic "^2.4.2" + camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -1075,6 +1180,11 @@ commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +commander@~2.20.3: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -1134,6 +1244,13 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +convert-source-map@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -1197,6 +1314,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-spawn@^4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" + integrity sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE= + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1293,7 +1418,7 @@ debug@^3.1.0: dependencies: ms "2.0.0" -debug@^4.0.1, debug@^4.1.0: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -1339,6 +1464,13 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +default-require-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" + integrity sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc= + dependencies: + strip-bom "^3.0.0" + defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -1536,6 +1668,13 @@ errno@^0.1.3, errno@~0.1.7: dependencies: prr "~1.0.1" +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-abstract@^1.5.1: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" @@ -1557,6 +1696,11 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + es6-promise@^4.0.3: version "4.2.6" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" @@ -1878,7 +2022,7 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-cache-dir@^2.1.0: +find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== @@ -1921,6 +2065,14 @@ for-in@^1.0.2: resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= +foreground-child@^1.5.6: + version "1.5.6" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9" + integrity sha1-T9ca0t/elnibmApcCilZN8svXOk= + dependencies: + cross-spawn "^4" + signal-exit "^3.0.0" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -2097,6 +2249,11 @@ global-modules-path@^2.1.0: resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.3.0.tgz#b0e2bac6beac39745f7db5c59d26a36a0b94f7dc" integrity sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag== +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" @@ -2107,6 +2264,17 @@ growl@1.10.5: resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== +handlebars@^4.1.2: + version "4.5.1" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.1.tgz#8a01c382c180272260d07f2d1aa3ae745715c7ba" + integrity sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA== + dependencies: + neo-async "^2.6.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -2189,6 +2357,13 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasha@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-3.0.0.tgz#52a32fab8569d41ca69a61ff1a214f8eb7c8bd39" + integrity sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk= + dependencies: + is-stream "^1.0.1" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -2203,6 +2378,11 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hosted-git-info@^2.1.4: + version "2.8.5" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" + integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg== + html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" @@ -2387,6 +2567,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + is-arrayish@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" @@ -2525,7 +2710,7 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" -is-stream@^1.1.0: +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -2579,6 +2764,58 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +istanbul-lib-coverage@^2.0.3, istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== + +istanbul-lib-hook@^2.0.3: + version "2.0.7" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133" + integrity sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA== + dependencies: + append-transform "^1.0.0" + +istanbul-lib-instrument@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== + dependencies: + "@babel/generator" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + istanbul-lib-coverage "^2.0.5" + semver "^6.0.0" + +istanbul-lib-report@^2.0.4: + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== + dependencies: + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + supports-color "^6.1.0" + +istanbul-lib-source-maps@^3.0.2: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" + source-map "^0.6.1" + +istanbul-reports@^2.1.1: + version "2.2.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af" + integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA== + dependencies: + handlebars "^4.1.2" + javascript-natural-sort@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" @@ -2634,7 +2871,12 @@ jsdom@^11.11.0: ws "^4.0.0" xml-name-validator "^3.0.0" -json-parse-better-errors@^1.0.2: +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== @@ -2739,6 +2981,16 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -2778,6 +3030,11 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -2788,6 +3045,11 @@ lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== +lodash@^4.17.13: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + log-symbols@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -2821,7 +3083,14 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -make-dir@^2.0.0: +make-dir@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== + dependencies: + pify "^3.0.0" + +make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== @@ -2910,6 +3179,13 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= +merge-source-map@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" + integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw== + dependencies: + source-map "^0.6.1" + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -3021,6 +3297,11 @@ minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + minipass@^2.2.1, minipass@^2.3.5: version "2.5.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.5.0.tgz#dddb1d001976978158a05badfcbef4a771612857" @@ -3164,7 +3445,7 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -neo-async@^2.5.0, neo-async@^2.6.1: +neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== @@ -3242,6 +3523,16 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -3294,6 +3585,36 @@ nwsapi@^2.0.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.4.tgz#dc79040a5f77b97716dc79565fc7fc3ef7d50570" integrity sha512-Zt6HRR6RcJkuj5/N9zeE7FN6YitRW//hK2wTOwX274IBphbY3Zf5+yn5mZ9v/SzAOTMjQNxZf9KkmPLWn0cV4g== +nyc@13: + version "13.3.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-13.3.0.tgz#da4dbe91a9c8b9ead3f4f3344c76f353e3c78c75" + integrity sha512-P+FwIuro2aFG6B0Esd9ZDWUd51uZrAEoGutqZxzrVmYl3qSfkLgcQpBPBjtDFsUQLFY1dvTQJPOyeqr8S9GF8w== + dependencies: + archy "^1.0.0" + arrify "^1.0.1" + caching-transform "^3.0.1" + convert-source-map "^1.6.0" + find-cache-dir "^2.0.0" + find-up "^3.0.0" + foreground-child "^1.5.6" + glob "^7.1.3" + istanbul-lib-coverage "^2.0.3" + istanbul-lib-hook "^2.0.3" + istanbul-lib-instrument "^3.1.0" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.2" + istanbul-reports "^2.1.1" + make-dir "^1.3.0" + merge-source-map "^1.1.0" + resolve-from "^4.0.0" + rimraf "^2.6.3" + signal-exit "^3.0.2" + spawn-wrap "^1.4.2" + test-exclude "^5.1.0" + uuid "^3.3.2" + yargs "^12.0.5" + yargs-parser "^11.1.1" + oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" @@ -3376,6 +3697,14 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + optionator@^0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" @@ -3393,7 +3722,7 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0: +os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= @@ -3482,6 +3811,16 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-3.0.0.tgz#50183f2d36c9e3e528ea0a8605dff57ce976f88e" + integrity sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA== + dependencies: + graceful-fs "^4.1.15" + hasha "^3.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + pako@~1.0.5: version "1.0.10" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" @@ -3508,6 +3847,14 @@ parse-asn1@^5.0.0: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + parse5@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" @@ -3560,11 +3907,23 @@ path-parse@^1.0.5: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" integrity sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME= +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -3591,6 +3950,11 @@ picomatch@^2.0.5: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -3813,6 +4177,23 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +read-pkg-up@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" + integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== + dependencies: + find-up "^3.0.0" + read-pkg "^3.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -3857,6 +4238,13 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= + dependencies: + es6-error "^4.0.1" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -3941,6 +4329,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -3951,6 +4344,13 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= +resolve@^1.10.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" + integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== + dependencies: + path-parse "^1.0.6" + resolve@^1.3.2: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" @@ -3971,7 +4371,7 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -4048,7 +4448,7 @@ seed-random@2.2.0: resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= -semver@^5.3.0, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4233,7 +4633,7 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@^0.5.6: +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -4243,6 +4643,44 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spawn-wrap@^1.4.2: + version "1.4.3" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.3.tgz#81b7670e170cca247d80bf5faf0cfb713bdcf848" + integrity sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw== + dependencies: + foreground-child "^1.5.6" + mkdirp "^0.5.0" + os-homedir "^1.0.1" + rimraf "^2.6.2" + signal-exit "^3.0.2" + which "^1.3.0" + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -4401,6 +4839,11 @@ strip-ansi@^5.1.0: dependencies: ansi-regex "^4.1.0" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -4432,6 +4875,13 @@ supports-color@^5.4.0: dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" @@ -4479,6 +4929,16 @@ terser@^4.1.2: source-map "~0.6.1" source-map-support "~0.5.12" +test-exclude@^5.1.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== + dependencies: + glob "^7.1.3" + minimatch "^3.0.4" + read-pkg-up "^4.0.0" + require-main-filename "^2.0.0" + text-hex@1.0.x: version "1.0.0" resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" @@ -4521,6 +4981,11 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -4729,6 +5194,14 @@ typescript@^3.5.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== +uglify-js@^3.1.4: + version "3.6.5" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.5.tgz#b0ee796d2ae7e25672e04f65629b997cd4b30bd6" + integrity sha512-7L3W+Npia1OCr5Blp4/Vw83tK1mu5gnoIURtT1fUVfQ3Kf8WStWV6NJz0fdoBJZls0KlweruRTLVe6XLafmy5g== + dependencies: + commander "~2.20.3" + source-map "~0.6.1" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -4830,11 +5303,24 @@ uuid@^3.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +uuid@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + v8-compile-cache@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c" integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw== +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -4962,7 +5448,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@1.3.1, which@^1.2.9: +which@1.3.1, which@^1.2.9, which@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -4999,6 +5485,11 @@ winston@^3.2.1: triple-beam "^1.3.0" winston-transport "^4.3.0" +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= + wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -5024,6 +5515,15 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +write-file-atomic@^2.4.2: + version "2.4.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" + integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + ws@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ws/-/ws-4.1.0.tgz#a979b5d7d4da68bf54efe0408967c324869a7289" From 39c0fdb54ad530466ed8bd72a75c387a7bde6fdf Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 30 Oct 2019 20:31:40 -0700 Subject: [PATCH 208/338] Remove report directory --- azure-pipelines.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cab8fa51..8c35196a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,7 +30,6 @@ jobs: inputs: codeCoverageTool: Cobertura summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml' - reportDirectory: '$(System.DefaultWorkingDirectory)/**/coverage' displayName: 'Publish coverage' - task: mspremier.BuildQualityChecks.QualityChecks-task.BuildQualityChecks@6 displayName: 'Check build quality' From 08b10ba65160aae9af802a2c6fea1d694ff5c64f Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 30 Oct 2019 21:13:09 -0700 Subject: [PATCH 209/338] Add note about unit test coverage in contributing --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8102a2e..d5efe9d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,3 +52,7 @@ By contributing code to xterm.js you: holder has explicitly granted the right to use it like this, through a compatible open source license or through a direct agreement with you.) + +### Test coverage + +One area that always needs attention is improving out unit test coverage, you can view the code coverage report on [Azure Pipelines](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3) by clicking the Code Coverage tab. From fbb9ef9f7f5f9b5cfa7e4fbfa419f0d0bbeabe54 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Oct 2019 11:59:04 -0700 Subject: [PATCH 210/338] Change to tracking lines in code coverage --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8c35196a..177f581f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -35,8 +35,8 @@ jobs: displayName: 'Check build quality' inputs: checkCoverage: true - coverageType: branches - coverageThreshold: 55 + coverageType: lines + coverageThreshold: 60 coverageFailOption: fixed - job: macOS From 1f4780a71ae4b5532a9597a569a819184de0c9ad Mon Sep 17 00:00:00 2001 From: Geraldo Neto Date: Sat, 2 Nov 2019 18:58:04 -0300 Subject: [PATCH 211/338] #2444: Fix wrapped lines search --- addons/xterm-addon-search/src/SearchAddon.ts | 57 +++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 5893f79f..9d553d76 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -12,6 +12,11 @@ export interface ISearchOptions { incremental?: boolean; } +export interface ISearchPosition { + startCol: number; + startRow: number; +} + export interface ISearchResult { term: string; col: number; @@ -71,17 +76,23 @@ export class SearchAddon implements ITerminalAddon { this._initLinesCache(); + const searchPosition: ISearchPosition = { + startRow, + startCol + }; + // Search startRow - let result = this._findInLine(term, startRow, startCol, searchOptions); + let result = this._findInLine(term, searchPosition, searchOptions); // Search from startRow + 1 to end if (!result) { for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) { - + searchPosition.startRow = y; + searchPosition.startCol = 0; // If the current line is wrapped line, increase index of column to ignore the previous scan // Otherwise, reset beginning column index to zero with set new unwrapped line index - result = this._findInLine(term, y, 0, searchOptions); + result = this._findInLine(term, searchPosition, searchOptions); if (result) { break; } @@ -90,7 +101,9 @@ export class SearchAddon implements ITerminalAddon { // If we hit the bottom and didn't search from the very top wrap back up if (!result && startRow !== 0) { for (let y = 0; y < startRow; y++) { - result = this._findInLine(term, y, 0, searchOptions); + searchPosition.startRow = y; + searchPosition.startCol = 0; + result = this._findInLine(term, searchPosition, searchOptions); if (result) { break; } @@ -131,21 +144,26 @@ export class SearchAddon implements ITerminalAddon { } this._initLinesCache(); + const searchPosition: ISearchPosition = { + startRow, + startCol + }; if (incremental) { - result = this._findInLine(term, startRow, startCol, searchOptions, false); + result = this._findInLine(term, searchPosition, searchOptions, false); if (!(result && result.row === startRow && result.col === startCol)) { - result = this._findInLine(term, startRow, startCol, searchOptions, true); + result = this._findInLine(term, searchPosition, searchOptions, true); } } else { - result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch); + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); } // Search from startRow - 1 to top if (!result) { - startCol = this._terminal.cols; + searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols); for (let y = startRow - 1; y >= 0; y--) { - result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch); + searchPosition.startRow = y; + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); if (result) { break; } @@ -154,7 +172,8 @@ export class SearchAddon implements ITerminalAddon { // If we hit the top and didn't search from the very bottom wrap back down if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) { for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) { - result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch); + searchPosition.startRow = y; + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); if (result) { break; } @@ -213,18 +232,28 @@ export class SearchAddon implements ITerminalAddon { * started on an earlier line then it is skipped since it will be properly searched when the terminal line that the * text starts on is searched. * @param term The search term. - * @param row The line to start the search from. - * @param col The column to start the search from. + * @param {ISearchPosition} position The position to start the search. * @param searchOptions Search options. * @return The search result if it was found. */ - protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { + protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { const terminal = this._terminal!; + let row = searchPosition.startRow; + const col = searchPosition.startCol; // Ignore wrapped lines, only consider on unwrapped line (first row of command string). const firstLine = terminal.buffer.getLine(row); if (firstLine && firstLine.isWrapped) { - return; + if (isReverseSearch) { + searchPosition.startCol += terminal.cols; + } + else { + // This will iterate until we find the line start. + // When we find it, we will search using the calculated start column. + searchPosition.startRow--; + searchPosition.startCol += terminal.cols; + return this._findInLine(term, searchPosition, searchOptions); + } } let stringLine = this._linesCache ? this._linesCache[row] : void 0; if (stringLine === void 0) { From 9d50d106e38e900d54155e1f1f24cc3bbd50ec4f Mon Sep 17 00:00:00 2001 From: Geraldo Neto Date: Sat, 2 Nov 2019 19:21:58 -0300 Subject: [PATCH 212/338] 2444: Return after calculating startCol in reverse search --- addons/xterm-addon-search/src/SearchAddon.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 9d553d76..3db1d6e1 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -246,6 +246,7 @@ export class SearchAddon implements ITerminalAddon { if (firstLine && firstLine.isWrapped) { if (isReverseSearch) { searchPosition.startCol += terminal.cols; + return; } else { // This will iterate until we find the line start. From 9358718b5ff767a427503c66eeaa4dff423747a9 Mon Sep 17 00:00:00 2001 From: Geraldo Neto Date: Sat, 2 Nov 2019 19:33:21 -0300 Subject: [PATCH 213/338] 2444: Fix no-else-return lint --- addons/xterm-addon-search/src/SearchAddon.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index b869209f..67f1ad6e 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -255,13 +255,12 @@ export class SearchAddon implements ITerminalAddon { searchPosition.startCol += terminal.cols; return; } - else { - // This will iterate until we find the line start. - // When we find it, we will search using the calculated start column. - searchPosition.startRow--; - searchPosition.startCol += terminal.cols; - return this._findInLine(term, searchPosition, searchOptions); - } + + // This will iterate until we find the line start. + // When we find it, we will search using the calculated start column. + searchPosition.startRow--; + searchPosition.startCol += terminal.cols; + return this._findInLine(term, searchPosition, searchOptions); } let stringLine = this._linesCache ? this._linesCache[row] : void 0; if (stringLine === void 0) { From 1afffba1d8bed670401fbef2391d2fb4de4c1c78 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Nov 2019 11:00:19 -0800 Subject: [PATCH 214/338] Only refresh rows that changed during parsing Fixes #2534 --- src/InputHandler.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7b6ea1fe..74208482 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -347,6 +347,9 @@ export class InputHandler extends Disposable implements IInputHandler { } } + // Clear the dirty row service so we know which lines changed as a result of parsing + this._dirtyRowService.clearRange(); + // process big data in smaller chunks if (data.length > MAX_PARSEBUFFER_LENGTH) { for (let i = 0; i < data.length; i += MAX_PARSEBUFFER_LENGTH) { @@ -367,6 +370,8 @@ export class InputHandler extends Disposable implements IInputHandler { if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { this._onCursorMove.fire(); } + + // Refresh any dirty rows accumulated as part of parsing this._terminal.refresh(this._dirtyRowService.start, this._dirtyRowService.end); } From 267fbc7f5ee63a9318b3f6d048b61c91685608c0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 10:54:02 -0800 Subject: [PATCH 215/338] typescript@3.7 and fix errors --- package.json | 2 +- src/browser/Clipboard.ts | 12 ++++++------ yarn.lock | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index fa29486a..0150569d 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "ts-loader": "^6.0.4", "tslint": "^5.18.0", "tslint-consistent-codestyle": "^1.13.0", - "typescript": "3.6", + "typescript": "3.7", "utf8": "^3.0.0", "webpack": "^4.35.3", "webpack-cli": "^3.1.0", diff --git a/src/browser/Clipboard.ts b/src/browser/Clipboard.ts index 594e4429..a7c48bfb 100644 --- a/src/browser/Clipboard.ts +++ b/src/browser/Clipboard.ts @@ -82,12 +82,12 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA // Reset the terminal textarea's styling // Timeout needs to be long enough for click event to be handled. setTimeout(() => { - textarea.style.position = null; - textarea.style.width = null; - textarea.style.height = null; - textarea.style.left = null; - textarea.style.top = null; - textarea.style.zIndex = null; + textarea.style.position = ''; + textarea.style.width = ''; + textarea.style.height = ''; + textarea.style.left = ''; + textarea.style.top = ''; + textarea.style.zIndex = ''; }, 200); } diff --git a/yarn.lock b/yarn.lock index c96c390c..cb02cf91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5184,10 +5184,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.6: - version "3.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" - integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== +typescript@3.7: + version "3.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== typescript@^3.5.1: version "3.5.1" From 843d5ff53ca966b809fe7371fb95464f79cc3b88 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 11:09:47 -0800 Subject: [PATCH 216/338] Adopt optional chaining --- src/Terminal.ts | 82 +++++++----------------- src/browser/MouseZoneManager.ts | 8 +-- src/browser/renderer/BaseRenderLayer.ts | 4 +- src/browser/services/SelectionService.ts | 4 +- 4 files changed, 27 insertions(+), 71 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 3e7e55ca..537ecb74 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -240,18 +240,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return; } super.dispose(); - if (this._windowsMode) { - this._windowsMode.dispose(); - this._windowsMode = undefined; - } - if (this._renderService) { - this._renderService.dispose(); - } + this._windowsMode?.dispose(); + this._windowsMode = undefined; + this._renderService?.dispose(); this._customKeyEventHandler = null; this.write = () => {}; - if (this.element && this.element.parentNode) { - this.element.parentNode.removeChild(this.element); - } + this.element?.parentNode?.removeChild(this.element); } private _setup(): void { @@ -336,12 +330,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp case 'fontFamily': case 'fontSize': // When the font changes the size of the cells may change which requires a renderer clear - if (this._renderService) { - this._renderService.clear(); - } - if (this._charSizeService) { - this._charSizeService.measure(); - } + this._renderService?.clear(); + this._charSizeService?.measure(); break; case 'drawBoldTextInBrightColors': case 'letterSpacing': @@ -363,9 +353,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp break; case 'scrollback': this.buffers.resize(this.cols, this.rows); - if (this.viewport) { - this.viewport.syncScrollArea(); - } + this.viewport?.syncScrollArea(); break; case 'screenReaderMode': if (this.optionsService.options.screenReaderMode) { @@ -373,10 +361,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._accessibilityManager = new AccessibilityManager(this, this._renderService); } } else { - if (this._accessibilityManager) { - this._accessibilityManager.dispose(); - this._accessibilityManager = null; - } + this._accessibilityManager?.dispose(); + this._accessibilityManager = null; } break; case 'tabStopWidth': this.buffers.setupTabStops(); break; @@ -389,10 +375,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._windowsMode = applyWindowsMode(this); } } else { - if (this._windowsMode) { - this._windowsMode.dispose(); - this._windowsMode = undefined; - } + this._windowsMode?.dispose(); + this._windowsMode = undefined; } break; } @@ -665,15 +649,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp */ private _setTheme(theme: ITheme): void { this._theme = theme; - if (this._colorManager) { - this._colorManager.setTheme(theme); - } - if (this._renderService) { - this._renderService.setColors(this._colorManager.colors); - } - if (this.viewport) { - this.viewport.onThemeChange(this._colorManager.colors); - } + this._colorManager?.setTheme(theme); + this._renderService?.setColors(this._colorManager.colors); + this.viewport?.onThemeChange(this._colorManager.colors); } /** @@ -940,9 +918,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * @param end The row to end at (between start and this.rows - 1). */ public refresh(start: number, end: number): void { - if (this._renderService) { - this._renderService.refreshRows(start, end); - } + this._renderService?.refreshRows(start, end); } /** @@ -951,9 +927,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * @param end The row to end at (between start and this.rows - 1). */ private _queueLinkification(start: number, end: number): void { - if (this.linkifier) { - this.linkifier.linkifyRows(start, end); - } + this.linkifier?.linkifyRows(start, end); } /** @@ -1233,24 +1207,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * Clears the current terminal selection. */ public clearSelection(): void { - if (this._selectionService) { - this._selectionService.clearSelection(); - } + this._selectionService?.clearSelection(); } /** * Selects all text within the terminal. */ public selectAll(): void { - if (this._selectionService) { - this._selectionService.selectAll(); - } + this._selectionService?.selectAll(); } public selectLines(start: number, end: number): void { - if (this._selectionService) { - this._selectionService.selectLines(start, end); - } + this._selectionService?.selectLines(start, end); } /** @@ -1457,9 +1425,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._bufferService.resize(x, y); this.buffers.setupTabStops(this.cols); - if (this._charSizeService) { - this._charSizeService.measure(); - } + this._charSizeService?.measure(); // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location @@ -1551,9 +1517,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._bufferService.reset(); this._coreService.reset(); this._coreMouseService.reset(); - if (this._selectionService) { - this._selectionService.reset(); - } + this._selectionService?.reset(); // reattach this._customKeyEventHandler = customKeyEventHandler; @@ -1563,9 +1527,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // do a full screen refresh this.refresh(0, this.rows - 1); - if (this.viewport) { - this.viewport.syncScrollArea(); - } + this.viewport?.syncScrollArea(); } // TODO: Remove cancel function and cancelEvents option diff --git a/src/browser/MouseZoneManager.ts b/src/browser/MouseZoneManager.ts index 7eb7c5f8..d697c133 100644 --- a/src/browser/MouseZoneManager.ts +++ b/src/browser/MouseZoneManager.ts @@ -175,11 +175,9 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { // Find the active zone, prevent event propagation if found to prevent other // components from handling the mouse event. const zone = this._findZoneEventAt(e); - if (zone) { - if (zone.willLinkActivate(e)) { - e.preventDefault(); - e.stopImmediatePropagation(); - } + if (zone?.willLinkActivate(e)) { + e.preventDefault(); + e.stopImmediatePropagation(); } } diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index e9ad5b3a..a33bef28 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -60,9 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public dispose(): void { this._container.removeChild(this._canvas); - if (this._charAtlas) { - this._charAtlas.dispose(); - } + this._charAtlas?.dispose(); } private _initCanvas(): void { diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 3631bbb7..4eb6550c 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -688,9 +688,7 @@ export class SelectionService implements ISelectionService { // reverseIndex) and delete in a splice is only ever used when the same // number of elements was just added. Given this is could actually be // beneficial to leave the selection as is for these cases. - if (this._trimListener) { - this._trimListener.dispose(); - } + this._trimListener.dispose(); this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount)); } From 3dca46619c8ad4d6ca1879e24208e349ef26db62 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 11:30:11 -0800 Subject: [PATCH 217/338] Skip puppeteer download on unit test runs Fixes #2539 --- azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 177f581f..2326e981 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,6 +7,8 @@ jobs: - job: Linux pool: vmImage: 'ubuntu-16.04' + variables: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 steps: - task: NodeTool@0 inputs: @@ -42,6 +44,8 @@ jobs: - job: macOS pool: vmImage: 'xcode9-macos10.13' + variables: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 steps: - task: NodeTool@0 inputs: @@ -57,6 +61,8 @@ jobs: - job: Windows pool: vmImage: 'vs2017-win2016' + variables: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 steps: - task: NodeTool@0 inputs: From 70500e31ac4da4468af267c2e3a0113ba4151d00 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 11:36:54 -0800 Subject: [PATCH 218/338] Add yarn cache caching --- azure-pipelines.yml | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2326e981..247395b3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,7 +18,12 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn | $(Agent.OS) | yarn.lock + path: $(YARN_CACHE_FOLDER) + displayName: Cache Yarn packages + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn test-unit --forbid-only displayName: 'Unit tests' @@ -51,7 +56,12 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn | $(Agent.OS) | yarn.lock + path: $(YARN_CACHE_FOLDER) + displayName: Cache Yarn packages + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn test-unit --forbid-only displayName: 'Unit tests' @@ -68,7 +78,12 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn | $(Agent.OS) | yarn.lock + path: $(YARN_CACHE_FOLDER) + displayName: Cache Yarn packages + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn test-unit --forbid-only displayName: 'Unit tests' @@ -87,7 +102,12 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn | $(Agent.OS) | yarn.lock + path: $(YARN_CACHE_FOLDER) + displayName: Cache Yarn packages + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: | yarn start & @@ -103,7 +123,12 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn | $(Agent.OS) | yarn.lock + path: $(YARN_CACHE_FOLDER) + displayName: Cache Yarn packages + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: | yarn start & @@ -130,7 +155,12 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - script: yarn + - task: CacheBeta@1 + inputs: + key: yarn | $(Agent.OS) | yarn.lock + path: $(YARN_CACHE_FOLDER) + displayName: Cache Yarn packages + - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js displayName: 'Package and publish to npm' From 86264d47635a8c360e7fecad10fa3fcfb2af58ee Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 11:42:35 -0800 Subject: [PATCH 219/338] Specify yarn cache folder --- azure-pipelines.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 247395b3..0998781f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,6 +9,7 @@ jobs: vmImage: 'ubuntu-16.04' variables: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 + YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -51,6 +52,7 @@ jobs: vmImage: 'xcode9-macos10.13' variables: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 + YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -73,6 +75,7 @@ jobs: vmImage: 'vs2017-win2016' variables: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 + YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -93,6 +96,8 @@ jobs: - job: Linux_IntegrationTests pool: vmImage: 'ubuntu-16.04' + variables: + YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -118,6 +123,8 @@ jobs: - job: macOS_IntegrationTests pool: vmImage: 'xcode9-macos10.13' + variables: + YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: From 5411f60f44c71a4ecf9b3a9e125bf3afa66f700e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 11:54:32 -0800 Subject: [PATCH 220/338] Try cache node_modules --- azure-pipelines.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0998781f..9ccc8393 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,7 +9,6 @@ jobs: vmImage: 'ubuntu-16.04' variables: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 - YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -22,7 +21,7 @@ jobs: - task: CacheBeta@1 inputs: key: yarn | $(Agent.OS) | yarn.lock - path: $(YARN_CACHE_FOLDER) + path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' @@ -52,7 +51,6 @@ jobs: vmImage: 'xcode9-macos10.13' variables: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 - YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -61,7 +59,7 @@ jobs: - task: CacheBeta@1 inputs: key: yarn | $(Agent.OS) | yarn.lock - path: $(YARN_CACHE_FOLDER) + path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' @@ -75,7 +73,6 @@ jobs: vmImage: 'vs2017-win2016' variables: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 - YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -84,7 +81,7 @@ jobs: - task: CacheBeta@1 inputs: key: yarn | $(Agent.OS) | yarn.lock - path: $(YARN_CACHE_FOLDER) + path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' @@ -97,7 +94,6 @@ jobs: pool: vmImage: 'ubuntu-16.04' variables: - YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -110,7 +106,7 @@ jobs: - task: CacheBeta@1 inputs: key: yarn | $(Agent.OS) | yarn.lock - path: $(YARN_CACHE_FOLDER) + path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' @@ -124,7 +120,6 @@ jobs: pool: vmImage: 'xcode9-macos10.13' variables: - YARN_CACHE_FOLDER: .yarn-cache steps: - task: NodeTool@0 inputs: @@ -133,7 +128,7 @@ jobs: - task: CacheBeta@1 inputs: key: yarn | $(Agent.OS) | yarn.lock - path: $(YARN_CACHE_FOLDER) + path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' @@ -165,7 +160,7 @@ jobs: - task: CacheBeta@1 inputs: key: yarn | $(Agent.OS) | yarn.lock - path: $(YARN_CACHE_FOLDER) + path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' From 2960d5df53fd7626e121fdf1fe951bb93d75bf2d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 11:55:06 -0800 Subject: [PATCH 221/338] New cache key --- azure-pipelines.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9ccc8393..4eb35079 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,7 +20,7 @@ jobs: displayName: 'Install Yarn' - task: CacheBeta@1 inputs: - key: yarn | $(Agent.OS) | yarn.lock + key: yarn1 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile @@ -58,7 +58,7 @@ jobs: displayName: 'Install Node.js' - task: CacheBeta@1 inputs: - key: yarn | $(Agent.OS) | yarn.lock + key: yarn1 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile @@ -80,7 +80,7 @@ jobs: displayName: 'Install Node.js' - task: CacheBeta@1 inputs: - key: yarn | $(Agent.OS) | yarn.lock + key: yarn1 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile @@ -105,7 +105,7 @@ jobs: displayName: 'Install Yarn' - task: CacheBeta@1 inputs: - key: yarn | $(Agent.OS) | yarn.lock + key: yarn1 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile @@ -127,7 +127,7 @@ jobs: displayName: 'Install Node.js' - task: CacheBeta@1 inputs: - key: yarn | $(Agent.OS) | yarn.lock + key: yarn1 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile @@ -159,7 +159,7 @@ jobs: displayName: 'Install Yarn' - task: CacheBeta@1 inputs: - key: yarn | $(Agent.OS) | yarn.lock + key: yarn1 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile From d2164584d53dfc59362f0e5702b45a6f56554488 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 11:56:44 -0800 Subject: [PATCH 222/338] Fix error in yml --- azure-pipelines.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4eb35079..9117b885 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -93,7 +93,6 @@ jobs: - job: Linux_IntegrationTests pool: vmImage: 'ubuntu-16.04' - variables: steps: - task: NodeTool@0 inputs: @@ -119,7 +118,6 @@ jobs: - job: macOS_IntegrationTests pool: vmImage: 'xcode9-macos10.13' - variables: steps: - task: NodeTool@0 inputs: From c974b273d40c6e2f595539bcc2fa07152ce51fa3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 12:41:03 -0800 Subject: [PATCH 223/338] Install puppeteer explicitly --- azure-pipelines.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9117b885..ff13e616 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -109,6 +109,8 @@ jobs: displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' + - script: yarn add puppeteer + displayName: 'Install puppeteer' - script: | yarn start & sleep 10 @@ -130,6 +132,8 @@ jobs: displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' + - script: yarn add puppeteer + displayName: 'Install puppeteer' - script: | yarn start & sleep 10 From 0630baaf0295d4100c5d40e87463070c3fe316d3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 5 Nov 2019 12:50:37 -0800 Subject: [PATCH 224/338] Don't cache on integration tests --- azure-pipelines.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ff13e616..aaa2b6ea 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,7 +20,7 @@ jobs: displayName: 'Install Yarn' - task: CacheBeta@1 inputs: - key: yarn1 | $(Agent.OS) | yarn.lock + key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile @@ -58,7 +58,7 @@ jobs: displayName: 'Install Node.js' - task: CacheBeta@1 inputs: - key: yarn1 | $(Agent.OS) | yarn.lock + key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile @@ -80,7 +80,7 @@ jobs: displayName: 'Install Node.js' - task: CacheBeta@1 inputs: - key: yarn1 | $(Agent.OS) | yarn.lock + key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile @@ -102,11 +102,6 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - task: CacheBeta@1 - inputs: - key: yarn1 | $(Agent.OS) | yarn.lock - path: node_modules - displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn add puppeteer @@ -125,11 +120,6 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - task: CacheBeta@1 - inputs: - key: yarn1 | $(Agent.OS) | yarn.lock - path: node_modules - displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn add puppeteer @@ -161,7 +151,7 @@ jobs: displayName: 'Install Yarn' - task: CacheBeta@1 inputs: - key: yarn1 | $(Agent.OS) | yarn.lock + key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules displayName: Cache Yarn packages - script: yarn --frozen-lockfile From fc0568074ffb7577680c6646725ce2053a145ada Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Nov 2019 10:37:00 -0800 Subject: [PATCH 225/338] Cache node modules with puppeteer --- azure-pipelines.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index aaa2b6ea..b4a6d940 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -98,6 +98,11 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' + - task: CacheBeta@1 + inputs: + key: yarn_puppeteer | $(Agent.OS) | yarn.lock + path: node_modules + displayName: Cache Yarn packages - task: YarnInstaller@3 inputs: versionSpec: '1.x' @@ -120,6 +125,11 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' + - task: CacheBeta@1 + inputs: + key: yarn_puppeteer | $(Agent.OS) | yarn.lock + path: node_modules + displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn add puppeteer From d4dbdac73f6bd20764ee2e1626c0f9a4265502c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Nov 2019 10:53:42 -0800 Subject: [PATCH 226/338] Don't cache integration test modules --- azure-pipelines.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b4a6d940..7ddb534f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,7 +22,7 @@ jobs: inputs: key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules - displayName: Cache Yarn packages + displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn test-unit --forbid-only @@ -60,7 +60,7 @@ jobs: inputs: key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules - displayName: Cache Yarn packages + displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn test-unit --forbid-only @@ -82,7 +82,7 @@ jobs: inputs: key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules - displayName: Cache Yarn packages + displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn test-unit --forbid-only @@ -102,7 +102,7 @@ jobs: inputs: key: yarn_puppeteer | $(Agent.OS) | yarn.lock path: node_modules - displayName: Cache Yarn packages + displayName: Cache node modules - task: YarnInstaller@3 inputs: versionSpec: '1.x' @@ -125,11 +125,6 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - task: CacheBeta@1 - inputs: - key: yarn_puppeteer | $(Agent.OS) | yarn.lock - path: node_modules - displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: yarn add puppeteer @@ -159,11 +154,6 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' - - task: CacheBeta@1 - inputs: - key: yarn2 | $(Agent.OS) | yarn.lock - path: node_modules - displayName: Cache Yarn packages - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js From d079775690a5e93f4d06971c7cc151912a7e569b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Nov 2019 11:04:37 -0800 Subject: [PATCH 227/338] Merge coverage check into regular unit test check Fixes #2542 --- azure-pipelines.yml | 17 +++-------------- bin/test.js | 21 ++++++++++++++++++++- package.json | 1 + 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7ddb534f..415f1fb2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,26 +25,15 @@ jobs: displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-unit --forbid-only + - script: yarn test-coverage --forbid-only displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' - - script: | - NODE_PATH=$(pwd)/out ./node_modules/.bin/nyc ./node_modules/.bin/mocha './out/*test.js' './out/**/*test.js' - ./node_modules/.bin/nyc report --reporter=cobertura - displayName: 'Coverage report' - task: PublishCodeCoverageResults@1 inputs: codeCoverageTool: Cobertura summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml' displayName: 'Publish coverage' - - task: mspremier.BuildQualityChecks.QualityChecks-task.BuildQualityChecks@6 - displayName: 'Check build quality' - inputs: - checkCoverage: true - coverageType: lines - coverageThreshold: 60 - coverageFailOption: fixed - job: macOS pool: @@ -63,7 +52,7 @@ jobs: displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-unit --forbid-only + - script: yarn test-coverage --forbid-only displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' @@ -85,7 +74,7 @@ jobs: displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-unit --forbid-only + - script: yarn test-coverage --forbid-only displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' diff --git a/bin/test.js b/bin/test.js index 3b08d4a1..2b15e907 100644 --- a/bin/test.js +++ b/bin/test.js @@ -28,6 +28,26 @@ if (process.argv.length > 2) { } } +const checkCoverage = flagArgs.indexOf('--coverage') >= 0; + +if (checkCoverage) { + flagArgs.splice(flagArgs.indexOf('--coverage'), 1); + const executable = path.resolve(__dirname, '../node_modules/.bin/nyc'); + const args = ['--check-coverage', '--lines=100', path.resolve(__dirname, '../node_modules/.bin/mocha'), ...testFiles, ...flagArgs]; + console.info('executable', executable); + console.info('args', args); + const run = cp.spawnSync( + executable, + args, + { + cwd: path.resolve(__dirname, '..'), + env, + stdio: 'inherit' + } + ); + process.exit(run.status); +} + const run = cp.spawnSync( path.resolve(__dirname, '../node_modules/.bin/mocha'), [...testFiles, ...flagArgs], @@ -37,5 +57,4 @@ const run = cp.spawnSync( stdio: 'inherit' } ); - process.exit(run.status); diff --git a/package.json b/package.json index 0150569d..c33c48bd 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "posttest": "npm run lint", "test-api": "mocha \"**/*.api.js\"", "test-unit": "node ./bin/test.js", + "test-coverage": "node ./bin/test.js --coverage", "build": "tsc -b ./tsconfig.all.json", "prepare": "npm run setup", "setup": "npm run build", From 030a8bda80359623fd4a80cce7ee2c8d5dd2508f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Nov 2019 11:08:39 -0800 Subject: [PATCH 228/338] Set coverage lines threshold to 60% --- bin/test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/test.js b/bin/test.js index 2b15e907..94b4ac7b 100644 --- a/bin/test.js +++ b/bin/test.js @@ -6,6 +6,8 @@ const cp = require('child_process'); const path = require('path'); +const COVERAGE_LINES_THRESHOLD = 60; + // Add `out` to the NODE_PATH so absolute paths can be resolved. const env = { ...process.env }; env.NODE_PATH = path.resolve(__dirname, '../out'); @@ -33,7 +35,7 @@ const checkCoverage = flagArgs.indexOf('--coverage') >= 0; if (checkCoverage) { flagArgs.splice(flagArgs.indexOf('--coverage'), 1); const executable = path.resolve(__dirname, '../node_modules/.bin/nyc'); - const args = ['--check-coverage', '--lines=100', path.resolve(__dirname, '../node_modules/.bin/mocha'), ...testFiles, ...flagArgs]; + const args = ['--check-coverage', `--lines=${COVERAGE_LINES_THRESHOLD}`, path.resolve(__dirname, '../node_modules/.bin/mocha'), ...testFiles, ...flagArgs]; console.info('executable', executable); console.info('args', args); const run = cp.spawnSync( From 48b20e32005d98a905c463c1acf9acc0bd9fd6b7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Nov 2019 11:08:47 -0800 Subject: [PATCH 229/338] Don't run through nyc on mac and Windows --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 415f1fb2..a4f6ede8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -52,7 +52,7 @@ jobs: displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-coverage --forbid-only + - script: yarn test-unit --forbid-only displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' @@ -74,7 +74,7 @@ jobs: displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-coverage --forbid-only + - script: yarn test-unit --forbid-only displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' From fe2f1ea4df1b0b426057944af9d5d352bbd96fc7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Nov 2019 11:20:52 -0800 Subject: [PATCH 230/338] Get Windows unit tests running again Fixes #2543 --- bin/test.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bin/test.js b/bin/test.js index 94b4ac7b..bd1784fb 100644 --- a/bin/test.js +++ b/bin/test.js @@ -34,8 +34,8 @@ const checkCoverage = flagArgs.indexOf('--coverage') >= 0; if (checkCoverage) { flagArgs.splice(flagArgs.indexOf('--coverage'), 1); - const executable = path.resolve(__dirname, '../node_modules/.bin/nyc'); - const args = ['--check-coverage', `--lines=${COVERAGE_LINES_THRESHOLD}`, path.resolve(__dirname, '../node_modules/.bin/mocha'), ...testFiles, ...flagArgs]; + const executable = npmBinScript('nyc'); + const args = ['--check-coverage', `--lines=${COVERAGE_LINES_THRESHOLD}`, npmBinScript('mocha'), ...testFiles, ...flagArgs]; console.info('executable', executable); console.info('args', args); const run = cp.spawnSync( @@ -51,7 +51,7 @@ if (checkCoverage) { } const run = cp.spawnSync( - path.resolve(__dirname, '../node_modules/.bin/mocha'), + npmBinScript('mocha'), [...testFiles, ...flagArgs], { cwd: path.resolve(__dirname, '..'), @@ -59,4 +59,9 @@ const run = cp.spawnSync( stdio: 'inherit' } ); + +function npmBinScript(script) { + return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script)); +} + process.exit(run.status); From 31afe5b30545da59a965ff69ba756c54bbe02308 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Nov 2019 11:23:42 -0800 Subject: [PATCH 231/338] test-coverage -> test-unit-coverage --- azure-pipelines.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a4f6ede8..1ee66ddb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,7 @@ jobs: displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-coverage --forbid-only + - script: yarn test-unit-coverage --forbid-only displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' diff --git a/package.json b/package.json index c33c48bd..7ed71741 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "posttest": "npm run lint", "test-api": "mocha \"**/*.api.js\"", "test-unit": "node ./bin/test.js", - "test-coverage": "node ./bin/test.js --coverage", + "test-unit-coverage": "node ./bin/test.js --coverage", "build": "tsc -b ./tsconfig.all.json", "prepare": "npm run setup", "setup": "npm run build", From 08782949648f064620a1cc646d7199cd70842c36 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Nov 2019 11:24:34 -0800 Subject: [PATCH 232/338] Skip Terminal2 tests on Windows --- src/Terminal2.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index efb2a446..f7046240 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -34,7 +34,6 @@ if (os.platform() === 'darwin') { // filter skipFilenames const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1); - describe('Escape Sequence Files', function(): void { this.timeout(1000); @@ -44,6 +43,9 @@ describe('Escape Sequence Files', function(): void { let customHandler: IDisposable | undefined; before(() => { + if (process.platform === 'win32') { + return; + } ptyTerm = (pty as any).open({cols: COLS, rows: ROWS}); slaveEnd = ptyTerm._slave; term = new Terminal({cols: COLS, rows: ROWS}); @@ -51,12 +53,15 @@ describe('Escape Sequence Files', function(): void { }); after(() => { + if (process.platform === 'win32') { + return; + } ptyTerm._master.end(); ptyTerm._master.destroy(); }); FILES.forEach(filename => { - it(filename.split('/').slice(-1)[0], async () => { + (process.platform === 'win32' ? it.skip : it)(filename.split('/').slice(-1)[0], async () => { // reset terminal and handler if (customHandler) { customHandler.dispose(); From 94ce037eb8d750839ea8ddaa87e712ab042a2f58 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 13:00:43 -0800 Subject: [PATCH 233/338] Add integration tests for regression in #2444 --- addons/xterm-addon-search/fixtures/issue-2444 | 70 ++++++++++++++++ .../xterm-addon-search/src/SearchAddon.api.ts | 84 ++++++++++++++++++- 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 addons/xterm-addon-search/fixtures/issue-2444 diff --git a/addons/xterm-addon-search/fixtures/issue-2444 b/addons/xterm-addon-search/fixtures/issue-2444 new file mode 100644 index 00000000..cb7f7878 --- /dev/null +++ b/addons/xterm-addon-search/fixtures/issue-2444 @@ -0,0 +1,70 @@ +/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles +make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release' +make[3]: Nothing to be done for `libsrc/wrap_curl/CMakeFiles/wrap_curl.dir/build'. +make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release' +[ 0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles +[ 0%] [ 0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles +Built target config_utils +[ 0%] make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release' +/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles +Built target omega +make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release' +cd /home/yan/ssd/searchtrunk/tmp/release && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/yan/ssd/searchtrunk /home/yan/ssd/searchtrunk/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp/CMakeFiles/protogen.dir/DependInfo.cmake --color= +/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles +Built target morpheus +make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release' +make -f libsrc/base/CMakeFiles/base.dir/build.make libsrc/base/CMakeFiles/base.dir/build +make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release' +make[3]: Nothing to be done for `libsrc/coroutine/CMakeFiles/coroutine.dir/build'. +Built target wrap_curl +make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release' +/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles +/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles +[ 0%] [ 0%] [ 0%] [ 0%] Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o +cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/crop_context.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp +Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o +Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o +cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/detector.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp +Built target coroutine +cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/image_crop.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp +make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release' +make[3]: Nothing to be done for `libsrc/base/CMakeFiles/base.dir/build'. +make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release' +/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles +make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release' +make -f projects/protogen/cpp/CMakeFiles/protogen.dir/build.make projects/protogen/cpp/CMakeFiles/protogen.dir/build +[ 0%] Built target base +In file included from /home/yan/ssd/searchtrunk/include/image_crop/detector.hpp:3:0, + from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp:2: +/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory + #include + ^~~~~~~~~~~~~~~~~ +compilation terminated. +make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o] Error 1 +make[3]: *** Waiting for unfinished jobs.... +In file included from /home/yan/ssd/searchtrunk/include/image_crop/image_crop.hpp:3:0, + from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp:1: +/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory + #include + ^~~~~~~~~~~~~~~~~ +compilation terminated. +make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o] Error 1 +In file included from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp:1:0: +/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory + #include + ^~~~~~~~~~~~~~~~~ +compilation terminated. +make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o] Error 1 +make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release' +make[2]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/all] Error 2 +make[2]: *** Waiting for unfinished jobs.... +make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release' +make[3]: Nothing to be done for `projects/protogen/cpp/CMakeFiles/protogen.dir/build'. +make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release' +/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles 63 64 65 66 +[ 25%] Built target protogen +make[2]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release' +make[1]: *** [all] Error 2 +make[1]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release/projects/projectX' +make: *** [install] Error 2 +yan@yPC:~/ssd/searchtrunk/projects/projectX$ diff --git a/addons/xterm-addon-search/src/SearchAddon.api.ts b/addons/xterm-addon-search/src/SearchAddon.api.ts index e3520116..ff195f5e 100644 --- a/addons/xterm-addon-search/src/SearchAddon.api.ts +++ b/addons/xterm-addon-search/src/SearchAddon.api.ts @@ -6,6 +6,8 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { readFile } from 'fs'; +import { resolve } from 'path'; const APP = 'http://127.0.0.1:3000/test'; @@ -91,7 +93,7 @@ describe('Search Tests', function (): void { selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc'); }); - it ('Simple Regex', async () => { + it('Simple Regex', async () => { await writeSync('abc123defABCD'); await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`); assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc'); @@ -106,6 +108,86 @@ describe('Search Tests', function (): void { assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true); assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc'); }); + + describe('Regression tests', () => { + describe('#2444 wrapped line content not being found', () => { + let fixture: string; + before(async () => { + const rawFixture = await new Promise(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data))); + fixture = rawFixture.toString() + .replace(/\n/g, '\\n\\r') + .replace(/'/g, '\\\''); + }); + it('should find all occurrences using findNext', async () => { + await writeSync(fixture); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 }); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 }); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 }); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 }); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 }); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 }); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 }); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 }); + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 }); + // Wrap around to first result + assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 }); + }); + it('should find all occurrences using findPrevious', async () => { + await writeSync(fixture); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 }); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 }); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 }); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 }); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 }); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 }); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 }); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 }); + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 }); + // Wrap around to first result + assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); + selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); + assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 }); + }); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { From fce7a67c8e73b9a7eba8767e5c1205f7dbf5bf5b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 13:39:42 -0800 Subject: [PATCH 234/338] Remove type jsdoc --- addons/xterm-addon-search/src/SearchAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 67f1ad6e..db2968e6 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -239,7 +239,7 @@ export class SearchAddon implements ITerminalAddon { * started on an earlier line then it is skipped since it will be properly searched when the terminal line that the * text starts on is searched. * @param term The search term. - * @param {ISearchPosition} position The position to start the search. + * @param position The position to start the search. * @param searchOptions Search options. * @return The search result if it was found. */ From 1d63be0506e70cd9ab36c4ea8f9f6478a09a8422 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 13:51:51 -0800 Subject: [PATCH 235/338] Reduce duplicate in pipeline, cache in release job --- azure-pipelines.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1ee66ddb..4daa0961 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -87,19 +87,12 @@ jobs: inputs: versionSpec: '8.x' displayName: 'Install Node.js' - - task: CacheBeta@1 - inputs: - key: yarn_puppeteer | $(Agent.OS) | yarn.lock - path: node_modules - displayName: Cache node modules - task: YarnInstaller@3 inputs: versionSpec: '1.x' displayName: 'Install Yarn' - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn add puppeteer - displayName: 'Install puppeteer' - script: | yarn start & sleep 10 @@ -116,8 +109,6 @@ jobs: displayName: 'Install Node.js' - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn add puppeteer - displayName: 'Install puppeteer' - script: | yarn start & sleep 10 @@ -143,6 +134,10 @@ jobs: inputs: versionSpec: '1.x' displayName: 'Install Yarn' + - task: CacheBeta@1 + inputs: + key: yarn2 | $(Agent.OS) | yarn.lock + path: node_modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js From 7c39217392e498442fdf64e3cac1ec6489e13136 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 17:35:22 -0800 Subject: [PATCH 236/338] Break DomRenderer dependency on ITerminal Part of #1507 --- src/Terminal.ts | 2 +- src/renderer/dom/DomRenderer.ts | 102 ++++++++++++++++---------------- 2 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 537ecb74..5e2fa04e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -638,7 +638,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _createRenderer(): IRenderer { switch (this.options.rendererType) { case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService); - case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService, this.optionsService); + case 'dom': return new DomRenderer(this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier, this._charSizeService, this.optionsService, this._bufferService, this._renderService); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } } diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index ef927f20..1beaadcd 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -4,13 +4,12 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; -import { ITerminal } from '../../Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { Disposable } from 'common/Lifecycle'; -import { IColorSet, ILinkifierEvent } from 'browser/Types'; -import { ICharSizeService } from 'browser/services/Services'; -import { IOptionsService } from 'common/services/Services'; +import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types'; +import { ICharSizeService, IRenderService } from 'browser/services/Services'; +import { IOptionsService, IBufferService } from 'common/services/Services'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -39,10 +38,15 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; constructor( - private _terminal: ITerminal, private _colors: IColorSet, - private _charSizeService: ICharSizeService, - private _optionsService: IOptionsService + private readonly _element: HTMLElement, + private readonly _screenElement: HTMLElement, + private readonly _viewportElement: HTMLElement, + private readonly _linkifier: ILinkifier, + private readonly _charSizeService: ICharSizeService, + private readonly _optionsService: IOptionsService, + private readonly _bufferService: IBufferService, + private readonly _renderService: IRenderService ) { super(); @@ -50,7 +54,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._rowContainer.classList.add(ROW_CONTAINER_CLASS); this._rowContainer.style.lineHeight = 'normal'; this._rowContainer.setAttribute('aria-hidden', 'true'); - this._refreshRowElements(this._terminal.cols, this._terminal.rows); + this._refreshRowElements(this._bufferService.cols, this._bufferService.rows); this._selectionContainer = document.createElement('div'); this._selectionContainer.classList.add(SELECTION_CLASS); this._selectionContainer.setAttribute('aria-hidden', 'true'); @@ -74,36 +78,36 @@ export class DomRenderer extends Disposable implements IRenderer { this._rowFactory = new DomRendererRowFactory(document, this._optionsService); - this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); - this._terminal.screenElement.appendChild(this._rowContainer); - this._terminal.screenElement.appendChild(this._selectionContainer); + this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); + this._screenElement.appendChild(this._rowContainer); + this._screenElement.appendChild(this._selectionContainer); - this._terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); - this._terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); + this._linkifier.onLinkHover(e => this._onLinkHover(e)); + this._linkifier.onLinkLeave(e => this._onLinkLeave(e)); } public dispose(): void { - this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); - this._terminal.screenElement.removeChild(this._rowContainer); - this._terminal.screenElement.removeChild(this._selectionContainer); - this._terminal.screenElement.removeChild(this._themeStyleElement); - this._terminal.screenElement.removeChild(this._dimensionsStyleElement); + this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); + this._screenElement.removeChild(this._rowContainer); + this._screenElement.removeChild(this._selectionContainer); + this._screenElement.removeChild(this._themeStyleElement); + this._screenElement.removeChild(this._dimensionsStyleElement); super.dispose(); } private _updateDimensions(): void { this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio; this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio); - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight); this.dimensions.scaledCharLeft = 0; this.dimensions.scaledCharTop = 0; - this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols; - this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows; + this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols; + this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._bufferService.rows; this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); - this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; - this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; + this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; + this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; this._rowElements.forEach(element => { element.style.width = `${this.dimensions.canvasWidth}px`; @@ -115,7 +119,7 @@ export class DomRenderer extends Disposable implements IRenderer { if (!this._dimensionsStyleElement) { this._dimensionsStyleElement = document.createElement('style'); - this._terminal.screenElement.appendChild(this._dimensionsStyleElement); + this._screenElement.appendChild(this._dimensionsStyleElement); } const styles = @@ -128,9 +132,9 @@ export class DomRenderer extends Disposable implements IRenderer { this._dimensionsStyleElement.innerHTML = styles; - this._selectionContainer.style.height = (this._terminal)._viewportElement.style.height; - this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; - this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._selectionContainer.style.height = this._viewportElement.style.height; + this._screenElement.style.width = `${this.dimensions.canvasWidth}px`; + this._screenElement.style.height = `${this.dimensions.canvasHeight}px`; } public setColors(colors: IColorSet): void { @@ -141,7 +145,7 @@ export class DomRenderer extends Disposable implements IRenderer { private _injectCss(): void { if (!this._themeStyleElement) { this._themeStyleElement = document.createElement('style'); - this._terminal.screenElement.appendChild(this._themeStyleElement); + this._screenElement.appendChild(this._themeStyleElement); } // Base CSS @@ -149,16 +153,16 @@ export class DomRenderer extends Disposable implements IRenderer { `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + ` color: ${this._colors.foreground.css};` + ` background-color: ${this._colors.background.css};` + - ` font-family: ${this._terminal.options.fontFamily};` + - ` font-size: ${this._terminal.options.fontSize}px;` + + ` font-family: ${this._optionsService.options.fontFamily};` + + ` font-size: ${this._optionsService.options.fontSize}px;` + `}`; // Text styles styles += `${this._terminalSelector} span:not(.${BOLD_CLASS}) {` + - ` font-weight: ${this._terminal.options.fontWeight};` + + ` font-weight: ${this._optionsService.options.fontWeight};` + `}` + `${this._terminalSelector} span.${BOLD_CLASS} {` + - ` font-weight: ${this._terminal.options.fontWeightBold};` + + ` font-weight: ${this._optionsService.options.fontWeightBold};` + `}` + `${this._terminalSelector} span.${ITALIC_CLASS} {` + ` font-style: italic;` + @@ -275,13 +279,13 @@ export class DomRenderer extends Disposable implements IRenderer { } // Translate from buffer position to viewport position - const viewportStartRow = start[1] - this._terminal.buffer.ydisp; - const viewportEndRow = end[1] - this._terminal.buffer.ydisp; + const viewportStartRow = start[1] - this._bufferService.buffer.ydisp; + const viewportEndRow = end[1] - this._bufferService.buffer.ydisp; const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); + const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1); // No need to draw the selection - if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { + if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) { return; } @@ -295,15 +299,15 @@ export class DomRenderer extends Disposable implements IRenderer { } else { // Draw first row const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols; documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); // Draw middle rows const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount)); // Draw final row if (viewportCappedStartRow !== viewportCappedEndRow) { // Only draw viewportEndRow if it's not the same as viewporttartRow - const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols; documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); } } @@ -333,7 +337,7 @@ export class DomRenderer extends Disposable implements IRenderer { // Force a refresh this._updateDimensions(); this._injectCss(); - this._terminal.refresh(0, this._terminal.rows - 1); + this._renderService.refreshRows(0, this._bufferService.rows - 1); } public clear(): void { @@ -341,20 +345,18 @@ export class DomRenderer extends Disposable implements IRenderer { } public renderRows(start: number, end: number): void { - const terminal = this._terminal; - - const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y; - const cursorX = this._terminal.buffer.x; - const cursorBlink = this._terminal.options.cursorBlink; + const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; + const cursorX = this._bufferService.buffer.x; + const cursorBlink = this._optionsService.options.cursorBlink; for (let y = start; y <= end; y++) { const rowElement = this._rowElements[y]; rowElement.innerHTML = ''; - 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, cursorBlink, this.dimensions.actualCellWidth, terminal.cols)); + const row = y + this._bufferService.buffer.ydisp; + const lineData = this._bufferService.buffer.lines.get(row); + const cursorStyle = this._optionsService.options.cursorStyle; + rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols)); } } From 82e18d7aaa4cb9219bcce0acd1de6298635bbf88 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 17:41:29 -0800 Subject: [PATCH 237/338] Move DomRenderer to browser, fix strict errors --- src/Terminal.ts | 4 +- src/{ => browser}/renderer/dom/DomRenderer.ts | 40 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) rename src/{ => browser}/renderer/dom/DomRenderer.ts (94%) diff --git a/src/Terminal.ts b/src/Terminal.ts index 5e2fa04e..6626b966 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -38,7 +38,7 @@ import { SoundService } from 'browser/services/SoundService'; import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; -import { DomRenderer } from './renderer/dom/DomRenderer'; +import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; @@ -638,7 +638,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _createRenderer(): IRenderer { switch (this.options.rendererType) { case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService); - case 'dom': return new DomRenderer(this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier, this._charSizeService, this.optionsService, this._bufferService, this._renderService); + case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } } diff --git a/src/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts similarity index 94% rename from src/renderer/dom/DomRenderer.ts rename to src/browser/renderer/dom/DomRenderer.ts index 1beaadcd..b512aa70 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -29,8 +29,8 @@ export class DomRenderer extends Disposable implements IRenderer { private _rowFactory: DomRendererRowFactory; private _terminalClass: number = nextTerminalId++; - private _themeStyleElement: HTMLStyleElement; - private _dimensionsStyleElement: HTMLStyleElement; + private _themeStyleElement!: HTMLStyleElement; + private _dimensionsStyleElement!: HTMLStyleElement; private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; private _selectionContainer: HTMLElement; @@ -43,10 +43,10 @@ export class DomRenderer extends Disposable implements IRenderer { private readonly _screenElement: HTMLElement, private readonly _viewportElement: HTMLElement, private readonly _linkifier: ILinkifier, - private readonly _charSizeService: ICharSizeService, - private readonly _optionsService: IOptionsService, - private readonly _bufferService: IBufferService, - private readonly _renderService: IRenderService + @ICharSizeService private readonly _charSizeService: ICharSizeService, + @IOptionsService private readonly _optionsService: IOptionsService, + @IBufferService private readonly _bufferService: IBufferService, + @IRenderService private readonly _renderService: IRenderService ) { super(); @@ -60,18 +60,18 @@ export class DomRenderer extends Disposable implements IRenderer { this._selectionContainer.setAttribute('aria-hidden', 'true'); this.dimensions = { - scaledCharWidth: null, - scaledCharHeight: null, - scaledCellWidth: null, - scaledCellHeight: null, - scaledCharLeft: null, - scaledCharTop: null, - scaledCanvasWidth: null, - scaledCanvasHeight: null, - canvasWidth: null, - canvasHeight: null, - actualCellWidth: null, - actualCellHeight: null + scaledCharWidth: 0, + scaledCharHeight: 0, + scaledCellWidth: 0, + scaledCellHeight: 0, + scaledCharLeft: 0, + scaledCharTop: 0, + scaledCanvasWidth: 0, + scaledCanvasHeight: 0, + canvasWidth: 0, + canvasHeight: 0, + actualCellWidth: 0, + actualCellHeight: 0 }; this._updateDimensions(); this._injectCss(); @@ -246,7 +246,7 @@ export class DomRenderer extends Disposable implements IRenderer { } // Remove excess elements while (this._rowElements.length > rows) { - this._rowContainer.removeChild(this._rowElements.pop()); + this._rowContainer.removeChild(this._rowElements.pop()!); } } @@ -356,7 +356,7 @@ export class DomRenderer extends Disposable implements IRenderer { const row = y + this._bufferService.buffer.ydisp; const lineData = this._bufferService.buffer.lines.get(row); const cursorStyle = this._optionsService.options.cursorStyle; - rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols)); + rowElement.appendChild(this._rowFactory.createRow(lineData!, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols)); } } From 509d0e701945c7313dae4d8ef7cfdfc2c91487a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 17:44:03 -0800 Subject: [PATCH 238/338] Remove some ITerminal usage from Renderer --- src/renderer/Renderer.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index d3977af3..5bc9d004 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -30,19 +30,19 @@ export class Renderer extends Disposable implements IRenderer { constructor( private _colors: IColorSet, private readonly _terminal: ITerminal, - readonly bufferService: IBufferService, + private readonly _bufferService: IBufferService, private readonly _charSizeService: ICharSizeService, - readonly optionsService: IOptionsService + private readonly _optionsService: IOptionsService ) { super(); - const allowTransparency = this._terminal.options.allowTransparency; - this._characterJoinerRegistry = new CharacterJoinerRegistry(bufferService); + const allowTransparency = this._optionsService.options.allowTransparency; + this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService); this._renderLayers = [ - new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, bufferService, optionsService), - new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, bufferService, optionsService), - new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, bufferService, optionsService), - new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, bufferService, optionsService) + new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService), + new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, this._bufferService, _optionsService), + new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, this._bufferService, _optionsService), + new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, this._bufferService, _optionsService) ]; this.dimensions = { scaledCharWidth: null, @@ -74,7 +74,7 @@ export class Renderer extends Disposable implements IRenderer { // and the terminal needs to refreshed if (this._devicePixelRatio !== window.devicePixelRatio) { this._devicePixelRatio = window.devicePixelRatio; - this.onResize(this._terminal.cols, this._terminal.rows); + this.onResize(this._bufferService.cols, this._bufferService.rows); } } @@ -101,7 +101,7 @@ export class Renderer extends Disposable implements IRenderer { } public onCharSizeChanged(): void { - this.onResize(this._terminal.cols, this._terminal.rows); + this.onResize(this._bufferService.cols, this._bufferService.rows); } public onBlur(): void { @@ -163,23 +163,23 @@ export class Renderer extends Disposable implements IRenderer { // will be floored because since lineHeight can never be lower then 1, there // is a guarentee that the scaled line height will always be larger than // scaled char height. - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight); // Calculate the y coordinate within a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCharTop = this._optionsService.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); // Calculate the scaled cell width, taking the letterSpacing into account. - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing); // Calculate the x coordinate with a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); + this.dimensions.scaledCharLeft = Math.floor(this._optionsService.options.letterSpacing / 2); // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas - this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; - this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth; // The the size of the canvas on the page. It's very important that this // rounds to nearest integer and not ceils as browsers often set @@ -194,8 +194,8 @@ export class Renderer extends Disposable implements IRenderer { // account window.devicePixelRatio. ICharSizeService.width/height by itself // is insufficient when the page is not at 100% zoom level as it's measured // in CSS pixels, but the actual char size on the canvas can differ. - this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; - this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; + this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; + this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { From 1e1038b2c7787ed0e050d8286d3e4fff7a7b61c8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 18:09:13 -0800 Subject: [PATCH 239/338] Remove renderer circular dep on renderservice Also fix super.dispose not being called correctly on RenderService --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 12 ++++++++---- .../src/renderLayer/CursorRenderLayer.ts | 16 +++++++++++----- src/TestUtils.test.ts | 3 ++- src/browser/renderer/Types.d.ts | 8 ++++++++ src/browser/renderer/dom/DomRenderer.ts | 13 ++++++++----- src/browser/services/RenderService.ts | 5 +++++ src/renderer/CursorRenderLayer.ts | 10 ++++++---- src/renderer/Renderer.ts | 8 ++++++-- 8 files changed, 54 insertions(+), 21 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 3f8163ba..1f28d589 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -15,13 +15,14 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Terminal } from 'xterm'; +import { Terminal, IEvent } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; -import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; +import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; import { FLAGS } from './Constants'; import { getCompatAttr } from './CharDataCompat'; +import { EventEmitter } from 'common/EventEmitter'; export const INDICIES_PER_CELL = 4; @@ -41,6 +42,9 @@ export class WebglRenderer extends Disposable implements IRenderer { private _core: ITerminal; + private _onRequestRefreshRows = new EventEmitter(); + public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } + constructor( private _terminal: Terminal, private _colors: IColorSet, @@ -54,7 +58,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers = [ new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core), - new CursorRenderLayer(this._core.screenElement, 3, this._colors) + new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows) ]; this.dimensions = { scaledCharWidth: 0, @@ -186,7 +190,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.updateSelection(this._model, columnSelectMode); // TODO: #2102 Should this move to RenderCoordinator? - this._core.refresh(0, this._terminal.rows - 1); + this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 }); } public onCursorMove(): void { diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 8a67526e..c7c68ff6 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -8,7 +8,8 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; +import { IEventEmitter } from 'common/EventEmitter'; interface ICursorState { x: number; @@ -29,7 +30,12 @@ export class CursorRenderLayer extends BaseRenderLayer { private _cursorBlinkStateManager: CursorBlinkStateManager | undefined; private _cell: ICellData = new CellData(); - constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { + constructor( + container: HTMLElement, + zIndex: number, + colors: IColorSet, + private _onRequestRefreshRowsEvent: IEventEmitter + ) { super(container, 'cursor', zIndex, true, colors); this._state = { x: 0, @@ -70,14 +76,14 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.pause(); } - terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY }); } public onFocus(terminal: Terminal): void { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.resume(terminal); } else { - terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY }); } } @@ -95,7 +101,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal - terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY }); } public onCursorMove(terminal: Terminal): void { diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index a70f751c..e7c48381 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types'; @@ -362,6 +362,7 @@ export class MockBuffer implements IBuffer { } export class MockRenderer implements IRenderer { + onRequestRefreshRows: IEvent; onCanvasResize: IEvent<{ width: number; height: number; }>; onRender: IEvent<{ start: number; end: number; }>; dispose(): void { diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 7f84dfd4..b4c321a1 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -5,6 +5,7 @@ import { IDisposable } from 'common/Types'; import { IColorSet } from 'browser/Types'; +import { IEvent } from 'common/EventEmitter'; export type CharacterJoinerHandler = (text: string) => [number, number][]; @@ -23,6 +24,11 @@ export interface IRenderDimensions { actualCellHeight: number; } +export interface IRequestRefreshRowsEvent { + start: number; + end: number; +} + /** * Note that IRenderer implementations should emit the refresh event after * rendering rows to the screen. @@ -30,6 +36,8 @@ export interface IRenderDimensions { export interface IRenderer extends IDisposable { readonly dimensions: IRenderDimensions; + readonly onRequestRefreshRows: IEvent; + dispose(): void; setColors(colors: IColorSet): void; onDevicePixelRatioChange(): void; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index b512aa70..3d544a90 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -3,13 +3,14 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types'; -import { ICharSizeService, IRenderService } from 'browser/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService, IBufferService } from 'common/services/Services'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -37,6 +38,9 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; + private _onRequestRefreshRows = new EventEmitter(); + public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } + constructor( private _colors: IColorSet, private readonly _element: HTMLElement, @@ -45,8 +49,7 @@ export class DomRenderer extends Disposable implements IRenderer { private readonly _linkifier: ILinkifier, @ICharSizeService private readonly _charSizeService: ICharSizeService, @IOptionsService private readonly _optionsService: IOptionsService, - @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService + @IBufferService private readonly _bufferService: IBufferService ) { super(); @@ -337,7 +340,7 @@ export class DomRenderer extends Disposable implements IRenderer { // Force a refresh this._updateDimensions(); this._injectCss(); - this._renderService.refreshRows(0, this._bufferService.rows - 1); + this._onRequestRefreshRows.fire({ start: 0, end: this._bufferService.rows - 1 }); } public clear(): void { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 63084f54..b28c9971 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -51,6 +51,9 @@ export class RenderService extends Disposable implements IRenderService { this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged())); + // No need to register this as renderer is explicitly disposed in RenderService.dispose + this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end)); + // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange())); @@ -105,12 +108,14 @@ export class RenderService extends Disposable implements IRenderService { public dispose(): void { this._renderer.dispose(); + super.dispose(); } public setRenderer(renderer: IRenderer): void { // TODO: RenderService should be the only one to dispose the renderer this._renderer.dispose(); this._renderer = renderer; + this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end)); this.refreshRows(0, this._rowCount - 1); } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 1f2791f1..d2278983 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -3,13 +3,14 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { BaseRenderLayer } from '../browser/renderer/BaseRenderLayer'; import { ITerminal } from '../Types'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IEventEmitter } from 'common/EventEmitter'; interface ICursorState { x: number; @@ -36,6 +37,7 @@ export class CursorRenderLayer extends BaseRenderLayer { colors: IColorSet, private _terminal: ITerminal, rendererId: number, + private _onRequestRefreshRowsEvent: IEventEmitter, readonly bufferService: IBufferService, readonly optionsService: IOptionsService ) { @@ -80,14 +82,14 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.pause(); } - this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y); + this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } public onFocus(): void { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.resume(); } else { - this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y); + this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } } @@ -106,7 +108,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal - this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y); + this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } public onCursorMove(): void { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 5bc9d004..45abbb82 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -6,7 +6,7 @@ import { TextRenderLayer } from '../browser/renderer/TextRenderLayer'; import { SelectionRenderLayer } from '../browser/renderer/SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; -import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry } from 'browser/renderer/Types'; +import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { ITerminal } from '../Types'; import { LinkRenderLayer } from '../browser/renderer/LinkRenderLayer'; import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry'; @@ -15,6 +15,7 @@ import { IColorSet } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; let nextRendererId = 1; @@ -27,6 +28,9 @@ export class Renderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; + private _onRequestRefreshRows = new EventEmitter(); + public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } + constructor( private _colors: IColorSet, private readonly _terminal: ITerminal, @@ -42,7 +46,7 @@ export class Renderer extends Disposable implements IRenderer { new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService), new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, this._bufferService, _optionsService), new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, this._bufferService, _optionsService), - new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, this._bufferService, _optionsService) + new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService) ]; this.dimensions = { scaledCharWidth: null, From 9d7935f8bdce02f669ffa757f1e2413ef6c3e1fe Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 18:21:06 -0800 Subject: [PATCH 240/338] Move cursorState into core service --- src/Terminal.test.ts | 11 --------- src/Terminal.ts | 11 +++------ src/Types.d.ts | 1 - src/common/TestUtils.test.ts | 3 +++ src/common/services/CoreService.test.ts | 32 +++++++++++++++++++++++++ src/common/services/CoreService.ts | 3 +++ src/common/services/Services.ts | 7 ++++++ src/renderer/CursorRenderLayer.ts | 7 +++--- src/renderer/Renderer.ts | 7 +++--- 9 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 src/common/services/CoreService.test.ts diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 667bf076..b55a516b 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -163,17 +163,6 @@ describe('Terminal', () => { }); }); - describe('reset', () => { - it('should not affect cursorState', () => { - term.cursorState = 1; - term.reset(); - assert.equal(term.cursorState, 1); - term.cursorState = 0; - term.reset(); - assert.equal(term.cursorState, 0); - }); - }); - describe('clear', () => { it('should clear a buffer equal to rows', () => { const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y); diff --git a/src/Terminal.ts b/src/Terminal.ts index 6626b966..f8f2c946 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -90,8 +90,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // TODO: We should remove options once components adopt optionsService public get options(): ITerminalOptions { return this.optionsService.options; } - // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options - public cursorState: number; public cursorHidden: boolean; private _customKeyEventHandler: CustomKeyEventHandler; @@ -251,7 +249,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _setup(): void { this._parent = document ? document.body : null; - this.cursorState = 0; this.cursorHidden = false; this._customKeyEventHandler = null; @@ -637,7 +634,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService); + case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService, this._coreService); case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } @@ -945,8 +942,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * Display the cursor element */ public showCursor(): void { - if (!this.cursorState) { - this.cursorState = 1; + if (!this._coreService.isCursorInitialized) { + this._coreService.isCursorInitialized = true; this.refresh(this.buffer.y, this.buffer.y); } } @@ -1510,7 +1507,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.options.cols = this.cols; const customKeyEventHandler = this._customKeyEventHandler; const inputHandler = this._inputHandler; - const cursorState = this.cursorState; const userScrolling = this._userScrolling; this._setup(); @@ -1522,7 +1518,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // reattach this._customKeyEventHandler = customKeyEventHandler; this._inputHandler = inputHandler; - this.cursorState = cursorState; this._userScrolling = userScrolling; // do a full screen refresh diff --git a/src/Types.d.ts b/src/Types.d.ts index be1019e5..11362058 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -155,7 +155,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc screenElement: HTMLElement; browser: IBrowser; cursorHidden: boolean; - cursorState: number; buffer: IBuffer; buffers: IBufferSet; isFocused: boolean; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index f2721bbe..7d9c3aa6 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -43,6 +43,9 @@ export class MockCoreMouseService implements ICoreMouseService { } export class MockCoreService implements ICoreService { + isCursorInitialized: boolean = false; + isCursorHidden: boolean = false; + isFocused: boolean = false; serviceBrand: any; decPrivateModes: IDecPrivateModes = {} as any; onData: IEvent = new EventEmitter().event; diff --git a/src/common/services/CoreService.test.ts b/src/common/services/CoreService.test.ts new file mode 100644 index 00000000..00be4953 --- /dev/null +++ b/src/common/services/CoreService.test.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ICoreService } from 'common/services/Services'; +import { CoreService } from 'common/services/CoreService'; +import { MockBufferService, MockLogService, MockOptionsService } from 'common/TestUtils.test'; +import { assert } from 'chai'; + +describe('CoreService', () => { + let coreService: ICoreService; + + beforeEach(() => { + coreService = new CoreService( + () => {}, + new MockBufferService(80, 30), + new MockLogService(), + new MockOptionsService()); + }); + + describe('reset', () => { + it('should not affect isCursorInitialized', () => { + coreService.isCursorInitialized = true; + coreService.reset(); + assert.equal(coreService.isCursorInitialized, true); + coreService.isCursorInitialized = false; + coreService.reset(); + assert.equal(coreService.isCursorInitialized, false); + }); + }); +}); diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 0e0ba609..a235e539 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -15,6 +15,9 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ export class CoreService implements ICoreService { serviceBrand: any; + public isCursorInitialized: boolean = false; + public isCursorHidden: boolean = false; + public isFocused: boolean = false; public decPrivateModes: IDecPrivateModes; private _onData = new EventEmitter(); diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 0872d3db..c067448d 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -58,6 +58,13 @@ export const ICoreService = createDecorator('CoreService'); export interface ICoreService { serviceBrand: any; + /** + * Initially the cursor will not be visible until the first time the terminal + * is focused. + */ + isCursorInitialized: boolean; + isCursorHidden: boolean; + isFocused: boolean; readonly decPrivateModes: IDecPrivateModes; readonly onData: IEvent; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index d2278983..bbbbcea4 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -9,7 +9,7 @@ import { ITerminal } from '../Types'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; interface ICursorState { @@ -39,7 +39,8 @@ export class CursorRenderLayer extends BaseRenderLayer { rendererId: number, private _onRequestRefreshRowsEvent: IEventEmitter, readonly bufferService: IBufferService, - readonly optionsService: IOptionsService + readonly optionsService: IOptionsService, + private readonly _coreService: ICoreService ) { super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService); this._state = { @@ -127,7 +128,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _render(triggeredByAnimationFrame: boolean): void { // Don't draw the cursor if it's hidden - if (!this._terminal.cursorState || this._terminal.cursorHidden) { + if (!this._coreService.isCursorInitialized || this._terminal.cursorHidden) { this._clearCursor(); return; } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 45abbb82..28c4ce03 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -13,7 +13,7 @@ import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistr import { Disposable } from 'common/Lifecycle'; import { IColorSet } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; @@ -36,7 +36,8 @@ export class Renderer extends Disposable implements IRenderer { private readonly _terminal: ITerminal, private readonly _bufferService: IBufferService, private readonly _charSizeService: ICharSizeService, - private readonly _optionsService: IOptionsService + private readonly _optionsService: IOptionsService, + readonly coreService: ICoreService ) { super(); const allowTransparency = this._optionsService.options.allowTransparency; @@ -46,7 +47,7 @@ export class Renderer extends Disposable implements IRenderer { new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService), new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, this._bufferService, _optionsService), new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, this._bufferService, _optionsService), - new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService) + new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService) ]; this.dimensions = { scaledCharWidth: null, From c204ab00e04908d4c2e808457d058ae69b2710a7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 18:22:18 -0800 Subject: [PATCH 241/338] Move cursorHidden into core service --- src/InputHandler.ts | 6 +++--- src/Terminal.ts | 3 --- src/Types.d.ts | 2 -- src/renderer/CursorRenderLayer.ts | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 74208482..3994d532 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1449,7 +1449,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._logService.debug('DECSET 1015 not supported (see #2507)'); break; case 25: // show cursor - this._terminal.cursorHidden = false; + this._coreService.isCursorHidden = false; break; case 1048: // alt screen cursor this.saveCursor(); @@ -1620,7 +1620,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._logService.debug('DECRST 1015 not supported (see #2507)'); break; case 25: // hide cursor - this._terminal.cursorHidden = true; + this._coreService.isCursorHidden = true; break; case 1048: // alt screen cursor this.restoreCursor(); @@ -1954,7 +1954,7 @@ export class InputHandler extends Disposable implements IInputHandler { * http://vt100.net/docs/vt220-rm/table4-10.html */ public softReset(params: IParams): void { - this._terminal.cursorHidden = false; + this._coreService.isCursorHidden = false; this._terminal.insertMode = false; this._terminal.originMode = false; this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false diff --git a/src/Terminal.ts b/src/Terminal.ts index f8f2c946..e19901b4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -90,8 +90,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // TODO: We should remove options once components adopt optionsService public get options(): ITerminalOptions { return this.optionsService.options; } - public cursorHidden: boolean; - private _customKeyEventHandler: CustomKeyEventHandler; // common services @@ -249,7 +247,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _setup(): void { this._parent = document ? document.body : null; - this.cursorHidden = false; this._customKeyEventHandler = null; // modes diff --git a/src/Types.d.ts b/src/Types.d.ts index 11362058..4f6ce40f 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -38,7 +38,6 @@ export interface IInputHandlingTerminal { savedCols: number; mouseEvents: CoreMouseEventType; sendFocus: boolean; - cursorHidden: boolean; buffers: IBufferSet; buffer: IBuffer; @@ -154,7 +153,6 @@ export interface IInputHandler { export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { screenElement: HTMLElement; browser: IBrowser; - cursorHidden: boolean; buffer: IBuffer; buffers: IBufferSet; isFocused: boolean; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index bbbbcea4..0bfdd35b 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -128,7 +128,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _render(triggeredByAnimationFrame: boolean): void { // Don't draw the cursor if it's hidden - if (!this._coreService.isCursorInitialized || this._terminal.cursorHidden) { + if (!this._coreService.isCursorInitialized || this._coreService.isCursorHidden) { this._clearCursor(); return; } From 873de2b127fb8f36ecefd5def0137258555bcbd5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 18:39:46 -0800 Subject: [PATCH 242/338] Move cursor render layer to browser --- .../src/renderLayer/CursorRenderLayer.ts | 5 +- src/Terminal.ts | 12 +-- src/Types.d.ts | 1 - .../renderer/CursorRenderLayer.ts | 88 +++++++++---------- src/browser/services/CoreBrowserService.ts | 19 ++++ src/browser/services/Services.ts | 7 ++ src/common/services/CoreService.ts | 1 - src/common/services/Services.ts | 1 - src/renderer/Renderer.ts | 31 +++---- 9 files changed, 93 insertions(+), 72 deletions(-) rename src/{ => browser}/renderer/CursorRenderLayer.ts (85%) create mode 100644 src/browser/services/CoreBrowserService.ts diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index c7c68ff6..b018cf2b 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -95,9 +95,8 @@ export class CursorRenderLayer extends BaseRenderLayer { }); } } else { - if (this._cursorBlinkStateManager) { - this._cursorBlinkStateManager.dispose(); - } + this._cursorBlinkStateManager?.dispose(); + this._cursorBlinkStateManager = undefined; } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal diff --git a/src/Terminal.ts b/src/Terminal.ts index e19901b4..f9adacc1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -48,7 +48,7 @@ import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; -import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { Disposable } from 'common/Lifecycle'; @@ -63,6 +63,7 @@ import { DirtyRowService } from 'common/services/DirtyRowService'; import { InstantiationService } from 'common/services/InstantiationService'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { WriteBuffer } from 'common/input/WriteBuffer'; +import { CoreBrowserService } from 'browser/services/CoreBrowserService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -313,10 +314,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } } - public get isFocused(): boolean { - return document.activeElement === this.textarea && document.hasFocus(); - } - private _setupOptionsListeners(): void { // TODO: These listeners should be owned by individual components this.optionsService.onOptionChange(key => { @@ -531,6 +528,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur())); this._helperContainer.appendChild(this.textarea); + const coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea); + this._instantiationService.setService(ICoreBrowserService, coreBrowserService); + this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer); this._instantiationService.setService(ICharSizeService, this._charSizeService); @@ -631,7 +631,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService, this._coreService); + case 'canvas': return new Renderer(this._colorManager.colors, this.screenElement, this.linkifier, this._bufferService, this._charSizeService, this.optionsService, this._coreService, this._coreBrowserService); case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } diff --git a/src/Types.d.ts b/src/Types.d.ts index 4f6ce40f..2a922ae1 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -155,7 +155,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc browser: IBrowser; buffer: IBuffer; buffers: IBufferSet; - isFocused: boolean; viewport: IViewport; bracketedPasteMode: boolean; optionsService: IOptionsService; diff --git a/src/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts similarity index 85% rename from src/renderer/CursorRenderLayer.ts rename to src/browser/renderer/CursorRenderLayer.ts index 0bfdd35b..0ca1b97a 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -4,13 +4,13 @@ */ import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; -import { BaseRenderLayer } from '../browser/renderer/BaseRenderLayer'; -import { ITerminal } from '../Types'; +import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; +import { ICoreBrowserService } from 'browser/services/Services'; interface ICursorState { x: number; @@ -28,27 +28,27 @@ const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer { private _state: ICursorState; private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void}; - private _cursorBlinkStateManager: CursorBlinkStateManager; + private _cursorBlinkStateManager: CursorBlinkStateManager | undefined; private _cell: ICellData = new CellData(); constructor( container: HTMLElement, zIndex: number, colors: IColorSet, - private _terminal: ITerminal, rendererId: number, private _onRequestRefreshRowsEvent: IEventEmitter, readonly bufferService: IBufferService, readonly optionsService: IOptionsService, - private readonly _coreService: ICoreService + private readonly _coreService: ICoreService, + private readonly _coreBrowserService: ICoreBrowserService ) { super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService); this._state = { - x: null, - y: null, - isFocused: null, - style: null, - width: null + x: 0, + y: 0, + isFocused: false, + style: '', + width: 0 }; this._cursorRenderers = { 'bar': this._renderBarCursor.bind(this), @@ -62,11 +62,11 @@ export class CursorRenderLayer extends BaseRenderLayer { super.resize(dim); // Resizing the canvas discards the contents of the canvas so clear state this._state = { - x: null, - y: null, - isFocused: null, - style: null, - width: null + x: 0, + y: 0, + isFocused: false, + style: '', + width: 0 }; } @@ -74,7 +74,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._clearCursor(); if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.dispose(); - this._cursorBlinkStateManager = null; + this._cursorBlinkStateManager = undefined; this.onOptionsChanged(); } } @@ -97,15 +97,13 @@ export class CursorRenderLayer extends BaseRenderLayer { public onOptionsChanged(): void { if (this._optionsService.options.cursorBlink) { if (!this._cursorBlinkStateManager) { - this._cursorBlinkStateManager = new CursorBlinkStateManager(this._terminal.isFocused, () => { + this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => { this._render(true); }); } } else { - if (this._cursorBlinkStateManager) { - this._cursorBlinkStateManager.dispose(); - this._cursorBlinkStateManager = null; - } + this._cursorBlinkStateManager?.dispose(); + this._cursorBlinkStateManager = undefined; } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal @@ -142,12 +140,12 @@ export class CursorRenderLayer extends BaseRenderLayer { return; } - this._bufferService.buffer.lines.get(cursorY).loadCell(this._bufferService.buffer.x, this._cell); + this._bufferService.buffer.lines.get(cursorY)!.loadCell(this._bufferService.buffer.x, this._cell); if (this._cell.content === undefined) { return; } - if (!this._terminal.isFocused) { + if (!this._coreBrowserService.isFocused) { this._clearCursor(); this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; @@ -176,7 +174,7 @@ export class CursorRenderLayer extends BaseRenderLayer { // The cursor is already in the correct spot, don't redraw if (this._state.x === this._bufferService.buffer.x && this._state.y === viewportRelativeCursorY && - this._state.isFocused === this._terminal.isFocused && + this._state.isFocused === this._coreBrowserService.isFocused && this._state.style === this._optionsService.options.cursorStyle && this._state.width === this._cell.getWidth()) { return; @@ -199,11 +197,11 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._state) { this._clearCells(this._state.x, this._state.y, this._state.width, 1); this._state = { - x: null, - y: null, - isFocused: null, - style: null, - width: null + x: 0, + y: 0, + isFocused: false, + style: '', + width: 0 }; } } @@ -242,16 +240,16 @@ export class CursorRenderLayer extends BaseRenderLayer { class CursorBlinkStateManager { public isCursorVisible: boolean; - private _animationFrame: number; - private _blinkStartTimeout: number; - private _blinkInterval: number; + private _animationFrame: number | undefined; + private _blinkStartTimeout: number | undefined; + private _blinkInterval: number | undefined; /** * The time at which the animation frame was restarted, this is used on the * next render to restart the timers so they don't need to restart the timers * multiple times over a short period. */ - private _animationTimeRestarted: number; + private _animationTimeRestarted: number | undefined; constructor( isFocused: boolean, @@ -268,15 +266,15 @@ class CursorBlinkStateManager { public dispose(): void { if (this._blinkInterval) { window.clearInterval(this._blinkInterval); - this._blinkInterval = null; + this._blinkInterval = undefined; } if (this._blinkStartTimeout) { window.clearTimeout(this._blinkStartTimeout); - this._blinkStartTimeout = null; + this._blinkStartTimeout = undefined; } if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = null; + this._animationFrame = undefined; } } @@ -291,7 +289,7 @@ class CursorBlinkStateManager { if (!this._animationFrame) { this._animationFrame = window.requestAnimationFrame(() => { this._renderCallback(); - this._animationFrame = null; + this._animationFrame = undefined; }); } } @@ -311,7 +309,7 @@ class CursorBlinkStateManager { // started if (this._animationTimeRestarted) { const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); - this._animationTimeRestarted = null; + this._animationTimeRestarted = undefined; if (time > 0) { this._restartInterval(time); return; @@ -322,7 +320,7 @@ class CursorBlinkStateManager { this.isCursorVisible = false; this._animationFrame = window.requestAnimationFrame(() => { this._renderCallback(); - this._animationFrame = null; + this._animationFrame = undefined; }); // Setup the blink interval @@ -332,7 +330,7 @@ class CursorBlinkStateManager { // calc time diff // Make restart interval do a setTimeout initially? const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); - this._animationTimeRestarted = null; + this._animationTimeRestarted = undefined; this._restartInterval(time); return; } @@ -341,7 +339,7 @@ class CursorBlinkStateManager { this.isCursorVisible = !this.isCursorVisible; this._animationFrame = window.requestAnimationFrame(() => { this._renderCallback(); - this._animationFrame = null; + this._animationFrame = undefined; }); }, BLINK_INTERVAL); }, timeToStart); @@ -351,20 +349,20 @@ class CursorBlinkStateManager { this.isCursorVisible = true; if (this._blinkInterval) { window.clearInterval(this._blinkInterval); - this._blinkInterval = null; + this._blinkInterval = undefined; } if (this._blinkStartTimeout) { window.clearTimeout(this._blinkStartTimeout); - this._blinkStartTimeout = null; + this._blinkStartTimeout = undefined; } if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = null; + this._animationFrame = undefined; } } public resume(): void { - this._animationTimeRestarted = null; + this._animationTimeRestarted = undefined; this._restartInterval(); this.restartBlinkAnimation(); } diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts new file mode 100644 index 00000000..e585f44a --- /dev/null +++ b/src/browser/services/CoreBrowserService.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ICoreBrowserService } from './Services'; + +export class CoreBrowserService implements ICoreBrowserService { + serviceBrand: any; + + constructor( + private _textarea: HTMLTextAreaElement + ) { + } + + public get isFocused(): boolean { + return document.activeElement === this._textarea && document.hasFocus(); + } +} diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 2170a04e..785fec34 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -23,6 +23,13 @@ export interface ICharSizeService { measure(): void; } +export const ICoreBrowserService = createDecorator('CoreBrowserService'); +export interface ICoreBrowserService { + serviceBrand: any; + + readonly isFocused: boolean; +} + export const IMouseService = createDecorator('MouseService'); export interface IMouseService { serviceBrand: any; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index a235e539..11c3f305 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -17,7 +17,6 @@ export class CoreService implements ICoreService { public isCursorInitialized: boolean = false; public isCursorHidden: boolean = false; - public isFocused: boolean = false; public decPrivateModes: IDecPrivateModes; private _onData = new EventEmitter(); diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index c067448d..e4ff90d0 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -64,7 +64,6 @@ export interface ICoreService { */ isCursorInitialized: boolean; isCursorHidden: boolean; - isFocused: boolean; readonly decPrivateModes: IDecPrivateModes; readonly onData: IEvent; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 28c4ce03..f45fd4b5 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -3,16 +3,15 @@ * @license MIT */ -import { TextRenderLayer } from '../browser/renderer/TextRenderLayer'; -import { SelectionRenderLayer } from '../browser/renderer/SelectionRenderLayer'; -import { CursorRenderLayer } from './CursorRenderLayer'; +import { TextRenderLayer } from 'browser/renderer/TextRenderLayer'; +import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer'; +import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer'; import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; -import { ITerminal } from '../Types'; -import { LinkRenderLayer } from '../browser/renderer/LinkRenderLayer'; +import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer'; import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry'; import { Disposable } from 'common/Lifecycle'; -import { IColorSet } from 'browser/Types'; -import { ICharSizeService } from 'browser/services/Services'; +import { IColorSet, ILinkifier } from 'browser/Types'; +import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; @@ -33,21 +32,23 @@ export class Renderer extends Disposable implements IRenderer { constructor( private _colors: IColorSet, - private readonly _terminal: ITerminal, + private readonly _screenElement: HTMLElement, + private readonly _linkifier: ILinkifier, private readonly _bufferService: IBufferService, private readonly _charSizeService: ICharSizeService, private readonly _optionsService: IOptionsService, - readonly coreService: ICoreService + readonly coreService: ICoreService, + readonly coreBrowserService: ICoreBrowserService ) { super(); const allowTransparency = this._optionsService.options.allowTransparency; this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService); this._renderLayers = [ - new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService), - new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, this._bufferService, _optionsService), - new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, this._bufferService, _optionsService), - new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService) + new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService), + new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService), + new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, this._linkifier, this._bufferService, _optionsService), + new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService, coreBrowserService) ]; this.dimensions = { scaledCharWidth: null, @@ -101,8 +102,8 @@ export class Renderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.resize(this.dimensions)); // Resize the screen - this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; - this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._screenElement.style.width = `${this.dimensions.canvasWidth}px`; + this._screenElement.style.height = `${this.dimensions.canvasHeight}px`; } public onCharSizeChanged(): void { From 3f7238d9dca3e20794a212ec13ebdedcde3aea4e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 18:45:13 -0800 Subject: [PATCH 243/338] Move Renderer to browser --- src/Terminal.ts | 4 +-- src/{ => browser}/renderer/Renderer.ts | 34 +++++++++++++------------- 2 files changed, 19 insertions(+), 19 deletions(-) rename src/{ => browser}/renderer/Renderer.ts (92%) diff --git a/src/Terminal.ts b/src/Terminal.ts index f9adacc1..3c589ffa 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -28,7 +28,7 @@ import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; import { C0 } from 'common/data/EscapeSequences'; import { InputHandler } from './InputHandler'; -import { Renderer } from './renderer/Renderer'; +import { Renderer } from 'browser/renderer/Renderer'; import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from 'browser/services/SelectionService'; import * as Browser from 'common/Platform'; @@ -631,7 +631,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return new Renderer(this._colorManager.colors, this.screenElement, this.linkifier, this._bufferService, this._charSizeService, this.optionsService, this._coreService, this._coreBrowserService); + case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager.colors, this.screenElement, this.linkifier); case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } diff --git a/src/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts similarity index 92% rename from src/renderer/Renderer.ts rename to src/browser/renderer/Renderer.ts index f45fd4b5..26b464cb 100644 --- a/src/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -34,11 +34,11 @@ export class Renderer extends Disposable implements IRenderer { private _colors: IColorSet, private readonly _screenElement: HTMLElement, private readonly _linkifier: ILinkifier, - private readonly _bufferService: IBufferService, - private readonly _charSizeService: ICharSizeService, - private readonly _optionsService: IOptionsService, - readonly coreService: ICoreService, - readonly coreBrowserService: ICoreBrowserService + @IBufferService private readonly _bufferService: IBufferService, + @ICharSizeService private readonly _charSizeService: ICharSizeService, + @IOptionsService private readonly _optionsService: IOptionsService, + @ICoreService readonly coreService: ICoreService, + @ICoreBrowserService readonly coreBrowserService: ICoreBrowserService ) { super(); const allowTransparency = this._optionsService.options.allowTransparency; @@ -51,18 +51,18 @@ export class Renderer extends Disposable implements IRenderer { new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService, coreBrowserService) ]; this.dimensions = { - scaledCharWidth: null, - scaledCharHeight: null, - scaledCellWidth: null, - scaledCellHeight: null, - scaledCharLeft: null, - scaledCharTop: null, - scaledCanvasWidth: null, - scaledCanvasHeight: null, - canvasWidth: null, - canvasHeight: null, - actualCellWidth: null, - actualCellHeight: null + scaledCharWidth: 0, + scaledCharHeight: 0, + scaledCellWidth: 0, + scaledCellHeight: 0, + scaledCharLeft: 0, + scaledCharTop: 0, + scaledCanvasWidth: 0, + scaledCanvasHeight: 0, + canvasWidth: 0, + canvasHeight: 0, + actualCellWidth: 0, + actualCellHeight: 0 }; this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); From 3bf149bbc569ecd9ef2825366bf30e58e70efe11 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Nov 2019 18:46:56 -0800 Subject: [PATCH 244/338] Fix webgl cursor --- addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index b018cf2b..413f4fdd 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -120,7 +120,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _render(terminal: Terminal, triggeredByAnimationFrame: boolean): void { // Don't draw the cursor if it's hidden // TODO: Need to expose API for this - if (!(terminal as any)._core.cursorState || (terminal as any)._core.cursorHidden) { + if (!(terminal as any)._core._coreService.isCursorInitialized || (terminal as any)._core._coreService.isCursorHidden) { this._clearCursor(); return; } From bcd4c766d61bbaf4d275d7faa1994d72923acdf5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 09:09:59 -0800 Subject: [PATCH 245/338] Implement webgl renderer dispose Fixes #2254 --- addons/xterm-addon-webgl/src/WebglAddon.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 0074f9b2..be867d75 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -9,20 +9,28 @@ import { IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; export class WebglAddon implements ITerminalAddon { + private _terminal?: Terminal; + constructor( private _preserveDrawingBuffer?: boolean ) {} public activate(terminal: Terminal): void { if (!terminal.element) { - throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); + throw new Error('Cannot activate WebglAddon before Terminal.open'); } + this._terminal = terminal; const renderService: IRenderService = (terminal)._core._renderService; const colors: IColorSet = (terminal)._core._colorManager.colors; renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer)); } public dispose(): void { - throw new Error('WebglRendererAddon.dispose Not yet implemented'); + if (!this._terminal) { + throw new Error('Cannot dispose WebglAddon because it is activated'); + } + const renderService: IRenderService = (this._terminal)._core._renderService; + renderService.setRenderer((this._terminal)._core._createRenderer()); + renderService.onResize(this._terminal.cols, this._terminal.rows); } } From 2aec4e6d29c1b1fd8ed4c2004ceac74b2a1a8634 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 09:27:30 -0800 Subject: [PATCH 246/338] Add a test for removing canvases --- .../src/WebglRenderer.api.ts | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 9303782d..4debb5f9 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -18,29 +18,25 @@ const height = 600; describe('WebGL Renderer Integration Tests', function(): void { this.timeout(20000); - before(async function(): Promise { - browser = await puppeteer.launch({ - headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, - args: [`--window-size=${width},${height}`, `--no-sandbox`] - }); - page = (await browser.pages())[0]; - await page.setViewport({ width, height }); - await page.goto(APP); - await openTerminal(); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); - }); - after(() => { browser.close(); }); - beforeEach(async () => { - await page.evaluate(`window.term.reset()`); + it('dispose removes renderer canvases', async () => { + await setupBrowser(); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); + await page.evaluate(`addon.dispose()`); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); }); - describe('WebGL Renderer', () => { - it('foreground colors normal', async function(): Promise { + describe('colors', () => { + before(async () => setupBrowser()); + + beforeEach(async () => { + await page.evaluate(`window.term.reset()`); + }); + + it('foreground colors normal', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -63,7 +59,7 @@ describe('WebGL Renderer Integration Tests', function(): void { assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); - it('foreground colors bright', async function(): Promise { + it('foreground colors bright', async () => { const theme: ITheme = { brightBlack: '#010203', brightRed: '#040506', @@ -86,7 +82,7 @@ describe('WebGL Renderer Integration Tests', function(): void { assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background colors normal', async function(): Promise { + it('background colors normal', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -109,7 +105,7 @@ describe('WebGL Renderer Integration Tests', function(): void { assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background colors bright', async function(): Promise { + it('background colors bright', async () => { const theme: ITheme = { brightBlack: '#010203', brightRed: '#040506', @@ -161,3 +157,21 @@ async function getCellColor(col: number, row: number): Promise { `); return await page.evaluate(`Array.from(window.result)`); } + +async function setupBrowser(): Promise { + browser = await puppeteer.launch({ + headless: process.argv.indexOf('--headless') !== -1, + slowMo: 80, + args: [`--window-size=${width},${height}`, `--no-sandbox`] + }); + page = (await browser.pages())[0]; + await page.setViewport({ width, height }); + await page.goto(APP); + await openTerminal({ + rendererType: 'dom' + }); + await page.evaluate(` + window.addon = new WebglAddon(true); + window.term.loadAddon(window.addon); + `); +} From d09ac41f75dbedaf104aabae5f16a6849c4a2c1a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 09:29:04 -0800 Subject: [PATCH 247/338] After tests wait for browser close --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 4debb5f9..be895743 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -18,9 +18,7 @@ const height = 600; describe('WebGL Renderer Integration Tests', function(): void { this.timeout(20000); - after(() => { - browser.close(); - }); + after(async () => browser.close()); it('dispose removes renderer canvases', async () => { await setupBrowser(); @@ -31,10 +29,7 @@ describe('WebGL Renderer Integration Tests', function(): void { describe('colors', () => { before(async () => setupBrowser()); - - beforeEach(async () => { - await page.evaluate(`window.term.reset()`); - }); + beforeEach(async () => page.evaluate(`window.term.reset()`)); it('foreground colors normal', async () => { const theme: ITheme = { From 8adbed84b07a8f278e082f4cc9888beaf92e0f06 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 09:34:40 -0800 Subject: [PATCH 248/338] webgl: Refresh rows on options change Fixes #2549 --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1f28d589..a32b03e0 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -189,7 +189,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode); this._glyphRenderer.updateSelection(this._model, columnSelectMode); - // TODO: #2102 Should this move to RenderCoordinator? this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 }); } @@ -201,6 +200,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); this._updateDimensions(); this._refreshCharAtlas(); + this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 }); } /** From 22f4342194b0576bedc22f370c568992d906ea0e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 10:07:24 -0800 Subject: [PATCH 249/338] Move options row refresh into render service --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 1 - src/browser/renderer/dom/DomRenderer.ts | 1 - src/browser/services/RenderService.ts | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a32b03e0..ab2cb354 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -200,7 +200,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); this._updateDimensions(); this._refreshCharAtlas(); - this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 }); } /** diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 3d544a90..e8fc85f6 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -340,7 +340,6 @@ export class DomRenderer extends Disposable implements IRenderer { // Force a refresh this._updateDimensions(); this._injectCss(); - this._onRequestRefreshRows.fire({ start: 0, end: this._bufferService.rows - 1 }); } public clear(): void { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index b28c9971..2949c2c5 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -95,6 +95,7 @@ export class RenderService extends Disposable implements IRenderService { public changeOptions(): void { this._renderer.onOptionsChanged(); + this.refreshRows(0, this._rowCount - 1); this._fireOnCanvasResize(); } From 60bda1bbd3fb0df867686426a29c6ae3c57f7d00 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 11:04:55 -0800 Subject: [PATCH 250/338] Fix tests hanging --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index be895743..73e83bb2 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -18,17 +18,17 @@ const height = 600; describe('WebGL Renderer Integration Tests', function(): void { this.timeout(20000); - after(async () => browser.close()); - it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); await page.evaluate(`addon.dispose()`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0); + await browser.close(); }); describe('colors', () => { before(async () => setupBrowser()); + after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); it('foreground colors normal', async () => { From b8acdd6e673e0cbec386d5f969e45238a668621f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 11:15:15 -0800 Subject: [PATCH 251/338] Remove slowmo from puppeteer --- addons/xterm-addon-attach/src/AttachAddon.api.ts | 2 +- addons/xterm-addon-fit/src/FitAddon.api.ts | 2 +- addons/xterm-addon-search/src/SearchAddon.api.ts | 2 +- addons/xterm-addon-web-links/src/WebLinksAddon.api.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 2 +- test/api/CharWidth.api.ts | 2 +- test/api/InputHandler.api.ts | 2 +- test/api/MouseTracking.api.ts | 2 +- test/api/Parser.api.ts | 2 +- test/api/Terminal.api.ts | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.api.ts b/addons/xterm-addon-attach/src/AttachAddon.api.ts index 94d11139..f027e25e 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.api.ts @@ -20,7 +20,7 @@ describe('AttachAddon', () => { this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-fit/src/FitAddon.api.ts b/addons/xterm-addon-fit/src/FitAddon.api.ts index 4f955868..3115f2ed 100644 --- a/addons/xterm-addon-fit/src/FitAddon.api.ts +++ b/addons/xterm-addon-fit/src/FitAddon.api.ts @@ -19,7 +19,7 @@ describe('FitAddon', () => { this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-search/src/SearchAddon.api.ts b/addons/xterm-addon-search/src/SearchAddon.api.ts index ff195f5e..96c62a7d 100644 --- a/addons/xterm-addon-search/src/SearchAddon.api.ts +++ b/addons/xterm-addon-search/src/SearchAddon.api.ts @@ -22,7 +22,7 @@ describe('Search Tests', function (): void { before(async function (): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 3fb1a536..2a3fd199 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -19,7 +19,7 @@ describe('WebLinksAddon', () => { this.timeout(10000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 9303782d..a7079df1 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -21,7 +21,7 @@ describe('WebGL Renderer Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/CharWidth.api.ts b/test/api/CharWidth.api.ts index 0561bf07..a20ecc8b 100644 --- a/test/api/CharWidth.api.ts +++ b/test/api/CharWidth.api.ts @@ -20,7 +20,7 @@ describe('CharWidth Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index fe068610..2070dfe6 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -20,7 +20,7 @@ describe('InputHandler Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index 37d46f74..a993c6a1 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -204,7 +204,7 @@ describe('Mouse Tracking Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/Parser.api.ts b/test/api/Parser.api.ts index e46242ab..4dcda70c 100644 --- a/test/api/Parser.api.ts +++ b/test/api/Parser.api.ts @@ -20,7 +20,7 @@ describe('Parser Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 08963d9c..07dc372c 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -20,7 +20,7 @@ describe('API Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 80, + slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; From 840e56cf1e0e53cab83b011f4455f1e62030e134 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 11:30:45 -0800 Subject: [PATCH 252/338] Fix Attach tests --- addons/xterm-addon-attach/src/AttachAddon.api.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.api.ts b/addons/xterm-addon-attach/src/AttachAddon.api.ts index f027e25e..627647cd 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.api.ts @@ -20,7 +20,6 @@ describe('AttachAddon', () => { this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -43,6 +42,7 @@ describe('AttachAddon', () => { const server = new WebSocket.Server({ port }); server.on('connection', socket => socket.send('foo')); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); server.close(); }); @@ -55,6 +55,7 @@ describe('AttachAddon', () => { const data = new Uint8Array([102, 111, 111]); server.on('connection', socket => socket.send(data)); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); server.close(); }); @@ -69,3 +70,12 @@ async function openTerminal(options: ITerminalOptions = {}): Promise { await page.waitForSelector('.xterm-text-layer'); } } + +async function pollFor(page: puppeteer.Page, fn: string, val: any): Promise { + const result = await page.evaluate(fn); + if (result !== val) { + return new Promise(r => { + setTimeout(() => r(pollFor(page, fn, val)), 10); + }); + } +} From d3bf013ceb1f31d81842b5abc9e49181f33f753a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 13:57:42 -0800 Subject: [PATCH 253/338] Fix timing issues in web links addon tests --- .../xterm-addon-attach/src/AttachAddon.api.ts | 2 - .../src/WebLinksAddon.api.ts | 38 +++++++++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.api.ts b/addons/xterm-addon-attach/src/AttachAddon.api.ts index 627647cd..264b32ee 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.api.ts @@ -43,7 +43,6 @@ describe('AttachAddon', () => { server.on('connection', socket => socket.send('foo')); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); server.close(); }); @@ -56,7 +55,6 @@ describe('AttachAddon', () => { server.on('connection', socket => socket.send(data)); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); server.close(); }); }); diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 2a3fd199..49ec4fee 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -19,7 +19,6 @@ describe('WebLinksAddon', () => { this.timeout(10000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -54,22 +53,25 @@ describe('WebLinksAddon', () => { async function testHostName(hostname: string): Promise { await openTerminal({ rendererType: 'dom' }); await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`); + await new Promise(r => setTimeout(r, 100)); await page.evaluate(` + window.ready = false; window.term.writeln(' http://${hostname} '); window.term.writeln(' http://${hostname}/a~b#c~d?e~f '); window.term.writeln(' http://${hostname}/colon:test '); window.term.writeln(' http://${hostname}/colon:test: '); window.term.writeln('"http://${hostname}/"'); window.term.writeln('\\'http://${hostname}/\\''); - window.term.writeln('http://${hostname}/subpath/+/id'); + window.term.writeln('http://${hostname}/subpath/+/id', () => window.ready = true); `); - assert.equal(await getLinkAtCell(3, 1), `http://${hostname}`); - assert.equal(await getLinkAtCell(3, 2), `http://${hostname}/a~b#c~d?e~f`); - assert.equal(await getLinkAtCell(3, 3), `http://${hostname}/colon:test`); - assert.equal(await getLinkAtCell(3, 4), `http://${hostname}/colon:test`); - assert.equal(await getLinkAtCell(2, 5), `http://${hostname}/`); - assert.equal(await getLinkAtCell(2, 6), `http://${hostname}/`); - assert.equal(await getLinkAtCell(1, 7), `http://${hostname}/subpath/+/id`); + await pollFor(page, 'window.ready', true); + await pollForLinkAtCell(3, 1, `http://${hostname}`); + await pollForLinkAtCell(3, 2, `http://${hostname}/a~b#c~d?e~f`); + await pollForLinkAtCell(3, 3, `http://${hostname}/colon:test`); + await pollForLinkAtCell(3, 4, `http://${hostname}/colon:test`); + await pollForLinkAtCell(2, 5, `http://${hostname}/`); + await pollForLinkAtCell(2, 6, `http://${hostname}/`); + await pollForLinkAtCell(1, 7, `http://${hostname}/subpath/+/id`); } async function openTerminal(options: ITerminalOptions = {}): Promise { @@ -82,8 +84,20 @@ async function openTerminal(options: ITerminalOptions = {}): Promise { } } -async function getLinkAtCell(col: number, row: number): Promise { +async function pollForLinkAtCell(col: number, row: number, value: string): Promise { const rowSelector = `.xterm-rows > :nth-child(${row})`; - await page.hover(`${rowSelector} > :nth-child(${col})`); - return await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`); + await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length > 0`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`)); + assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value); +} + +async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => Promise): Promise { + if (preFn) { + await preFn(); + } + const result = await page.evaluate(fn); + if (result !== val) { + return new Promise(r => { + setTimeout(() => r(pollFor(page, fn, val, preFn)), 10); + }); + } } From b0c0f8701e8037485e3f777545d5b17e3bbee3c9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 14:08:37 -0800 Subject: [PATCH 254/338] Fix timing issues in webglrenderer --- .../src/WebglRenderer.api.ts | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index a7079df1..18d80276 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -15,13 +15,10 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('WebGL Renderer Integration Tests', function(): void { - this.timeout(20000); - +describe.only('WebGL Renderer Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -53,14 +50,14 @@ describe('WebGL Renderer Integration Tests', function(): void { }; await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`); - assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); it('foreground colors bright', async function(): Promise { @@ -76,14 +73,14 @@ describe('WebGL Renderer Integration Tests', function(): void { }; await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`); - assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); it('background colors normal', async function(): Promise { @@ -99,14 +96,14 @@ describe('WebGL Renderer Integration Tests', function(): void { }; await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); it('background colors bright', async function(): Promise { @@ -122,14 +119,14 @@ describe('WebGL Renderer Integration Tests', function(): void { }; await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); - assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); }); }); @@ -161,3 +158,21 @@ async function getCellColor(col: number, row: number): Promise { `); return await page.evaluate(`Array.from(window.result)`); } + +async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { + if (preFn) { + await preFn(); + } + const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); + let equal = false; + if (typeof result === 'object') { + equal = Object.keys(result).every(e => result[e] === (val as any)[e]); + } else { + equal = result === val; + } + if (!equal) { + return new Promise(r => { + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); + }); + } +} From 74f04ae92e4251e029faab3ed05fe5f5e4c201da Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 14:12:46 -0800 Subject: [PATCH 255/338] Fix timing issues in char width test --- .../src/WebglRenderer.api.ts | 3 +-- test/api/CharWidth.api.ts | 17 ++++++------- test/api/TestUtils.ts | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 test/api/TestUtils.ts diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 18d80276..9dfac843 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -4,7 +4,6 @@ */ import * as puppeteer from 'puppeteer'; -import { assert } from 'chai'; import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; @@ -15,7 +14,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe.only('WebGL Renderer Integration Tests', function(): void { +describe('WebGL Renderer Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, diff --git a/test/api/CharWidth.api.ts b/test/api/CharWidth.api.ts index a20ecc8b..39f0bfe7 100644 --- a/test/api/CharWidth.api.ts +++ b/test/api/CharWidth.api.ts @@ -4,8 +4,8 @@ */ import * as puppeteer from 'puppeteer'; -import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { pollFor } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -15,12 +15,9 @@ const width = 800; const height = 600; describe('CharWidth Integration Tests', function(): void { - this.timeout(20000); - before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -41,37 +38,37 @@ describe('CharWidth Integration Tests', function(): void { it('ASCII chars', async function(): Promise { const input = 'This is just ASCII text.#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(25, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 25); }); it('combining chars', async function(): Promise { const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(10, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 10); }); it('surrogate chars', async function(): Promise { const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(28, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 28); }); it('surrogate combining chars', async function(): Promise { const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(12, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 12); }); it('fullwidth chars', async function(): Promise { const input = '1234567890#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(21, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 21); }); it('fullwidth chars offset 1', async function(): Promise { const input = 'a1234567890#'; await page.evaluate(`window.term.write('${input}')`); - assert.equal(22, await sumWidths(0, 1, '#')); + await pollFor(page, () => sumWidths(0, 1, '#'), 22); }); // TODO: multiline tests once #1685 is resolved diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts new file mode 100644 index 00000000..ff1c661e --- /dev/null +++ b/test/api/TestUtils.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; + +export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { + if (preFn) { + await preFn(); + } + const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); + let equal = false; + if (typeof result === 'object') { + equal = Object.keys(result).every(e => result[e] === (val as any)[e]); + } else { + equal = result === val; + } + if (!equal) { + return new Promise(r => { + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); + }); + } +} From 2aaa997d395d97bd7ac9a6633ced85eae6fa19c0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 14:15:16 -0800 Subject: [PATCH 256/338] Fix timing issues in input handler --- test/api/InputHandler.api.ts | 86 ++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 2070dfe6..4bc2c07d 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { pollFor } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -15,8 +16,6 @@ const width = 800; const height = 600; describe('InputHandler Integration Tests', function(): void { - this.timeout(20000); - before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, @@ -45,7 +44,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('bar\\x1b[3D\\x1b[4@') `); - assert.deepEqual(await getLinesAsArray(2), [' foo', ' bar']); + await pollFor(page, () => getLinesAsArray(2), [' foo', ' bar']); }); it('CUU: Cursor Up Ps Times (default = 1) - CSI Ps A', async function(): Promise { @@ -55,7 +54,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Ab') `); - assert.deepEqual(await getLinesAsArray(4), ['', ' b', '', 'a']); + await pollFor(page, () => getLinesAsArray(4), ['', ' b', '', 'a']); }); it('CUD: Cursor Down Ps Times (default = 1) - CSI Ps B', async function(): Promise { @@ -65,7 +64,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Bb') `); - assert.deepEqual(await getLinesAsArray(4), ['', 'a', '', ' b']); + await pollFor(page, () => getLinesAsArray(4), ['', 'a', '', ' b']); }); it('CUF: Cursor Forward Ps Times (default = 1) - CSI Ps C', async function(): Promise { @@ -75,7 +74,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Cb') `); - assert.deepEqual(await getLinesAsArray(1), [' a b']); + await pollFor(page, () => getLinesAsArray(1), [' a b']); }); it('CUB: Cursor Backward Ps Times (default = 1) - CSI Ps D', async function(): Promise { @@ -85,7 +84,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Db') `); - assert.deepEqual(await getLinesAsArray(1), ['fba']); + await pollFor(page, () => getLinesAsArray(1), ['fba']); }); it('CNL: Cursor Next Line Ps Times (default = 1) - CSI Ps E', async function(): Promise { @@ -95,7 +94,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Eb') `); - assert.deepEqual(await getLinesAsArray(4), ['', 'a', '', 'b']); + await pollFor(page, () => getLinesAsArray(4), ['', 'a', '', 'b']); }); it('CPL: Cursor Preceding Line Ps Times (default = 1) - CSI Ps F', async function(): Promise { @@ -105,7 +104,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Fb') `); - assert.deepEqual(await getLinesAsArray(5), ['', 'b', '', 'a', '']); + await pollFor(page, () => getLinesAsArray(5), ['', 'b', '', 'a', '']); }); it('CHA: Cursor Character Absolute [column] (default = [row,1]) - CSI Ps G', async function(): Promise { @@ -115,7 +114,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[10Gb') `); - assert.deepEqual(await getLinesAsArray(1), ['aoo b']); + await pollFor(page, () => getLinesAsArray(1), ['aoo b']); }); it('CUP: Cursor Position [row;column] (default = [1,1]) - CSI Ps ; Ps H', async function(): Promise { @@ -125,7 +124,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[3;3Hb') `); - assert.deepEqual(await getLinesAsArray(3), ['aoo', '', ' b']); + await pollFor(page, () => getLinesAsArray(3), ['aoo', '', ' b']); }); it('CHT: Cursor Forward Tabulation Ps tab stops (default = 1) - CSI Ps I', async function(): Promise { @@ -135,7 +134,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\\n\\r\x1b[2Ib') `); - assert.deepEqual(await getLinesAsArray(2), [' a', ' b']); + await pollFor(page, () => getLinesAsArray(2), [' a', ' b']); }); it('ED: Erase in Display, VT100 - CSI Ps J', async function(): Promise { @@ -145,26 +144,26 @@ describe('InputHandler Integration Tests', function(): void { window.term.resize(5, 5); window.term.write('${fixture}\x1b[J') `); - assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']); + await pollFor(page, () => getLinesAsArray(3), ['abc', 'd', '']); await page.evaluate(` // 0: Erase Below window.term.reset() window.term.write('${fixture}\x1b[0J') `); - assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']); + await pollFor(page, () => getLinesAsArray(3), ['abc', 'd', '']); await page.evaluate(` // 1: Erase Above window.term.reset() window.term.write('${fixture}\x1b[1J') `); - assert.deepEqual(await getLinesAsArray(3), ['', ' f', 'ghi']); + await pollFor(page, () => getLinesAsArray(3), ['', ' f', 'ghi']); await page.evaluate(` // 2: Erase Saved Lines (scrollback) window.term.reset() window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[3J') `); - assert.equal(await page.evaluate(`window.term.buffer.length`), 5); - assert.deepEqual(await getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); + await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5); + await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); }); it('DECSED: Erase in Display, VT220 - CSI ? Ps J', async function(): Promise { @@ -174,26 +173,26 @@ describe('InputHandler Integration Tests', function(): void { window.term.resize(5, 5); window.term.write('${fixture}\x1b[?J') `); - assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']); + await pollFor(page, () => getLinesAsArray(3), ['abc', 'd', '']); await page.evaluate(` // 0: Erase Below window.term.reset() window.term.write('${fixture}\x1b[?0J') `); - assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']); + await pollFor(page, () => getLinesAsArray(3), ['abc', 'd', '']); await page.evaluate(` // 1: Erase Above window.term.reset() window.term.write('${fixture}\x1b[?1J') `); - assert.deepEqual(await getLinesAsArray(3), ['', ' f', 'ghi']); + await pollFor(page, () => getLinesAsArray(3), ['', ' f', 'ghi']); await page.evaluate(` // 2: Erase Saved Lines (scrollback) window.term.reset() window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[?3J') `); - assert.equal(await page.evaluate(`window.term.buffer.length`), 5); - assert.deepEqual(await getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); + await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5); + await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); }); it('IL: Insert Ps Line(s) (default = 1) - CSI Ps L', async function(): Promise { @@ -203,7 +202,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[2Lb') `); - assert.deepEqual(await getLinesAsArray(4), ['b', '', 'a', 'foo']); + await pollFor(page, () => getLinesAsArray(4), ['b', '', 'a', 'foo']); }); it('DL: Delete Ps Line(s) (default = 1) - CSI Ps M', async function(): Promise { @@ -213,7 +212,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\x1b[1Ed\\ne\\nf\x1b[2F\x1b[2M') `); - assert.deepEqual(await getLinesAsArray(5), [' b', ' f', '', '', '']); + await pollFor(page, () => getLinesAsArray(5), [' b', ' f', '', '', '']); }); it('DCH: Delete Ps Character(s) (default = 1) - CSI Ps P', async function(): Promise { @@ -223,7 +222,7 @@ describe('InputHandler Integration Tests', function(): void { // Explicit window.term.write('\\n\\rdef\x1b[2;1H\x1b[2P') `); - assert.deepEqual(await getLinesAsArray(2), ['bc', 'f']); + await pollFor(page, () => getLinesAsArray(2), ['bc', 'f']); }); describe('DSR: Device Status Report', () => { @@ -232,31 +231,31 @@ describe('InputHandler Integration Tests', function(): void { window.term.onData(e => window.result = e); window.term.write('\\x1b[5n'); `); - assert.equal(await page.evaluate(`window.result`), '\x1b[0n'); + await pollFor(page, () => page.evaluate(`window.result`), '\x1b[0n'); }); it('Report Cursor Position (CPR) - CSI 6 n', async function(): Promise { await page.evaluate(`window.term.write('\\n\\nfoo')`); - assert.deepEqual(await page.evaluate(` + await pollFor(page, () => page.evaluate(` [window.term.buffer.cursorY, window.term.buffer.cursorX] `), [2, 3]); await page.evaluate(` window.term.onData(e => window.result = e); window.term.write('\\x1b[6n'); `); - assert.equal(await page.evaluate(`window.result`), '\x1b[3;4R'); + await pollFor(page, () => page.evaluate(`window.result`), '\x1b[3;4R'); }); it('Report Cursor Position (DECXCPR) - CSI ? 6 n', async function(): Promise { await page.evaluate(`window.term.write('\\n\\nfoo')`); - assert.deepEqual(await page.evaluate(` + await pollFor(page, () => page.evaluate(` [window.term.buffer.cursorY, window.term.buffer.cursorX] `), [2, 3]); await page.evaluate(` window.term.onData(e => window.result = e); window.term.write('\\x1b[?6n'); `); - assert.equal(await page.evaluate(`window.result`), '\x1b[?3;4R'); + await pollFor(page, () => page.evaluate(`window.result`), '\x1b[?3;4R'); }); }); @@ -277,22 +276,23 @@ describe('InputHandler Integration Tests', function(): void { await page.mouse.up(); // Clear selection await page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2); - assert.equal(await page.evaluate(`window.term.getSelection().length`), 0); + await pollFor(page, () => page.evaluate(`window.term.getSelection().length`), 0); // Enable mouse events await page.evaluate(`window.term.write('\x1b[?1003h')`); // Click and drag and ensure there is no selection await page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2); await page.mouse.down(); await page.mouse.move((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 4); - assert.equal(await page.evaluate(`window.term.getSelection().length`), 0, 'mouse events are on so there should be no selection'); + // mouse events are on so there should be no selection + await pollFor(page, () => page.evaluate(`window.term.getSelection().length`), 0); await page.mouse.up(); }); it('Pm = 2004, Set bracketed paste mode', async function(): Promise { - assert.equal(await simulatePaste('foo'), 'foo'); + await pollFor(page, () => simulatePaste('foo'), 'foo'); await page.evaluate(`window.term.write('\x1b[?2004h')`); - assert.equal(await simulatePaste('bar'), '\x1b[200~bar\x1b[201~'); + await pollFor(page, () => simulatePaste('bar'), '\x1b[200~bar\x1b[201~'); await page.evaluate(`window.term.write('\x1b[?2004l')`); - assert.equal(await simulatePaste('baz'), 'baz'); + await pollFor(page, () => simulatePaste('baz'), 'baz'); }); }); }); @@ -309,32 +309,32 @@ describe('InputHandler Integration Tests', function(): void { window.term.writeln(''); window.term.write('#\x1b[5b'); `); - assert.deepEqual(await getLinesAsArray(4), ['##', '##', '##', '######']); - assert.deepEqual(await getCursor(), {col: 6, row: 3}); + await pollFor(page, () => getLinesAsArray(4), ['##', '##', '##', '######']); + await pollFor(page, () => getCursor(), {col: 6, row: 3}); // should not repeat on fullwidth chars await page.evaluate(` window.term.reset(); window.term.write('¥\x1b[10b'); `); - assert.deepEqual(await getLinesAsArray(1), ['¥']); + await pollFor(page, () => getLinesAsArray(1), ['¥']); // should repeat only base char of combining await page.evaluate(` window.term.reset(); window.term.write('e\u0301\x1b[5b'); `); - assert.deepEqual(await getLinesAsArray(1), ['e\u0301eeeee']); + await pollFor(page, () => getLinesAsArray(1), ['e\u0301eeeee']); // should wrap correctly await page.evaluate(` window.term.reset(); window.term.write('#\x1b[15b'); `); - assert.deepEqual(await getLinesAsArray(2), ['##########', '######']); + await pollFor(page, () => getLinesAsArray(2), ['##########', '######']); await page.evaluate(` window.term.reset(); window.term.write('\x1b[?7l'); // disable wrap around window.term.write('#\x1b[15b'); `); - assert.deepEqual(await getLinesAsArray(2), ['##########', '']); + await pollFor(page, () => getLinesAsArray(2), ['##########', '']); // any successful sequence should reset REP await page.evaluate(` window.term.reset(); @@ -344,7 +344,7 @@ describe('InputHandler Integration Tests', function(): void { window.term.writeln(''); window.term.write('abcdefg\x1b[3D\x1b[10b#\x1b[3b'); `); - assert.deepEqual(await getLinesAsArray(3), ['#', ' #', 'abcd####']); + await pollFor(page, () => getLinesAsArray(3), ['#', ' #', 'abcd####']); }); }); @@ -360,7 +360,7 @@ describe('InputHandler Integration Tests', function(): void { window.term.resize(10, 4); window.term.write('\\x1b[?47l\\x1b8'); `); - assert.deepEqual(await getCursor(), {col: 1, row: 3}); + await pollFor(page, () => getCursor(), {col: 1, row: 3}); }); }); }); From 17677568f9ab3402355ba94870ab1defc3bbede6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 14:16:57 -0800 Subject: [PATCH 257/338] Fix timing issues in mouse tracking tests --- test/api/MouseTracking.api.ts | 249 +++++++++++++++++----------------- 1 file changed, 122 insertions(+), 127 deletions(-) diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index a993c6a1..910763cd 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -4,8 +4,8 @@ */ import * as puppeteer from 'puppeteer'; -import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { pollFor } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -199,21 +199,16 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number; * Mouse tracking tests. */ describe('Mouse Tracking Tests', function(): void { - this.timeout(60000); - before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; await page.setViewport({ width, height }); }); - after(() => { - browser.close(); - }); + after(async () => browser.close()); beforeEach(async () => { await page.goto(APP); @@ -246,18 +241,18 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // mouseup should not report await mouseUp('left'); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // test at max rows/cols // bug: we are capped at col 95 currently @@ -265,7 +260,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // button press/move/release tests // left button @@ -274,7 +269,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // middle button // bug: default action not cancelled (adds data to getReports from clipboard under X11) // await mouseMove(43, 24); @@ -282,7 +277,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -290,15 +285,15 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); // wheel await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await wheelDown(); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); // modifiers // CTRL @@ -310,7 +305,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // ALT @@ -322,7 +317,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // SHIFT // note: caught by selection manager @@ -337,9 +332,9 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); } @@ -358,7 +353,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); }); it('SGR encoding', async () => { const encoding = 'SGR'; @@ -368,24 +363,24 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // mouseup should not report await mouseUp('left'); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // test at max rows/cols await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // button press/move/release tests // left button @@ -394,7 +389,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // middle button // bug: default action not cancelled (adds data to getReports from clipboard under X11) // await mouseMove(43, 24); @@ -402,7 +397,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -410,15 +405,15 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]); // wheel await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await wheelDown(); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); // modifiers // CTRL @@ -430,7 +425,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // ALT @@ -442,7 +437,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // SHIFT // note: caught by selection manager @@ -455,9 +450,9 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); } @@ -476,7 +471,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); }); }); describe('DECSET 1000 (VT200 mouse)', () => { @@ -495,22 +490,22 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -521,7 +516,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -533,7 +528,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -544,7 +539,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -552,7 +547,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -561,9 +556,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -575,7 +570,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} @@ -590,7 +585,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} @@ -608,11 +603,11 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} @@ -633,7 +628,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} @@ -647,22 +642,22 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -671,7 +666,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -683,7 +678,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -694,7 +689,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -702,7 +697,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}} ]); @@ -711,9 +706,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -725,7 +720,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}} @@ -740,7 +735,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}} @@ -757,11 +752,11 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} @@ -782,7 +777,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}} @@ -805,22 +800,22 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -831,7 +826,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -843,7 +838,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} @@ -855,7 +850,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -863,7 +858,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} @@ -873,9 +868,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -887,7 +882,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: false}}}, @@ -903,7 +898,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: true}}}, @@ -921,11 +916,11 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: false, shift: true, meta: false}}}, @@ -947,7 +942,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: '', modifier: {control: true, shift: false, meta: true}}}, @@ -963,22 +958,22 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 // bug: release is fired immediately await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should not report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), []); + await pollFor(page, () => getReports(encoding), []); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -987,7 +982,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -999,7 +994,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} @@ -1011,7 +1006,7 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); @@ -1019,7 +1014,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}} @@ -1029,9 +1024,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -1043,7 +1038,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}}, @@ -1059,7 +1054,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}}, @@ -1077,11 +1072,11 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}}, @@ -1103,7 +1098,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}}, @@ -1124,24 +1119,24 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}} ]); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); @@ -1152,7 +1147,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 95, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} @@ -1164,7 +1159,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, @@ -1177,14 +1172,14 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, @@ -1195,9 +1190,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -1208,7 +1203,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, @@ -1224,7 +1219,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: true}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, @@ -1242,12 +1237,12 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, @@ -1269,7 +1264,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: true}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, @@ -1285,24 +1280,24 @@ describe('Mouse Tracking Tests', function(): void { // test at 0,0 await mouseDown('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mouseup should report, encoding cannot report released button await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); // mousemove should report await mouseMove(50, 10); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}} ]); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} ]); @@ -1313,7 +1308,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(cols - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: cols, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}} @@ -1325,7 +1320,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseDown('left'); await mouseMove(44, 24); await mouseUp('left'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}}, @@ -1338,14 +1333,14 @@ describe('Mouse Tracking Tests', function(): void { // await mouseDown('middle'); // await mouseMove(44, 24); // await mouseUp('middle'); - // assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); + // await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]); // right button // bug: default action not cancelled (popup shown) await mouseMove(43, 24); await mouseDown('right'); await mouseMove(44, 24); await mouseUp('right'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}}, @@ -1356,9 +1351,9 @@ describe('Mouse Tracking Tests', function(): void { await mouseMove(43, 24); await getReports(encoding); // clear reports await wheelUp(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); await wheelDown(); - assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]); // modifiers // CTRL @@ -1369,7 +1364,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Control'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}}, @@ -1385,7 +1380,7 @@ describe('Mouse Tracking Tests', function(): void { await mouseUp('left'); await wheelDown(); await page.keyboard.up('Alt'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: true}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}}, @@ -1403,12 +1398,12 @@ describe('Mouse Tracking Tests', function(): void { await wheelDown(); await page.keyboard.up('Shift'); if (noShift) { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}} ]); } else { - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: false, shift: true, meta: false}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}}, @@ -1430,7 +1425,7 @@ describe('Mouse Tracking Tests', function(): void { await page.keyboard.up('Control'); await page.keyboard.up('Alt'); // await page.keyboard.up('Shift'); - assert.deepEqual(await getReports(encoding), [ + await pollFor(page, () => getReports(encoding), [ {col: 44, row: 25, state: {action: 'move', button: '', modifier: {control: true, shift: false, meta: true}}}, {col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}}, {col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}}, From 34e703e4725b06179ec0d69d8258513f5e674ad3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 17:29:32 -0800 Subject: [PATCH 258/338] Fix most of Terminal.api timing issues --- package.json | 2 + test/api/MouseTracking.api.ts | 4 +- test/api/Parser.api.ts | 7 +- test/api/Terminal.api.ts | 215 +++++++++++++++++----------------- test/api/TestUtils.ts | 17 +-- yarn.lock | 36 +++++- 6 files changed, 156 insertions(+), 125 deletions(-) diff --git a/package.json b/package.json index 7ed71741..c37594af 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "devDependencies": { "@types/chai": "^3.4.34", + "@types/deep-equal": "^1.0.1", "@types/glob": "^5.0.35", "@types/jsdom": "11.0.1", "@types/mocha": "^2.2.33", @@ -39,6 +40,7 @@ "@types/webpack": "^4.4.11", "@types/ws": "^6.0.1", "chai": "3.5.0", + "deep-equal": "^1.1.0", "express": "^4.17.1", "express-ws": "^4.0.0", "glob": "^7.0.5", diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index 910763cd..d10a5b95 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -198,8 +198,8 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number; /** * Mouse tracking tests. */ -describe('Mouse Tracking Tests', function(): void { - before(async function(): Promise { +describe('Mouse Tracking Tests', () => { + before(async () => { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, args: [`--window-size=${width},${height}`, `--no-sandbox`] diff --git a/test/api/Parser.api.ts b/test/api/Parser.api.ts index 4dcda70c..9b0fb66b 100644 --- a/test/api/Parser.api.ts +++ b/test/api/Parser.api.ts @@ -15,12 +15,9 @@ const width = 800; const height = 600; describe('Parser Integration Tests', function(): void { - this.timeout(20000); - before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; @@ -29,9 +26,7 @@ describe('Parser Integration Tests', function(): void { await openTerminal(); }); - after(() => { - browser.close(); - }); + after(async () => browser.close()); describe('addCsiHandler', () => { it('should call custom CSI handler with js array params', async () => { diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 07dc372c..6d7a4d20 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { pollFor, writeSync } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -14,55 +15,47 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('API Integration Tests', function(): void { - this.timeout(20000); - - before(async function(): Promise { +describe.only('API Integration Tests', function(): void { + before(async () => { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; await page.setViewport({ width, height }); }); - after(() => { - browser.close(); - }); + after(async () => browser.close()); + beforeEach(async () => page.goto(APP)); - beforeEach(async () => { - await page.goto(APP); - }); - - it('Default options', async function(): Promise { + it('Default options', async () => { await openTerminal(); assert.equal(await page.evaluate(`window.term.cols`), 80); assert.equal(await page.evaluate(`window.term.rows`), 24); }); - it('write', async function(): Promise { + it('write', async () => { await openTerminal(); await page.evaluate(` window.term.write('foo'); window.term.write('bar'); window.term.write('文'); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); }); - it('write with callback', async function(): Promise { + it('write with callback', async () => { await openTerminal(); await page.evaluate(` window.term.write('foo', () => { window.__x = 'a'; }); window.term.write('bar', () => { window.__x += 'b'; }); window.term.write('文', () => { window.__x += 'c'; }); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); - assert.equal(await page.evaluate(`window.__x`), 'abc'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); + await pollFor(page, `window.__x`, 'abc'); }); - it('write - bytes (UTF8)', async function(): Promise { + it('write - bytes (UTF8)', async () => { await openTerminal(); await page.evaluate(` // foo @@ -72,10 +65,10 @@ describe('API Integration Tests', function(): void { // 文 window.term.write(new Uint8Array([230, 150, 135])); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); }); - it('write - bytes (UTF8) with callback', async function(): Promise { + it('write - bytes (UTF8) with callback', async () => { await openTerminal(); await page.evaluate(` // foo @@ -85,48 +78,48 @@ describe('API Integration Tests', function(): void { // 文 window.term.write(new Uint8Array([230, 150, 135]), () => { window.__x += 'C'; }); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); - assert.equal(await page.evaluate(`window.__x`), 'ABC'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); + await pollFor(page, `window.__x`, 'ABC'); }); - it('writeln', async function(): Promise { + it('writeln', async () => { await openTerminal(); await page.evaluate(` window.term.writeln('foo'); window.term.writeln('bar'); window.term.writeln('文'); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); }); - it('writeln with callback', async function(): Promise { + it('writeln with callback', async () => { await openTerminal(); await page.evaluate(` window.term.writeln('foo', () => { window.__x = '1'; }); window.term.writeln('bar', () => { window.__x += '2'; }); window.term.writeln('文', () => { window.__x += '3'; }); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文'); - assert.equal(await page.evaluate(`window.__x`), '123'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); + await pollFor(page, `window.__x`, '123'); }); - it('writeln - bytes (UTF8)', async function(): Promise { + it('writeln - bytes (UTF8)', async () => { await openTerminal(); await page.evaluate(` window.term.writeln(new Uint8Array([102, 111, 111])); window.term.writeln(new Uint8Array([98, 97, 114])); window.term.writeln(new Uint8Array([230, 150, 135])); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文'); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); }); - it('paste', async function(): Promise { + it('paste', async () => { await openTerminal(); await page.evaluate(` window.calls = []; @@ -137,26 +130,28 @@ describe('API Integration Tests', function(): void { window.term.paste('foo'); }); `); - assert.deepEqual(await page.evaluate(`window.calls`), ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']); + await pollFor(page, `window.calls`, ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']); }); - it('clear', async function(): Promise { + it('clear', async () => { await openTerminal({ rows: 5 }); await page.evaluate(` window.term.write('test0'); + window.parsed = 0; for (let i = 1; i < 10; i++) { - window.term.write('\\n\\rtest' + i); + window.term.write('\\n\\rtest' + i, () => window.parsed++); } `); + await pollFor(page, `window.parsed`, 9); await page.evaluate(`window.term.clear()`); - assert.equal(await page.evaluate(`window.term.buffer.length`), '5'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'test9'); + await pollFor(page, `window.term.buffer.length`, 5); + await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'test9'); for (let i = 1; i < 5; i++) { - assert.equal(await page.evaluate(`window.term.buffer.getLine(${i}).translateToString(true)`), ''); + await pollFor(page, `window.term.buffer.getLine(${i}).translateToString(true)`, ''); } }); - it('getOption, setOption', async function(): Promise { + it('getOption, setOption', async () => { await openTerminal(); assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas'); await page.evaluate(`window.term.setOption('rendererType', 'dom')`); @@ -164,9 +159,10 @@ describe('API Integration Tests', function(): void { }); describe('renderer', () => { - it('foreground', async function(): Promise { + it('foreground', async () => { await openTerminal({ rendererType: 'dom' }); - await page.evaluate(`window.term.write('\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7')`); + await writeSync(page, '\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7'); + await pollFor(page, `!!document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)')`, true); assert.deepEqual(await page.evaluate(` [ document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, @@ -188,9 +184,10 @@ describe('API Integration Tests', function(): void { ]); }); - it('background', async function(): Promise { + it('background', async () => { await openTerminal({ rendererType: 'dom' }); - await page.evaluate(`window.term.write('\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7')`); + await writeSync(page, '\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7'); + await pollFor(page, `!!document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)')`, true); assert.deepEqual(await page.evaluate(` [ document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, @@ -213,7 +210,7 @@ describe('API Integration Tests', function(): void { }); }); - it('selection', async function(): Promise { + it('selection', async () => { await openTerminal({ rows: 5, cols: 5 }); await page.evaluate(`window.term.write('\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz')`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); @@ -233,7 +230,7 @@ describe('API Integration Tests', function(): void { assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 1, startRow: 2, endColumn: 3, endRow: 2 }); }); - it('focus, blur', async function(): Promise { + it('focus, blur', async () => { await openTerminal(); assert.equal(await page.evaluate(`document.activeElement.className`), ''); await page.evaluate(`window.term.focus()`); @@ -243,7 +240,7 @@ describe('API Integration Tests', function(): void { }); describe('loadAddon', () => { - it('constructor', async function(): Promise { + it('constructor', async () => { await openTerminal({ cols: 5 }); await page.evaluate(` window.cols = 0; @@ -255,7 +252,7 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.cols`), 5); }); - it('dispose (addon)', async function(): Promise { + it('dispose (addon)', async () => { await openTerminal(); await page.evaluate(` window.disposeCalled = false @@ -270,7 +267,7 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.disposeCalled`), true); }); - it('dispose (terminal)', async function(): Promise { + it('dispose (terminal)', async () => { await openTerminal(); await page.evaluate(` window.disposeCalled = false @@ -286,19 +283,19 @@ describe('API Integration Tests', function(): void { }); describe('Events', () => { - it('onCursorMove', async function(): Promise { + it('onCursorMove', async () => { await openTerminal(); await page.evaluate(` window.callCount = 0; window.term.onCursorMove(e => window.callCount++); window.term.write('foo'); `); - assert.equal(await page.evaluate(`window.callCount`), 1); + await pollFor(page, `window.callCount`, 1); await page.evaluate(`window.term.write('bar')`); - assert.equal(await page.evaluate(`window.callCount`), 2); + await pollFor(page, `window.callCount`, 2); }); - it('onData', async function(): Promise { + it('onData', async () => { await openTerminal(); await page.evaluate(` window.calls = []; @@ -308,7 +305,7 @@ describe('API Integration Tests', function(): void { assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']); }); - it('onKey', async function(): Promise { + it('onKey', async () => { await openTerminal(); await page.evaluate(` window.calls = []; @@ -318,19 +315,19 @@ describe('API Integration Tests', function(): void { assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']); }); - it('onLineFeed', async function(): Promise { + it('onLineFeed', async () => { await openTerminal(); await page.evaluate(` window.callCount = 0; window.term.onLineFeed(() => callCount++); window.term.writeln('foo'); `); - assert.equal(await page.evaluate(`window.callCount`), 1); + await pollFor(page, `window.callCount`, 1); await page.evaluate(`window.term.writeln('bar')`); - assert.equal(await page.evaluate(`window.callCount`), 2); + await pollFor(page, `window.callCount`, 2); }); - it('onScroll', async function(): Promise { + it('onScroll', async () => { await openTerminal({ rows: 5 }); await page.evaluate(` window.calls = []; @@ -339,94 +336,94 @@ describe('API Integration Tests', function(): void { window.term.writeln('foo'); } `); - assert.deepEqual(await page.evaluate(`window.calls`), []); + await pollFor(page, `window.calls`, []); await page.evaluate(`window.term.writeln('bar')`); - assert.deepEqual(await page.evaluate(`window.calls`), [1]); + await pollFor(page, `window.calls`, [1]); await page.evaluate(`window.term.writeln('baz')`); - assert.deepEqual(await page.evaluate(`window.calls`), [1, 2]); + await pollFor(page, `window.calls`, [1, 2]); }); - it('onSelectionChange', async function(): Promise { + it('onSelectionChange', async () => { await openTerminal(); await page.evaluate(` window.callCount = 0; window.term.onSelectionChange(() => window.callCount++); `); - assert.equal(await page.evaluate(`window.callCount`), 0); + await pollFor(page, `window.callCount`, 0); await page.evaluate(`window.term.selectAll()`); - assert.equal(await page.evaluate(`window.callCount`), 1); + await pollFor(page, `window.callCount`, 1); await page.evaluate(`window.term.clearSelection()`); - assert.equal(await page.evaluate(`window.callCount`), 2); + await pollFor(page, `window.callCount`, 2); }); - it('onRender', async function(): Promise { + it('onRender', async () => { await openTerminal(); await page.evaluate(` window.calls = []; window.term.onRender(e => window.calls.push([e.start, e.end])); `); - assert.deepEqual(await page.evaluate(`window.calls`), []); + await pollFor(page, `window.calls`, []); await page.evaluate(`window.term.write('foo')`); - assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0]]); + await pollFor(page, `window.calls`, [[0, 0]]); await page.evaluate(`window.term.write('bar\\n\\nbaz')`); - assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0], [0, 2]]); + await pollFor(page, `window.calls`, [[0, 0], [0, 2]]); }); - it('onResize', async function(): Promise { + it('onResize', async () => { await openTerminal(); await page.evaluate(` window.calls = []; window.term.onResize(e => window.calls.push([e.cols, e.rows])); `); - assert.deepEqual(await page.evaluate(`window.calls`), []); + await pollFor(page, `window.calls`, []); await page.evaluate(`window.term.resize(10, 5)`); - assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5]]); + await pollFor(page, `window.calls`, [[10, 5]]); await page.evaluate(`window.term.resize(20, 15)`); - assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5], [20, 15]]); + await pollFor(page, `window.calls`, [[10, 5], [20, 15]]); }); - it('onTitleChange', async function(): Promise { + it('onTitleChange', async () => { await openTerminal(); await page.evaluate(` window.calls = []; window.term.onTitleChange(e => window.calls.push(e)); `); - assert.deepEqual(await page.evaluate(`window.calls`), []); + await pollFor(page, `window.calls`, []); await page.evaluate(`window.term.write('\\x1b]2;foo\\x9c')`); - assert.deepEqual(await page.evaluate(`window.calls`), ['foo']); + await pollFor(page, `window.calls`, ['foo']); }); }); describe('buffer', () => { - it('cursorX, cursorY', async function(): Promise { + it('cursorX, cursorY', async () => { await openTerminal({ rows: 5, cols: 5 }); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); - await page.evaluate(`window.term.write('foo')`); + await writeSync(page, 'foo'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); - await page.evaluate(`window.term.write('\\n')`); + await writeSync(page, '\\n'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); - await page.evaluate(`window.term.write('\\r')`); + await writeSync(page, '\\r'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); - await page.evaluate(`window.term.write('abcde')`); + await writeSync(page, 'abcde'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 5); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); - await page.evaluate(`window.term.write('\\n\\r\\n\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\r\\n\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 4); }); - it('viewportY', async function(): Promise { + it('viewportY', async () => { await openTerminal({ rows: 5 }); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); - await page.evaluate(`window.term.write('\\n')`); + await writeSync(page, '\\n'); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 1); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 5); await page.evaluate(`window.term.scrollLines(-1)`); assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 4); @@ -434,14 +431,14 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); }); - it('baseY', async function(): Promise { + it('baseY', async () => { await openTerminal({ rows: 5 }); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); - await page.evaluate(`window.term.write('\\n')`); + await writeSync(page, '\\n'); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 1); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); await page.evaluate(`window.term.scrollLines(-1)`); assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); @@ -449,44 +446,44 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); }); - it('length', async function(): Promise { + it('length', async () => { await openTerminal({ rows: 5 }); assert.equal(await page.evaluate(`window.term.buffer.length`), 5); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.length`), 5); - await page.evaluate(`window.term.write('\\n')`); + await writeSync(page, '\\n'); assert.equal(await page.evaluate(`window.term.buffer.length`), 6); - await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + await writeSync(page, '\\n\\n\\n\\n'); assert.equal(await page.evaluate(`window.term.buffer.length`), 10); }); describe('getLine', () => { - it('invalid index', async function(): Promise { + it('invalid index', async () => { await openTerminal({ rows: 5 }); assert.equal(await page.evaluate(`window.term.buffer.getLine(-1)`), undefined); assert.equal(await page.evaluate(`window.term.buffer.getLine(5)`), undefined); }); - it('isWrapped', async function(): Promise { + it('isWrapped', async () => { await openTerminal({ cols: 5 }); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false); - await page.evaluate(`window.term.write('abcde')`); + await writeSync(page, 'abcde'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false); - await page.evaluate(`window.term.write('f')`); + await writeSync(page, 'f'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), true); }); - it('translateToString', async function(): Promise { + it('translateToString', async () => { await openTerminal({ cols: 5 }); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), ' '); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), ''); - await page.evaluate(`window.term.write('foo')`); + await writeSync(page, 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'foo '); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); - await page.evaluate(`window.term.write('bar')`); + await writeSync(page, 'bar'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'fooba'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'fooba'); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'r'); @@ -494,13 +491,13 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1, 3)`), 'oo'); }); - it('getCell', async function(): Promise { + it('getCell', async () => { await openTerminal({ cols: 5 }); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(-1)`), undefined); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(5)`), undefined); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), ''); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1); - await page.evaluate(`window.term.write('a文')`); + await writeSync(page, 'a文'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), 'a'); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).char`), '文'); @@ -511,7 +508,7 @@ describe('API Integration Tests', function(): void { }); }); - it('dispose', async function(): Promise { + it('dispose', async () => { await page.evaluate(` window.term = new Terminal(); window.term.dispose(); @@ -519,7 +516,7 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term._core._isDisposed`), true); }); - it('dispose (opened)', async function(): Promise { + it('dispose (opened)', async () => { await openTerminal(); await page.evaluate(`window.term.dispose()`); assert.equal(await page.evaluate(`window.term._core._isDisposed`), true); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index ff1c661e..f0dd71d4 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -4,21 +4,24 @@ */ import * as puppeteer from 'puppeteer'; +import deepEqual = require('deep-equal'); export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { if (preFn) { await preFn(); } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); - let equal = false; - if (typeof result === 'object') { - equal = Object.keys(result).every(e => result[e] === (val as any)[e]); - } else { - equal = result === val; - } - if (!equal) { + if (!deepEqual(result, val)) { return new Promise(r => { setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); }); } } + +export async function writeSync(page: puppeteer.Page, data: string): Promise { + await page.evaluate(` + window.ready = false; + window.term.write('${data}', () => window.ready = true); + `); + await pollFor(page, 'window.ready', true); +} diff --git a/yarn.lock b/yarn.lock index cb02cf91..171b66e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -130,6 +130,11 @@ resolved "https://registry.yarnpkg.com/@types/cli-table/-/cli-table-0.3.0.tgz#f1857156bf5fd115c6a2db260ba0be1f8fc5671c" integrity sha512-QnZUISJJXyhyD6L1e5QwXDV/A5i2W1/gl6D6YMc8u0ncPepbv/B4w3S+izVvtAg60m6h+JP09+Y/0zF2mojlFQ== +"@types/deep-equal@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" + integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg== + "@types/events@*": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" @@ -1454,6 +1459,18 @@ deep-eql@^0.1.3: dependencies: type-detect "0.1.1" +deep-equal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.0.tgz#3103cdf8ab6d32cf4a8df7865458f2b8d33f3745" + integrity sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -2567,6 +2584,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -3634,7 +3656,12 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-keys@^1.0.11, object-keys@^1.0.12: +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + integrity sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY= + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -4238,6 +4265,13 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexp.prototype.flags@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz#6b30724e306a27833eeb171b66ac8890ba37e41c" + integrity sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA== + dependencies: + define-properties "^1.1.2" + release-zalgo@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" From 7ff6e4f4933bc604325aab5feb2b248e9a735082 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:28:24 -0800 Subject: [PATCH 259/338] Fix flaky terminal api tests --- test/api/Terminal.api.ts | 10 ++++++---- test/api/TestUtils.ts | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 6d7a4d20..0b05736d 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -6,7 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; -import { pollFor, writeSync } from './TestUtils'; +import { pollFor, timeout, writeSync } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -15,7 +15,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe.only('API Integration Tests', function(): void { +describe('API Integration Tests', function(): void { before(async () => { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, @@ -162,7 +162,7 @@ describe.only('API Integration Tests', function(): void { it('foreground', async () => { await openTerminal({ rendererType: 'dom' }); await writeSync(page, '\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7'); - await pollFor(page, `!!document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)')`, true); + await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9); assert.deepEqual(await page.evaluate(` [ document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, @@ -187,7 +187,7 @@ describe.only('API Integration Tests', function(): void { it('background', async () => { await openTerminal({ rendererType: 'dom' }); await writeSync(page, '\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7'); - await pollFor(page, `!!document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)')`, true); + await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9); assert.deepEqual(await page.evaluate(` [ document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, @@ -358,6 +358,7 @@ describe.only('API Integration Tests', function(): void { it('onRender', async () => { await openTerminal(); + await timeout(20); // Ensure all init events are fired await page.evaluate(` window.calls = []; window.term.onRender(e => window.calls.push([e.start, e.end])); @@ -371,6 +372,7 @@ describe.only('API Integration Tests', function(): void { it('onResize', async () => { await openTerminal(); + await timeout(20); // Ensure all init events are fired await page.evaluate(` window.calls = []; window.term.onResize(e => window.calls.push([e.cols, e.rows])); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index f0dd71d4..a31c2b46 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -12,8 +12,9 @@ export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); if (!deepEqual(result, val)) { + console.log('result', result, 'val', val); return new Promise(r => { - setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); }); } } @@ -25,3 +26,7 @@ export async function writeSync(page: puppeteer.Page, data: string): Promise { + return new Promise(r => setTimeout(r, ms)); +} From b0640b7705b2b80df84c867ba60913e5729ca5cd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:28:50 -0800 Subject: [PATCH 260/338] Remove slowMo param everywhere --- addons/xterm-addon-fit/src/FitAddon.api.ts | 1 - addons/xterm-addon-search/src/SearchAddon.api.ts | 1 - test/api/InputHandler.api.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/addons/xterm-addon-fit/src/FitAddon.api.ts b/addons/xterm-addon-fit/src/FitAddon.api.ts index 3115f2ed..aa73a9b6 100644 --- a/addons/xterm-addon-fit/src/FitAddon.api.ts +++ b/addons/xterm-addon-fit/src/FitAddon.api.ts @@ -19,7 +19,6 @@ describe('FitAddon', () => { this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/addons/xterm-addon-search/src/SearchAddon.api.ts b/addons/xterm-addon-search/src/SearchAddon.api.ts index 96c62a7d..72a69b5f 100644 --- a/addons/xterm-addon-search/src/SearchAddon.api.ts +++ b/addons/xterm-addon-search/src/SearchAddon.api.ts @@ -22,7 +22,6 @@ describe('Search Tests', function (): void { before(async function (): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 4bc2c07d..968a9e0b 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -19,7 +19,6 @@ describe('InputHandler Integration Tests', function(): void { before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, - slowMo: 0, args: [`--window-size=${width},${height}`, `--no-sandbox`] }); page = (await browser.pages())[0]; From 971e69f50592046d12ad64ae8cd11575c9d725b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:43:25 -0800 Subject: [PATCH 261/338] Reduce flakiness --- .../src/WebLinksAddon.api.ts | 28 +++++++++++-------- test/api/MouseTracking.api.ts | 20 ++++++------- test/api/Parser.api.ts | 17 ++++------- test/api/TestUtils.ts | 1 - 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 49ec4fee..0cb222c8 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -53,18 +53,14 @@ describe('WebLinksAddon', () => { async function testHostName(hostname: string): Promise { await openTerminal({ rendererType: 'dom' }); await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`); - await new Promise(r => setTimeout(r, 100)); - await page.evaluate(` - window.ready = false; - window.term.writeln(' http://${hostname} '); - window.term.writeln(' http://${hostname}/a~b#c~d?e~f '); - window.term.writeln(' http://${hostname}/colon:test '); - window.term.writeln(' http://${hostname}/colon:test: '); - window.term.writeln('"http://${hostname}/"'); - window.term.writeln('\\'http://${hostname}/\\''); - window.term.writeln('http://${hostname}/subpath/+/id', () => window.ready = true); - `); - await pollFor(page, 'window.ready', true); + const data = ` http://${hostname} \\r\\n` + + ` http://${hostname}/a~b#c~d?e~f \\r\\n` + + ` http://${hostname}/colon:test \\r\\n` + + ` http://${hostname}/colon:test: \\r\\n` + + `"http://${hostname}/"\\r\\n` + + `\\'http://${hostname}/\\'\\r\\n` + + `http://${hostname}/subpath/+/id`; + await writeSync(page, data); await pollForLinkAtCell(3, 1, `http://${hostname}`); await pollForLinkAtCell(3, 2, `http://${hostname}/a~b#c~d?e~f`); await pollForLinkAtCell(3, 3, `http://${hostname}/colon:test`); @@ -101,3 +97,11 @@ async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => }); } } + +async function writeSync(page: puppeteer.Page, data: string): Promise { + await page.evaluate(` + window.ready = false; + window.term.write('${data}', () => window.ready = true); + `); + await pollFor(page, 'window.ready', true); +} diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index d10a5b95..f8124350 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -5,7 +5,7 @@ import * as puppeteer from 'puppeteer'; import { ITerminalOptions } from 'xterm'; -import { pollFor } from './TestUtils'; +import { pollFor, writeSync } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -199,7 +199,7 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number; * Mouse tracking tests. */ describe('Mouse Tracking Tests', () => { - before(async () => { + before(async function(): Promise { browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, args: [`--window-size=${width},${height}`, `--no-sandbox`] @@ -237,7 +237,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'DEFAULT'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?9h');`); + await writeSync(page, '\x1b[?9h'); // test at 0,0 await mouseDown('left'); @@ -359,7 +359,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'SGR'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?9h\x1b[?1006h');`); + await writeSync(page, '\x1b[?9h\x1b[?1006h'); // test at 0,0 await mouseDown('left'); @@ -486,7 +486,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'DEFAULT'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1000h');`); + await writeSync(page, '\x1b[?1000h'); // test at 0,0 await mouseDown('left'); @@ -638,7 +638,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'SGR'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1006h');`); + await writeSync(page, '\x1b[?1000h\x1b[?1006h'); // test at 0,0 await mouseDown('left'); @@ -796,7 +796,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'DEFAULT'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1002h');`); + await writeSync(page, '\x1b[?1002h'); // test at 0,0 await mouseDown('left'); @@ -953,7 +953,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'SGR'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1002h\x1b[?1006h');`); + await writeSync(page, '\x1b[?1002h\x1b[?1006h'); // test at 0,0 // bug: release is fired immediately @@ -1115,7 +1115,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'DEFAULT'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1003h');`); + await writeSync(page, '\x1b[?1003h'); // test at 0,0 await mouseDown('left'); @@ -1276,7 +1276,7 @@ describe('Mouse Tracking Tests', () => { const encoding = 'SGR'; await resetMouseModes(); await mouseMove(0, 0); - await page.evaluate(`window.term.write('\x1b[?1003h\x1b[?1006h');`); + await writeSync(page, '\x1b[?1003h\x1b[?1006h'); // test at 0,0 await mouseDown('left'); diff --git a/test/api/Parser.api.ts b/test/api/Parser.api.ts index 9b0fb66b..72f56799 100644 --- a/test/api/Parser.api.ts +++ b/test/api/Parser.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from 'xterm'; +import { writeSync } from './TestUtils'; const APP = 'http://127.0.0.1:3000/test'; @@ -38,9 +39,7 @@ describe('Parser Integration Tests', function(): void { return false; }, ''); `); - await page.evaluate(` - window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams'); - `); + await writeSync(page, '\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams'); assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]); }); }); @@ -62,9 +61,7 @@ describe('Parser Integration Tests', function(): void { return false; }); `); - await page.evaluate(` - window.term.write('\x1bP1;2+psome data\x1b\\\\'); - `); + await writeSync(page, '\x1bP1;2+psome data\x1b\\\\'); assert.deepEqual(await page.evaluate(`(() => _customDcsHandlerCallStack)();`), [['C', [1, 2], 'some data'], ['B', [1, 2], 'some data']]); }); }); @@ -86,9 +83,7 @@ describe('Parser Integration Tests', function(): void { return false; }); `); - await page.evaluate(` - window.term.write('\x1b(B'); - `); + await writeSync(page, '\x1b(B'); assert.deepEqual(await page.evaluate(`(() => _customEscHandlerCallStack)();`), ['C', 'B']); }); }); @@ -110,9 +105,7 @@ describe('Parser Integration Tests', function(): void { return false; }); `); - await page.evaluate(` - window.term.write('\x1b]1234;some data\x07'); - `); + await writeSync(page, '\x1b]1234;some data\x07'); assert.deepEqual(await page.evaluate(`(() => _customOscHandlerCallStack)();`), [['C', 'some data'], ['B', 'some data']]); }); }); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index a31c2b46..d3674812 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -12,7 +12,6 @@ export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); if (!deepEqual(result, val)) { - console.log('result', result, 'val', val); return new Promise(r => { setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); }); From 669622108cbe19d1ef1126d478aff291d261336b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:48:09 -0800 Subject: [PATCH 262/338] Wait for all elements to be rendered in web links --- addons/xterm-addon-web-links/src/WebLinksAddon.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 0cb222c8..7bbaf29e 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -82,7 +82,7 @@ async function openTerminal(options: ITerminalOptions = {}): Promise { async function pollForLinkAtCell(col: number, row: number, value: string): Promise { const rowSelector = `.xterm-rows > :nth-child(${row})`; - await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length > 0`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`)); + await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length >= ${value.length}`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`)); assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value); } From 057d0a16f2ef5c41abf4854f2b23158f0a831c25 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 18:54:24 -0800 Subject: [PATCH 263/338] Ensure link hover exists before hovering --- addons/xterm-addon-web-links/src/WebLinksAddon.api.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts index 7bbaf29e..ab61fdbf 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.api.ts @@ -82,6 +82,8 @@ async function openTerminal(options: ITerminalOptions = {}): Promise { async function pollForLinkAtCell(col: number, row: number, value: string): Promise { const rowSelector = `.xterm-rows > :nth-child(${row})`; + // Ensure the hover element exists before trying to hover it + await pollFor(page, `!!document.querySelector('${rowSelector} > :nth-child(${col})')`, true); await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length >= ${value.length}`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`)); assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value); } From 40b3766abc26c4a3233d9511bed80cdf872b76b1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Nov 2019 19:05:20 -0800 Subject: [PATCH 264/338] Fix import that got lost in merge --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index c3c54340..51dae37a 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; +import { assert } from 'chai'; const APP = 'http://127.0.0.1:3000/test'; From 8a6e0fca07fa28ef51df1d1af296c73ab53f402b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 21:15:07 -0800 Subject: [PATCH 265/338] Move parts over to CellData --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index ab2cb354..785f6e69 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -23,6 +23,7 @@ import { IColorSet } from 'browser/Types'; import { FLAGS } from './Constants'; import { getCompatAttr } from './CharDataCompat'; import { EventEmitter } from 'common/EventEmitter'; +import { CellData } from 'common/buffer/CellData'; export const INDICIES_PER_CELL = 4; @@ -32,6 +33,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _devicePixelRatio: number; private _model: RenderModel = new RenderModel(); + private _workCell: CellData = new CellData(); private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; @@ -258,12 +260,19 @@ export class WebglRenderer extends Disposable implements IRenderer { const line = terminal.buffer.lines.get(row)!; this._model.lineLengths[y] = 0; for (let x = 0; x < terminal.cols; x++) { - const charData = line.get(x); - const chars = charData[CHAR_DATA_CHAR_INDEX]; - let code = charData[CHAR_DATA_CODE_INDEX]; + line.loadCell(x, this._workCell); + + // const charData = line.get(x); + // const chars = charData[CHAR_DATA_CHAR_INDEX]; + const chars = this._workCell.getChars(); + // let code = charData[CHAR_DATA_CODE_INDEX]; + let code = this._workCell.getCode(); const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; + // console.log('workcell', this._workCell); + // console.log('attr: ' + attr); + if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; } From e72ee3c58dc87ccfaffd103eff24439e1554ce96 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:28:59 -0800 Subject: [PATCH 266/338] Move webgl drawing over from compat Still need to tackle glyph key --- .../src/RectangleRenderer.ts | 25 ++++++++++++------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 22 +++++++--------- .../src/atlas/CharAtlasUtils.ts | 4 +-- .../src/atlas/WebglCharAtlas.ts | 14 +++++------ 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 2c464d2a..70b5ee3c 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -8,7 +8,7 @@ import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelect import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from './atlas/CharAtlasUtils'; -import { DEFAULT_COLOR } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; @@ -248,37 +248,44 @@ export class RectangleRenderer { for (let y = 0; y < terminal.rows; y++) { let currentStartX = -1; let currentBg = DEFAULT_COLOR; + let currentFg = DEFAULT_COLOR; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * 4; const bg = model.cells[modelIndex + 2]; + const fg = model.cells[modelIndex + 1]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color + // TODO: DEFAULT_COLOR probably isn't right anymore? if (currentBg !== DEFAULT_COLOR) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; - this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y); + this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y); } currentStartX = x; currentBg = bg; + currentFg = fg; } } // Finish rectangle if it's still going if (currentBg !== DEFAULT_COLOR) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; - this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y); + this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y); } } vertices.count = rectangleCount; } - private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void { + private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { let color: IColor | null = null; - if (bg === INVERTED_DEFAULT_COLOR) { + if (fg & FgFlags.INVERSE) { + // Inverted color color = this._colors.foreground; - } else if (is256Color(bg)) { - color = this._colors.ansi[bg]; + } else if ((bg & Attributes.CM_MASK) === Attributes.CM_P16 || (bg & Attributes.CM_MASK) === Attributes.CM_P256) { + // 256 palette + color = this._colors.ansi[bg & Attributes.PCOLOR_MASK]; } else { - // TODO: Add support for true color - color = this._colors.foreground; + // TODO: Support true color + // Default color + color = this._colors.background; } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 785f6e69..ef20f614 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,7 +14,7 @@ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; @@ -262,17 +262,11 @@ export class WebglRenderer extends Disposable implements IRenderer { for (let x = 0; x < terminal.cols; x++) { line.loadCell(x, this._workCell); - // const charData = line.get(x); - // const chars = charData[CHAR_DATA_CHAR_INDEX]; const chars = this._workCell.getChars(); - // let code = charData[CHAR_DATA_CODE_INDEX]; let code = this._workCell.getCode(); const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; - // console.log('workcell', this._workCell); - // console.log('attr: ' + attr); - if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; } @@ -283,12 +277,11 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Resolve bg and fg and cache in the model - const flags = attr >> 18; - let bg = attr & 0x1ff; - let fg = (attr >> 9) & 0x1ff; + let bg = this._workCell.bg; + let fg = this._workCell.fg; // If inverse flag is on, the foreground should become the background. - if (flags & FLAGS.INVERSE) { + if (this._workCell.isInverse()) { const temp = bg; bg = fg; fg = temp; @@ -299,8 +292,11 @@ export class WebglRenderer extends Disposable implements IRenderer { bg = INVERTED_DEFAULT_COLOR; } } - const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; - fg += drawInBrightColor ? 8 : 0; + + // Apply drawBoldTextInBrightColors + if (terminal.options.drawBoldTextInBrightColors && this._workCell.isBold() && fg & FgFlags.BOLD && this._workCell.getFgColor() < 8) { + fg += 8; + } // Flag combined chars with a bit mask so they're easily identifiable if (chars.length > 1) { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 857fa628..821b53a4 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DEFAULT_COLOR } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants'; import { Terminal, FontWeight } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; @@ -57,5 +57,5 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean } export function is256Color(colorCode: number): boolean { - return colorCode < DEFAULT_COLOR; + return (colorCode & Attributes.CM_MASK) === Attributes.CM_P16 || (colorCode & Attributes.CM_MASK) === Attributes.CM_P256; } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 8d8e995b..14cd017d 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,7 +6,7 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; @@ -161,26 +161,26 @@ export class WebglCharAtlas implements IDisposable { return this._config.colors.ansi[idx]; } - private _getBackgroundColor(bg: number): IColor { + private _getBackgroundColor(bg: number, fg: number): IColor { if (this._config.allowTransparency) { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. return TRANSPARENT_COLOR; - } else if (bg === INVERTED_DEFAULT_COLOR) { + } else if (fg & FgFlags.INVERSE) { return this._config.colors.foreground; } else if (is256Color(bg)) { - return this._getColorFromAnsiIndex(bg); + return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); } // TODO: Support true color return this._config.colors.background; } private _getForegroundColor(fg: number): IColor { - if (fg === INVERTED_DEFAULT_COLOR) { + if (fg & FgFlags.INVERSE) { return this._config.colors.background; } else if (is256Color(fg)) { - return this._getColorFromAnsiIndex(fg); + return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); } // TODO: Support true color return this._config.colors.foreground; @@ -202,7 +202,7 @@ export class WebglCharAtlas implements IDisposable { this._tmpCtx.save(); // draw the background - const backgroundColor = this._getBackgroundColor(bg); + const backgroundColor = this._getBackgroundColor(bg, fg); // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of // transparency in backgroundColor this._tmpCtx.globalCompositeOperation = 'copy'; From 8f0f8bf827435d9268ef59d88b4a54fd3b99fdeb Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:31:10 -0800 Subject: [PATCH 267/338] Remove cache reliance on flags --- .../src/atlas/WebglCharAtlas.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 14cd017d..f3858c69 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,7 +6,7 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; @@ -103,7 +103,7 @@ export class WebglCharAtlas implements IDisposable { protected _doWarmUp(): void { // Pre-fill with ASCII 33-126 for (let i = 33; i < 126; i++) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR); + const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR); this._cacheMap[i] = { [DEFAULT_ATTR]: rasterizedGlyph }; @@ -131,7 +131,7 @@ export class WebglCharAtlas implements IDisposable { } let rasterizedGlyph = rasterizedGlyphSet[attr]; if (!rasterizedGlyph) { - rasterizedGlyph = this._drawToCache(chars, attr, bg, fg); + rasterizedGlyph = this._drawToCache(chars, bg, fg); rasterizedGlyphSet[attr] = rasterizedGlyph; } return rasterizedGlyph; @@ -148,7 +148,7 @@ export class WebglCharAtlas implements IDisposable { } let rasterizedGlyph = rasterizedGlyphSet[attr]; if (!rasterizedGlyph) { - rasterizedGlyph = this._drawToCache(code, attr, bg, fg); + rasterizedGlyph = this._drawToCache(code, bg, fg); rasterizedGlyphSet[attr] = rasterizedGlyph; } return rasterizedGlyph; @@ -186,18 +186,16 @@ export class WebglCharAtlas implements IDisposable { return this._config.colors.foreground; } - private _drawToCache(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph; - private _drawToCache(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph; - private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number): IRasterizedGlyph { + private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph; + private _drawToCache(chars: string, bg: number, fg: number): IRasterizedGlyph; + private _drawToCache(codeOrChars: number | string, bg: number, fg: number): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; this.hasCanvasChanged = true; - const flags = attr >> 18; - - const bold = !!(flags & FLAGS.BOLD); - const dim = !!(flags & FLAGS.DIM); - const italic = !!(flags & FLAGS.ITALIC); + const bold = !!(fg & FgFlags.BOLD); + const dim = !!(bg & BgFlags.DIM); + const italic = !!(bg & BgFlags.ITALIC); this._tmpCtx.save(); From e15476704ca9fecbf93e607ce7253eedd040840e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:43:25 -0800 Subject: [PATCH 268/338] Adopt new [bg][fg] map in char cache --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 14 ++++---- addons/xterm-addon-webgl/src/Types.d.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 +- .../src/atlas/WebglCharAtlas.ts | 32 ++++++++++++++----- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index bd4488dc..dd83131e 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -169,11 +169,11 @@ export class GlyphRenderer { return this._atlas ? this._atlas.beginFrame() : true; } - public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void { - this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars); + public updateCell(x: number, y: number, code: number, bg: number, fg: number, chars: string): void { + this._updateCell(this._vertices.attributes, x, y, code, bg, fg, chars); } - private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void { + private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, chars?: string): void { const terminal = this._terminal; const i = (y * terminal.cols + x) * INDICES_PER_CELL; @@ -189,9 +189,9 @@ export class GlyphRenderer { throw new Error('atlas must be set before updating cell'); } if (chars && chars.length > 1) { - rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg); + rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg); } else { - rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg); + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg); } // Fill empty if no glyph was found @@ -269,9 +269,9 @@ export class GlyphRenderer { line = terminal.buffer.getLine(row); } const chars = line!.getCell(x)!.char; - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars); } else { - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg); } } } diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index 6ebc8d64..f01092ee 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -4,7 +4,7 @@ */ export interface IRasterizedGlyphSet { - [flags: number]: IRasterizedGlyph; + [bg: number]: { [fg: number]: IRasterizedGlyph } | undefined; } /** diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index ef20f614..afdb7fa4 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -304,11 +304,12 @@ export class WebglRenderer extends Disposable implements IRenderer { } this._model.cells[i ] = code; + // TODO: Remove attr from model this._model.cells[i + 1] = attr; this._model.cells[i + 2] = bg; this._model.cells[i + 3] = fg; - this._glyphRenderer.updateCell(x, y, code, attr, bg, fg, chars); + this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); } } this._rectangleRenderer.updateBackgrounds(this._model); diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index f3858c69..9d8eb286 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; +import { DIM_OPACITY } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; @@ -105,7 +105,9 @@ export class WebglCharAtlas implements IDisposable { for (let i = 33; i < 126; i++) { const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR); this._cacheMap[i] = { - [DEFAULT_ATTR]: rasterizedGlyph + [DEFAULT_COLOR]: { + [DEFAULT_COLOR]: rasterizedGlyph + } }; } } @@ -123,16 +125,23 @@ export class WebglCharAtlas implements IDisposable { return false; } - public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph { + public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph { let rasterizedGlyphSet = this._cacheMapCombined[chars]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; this._cacheMapCombined[chars] = rasterizedGlyphSet; } - let rasterizedGlyph = rasterizedGlyphSet[attr]; + let rasterizedGlyph: IRasterizedGlyph | undefined; + const rasterizedGlyphSetBg = rasterizedGlyphSet[bg]; + if (rasterizedGlyphSetBg) { + rasterizedGlyph = rasterizedGlyphSetBg[fg]; + } if (!rasterizedGlyph) { rasterizedGlyph = this._drawToCache(chars, bg, fg); - rasterizedGlyphSet[attr] = rasterizedGlyph; + if (!rasterizedGlyphSet[bg]) { + rasterizedGlyphSet[bg] = {}; + } + rasterizedGlyphSet[bg]![fg] = rasterizedGlyph; } return rasterizedGlyph; } @@ -140,16 +149,23 @@ export class WebglCharAtlas implements IDisposable { /** * Gets the glyphs texture coords, drawing the texture if it's not already */ - public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph { + public getRasterizedGlyph(code: number, bg: number, fg: number): IRasterizedGlyph { let rasterizedGlyphSet = this._cacheMap[code]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; this._cacheMap[code] = rasterizedGlyphSet; } - let rasterizedGlyph = rasterizedGlyphSet[attr]; + let rasterizedGlyph: IRasterizedGlyph | undefined; + const rasterizedGlyphSetBg = rasterizedGlyphSet[bg]; + if (rasterizedGlyphSetBg) { + rasterizedGlyph = rasterizedGlyphSetBg[fg]; + } if (!rasterizedGlyph) { rasterizedGlyph = this._drawToCache(code, bg, fg); - rasterizedGlyphSet[attr] = rasterizedGlyph; + if (!rasterizedGlyphSet[bg]) { + rasterizedGlyphSet[bg] = {}; + } + rasterizedGlyphSet[bg]![fg] = rasterizedGlyph; } return rasterizedGlyph; } From 9dd2ccb1ad87e8af70d970eb481316097cf8b9dc Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:48:28 -0800 Subject: [PATCH 269/338] Fix selection color to set correct mode --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 6 +++--- addons/xterm-addon-webgl/src/WebglRenderer.ts | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index dd83131e..00b21629 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -10,7 +10,7 @@ import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { getLuminance } from './ColorUtils'; import { Terminal, IBufferLine } from 'xterm'; import { IColorSet } from 'browser/Types'; @@ -223,8 +223,8 @@ export class GlyphRenderer { // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. const lumi = getLuminance(this._colors.background); - const fg = lumi > 0.5 ? 7 : 0; - const bg = lumi > 0.5 ? 0 : 7; + const fg = (lumi > 0.5 ? 7 : 0) | Attributes.CM_P16; + const bg = (lumi > 0.5 ? 0 : 7) | Attributes.CM_P16; if (columnSelectMode) { const startCol = model.selection.startCol; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index afdb7fa4..a9a2f860 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -271,15 +271,15 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.lineLengths[y] = x + 1; } - // Nothing has changed, no updates needed - if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) { - continue; - } - - // Resolve bg and fg and cache in the model + // Resolve bg and fg let bg = this._workCell.bg; let fg = this._workCell.fg; + // Nothing has changed, no updates needed + if (this._model.cells[i] === code && this._model.cells[i + 2] === bg && this._model.cells[i + 3] === fg) { + continue; + } + // If inverse flag is on, the foreground should become the background. if (this._workCell.isInverse()) { const temp = bg; @@ -303,6 +303,7 @@ export class WebglRenderer extends Disposable implements IRenderer { code = code | COMBINED_CHAR_BIT_MASK; } + // Cache the results in the model this._model.cells[i ] = code; // TODO: Remove attr from model this._model.cells[i + 1] = attr; From a2bd0e8eaac09bfcf6eea556685f821a048f5e7d Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:50:22 -0800 Subject: [PATCH 270/338] Remove attr compat from webgl --- .../xterm-addon-webgl/src/CharDataCompat.ts | 28 ------------------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 7 ++--- 2 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/CharDataCompat.ts diff --git a/addons/xterm-addon-webgl/src/CharDataCompat.ts b/addons/xterm-addon-webgl/src/CharDataCompat.ts deleted file mode 100644 index 13613c88..00000000 --- a/addons/xterm-addon-webgl/src/CharDataCompat.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { CellData } from 'common/buffer/CellData'; -import { FLAGS } from './Constants'; -import { IBufferLine } from 'common/Types'; - -export function getCompatAttr(bufferLine: IBufferLine, index: number): number { - // TODO: Need to move WebGL over to the new system and remove this block - const cell = new CellData(); - bufferLine.loadCell(index, cell); - const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor(); - const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor(); - const oldAttr = - (cell.isBold() ? FLAGS.BOLD : 0) | - (cell.isUnderline() ? FLAGS.UNDERLINE : 0) | - (cell.isBlink() ? FLAGS.BLINK : 0) | - (cell.isInverse() ? FLAGS.INVERSE : 0) | - (cell.isDim() ? FLAGS.DIM : 0) | - (cell.isItalic() ? FLAGS.ITALIC : 0); - const attrCompat = - oldBg | - (oldFg << 9) | - (oldAttr << 18); - return attrCompat; -} diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a9a2f860..9acbc8df 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,14 +14,12 @@ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; -import { FLAGS } from './Constants'; -import { getCompatAttr } from './CharDataCompat'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; @@ -264,7 +262,6 @@ export class WebglRenderer extends Disposable implements IRenderer { const chars = this._workCell.getChars(); let code = this._workCell.getCode(); - const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; if (code !== NULL_CELL_CODE) { @@ -306,7 +303,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i ] = code; // TODO: Remove attr from model - this._model.cells[i + 1] = attr; + this._model.cells[i + 1] = 0; this._model.cells[i + 2] = bg; this._model.cells[i + 3] = fg; From 2b5d229aabfa63e4d6e4d28b9fd66c754ee686c1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 22:56:42 -0800 Subject: [PATCH 271/338] Reduce size of model to 3 by removing attr --- addons/xterm-addon-webgl/src/Constants.ts | 12 ++---------- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 3 --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 7 +++---- addons/xterm-addon-webgl/src/RenderModel.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 13 +++++++------ .../xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 3 +-- 6 files changed, 14 insertions(+), 26 deletions(-) diff --git a/addons/xterm-addon-webgl/src/Constants.ts b/addons/xterm-addon-webgl/src/Constants.ts index ab18b97a..bf39ec98 100644 --- a/addons/xterm-addon-webgl/src/Constants.ts +++ b/addons/xterm-addon-webgl/src/Constants.ts @@ -3,13 +3,5 @@ * @license MIT */ -// TODO: Should be removed after chardata workaround is fixed -export const enum FLAGS { - BOLD = 1, - UNDERLINE = 2, - BLINK = 4, - INVERSE = 8, - INVISIBLE = 16, - DIM = 32, - ITALIC = 64 -} +export const MODEL_BG_OFFSET = 2; +export const MODEL_FG_OFFSET = 3; diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 00b21629..a28d5c10 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -260,9 +260,6 @@ export class GlyphRenderer { let line: IBufferLine | undefined; for (let x = startCol; x < endCol; x++) { const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL; - // Because the cache uses attr as a lookup key it needs to contain the selection colors as well - let attr = model.cells[offset + 1]; - attr = attr & ~0x3ffff | bg << 9 | fg; const code = model.cells[offset]; if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 70b5ee3c..3a5f6417 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -6,12 +6,11 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; +import { MODEL_FG_OFFSET, MODEL_BG_OFFSET } from './Constants'; const enum VertexAttribLocations { POSITION = 0, @@ -251,8 +250,8 @@ export class RectangleRenderer { let currentFg = DEFAULT_COLOR; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * 4; - const bg = model.cells[modelIndex + 2]; - const fg = model.cells[modelIndex + 1]; + const bg = model.cells[modelIndex + MODEL_BG_OFFSET]; + const fg = model.cells[modelIndex + MODEL_FG_OFFSET]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color // TODO: DEFAULT_COLOR probably isn't right anymore? diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index a9ee24e9..f55111d4 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -6,7 +6,7 @@ import { IRenderModel, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; -export const RENDER_MODEL_INDICIES_PER_CELL = 4; +export const RENDER_MODEL_INDICIES_PER_CELL = 3; export const COMBINED_CHAR_BIT_MASK = 0x80000000; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 9acbc8df..8bbd1b64 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -22,6 +22,7 @@ import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/ import { IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; +import { MODEL_BG_OFFSET, MODEL_FG_OFFSET } from './Constants'; export const INDICIES_PER_CELL = 4; @@ -273,7 +274,9 @@ export class WebglRenderer extends Disposable implements IRenderer { let fg = this._workCell.fg; // Nothing has changed, no updates needed - if (this._model.cells[i] === code && this._model.cells[i + 2] === bg && this._model.cells[i + 3] === fg) { + if (this._model.cells[i] === code && + this._model.cells[i + MODEL_BG_OFFSET] === bg && + this._model.cells[i + MODEL_FG_OFFSET] === fg) { continue; } @@ -301,11 +304,9 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Cache the results in the model - this._model.cells[i ] = code; - // TODO: Remove attr from model - this._model.cells[i + 1] = 0; - this._model.cells[i + 2] = bg; - this._model.cells[i + 3] = fg; + this._model.cells[i] = code; + this._model.cells[i + MODEL_BG_OFFSET] = bg; + this._model.cells[i + MODEL_FG_OFFSET] = fg; this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9d8eb286..b9c4a827 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,11 +6,10 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { DEFAULT_COLOR, DEFAULT_ATTR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; -import { FLAGS } from '../Constants'; import { IDisposable } from 'xterm'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, From 39f0e17edf4ff0a64d51eea31f7e4c3a01db09a6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 23:05:39 -0800 Subject: [PATCH 272/338] Consolidate constants, fix rectangle rendering --- addons/xterm-addon-webgl/src/Constants.ts | 7 ------- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 5 ++--- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 8 ++++---- addons/xterm-addon-webgl/src/RenderModel.ts | 2 ++ addons/xterm-addon-webgl/src/WebglRenderer.ts | 15 ++++++--------- 5 files changed, 14 insertions(+), 23 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/Constants.ts diff --git a/addons/xterm-addon-webgl/src/Constants.ts b/addons/xterm-addon-webgl/src/Constants.ts deleted file mode 100644 index bf39ec98..00000000 --- a/addons/xterm-addon-webgl/src/Constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -export const MODEL_BG_OFFSET = 2; -export const MODEL_FG_OFFSET = 3; diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index a28d5c10..7ec80907 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -6,8 +6,7 @@ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; -import { INDICIES_PER_CELL } from './WebglRenderer'; -import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; @@ -259,7 +258,7 @@ export class GlyphRenderer { const row = y + terminal.buffer.viewportY; let line: IBufferLine | undefined; for (let x = startCol; x < endCol; x++) { - const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL; + const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL; const code = model.cells[offset]; if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 3a5f6417..0a088034 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -10,7 +10,7 @@ import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; -import { MODEL_FG_OFFSET, MODEL_BG_OFFSET } from './Constants'; +import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; const enum VertexAttribLocations { POSITION = 0, @@ -249,9 +249,9 @@ export class RectangleRenderer { let currentBg = DEFAULT_COLOR; let currentFg = DEFAULT_COLOR; for (let x = 0; x < terminal.cols; x++) { - const modelIndex = ((y * terminal.cols) + x) * 4; - const bg = model.cells[modelIndex + MODEL_BG_OFFSET]; - const fg = model.cells[modelIndex + MODEL_FG_OFFSET]; + const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; + const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; + const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color // TODO: DEFAULT_COLOR probably isn't right anymore? diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index f55111d4..2b48047f 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -7,6 +7,8 @@ import { IRenderModel, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 3; +export const RENDER_MODEL_BG_OFFSET = 1; +export const RENDER_MODEL_FG_OFFSET = 2; export const COMBINED_CHAR_BIT_MASK = 0x80000000; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 8bbd1b64..4d6428f5 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -12,7 +12,7 @@ import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { DEFAULT_COLOR, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; @@ -22,9 +22,6 @@ import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/ import { IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; -import { MODEL_BG_OFFSET, MODEL_FG_OFFSET } from './Constants'; - -export const INDICIES_PER_CELL = 4; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -263,7 +260,7 @@ export class WebglRenderer extends Disposable implements IRenderer { const chars = this._workCell.getChars(); let code = this._workCell.getCode(); - const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; + const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; @@ -275,8 +272,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + MODEL_BG_OFFSET] === bg && - this._model.cells[i + MODEL_FG_OFFSET] === fg) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === fg) { continue; } @@ -305,8 +302,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + MODEL_BG_OFFSET] = bg; - this._model.cells[i + MODEL_FG_OFFSET] = fg; + this._model.cells[i + RENDER_MODEL_BG_OFFSET] = bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = fg; this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); } From 3773b22439b6630aac5c838a225e93cae91b7694 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 Nov 2019 23:41:00 -0800 Subject: [PATCH 273/338] Support true color... mostly --- .../src/RectangleRenderer.ts | 22 +++++++++------ .../src/atlas/WebglCharAtlas.ts | 27 ++++++++++++++++--- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 0a088034..4e711846 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -11,6 +11,7 @@ import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; +import { AttributeData } from 'common/buffer/AttributeData'; const enum VertexAttribLocations { POSITION = 0, @@ -274,26 +275,31 @@ export class RectangleRenderer { } private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { - let color: IColor | null = null; + let rgba: number | undefined; + const colorMode = bg & Attributes.CM_MASK; if (fg & FgFlags.INVERSE) { // Inverted color - color = this._colors.foreground; + rgba = this._colors.foreground.rgba; } else if ((bg & Attributes.CM_MASK) === Attributes.CM_P16 || (bg & Attributes.CM_MASK) === Attributes.CM_P256) { // 256 palette - color = this._colors.ansi[bg & Attributes.PCOLOR_MASK]; + rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; + } else if (colorMode === Attributes.CM_RGB) { + // True color + // TODO: Use a switch instead? + rgba = (bg & Attributes.RGB_MASK) << 8; } else { - // TODO: Support true color // Default color - color = this._colors.background; + rgba = this._colors.background.rgba; } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); } const x1 = startX * this._dimensions.scaledCellWidth; const y1 = y * this._dimensions.scaledCellHeight; - const r = ((color.rgba >> 24) & 0xFF) / 255; - const g = ((color.rgba >> 16) & 0xFF) / 255; - const b = ((color.rgba >> 8 ) & 0xFF) / 255; + const r = ((rgba >> 24) & 0xFF) / 255; + const g = ((rgba >> 16) & 0xFF) / 255; + const b = ((rgba >> 8 ) & 0xFF) / 255; + console.log(r, g, b); this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1); } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b9c4a827..00b24988 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -11,6 +11,7 @@ import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { IDisposable } from 'xterm'; +import { AttributeData } from 'common/buffer/AttributeData'; // 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. @@ -186,18 +187,33 @@ export class WebglCharAtlas implements IDisposable { return this._config.colors.foreground; } else if (is256Color(bg)) { return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); + } else if ((bg & Attributes.CM_MASK) === Attributes.CM_RGB) { + // TODO: Use a switch for the color mode? + // True color + const rgb = bg & Attributes.RGB_MASK; + const arr = AttributeData.toColorRGB(rgb); + // TODO: This object creation is slow + return { + rgba: rgb << 255, + css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` + }; } - // TODO: Support true color return this._config.colors.background; } - private _getForegroundColor(fg: number): IColor { + private _getForegroundColor(fg: number): { css: string } { + // TODO: Just return the string value if (fg & FgFlags.INVERSE) { return this._config.colors.background; } else if (is256Color(fg)) { return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); + } else if ((fg & Attributes.CM_MASK) === Attributes.CM_RGB) { + // TODO: Use a switch for the color mode? + // TODO: True color + const rgb = fg & Attributes.RGB_MASK; + const arr = AttributeData.toColorRGB(rgb); + return { css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; } - // TODO: Support true color return this._config.colors.foreground; } @@ -411,3 +427,8 @@ function clearColor(imageData: ImageData, color: IColor): boolean { } return isEmpty; } + +function toPaddedHex(c: number): string { + const s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} From a2da8f9fbb3dd5a66bcf63f81222c15848168f42 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:23:08 -0800 Subject: [PATCH 274/338] Fix true color glyphs not having background cleared --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 00b24988..0dc3b974 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -194,7 +194,7 @@ export class WebglCharAtlas implements IDisposable { const arr = AttributeData.toColorRGB(rgb); // TODO: This object creation is slow return { - rgba: rgb << 255, + rgba: rgb << 8, css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; } From b13e9066bdc028045457fed73851d408dd0b06aa Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:29:50 -0800 Subject: [PATCH 275/338] Use a switch for color mode conditions --- .../src/RectangleRenderer.ts | 23 ++++---- .../src/atlas/WebglCharAtlas.ts | 54 +++++++++++-------- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 4e711846..ed7e8f0c 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -280,17 +280,21 @@ export class RectangleRenderer { if (fg & FgFlags.INVERSE) { // Inverted color rgba = this._colors.foreground.rgba; - } else if ((bg & Attributes.CM_MASK) === Attributes.CM_P16 || (bg & Attributes.CM_MASK) === Attributes.CM_P256) { - // 256 palette - rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; - } else if (colorMode === Attributes.CM_RGB) { - // True color - // TODO: Use a switch instead? - rgba = (bg & Attributes.RGB_MASK) << 8; } else { - // Default color - rgba = this._colors.background.rgba; + switch (colorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + rgba = (bg & Attributes.RGB_MASK) << 8; + break; + case Attributes.CM_DEFAULT: + default: + rgba = this._colors.background.rgba; + } } + if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); } @@ -299,7 +303,6 @@ export class RectangleRenderer { const r = ((rgba >> 24) & 0xFF) / 255; const g = ((rgba >> 16) & 0xFF) / 255; const b = ((rgba >> 8 ) & 0xFF) / 255; - console.log(r, g, b); this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1); } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 0dc3b974..9a923863 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -185,36 +185,46 @@ export class WebglCharAtlas implements IDisposable { return TRANSPARENT_COLOR; } else if (fg & FgFlags.INVERSE) { return this._config.colors.foreground; - } else if (is256Color(bg)) { - return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); - } else if ((bg & Attributes.CM_MASK) === Attributes.CM_RGB) { - // TODO: Use a switch for the color mode? - // True color - const rgb = bg & Attributes.RGB_MASK; - const arr = AttributeData.toColorRGB(rgb); - // TODO: This object creation is slow - return { - rgba: rgb << 8, - css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` - }; } - return this._config.colors.background; + + const colorMode = bg & Attributes.CM_MASK; + switch (colorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); + case Attributes.CM_RGB: + const rgb = bg & Attributes.RGB_MASK; + const arr = AttributeData.toColorRGB(rgb); + // TODO: This object creation is slow + return { + rgba: rgb << 8, + css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` + }; + case Attributes.CM_DEFAULT: + default: + return this._config.colors.background; + } } private _getForegroundColor(fg: number): { css: string } { // TODO: Just return the string value if (fg & FgFlags.INVERSE) { return this._config.colors.background; - } else if (is256Color(fg)) { - return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); - } else if ((fg & Attributes.CM_MASK) === Attributes.CM_RGB) { - // TODO: Use a switch for the color mode? - // TODO: True color - const rgb = fg & Attributes.RGB_MASK; - const arr = AttributeData.toColorRGB(rgb); - return { css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; } - return this._config.colors.foreground; + + const colorMode = fg & Attributes.CM_MASK; + switch (colorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); + case Attributes.CM_RGB: + const rgb = fg & Attributes.RGB_MASK; + const arr = AttributeData.toColorRGB(rgb); + return { css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; + case Attributes.CM_DEFAULT: + default: + return this._config.colors.foreground; + } } private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph; From 149cc76803c79d525a69bea4862d872eae67e91c Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:30:58 -0800 Subject: [PATCH 276/338] Avoid extra object creation for foreground fill --- .../xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9a923863..69acfdbf 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -206,24 +206,23 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundColor(fg: number): { css: string } { - // TODO: Just return the string value + private _getForegroundCss(fg: number): string { if (fg & FgFlags.INVERSE) { - return this._config.colors.background; + return this._config.colors.background.css; } const colorMode = fg & Attributes.CM_MASK; switch (colorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK); + return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK).css; case Attributes.CM_RGB: const rgb = fg & Attributes.RGB_MASK; const arr = AttributeData.toColorRGB(rgb); - return { css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; + return `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`; case Attributes.CM_DEFAULT: default: - return this._config.colors.foreground; + return this._config.colors.foreground.css; } } @@ -256,7 +255,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundColor(fg).css; + this._tmpCtx.fillStyle = this._getForegroundCss(fg); // Apply alpha to dim the character if (dim) { From b56e29a35bfc7ff26c7fd7e702df744fecaa4a6e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:39:46 -0800 Subject: [PATCH 277/338] Import clean up --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 1 - addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts | 2 +- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ed7e8f0c..7746acb1 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -11,7 +11,6 @@ import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; -import { AttributeData } from 'common/buffer/AttributeData'; const enum VertexAttribLocations { POSITION = 0, diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 821b53a4..1fd4c4f6 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants'; +import { Attributes } from 'common/buffer/Constants'; import { Terminal, FontWeight } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 69acfdbf..c364c3c1 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -7,7 +7,6 @@ import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, FgFlags, Attributes, BgFlags } from 'common/buffer/Constants'; -import { is256Color } from './CharAtlasUtils'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { IDisposable } from 'xterm'; From 2cde01139781642f4b4d03ddedcc480f35137fa5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:49:10 -0800 Subject: [PATCH 278/338] Add retries to onRender test I've seen this flaky locally and in CI --- test/api/Terminal.api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 0b05736d..40deecf2 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -356,7 +356,8 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.callCount`, 2); }); - it('onRender', async () => { + it('onRender', async function (): Promise { + this.retries(3); await openTerminal(); await timeout(20); // Ensure all init events are fired await page.evaluate(` From 811307562ba9b4eca27f930f51096d3bbeb7a2fc Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 00:56:43 -0800 Subject: [PATCH 279/338] Use actual default color for rectangle renderer --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 7746acb1..d5e245c0 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -6,7 +6,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; -import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants'; +import { Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; @@ -246,16 +246,15 @@ export class RectangleRenderer { for (let y = 0; y < terminal.rows; y++) { let currentStartX = -1; - let currentBg = DEFAULT_COLOR; - let currentFg = DEFAULT_COLOR; + let currentBg = 0; + let currentFg = 0; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color - // TODO: DEFAULT_COLOR probably isn't right anymore? - if (currentBg !== DEFAULT_COLOR) { + if (currentBg !== 0) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y); } @@ -265,7 +264,7 @@ export class RectangleRenderer { } } // Finish rectangle if it's still going - if (currentBg !== DEFAULT_COLOR) { + if (currentBg !== 0) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y); } From 01628362f566524e55aa15f58f3ac1fd5178336c Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 10 Nov 2019 01:19:27 -0800 Subject: [PATCH 280/338] Support selection color --- addons/xterm-addon-webgl/src/ColorUtils.ts | 14 ----------- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 23 ++++++++----------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 14 ----------- 3 files changed, 9 insertions(+), 42 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/ColorUtils.ts diff --git a/addons/xterm-addon-webgl/src/ColorUtils.ts b/addons/xterm-addon-webgl/src/ColorUtils.ts deleted file mode 100644 index 80372e65..00000000 --- a/addons/xterm-addon-webgl/src/ColorUtils.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @license MIT - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - */ - -import { IColor } from 'browser/Types'; - -export function getLuminance(color: IColor): number { - // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast - const r = color.rgba >> 24 & 0xff; - const g = color.rgba >> 16 & 0xff; - const b = color.rgba >> 8 & 0xff; - return (0.299 * r + 0.587 * g + 0.114 * b) / 255; -} diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 7ec80907..3ee9cdb5 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -6,11 +6,10 @@ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; -import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; +import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; -import { getLuminance } from './ColorUtils'; import { Terminal, IBufferLine } from 'xterm'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; @@ -219,41 +218,37 @@ export class GlyphRenderer { this._vertices.selectionAttributes = slice(this._vertices.attributes, 0); - // TODO: Make fg and bg configurable, currently since the buffer doesn't - // support truecolor the char atlas cannot store it. - const lumi = getLuminance(this._colors.background); - const fg = (lumi > 0.5 ? 7 : 0) | Attributes.CM_P16; - const bg = (lumi > 0.5 ? 0 : 7) | Attributes.CM_P16; + const bg = (this._colors.selection.rgba >>> 8) | Attributes.CM_RGB; if (columnSelectMode) { const startCol = model.selection.startCol; const width = model.selection.endCol - startCol; const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1; for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) { - this._updateSelectionRange(startCol, startCol + width, y, model, bg, fg); + this._updateSelectionRange(startCol, startCol + width, y, model, bg); } } else { // Draw first row const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0; const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; - this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg, fg); + this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg); // Draw middle rows const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0); for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { - this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg); + this._updateSelectionRange(0, startRowEndCol, y, model, bg); } // Draw final row if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) { // Only draw viewportEndRow if it's not the same as viewportStartRow const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; - this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg, fg); + this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg); } } } - private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void { + private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void { const terminal = this._terminal; const row = y + terminal.buffer.viewportY; let line: IBufferLine | undefined; @@ -265,9 +260,9 @@ export class GlyphRenderer { line = terminal.buffer.getLine(row); } const chars = line!.getCell(x)!.char; - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET], chars); } else { - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET]); } } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 4d6428f5..5eae61db 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -16,7 +16,6 @@ import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MOD import { Disposable } from 'common/Lifecycle'; import { DEFAULT_COLOR, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; -import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; @@ -52,8 +51,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core = (this._terminal)._core; - this._applyBgLuminanceBasedSelection(); - this._renderLayers = [ new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core), new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows) @@ -101,20 +98,9 @@ export class WebglRenderer extends Disposable implements IRenderer { super.dispose(); } - private _applyBgLuminanceBasedSelection(): void { - // HACK: This is needed until webgl renderer adds support for selection colors - if (getLuminance(this._colors.background) > 0.5) { - this._colors.selection = { css: '#000', rgba: 255 }; - } else { - this._colors.selection = { css: '#fff', rgba: 4294967295 }; - } - } - public setColors(colors: IColorSet): void { this._colors = colors; - this._applyBgLuminanceBasedSelection(); - // Clear layers and force a full render this._renderLayers.forEach(l => { l.setColors(this._terminal, this._colors); From 8bf837795ffff9f70b17e0714f13e173bd77cb7f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 10:29:50 -0800 Subject: [PATCH 281/338] Add tests for webgl colors 16-255 --- .../src/WebglRenderer.api.ts | 81 +++++++++++++++---- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 51dae37a..3f7dee35 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -7,6 +7,7 @@ import * as puppeteer from 'puppeteer'; import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; import { assert } from 'chai'; +import deepEqual = require('deep-equal'); const APP = 'http://127.0.0.1:3000/test'; @@ -15,7 +16,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('WebGL Renderer Integration Tests', function(): void { +describe.only('WebGL Renderer Integration Tests', function(): void { it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); @@ -29,7 +30,7 @@ describe('WebGL Renderer Integration Tests', function(): void { after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); - it('foreground colors normal', async () => { + it('foreground 0-15', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -52,7 +53,7 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('foreground colors bright', async () => { + it('foreground 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', brightRed: '#040506', @@ -75,7 +76,7 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background colors normal', async () => { + it('background 0-15 normal', async () => { const theme: ITheme = { black: '#010203', red: '#040506', @@ -98,7 +99,7 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background colors bright', async () => { + it('background 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', brightRed: '#040506', @@ -120,6 +121,46 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + + it('background 16-255', async function(): Promise { + let data = ''; + for (let x = 0; x < 240 / 16; x++) { + for (let y = 0; y < 16; y++) { + data += `\\x1b[48;5;${16 + x * 16 + y}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + it('foreground 16-255', async function(): Promise { + let data = ''; + for (let x = 0; x < 240 / 16; x++) { + for (let y = 0; y < 16; y++) { + data += `\\x1b[38;5;${16 + x * 16 + y}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); }); }); @@ -168,20 +209,32 @@ async function setupBrowser(): Promise { `); } -async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { +export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise): Promise { if (preFn) { await preFn(); } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); - let equal = false; - if (typeof result === 'object') { - equal = Object.keys(result).every(e => result[e] === (val as any)[e]); - } else { - equal = result === val; - } - if (!equal) { + if (!deepEqual(result, val)) { return new Promise(r => { - setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 10); + setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); }); } } + +const COLORS_16_TO_255 = [ + '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', + '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', + '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff', + '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', + '#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', + '#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff', + '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af', + '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', + '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', + '#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', + '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', + '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', + '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', + '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', + '#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee' +]; From 753ad30dbc0b48b96483d31fbdbf38d18c1f5d3d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 10:38:19 -0800 Subject: [PATCH 282/338] Add true color fg tests for webgl --- .../src/WebglRenderer.api.ts | 88 +++++++++++++++++-- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 3f7dee35..71add30a 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -122,11 +122,11 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background 16-255', async function(): Promise { + it('background 16-255', async () => { let data = ''; - for (let x = 0; x < 240 / 16; x++) { - for (let y = 0; y < 16; y++) { - data += `\\x1b[48;5;${16 + x * 16 + y}m \x1b[0m`; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; } data += '\\r\\n'; } @@ -142,11 +142,11 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); - it('foreground 16-255', async function(): Promise { + it('foreground 16-255', async () => { let data = ''; - for (let x = 0; x < 240 / 16; x++) { - for (let y = 0; y < 16; y++) { - data += `\\x1b[38;5;${16 + x * 16 + y}m█\x1b[0m`; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; } data += '\\r\\n'; } @@ -161,6 +161,78 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } } }); + + it('foreground true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[38;2;${i};0;0m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + it('foreground true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[38;2;0;${i};0m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + it('foreground true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[38;2;0;0;${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + it('foreground true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[38;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); }); }); From 350ca8d5fb08000857e6a792f098560d257fac6b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 10:39:56 -0800 Subject: [PATCH 283/338] Organize tests --- .../src/WebglRenderer.api.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 71add30a..bb2f5969 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -53,6 +53,29 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + it('background 0-15', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + it('foreground 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', @@ -76,29 +99,6 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background 0-15 normal', async () => { - const theme: ITheme = { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); - await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); - await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); - await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); - await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); - await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); - await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); - await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); - await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); - }); - it('background 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', @@ -122,11 +122,11 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); - it('background 16-255', async () => { + it('foreground 16-255', async () => { let data = ''; for (let y = 0; y < 240 / 16; y++) { for (let x = 0; x < 16; x++) { - data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; + data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; } data += '\\r\\n'; } @@ -142,11 +142,11 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); - it('foreground 16-255', async () => { + it('background 16-255', async () => { let data = ''; for (let y = 0; y < 240 / 16; y++) { for (let x = 0; x < 16; x++) { - data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`; + data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`; } data += '\\r\\n'; } From 2b2bd91e2ef5a8208e569add26b8e91679ea7908 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 10:41:15 -0800 Subject: [PATCH 284/338] Add background true color tests --- .../src/WebglRenderer.api.ts | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index bb2f5969..304973fe 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -16,7 +16,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe.only('WebGL Renderer Integration Tests', function(): void { +describe('WebGL Renderer Integration Tests', function(): void { it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); @@ -180,6 +180,24 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); + it('background true color red', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[48;2;${i};0;0m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + it('foreground true color green', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -198,6 +216,24 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); + it('background true color green', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[48;2;0;${i};0m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + it('foreground true color blue', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -216,6 +252,24 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); + it('background true color blue', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[48;2;0;0;${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + it('foreground true color grey', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -233,6 +287,24 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } } }); + + it('background true color grey', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[48;2;${i};${i};${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); }); }); From 53b0149764a94471c6a3dcb32510505cf837f928 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:11:26 -0800 Subject: [PATCH 285/338] Add selectionOpaque and factor out color into new file --- .../src/atlas/CharAtlasUtils.ts | 1 + src/browser/Color.test.ts | 31 ++++++++++ src/browser/Color.ts | 39 +++++++++++++ src/browser/ColorManager.ts | 56 ++++++++----------- src/browser/Types.d.ts | 2 + 5 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 src/browser/Color.test.ts create mode 100644 src/browser/Color.ts diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 1fd4c4f6..03867b41 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -21,6 +21,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number cursor: NULL_COLOR, cursorAccent: NULL_COLOR, selection: NULL_COLOR, + selectionOpaque: NULL_COLOR, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. ansi: colors.ansi.slice() diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts new file mode 100644 index 00000000..fc75d4b9 --- /dev/null +++ b/src/browser/Color.test.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { blend } from 'browser/Color'; + +describe('Color', () => { + describe('blend', () => { + it('should blend colors based on the alpha channel', () => { + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF }); + assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }); + }); + }); +}); diff --git a/src/browser/Color.ts b/src/browser/Color.ts new file mode 100644 index 00000000..31087116 --- /dev/null +++ b/src/browser/Color.ts @@ -0,0 +1,39 @@ +import { IColor } from './Types'; + +export function blend(bg: IColor, fg: IColor): IColor { + const a = (fg.rgba & 0xFF) / 255; + if (a === 1) { + return { + css: fg.css, + rgba: fg.rgba + }; + } + const fgR = (fg.rgba >> 24) & 0xFF; + const fgG = (fg.rgba >> 16) & 0xFF; + const fgB = (fg.rgba >> 8) & 0xFF; + const bgR = (bg.rgba >> 24) & 0xFF; + const bgG = (bg.rgba >> 16) & 0xFF; + const bgB = (bg.rgba >> 8) & 0xFF; + const r = bgR + Math.round((fgR - bgR) * a); + const g = bgG + Math.round((fgG - bgG) * a); + const b = bgB + Math.round((fgB - bgB) * a); + const css = toCss(r, g, b); + const rgba = ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0; + return { css, rgba }; +} + +export function fromCss(css: string): IColor { + return { + css, + rgba: parseInt(css.slice(1), 16) << 8 | 0xFF + }; +} + +export function toPaddedHex(c: number): string { + const s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} + +export function toCss(r: number, g: number, b: number): string { + return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; +} diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 70d21a7a..a1bfc105 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,11 +5,12 @@ import { IColorManager, IColor, IColorSet } from 'browser/Types'; import { ITheme } from 'common/services/Services'; +import { fromCss, toPaddedHex, toCss, blend } from 'browser/Color'; -const DEFAULT_FOREGROUND = fromHex('#ffffff'); -const DEFAULT_BACKGROUND = fromHex('#000000'); -const DEFAULT_CURSOR = fromHex('#ffffff'); -const DEFAULT_CURSOR_ACCENT = fromHex('#000000'); +const DEFAULT_FOREGROUND = fromCss('#ffffff'); +const DEFAULT_BACKGROUND = fromCss('#000000'); +const DEFAULT_CURSOR = fromCss('#ffffff'); +const DEFAULT_CURSOR_ACCENT = fromCss('#000000'); const DEFAULT_SELECTION = { css: 'rgba(255, 255, 255, 0.3)', rgba: 0xFFFFFF77 @@ -20,23 +21,23 @@ const DEFAULT_SELECTION = { export const DEFAULT_ANSI_COLORS = (() => { const colors = [ // dark: - fromHex('#2e3436'), - fromHex('#cc0000'), - fromHex('#4e9a06'), - fromHex('#c4a000'), - fromHex('#3465a4'), - fromHex('#75507b'), - fromHex('#06989a'), - fromHex('#d3d7cf'), + fromCss('#2e3436'), + fromCss('#cc0000'), + fromCss('#4e9a06'), + fromCss('#c4a000'), + fromCss('#3465a4'), + fromCss('#75507b'), + fromCss('#06989a'), + fromCss('#d3d7cf'), // bright: - fromHex('#555753'), - fromHex('#ef2929'), - fromHex('#8ae234'), - fromHex('#fce94f'), - fromHex('#729fcf'), - fromHex('#ad7fa8'), - fromHex('#34e2e2'), - fromHex('#eeeeec') + fromCss('#555753'), + fromCss('#ef2929'), + fromCss('#8ae234'), + fromCss('#fce94f'), + fromCss('#729fcf'), + fromCss('#ad7fa8'), + fromCss('#34e2e2'), + fromCss('#eeeeec') ]; // Fill in the remaining 240 ANSI colors. @@ -47,7 +48,7 @@ export const DEFAULT_ANSI_COLORS = (() => { const g = v[(i / 6) % 6 | 0]; const b = v[i % 6]; colors.push({ - css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`, + css: toCss(r, g, b), // Use >>> 0 to force a conversion to an unsigned int rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0 }); @@ -66,18 +67,6 @@ export const DEFAULT_ANSI_COLORS = (() => { return colors; })(); -function fromHex(css: string): IColor { - return { - css, - rgba: parseInt(css.slice(1), 16) << 8 | 0xFF - }; -} - -function toPaddedHex(c: number): string { - const s = c.toString(16); - return s.length < 2 ? '0' + s : s; -} - /** * Manages the source of truth for a terminal's colors. */ @@ -103,6 +92,7 @@ export class ColorManager implements IColorManager { cursor: DEFAULT_CURSOR, cursorAccent: DEFAULT_CURSOR_ACCENT, selection: DEFAULT_SELECTION, + selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), ansi: DEFAULT_ANSI_COLORS.slice() }; } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index aa7a4c86..ca852b6a 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -21,6 +21,8 @@ export interface IColorSet { cursor: IColor; cursorAccent: IColor; selection: IColor; + /** The selection blended on top of background. */ + selectionOpaque: IColor; ansi: IColor[]; } From 51c77568a8ad2dc1db4bb0fdd1f52183a2dab7b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:17:36 -0800 Subject: [PATCH 286/338] Add tests for Color functions --- src/browser/Color.test.ts | 68 ++++++++++++++++++++++++++++++++++++++- src/browser/Color.ts | 2 +- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index fc75d4b9..5542611f 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -28,4 +28,70 @@ describe('Color', () => { assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }); }); }); + + describe('fromCss', () => { + it('should covert a CSS string to an IColor', () => { + assert.deepEqual(fromCss('#000000'), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(fromCss('#101010'), { css: '#101010', rgba: 0x101010FF }); + assert.deepEqual(fromCss('#202020'), { css: '#202020', rgba: 0x202020FF }); + assert.deepEqual(fromCss('#303030'), { css: '#303030', rgba: 0x303030FF }); + assert.deepEqual(fromCss('#404040'), { css: '#404040', rgba: 0x404040FF }); + assert.deepEqual(fromCss('#505050'), { css: '#505050', rgba: 0x505050FF }); + assert.deepEqual(fromCss('#606060'), { css: '#606060', rgba: 0x606060FF }); + assert.deepEqual(fromCss('#707070'), { css: '#707070', rgba: 0x707070FF }); + assert.deepEqual(fromCss('#808080'), { css: '#808080', rgba: 0x808080FF }); + assert.deepEqual(fromCss('#909090'), { css: '#909090', rgba: 0x909090FF }); + assert.deepEqual(fromCss('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF }); + assert.deepEqual(fromCss('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF }); + assert.deepEqual(fromCss('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF }); + assert.deepEqual(fromCss('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF }); + assert.deepEqual(fromCss('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF }); + assert.deepEqual(fromCss('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF }); + assert.deepEqual(fromCss('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF }); + }); + }); + + describe('toPaddedHex', () => { + it('should convert numbers to 2-digit hex values', () => { + assert.equal(toPaddedHex(0x00), '00'); + assert.equal(toPaddedHex(0x10), '10'); + assert.equal(toPaddedHex(0x20), '20'); + assert.equal(toPaddedHex(0x30), '30'); + assert.equal(toPaddedHex(0x40), '40'); + assert.equal(toPaddedHex(0x50), '50'); + assert.equal(toPaddedHex(0x60), '60'); + assert.equal(toPaddedHex(0x70), '70'); + assert.equal(toPaddedHex(0x80), '80'); + assert.equal(toPaddedHex(0x90), '90'); + assert.equal(toPaddedHex(0xa0), 'a0'); + assert.equal(toPaddedHex(0xb0), 'b0'); + assert.equal(toPaddedHex(0xc0), 'c0'); + assert.equal(toPaddedHex(0xd0), 'd0'); + assert.equal(toPaddedHex(0xe0), 'e0'); + assert.equal(toPaddedHex(0xf0), 'f0'); + assert.equal(toPaddedHex(0xff), 'ff'); + }); + }); + + describe('toCss', () => { + it('should convert an rgb array to css hex string', () => { + assert.equal(toCss(0x00, 0x00, 0x00), '#000000'); + assert.equal(toCss(0x10, 0x10, 0x10), '#101010'); + assert.equal(toCss(0x20, 0x20, 0x20), '#202020'); + assert.equal(toCss(0x30, 0x30, 0x30), '#303030'); + assert.equal(toCss(0x40, 0x40, 0x40), '#404040'); + assert.equal(toCss(0x50, 0x50, 0x50), '#505050'); + assert.equal(toCss(0x60, 0x60, 0x60), '#606060'); + assert.equal(toCss(0x70, 0x70, 0x70), '#707070'); + assert.equal(toCss(0x80, 0x80, 0x80), '#808080'); + assert.equal(toCss(0x90, 0x90, 0x90), '#909090'); + assert.equal(toCss(0xa0, 0xa0, 0xa0), '#a0a0a0'); + assert.equal(toCss(0xb0, 0xb0, 0xb0), '#b0b0b0'); + assert.equal(toCss(0xc0, 0xc0, 0xc0), '#c0c0c0'); + assert.equal(toCss(0xd0, 0xd0, 0xd0), '#d0d0d0'); + assert.equal(toCss(0xe0, 0xe0, 0xe0), '#e0e0e0'); + assert.equal(toCss(0xf0, 0xf0, 0xf0), '#f0f0f0'); + assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff'); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 31087116..9771f656 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -25,7 +25,7 @@ export function blend(bg: IColor, fg: IColor): IColor { export function fromCss(css: string): IColor { return { css, - rgba: parseInt(css.slice(1), 16) << 8 | 0xFF + rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0 }; } From 2da77dd5f8dfe729bcfdd8499d5070b559566052 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:23:34 -0800 Subject: [PATCH 287/338] Use opaque selection in webgl, fix selection default rgba --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 2 +- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 2 +- src/browser/Color.ts | 5 +++++ src/browser/ColorManager.ts | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 3ee9cdb5..3d4d43d1 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -218,7 +218,7 @@ export class GlyphRenderer { this._vertices.selectionAttributes = slice(this._vertices.attributes, 0); - const bg = (this._colors.selection.rgba >>> 8) | Attributes.CM_RGB; + const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB; if (columnSelectMode) { const startCol = model.selection.startCol; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index d5e245c0..8dcc5c08 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -155,7 +155,7 @@ export class RectangleRenderer { private _updateCachedColors(): void { this._bgFloat = this._colorToFloat32Array(this._colors.background); - this._selectionFloat = this._colorToFloat32Array(this._colors.selection); + this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque); } private _updateViewportRectangle(): void { diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 9771f656..5e86d5e9 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IColor } from './Types'; export function blend(bg: IColor, fg: IColor): IColor { diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index a1bfc105..170fead6 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -13,7 +13,7 @@ const DEFAULT_CURSOR = fromCss('#ffffff'); const DEFAULT_CURSOR_ACCENT = fromCss('#000000'); const DEFAULT_SELECTION = { css: 'rgba(255, 255, 255, 0.3)', - rgba: 0xFFFFFF77 + rgba: 0xFFFFFF4D }; // An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy @@ -108,6 +108,7 @@ export class ColorManager implements IColorManager { this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + this.colors.selectionOpaque = blend(this.colors.background, this.colors.selection); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); From 734bd8c96a43f7ed38e1bdf377b014533d4a7928 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:30:48 -0800 Subject: [PATCH 288/338] Factor out toRgba function and test --- src/browser/Color.test.ts | 43 ++++++++++++++++++++++++++++++++++++- src/browser/Color.ts | 7 +++++- src/browser/ColorManager.ts | 12 +++++------ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index 5542611f..44cd52f3 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss, toRgba } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -94,4 +94,45 @@ describe('Color', () => { assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff'); }); }); + + describe('toRgba', () => { + it('should convert an rgb array to an rgba number', () => { + assert.equal(toRgba(0x00, 0x00, 0x00), 0x000000FF); + assert.equal(toRgba(0x10, 0x10, 0x10), 0x101010FF); + assert.equal(toRgba(0x20, 0x20, 0x20), 0x202020FF); + assert.equal(toRgba(0x30, 0x30, 0x30), 0x303030FF); + assert.equal(toRgba(0x40, 0x40, 0x40), 0x404040FF); + assert.equal(toRgba(0x50, 0x50, 0x50), 0x505050FF); + assert.equal(toRgba(0x60, 0x60, 0x60), 0x606060FF); + assert.equal(toRgba(0x70, 0x70, 0x70), 0x707070FF); + assert.equal(toRgba(0x80, 0x80, 0x80), 0x808080FF); + assert.equal(toRgba(0x90, 0x90, 0x90), 0x909090FF); + assert.equal(toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF); + assert.equal(toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF); + assert.equal(toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF); + assert.equal(toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF); + assert.equal(toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF); + assert.equal(toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF); + assert.equal(toRgba(0xff, 0xff, 0xff), 0xffffffFF); + }); + it('should convert an rgba array to an rgba number', () => { + assert.equal(toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000); + assert.equal(toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010); + assert.equal(toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020); + assert.equal(toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030); + assert.equal(toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040); + assert.equal(toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050); + assert.equal(toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060); + assert.equal(toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070); + assert.equal(toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080); + assert.equal(toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090); + assert.equal(toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0); + assert.equal(toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0); + assert.equal(toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0); + assert.equal(toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0); + assert.equal(toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0); + assert.equal(toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0); + assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 5e86d5e9..51c1a446 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -23,7 +23,7 @@ export function blend(bg: IColor, fg: IColor): IColor { const g = bgG + Math.round((fgG - bgG) * a); const b = bgB + Math.round((fgB - bgB) * a); const css = toCss(r, g, b); - const rgba = ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0; + const rgba = toRgba(r, g, b); return { css, rgba }; } @@ -42,3 +42,8 @@ export function toPaddedHex(c: number): string { export function toCss(r: number, g: number, b: number): string { return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; } + +export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { + // >>> 0 forces an unsigned int + return (r << 24 | g << 16 | b << 8 | a) >>> 0; +} diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 170fead6..c6354f71 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,7 +5,7 @@ import { IColorManager, IColor, IColorSet } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { fromCss, toPaddedHex, toCss, blend } from 'browser/Color'; +import { fromCss, toCss, blend, toRgba } from 'browser/Color'; const DEFAULT_FOREGROUND = fromCss('#ffffff'); const DEFAULT_BACKGROUND = fromCss('#000000'); @@ -49,18 +49,16 @@ export const DEFAULT_ANSI_COLORS = (() => { const b = v[i % 6]; colors.push({ css: toCss(r, g, b), - // Use >>> 0 to force a conversion to an unsigned int - rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0 + rgba: toRgba(r, g, b) }); } // Generate greys (232-255) for (let i = 0; i < 24; i++) { const c = 8 + i * 10; - const ch = toPaddedHex(c); colors.push({ - css: `#${ch}${ch}${ch}`, - rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0 + css: toCss(c, c, c), + rgba: toRgba(c, c, c) }); } @@ -175,7 +173,7 @@ export class ColorManager implements IColorManager { return { css, - rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0 + rgba: toRgba(data[0], data[1], data[2], data[3]) }; } } From e89d79b879a4aa8550a7957e78aeafd584effa56 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:33:41 -0800 Subject: [PATCH 289/338] Remove unused code --- .../src/renderLayer/BaseRenderLayer.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 0e2d645e..29487ad0 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,7 +4,6 @@ */ import { IRenderLayer } from './Types'; -import { IGlyphIdentifier } from '../atlas/Types'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; @@ -25,19 +24,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _charAtlas: WebglCharAtlas | undefined; - /** - * An object that's reused when drawing glyphs in order to reduce GC. - */ - private _currentGlyphIdentifier: IGlyphIdentifier = { - chars: '', - code: 0, - bg: 0, - fg: 0, - bold: false, - dim: false, - italic: false - }; - constructor( private _container: HTMLElement, id: string, From 450a6262181e1bfc42fb1197f7760d90b1ca3d3e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 11:48:32 -0800 Subject: [PATCH 290/338] Correct import in Color.ts --- src/browser/Color.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 51c1a446..a8ce2d16 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColor } from './Types'; +import { IColor } from 'browser/Types'; export function blend(bg: IColor, fg: IColor): IColor { const a = (fg.rgba & 0xFF) / 255; From 6b7e29ea32d5ca4f8421d113835aaefd8b64adb9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 12:06:20 -0800 Subject: [PATCH 291/338] Whitespace to force a webgl release --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index c364c3c1..16b7ba07 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -440,3 +440,4 @@ function toPaddedHex(c: number): string { const s = c.toString(16); return s.length < 2 ? '0' + s : s; } + From 94afd1fa38d31c07838d0960075b3994f610dcd7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Nov 2019 21:38:43 -0800 Subject: [PATCH 292/338] Generate coverage report for pipelines --- azure-pipelines.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4daa0961..0f61dfe1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,9 @@ jobs: displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - - script: yarn test-unit-coverage --forbid-only + - script: | + yarn test-unit-coverage --forbid-only + ./node_modules/.bin/nyc report --reporter=cobertura displayName: 'Unit tests' - script: yarn lint displayName: 'Lint' From 25f9487239aad2a50368f79d19692c0007234670 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 10:24:54 -0800 Subject: [PATCH 293/338] Prefer non-relative imports Fixes some auto imports in VS Code, see microsoft/TypeScript#32144 --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c8a2dc2..20e4915c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "typescript.preferences.quoteStyle": "single" + "typescript.preferences.quoteStyle": "single", + "typescript.preferences.importModuleSpecifier": "non-relative" } From dfc1664d6e29d2ff7ed7a1bab6a8560e673b837c Mon Sep 17 00:00:00 2001 From: Andrew Sverdrup Date: Mon, 11 Nov 2019 13:23:59 -0800 Subject: [PATCH 294/338] Update Codevolve to Next Tech We changed our name several months back. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cf2848f..d2944003 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using xterm.js, socket.io, and ssh2. - [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE. - [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor. -- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js. +- [**Next Tech**](https://next.tech "Next Tech"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js. - [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R. - [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor. - [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud. From e46ff9ec852f1ee4f8c076950e445b9c4442c868 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 19:03:12 -0800 Subject: [PATCH 295/338] Add minimumContrastRatio to DOM renderer Part of #322 --- src/browser/Color.test.ts | 24 ++++- src/browser/Color.ts | 99 +++++++++++++++++ src/browser/renderer/dom/DomRenderer.ts | 2 +- .../dom/DomRendererRowFactory.test.ts | 2 +- .../renderer/dom/DomRendererRowFactory.ts | 100 +++++++++++++----- src/common/services/OptionsService.ts | 3 + src/common/services/Services.ts | 1 + typings/xterm.d.ts | 8 ++ 8 files changed, 209 insertions(+), 30 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index 44cd52f3..a8e4fa03 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss, toRgba } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss, toRgba, rgbRelativeLuminance, contrastRatio } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -135,4 +135,26 @@ describe('Color', () => { assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff); }); }); + describe('rgbRelativeLuminance', () => { + it('should calculate the relative luminance of the color', () => { + assert.equal(rgbRelativeLuminance(0x000000), 0); + + // TODO: Fill in tests + + assert.equal(rgbRelativeLuminance(0xFFFFFF), 1); + }); + }); + describe('contrastRatio', () => { + it('should calculate the relative luminance of the color', () => { + assert.equal(contrastRatio(0, 0), 1); + + // TODO: Fill in tests + + assert.equal(contrastRatio(0, 1), 21); + }); + it('should work regardless of the parameter order', () => { + assert.equal(contrastRatio(0, 1), 21); + assert.equal(contrastRatio(1, 0), 21); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index a8ce2d16..4063e4c1 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -47,3 +47,102 @@ export function toRgba(r: number, g: number, b: number, a: number = 0xFF): numbe // >>> 0 forces an unsigned int return (r << 24 | g << 16 | b << 8 | a) >>> 0; } + +/** + * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio + * between two colors. + * @param rgb The color to use. + * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef + */ +export function rgbRelativeLuminance(rgb: number): number { + return rgbRelativeLuminance2( + (rgb >> 16) & 0xFF, + (rgb >> 8 ) & 0xFF, + (rgb ) & 0xFF); +} + +export function rgbRelativeLuminance2(r: number, g: number, b: number): number { + const rs = r / 255; + const gs = g / 255; + const bs = b / 255; + const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4); + const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4); + const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4); + return rr * 0.2126 + rg * 0.7152 + rb * 0.0722; +} + +/** + * Gets the contrast ratio between two relative luminance values. + * @param l1 The first relative luminance. + * @param l2 The first relative luminance. + * + * // TODO: Is this link right? + * @see https://www.w3.org/TR/WCAG20/#contrastratio + */ +export function contrastRatio(l1: number, l2: number): number { + if (l1 < l2) { + return (l2 + 0.05) / (l1 + 0.05); + } + return (l1 + 0.05) / (l2 + 0.05); +} + +// TODO: Cache [bg][fg]: result, should probably be owned by ColorManager? + +export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { + const bgL = rgbRelativeLuminance(bg.rgba >> 8); + const fgL = rgbRelativeLuminance(fg.rgba >> 8); + const cr = contrastRatio(bgL, fgL); + if (cr < ratio) { + if (fgL < bgL) { + return reduceLuminance(bg, fg, ratio); + } + return increaseLuminance(bg, fg, ratio); + } + return undefined; +} + +export function reduceLuminance(bg: IColor, fg: IColor, ratio: number): IColor { + // This is a naive but fast approach to reducing luminance as converting to + // HSL and back is expensive + const bgR = (bg.rgba >> 24) & 0xFF; + const bgG = (bg.rgba >> 16) & 0xFF; + const bgB = (bg.rgba >> 8) & 0xFF; + let fgR = (fg.rgba >> 24) & 0xFF; + let fgG = (fg.rgba >> 16) & 0xFF; + let fgB = (fg.rgba >> 8) & 0xFF; + let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { + // Increase by 10% (ceil) until the ratio is hit + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + } + return { + css: toCss(fgR, fgG, fgB), + rgba: toRgba(fgR, fgG, fgB) + }; +} + +export function increaseLuminance(bg: IColor, fg: IColor, ratio: number): IColor { + // This is a naive but fast approach to increasing luminance as converting to + // HSL and back is expensive + const bgR = (bg.rgba >> 24) & 0xFF; + const bgG = (bg.rgba >> 16) & 0xFF; + const bgB = (bg.rgba >> 8) & 0xFF; + let fgR = (fg.rgba >> 24) & 0xFF; + let fgG = (fg.rgba >> 16) & 0xFF; + let fgB = (fg.rgba >> 8) & 0xFF; + let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { + // Increase by 10% until the ratio is hit + fgR = Math.min(0xFF, fgR + Math.floor((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.floor((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); + cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + } + return { + css: toCss(fgR, fgG, fgB), + rgba: toRgba(fgR, fgG, fgB) + }; +} diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index e8fc85f6..23c5a3a1 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -79,7 +79,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._updateDimensions(); this._injectCss(); - this._rowFactory = new DomRendererRowFactory(document, this._optionsService); + this._rowFactory = new DomRendererRowFactory(document, this._optionsService, this._colors); this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); this._screenElement.appendChild(this._rowContainer); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index b76d644d..bb9d97b6 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -19,7 +19,7 @@ describe('DomRendererRowFactory', () => { beforeEach(() => { dom = new jsdom.JSDOM(''); - rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true })); + rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), {} as any); lineData = createEmptyLineData(2); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 3e3b5df0..2c206f33 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -6,9 +6,11 @@ import { IBufferLine } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; +import { rgbRelativeLuminance, fromCss, contrastRatio, ensureContrastRatio } from 'browser/Color'; +import { IColorSet, IColor } from 'browser/Types'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; @@ -24,11 +26,16 @@ export class DomRendererRowFactory { private _workCell: CellData = new CellData(); constructor( - private _document: Document, - private _optionsService: IOptionsService + private readonly _document: Document, + private readonly _optionsService: IOptionsService, + private _colors: IColorSet ) { } + public setColors(colors: IColorSet): void { + this._colors = colors; + } + public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); @@ -97,36 +104,75 @@ export class DomRendererRowFactory { charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; - const swapColor = this._workCell.isInverse(); - - // fg - if (this._workCell.isFgRGB()) { - let style = charElement.getAttribute('style') || ''; - style += `${swapColor ? 'background-' : ''}color:rgb(${(AttributeData.toColorRGB(this._workCell.getFgColor())).join(',')});`; - charElement.setAttribute('style', style); - } else if (this._workCell.isFgPalette()) { - let fg = this._workCell.getFgColor(); - if (this._workCell.isBold() && fg < 8 && !swapColor && this._optionsService.options.drawBoldTextInBrightColors) { - fg += 8; - } - charElement.classList.add(`xterm-${swapColor ? 'b' : 'f'}g-${fg}`); - } else if (swapColor) { - charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`); + let fg = this._workCell.getFgColor(); + let fgColorMode = this._workCell.getFgColorMode(); + let bg = this._workCell.getBgColor(); + let bgColorMode = this._workCell.getBgColorMode(); + const isInverse = !!this._workCell.isInverse(); + if (isInverse) { + const temp = fg; + fg = bg; + bg = temp; + const temp2 = fgColorMode; + fgColorMode = bgColorMode; + bgColorMode = temp2; } - // bg - if (this._workCell.isBgRGB()) { - let style = charElement.getAttribute('style') || ''; - style += `${swapColor ? '' : 'background-'}color:rgb(${(AttributeData.toColorRGB(this._workCell.getBgColor())).join(',')});`; - charElement.setAttribute('style', style); - } else if (this._workCell.isBgPalette()) { - charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._workCell.getBgColor()}`); - } else if (swapColor) { - charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); + // Foreground + switch (fgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + if (this._workCell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) { + fg += 8; + } + if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) { + charElement.classList.add(`xterm-fg-${fg}`); + } + break; + case Attributes.CM_RGB: + charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}color:rgb(${(AttributeData.toColorRGB(fg)).join(',')});`); + break; + case Attributes.CM_DEFAULT: + default: + if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) { + if (isInverse) { + charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); + } + } + } + + // Background + switch (bgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + charElement.classList.add(`xterm-bg-${bg}`); + break; + case Attributes.CM_RGB: + charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}background-color:rgb(${(AttributeData.toColorRGB(bg)).join(',')});`); + break; + case Attributes.CM_DEFAULT: + default: + if (isInverse) { + charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`); + } } fragment.appendChild(charElement); } return fragment; } + + private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean { + if (this._optionsService.options.minimumContrastRatio === 1) { + return false; + } + + const adustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); + if (adustedColor) { + element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adustedColor.css}`); + return true; + } + + return false; + } } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d9ea60d5..9174398e 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -37,6 +37,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenReaderMode: false, macOptionIsMeta: false, macOptionClickForcesSelection: false, + minimumContrastRatio: 6, disableStdin: false, allowTransparency: false, tabStopWidth: 8, @@ -116,6 +117,8 @@ export class OptionsService implements IOptionsService { throw new Error(`${key} cannot be less than 1, value: ${value}`); } break; + case 'minimumContrastRatio': + value = Math.max(1, Math.min(21, Math.round(value * 10) / 10)); case 'scrollback': value = Math.min(value, 4294967295); if (value < 0) { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e4ff90d0..1a39cced 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -229,6 +229,7 @@ export interface ITerminalOptions { logLevel: LogLevel; macOptionIsMeta: boolean; macOptionClickForcesSelection: boolean; + minimumContrastRatio: number; rendererType: RendererType; rightClickSelectsWord: boolean; rows: number; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9728a72c..b49ed04b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -148,6 +148,14 @@ declare module 'xterm' { */ macOptionClickForcesSelection?: boolean; + /** + * The minimum contrast ratio for text in the terminal, setting this will + * change the foreground color dynamically depending on whether the contrast + * ratio is met. This can be to set 0.1 increments between 1 (default, do + * nothing) and 21 (foreground will be black or white). + */ + minimumContrastRatio?: number; + /** * The type of renderer to use, this allows using the fallback DOM renderer * when canvas is too slow for the environment. The following features do From 9cd66e72bbfac8f06c752fe8c88bda307d49fa9a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 19:14:35 -0800 Subject: [PATCH 296/338] Add tests for relative luminance --- src/browser/Color.test.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index a8e4fa03..975315c6 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -138,18 +138,28 @@ describe('Color', () => { describe('rgbRelativeLuminance', () => { it('should calculate the relative luminance of the color', () => { assert.equal(rgbRelativeLuminance(0x000000), 0); - - // TODO: Fill in tests - + assert.equal(rgbRelativeLuminance(0x101010).toFixed(4), '0.0052'); + assert.equal(rgbRelativeLuminance(0x202020).toFixed(4), '0.0144'); + assert.equal(rgbRelativeLuminance(0x303030).toFixed(4), '0.0296'); + assert.equal(rgbRelativeLuminance(0x404040).toFixed(4), '0.0513'); + assert.equal(rgbRelativeLuminance(0x505050).toFixed(4), '0.0802'); + assert.equal(rgbRelativeLuminance(0x606060).toFixed(4), '0.1170'); + assert.equal(rgbRelativeLuminance(0x707070).toFixed(4), '0.1620'); + assert.equal(rgbRelativeLuminance(0x808080).toFixed(4), '0.2159'); + assert.equal(rgbRelativeLuminance(0x909090).toFixed(4), '0.2789'); + assert.equal(rgbRelativeLuminance(0xA0A0A0).toFixed(4), '0.3515'); + assert.equal(rgbRelativeLuminance(0xB0B0B0).toFixed(4), '0.4342'); + assert.equal(rgbRelativeLuminance(0xC0C0C0).toFixed(4), '0.5271'); + assert.equal(rgbRelativeLuminance(0xD0D0D0).toFixed(4), '0.6308'); + assert.equal(rgbRelativeLuminance(0xE0E0E0).toFixed(4), '0.7454'); + assert.equal(rgbRelativeLuminance(0xF0F0F0).toFixed(4), '0.8714'); assert.equal(rgbRelativeLuminance(0xFFFFFF), 1); }); }); describe('contrastRatio', () => { it('should calculate the relative luminance of the color', () => { assert.equal(contrastRatio(0, 0), 1); - - // TODO: Fill in tests - + assert.equal(contrastRatio(0, 0.5), 11); assert.equal(contrastRatio(0, 1), 21); }); it('should work regardless of the parameter order', () => { From a6dcb21f1ca8c0cd70437d92a3f2da12fca3ca46 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 19:30:36 -0800 Subject: [PATCH 297/338] Fix tests --- src/browser/Color.ts | 4 +-- .../dom/DomRendererRowFactory.test.ts | 34 ++++++++++++++++--- .../renderer/dom/DomRendererRowFactory.ts | 2 +- src/common/services/OptionsService.ts | 2 +- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 4063e4c1..c74e8745 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -75,9 +75,7 @@ export function rgbRelativeLuminance2(r: number, g: number, b: number): number { * Gets the contrast ratio between two relative luminance values. * @param l1 The first relative luminance. * @param l2 The first relative luminance. - * - * // TODO: Is this link right? - * @see https://www.w3.org/TR/WCAG20/#contrastratio + * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef */ export function contrastRatio(l1: number, l2: number): number { if (l1 < l2) { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index bb9d97b6..180db0fc 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -11,6 +11,7 @@ import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { MockOptionsService } from 'common/TestUtils.test'; +import { fromCss } from 'browser/Color'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; @@ -19,7 +20,30 @@ describe('DomRendererRowFactory', () => { beforeEach(() => { dom = new jsdom.JSDOM(''); - rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), {} as any); + rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), { + background: fromCss('#010101'), + foreground: fromCss('#020202'), + ansi: [ + // dark: + fromCss('#2e3436'), + fromCss('#cc0000'), + fromCss('#4e9a06'), + fromCss('#c4a000'), + fromCss('#3465a4'), + fromCss('#75507b'), + fromCss('#06989a'), + fromCss('#d3d7cf'), + // bright: + fromCss('#555753'), + fromCss('#ef2929'), + fromCss('#8ae234'), + fromCss('#fce94f'), + fromCss('#729fcf'), + fromCss('#ad7fa8'), + fromCss('#34e2e2'), + fromCss('#eeeeec') + ] + } as any); lineData = createEmptyLineData(2); }); @@ -142,7 +166,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -153,7 +177,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -163,7 +187,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -199,7 +223,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 2c206f33..b5caccaf 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -9,7 +9,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; -import { rgbRelativeLuminance, fromCss, contrastRatio, ensureContrastRatio } from 'browser/Color'; +import { ensureContrastRatio } from 'browser/Color'; import { IColorSet, IColor } from 'browser/Types'; export const BOLD_CLASS = 'xterm-bold'; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 9174398e..b0b57694 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -37,7 +37,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenReaderMode: false, macOptionIsMeta: false, macOptionClickForcesSelection: false, - minimumContrastRatio: 6, + minimumContrastRatio: 1, disableStdin: false, allowTransparency: false, tabStopWidth: 8, From b1bcb899e0aa478ee49023ede4c346bbc6f20c66 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 21:29:02 -0800 Subject: [PATCH 298/338] Fix inverse in webgl, add tests --- .../src/RectangleRenderer.ts | 26 +- .../src/WebglRenderer.api.ts | 233 +++++++++++++++++- addons/xterm-addon-webgl/src/WebglRenderer.ts | 32 +-- .../src/atlas/WebglCharAtlas.ts | 74 ++++-- 4 files changed, 309 insertions(+), 56 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 8dcc5c08..356e1747 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -248,23 +248,26 @@ export class RectangleRenderer { let currentStartX = -1; let currentBg = 0; let currentFg = 0; + let currentInverse = false; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; - if (bg !== currentBg) { + const inverse = !!(fg & FgFlags.INVERSE); + if (bg !== currentBg || ((inverse || currentInverse) && fg !== currentFg)) { // A rectangle needs to be drawn if going from non-default to another color - if (currentBg !== 0) { + if (currentBg !== 0 || (currentInverse && currentFg !== 0)) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y); } currentStartX = x; currentBg = bg; currentFg = fg; + currentInverse = inverse; } } // Finish rectangle if it's still going - if (currentBg !== 0) { + if (currentBg !== 0 || (currentInverse && currentFg !== 0)) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y); } @@ -274,12 +277,21 @@ export class RectangleRenderer { private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { let rgba: number | undefined; - const colorMode = bg & Attributes.CM_MASK; if (fg & FgFlags.INVERSE) { - // Inverted color - rgba = this._colors.foreground.rgba; + switch (fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + rgba = (fg & Attributes.RGB_MASK) << 8; + break; + case Attributes.CM_DEFAULT: + default: + rgba = this._colors.foreground.rgba; + } } else { - switch (colorMode) { + switch (bg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 304973fe..556061e7 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -16,7 +16,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('WebGL Renderer Integration Tests', function(): void { +describe.only('WebGL Renderer Integration Tests', function(): void { it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); @@ -76,6 +76,52 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + it('foreground 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(`\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + + it('background 0-15 inverse', async () => { + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(`\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + it('foreground 0-15 bright', async () => { const theme: ITheme = { brightBlack: '#010203', @@ -162,6 +208,46 @@ describe('WebGL Renderer Integration Tests', function(): void { } }); + it('foreground 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + + it('background 16-255 inverse', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.substr(1, 2), 16); + const g = parseInt(cssColor.substr(3, 2), 16); + const b = parseInt(cssColor.substr(5, 2), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + it('foreground true color red', async () => { let data = ''; for (let y = 0; y < 16; y++) { @@ -305,6 +391,151 @@ describe('WebGL Renderer Integration Tests', function(): void { } } }); + + it('foreground true color red inverse', async function(): Promise { + this.timeout(60000); + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\x1b[7;38;2;${i};0;0m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + it('background true color red inverse', async function(): Promise { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;48;2;${i};0;0m█\\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]); + } + } + }); + + it('foreground true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;38;2;0;${i};0m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + it('background true color green inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;48;2;0;${i};0m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]); + } + } + }); + + it('foreground true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;38;2;0;0;${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + it('background true color blue inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;48;2;0;0;${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]); + } + } + }); + + it('foreground true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;38;2;${i};${i};${i}m \x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); + + it('background true color grey inverse', async () => { + let data = ''; + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + data += `\\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(data); + for (let y = 0; y < 16; y++) { + for (let x = 0; x < 16; x++) { + const i = y * 16 + x; + await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]); + } + } + }); }); }); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 5eae61db..3a71dcd8 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -252,35 +252,13 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.lineLengths[y] = x + 1; } - // Resolve bg and fg - let bg = this._workCell.bg; - let fg = this._workCell.fg; - // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === fg) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workCell.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workCell.fg) { continue; } - // If inverse flag is on, the foreground should become the background. - if (this._workCell.isInverse()) { - const temp = bg; - bg = fg; - fg = temp; - if (fg === DEFAULT_COLOR) { - fg = INVERTED_DEFAULT_COLOR; - } - if (bg === DEFAULT_COLOR) { - bg = INVERTED_DEFAULT_COLOR; - } - } - - // Apply drawBoldTextInBrightColors - if (terminal.options.drawBoldTextInBrightColors && this._workCell.isBold() && fg & FgFlags.BOLD && this._workCell.getFgColor() < 8) { - fg += 8; - } - // Flag combined chars with a bit mask so they're easily identifiable if (chars.length > 1) { code = code | COMBINED_CHAR_BIT_MASK; @@ -288,10 +266,10 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + RENDER_MODEL_BG_OFFSET] = bg; - this._model.cells[i + RENDER_MODEL_FG_OFFSET] = fg; + this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workCell.bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workCell.fg; - this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); + this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars); } } this._rectangleRenderer.updateBackgrounds(this._model); diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 16b7ba07..b4d3f822 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -176,51 +176,47 @@ export class WebglCharAtlas implements IDisposable { return this._config.colors.ansi[idx]; } - private _getBackgroundColor(bg: number, fg: number): IColor { + private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean): IColor { if (this._config.allowTransparency) { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. return TRANSPARENT_COLOR; - } else if (fg & FgFlags.INVERSE) { - return this._config.colors.foreground; } - const colorMode = bg & Attributes.CM_MASK; - switch (colorMode) { + switch (bgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - return this._getColorFromAnsiIndex(bg & Attributes.PCOLOR_MASK); + return this._getColorFromAnsiIndex(bgColor); case Attributes.CM_RGB: - const rgb = bg & Attributes.RGB_MASK; - const arr = AttributeData.toColorRGB(rgb); + const arr = AttributeData.toColorRGB(bgColor); // TODO: This object creation is slow return { - rgba: rgb << 8, + rgba: bgColor << 8, css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}` }; case Attributes.CM_DEFAULT: default: + if (inverse) { + return this._config.colors.foreground; + } return this._config.colors.background; } } - private _getForegroundCss(fg: number): string { - if (fg & FgFlags.INVERSE) { - return this._config.colors.background.css; - } - - const colorMode = fg & Attributes.CM_MASK; - switch (colorMode) { + private _getForegroundCss(fgColorMode: number, fgColor: number, inverse: boolean): string { + switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - return this._getColorFromAnsiIndex(fg & Attributes.PCOLOR_MASK).css; + return this._getColorFromAnsiIndex(fgColor).css; case Attributes.CM_RGB: - const rgb = fg & Attributes.RGB_MASK; - const arr = AttributeData.toColorRGB(rgb); + const arr = AttributeData.toColorRGB(fgColor); return `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`; case Attributes.CM_DEFAULT: default: + if (inverse) { + return this._config.colors.background.css; + } return this._config.colors.foreground.css; } } @@ -233,13 +229,33 @@ export class WebglCharAtlas implements IDisposable { this.hasCanvasChanged = true; const bold = !!(fg & FgFlags.BOLD); + const inverse = !!(fg & FgFlags.INVERSE); const dim = !!(bg & BgFlags.DIM); const italic = !!(bg & BgFlags.ITALIC); this._tmpCtx.save(); + let fgColor = getFgColor(fg); + let fgColorMode = fg & Attributes.CM_MASK; + let bgColor = getBgColor(bg); + let bgColorMode = bg & Attributes.CM_MASK; + if (inverse) { + const temp = fgColor; + fgColor = bgColor; + bgColor = temp; + const temp2 = fgColorMode; + fgColorMode = bgColorMode; + bgColorMode = temp2; + } + + // TODO: Pass drawBoldTextInBrightColors through + // Apply drawBoldTextInBrightColors + // if (terminal.options.drawBoldTextInBrightColors && this._workCell.isBold() && fg & FgFlags.BOLD && this._workCell.getFgColor() < 8) { + // fg += 8; + // } + // draw the background - const backgroundColor = this._getBackgroundColor(bg, fg); + const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse); // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of // transparency in backgroundColor this._tmpCtx.globalCompositeOperation = 'copy'; @@ -254,7 +270,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundCss(fg); + this._tmpCtx.fillStyle = this._getForegroundCss(fgColorMode, fgColor, inverse); // Apply alpha to dim the character if (dim) { @@ -441,3 +457,19 @@ function toPaddedHex(c: number): string { return s.length < 2 ? '0' + s : s; } +function getFgColor(fg: number): number { + switch (fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: return fg & Attributes.PCOLOR_MASK; + case Attributes.CM_RGB: return fg & Attributes.RGB_MASK; + default: return -1; // CM_DEFAULT defaults to -1 + } +} +function getBgColor(bg: number): number { + switch (bg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: return bg & Attributes.PCOLOR_MASK; + case Attributes.CM_RGB: return bg & Attributes.RGB_MASK; + default: return -1; // CM_DEFAULT defaults to -1 + } +} From db775afd70086be49fe2df9aa429523900ac0d42 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 21:35:49 -0800 Subject: [PATCH 299/338] Support drawBoldTextInBrightColors in webgl again This broke as part of this PR --- .../src/WebglRenderer.api.ts | 27 ++++++++++++++++++- .../src/atlas/CharAtlasUtils.ts | 1 + addons/xterm-addon-webgl/src/atlas/Types.d.ts | 1 + .../src/atlas/WebglCharAtlas.ts | 18 ++++++------- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 556061e7..18449e8a 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -53,6 +53,32 @@ describe.only('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); }); + it('foreground 0-7 drawBoldTextInBrightColors', async () => { + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await page.evaluate(` + window.term.setOption('theme', ${JSON.stringify(theme)}); + window.term.setOption('drawBoldTextInBrightColors', true); + `); + await writeSync(`\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`); + await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); + await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); + await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]); + await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]); + await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]); + await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]); + await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]); + await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]); + }); + it('background 0-15', async () => { const theme: ITheme = { black: '#010203', @@ -393,7 +419,6 @@ describe.only('WebGL Renderer Integration Tests', function(): void { }); it('foreground true color red inverse', async function(): Promise { - this.timeout(60000); let data = ''; for (let y = 0; y < 16; y++) { for (let x = 0; x < 16; x++) { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 03867b41..ec7d9a7e 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -35,6 +35,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number fontWeight: terminal.getOption('fontWeight') as FontWeight, fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight, allowTransparency: terminal.getOption('allowTransparency'), + drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'), colors: clonedColors }; } diff --git a/addons/xterm-addon-webgl/src/atlas/Types.d.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts index 1de843e0..5b96adb0 100644 --- a/addons/xterm-addon-webgl/src/atlas/Types.d.ts +++ b/addons/xterm-addon-webgl/src/atlas/Types.d.ts @@ -25,5 +25,6 @@ export interface ICharAtlasConfig { scaledCharWidth: number; scaledCharHeight: number; allowTransparency: boolean; + drawBoldTextInBrightColors: boolean; colors: IColorSet; } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b4d3f822..6325ca92 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -68,7 +68,10 @@ export class WebglCharAtlas implements IDisposable { private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; - constructor(document: Document, private _config: ICharAtlasConfig) { + constructor( + document: Document, + private _config: ICharAtlasConfig + ) { this.cacheCanvas = document.createElement('canvas'); this.cacheCanvas.width = TEXTURE_WIDTH; this.cacheCanvas.height = TEXTURE_HEIGHT; @@ -204,10 +207,13 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundCss(fgColorMode: number, fgColor: number, inverse: boolean): string { + private _getForegroundCss(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: + if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) { + fgColor += 8; + } return this._getColorFromAnsiIndex(fgColor).css; case Attributes.CM_RGB: const arr = AttributeData.toColorRGB(fgColor); @@ -248,12 +254,6 @@ export class WebglCharAtlas implements IDisposable { bgColorMode = temp2; } - // TODO: Pass drawBoldTextInBrightColors through - // Apply drawBoldTextInBrightColors - // if (terminal.options.drawBoldTextInBrightColors && this._workCell.isBold() && fg & FgFlags.BOLD && this._workCell.getFgColor() < 8) { - // fg += 8; - // } - // draw the background const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse); // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of @@ -270,7 +270,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundCss(fgColorMode, fgColor, inverse); + this._tmpCtx.fillStyle = this._getForegroundCss(fgColorMode, fgColor, inverse, bold); // Apply alpha to dim the character if (dim) { From 2283ea45a6de0dddacd7a112cdca2b5bd5d694e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 22:18:39 -0800 Subject: [PATCH 300/338] Refresh rows after ratio changes --- src/Terminal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 3c589ffa..b08270fd 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -329,6 +329,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp case 'lineHeight': case 'fontWeight': case 'fontWeightBold': + case 'minimumContrastRatio': // When the font changes the size of the cells may change which requires a renderer clear if (this._renderService) { this._renderService.clear(); From 531409c2c7c3dea4b05394e0ec1952bfe443fa2a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 22:47:26 -0800 Subject: [PATCH 301/338] Support minimum contrast ratio in webgl --- .../src/WebglRenderer.api.ts | 72 ++++++- .../src/atlas/CharAtlasUtils.ts | 3 + addons/xterm-addon-webgl/src/atlas/Types.d.ts | 1 + .../src/atlas/WebglCharAtlas.ts | 179 +++++++++++++++++- src/browser/Color.ts | 67 ++++--- 5 files changed, 291 insertions(+), 31 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 18449e8a..25719faa 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -16,7 +16,7 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe.only('WebGL Renderer Integration Tests', function(): void { +describe('WebGL Renderer Integration Tests', function(): void { it('dispose removes renderer canvases', async () => { await setupBrowser(); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3); @@ -562,6 +562,76 @@ describe.only('WebGL Renderer Integration Tests', function(): void { } }); }); + + describe('minimumContrastRatio', async () => { + before(async () => setupBrowser()); + after(async () => browser.close()); + beforeEach(async () => page.evaluate(`window.term.reset()`)); + + it('should adjust 0-15 colors on black background', async () => { + const theme: ITheme = { + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync( + `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + + `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█` + ); + // Validate before minimumContrastRatio is applied + await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are note + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`); + await pollFor(page, () => getCellColor(1, 1), [179, 182, 182, 255]); + await pollFor(page, () => getCellColor(2, 1), [234, 163, 163, 255]); + await pollFor(page, () => getCellColor(3, 1), [196, 221, 174, 255]); + await pollFor(page, () => getCellColor(4, 1), [231, 219, 163, 255]); + await pollFor(page, () => getCellColor(5, 1), [133, 162, 200, 255]); + await pollFor(page, () => getCellColor(6, 1), [180, 159, 182, 255]); + await pollFor(page, () => getCellColor(7, 1), [106, 192, 194, 255]); + await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]); + await pollFor(page, () => getCellColor(1, 2), [180, 181, 179, 255]); + await pollFor(page, () => getCellColor(2, 2), [246, 160, 160, 255]); + await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]); + await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]); + await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]); + await pollFor(page, () => getCellColor(6, 2), [188, 150, 183, 255]); + // Unchanged + await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index ec7d9a7e..e8e19095 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -36,6 +36,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight, allowTransparency: terminal.getOption('allowTransparency'), drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'), + minimumContrastRatio: terminal.getOption('minimumContrastRatio'), colors: clonedColors }; } @@ -54,6 +55,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.allowTransparency === b.allowTransparency && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && + a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors && + a.minimumContrastRatio === b.minimumContrastRatio && a.colors.foreground === b.colors.foreground && a.colors.background === b.colors.background; } diff --git a/addons/xterm-addon-webgl/src/atlas/Types.d.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts index 5b96adb0..cd73393c 100644 --- a/addons/xterm-addon-webgl/src/atlas/Types.d.ts +++ b/addons/xterm-addon-webgl/src/atlas/Types.d.ts @@ -26,5 +26,6 @@ export interface ICharAtlasConfig { scaledCharHeight: number; allowTransparency: boolean; drawBoldTextInBrightColors: boolean; + minimumContrastRatio: number; colors: IColorSet; } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 6325ca92..f6691067 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -207,7 +207,12 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundCss(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { + private _getForegroundCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { + const minimumContrastCss = this._getMinimumContrastCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + if (minimumContrastCss) { + return minimumContrastCss; + } + switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: @@ -217,7 +222,7 @@ export class WebglCharAtlas implements IDisposable { return this._getColorFromAnsiIndex(fgColor).css; case Attributes.CM_RGB: const arr = AttributeData.toColorRGB(fgColor); - return `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`; + return toCss(arr[0], arr[1], arr[2]); case Attributes.CM_DEFAULT: default: if (inverse) { @@ -227,6 +232,57 @@ export class WebglCharAtlas implements IDisposable { } } + private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): number { + switch (bgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._getColorFromAnsiIndex(bgColor).rgba; + case Attributes.CM_RGB: + return bgColor << 8; + case Attributes.CM_DEFAULT: + default: + if (inverse) { + this._config.colors.foreground.rgba; + } + return this._config.colors.background.rgba; + } + } + + private _resolveForegroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): number { + switch (fgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._getColorFromAnsiIndex(fgColor).rgba; + case Attributes.CM_RGB: + return fgColor << 8; + case Attributes.CM_DEFAULT: + default: + if (inverse) { + this._config.colors.background.rgba; + } + return this._config.colors.foreground.rgba; + } + } + + private _getMinimumContrastCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): string | undefined { + const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + console.log('ratio', this._config.minimumContrastRatio); + if (this._config.minimumContrastRatio === 1) { + return undefined; + } + const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); + console.log('get min', result, bgRgba, fgRgba); + if (!result) { + return undefined; + } + return toCss( + (result >> 24) & 0xFF, + (result >> 16) & 0xFF, + (result >> 8) & 0xFF + ); + } + private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph; private _drawToCache(chars: string, bg: number, fg: number): IRasterizedGlyph; private _drawToCache(codeOrChars: number | string, bg: number, fg: number): IRasterizedGlyph { @@ -270,7 +326,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundCss(fgColorMode, fgColor, inverse, bold); + this._tmpCtx.fillStyle = this._getForegroundCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); // Apply alpha to dim the character if (dim) { @@ -473,3 +529,120 @@ function getBgColor(bg: number): number { default: return -1; // CM_DEFAULT defaults to -1 } } + +export function toCss(r: number, g: number, b: number): string { + return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; +} + +export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { + // >>> 0 forces an unsigned int + return (r << 24 | g << 16 | b << 8 | a) >>> 0; +} + +/** + * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio + * between two colors. + * @param rgb The color to use. + * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef + */ +export function rgbRelativeLuminance(rgb: number): number { + return rgbRelativeLuminance2( + (rgb >> 16) & 0xFF, + (rgb >> 8 ) & 0xFF, + (rgb ) & 0xFF); +} + +export function rgbRelativeLuminance2(r: number, g: number, b: number): number { + const rs = r / 255; + const gs = g / 255; + const bs = b / 255; + const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4); + const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4); + const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4); + return rr * 0.2126 + rg * 0.7152 + rb * 0.0722; +} + +/** + * Gets the contrast ratio between two relative luminance values. + * @param l1 The first relative luminance. + * @param l2 The first relative luminance. + * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef + */ +export function contrastRatio(l1: number, l2: number): number { + if (l1 < l2) { + return (l2 + 0.05) / (l1 + 0.05); + } + return (l1 + 0.05) / (l2 + 0.05); +} + +function rgbaToColor(r: number, g: number, b: number): IColor { + return { + css: toCss(r, g, b), + rgba: toRgba(r, g, b) + }; +} + +export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined { + const bgL = rgbRelativeLuminance(bgRgba >> 8); + const fgL = rgbRelativeLuminance(fgRgba >> 8); + const cr = contrastRatio(bgL, fgL); + if (cr < ratio) { + if (fgL < bgL) { + return reduceLuminance(bgRgba, fgRgba, ratio); + } + return increaseLuminance(bgRgba, fgRgba, ratio); + } + return undefined; +} + +export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { + const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio); + if (!result) { + return undefined; + } + return rgbaToColor( + (result >> 24 & 0xFF), + (result >> 16 & 0xFF), + (result >> 8 & 0xFF) + ); +} + +export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number { + // This is a naive but fast approach to reducing luminance as converting to + // HSL and back is expensive + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; + let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { + // Increase by 10% (ceil) until the ratio is hit + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + } + return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; +} + +export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { + // This is a naive but fast approach to increasing luminance as converting to + // HSL and back is expensive + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; + let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { + // Increase by 10% until the ratio is hit + fgR = Math.min(0xFF, fgR + Math.floor((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.floor((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); + cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); + } + return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; +} diff --git a/src/browser/Color.ts b/src/browser/Color.ts index c74e8745..c63ccee3 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -86,28 +86,47 @@ export function contrastRatio(l1: number, l2: number): number { // TODO: Cache [bg][fg]: result, should probably be owned by ColorManager? -export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { - const bgL = rgbRelativeLuminance(bg.rgba >> 8); - const fgL = rgbRelativeLuminance(fg.rgba >> 8); +function rgbaToColor(r: number, g: number, b: number): IColor { + return { + css: toCss(r, g, b), + rgba: toRgba(r, g, b) + }; +} + +export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined { + const bgL = rgbRelativeLuminance(bgRgba >> 8); + const fgL = rgbRelativeLuminance(fgRgba >> 8); const cr = contrastRatio(bgL, fgL); if (cr < ratio) { if (fgL < bgL) { - return reduceLuminance(bg, fg, ratio); + return reduceLuminance(bgRgba, fgRgba, ratio); } - return increaseLuminance(bg, fg, ratio); + return increaseLuminance(bgRgba, fgRgba, ratio); } return undefined; } -export function reduceLuminance(bg: IColor, fg: IColor, ratio: number): IColor { +export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { + const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio); + if (!result) { + return undefined; + } + return rgbaToColor( + (result >> 24 & 0xFF), + (result >> 16 & 0xFF), + (result >> 8 & 0xFF) + ); +} + +export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number { // This is a naive but fast approach to reducing luminance as converting to // HSL and back is expensive - const bgR = (bg.rgba >> 24) & 0xFF; - const bgG = (bg.rgba >> 16) & 0xFF; - const bgB = (bg.rgba >> 8) & 0xFF; - let fgR = (fg.rgba >> 24) & 0xFF; - let fgG = (fg.rgba >> 16) & 0xFF; - let fgB = (fg.rgba >> 8) & 0xFF; + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { // Increase by 10% (ceil) until the ratio is hit @@ -116,21 +135,18 @@ export function reduceLuminance(bg: IColor, fg: IColor, ratio: number): IColor { fgB -= Math.max(0, Math.ceil(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } - return { - css: toCss(fgR, fgG, fgB), - rgba: toRgba(fgR, fgG, fgB) - }; + return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; } -export function increaseLuminance(bg: IColor, fg: IColor, ratio: number): IColor { +export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { // This is a naive but fast approach to increasing luminance as converting to // HSL and back is expensive - const bgR = (bg.rgba >> 24) & 0xFF; - const bgG = (bg.rgba >> 16) & 0xFF; - const bgB = (bg.rgba >> 8) & 0xFF; - let fgR = (fg.rgba >> 24) & 0xFF; - let fgG = (fg.rgba >> 16) & 0xFF; - let fgB = (fg.rgba >> 8) & 0xFF; + const bgR = (bgRgba >> 24) & 0xFF; + const bgG = (bgRgba >> 16) & 0xFF; + const bgB = (bgRgba >> 8) & 0xFF; + let fgR = (fgRgba >> 24) & 0xFF; + let fgG = (fgRgba >> 16) & 0xFF; + let fgB = (fgRgba >> 8) & 0xFF; let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { // Increase by 10% until the ratio is hit @@ -139,8 +155,5 @@ export function increaseLuminance(bg: IColor, fg: IColor, ratio: number): IColor fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } - return { - css: toCss(fgR, fgG, fgB), - rgba: toRgba(fgR, fgG, fgB) - }; + return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; } From 9b139a744b4d4862dda4e66b924210ed7ec4d0c8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 22:55:08 -0800 Subject: [PATCH 302/338] Add test for min contrast on white bg --- .../src/WebglRenderer.api.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 25719faa..998d5536 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -631,6 +631,70 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); }); + + it.only('should adjust 0-15 colors on white background', async () => { + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync( + `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + + `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█` + ); + // Validate before minimumContrastRatio is applied + await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]); + await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]); + await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]); + await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]); + await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]); + await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]); + await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]); + await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]); + await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]); + await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]); + await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]); + await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]); + await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]); + await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); + await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); + await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); + // Setting and check for minimum contrast values, note that these are note + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`); + await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]); + await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]); + await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]); + await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]); + await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]); + await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]); + await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]); + await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]); + await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]); + await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]); + await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]); + await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]); + await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]); + await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]); + await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]); + await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]); + }); }); }); From 2fc1791dde2bc26e23ada463c45319410016b831 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 23:11:36 -0800 Subject: [PATCH 303/338] Fix floor/ceil in luminance methods --- .../src/atlas/WebglCharAtlas.ts | 27 ++++++++++--------- src/browser/Color.ts | 14 +++++----- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index f6691067..60068de8 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -208,7 +208,7 @@ export class WebglCharAtlas implements IDisposable { } private _getForegroundCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { - const minimumContrastCss = this._getMinimumContrastCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + const minimumContrastCss = this._getMinimumContrastCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); if (minimumContrastCss) { return minimumContrastCss; } @@ -248,10 +248,13 @@ export class WebglCharAtlas implements IDisposable { } } - private _resolveForegroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): number { + private _resolveForegroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: + if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) { + fgColor += 8; + } return this._getColorFromAnsiIndex(fgColor).rgba; case Attributes.CM_RGB: return fgColor << 8; @@ -264,15 +267,13 @@ export class WebglCharAtlas implements IDisposable { } } - private _getMinimumContrastCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): string | undefined { + private _getMinimumContrastCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined { const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); - const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); - console.log('ratio', this._config.minimumContrastRatio); + const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); if (this._config.minimumContrastRatio === 1) { return undefined; } const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); - console.log('get min', result, bgRgba, fgRgba); if (!result) { return undefined; } @@ -618,10 +619,10 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): let fgB = (fgRgba >> 8) & 0xFF; let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { - // Increase by 10% (ceil) until the ratio is hit - fgR -= Math.max(0, Math.ceil(fgR * 0.1)); - fgG -= Math.max(0, Math.ceil(fgG * 0.1)); - fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + // Reduce by 10% until the ratio is hit + fgR -= Math.max(0, Math.floor(fgR * 0.1)); + fgG -= Math.max(0, Math.floor(fgG * 0.1)); + fgB -= Math.max(0, Math.floor(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; @@ -639,9 +640,9 @@ export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number) let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { // Increase by 10% until the ratio is hit - fgR = Math.min(0xFF, fgR + Math.floor((255 - fgR) * 0.1)); - fgG = Math.min(0xFF, fgG + Math.floor((255 - fgG) * 0.1)); - fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); + fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; diff --git a/src/browser/Color.ts b/src/browser/Color.ts index c63ccee3..258ea45a 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -129,10 +129,10 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): let fgB = (fgRgba >> 8) & 0xFF; let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { - // Increase by 10% (ceil) until the ratio is hit - fgR -= Math.max(0, Math.ceil(fgR * 0.1)); - fgG -= Math.max(0, Math.ceil(fgG * 0.1)); - fgB -= Math.max(0, Math.ceil(fgB * 0.1)); + // Reduce by 10% until the ratio is hit + fgR -= Math.max(0, Math.floor(fgR * 0.1)); + fgG -= Math.max(0, Math.floor(fgG * 0.1)); + fgB -= Math.max(0, Math.floor(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; @@ -150,9 +150,9 @@ export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number) let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { // Increase by 10% until the ratio is hit - fgR = Math.min(0xFF, fgR + Math.floor((255 - fgR) * 0.1)); - fgG = Math.min(0xFF, fgG + Math.floor((255 - fgG) * 0.1)); - fgB = Math.min(0xFF, fgB + Math.floor((255 - fgB) * 0.1)); + fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); + fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); + fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; From 6626f9628d22ba9e41efc033ac34eff7e7c77dee Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 23:13:21 -0800 Subject: [PATCH 304/338] Use ceil in both --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 6 +++--- src/browser/Color.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 60068de8..1642ce08 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -620,9 +620,9 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { // Reduce by 10% until the ratio is hit - fgR -= Math.max(0, Math.floor(fgR * 0.1)); - fgG -= Math.max(0, Math.floor(fgG * 0.1)); - fgB -= Math.max(0, Math.floor(fgB * 0.1)); + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 258ea45a..3a2b5032 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -130,9 +130,9 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { // Reduce by 10% until the ratio is hit - fgR -= Math.max(0, Math.floor(fgR * 0.1)); - fgG -= Math.max(0, Math.floor(fgG * 0.1)); - fgB -= Math.max(0, Math.floor(fgB * 0.1)); + fgR -= Math.max(0, Math.ceil(fgR * 0.1)); + fgG -= Math.max(0, Math.ceil(fgG * 0.1)); + fgB -= Math.max(0, Math.ceil(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; From 5ee9bcf2b58e2d21291471c31c83cd76adfa9187 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Nov 2019 23:23:33 -0800 Subject: [PATCH 305/338] Remove .only --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 998d5536..88aa3b45 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -632,7 +632,7 @@ describe('WebGL Renderer Integration Tests', function(): void { await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); }); - it.only('should adjust 0-15 colors on white background', async () => { + it('should adjust 0-15 colors on white background', async () => { const theme: ITheme = { background: '#ffffff', black: '#2e3436', From ca5cee7e8625ec7ed0dfd0bdf642513d32372f6d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 08:53:06 -0800 Subject: [PATCH 306/338] Fix contrast tests --- .../src/WebglRenderer.api.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 88aa3b45..72f65c97 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -8,6 +8,7 @@ import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; import { assert } from 'chai'; import deepEqual = require('deep-equal'); +import { contrastRatio, reduceLuminance, toRgba } from 'atlas/WebglCharAtlas'; const APP = 'http://127.0.0.1:3000/test'; @@ -613,20 +614,20 @@ describe('WebGL Renderer Integration Tests', function(): void { // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`); - await pollFor(page, () => getCellColor(1, 1), [179, 182, 182, 255]); - await pollFor(page, () => getCellColor(2, 1), [234, 163, 163, 255]); - await pollFor(page, () => getCellColor(3, 1), [196, 221, 174, 255]); - await pollFor(page, () => getCellColor(4, 1), [231, 219, 163, 255]); - await pollFor(page, () => getCellColor(5, 1), [133, 162, 200, 255]); - await pollFor(page, () => getCellColor(6, 1), [180, 159, 182, 255]); - await pollFor(page, () => getCellColor(7, 1), [106, 192, 194, 255]); + await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]); + await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]); + await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]); + await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]); + await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]); + await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]); + await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]); await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]); - await pollFor(page, () => getCellColor(1, 2), [180, 181, 179, 255]); - await pollFor(page, () => getCellColor(2, 2), [246, 160, 160, 255]); + await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]); + await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]); await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]); await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]); await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]); - await pollFor(page, () => getCellColor(6, 2), [188, 150, 183, 255]); + await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]); // Unchanged await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); @@ -748,6 +749,7 @@ export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => await preFn(); } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); + console.log(result); if (!deepEqual(result, val)) { return new Promise(r => { setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); From c2474626f0678402e9e572ada2a5921af19a06ce Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:07:08 -0800 Subject: [PATCH 307/338] Clear setting as part of run --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 72f65c97..26e966a0 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -8,7 +8,6 @@ import { ITerminalOptions } from '../../../src/Types'; import { ITheme } from 'xterm'; import { assert } from 'chai'; import deepEqual = require('deep-equal'); -import { contrastRatio, reduceLuminance, toRgba } from 'atlas/WebglCharAtlas'; const APP = 'http://127.0.0.1:3000/test'; @@ -588,7 +587,10 @@ describe('WebGL Renderer Integration Tests', function(): void { brightCyan: '#34e2e2', brightWhite: '#eeeeec' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(` + window.term.setOption('theme', ${JSON.stringify(theme)}); + window.term.setOption('minimumContrastRatio', 1); + `); await writeSync( `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█` @@ -653,7 +655,10 @@ describe('WebGL Renderer Integration Tests', function(): void { brightCyan: '#34e2e2', brightWhite: '#eeeeec' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(` + window.term.setOption('theme', ${JSON.stringify(theme)}); + window.term.setOption('minimumContrastRatio', 1); + `); await writeSync( `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█` From faaa07be68849e0a85dc17181900f904d15a1ba9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:33:50 -0800 Subject: [PATCH 308/338] Add contrast caching for DOM renderer --- .../src/atlas/CharAtlasUtils.ts | 3 +- src/Terminal.ts | 1 + src/browser/ColorContrastCache.ts | 38 +++++++++++++++++++ src/browser/ColorManager.ts | 16 +++++++- src/browser/Types.d.ts | 10 +++++ .../renderer/dom/DomRendererRowFactory.ts | 14 +++++-- 6 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 src/browser/ColorContrastCache.ts diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index e8e19095..ad25bf87 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -24,7 +24,8 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number selectionOpaque: NULL_COLOR, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. - ansi: colors.ansi.slice() + ansi: colors.ansi.slice(), + contrastCache: {} as any }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/src/Terminal.ts b/src/Terminal.ts index b08270fd..927ff47b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -546,6 +546,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._theme = this.options.theme || this._theme; this.options.theme = undefined; this._colorManager = new ColorManager(document, this.options.allowTransparency); + this.optionsService.onOptionChange(e => this._colorManager.onOptionsChange(e)); this._colorManager.setTheme(this._theme); const renderer = this._createRenderer(); diff --git a/src/browser/ColorContrastCache.ts b/src/browser/ColorContrastCache.ts new file mode 100644 index 00000000..3198ad83 --- /dev/null +++ b/src/browser/ColorContrastCache.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IColor, IColorContrastCache } from 'browser/Types'; + +export class ColorContrastCache implements IColorContrastCache { + private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {}; + private _rgba: { [bg: number]: { [fg: number]: number | null | undefined } | undefined } = {}; + + public clear(): void { + this._color = {}; + this._rgba = {}; + } + + public setRgba(bg: number, fg: number, value: number | null): void { + if (!this._rgba[bg]) { + this._rgba[bg] = {}; + } + this._rgba[bg]![fg] = value; + } + + public getRgba(bg: number, fg: number): number | null | undefined { + return this._rgba[bg] ? this._rgba[bg]![fg] : undefined; + } + + public setColor(bg: number, fg: number, value: IColor | null): void { + if (!this._color[bg]) { + this._color[bg] = {}; + } + this._color[bg]![fg] = value; + } + + public getColor(bg: number, fg: number): IColor | null | undefined { + return this._color[bg] ? this._color[bg]![fg] : undefined; + } +} diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index c6354f71..92994cfc 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -3,9 +3,10 @@ * @license MIT */ -import { IColorManager, IColor, IColorSet } from 'browser/Types'; +import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; import { fromCss, toCss, blend, toRgba } from 'browser/Color'; +import { ColorContrastCache } from 'browser/ColorContrastCache'; const DEFAULT_FOREGROUND = fromCss('#ffffff'); const DEFAULT_BACKGROUND = fromCss('#000000'); @@ -72,6 +73,7 @@ export class ColorManager implements IColorManager { public colors: IColorSet; private _ctx: CanvasRenderingContext2D; private _litmusColor: CanvasGradient; + private _contrastCache: IColorContrastCache; constructor(document: Document, public allowTransparency: boolean) { const canvas = document.createElement('canvas'); @@ -84,6 +86,7 @@ export class ColorManager implements IColorManager { this._ctx = ctx; this._ctx.globalCompositeOperation = 'copy'; this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1); + this._contrastCache = new ColorContrastCache(); this.colors = { foreground: DEFAULT_FOREGROUND, background: DEFAULT_BACKGROUND, @@ -91,10 +94,17 @@ export class ColorManager implements IColorManager { cursorAccent: DEFAULT_CURSOR_ACCENT, selection: DEFAULT_SELECTION, selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), - ansi: DEFAULT_ANSI_COLORS.slice() + ansi: DEFAULT_ANSI_COLORS.slice(), + contrastCache: this._contrastCache }; } + public onOptionsChange(key: string): void { + if (key === 'minimumContrastRatio') { + this._contrastCache.clear(); + } + } + /** * Sets the terminal's theme. * @param theme The theme to use. If a partial theme is provided then default @@ -123,6 +133,8 @@ export class ColorManager implements IColorManager { this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]); this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); + // Clear our the cache + this._contrastCache.clear(); } private _parseColor( diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index ca852b6a..06c3fe38 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -8,6 +8,7 @@ import { IDisposable } from 'common/Types'; export interface IColorManager { colors: IColorSet; + onOptionsChange(key: string): void; } export interface IColor { @@ -24,6 +25,15 @@ export interface IColorSet { /** The selection blended on top of background. */ selectionOpaque: IColor; ansi: IColor[]; + contrastCache: IColorContrastCache +} + +export interface IColorContrastCache { + clear(): void; + setRgba(bg: number, fg: number, value: number | null): void; + getRgba(bg: number, fg: number): number | null | undefined; + setColor(bg: number, fg: number, value: IColor | null): void; + getColor(bg: number, fg: number): IColor | null | undefined; } export interface IPartialColorSet { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index b5caccaf..bb96fb72 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -167,9 +167,17 @@ export class DomRendererRowFactory { return false; } - const adustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); - if (adustedColor) { - element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adustedColor.css}`); + // Try get from cache first + let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg); + + // Calculate and store in cache + if (adjustedColor === undefined) { + adjustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); + this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null); + } + + if (adjustedColor) { + element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adjustedColor.css}`); return true; } From 8a0122f482a2c9a63dcb535be8d9f29e596e1e5f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:42:55 -0800 Subject: [PATCH 309/338] Add contrast caching to webgl --- .../src/atlas/CharAtlasUtils.ts | 2 +- .../src/atlas/WebglCharAtlas.ts | 27 ++++++++++++++----- src/browser/ColorContrastCache.ts | 6 ++--- src/browser/Types.d.ts | 4 +-- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index ad25bf87..8e26b1d6 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -25,7 +25,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. ansi: colors.ansi.slice(), - contrastCache: {} as any + contrastCache: colors.contrastCache }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 1642ce08..b40c27cc 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -207,8 +207,8 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { - const minimumContrastCss = this._getMinimumContrastCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); + private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { + const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold); if (minimumContrastCss) { return minimumContrastCss; } @@ -267,21 +267,34 @@ export class WebglCharAtlas implements IDisposable { } } - private _getMinimumContrastCss(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined { - const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); - const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); + private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined { if (this._config.minimumContrastRatio === 1) { return undefined; } + + // Try get from cache first + const adjustedColor = this._config.colors.contrastCache.getCss(bg, fg); + if (adjustedColor !== undefined) { + return adjustedColor || undefined; + } + + const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); + const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); + if (!result) { + this._config.colors.contrastCache.setCss(bg, fg, null); return undefined; } - return toCss( + + const css = toCss( (result >> 24) & 0xFF, (result >> 16) & 0xFF, (result >> 8) & 0xFF ); + this._config.colors.contrastCache.setCss(bg, fg, css); + + return css; } private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph; @@ -327,7 +340,7 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = 'top'; - this._tmpCtx.fillStyle = this._getForegroundCss(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); + this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold); // Apply alpha to dim the character if (dim) { diff --git a/src/browser/ColorContrastCache.ts b/src/browser/ColorContrastCache.ts index 3198ad83..b96b66cc 100644 --- a/src/browser/ColorContrastCache.ts +++ b/src/browser/ColorContrastCache.ts @@ -7,21 +7,21 @@ import { IColor, IColorContrastCache } from 'browser/Types'; export class ColorContrastCache implements IColorContrastCache { private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {}; - private _rgba: { [bg: number]: { [fg: number]: number | null | undefined } | undefined } = {}; + private _rgba: { [bg: number]: { [fg: number]: string | null | undefined } | undefined } = {}; public clear(): void { this._color = {}; this._rgba = {}; } - public setRgba(bg: number, fg: number, value: number | null): void { + public setCss(bg: number, fg: number, value: string | null): void { if (!this._rgba[bg]) { this._rgba[bg] = {}; } this._rgba[bg]![fg] = value; } - public getRgba(bg: number, fg: number): number | null | undefined { + public getCss(bg: number, fg: number): string | null | undefined { return this._rgba[bg] ? this._rgba[bg]![fg] : undefined; } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 06c3fe38..89c72fa3 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -30,8 +30,8 @@ export interface IColorSet { export interface IColorContrastCache { clear(): void; - setRgba(bg: number, fg: number, value: number | null): void; - getRgba(bg: number, fg: number): number | null | undefined; + setCss(bg: number, fg: number, value: string | null): void; + getCss(bg: number, fg: number): string | null | undefined; setColor(bg: number, fg: number, value: IColor | null): void; getColor(bg: number, fg: number): IColor | null | undefined; } From 3b0042fd46df81b44f4fe16a8068034502b9e934 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:46:07 -0800 Subject: [PATCH 310/338] Remove log --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 26e966a0..e26e054a 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -754,7 +754,6 @@ export async function pollFor(page: puppeteer.Page, evalOrFn: string | (() => await preFn(); } const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn(); - console.log(result); if (!deepEqual(result, val)) { return new Promise(r => { setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1); From 647cff53dbd9c4f964b87b763b5ea97a4158751d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:53:42 -0800 Subject: [PATCH 311/338] Lint --- src/browser/Types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 89c72fa3..31d00af8 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -25,7 +25,7 @@ export interface IColorSet { /** The selection blended on top of background. */ selectionOpaque: IColor; ansi: IColor[]; - contrastCache: IColorContrastCache + contrastCache: IColorContrastCache; } export interface IColorContrastCache { From b3f439af092c1adaf4a1695be44f7a5b8e5753dd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 09:55:09 -0800 Subject: [PATCH 312/338] Remove unused imports --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 3a71dcd8..46fd2730 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -11,10 +11,9 @@ import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { DEFAULT_COLOR, NULL_CELL_CODE, FgFlags } from 'common/buffer/Constants'; +import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types'; From 032185f8b06a6289375e4e899a99638b29d97e9b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:00:40 -0800 Subject: [PATCH 313/338] Link webgl directly to browser/Color --- .../src/atlas/WebglCharAtlas.ts | 118 +----------------- 1 file changed, 1 insertion(+), 117 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b40c27cc..e1b0474f 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -11,6 +11,7 @@ import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; +import { toCss, ensureContrastRatioRgba } from 'browser/Color'; // 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. @@ -543,120 +544,3 @@ function getBgColor(bg: number): number { default: return -1; // CM_DEFAULT defaults to -1 } } - -export function toCss(r: number, g: number, b: number): string { - return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`; -} - -export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { - // >>> 0 forces an unsigned int - return (r << 24 | g << 16 | b << 8 | a) >>> 0; -} - -/** - * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio - * between two colors. - * @param rgb The color to use. - * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef - */ -export function rgbRelativeLuminance(rgb: number): number { - return rgbRelativeLuminance2( - (rgb >> 16) & 0xFF, - (rgb >> 8 ) & 0xFF, - (rgb ) & 0xFF); -} - -export function rgbRelativeLuminance2(r: number, g: number, b: number): number { - const rs = r / 255; - const gs = g / 255; - const bs = b / 255; - const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4); - const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4); - const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4); - return rr * 0.2126 + rg * 0.7152 + rb * 0.0722; -} - -/** - * Gets the contrast ratio between two relative luminance values. - * @param l1 The first relative luminance. - * @param l2 The first relative luminance. - * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef - */ -export function contrastRatio(l1: number, l2: number): number { - if (l1 < l2) { - return (l2 + 0.05) / (l1 + 0.05); - } - return (l1 + 0.05) / (l2 + 0.05); -} - -function rgbaToColor(r: number, g: number, b: number): IColor { - return { - css: toCss(r, g, b), - rgba: toRgba(r, g, b) - }; -} - -export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined { - const bgL = rgbRelativeLuminance(bgRgba >> 8); - const fgL = rgbRelativeLuminance(fgRgba >> 8); - const cr = contrastRatio(bgL, fgL); - if (cr < ratio) { - if (fgL < bgL) { - return reduceLuminance(bgRgba, fgRgba, ratio); - } - return increaseLuminance(bgRgba, fgRgba, ratio); - } - return undefined; -} - -export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { - const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio); - if (!result) { - return undefined; - } - return rgbaToColor( - (result >> 24 & 0xFF), - (result >> 16 & 0xFF), - (result >> 8 & 0xFF) - ); -} - -export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number { - // This is a naive but fast approach to reducing luminance as converting to - // HSL and back is expensive - const bgR = (bgRgba >> 24) & 0xFF; - const bgG = (bgRgba >> 16) & 0xFF; - const bgB = (bgRgba >> 8) & 0xFF; - let fgR = (fgRgba >> 24) & 0xFF; - let fgG = (fgRgba >> 16) & 0xFF; - let fgB = (fgRgba >> 8) & 0xFF; - let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { - // Reduce by 10% until the ratio is hit - fgR -= Math.max(0, Math.ceil(fgR * 0.1)); - fgG -= Math.max(0, Math.ceil(fgG * 0.1)); - fgB -= Math.max(0, Math.ceil(fgB * 0.1)); - cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - } - return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; -} - -export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { - // This is a naive but fast approach to increasing luminance as converting to - // HSL and back is expensive - const bgR = (bgRgba >> 24) & 0xFF; - const bgG = (bgRgba >> 16) & 0xFF; - const bgB = (bgRgba >> 8) & 0xFF; - let fgR = (fgRgba >> 24) & 0xFF; - let fgG = (fgRgba >> 16) & 0xFF; - let fgB = (fgRgba >> 8) & 0xFF; - let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { - // Increase by 10% until the ratio is hit - fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); - fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); - fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); - cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); - } - return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; -} From b0c3606207f043f234de51860d5a94fbee5b9481 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:03:38 -0800 Subject: [PATCH 314/338] Add example values to .d.ts --- typings/xterm.d.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b49ed04b..03f89e0c 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -151,8 +151,12 @@ declare module 'xterm' { /** * The minimum contrast ratio for text in the terminal, setting this will * change the foreground color dynamically depending on whether the contrast - * ratio is met. This can be to set 0.1 increments between 1 (default, do - * nothing) and 21 (foreground will be black or white). + * ratio is met. Example values: + * + * - 1: The default, do nothing. + * - 4.5: Minimum for WCAG AA compliance. + * - 7: Minimum for WCAG AAA compliance. + * - 21: White on black or black on white. */ minimumContrastRatio?: number; From 56d12fe84651a9a59b28b444c0debfb23a7c3010 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:09:32 -0800 Subject: [PATCH 315/338] Fix color manager unit test --- src/browser/ColorManager.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index a213c616..39a8f8c2 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -34,7 +34,7 @@ describe('ColorManager', () => { describe('constructor', () => { it('should fill all colors with values', () => { for (const key of Object.keys(cm.colors)) { - if (key !== 'ansi') { + if (key !== 'ansi' && key !== 'contrastCache') { // A #rrggbb or rgba(...) assert.ok((cm.colors)[key].css.length >= 7); } From d1f7d2ef6acf9577ca4e2ca9556eaba9a55a8829 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:10:55 -0800 Subject: [PATCH 316/338] Tweak readme to call out min contrast ratio --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2944003..70ff7891 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b - **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer. - **Rich unicode support**: Supports CJK, emojis and IMEs. - **Self-contained**: Requires zero dependencies to work. -- **Accessible**: Screen reader support can be turned on using the `screenReaderMode` option. +- **Accessible**: Screen reader and minimum contrast ratio support can be turned on - **And much more**: Links, theming, addons, well documented API, etc. ## What xterm.js is not From db090c70de99e8623e38d01e44f5132a67a58dd4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:21:23 -0800 Subject: [PATCH 317/338] Add unit tests for ColorContrastCache --- src/browser/Color.ts | 2 -- src/browser/ColorContrastCache.test.ts | 43 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/browser/ColorContrastCache.test.ts diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 3a2b5032..09c6a4f1 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -84,8 +84,6 @@ export function contrastRatio(l1: number, l2: number): number { return (l1 + 0.05) / (l2 + 0.05); } -// TODO: Cache [bg][fg]: result, should probably be owned by ColorManager? - function rgbaToColor(r: number, g: number, b: number): IColor { return { css: toCss(r, g, b), diff --git a/src/browser/ColorContrastCache.test.ts b/src/browser/ColorContrastCache.test.ts new file mode 100644 index 00000000..17e5df38 --- /dev/null +++ b/src/browser/ColorContrastCache.test.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { ColorContrastCache } from 'browser/ColorContrastCache'; + +describe('ColorContrastCache', () => { + let cache: ColorContrastCache; + + beforeEach(() => { + cache = new ColorContrastCache(); + }); + + it('should save and get color values', () => { + assert.strictEqual(cache.getColor(0x01, 0x00), undefined); + cache.setColor(0x01, 0x01, null); + assert.strictEqual(cache.getColor(0x01, 0x01), null); + cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff}); + assert.deepEqual(cache.getColor(0x01, 0x02), { css: '#030303', rgba: 0x030303ff}); + }); + + it('should save and get css values', () => { + assert.strictEqual(cache.getCss(0x01, 0x00), undefined); + cache.setCss(0x01, 0x01, null); + assert.strictEqual(cache.getCss(0x01, 0x01), null); + cache.setCss(0x01, 0x02, '#030303'); + assert.deepEqual(cache.getCss(0x01, 0x02), '#030303'); + }); + + it('should clear all values on clear', () => { + cache.setColor(0x01, 0x01, null); + cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff}); + cache.setCss(0x01, 0x01, null); + cache.setCss(0x01, 0x02, '#030303'); + cache.clear(); + assert.strictEqual(cache.getColor(0x01, 0x01), undefined); + assert.strictEqual(cache.getColor(0x01, 0x02), undefined); + assert.strictEqual(cache.getCss(0x01, 0x01), undefined); + assert.strictEqual(cache.getCss(0x01, 0x02), undefined); + }); +}); From 20b0a95bb0ef8decb76720f6187d1e8c8609a523 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Nov 2019 10:40:35 -0800 Subject: [PATCH 318/338] Add tests for ensureContrastRatioRgba --- src/browser/Color.test.ts | 54 ++++++++++++++++++++++++++++++++++++++- src/browser/Color.ts | 4 +-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index 975315c6..673fffc8 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { blend, fromCss, toPaddedHex, toCss, toRgba, rgbRelativeLuminance, contrastRatio } from 'browser/Color'; +import { blend, fromCss, toPaddedHex, toCss, toRgba, rgbRelativeLuminance, contrastRatio, ensureContrastRatioRgba } from 'browser/Color'; describe('Color', () => { describe('blend', () => { @@ -167,4 +167,56 @@ describe('Color', () => { assert.equal(contrastRatio(1, 0), 21); }); }); + describe('ensureContrastRatioRgba', () => { + it('should return undefined if the color already meets the contrast ratio (black bg)', () => { + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 1), undefined); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 2), undefined); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 3), undefined); + }); + it('should return a color that meets the contrast ratio (black bg)', () => { + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 4), 0x707070ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 5), 0x7f7f7fff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 6), 0x8c8c8cff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 7), 0x989898ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 9), 0xadadadff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 11), 0xbebebeff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 15), 0xdbdbdbff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 18), 0xeeeeeeff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 20), 0xfafafaff); + assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 21), 0xffffffff); + }); + it('should return undefined if the color already meets the contrast ratio (white bg)', () => { + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 1), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 2), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 3), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 4), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 5), undefined); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 6), undefined); + }); + it('should return a color that meets the contrast ratio (white bg)', () => { + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 7), 0x565656ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 8), 0x4d4d4dff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 9), 0x454545ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 10), 0x3e3e3eff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 11), 0x373737ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 12), 0x313131ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 13), 0x313131ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 14), 0x272727ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 15), 0x232323ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 16), 0x1f1f1fff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 17), 0x1b1b1bff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 18), 0x151515ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 19), 0x101010ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 20), 0x080808ff); + assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 21), 0x000000ff); + }); + }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 09c6a4f1..aaf5ea2e 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -133,7 +133,7 @@ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): fgB -= Math.max(0, Math.ceil(fgB * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } - return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; + return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; } export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number { @@ -153,5 +153,5 @@ export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number) fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB)); } - return fgR << 24 | fgG << 16 | fgB << 8 | 0xFF; + return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; } From 2c959dfb2e3df7b6ecefa20d69af019702b774f2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Nov 2019 06:52:17 -0800 Subject: [PATCH 319/338] Keep a working AttributeData in WebglCharAtlas --- .../src/atlas/WebglCharAtlas.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index e1b0474f..9910b009 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -68,6 +68,7 @@ export class WebglCharAtlas implements IDisposable { public hasCanvasChanged = false; private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; + private _workAttributeData: AttributeData = new AttributeData(); constructor( document: Document, @@ -305,17 +306,19 @@ export class WebglCharAtlas implements IDisposable { this.hasCanvasChanged = true; - const bold = !!(fg & FgFlags.BOLD); - const inverse = !!(fg & FgFlags.INVERSE); - const dim = !!(bg & BgFlags.DIM); - const italic = !!(bg & BgFlags.ITALIC); - this._tmpCtx.save(); - let fgColor = getFgColor(fg); - let fgColorMode = fg & Attributes.CM_MASK; - let bgColor = getBgColor(bg); - let bgColorMode = bg & Attributes.CM_MASK; + this._workAttributeData.fg = fg; + this._workAttributeData.bg = bg; + + const bold = !!this._workAttributeData.isBold(); + const inverse = !!this._workAttributeData.isInverse(); + const dim = !!this._workAttributeData.isDim(); + const italic = !!this._workAttributeData.isItalic(); + let fgColor = this._workAttributeData.getFgColor(); + let fgColorMode = this._workAttributeData.getFgColorMode(); + let bgColor = this._workAttributeData.getBgColor(); + let bgColorMode = this._workAttributeData.getBgColorMode(); if (inverse) { const temp = fgColor; fgColor = bgColor; From 579d74ee33b54c1db62d568ef71c7f0028f93ccd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Nov 2019 06:53:29 -0800 Subject: [PATCH 320/338] jsdoc rgbRelativeLuminance2 --- src/browser/Color.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index aaf5ea2e..d4cbfe0e 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -61,6 +61,14 @@ export function rgbRelativeLuminance(rgb: number): number { (rgb ) & 0xFF); } +/** + * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio + * between two colors. + * @param r The red channel (0x00 to 0xFF). + * @param g The green channel (0x00 to 0xFF). + * @param b The blue channel (0x00 to 0xFF). + * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef + */ export function rgbRelativeLuminance2(r: number, g: number, b: number): number { const rs = r / 255; const gs = g / 255; From 7652cd8af3a20d5466634fd417126db9f81e049c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Nov 2019 06:59:46 -0800 Subject: [PATCH 321/338] Use hash notation over rgb() for dom colors --- .../renderer/dom/DomRendererRowFactory.test.ts | 4 ++-- src/browser/renderer/dom/DomRendererRowFactory.ts | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 180db0fc..c71945cf 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -212,7 +212,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); @@ -223,7 +223,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, cell); const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), - 'a' + 'a' ); }); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index bb96fb72..5e53cf60 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -130,7 +130,7 @@ export class DomRendererRowFactory { } break; case Attributes.CM_RGB: - charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}color:rgb(${(AttributeData.toColorRGB(fg)).join(',')});`); + charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}color:#${padStart(fg.toString(16), '0', 6)};`); break; case Attributes.CM_DEFAULT: default: @@ -148,7 +148,7 @@ export class DomRendererRowFactory { charElement.classList.add(`xterm-bg-${bg}`); break; case Attributes.CM_RGB: - charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}background-color:rgb(${(AttributeData.toColorRGB(bg)).join(',')});`); + charElement.setAttribute('style', `${charElement.getAttribute('style') || ''}background-color:#${padStart(bg.toString(16), '0', 6)};`); break; case Attributes.CM_DEFAULT: default: @@ -184,3 +184,10 @@ export class DomRendererRowFactory { return false; } } + +function padStart(text: string, padChar: string, length: number): string { + while (text.length < length) { + text = padChar + text; + } + return text; +} From b780abaf3b934dcae1c3cadd5391538b0fdbb42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 13 Nov 2019 23:46:01 +0100 Subject: [PATCH 322/338] add onBinary on CoreService, use for DEFAULT mouse reports --- src/common/TestUtils.test.ts | 2 ++ src/common/services/CoreMouseService.test.ts | 11 +++++----- src/common/services/CoreMouseService.ts | 21 ++++++++++++-------- src/common/services/CoreService.ts | 10 ++++++++++ src/common/services/Services.ts | 9 ++++++++- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 7d9c3aa6..7e47d4b0 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -50,8 +50,10 @@ export class MockCoreService implements ICoreService { decPrivateModes: IDecPrivateModes = {} as any; onData: IEvent = new EventEmitter().event; onUserInput: IEvent = new EventEmitter().event; + onBinary: IEvent = new EventEmitter().event; reset(): void {} triggerDataEvent(data: string, wasUserInput?: boolean): void {} + triggerBinaryEvent(data: string): void {} } export class MockDirtyRowService implements IDirtyRowService { diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts index f7d3bf83..d2a470e5 100644 --- a/src/common/services/CoreMouseService.test.ts +++ b/src/common/services/CoreMouseService.test.ts @@ -6,7 +6,7 @@ import { CoreMouseService } from 'common/services/CoreMouseService'; import { MockCoreService, MockBufferService } from 'common/TestUtils.test'; import { assert } from 'chai'; import { ICoreMouseEvent, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; - +declare const console: any; // needed mock services const bufferService = new MockBufferService(300, 100); const coreService = new MockCoreService(); @@ -79,6 +79,7 @@ describe('CoreMouseService', () => { cms = new CoreMouseService(bufferService, coreService); reports = []; coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data); + coreService.triggerBinaryEvent = (data: string) => reports.push(data); }); it('NONE', () => { assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false); @@ -143,11 +144,11 @@ describe('CoreMouseService', () => { cms.activeProtocol = 'ANY'; for (let i = 0; i < bufferService.cols; ++i) { assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true); - // capped at 95 - if (i < 95) { - assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); + if (i > 222) { + // supress mouse reports if we are out of addressible range (max. 222) + assert.deepEqual(toBytes(reports.pop()), []); } else { - assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, 0x7f, 0x21]); + assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]); } } }); diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 500655b9..57aa2581 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -121,15 +121,13 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = { /** * DEFAULT - CSI M Pb Px Py * Single byte encoding for coords and event code. - * Can encode values up to 223. The Encoding of higher - * values is not UTF-8 compatible (and currently limited - * to 95 in xterm.js). + * Can encode values up to 223 (1-based). */ DEFAULT: (e: ICoreMouseEvent) => { - let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; - // FIXME: we are currently limited to ASCII range - params = params.map(v => (v > 127) ? 127 : v); - // FIXED: params = params.map(v => (v > 255) ? 0 : value); + const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; + if (params[0] > 255 || params[1] > 255 || params[2] > 255) { + return ''; + } return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`; }, /** @@ -266,7 +264,14 @@ export class CoreMouseService implements ICoreMouseService { // encode report and send const report = this._encodings[this._activeEncoding](e); - this._coreService.triggerDataEvent(report, true); + if (this._activeProtocol === 'DEFAULT') { + // always send DEFAULT as binary data + if (report) { + this._coreService.triggerBinaryEvent(report); + } + } else { + this._coreService.triggerDataEvent(report, true); + } this._lastEvent = e; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 11c3f305..35b61e84 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -23,6 +23,8 @@ export class CoreService implements ICoreService { public get onData(): IEvent { return this._onData.event; } private _onUserInput = new EventEmitter(); public get onUserInput(): IEvent { return this._onUserInput.event; } + private _onBinary = new EventEmitter(); + public get onBinary(): IEvent { return this._onBinary.event; } constructor( // TODO: Move this into a service @@ -59,4 +61,12 @@ export class CoreService implements ICoreService { this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); this._onData.fire(data); } + + public triggerBinaryEvent(data: string): void { + if (this._optionsService.options.disableStdin) { + return; + } + this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); + this._onBinary.fire(data); + } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e4ff90d0..45d39a6a 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -68,6 +68,7 @@ export interface ICoreService { readonly onData: IEvent; readonly onUserInput: IEvent; + readonly onBinary: IEvent; reset(): void; @@ -78,8 +79,14 @@ export interface ICoreService { * resulting from parsing incoming data). When true this will also: * - Scroll to the bottom of the buffer.s * - Fire the `onUserInput` event (so selection can be cleared). - */ + */ triggerDataEvent(data: string, wasUserInput?: boolean): void; + + /** + * Triggers the onBinary event in the public API. + * @param data The data that is being emitted. + */ + triggerBinaryEvent(data: string): void; } export const IDirtyRowService = createDecorator('DirtyRowService'); From 00d626b6ccd449698de12a5132fd8c95058b7827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 13 Nov 2019 23:57:51 +0100 Subject: [PATCH 323/338] add onBinary to interfaces --- src/Terminal.ts | 2 ++ src/TestUtils.test.ts | 1 + src/Types.d.ts | 1 + src/common/services/CoreMouseService.test.ts | 2 +- src/public/Terminal.ts | 1 + typings/xterm.d.ts | 11 +++++++++++ 6 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 3c589ffa..d19f34e3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -169,6 +169,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onData = new EventEmitter(); public get onData(): IEvent { return this._onData.event; } + private _onBinary = new EventEmitter(); + public get onBinary(): IEvent { return this._onBinary.event; } private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } private _onLineFeed = new EventEmitter(); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index e7c48381..84c0d0b7 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -32,6 +32,7 @@ export class MockTerminal implements ITerminal { onLineFeed: IEvent; onSelectionChange: IEvent; onData: IEvent; + onBinary: IEvent; onTitleChange: IEvent; onScroll: IEvent; onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>; diff --git a/src/Types.d.ts b/src/Types.d.ts index 2a922ae1..f4d3a556 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -180,6 +180,7 @@ export interface IPublicTerminal extends IDisposable { markers: IMarker[]; onCursorMove: IEvent; onData: IEvent; + onBinary: IEvent; onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; onLineFeed: IEvent; onScroll: IEvent; diff --git a/src/common/services/CoreMouseService.test.ts b/src/common/services/CoreMouseService.test.ts index d2a470e5..2bf011b0 100644 --- a/src/common/services/CoreMouseService.test.ts +++ b/src/common/services/CoreMouseService.test.ts @@ -6,7 +6,7 @@ import { CoreMouseService } from 'common/services/CoreMouseService'; import { MockCoreService, MockBufferService } from 'common/TestUtils.test'; import { assert } from 'chai'; import { ICoreMouseEvent, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; -declare const console: any; + // needed mock services const bufferService = new MockBufferService(300, 100); const coreService = new MockCoreService(); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index c167bed8..397898c7 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -27,6 +27,7 @@ export class Terminal implements ITerminalApi { public get onLineFeed(): IEvent { return this._core.onLineFeed; } public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } public get onData(): IEvent { return this._core.onData; } + public get onBinary(): IEvent { return this._core.onBinary; } public get onTitleChange(): IEvent { return this._core.onTitleChange; } public get onScroll(): IEvent { return this._core.onScroll; } public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9728a72c..a086d030 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -421,6 +421,17 @@ declare module 'xterm' { */ constructor(options?: ITerminalOptions); + /** + * Adds an event listener for when a binary event fires. This is used to + * enable non UTF-8 conformant binary messages to be sent to the backend. + * Currently this is only used for a certain type of mouse reports that + * happen to be not UTF-8 compatible. + * The event value is a JS string, pass it to the underlying pty as + * binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`. + * @returns an `IDisposable` to stop listening. + */ + onBinary: IEvent; + /** * Adds an event listener for the cursor moves. * @returns an `IDisposable` to stop listening. From f1f103850ce2bcf43d43a269e48d74fa8980f542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 00:48:16 +0100 Subject: [PATCH 324/338] fix tests; fix attach addon --- addons/xterm-addon-attach/src/AttachAddon.ts | 12 ++++++ src/Terminal.ts | 1 + src/common/services/CoreMouseService.ts | 2 +- test/api/MouseTracking.api.ts | 43 +++++++++++--------- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.ts b/addons/xterm-addon-attach/src/AttachAddon.ts index 117b2b58..279d1b2e 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.ts @@ -33,6 +33,7 @@ export class AttachAddon implements ITerminalAddon { if (this._bidirectional) { this._disposables.push(terminal.onData(data => this._sendData(data))); + this._disposables.push(terminal.onBinary(data => this._sendBinary(data))); } this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose())); @@ -51,6 +52,17 @@ export class AttachAddon implements ITerminalAddon { } this._socket.send(data); } + + private _sendBinary(data: string): void { + if (this._socket.readyState !== 1) { + return; + } + const buffer = new Uint8Array(data.length); + for (let i = 0; i < data.length; ++i) { + buffer[i] = data.charCodeAt(i) & 255; + } + this._socket.send(buffer); + } } function addSocketListener(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable { diff --git a/src/Terminal.ts b/src/Terminal.ts index d19f34e3..6a039a0e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -223,6 +223,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()); this._instantiationService.setService(ICoreService, this._coreService); this._coreService.onData(e => this._onData.fire(e)); + this._coreService.onBinary(e => this._onBinary.fire(e)); this._coreMouseService = this._instantiationService.createInstance(CoreMouseService); this._instantiationService.setService(ICoreMouseService, this._coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 57aa2581..5c9d3944 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -264,7 +264,7 @@ export class CoreMouseService implements ICoreMouseService { // encode report and send const report = this._encodings[this._activeEncoding](e); - if (this._activeProtocol === 'DEFAULT') { + if (this._activeEncoding === 'DEFAULT') { // always send DEFAULT as binary data if (report) { this._coreService.triggerBinaryEvent(report); diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index f8124350..7cae660f 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -220,6 +220,7 @@ describe('Mouse Tracking Tests', () => { await page.evaluate(` window.calls = []; window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); + window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); window.term.setOption('fontSize', ${fontSize}); window.term.resize(${cols}, ${rows}); `); @@ -255,12 +256,17 @@ describe('Mouse Tracking Tests', () => { await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); // test at max rows/cols - // bug: we are capped at col 95 currently - // fix: allow values up to 223, any bigger should drop to 0 - await mouseMove(cols - 1, rows - 1); + // capped at 223 (1-based) + await mouseMove(223 - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); - await pollFor(page, () => getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + await pollFor(page, () => getReports(encoding), [{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]); + + // higher than 223 should not report at all + await mouseMove(257, rows - 1); + await mouseDown('left'); + await mouseUp('left'); + await pollFor(page, () => getReports(encoding), []); // button press/move/release tests // left button @@ -511,14 +517,13 @@ describe('Mouse Tracking Tests', () => { ]); // test at max rows/cols - // bug: we are capped at col 95 currently - // fix: allow values up to 223, any bigger should drop to 0 - await mouseMove(cols - 1, rows - 1); + // capped at 223 (1-based) + await mouseMove(223 - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); await pollFor(page, () => getReports(encoding), [ - {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 223, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // button press/move/release tests @@ -821,14 +826,13 @@ describe('Mouse Tracking Tests', () => { ]); // test at max rows/cols - // bug: we are capped at col 95 currently - // fix: allow values up to 223, any bigger should drop to 0 - await mouseMove(cols - 1, rows - 1); + // capped at 223 (1-based) + await mouseMove(223 - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); await pollFor(page, () => getReports(encoding), [ - {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 223, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // button press/move/release tests @@ -1142,15 +1146,14 @@ describe('Mouse Tracking Tests', () => { ]); // test at max rows/cols - // bug: we are capped at col 95 currently - // fix: allow values up to 223, any bigger should drop to 0 - await mouseMove(cols - 1, rows - 1); + // capped at 223 (1-based) + await mouseMove(223 - 1, rows - 1); await mouseDown('left'); await mouseUp('left'); await pollFor(page, () => getReports(encoding), [ - {col: 95, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, - {col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, - {col: 95, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} + {col: 223, row: rows, state: {action: 'move', button: '', modifier: {control: false, shift: false, meta: false}}}, + {col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}, + {col: 223, row: rows, state: {action: 'release', button: '', modifier: {control: false, shift: false, meta: false}}} ]); // button press/move/release tests From 57fe84508cb43291d508e546ed5130174ee5a141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 14 Nov 2019 22:29:46 +0100 Subject: [PATCH 325/338] apply empty report rule to all encodings; comments added --- src/common/Types.d.ts | 3 +++ src/common/services/CoreMouseService.ts | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 2f742038..2dcc704b 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -249,5 +249,8 @@ export interface ICoreMouseProtocol { * The tracking encoding can be registered and activated at the CoreMouseService. * If a ICoreMouseEvent passes all procotol restrictions it will be encoded * with the active encoding and sent out. + * Note: Returning an empty string will supress sending a mouse report, + * which can be used to skip creating falsey reports in limited encodings + * (DEFAULT only supports up to 223 1-based as coord value). */ export type CoreMouseEncoding = (event: ICoreMouseEvent) => string; diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 5c9d3944..0bd25dd0 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -125,6 +125,10 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = { */ DEFAULT: (e: ICoreMouseEvent) => { const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32]; + // supress mouse report if we exceed addressible range + // Note this is handled differently by emulators + // - xterm: sends 0;0 coords instead + // - vte, konsole: no report if (params[0] > 255 || params[1] > 255 || params[2] > 255) { return ''; } @@ -264,13 +268,13 @@ export class CoreMouseService implements ICoreMouseService { // encode report and send const report = this._encodings[this._activeEncoding](e); - if (this._activeEncoding === 'DEFAULT') { + if (report) { // always send DEFAULT as binary data - if (report) { + if (this._activeEncoding === 'DEFAULT') { this._coreService.triggerBinaryEvent(report); + } else { + this._coreService.triggerDataEvent(report, true); } - } else { - this._coreService.triggerDataEvent(report, true); } this._lastEvent = e; From 24dc87af2c6165216d4fb4b70c84f474f72155ed Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 19:46:24 -0800 Subject: [PATCH 326/338] Only enter updateBackgounds condition if cell changed We want to check when the background changed or when the foreground changed (and either the old or new value was inverse) --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 356e1747..b52a506e 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -254,7 +254,7 @@ export class RectangleRenderer { const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; const inverse = !!(fg & FgFlags.INVERSE); - if (bg !== currentBg || ((inverse || currentInverse) && fg !== currentFg)) { + if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) { // A rectangle needs to be drawn if going from non-default to another color if (currentBg !== 0 || (currentInverse && currentFg !== 0)) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; From be9700878b7604dfec6133c73c20926fc72af8ae Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 20:02:40 -0800 Subject: [PATCH 327/338] Have inverse bold respect drawBoldTextInBrightColors --- src/browser/renderer/BaseRenderLayer.ts | 8 ++++++-- src/browser/renderer/TextRenderLayer.ts | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index a33bef28..2c748c50 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -282,7 +282,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); } - const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8; fg += drawInBrightColor ? 8 : 0; this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR; @@ -325,7 +325,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { } else if (cell.isBgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else { - this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; + let bg = cell.getBgColor(); + if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { + bg += 8; + } + this._ctx.fillStyle = this._colors.ansi[bg].css; } } else { if (cell.isFgDefault()) { diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 330400ca..fee261f6 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -227,7 +227,11 @@ export class TextRenderLayer extends BaseRenderLayer { } else if (cell.isBgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else { - this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; + let bg = cell.getBgColor(); + if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { + bg += 8; + } + this._ctx.fillStyle = this._colors.ansi[bg].css; } } else { if (cell.isFgDefault()) { From 023fab9095c63ff3e549dbc171225da232b7d2ea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 20:34:01 -0800 Subject: [PATCH 328/338] Remove unused import --- src/browser/renderer/dom/DomRendererRowFactory.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 5e53cf60..cffc5624 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -5,7 +5,6 @@ import { IBufferLine } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService } from 'common/services/Services'; From c95337a742c6334df50c82121416b97e63dbae55 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 21:57:15 -0800 Subject: [PATCH 329/338] Remove unneeded params from webgl funcs --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9910b009..686dcb10 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -234,7 +234,7 @@ export class WebglCharAtlas implements IDisposable { } } - private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean): number { + private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number { switch (bgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: @@ -250,7 +250,7 @@ export class WebglCharAtlas implements IDisposable { } } - private _resolveForegroundRgba(bgColorMode: number, bgColor: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { + private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { switch (fgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: @@ -280,8 +280,8 @@ export class WebglCharAtlas implements IDisposable { return adjustedColor || undefined; } - const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse); - const fgRgba = this._resolveForegroundRgba(bgColorMode, bgColor, fgColorMode, fgColor, inverse, bold); + const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse); + const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold); const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio); if (!result) { From e5f4e2ff8f2ac034764744fb4d411c0ac1b62952 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 22:07:50 -0800 Subject: [PATCH 330/338] Support min contrast ratio in canvas renderer too: --- src/browser/renderer/BaseRenderLayer.ts | 103 ++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 2c748c50..599ab114 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -5,16 +5,17 @@ import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types'; import { ICellData } from 'common/Types'; -import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from 'browser/renderer/atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IColor } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { toCss, ensureContrastRatioRgba } from 'browser/Color'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -262,13 +263,14 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param bold Whether the text is bold. */ protected _drawChars(cell: ICellData, x: number, y: number): void { + const contrastColor = this._getContrastColor(cell); // skip cache right away if we draw in RGB // Note: to avoid bad runtime JoinedCellData will be skipped // in the cache handler itself (atlasDidDraw == false) and // fall through to uncached later down below - if (cell.isFgRGB() || cell.isBgRGB()) { - this._drawUncachedChars(cell, x, y); + if (contrastColor || cell.isFgRGB() || cell.isBgRGB()) { + this._drawUncachedChars(cell, x, y, contrastColor); return; } @@ -314,13 +316,15 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to draw at. * @param y The row to draw at. */ - private _drawUncachedChars(cell: ICellData, x: number, y: number): void { + private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void { this._ctx.save(); this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic()); this._ctx.textBaseline = 'middle'; if (cell.isInverse()) { - if (cell.isBgDefault()) { + if (fgOverride) { + this._ctx.fillStyle = fgOverride.css; + } else if (cell.isBgDefault()) { this._ctx.fillStyle = this._colors.background.css; } else if (cell.isBgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; @@ -332,7 +336,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillStyle = this._colors.ansi[bg].css; } } else { - if (cell.isFgDefault()) { + if (fgOverride) { + this._ctx.fillStyle = fgOverride.css; + } else if (cell.isFgDefault()) { this._ctx.fillStyle = this._colors.foreground.css; } else if (cell.isFgRGB()) { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; @@ -383,5 +389,88 @@ export abstract class BaseRenderLayer implements IRenderLayer { return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`; } + + private _getContrastColor(cell: CellData): IColor | undefined { + if (this._optionsService.options.minimumContrastRatio === 1) { + return undefined; + } + + // Try get from cache first + const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg); + if (adjustedColor !== undefined) { + return adjustedColor || undefined; + } + + let fgColor = cell.getFgColor(); + let fgColorMode = cell.getFgColorMode(); + let bgColor = cell.getBgColor(); + let bgColorMode = cell.getBgColorMode(); + const isInverse = !!cell.isInverse(); + const isBold = !!cell.isInverse(); + if (isInverse) { + const temp = fgColor; + fgColor = bgColor; + bgColor = temp; + const temp2 = fgColorMode; + fgColorMode = bgColorMode; + bgColorMode = temp2; + } + + const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse); + const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold); + const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio); + + if (!result) { + this._colors.contrastCache.setColor(cell.bg, cell.fg, null); + return undefined; + } + + const color: IColor = { + css: toCss( + (result >> 24) & 0xFF, + (result >> 16) & 0xFF, + (result >> 8) & 0xFF + ), + rgba: result + }; + this._colors.contrastCache.setColor(cell.bg, cell.fg, color); + + return color; + } + + private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number { + switch (bgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + return this._colors.ansi[bgColor].rgba; + case Attributes.CM_RGB: + return bgColor << 8; + case Attributes.CM_DEFAULT: + default: + if (inverse) { + return this._colors.foreground.rgba; + } + return this._colors.background.rgba; + } + } + + private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { + switch (fgColorMode) { + case Attributes.CM_P16: + case Attributes.CM_P256: + if (this._optionsService.options.drawBoldTextInBrightColors && bold && fgColor < 8) { + fgColor += 8; + } + return this._colors.ansi[fgColor].rgba; + case Attributes.CM_RGB: + return fgColor << 8; + case Attributes.CM_DEFAULT: + default: + if (inverse) { + return this._colors.background.rgba; + } + return this._colors.foreground.rgba; + } + } } From 5aa31576722f02213411be860b678d12281b58d0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 22:12:36 -0800 Subject: [PATCH 331/338] Return inverse colors correctly This probably wasn't causing a bug since the opposite values were being returned, it was wrong and made the code confusing though. --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 686dcb10..cda20225 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -244,7 +244,7 @@ export class WebglCharAtlas implements IDisposable { case Attributes.CM_DEFAULT: default: if (inverse) { - this._config.colors.foreground.rgba; + return this._config.colors.foreground.rgba; } return this._config.colors.background.rgba; } @@ -263,7 +263,7 @@ export class WebglCharAtlas implements IDisposable { case Attributes.CM_DEFAULT: default: if (inverse) { - this._config.colors.background.rgba; + return this._config.colors.background.rgba; } return this._config.colors.foreground.rgba; } From 943928c4d1a6d4f46fb33fd7969fff71aeed7786 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Nov 2019 22:26:02 -0800 Subject: [PATCH 332/338] Move WindowsMode to common/ Part of #1507 --- src/Terminal.ts | 14 +++++++++----- src/{ => common}/WindowsMode.ts | 15 +++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) rename src/{ => common}/WindowsMode.ts (67%) diff --git a/src/Terminal.ts b/src/Terminal.ts index 3c589ffa..fc354966 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,7 +43,7 @@ import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeDa import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { applyWindowsMode } from './WindowsMode'; +import { handleWindowsModeLineFeed } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services'; @@ -281,7 +281,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService); if (this.options.windowsMode) { - this._windowsMode = applyWindowsMode(this); + this._enableWindowsMode(); + } + } + + private _enableWindowsMode(): void { + if (!this._windowsMode) { + this._windowsMode = this.onLineFeed(handleWindowsModeLineFeed.bind(null, this._bufferService)); } } @@ -362,9 +368,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp break; case 'windowsMode': if (this.optionsService.options.windowsMode) { - if (!this._windowsMode) { - this._windowsMode = applyWindowsMode(this); - } + this._enableWindowsMode(); } else { this._windowsMode?.dispose(); this._windowsMode = undefined; diff --git a/src/WindowsMode.ts b/src/common/WindowsMode.ts similarity index 67% rename from src/WindowsMode.ts rename to src/common/WindowsMode.ts index 0838cae2..ff0e7591 100644 --- a/src/WindowsMode.ts +++ b/src/common/WindowsMode.ts @@ -3,11 +3,10 @@ * @license MIT */ -import { IDisposable } from 'xterm'; -import { ITerminal } from './Types'; import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { IBufferService } from 'common/services/Services'; -export function applyWindowsMode(terminal: ITerminal): IDisposable { +export function handleWindowsModeLineFeed(bufferService: IBufferService): void { // Winpty does not support wraparound mode which means that lines will never // be marked as wrapped. This causes issues for things like copying a line // retaining the wrapped new line characters or if consumers are listening @@ -18,11 +17,11 @@ export function applyWindowsMode(terminal: ITerminal): IDisposable { // space. This is certainly not without its problems, but generally on // Windows when text reaches the end of the terminal it's likely going to be // wrapped. - return terminal.onLineFeed(() => { - const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1); - const lastChar = line.get(terminal.cols - 1); + const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1); + const lastChar = line?.get(bufferService.cols - 1); - const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y); + const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y); + if (nextLine && lastChar) { nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE); - }); + } } From 93487d4fb373602686ab0e874af1824287eafbae Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 22 Nov 2019 09:34:06 -0800 Subject: [PATCH 333/338] Direct question issues to Stack Overflow --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ .github/ISSUE_TEMPLATE/question.md | 8 -------- 2 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..dccd0c5d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Question + url: https://stackoverflow.com/questions/tagged/xtermjs + about: Please ask and answer questions here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 1fe5c763..00000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: Question -about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs ---- - -🛑 The issue tracker is not for questions 🛑 - -If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs. From 0bd0f3a364c8f403d7a49c2d434d614e6f1a7911 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 22 Nov 2019 16:00:25 -0800 Subject: [PATCH 334/338] Prevent atlas missing exception when opened before attach See microsoft/vscode#85048 --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 46fd2730..6f98d2c1 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -37,6 +37,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; private _core: ITerminal; + private _isAttached: boolean; private _onRequestRefreshRows = new EventEmitter(); public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } @@ -89,6 +90,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update dimensions and acquire char atlas this.onCharSizeChanged(); + + this._isAttached = document.body.contains(this._core.screenElement); } public dispose(): void { @@ -99,7 +102,6 @@ export class WebglRenderer extends Disposable implements IRenderer { public setColors(colors: IColorSet): void { this._colors = colors; - // Clear layers and force a full render this._renderLayers.forEach(l => { l.setColors(this._terminal, this._colors); @@ -192,6 +194,8 @@ export class WebglRenderer extends Disposable implements IRenderer { */ private _refreshCharAtlas(): void { if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) { + // Mark as not attached so char atlas gets refreshed on next render + this._isAttached = false; return; } @@ -217,6 +221,16 @@ export class WebglRenderer extends Disposable implements IRenderer { } public renderRows(start: number, end: number): void { + if (!this._isAttached) { + if (document.body.contains(this._core.screenElement) && (this._core)._charSizeService.width && (this._core)._charSizeService.height) { + this._updateDimensions(); + this._refreshCharAtlas(); + this._isAttached = true; + } else { + return; + } + } + // Update render layers this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); From 2f1f2f87855b2d78af6f6e021f0d97127fdb7740 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 22 Nov 2019 19:37:53 -0800 Subject: [PATCH 335/338] Add a . before beta and beta version Fixes #2576 --- bin/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/publish.js b/bin/publish.js index 9d58ed15..4a7536cb 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -105,7 +105,7 @@ function getNextBetaVersion(packageJson) { return aVersion > bVersion ? -1 : 1; })[0]; const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); - return `${nextStableVersion}-${tag}${latestTagVersion + 1}`; + return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`; } function getPublishedVersions(packageJson, version, tag) { From 53fb1ba54df402d24f0e48ba36e918ca46d05653 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Nov 2019 09:22:07 -0800 Subject: [PATCH 336/338] Don't throw when rendering before atlas is set This fixes a problem when loading the webgl addon before the terminal is attached. See microsoft/vscode#85048 --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 3d4d43d1..e72346dc 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -184,7 +184,7 @@ export class GlyphRenderer { let rasterizedGlyph: IRasterizedGlyph; if (!this._atlas) { - throw new Error('atlas must be set before updating cell'); + return; } if (chars && chars.length > 1) { rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg); From c60b822f4b3dd8422253c41795e726c57eb5d13d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Nov 2019 09:34:29 -0800 Subject: [PATCH 337/338] Fix publish script for dot separated beta --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 1 - bin/publish.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index e72346dc..7b35949d 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -101,7 +101,6 @@ export class GlyphRenderer { private _dimensions: IRenderDimensions ) { const gl = this._gl; - const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource)); this._program = program; diff --git a/bin/publish.js b/bin/publish.js index 4a7536cb..9050c344 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -97,7 +97,7 @@ function getNextBetaVersion(packageJson) { const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag); if (publishedVersions.length === 0) { - return `${nextStableVersion}-${tag}1`; + return `${nextStableVersion}-${tag}.1`; } const latestPublishedVersion = publishedVersions.sort((a, b) => { const aVersion = parseInt(a.substr(a.search(/[0-9]+$/))); @@ -112,7 +112,7 @@ function getPublishedVersions(packageJson, version, tag) { const versionsProcess = cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']); const versionsJson = JSON.parse(versionsProcess.stdout); if (tag) { - return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); + return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}.[0-9]+`))); } return versionsJson; } From 2da5a7a26b44dc66f4ac9b772cd6b9bb4a4271b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Nov 2019 09:42:33 -0800 Subject: [PATCH 338/338] Skip puppeteer download during release job --- azure-pipelines.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0f61dfe1..f6f892c7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -127,6 +127,8 @@ jobs: condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true'))) pool: vmImage: 'ubuntu-16.04' + variables: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 steps: - task: NodeTool@0 inputs: @@ -140,6 +142,7 @@ jobs: inputs: key: yarn2 | $(Agent.OS) | yarn.lock path: node_modules + displayName: Cache node modules - script: yarn --frozen-lockfile displayName: 'Install dependencies and build' - script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js