From ca1f7f1392203c9f601ad3f68dd818fb1bde025a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 10:11:59 -0700 Subject: [PATCH 01/25] Add EventEmitter2 --- src/common/EventEmitter2.test.ts | 31 +++++++++++++++++++++ src/common/EventEmitter2.ts | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/common/EventEmitter2.test.ts create mode 100644 src/common/EventEmitter2.ts diff --git a/src/common/EventEmitter2.test.ts b/src/common/EventEmitter2.test.ts new file mode 100644 index 00000000..53ee4c4f --- /dev/null +++ b/src/common/EventEmitter2.test.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { EventEmitter2 } from './EventEmitter2'; + +describe('EventEmitter2', () => { + it('should fire listeners multiple times', () => { + const order: string[] = []; + const emitter = new EventEmitter2(); + emitter.event(data => order.push(data + 'a')); + emitter.event(data => order.push(data + 'b')); + emitter.fire(1); + emitter.fire(2); + assert.deepEqual(order, [ '1a', '1b', '2a', '2b' ]); + }); + + it('should not fire listeners once disposed', () => { + const order: string[] = []; + const emitter = new EventEmitter2(); + emitter.event(data => order.push(data + 'a')); + const disposeB = emitter.event(data => order.push(data + 'b')); + emitter.event(data => order.push(data + 'c')); + emitter.fire(1); + disposeB.dispose(); + emitter.fire(2); + assert.deepEqual(order, [ '1a', '1b', '1c', '2a', '2c' ]); + }); +}); diff --git a/src/common/EventEmitter2.ts b/src/common/EventEmitter2.ts new file mode 100644 index 00000000..447f816c --- /dev/null +++ b/src/common/EventEmitter2.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from 'xterm'; + +type Listener = (e: T) => void; + +export interface IEvent { + (listener: (e: T) => any): IDisposable; +} + +export class EventEmitter2 { + private _listeners: Listener[] = []; + private _event?: IEvent; + + public get event(): IEvent { + if (!this._event) { + this._event = (listener: (e: T) => any) => { + 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; + } + } + } + }; + return disposable; + }; + } + return this._event; + } + + public fire(data: T): void { + const queue: Listener[] = []; + for (let i = 0; i < this._listeners.length; i++) { + queue.push(this._listeners[i]); + } + for (let i = 0; i < queue.length; i++) { + queue[i].call(undefined, data); + } + } +} From ba4662ac147968bf44494a5db0d22d66913dd762 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:01:16 -0700 Subject: [PATCH 02/25] Introduce new onEvent APIs and deprecate on/off/etc. Part of #1505 --- src/Terminal.ts | 28 +++++++++++++++ src/public/Terminal.ts | 10 ++++++ typings/xterm.d.ts | 77 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index db696bd6..8c69a551 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; import { clone } from './common/Clone'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -217,6 +218,23 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public cols: number; public rows: number; + private _onLineFeed = new EventEmitter2(); + public get onLineFeed(): IEvent { return this._onLineFeed.event; } + private _onSelectionChange = new EventEmitter2(); + public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + private _onInput = new EventEmitter2(); + public get onInput(): IEvent { return this._onInput.event; } + private _onTitleChange = new EventEmitter2(); + public get onTitleChange(): IEvent { return this._onTitleChange.event; } + private _onScroll = new EventEmitter2(); + public get onScroll(): IEvent { return this._onScroll.event; } + private _onKey = new EventEmitter2<{ key: string, domEvent: KeyboardEvent }>(); + public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + private _onResize = new EventEmitter2<{ cols: number, rows: number }>(); + public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + /** * Creates a new `Terminal` object. * @@ -235,6 +253,16 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II super(); this.options = clone(options); this._setup(); + + // TODO: Replace EventEmitter with EventEmitter2 internally + this.on('linefeed', () => this._onLineFeed.fire()); + this.on('selection', () => this._onSelectionChange.fire()); + this.on('data', e => this._onInput.fire(e)); + this.on('title', e => this._onTitleChange.fire(e)); + this.on('scroll', e => this._onScroll.fire(e)); + this.on('key', e => this._onKey.fire(e)); + this.on('refresh', e => this._onRender.fire(e)); + this.on('resize', e => this._onResize.fire(e)); } public dispose(): void { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 87fcfaef..181beeeb 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -7,6 +7,7 @@ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILink import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; +import { IEvent } from '../../lib/common/EventEmitter2'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -15,6 +16,15 @@ export class Terminal implements ITerminalApi { this._core = new TerminalCore(options); } + public get onLineFeed(): IEvent { return this._core.onLineFeed; } + public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } + public get onInput(): IEvent { return this._core.onInput; } + 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; } + public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; } + public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; } + public get element(): HTMLElement { return this._core.element; } public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d813bc5f..0a29cfc8 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -295,6 +295,14 @@ declare module 'xterm' { dispose(): void; } + /** + * An event that can be listened to. + * @returns an `IDisposable` to stop listening. + */ + export interface IEvent { + (listener: (e: T) => any): IDisposable; + } + export interface IMarker extends IDisposable { readonly id: number; readonly isDisposed: boolean; @@ -353,6 +361,64 @@ declare module 'xterm' { */ constructor(options?: ITerminalOptions); + /** + * Adds an event listener for when a line feed is added. + * @returns an `IDisposable` to stop listening. + */ + onLineFeed: IEvent; + + /** + * Adds an event listener for when a selection change occurs. + * @returns an `IDisposable` to stop listening. + */ + onSelectionChange: IEvent; + + /** + * Adds an event listener for when an input event fires. This happens for + * example when the user types or pastes into the terminal. The event value + * is whatever `string` results, in a typical setup, this should be passed + * on to the backing pty. + * @returns an `IDisposable` to stop listening. + */ + onInput: IEvent; + + /** + * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. + * The event value is the new title. + * @returns an `IDisposable` to stop listening. + */ + onTitleChange: IEvent; + + /** + * Adds an event listener for when a scroll occurs. The event value is the + * new position of the viewport. + * @returns an `IDisposable` to stop listening. + */ + onScroll: IEvent; + + /** + * Adds an event listener for 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. + */ + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + + /** + * Adds an event listener for when rows are rendered. The event value + * contains the start row and end rows of the rendered area (ranges from `0` + * to `Terminal.rows - 1`). + * @returns an `IDisposable` to stop listening. + */ + onRender: IEvent<{ start: number, end: number }>; + + /** + * Adds an event listener for when the terminal is resized. The event value + * contains the new size. + * @returns an `IDisposable` to stop listening. + */ + onResize: IEvent<{ cols: number, rows: number }>; + /** * Unfocus the terminal. */ @@ -367,54 +433,63 @@ declare module 'xterm' { * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'data', listener: (...args: any[]) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'key', listener: (key: string, event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'keypress' | 'keydown', listener: (event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'refresh', listener: (data: {start: number, end: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'resize', listener: (data: {cols: number, rows: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'scroll', listener: (ydisp: number) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'title', listener: (title: string) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: string, listener: (...args: any[]) => void): void; @@ -422,6 +497,7 @@ declare module 'xterm' { * Deregisters an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener).dispose()` instead. */ off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; @@ -439,6 +515,7 @@ declare module 'xterm' { * be used to conveniently remove the event listener. * @param type The type of event. * @param handler The event handler. + * @deprecated use `Terminal.onEvent(listener)` instead. */ addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable; From 5f014be057db713055a0038379abbb4f2984d347 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:12:57 -0700 Subject: [PATCH 03/25] Add onCursorMove, replace .on usage in addons --- src/Terminal.ts | 3 +++ src/addons/search/SearchHelper.ts | 9 +++++++-- src/addons/terminado/terminado.ts | 4 ++-- src/addons/winptyCompat/winptyCompat.ts | 2 +- src/public/Terminal.ts | 1 + src/ui/TestUtils.test.ts | 11 ++++++++++- typings/xterm.d.ts | 6 ++++++ 7 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 8c69a551..1f05ec2e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -218,6 +218,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public cols: number; public rows: number; + private _onCursorMove = new EventEmitter2(); + public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter2(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onSelectionChange = new EventEmitter2(); @@ -255,6 +257,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setup(); // TODO: Replace EventEmitter with EventEmitter2 internally + this.on('cursormove', () => this._onCursorMove.fire()); this.on('linefeed', () => this._onLineFeed.fire()); this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 96cd845d..7562efdf 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -4,6 +4,7 @@ */ import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces'; +import { IDisposable } from 'xterm'; const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?'; const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs @@ -19,6 +20,7 @@ export class SearchHelper implements ISearchHelper { */ private _linesCache: string[] = null; private _linesCacheTimeoutId = 0; + private _cursorMoveListener: IDisposable | undefined; constructor(private _terminal: ISearchAddonTerminal) { this._destroyLinesCache = this._destroyLinesCache.bind(this); @@ -148,7 +150,7 @@ export class SearchHelper implements ISearchHelper { private _initLinesCache(): void { if (!this._linesCache) { this._linesCache = new Array(this._terminal._core.buffer.length); - this._terminal.on('cursormove', this._destroyLinesCache); + this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache()); } window.clearTimeout(this._linesCacheTimeoutId); @@ -157,7 +159,10 @@ export class SearchHelper implements ISearchHelper { private _destroyLinesCache(): void { this._linesCache = null; - this._terminal.off('cursormove', this._destroyLinesCache); + if (this._cursorMoveListener) { + this._cursorMoveListener.dispose(); + this._cursorMoveListener = undefined; + } if (this._linesCacheTimeoutId) { window.clearTimeout(this._linesCacheTimeoutId); this._linesCacheTimeoutId = 0; diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts index 136eea8e..b36c2cbf 100644 --- a/src/addons/terminado/terminado.ts +++ b/src/addons/terminado/terminado.ts @@ -59,9 +59,9 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional socket.addEventListener('message', addonTerminal.__getMessage); if (bidirectional) { - addonTerminal.on('data', addonTerminal.__sendData); + addonTerminal.onInput(addonTerminal.__sendData); } - addonTerminal.on('resize', addonTerminal.__setSize); + addonTerminal.onResize(addonTerminal.__setSize); socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket)); socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket)); diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index 58f59fd9..59044613 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -25,7 +25,7 @@ export function winptyCompatInit(terminal: Terminal): void { // space. This is certainly not without its problems, but generally on // Windows when text reaches the end of the terminal it's likely going to be // wrapped. - addonTerminal.on('linefeed', () => { + addonTerminal.onLineFeed(() => { const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1); const lastChar = line.get(addonTerminal.cols - 1); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 181beeeb..60b275f3 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -16,6 +16,7 @@ export class Terminal implements ITerminalApi { this._core = new TerminalCore(options); } + public get onCursorMove(): IEvent { return this._core.onCursorMove; } public get onLineFeed(): IEvent { return this._core.onLineFeed; } public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } public get onInput(): IEvent { return this._core.onInput; } diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index 9d525fbf..2e23fe70 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -8,7 +8,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuff import { ICircularList, XtermListener } from '../common/Types'; import { Buffer } from '../Buffer'; import * as Browser from '../core/Platform'; -import { ITheme, IDisposable, IMarker } from 'xterm'; +import { ITheme, IDisposable, IMarker, IEvent } from 'xterm'; import { Terminal } from '../Terminal'; export class TestTerminal extends Terminal { @@ -19,6 +19,15 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { + onCursorMove: IEvent; + onLineFeed: IEvent; + onSelectionChange: IEvent; + onInput: IEvent; + onTitleChange: IEvent; + onScroll: IEvent; + onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>; + onRender: IEvent<{ start: number; end: number; }>; + onResize: IEvent<{ cols: number; rows: number; }>; markers: IMarker[]; addMarker(cursorYOffset: number): IMarker { throw new Error('Method not implemented.'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0a29cfc8..e51c1167 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -361,6 +361,12 @@ declare module 'xterm' { */ constructor(options?: ITerminalOptions); + /** + * Adds an event listener for the cursor moves. + * @returns an `IDisposable` to stop listening. + */ + onCursorMove: IEvent; + /** * Adds an event listener for when a line feed is added. * @returns an `IDisposable` to stop listening. From f8fb4da9f49585389f53432ed1b0eaaf66f1fa10 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:30:47 -0700 Subject: [PATCH 04/25] Replace EventEmitter with EventEmitter2 in CircularList --- src/Buffer.test.ts | 4 ++-- src/Buffer.ts | 14 +++++++------- src/BufferReflow.ts | 6 +++--- src/SelectionManager.ts | 14 ++++++++------ src/common/CircularList.ts | 22 ++++++++++++++-------- src/common/Types.ts | 11 ++++++++++- 6 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 59475adb..72c07dc7 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -1025,7 +1025,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); - buffer.lines.emit('trim', 1); + buffer.lines.onTrimEmitter.fire(1); assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { @@ -1036,7 +1036,7 @@ describe('Buffer', () => { const marker = buffer.addMarker(0); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.emit('trim', 1); + buffer.lines.onTrimEmitter.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); }); diff --git a/src/Buffer.ts b/src/Buffer.ts index 9cc1adba..790667b6 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { CircularList, IInsertEvent, IDeleteEvent } from './common/CircularList'; +import { CircularList, IInsertEvent } from './common/CircularList'; import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData } from './Types'; import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; @@ -442,7 +442,7 @@ export class Buffer implements IBuffer { insertEvents.push({ index: originalLineIndex + 1, amount: nextToInsert.newLines.length - } as IInsertEvent); + }); countInsertedSoFar += nextToInsert.newLines.length; nextToInsert = toInsert[++nextToInsertIndex]; @@ -455,12 +455,12 @@ export class Buffer implements IBuffer { let insertCountEmitted = 0; for (let i = insertEvents.length - 1; i >= 0; i--) { insertEvents[i].index += insertCountEmitted; - this.lines.emit('insert', insertEvents[i]); + this.lines.onInsertEmitter.fire(insertEvents[i]); insertCountEmitted += insertEvents[i].amount; } const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength); if (amountToTrim > 0) { - this.lines.emitMayRemoveListeners('trim', amountToTrim); + this.lines.onTrimEmitter.fire(amountToTrim); } } } @@ -580,19 +580,19 @@ export class Buffer implements IBuffer { public addMarker(y: number): Marker { const marker = new Marker(y); this.markers.push(marker); - marker.register(this.lines.addDisposableListener('trim', amount => { + marker.register(this.lines.onTrim(amount => { marker.line -= amount; // The marker should be disposed when the line is trimmed from the buffer if (marker.line < 0) { marker.dispose(); } })); - marker.register(this.lines.addDisposableListener('insert', (event: IInsertEvent) => { + marker.register(this.lines.onInsert(event => { if (marker.line >= event.index) { marker.line += event.amount; } })); - marker.register(this.lines.addDisposableListener('delete', (event: IDeleteEvent) => { + marker.register(this.lines.onDelete(event => { // Delete the marker if it's within the range if (marker.line >= event.index && marker.line < event.index + event.amount) { marker.dispose(); diff --git a/src/BufferReflow.ts b/src/BufferReflow.ts index d27d7c48..afe336d0 100644 --- a/src/BufferReflow.ts +++ b/src/BufferReflow.ts @@ -4,7 +4,7 @@ */ import { BufferLine, CellData } from './BufferLine'; -import { CircularList, IDeleteEvent } from './common/CircularList'; +import { CircularList } from './common/CircularList'; import { IBufferLine } from './Types'; import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer'; @@ -120,10 +120,10 @@ export function reflowLargerCreateNewLayout(lines: CircularList, to const countToRemove = toRemove[++nextToRemoveIndex]; // Tell markers that there was a deletion - lines.emit('delete', { + lines.onDeleteEmitter.fire({ index: i - countRemovedSoFar, amount: countToRemove - } as IDeleteEvent); + }); i += countToRemove - 1; countRemovedSoFar += countToRemove; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 361b1123..702f6790 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -4,7 +4,6 @@ */ import { ITerminal, ISelectionManager, IBuffer, IBufferLine } from './Types'; -import { XtermListener } from './common/Types'; import { MouseHelper } from './ui/MouseHelper'; import * as Browser from './core/Platform'; import { CharMeasure } from './ui/CharMeasure'; @@ -12,6 +11,7 @@ import { EventEmitter } from './common/EventEmitter'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from './BufferLine'; +import { IDisposable } from 'xterm'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -102,7 +102,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _mouseMoveListener: EventListener; private _mouseUpListener: EventListener; - private _trimListener: XtermListener; + private _trimListener: IDisposable; private _workCell: CellData = new CellData(); private _mouseDownTimeStamp: number; @@ -134,13 +134,12 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _initListeners(): void { this._mouseMoveListener = event => this._onMouseMove(event); this._mouseUpListener = event => this._onMouseUp(event); - this._trimListener = (amount: number) => this._onTrim(amount); this.initBuffersListeners(); } public initBuffersListeners(): void { - this._terminal.buffer.lines.on('trim', this._trimListener); + this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount)); this._terminal.buffers.on('activate', e => this._onBufferActivate(e)); } @@ -337,6 +336,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param amount The amount the buffer is being trimmed. */ private _onTrim(amount: number): void { + console.log('onTrim', amount); const needsRefresh = this._model.onTrim(amount); if (needsRefresh) { this.refresh(); @@ -657,8 +657,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // 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. - e.inactiveBuffer.lines.off('trim', this._trimListener); - e.activeBuffer.lines.on('trim', this._trimListener); + if (this._trimListener) { + this._trimListener.dispose(); + } + this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount)); } /** diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 90891b72..d4fc41cd 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { EventEmitter } from './EventEmitter'; import { ICircularList } from './Types'; +import { EventEmitter2, IEvent } from './EventEmitter2'; export interface IInsertEvent { index: number; @@ -20,15 +20,21 @@ export interface IDeleteEvent { * Represents a circular list; a list with a maximum size that wraps around when push is called, * overriding values at the start of the list. */ -export class CircularList extends EventEmitter implements ICircularList { +export class CircularList implements ICircularList { protected _array: (T | undefined)[]; private _startIndex: number; private _length: number; + public onDeleteEmitter = new EventEmitter2(); + public get onDelete(): IEvent { return this.onDeleteEmitter.event; } + public onInsertEmitter = new EventEmitter2(); + public get onInsert(): IEvent { return this.onInsertEmitter.event; } + public onTrimEmitter = new EventEmitter2(); + public get onTrim(): IEvent { return this.onTrimEmitter.event; } + constructor( private _maxLength: number ) { - super(); this._array = new Array(this._maxLength); this._startIndex = 0; this._length = 0; @@ -101,7 +107,7 @@ export class CircularList extends EventEmitter implements ICircularList { this._array[this._getCyclicIndex(this._length)] = value; if (this._length === this._maxLength) { this._startIndex = ++this._startIndex % this._maxLength; - this.emitMayRemoveListeners('trim', 1); + this.onTrimEmitter.fire(1); } else { this._length++; } @@ -117,7 +123,7 @@ export class CircularList extends EventEmitter implements ICircularList { throw new Error('Can only recycle when the buffer is full'); } this._startIndex = ++this._startIndex % this._maxLength; - this.emitMayRemoveListeners('trim', 1); + this.onTrimEmitter.fire(1); return this._array[this._getCyclicIndex(this._length - 1)]!; } @@ -167,7 +173,7 @@ export class CircularList extends EventEmitter implements ICircularList { const countToTrim = (this._length + items.length) - this._maxLength; this._startIndex += countToTrim; this._length = this._maxLength; - this.emitMayRemoveListeners('trim', countToTrim); + this.onTrimEmitter.fire(countToTrim); } else { this._length += items.length; } @@ -183,7 +189,7 @@ export class CircularList extends EventEmitter implements ICircularList { } this._startIndex += count; this._length -= count; - this.emitMayRemoveListeners('trim', count); + this.onTrimEmitter.fire(count); } public shiftElements(start: number, count: number, offset: number): void { @@ -207,7 +213,7 @@ export class CircularList extends EventEmitter implements ICircularList { while (this._length > this._maxLength) { this._length--; this._startIndex++; - this.emitMayRemoveListeners('trim', 1); + this.onTrimEmitter.fire(1); } } } else { diff --git a/src/common/Types.ts b/src/common/Types.ts index 8a416bf1..c38b9c16 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -4,6 +4,8 @@ */ import { IEventEmitter } from 'xterm'; +import { IEvent, EventEmitter2 } from './EventEmitter2'; +import { IDeleteEvent, IInsertEvent } from './CircularList'; export type XtermListener = (...args: any[]) => void; @@ -21,11 +23,18 @@ export interface IKeyboardEvent { type: string; } -export interface ICircularList extends IEventEmitter { +export interface ICircularList { length: number; maxLength: number; isFull: boolean; + onDeleteEmitter: EventEmitter2; + onDelete: IEvent; + onInsertEmitter: EventEmitter2; + onInsert: IEvent; + onTrimEmitter: EventEmitter2; + onTrim: IEvent; + get(index: number): T | undefined; set(index: number, value: T): void; push(value: T): void; From 60c2b20d68bdf771b2f2a6c43eab6d5b7b619355 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:35:20 -0700 Subject: [PATCH 05/25] Replace EventEmitter usage in BufferSet --- src/BufferSet.ts | 15 +++++++++------ src/SelectionManager.ts | 3 +-- src/Types.ts | 5 ++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/BufferSet.ts b/src/BufferSet.ts index f84757d1..ffa5bc90 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -3,25 +3,28 @@ * @license MIT */ -import { ITerminal, IBufferSet } from './Types'; +import { ITerminal, IBufferSet, IBuffer } from './Types'; import { Buffer } from './Buffer'; -import { EventEmitter } from './common/EventEmitter'; +import { EventEmitter2, IEvent } from '../lib/common/EventEmitter2'; /** * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and * provides also utilities for working with them. */ -export class BufferSet extends EventEmitter implements IBufferSet { +export class BufferSet implements IBufferSet { private _normal: Buffer; private _alt: Buffer; private _activeBuffer: Buffer; + + private _onBufferActivate = new EventEmitter2<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>(); + public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; } + /** * Create a new BufferSet for the given terminal. * @param _terminal - The terminal the BufferSet will belong to */ constructor(private _terminal: ITerminal) { - super(); this._normal = new Buffer(this._terminal, true); this._normal.fillViewportRows(); @@ -68,7 +71,7 @@ export class BufferSet extends EventEmitter implements IBufferSet { // when activated. this._alt.clear(); this._activeBuffer = this._normal; - this.emit('activate', { + this._onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -87,7 +90,7 @@ export class BufferSet extends EventEmitter implements IBufferSet { this._alt.x = this._normal.x; this._alt.y = this._normal.y; this._activeBuffer = this._alt; - this.emit('activate', { + this._onBufferActivate.fire({ activeBuffer: this._alt, inactiveBuffer: this._normal }); diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 702f6790..789a4bbf 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -140,7 +140,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager public initBuffersListeners(): void { this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount)); - this._terminal.buffers.on('activate', e => this._onBufferActivate(e)); + this._terminal.buffers.onBufferActivate(e => this._onBufferActivate(e)); } /** @@ -336,7 +336,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param amount The amount the buffer is being trimmed. */ private _onTrim(amount: number): void { - console.log('onTrim', amount); const needsRefresh = this._model.onTrim(amount); if (needsRefresh) { this.refresh(); diff --git a/src/Types.ts b/src/Types.ts index 10665f25..a4da0d54 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -8,6 +8,7 @@ import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; import { ICircularList } from './common/Types'; +import { IEvent } from '../lib/common/EventEmitter2'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -302,11 +303,13 @@ export interface IBuffer { getWhitespaceCell(fg?: number, bg?: number): ICellData; } -export interface IBufferSet extends IEventEmitter { +export interface IBufferSet { alt: IBuffer; normal: IBuffer; active: IBuffer; + onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>; + activateNormalBuffer(): void; activateAltBuffer(fillAttr?: number): void; } From 60113fa155f5302ac7c6c9b2e662a6e3eb54269f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:38:55 -0700 Subject: [PATCH 06/25] Replace EventEmitter usage in CharMeasure --- src/Terminal.ts | 2 +- src/Types.ts | 3 +++ src/ui/CharMeasure.ts | 10 ++++++---- src/ui/TestUtils.test.ts | 1 + 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 1f05ec2e..991ede25 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -748,7 +748,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // 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.renderer.onWindowResize(window.devicePixelRatio))); - this.register(this.charMeasure.addDisposableListener('charsizechanged', () => this.renderer.onCharSizeChanged())); + this.register(this.charMeasure.onCharSizeChanged(() => this.renderer.onCharSizeChanged())); this.register(this.renderer.addDisposableListener('resize', (dimensions) => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this.charMeasure); diff --git a/src/Types.ts b/src/Types.ts index a4da0d54..38947af5 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -253,6 +253,9 @@ export interface IMouseHelper { export interface ICharMeasure { width: number; height: number; + + onCharSizeChanged: IEvent; + measure(options: ITerminalOptions): void; } diff --git a/src/ui/CharMeasure.ts b/src/ui/CharMeasure.ts index 0ac755ea..c24e82a0 100644 --- a/src/ui/CharMeasure.ts +++ b/src/ui/CharMeasure.ts @@ -4,22 +4,24 @@ */ import { ICharMeasure, ITerminalOptions } from '../Types'; -import { EventEmitter } from '../common/EventEmitter'; +import { EventEmitter2, IEvent } from '../../lib/common/EventEmitter2'; /** * Utility class that measures the size of a character. Measurements are done in * the DOM rather than with a canvas context because support for extracting the * height of characters is patchy across browsers. */ -export class CharMeasure extends EventEmitter implements ICharMeasure { +export class CharMeasure implements ICharMeasure { private _document: Document; private _parentElement: HTMLElement; private _measureElement: HTMLElement; private _width: number; private _height: number; + private _onCharSizeChanged = new EventEmitter2(); + public get onCharSizeChanged(): IEvent { return this._onCharSizeChanged.event; } + constructor(document: Document, parentElement: HTMLElement) { - super(); this._document = document; this._parentElement = parentElement; this._measureElement = this._document.createElement('span'); @@ -50,7 +52,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure { if (this._width !== geometry.width || this._height !== adjustedHeight) { this._width = geometry.width; this._height = adjustedHeight; - this.emit('charsizechanged'); + this._onCharSizeChanged.fire(); } } } diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index 2e23fe70..0e71f1a3 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -174,6 +174,7 @@ export class MockTerminal implements ITerminal { } export class MockCharMeasure implements ICharMeasure { + onCharSizeChanged: IEvent; width: number; height: number; measure(options: ITerminalOptions): void { From 7b9a718af01dc5edfefae4daae00383c8a19ea03 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 11:40:19 -0700 Subject: [PATCH 07/25] Fix some imports to point at ts files, not lib/ --- src/BufferSet.ts | 2 +- src/Types.ts | 2 +- src/public/Terminal.ts | 2 +- src/ui/CharMeasure.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BufferSet.ts b/src/BufferSet.ts index ffa5bc90..9eb14add 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -5,7 +5,7 @@ import { ITerminal, IBufferSet, IBuffer } from './Types'; import { Buffer } from './Buffer'; -import { EventEmitter2, IEvent } from '../lib/common/EventEmitter2'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and diff --git a/src/Types.ts b/src/Types.ts index 38947af5..c9e51939 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -8,7 +8,7 @@ import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; import { ICircularList } from './common/Types'; -import { IEvent } from '../lib/common/EventEmitter2'; +import { IEvent } from './common/EventEmitter2'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 60b275f3..53c3c693 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -7,7 +7,7 @@ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILink import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; -import { IEvent } from '../../lib/common/EventEmitter2'; +import { IEvent } from '../common/EventEmitter2'; export class Terminal implements ITerminalApi { private _core: ITerminal; diff --git a/src/ui/CharMeasure.ts b/src/ui/CharMeasure.ts index c24e82a0..c37df312 100644 --- a/src/ui/CharMeasure.ts +++ b/src/ui/CharMeasure.ts @@ -4,7 +4,7 @@ */ import { ICharMeasure, ITerminalOptions } from '../Types'; -import { EventEmitter2, IEvent } from '../../lib/common/EventEmitter2'; +import { EventEmitter2, IEvent } from '../common/EventEmitter2'; /** * Utility class that measures the size of a character. Measurements are done in From 4e6f4be6cce7e9b99cc894fbce4b23869dc5ebe3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:14:04 -0400 Subject: [PATCH 08/25] Convert Listener to an interface --- src/common/EventEmitter2.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/EventEmitter2.ts b/src/common/EventEmitter2.ts index a60c5836..71c0b72d 100644 --- a/src/common/EventEmitter2.ts +++ b/src/common/EventEmitter2.ts @@ -5,14 +5,16 @@ import { IDisposable } from './Types'; -type Listener = (e: T) => void; +interface IListener { + (e: T): void; +} export interface IEvent { (listener: (e: T) => any): IDisposable; } export class EventEmitter2 { - private _listeners: Listener[] = []; + private _listeners: IListener[] = []; private _event?: IEvent; public get event(): IEvent { @@ -36,7 +38,7 @@ export class EventEmitter2 { } public fire(data: T): void { - const queue: Listener[] = []; + const queue: IListener[] = []; for (let i = 0; i < this._listeners.length; i++) { queue.push(this._listeners[i]); } From f35dfb496aa46cc95dac6cd484aa5ad034ae78aa Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:19:08 -0400 Subject: [PATCH 09/25] Use onKey in demo --- demo/client.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 7a601898..8a3e4d56 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -165,8 +165,9 @@ function runFakeTerminal(): void { term.writeln(''); term.prompt(); - term._core.register(term.addDisposableListener('key', (key, ev) => { - const printable = !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey; + term.onKey((e: { key: string, domEvent: KeyboardEvent }) => { + const ev = e.domEvent; + const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey; if (ev.keyCode === 13) { term.prompt(); @@ -176,9 +177,9 @@ function runFakeTerminal(): void { term.write('\b \b'); } } else if (printable) { - term.write(key); + term.write(e.key); } - })); + }); term._core.register(term.addDisposableListener('paste', (data, ev) => { term.write(data); From 3f9d90ecf65bf049b233d3fc6776b94c1c4425a8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:23:12 -0400 Subject: [PATCH 10/25] Remove unnecessary paste listener from demo --- demo/client.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 8a3e4d56..f631a917 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -180,10 +180,6 @@ function runFakeTerminal(): void { term.write(e.key); } }); - - term._core.register(term.addDisposableListener('paste', (data, ev) => { - term.write(data); - })); } function initOptions(term: TerminalType): void { From 629238ae700b259ac8fe4e5e6303354648927806 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:25:03 -0400 Subject: [PATCH 11/25] Use onResize in demo --- demo/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index f631a917..c73d81dd 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -86,7 +86,7 @@ function createTerminal(): void { windowsMode: isWindows } as ITerminalOptions); window.term = term; // Expose `term` to window for debugging purposes - term.on('resize', (size: { cols: number, rows: number }) => { + term.onResize((size: { cols: number, rows: number }) => { if (!pid) { return; } From 2f8d49c74df812e8c4a5a90b0f5b44c5d3dcb2d9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Apr 2019 00:45:57 -0400 Subject: [PATCH 12/25] Convert internal key event usages --- src/AccessibilityManager.ts | 2 +- src/Terminal.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 877676c9..a8e0ba1b 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -79,7 +79,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); this.register(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n'))); this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount))); - this.register(this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar))); + this.register(this._terminal.onKey(e => this._onKey(e.key))); this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion())); // TODO: Maybe renderer should fire an event on terminal when the characters change and that // should be listened to instead? That would mean that the order of events are always diff --git a/src/Terminal.ts b/src/Terminal.ts index f6a987f7..5d2e96de 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -266,9 +266,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.on('data', e => this._onInput.fire(e)); this.on('title', e => this._onTitleChange.fire(e)); this.on('scroll', e => this._onScroll.fire(e)); - this.on('key', e => this._onKey.fire(e)); this.on('refresh', e => this._onRender.fire(e)); this.on('resize', e => this._onResize.fire(e)); + + // TODO: Remove these in v4 + // Fire old style events from new emitters + this.onKey(e => this.emit('key', e.key, e.domEvent)); } public dispose(): void { @@ -1615,7 +1618,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } this.emit('keydown', event); - this.emit('key', result.key, event); + this._onKey.fire({ key: result.key, domEvent: event }); this.showCursor(); this.handler(result.key); @@ -1694,7 +1697,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II key = String.fromCharCode(key); this.emit('keypress', key, ev); - this.emit('key', key, ev); + this._onKey.fire({ key, domEvent: ev }); this.showCursor(); this.handler(key); From 625e168e1be9149f01a6ab859c1c43a7cb6336de Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 14:02:11 -0400 Subject: [PATCH 13/25] Convert title and resize events --- src/Terminal.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index cb0e3050..b346e851 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -265,14 +265,14 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.on('linefeed', () => this._onLineFeed.fire()); this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); - this.on('title', e => this._onTitleChange.fire(e)); this.on('scroll', e => this._onScroll.fire(e)); this.on('refresh', e => this._onRender.fire(e)); - this.on('resize', e => this._onResize.fire(e)); // TODO: Remove these in v4 // Fire old style events from new emitters this.onKey(e => this.emit('key', e.key, e.domEvent)); + this.onResize(e => this.emit('resize', e)); + this.onTitleChange(e => this.emit('title', e)); } public dispose(): void { @@ -770,7 +770,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.viewport); this.register(this.addDisposableListener('cursormove', () => this.renderer.onCursorMove())); - this.register(this.addDisposableListener('resize', () => this.renderer.onResize(this.cols, this.rows))); + this.register(this.onResize(() => this.renderer.onResize(this.cols, this.rows))); this.register(this.addDisposableListener('blur', () => this.renderer.onBlur())); this.register(this.addDisposableListener('focus', () => this.renderer.onFocus())); this.register(this.addDisposableListener('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio))); @@ -1778,7 +1778,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } this.refresh(0, this.rows - 1); - this.emit('resize', {cols: x, rows: y}); + this._onResize.fire({ cols: x, rows: y }); } /** @@ -1859,13 +1859,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param title The title to populate in the event. */ public handleTitle(title: string): void { - /** - * This event is emitted when the title of the terminal is changed - * from inside the terminal. The parameter is the new title. - * - * @event title - */ - this.emit('title', title); + this._onTitleChange.fire(title); } /** From 6ff81c43f64eff514f009879f360fa7c277d997a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 14:09:31 -0400 Subject: [PATCH 14/25] Convert marker dispose, selection manager newselection --- src/Buffer.ts | 8 ++++++-- src/SelectionManager.ts | 12 ++++++++---- src/Terminal.ts | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index c1dc2fec..e84908d0 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -10,6 +10,7 @@ import { IMarker } from 'xterm'; import { BufferLine, CellData, AttributeData } from './BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); @@ -615,7 +616,7 @@ export class Buffer implements IBuffer { marker.line -= event.amount; } })); - marker.register(marker.addDisposableListener('dispose', () => this._removeMarker(marker))); + marker.register(marker.onDispose(() => this._removeMarker(marker))); return marker; } @@ -636,6 +637,9 @@ export class Marker extends EventEmitter implements IMarker { public get id(): number { return this._id; } + private _onDispose = new EventEmitter2(); + public get onDispose(): IEvent { return this._onDispose.event; } + constructor( public line: number ) { @@ -648,7 +652,7 @@ export class Marker extends EventEmitter implements IMarker { } this.isDisposed = true; // Emit before super.dispose such that dispose listeners get a change to react - this.emit('dispose'); + this._onDispose.fire(); super.dispose(); } } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 366dc767..54499428 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -12,6 +12,7 @@ import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from './BufferLine'; import { IDisposable } from 'xterm'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -107,6 +108,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _mouseDownTimeStamp: number; + private _onNewMouseSelection = new EventEmitter2(); + public get onNewMouseSelection(): IEvent { return this._onNewMouseSelection.event; } + constructor( private _terminal: ITerminal, private _charMeasure: CharMeasure @@ -244,10 +248,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager /** * Queues a refresh, redrawing the selection on the next opportunity. - * @param isNewSelection Whether the selection should be registered as a new + * @param isNewMouseSelection Whether the selection should be registered as a new * selection on Linux. */ - public refresh(isNewSelection?: boolean): void { + public refresh(isNewMouseSelection?: boolean): void { // Queue the refresh for the renderer if (!this._refreshAnimationFrame) { this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh()); @@ -255,10 +259,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If the platform is Linux and the refresh call comes from a mouse event, // we need to update the selection for middle click to paste selection. - if (Browser.isLinux && isNewSelection) { + if (Browser.isLinux && isNewMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { - this.emit('newselection', this.selectionText); + this._onNewMouseSelection.fire(this.selectionText); } } } diff --git a/src/Terminal.ts b/src/Terminal.ts index b346e851..c92f5da2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -783,7 +783,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.selectionManager = new SelectionManager(this, this.charMeasure); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.addDisposableListener('refresh', data => this.renderer.onSelectionChanged(data.start, data.end, data.columnSelectMode))); - this.register(this.selectionManager.addDisposableListener('newselection', text => { + this.register(this.selectionManager.onNewMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired // only on Linux to enable middle click to paste selection. From 8e5d372b81141cbe1af2c3ca842dec52ae16ed4a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:31:59 -0700 Subject: [PATCH 15/25] Convert cursormove to EventEmitter2 --- src/InputHandler.ts | 6 +++++- src/Terminal.test.ts | 10 ++++++++++ src/Terminal.ts | 6 ++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 95eab4a3..d0471286 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -15,6 +15,7 @@ import { Disposable } from './common/Lifecycle'; import { concat } from './common/TypedArrayUtils'; import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder'; import { CellData, Attributes, FgFlags, BgFlags, AttributeData } from './BufferLine'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * Map collect to glevel. Used in `selectCharset`. @@ -106,6 +107,9 @@ export class InputHandler extends Disposable implements IInputHandler { private _stringDecoder: StringToUtf32 = new StringToUtf32(); private _workCell: CellData = new CellData(); + private _onCursorMove = new EventEmitter2(); + public get onCursorMove(): IEvent { return this._onCursorMove.event; } + constructor( protected _terminal: IInputHandlingTerminal, private _parser: IEscapeSequenceParser = new EscapeSequenceParser()) @@ -305,7 +309,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer = this._terminal.buffer; if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { - this._terminal.emit('cursormove'); + this._onCursorMove.fire(); } } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index f4b717d8..2422c038 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -85,6 +85,16 @@ describe('term.js addons', () => { }); }); + describe('cursormove', () => { + it('should emit a cursormove event', (done) => { + term.on('cursormove', () => { + done(); + }); + + term.write('foo'); + }); + }); + describe(`keypress (including 'key' event)`, () => { it('should receive a string and event object', (done) => { let steps = 0; diff --git a/src/Terminal.ts b/src/Terminal.ts index c92f5da2..64178c74 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -261,7 +261,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setup(); // TODO: Replace EventEmitter with EventEmitter2 internally - this.on('cursormove', () => this._onCursorMove.fire()); this.on('linefeed', () => this._onLineFeed.fire()); this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); @@ -270,6 +269,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // TODO: Remove these in v4 // Fire old style events from new emitters + this.onCursorMove(() => this.emit('cursormove')); this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onResize(e => this.emit('resize', e)); this.onTitleChange(e => this.emit('title', e)); @@ -350,7 +350,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._userScrolling = false; this._inputHandler = new InputHandler(this); + this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this.register(this._inputHandler); + // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -769,7 +771,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.viewport.onThemeChanged(this.renderer.colorManager.colors); this.register(this.viewport); - this.register(this.addDisposableListener('cursormove', () => this.renderer.onCursorMove())); + this.register(this.onCursorMove(() => this.renderer.onCursorMove())); this.register(this.onResize(() => this.renderer.onResize(this.cols, this.rows))); this.register(this.addDisposableListener('blur', () => this.renderer.onBlur())); this.register(this.addDisposableListener('focus', () => this.renderer.onFocus())); From f111aa27dc5a076402ee4d82153343c11c07b615 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:35:41 -0700 Subject: [PATCH 16/25] Convert linefeed to EventEmitter2 --- src/AccessibilityManager.ts | 2 +- src/InputHandler.ts | 10 ++++------ src/Terminal.test.ts | 12 +++++++++++- src/Terminal.ts | 4 +++- src/WindowsMode.ts | 2 +- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index a8e0ba1b..866dc3c7 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -77,7 +77,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); - this.register(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n'))); + this.register(this._terminal.onLineFeed(() => this._onChar('\n'))); this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount))); this.register(this._terminal.onKey(e => this._onKey(e.key))); this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion())); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d0471286..da167e26 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -109,6 +109,8 @@ export class InputHandler extends Disposable implements IInputHandler { private _onCursorMove = new EventEmitter2(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } + private _onLineFeed = new EventEmitter2(); + public get onLineFeed(): IEvent { return this._onLineFeed.event; } constructor( protected _terminal: IInputHandlingTerminal, @@ -458,12 +460,8 @@ export class InputHandler extends Disposable implements IInputHandler { if (buffer.x >= this._terminal.cols) { buffer.x--; } - /** - * This event is emitted whenever the terminal outputs a LF or NL. - * - * @event linefeed - */ - this._terminal.emit('linefeed'); + + this._onLineFeed.fire(); } /** diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 2422c038..b44baa5b 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -17,7 +17,7 @@ class TestTerminal extends Terminal { public keyPress(ev: any): boolean { return this._keyPress(ev); } } -describe('term.js addons', () => { +describe('xterm.js', () => { let term: TestTerminal; const termOptions = { cols: INIT_COLS, @@ -95,6 +95,16 @@ describe('term.js addons', () => { }); }); + describe('linefeed', () => { + it('should emit a linefeed event', (done) => { + term.on('linefeed', () => { + done(); + }); + + term.write('\n'); + }); + }); + describe(`keypress (including 'key' event)`, () => { it('should receive a string and event object', (done) => { let steps = 0; diff --git a/src/Terminal.ts b/src/Terminal.ts index 64178c74..662ce7eb 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -261,7 +261,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setup(); // TODO: Replace EventEmitter with EventEmitter2 internally - this.on('linefeed', () => this._onLineFeed.fire()); this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); this.on('scroll', e => this._onScroll.fire(e)); @@ -271,6 +270,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Fire old style events from new emitters this.onCursorMove(() => this.emit('cursormove')); this.onKey(e => this.emit('key', e.key, e.domEvent)); + this.onLineFeed(() => this.emit('linefeed')); this.onResize(e => this.emit('resize', e)); this.onTitleChange(e => this.emit('title', e)); } @@ -349,8 +349,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // this._writeStopped = false; this._userScrolling = false; + // Register input handler and refire/handle events this._inputHandler = new InputHandler(this); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); + this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); // Reuse renderer if the Terminal is being recreated via a reset call. diff --git a/src/WindowsMode.ts b/src/WindowsMode.ts index 33a9bed5..ac1d193e 100644 --- a/src/WindowsMode.ts +++ b/src/WindowsMode.ts @@ -18,7 +18,7 @@ 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.addDisposableListener('linefeed', () => { + return terminal.onLineFeed(() => { const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1); const lastChar = line.get(terminal.cols - 1); From 2c96c262f4a520b6e7c967d01a961670ccf2dc7d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:42:31 -0700 Subject: [PATCH 17/25] Convert scroll to EventEmitter2 --- src/AccessibilityManager.ts | 2 +- src/InputHandler.ts | 4 +++- src/Terminal.test.ts | 18 ++++++++++++++++-- src/Terminal.ts | 18 ++++++------------ 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 866dc3c7..aeba5d8b 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -74,7 +74,7 @@ export class AccessibilityManager extends Disposable { this.register(this._renderRowsDebouncer); this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.rows))); this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); - this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); + this.register(this._terminal.onScroll(() => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); this.register(this._terminal.onLineFeed(() => this._onChar('\n'))); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index da167e26..c77b1c60 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -111,6 +111,8 @@ export class InputHandler extends Disposable implements IInputHandler { public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter2(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } + private _onScroll = new EventEmitter2(); + public get onScroll(): IEvent { return this._onScroll.event; } constructor( protected _terminal: IInputHandlingTerminal, @@ -764,7 +766,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0); this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0); // Force a scroll event to refresh viewport - this._terminal.emit('scroll', 0); + this._onScroll.fire(0); } break; } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index b44baa5b..b4818347 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -90,7 +90,6 @@ describe('xterm.js', () => { term.on('cursormove', () => { done(); }); - term.write('foo'); }); }); @@ -100,11 +99,26 @@ describe('xterm.js', () => { term.on('linefeed', () => { done(); }); - term.write('\n'); }); }); + describe('scroll', () => { + it('should emit a scroll event when scrollback is created', (done) => { + term.on('scroll', () => { + done(); + }); + term.write('\n'.repeat(INIT_ROWS)); + }); + it('should emit a scroll event when scrollback is cleared', (done) => { + term.write('\n'.repeat(INIT_ROWS)); + term.on('scroll', () => { + done(); + }); + term.clear(); + }); + }); + describe(`keypress (including 'key' event)`, () => { it('should receive a string and event object', (done) => { let steps = 0; diff --git a/src/Terminal.ts b/src/Terminal.ts index 662ce7eb..dfdb8569 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -263,7 +263,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // TODO: Replace EventEmitter with EventEmitter2 internally this.on('selection', () => this._onSelectionChange.fire()); this.on('data', e => this._onInput.fire(e)); - this.on('scroll', e => this._onScroll.fire(e)); this.on('refresh', e => this._onRender.fire(e)); // TODO: Remove these in v4 @@ -272,6 +271,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onLineFeed(() => this.emit('linefeed')); this.onResize(e => this.emit('resize', e)); + this.onScroll(e => this.emit('scroll', e)); this.onTitleChange(e => this.emit('title', e)); } @@ -740,7 +740,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._mouseZoneManager = new MouseZoneManager(this); this.register(this._mouseZoneManager); - this.register(this.addDisposableListener('scroll', () => this._mouseZoneManager.clearAll())); + this.register(this.onScroll(() => this._mouseZoneManager.clearAll())); this.linkifier.attachToDom(this._mouseZoneManager); this.textarea = document.createElement('textarea'); @@ -795,7 +795,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.textarea.focus(); this.textarea.select(); })); - this.register(this.addDisposableListener('scroll', () => { + this.register(this.onScroll(() => { this.viewport.syncScrollArea(); this.selectionManager.refresh(); })); @@ -1302,13 +1302,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.updateRange(this.buffer.scrollTop); this.updateRange(this.buffer.scrollBottom); - /** - * This event is emitted whenever the terminal is scrolled. - * The one parameter passed is the new y display position. - * - * @event scroll - */ - this.emit('scroll', this.buffer.ydisp); + this._onScroll.fire(this.buffer.ydisp); } /** @@ -1337,7 +1331,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } if (!suppressScrollEvent) { - this.emit('scroll', this.buffer.ydisp); + this._onScroll.fire(this.buffer.ydisp); } this.refresh(0, this.rows - 1); @@ -1825,7 +1819,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } this.refresh(0, this.rows - 1); - this.emit('scroll', this.buffer.ydisp); + this._onScroll.fire(this.buffer.ydisp); } /** From 87328fe29365a5b9fc2a64232c3e857eb4a01923 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:53:02 -0700 Subject: [PATCH 18/25] Convert data to EventEmitter2 --- src/InputHandler.ts | 8 +++++--- src/Terminal.ts | 28 ++++++++++++++-------------- src/Types.ts | 4 ---- src/addons/attach/Interfaces.ts | 1 + src/addons/attach/attach.ts | 6 ++++-- src/addons/terminado/Interfaces.ts | 7 ++++++- src/addons/terminado/terminado.ts | 7 ++++--- src/public/Terminal.ts | 2 +- src/ui/TestUtils.test.ts | 2 +- typings/xterm.d.ts | 4 ++-- 10 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c77b1c60..fe8ab53d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -109,6 +109,8 @@ export class InputHandler extends Disposable implements IInputHandler { private _onCursorMove = new EventEmitter2(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } + private _onData = new EventEmitter2(); + public get onData(): IEvent { return this._onData.event; } private _onLineFeed = new EventEmitter2(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onScroll = new EventEmitter2(); @@ -1725,13 +1727,13 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params[0]) { case 5: // status report - this._terminal.emit('data', `${C0.ESC}[0n`); + this._onData.fire(`${C0.ESC}[0n`); break; case 6: // cursor position const y = this._terminal.buffer.y + 1; const x = this._terminal.buffer.x + 1; - this._terminal.emit('data', `${C0.ESC}[${y};${x}R`); + this._onData.fire(`${C0.ESC}[${y};${x}R`); break; } } else if (collect === '?') { @@ -1742,7 +1744,7 @@ export class InputHandler extends Disposable implements IInputHandler { // cursor position const y = this._terminal.buffer.y + 1; const x = this._terminal.buffer.x + 1; - this._terminal.emit('data', `${C0.ESC}[?${y};${x}R`); + this._onData.fire(`${C0.ESC}[?${y};${x}R`); break; case 15: // no printer diff --git a/src/Terminal.ts b/src/Terminal.ts index dfdb8569..a52da3d5 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -224,22 +224,22 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _onCursorMove = new EventEmitter2(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } - private _onLineFeed = new EventEmitter2(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } - private _onSelectionChange = new EventEmitter2(); - public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } - private _onInput = new EventEmitter2(); - public get onInput(): IEvent { return this._onInput.event; } - private _onTitleChange = new EventEmitter2(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } - private _onScroll = new EventEmitter2(); - public get onScroll(): IEvent { return this._onScroll.event; } + private _onData = new EventEmitter2(); + public get onData(): IEvent { return this._onData.event; } private _onKey = new EventEmitter2<{ key: string, domEvent: KeyboardEvent }>(); public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } + private _onLineFeed = new EventEmitter2(); + public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } private _onResize = new EventEmitter2<{ cols: number, rows: number }>(); public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + private _onScroll = new EventEmitter2(); + public get onScroll(): IEvent { return this._onScroll.event; } + private _onSelectionChange = new EventEmitter2(); + public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + private _onTitleChange = new EventEmitter2(); + public get onTitleChange(): IEvent { return this._onTitleChange.event; } /** * Creates a new `Terminal` object. @@ -262,12 +262,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // TODO: Replace EventEmitter with EventEmitter2 internally this.on('selection', () => this._onSelectionChange.fire()); - this.on('data', e => this._onInput.fire(e)); this.on('refresh', e => this._onRender.fire(e)); // TODO: Remove these in v4 // Fire old style events from new emitters this.onCursorMove(() => this.emit('cursormove')); + this.onData(e => this.emit('data', e)); this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onLineFeed(() => this.emit('linefeed')); this.onResize(e => this.emit('resize', e)); @@ -313,7 +313,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.rows = Math.max(this.options.rows, MINIMUM_ROWS); if (this.options.handler) { - this.on('data', this.options.handler); + this.onData(this.options.handler); } this.cursorState = 0; @@ -1831,7 +1831,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } /** - * Emit the 'data' event and populate the given data. + * Emit the data event and populate the given data. * @param data The data to populate in the event. */ public handler(data: string): void { @@ -1849,7 +1849,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(); } - this.emit('data', data); + this._onData.fire(data); } /** diff --git a/src/Types.ts b/src/Types.ts index 0fcf291e..ef99f147 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -221,10 +221,6 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce bracketedPasteMode: boolean; applicationCursor: boolean; - /** - * Emit the 'data' event and populate the given data. - * @param data The data to populate in the event. - */ handler(data: string): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; cancel(ev: Event, force?: boolean): boolean | void; diff --git a/src/addons/attach/Interfaces.ts b/src/addons/attach/Interfaces.ts index ab5846f5..4b269099 100644 --- a/src/addons/attach/Interfaces.ts +++ b/src/addons/attach/Interfaces.ts @@ -14,6 +14,7 @@ export interface IAttachAddonTerminal extends Terminal { __socket?: WebSocket; __attachSocketBuffer?: string; + __dataListener?: IDisposable; __getMessage?(ev: MessageEvent): void; __flushBuffer?(): void; diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts index f121e2e2..2c8a5d4d 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -90,7 +90,8 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage)); if (bidirectional) { - addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData)); + addonTerminal.__dataListener = addonTerminal.onData(addonTerminal.__sendData); + addonTerminal._core.register(addonTerminal.__dataListener); } addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket))); @@ -119,7 +120,8 @@ function addSocketListener(socket: WebSocket, type: string, handler: (this: WebS */ export function detach(term: Terminal, socket: WebSocket): void { const addonTerminal = term; - addonTerminal.off('data', addonTerminal.__sendData); + addonTerminal.__dataListener.dispose(); + addonTerminal.__dataListener = undefined; socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket; diff --git a/src/addons/terminado/Interfaces.ts b/src/addons/terminado/Interfaces.ts index 8f17b0cc..dd7b045c 100644 --- a/src/addons/terminado/Interfaces.ts +++ b/src/addons/terminado/Interfaces.ts @@ -5,11 +5,16 @@ * Implements the attach method, that attaches the terminal to a WebSocket stream. */ -import { Terminal } from 'xterm'; +import { Terminal, IDisposable } from 'xterm'; export interface ITerminadoAddonTerminal extends Terminal { + _core: { + register(d: T): void; + }; + __socket?: WebSocket; __attachSocketBuffer?: string; + __dataListener?: IDisposable; __getMessage?(ev: MessageEvent): void; __flushBuffer?(): void; diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts index b36c2cbf..cefa8087 100644 --- a/src/addons/terminado/terminado.ts +++ b/src/addons/terminado/terminado.ts @@ -59,9 +59,9 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional socket.addEventListener('message', addonTerminal.__getMessage); if (bidirectional) { - addonTerminal.onInput(addonTerminal.__sendData); + addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData); } - addonTerminal.onResize(addonTerminal.__setSize); + addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize)); socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket)); socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket)); @@ -75,7 +75,8 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional */ export function terminadoDetach(term: Terminal, socket: WebSocket): void { const addonTerminal = term; - addonTerminal.off('data', addonTerminal.__sendData); + addonTerminal.__dataListener.dispose(); + addonTerminal.__dataListener = undefined; socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 53c3c693..d05a4f10 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -19,7 +19,7 @@ export class Terminal implements ITerminalApi { public get onCursorMove(): IEvent { return this._core.onCursorMove; } public get onLineFeed(): IEvent { return this._core.onLineFeed; } public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } - public get onInput(): IEvent { return this._core.onInput; } + public get onData(): IEvent { return this._core.onData; } 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/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index ddf8e2c8..ae57d1f9 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -23,7 +23,7 @@ export class MockTerminal implements ITerminal { onCursorMove: IEvent; onLineFeed: IEvent; onSelectionChange: IEvent; - onInput: IEvent; + onData: IEvent; onTitleChange: IEvent; onScroll: IEvent; onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f8bbdbdb..2c4bb2ec 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -392,13 +392,13 @@ declare module 'xterm' { onSelectionChange: IEvent; /** - * Adds an event listener for when an input event fires. This happens for + * Adds an event listener for when a data event fires. This happens for * example when the user types or pastes into the terminal. The event value * is whatever `string` results, in a typical setup, this should be passed * on to the backing pty. * @returns an `IDisposable` to stop listening. */ - onInput: IEvent; + onData: IEvent; /** * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. From db78b75e8bd6a88b870daa5b1fa35a990afef476 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 19:55:33 -0700 Subject: [PATCH 19/25] Convert selection to EventEmitter2 --- src/SelectionManager.ts | 14 ++++++++------ src/Terminal.ts | 5 +++-- src/addons/terminado/terminado.ts | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 54499428..087e8995 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -108,8 +108,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _mouseDownTimeStamp: number; - private _onNewMouseSelection = new EventEmitter2(); - public get onNewMouseSelection(): IEvent { return this._onNewMouseSelection.event; } + private _onLinuxMouseSelection = new EventEmitter2(); + public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } + private _onSelectionChange = new EventEmitter2(); + public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } constructor( private _terminal: ITerminal, @@ -262,7 +264,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager if (Browser.isLinux && isNewMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { - this._onNewMouseSelection.fire(this.selectionText); + this._onLinuxMouseSelection.fire(this.selectionText); } } } @@ -322,7 +324,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager public selectAll(): void { this._model.isSelectAllActive = true; this.refresh(); - this._terminal.emit('selection'); + this._onSelectionChange.fire(); } public selectLines(start: number, end: number): void { @@ -332,7 +334,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this._model.selectionStart = [0, start]; this._model.selectionEnd = [this._terminal.cols, end]; this.refresh(); - this._terminal.emit('selection'); + this._onSelectionChange.fire(); } /** @@ -650,7 +652,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { (new AltClickHandler(event, this._terminal)).move(); } else if (this.hasSelection) { - this._terminal.emit('selection'); + this._onSelectionChange.fire(); } } diff --git a/src/Terminal.ts b/src/Terminal.ts index a52da3d5..59f675f3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -261,7 +261,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setup(); // TODO: Replace EventEmitter with EventEmitter2 internally - this.on('selection', () => this._onSelectionChange.fire()); this.on('refresh', e => this._onRender.fire(e)); // TODO: Remove these in v4 @@ -271,6 +270,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onLineFeed(() => this.emit('linefeed')); this.onResize(e => this.emit('resize', e)); + this.onSelectionChange(() => this.emit('selection')); this.onScroll(e => this.emit('scroll', e)); this.onTitleChange(e => this.emit('title', e)); } @@ -785,9 +785,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.renderer.addDisposableListener('resize', (dimensions) => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this.charMeasure); + this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.addDisposableListener('refresh', data => this.renderer.onSelectionChanged(data.start, data.end, data.columnSelectMode))); - this.register(this.selectionManager.onNewMouseSelection(text => { + this.register(this.selectionManager.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired // only on Linux to enable middle click to paste selection. diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts index cefa8087..9895a07b 100644 --- a/src/addons/terminado/terminado.ts +++ b/src/addons/terminado/terminado.ts @@ -59,7 +59,7 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional socket.addEventListener('message', addonTerminal.__getMessage); if (bidirectional) { - addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData); + addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData)); } addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize)); From 290bde616331026e1a591b341b4da249b239d0f0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:16:26 -0700 Subject: [PATCH 20/25] Convert refresh to EventEmitter2 --- src/Terminal.ts | 9 ++++----- src/renderer/Renderer.ts | 6 +++++- src/renderer/Types.ts | 3 +++ src/renderer/dom/DomRenderer.ts | 6 +++++- src/ui/TestUtils.test.ts | 1 + 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 59f675f3..4a667387 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -260,15 +260,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.options = clone(options); this._setup(); - // TODO: Replace EventEmitter with EventEmitter2 internally - this.on('refresh', e => this._onRender.fire(e)); - // TODO: Remove these in v4 // Fire old style events from new emitters this.onCursorMove(() => this.emit('cursormove')); this.onData(e => this.emit('data', e)); this.onKey(e => this.emit('key', e.key, e.domEvent)); this.onLineFeed(() => this.emit('linefeed')); + this.onRender(e => this.emit('refresh', e)); this.onResize(e => this.emit('resize', e)); this.onSelectionChange(() => this.emit('selection')); this.onScroll(e => this.emit('scroll', e)); @@ -687,8 +685,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(addDisposableDomListener(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart())); this.register(addDisposableDomListener(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e))); this.register(addDisposableDomListener(this.textarea, 'compositionend', () => this._compositionHelper.compositionend())); - this.register(this.addDisposableListener('refresh', () => this._compositionHelper.updateCompositionElements())); - this.register(this.addDisposableListener('refresh', (data) => this._queueLinkification(data.start, data.end))); + this.register(this.onRender(() => this._compositionHelper.updateCompositionElements())); + this.register(this.onRender(e => this._queueLinkification(e.start, e.end))); } /** @@ -838,6 +836,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } + this.renderer.onRender(e => this._onRender.fire(e)); this.register(this.renderer); } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2c1b516a..b7ad6854 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -15,6 +15,7 @@ import { RenderDebouncer } from '../ui/RenderDebouncer'; import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; import { ITheme } from 'xterm'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; +import { EventEmitter2, IEvent } from '../common/EventEmitter2'; export class Renderer extends EventEmitter implements IRenderer { private _renderDebouncer: RenderDebouncer; @@ -29,6 +30,9 @@ export class Renderer extends EventEmitter implements IRenderer { public colorManager: ColorManager; public dimensions: IRenderDimensions; + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + constructor(private _terminal: ITerminal, theme: ITheme) { super(); const allowTransparency = this._terminal.options.allowTransparency; @@ -197,7 +201,7 @@ export class Renderer extends EventEmitter implements IRenderer { */ private _renderRows(start: number, end: number): void { this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); - this._terminal.emit('refresh', { start, end }); + this._onRender.fire({ start, end }); } /** diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index f2271f95..71e5fc30 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -5,6 +5,7 @@ import { ITerminal, CharacterJoinerHandler } from '../Types'; import { IEventEmitter, ITheme, IDisposable } from 'xterm'; +import { IEvent } from '../common/EventEmitter2'; /** * Flags used to render terminal text properly. @@ -27,6 +28,8 @@ export interface IRenderer extends IEventEmitter, IDisposable { dimensions: IRenderDimensions; colorManager: IColorManager; + onRender: IEvent<{ start: number, end: number }>; + dispose(): void; setTheme(theme: ITheme): IColorSet; onWindowResize(devicePixelRatio: number): void; diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 78ccc620..0f70e93b 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -11,6 +11,7 @@ import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -43,6 +44,9 @@ export class DomRenderer extends EventEmitter implements IRenderer { public dimensions: IRenderDimensions; public colorManager: ColorManager; + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + constructor(private _terminal: ITerminal, theme: ITheme | undefined) { super(); const allowTransparency = this._terminal.options.allowTransparency; @@ -350,7 +354,7 @@ export class DomRenderer extends EventEmitter implements IRenderer { rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols)); } - this._terminal.emit('refresh', {start, end}); + this._onRender.fire({ start, end }); } private get _terminalSelector(): string { diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index ae57d1f9..9488a927 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -354,6 +354,7 @@ export class MockBuffer implements IBuffer { } export class MockRenderer implements IRenderer { + onRender: IEvent<{ start: number; end: number; }>; dispose(): void { throw new Error('Method not implemented.'); } From 2e0e24c382f623dc76759ce86a25395ec106769e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:26:18 -0700 Subject: [PATCH 21/25] Remove extends EventEmitter from SelectinoManager --- src/AccessibilityManager.ts | 4 ++-- src/SelectionManager.ts | 21 ++++++++++----------- src/Terminal.ts | 2 +- src/Types.ts | 6 ++++++ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index aeba5d8b..d72f6647 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -72,8 +72,8 @@ export class AccessibilityManager extends Disposable { this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot); this.register(this._renderRowsDebouncer); - this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.rows))); - this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); + this.register(this._terminal.onResize(e => this._onResize(e.rows))); + this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end))); this.register(this._terminal.onScroll(() => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 087e8995..eed206cc 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,11 +3,10 @@ * @license MIT */ -import { ITerminal, ISelectionManager, IBuffer, IBufferLine } from './Types'; +import { ITerminal, ISelectionManager, IBuffer, IBufferLine, ISelectionRedrawRequestEvent } from './Types'; import { MouseHelper } from './ui/MouseHelper'; import * as Browser from './common/Platform'; import { CharMeasure } from './ui/CharMeasure'; -import { EventEmitter } from './common/EventEmitter'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from './BufferLine'; @@ -68,10 +67,10 @@ export const enum SelectionMode { * SelectionModel, SelectionManager handles with all logic associated with * dealing with the selection, including handling mouse interaction, wide * characters and fetching the actual text within the selection. Rendering is - * not handled by the SelectionManager but a 'refresh' event is fired when the - * selection is ready to be redrawn. + * not handled by the SelectionManager but the onRedrawRequest event is fired + * when the selection is ready to be redrawn (on an animation frame). */ -export class SelectionManager extends EventEmitter implements ISelectionManager { +export class SelectionManager implements ISelectionManager { protected _model: SelectionModel; /** @@ -110,6 +109,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _onLinuxMouseSelection = new EventEmitter2(); public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } + private _onRedrawRequest = new EventEmitter2(); + public get onRedrawRequest(): IEvent { return this._onRedrawRequest.event; } private _onSelectionChange = new EventEmitter2(); public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } @@ -117,7 +118,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _terminal: ITerminal, private _charMeasure: CharMeasure ) { - super(); this._initListeners(); this.enable(); @@ -126,7 +126,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager } public dispose(): void { - super.dispose(); this._removeMouseDownListeners(); } @@ -250,10 +249,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager /** * Queues a refresh, redrawing the selection on the next opportunity. - * @param isNewMouseSelection Whether the selection should be registered as a new + * @param isLinuxMouseSelection Whether the selection should be registered as a new * selection on Linux. */ - public refresh(isNewMouseSelection?: boolean): void { + public refresh(isLinuxMouseSelection?: boolean): void { // Queue the refresh for the renderer if (!this._refreshAnimationFrame) { this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh()); @@ -261,7 +260,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If the platform is Linux and the refresh call comes from a mouse event, // we need to update the selection for middle click to paste selection. - if (Browser.isLinux && isNewMouseSelection) { + if (Browser.isLinux && isLinuxMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { this._onLinuxMouseSelection.fire(this.selectionText); @@ -275,7 +274,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager */ private _refresh(): void { this._refreshAnimationFrame = null; - this.emit('refresh', { + this._onRedrawRequest.fire({ start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd, columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN diff --git a/src/Terminal.ts b/src/Terminal.ts index 4a667387..cb2743fd 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -785,7 +785,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.selectionManager = new SelectionManager(this, this.charMeasure); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); - this.register(this.selectionManager.addDisposableListener('refresh', data => this.renderer.onSelectionChanged(data.start, data.end, data.columnSelectMode))); + this.register(this.selectionManager.onRedrawRequest(e => this.renderer.onSelectionChanged(e.start, e.end, e.columnSelectMode))); this.register(this.selectionManager.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired diff --git a/src/Types.ts b/src/Types.ts index ef99f147..be75b0c9 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -324,6 +324,12 @@ export interface ISelectionManager { selectWordAtCursor(event: MouseEvent): void; } +export interface ISelectionRedrawRequestEvent { + start: [number, number]; + end: [number, number]; + columnSelectMode: boolean; +} + export interface ILinkifier extends IEventEmitter { attachToDom(mouseZoneManager: IMouseZoneManager): void; linkifyRows(start: number, end: number): void; From 36567e60df8220374b58d538b63a582cb8279502 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:36:08 -0700 Subject: [PATCH 22/25] Remove extends EventEmitter from more classes --- src/AccessibilityManager.ts | 2 +- src/Buffer.ts | 5 ++--- src/Terminal.ts | 2 +- src/renderer/Renderer.ts | 8 +++++--- src/renderer/Types.ts | 5 +++-- src/renderer/dom/DomRenderer.ts | 10 ++++++++-- src/ui/TestUtils.test.ts | 1 + 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index d72f6647..e2115797 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -85,7 +85,7 @@ export class AccessibilityManager extends Disposable { // should be listened to instead? That would mean that the order of events are always // guarenteed this.register(this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions())); - this.register(this._terminal.renderer.addDisposableListener('resize', () => this._refreshRowsDimensions())); + this.register(this._terminal.renderer.onCanvasResize(() => this._refreshRowsDimensions())); // This shouldn't be needed on modern browsers but is present in case the // media query that drives the dprchange event isn't supported this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions())); diff --git a/src/Buffer.ts b/src/Buffer.ts index ba686238..c1c08c85 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -5,12 +5,12 @@ import { CircularList, IInsertEvent } from './common/CircularList'; import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData, IAttributeData } from './Types'; -import { EventEmitter } from './common/EventEmitter'; import { IMarker } from 'xterm'; import { BufferLine, CellData, AttributeData } from './BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './BufferReflow'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; +import { Disposable } from '../lib/common/Lifecycle'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); @@ -629,7 +629,7 @@ export class Buffer implements IBuffer { } } -export class Marker extends EventEmitter implements IMarker { +export class Marker extends Disposable implements IMarker { private static _nextId = 1; private _id: number = Marker._nextId++; @@ -653,7 +653,6 @@ export class Marker extends EventEmitter implements IMarker { this.isDisposed = true; // Emit before super.dispose such that dispose listeners get a change to react this._onDispose.fire(); - super.dispose(); } } diff --git a/src/Terminal.ts b/src/Terminal.ts index cb2743fd..16ac69b3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -780,7 +780,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // matchMedia query. this.register(addDisposableDomListener(window, 'resize', () => this.renderer.onWindowResize(window.devicePixelRatio))); this.register(this.charMeasure.onCharSizeChanged(() => this.renderer.onCharSizeChanged())); - this.register(this.renderer.addDisposableListener('resize', (dimensions) => this.viewport.syncScrollArea())); + this.register(this.renderer.onCanvasResize(() => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this.charMeasure); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index b7ad6854..2a205be3 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -10,14 +10,14 @@ import { ColorManager } from './ColorManager'; import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { ITerminal, CharacterJoinerHandler } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; -import { EventEmitter } from '../common/EventEmitter'; import { RenderDebouncer } from '../ui/RenderDebouncer'; import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; import { ITheme } from 'xterm'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; import { EventEmitter2, IEvent } from '../common/EventEmitter2'; +import { Disposable } from '../common/Lifecycle'; -export class Renderer extends EventEmitter implements IRenderer { +export class Renderer extends Disposable implements IRenderer { private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; @@ -30,6 +30,8 @@ export class Renderer extends EventEmitter implements IRenderer { public colorManager: ColorManager; public dimensions: IRenderDimensions; + private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); + public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } @@ -138,7 +140,7 @@ export class Renderer extends EventEmitter implements IRenderer { this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; - this.emit('resize', { + this._onCanvasResize.fire({ width: this.dimensions.canvasWidth, height: this.dimensions.canvasHeight }); diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 71e5fc30..9d2ddcc3 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -4,7 +4,7 @@ */ import { ITerminal, CharacterJoinerHandler } from '../Types'; -import { IEventEmitter, ITheme, IDisposable } from 'xterm'; +import { ITheme, IDisposable } from 'xterm'; import { IEvent } from '../common/EventEmitter2'; /** @@ -24,10 +24,11 @@ export const enum FLAGS { * Note that IRenderer implementations should emit the refresh event after * rendering rows to the screen. */ -export interface IRenderer extends IEventEmitter, IDisposable { +export interface IRenderer extends IDisposable { dimensions: IRenderDimensions; colorManager: IColorManager; + onCanvasResize: IEvent<{ width: number, height: number }>; onRender: IEvent<{ start: number, end: number }>; dispose(): void; diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 0f70e93b..c13b6e27 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -6,12 +6,12 @@ import { IRenderer, IRenderDimensions, IColorSet } from '../Types'; import { ILinkHoverEvent, ITerminal, CharacterJoinerHandler, LinkHoverEventTypes } from '../../Types'; import { ITheme } from 'xterm'; -import { EventEmitter } from '../../common/EventEmitter'; import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; +import { Disposable } from '../../common/Lifecycle'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -30,7 +30,7 @@ let nextTerminalId = 1; * particularly fast or feature complete, more just stable and usable for when * canvas is not an option. */ -export class DomRenderer extends EventEmitter implements IRenderer { +export class DomRenderer extends Disposable implements IRenderer { private _renderDebouncer: RenderDebouncer; private _rowFactory: DomRendererRowFactory; private _terminalClass: number = nextTerminalId++; @@ -44,6 +44,8 @@ export class DomRenderer extends EventEmitter implements IRenderer { public dimensions: IRenderDimensions; public colorManager: ColorManager; + private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); + public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } @@ -242,6 +244,10 @@ export class DomRenderer extends EventEmitter implements IRenderer { public onResize(cols: number, rows: number): void { this._refreshRowElements(cols, rows); this._updateDimensions(); + this._onCanvasResize.fire({ + width: this.dimensions.canvasWidth, + height: this.dimensions.canvasHeight + }); } public onCharSizeChanged(): void { diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index 9488a927..5ee0f9d5 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -354,6 +354,7 @@ export class MockBuffer implements IBuffer { } export class MockRenderer implements IRenderer { + onCanvasResize: IEvent<{ width: number; height: number; }>; onRender: IEvent<{ start: number; end: number; }>; dispose(): void { throw new Error('Method not implemented.'); From f4c7e3fd7dbeede76668eabe212bf261afc67a79 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:43:42 -0700 Subject: [PATCH 23/25] Remove extends EventEmitter from Linkifier --- src/Linkifier.ts | 24 +++++++++++++++--------- src/Types.ts | 14 ++++++-------- src/renderer/LinkRenderLayer.ts | 12 ++++++------ src/renderer/dom/DomRenderer.ts | 10 +++++----- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 80399904..8c57e1b2 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -4,15 +4,15 @@ */ import { IMouseZoneManager } from './ui/Types'; -import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types'; +import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types'; import { MouseZone } from './ui/MouseZoneManager'; -import { EventEmitter } from './common/EventEmitter'; import { getStringCellWidth } from './CharWidth'; +import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** * The Linkifier applies links to rows shortly after they have been refreshed. */ -export class Linkifier extends EventEmitter implements ILinkifier { +export class Linkifier implements ILinkifier { /** * The time to wait after a row is changed before it is linkified. This prevents * the costly operation of searching every row multiple times, potentially a @@ -34,10 +34,16 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _nextLinkMatcherId = 0; private _rowsToLinkify: { start: number, end: number }; + private _onLinkHover = new EventEmitter2(); + public get onLinkHover(): IEvent { return this._onLinkHover.event; } + private _onLinkLeave = new EventEmitter2(); + public get onLinkLeave(): IEvent { return this._onLinkLeave.event; } + private _onLinkTooltip = new EventEmitter2(); + public get onLinkTooltip(): IEvent { return this._onLinkTooltip.event; } + constructor( protected _terminal: ITerminal ) { - super(); this._rowsToLinkify = { start: null, end: null @@ -283,18 +289,18 @@ export class Linkifier extends EventEmitter implements ILinkifier { } window.open(uri, '_blank'); }, - e => { - this.emit(LinkHoverEventTypes.HOVER, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + () => { + this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._terminal.element.classList.add('xterm-cursor-pointer'); }, e => { - this.emit(LinkHoverEventTypes.TOOLTIP, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); if (matcher.hoverTooltipCallback) { matcher.hoverTooltipCallback(e, uri); } }, () => { - this.emit(LinkHoverEventTypes.LEAVE, this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + this._onLinkLeave.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); this._terminal.element.classList.remove('xterm-cursor-pointer'); if (matcher.hoverLeaveCallback) { matcher.hoverLeaveCallback(); @@ -309,7 +315,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { )); } - private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkHoverEvent { + private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkifierEvent { return { x1, y1, x2, y2, cols: this._terminal.cols, fg }; } } diff --git a/src/Types.ts b/src/Types.ts index be75b0c9..a70dfdad 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -23,12 +23,6 @@ export type CharacterJoinerHandler = (text: string) => [number, number][]; // BufferIndex denotes a position in the buffer: [rowIndex, colIndex] export type BufferIndex = [number, number]; -export const enum LinkHoverEventTypes { - HOVER = 'linkhover', - TOOLTIP = 'linktooltip', - LEAVE = 'linkleave' -} - /** * This interface encapsulates everything needed from the Terminal by the * InputHandler. This cleanly separates the large amount of methods needed by @@ -194,7 +188,7 @@ export interface ILinkMatcher { willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } -export interface ILinkHoverEvent { +export interface ILinkifierEvent { x1: number; y1: number; x2: number; @@ -330,7 +324,11 @@ export interface ISelectionRedrawRequestEvent { columnSelectMode: boolean; } -export interface ILinkifier extends IEventEmitter { +export interface ILinkifier { + onLinkHover: IEvent; + onLinkLeave: IEvent; + onLinkTooltip: IEvent; + attachToDom(mouseZoneManager: IMouseZoneManager): void; linkifyRows(start: number, end: number): void; registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 855830e4..abb899b0 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -3,19 +3,19 @@ * @license MIT */ -import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, LinkHoverEventTypes } from '../Types'; +import { ILinkifierEvent, ITerminal, ILinkifierAccessor } from '../Types'; import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; export class LinkRenderLayer extends BaseRenderLayer { - private _state: ILinkHoverEvent = null; + private _state: ILinkifierEvent = null; constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); - terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: ILinkHoverEvent) => this._onLinkHover(e)); - terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e)); + terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); + terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } public resize(terminal: ITerminal, dim: IRenderDimensions): void { @@ -40,7 +40,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } } - private _onLinkHover(e: ILinkHoverEvent): void { + private _onLinkHover(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; } else if (is256Color(e.fg)) { @@ -64,7 +64,7 @@ export class LinkRenderLayer extends BaseRenderLayer { this._state = e; } - private _onLinkLeave(e: ILinkHoverEvent): void { + private _onLinkLeave(e: ILinkifierEvent): void { this._clearCurrentLink(); } } diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index c13b6e27..1d879fcd 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions, IColorSet } from '../Types'; -import { ILinkHoverEvent, ITerminal, CharacterJoinerHandler, LinkHoverEventTypes } from '../../Types'; +import { ILinkifierEvent, ITerminal, CharacterJoinerHandler } from '../../Types'; import { ITheme } from 'xterm'; import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; @@ -87,8 +87,8 @@ export class DomRenderer extends Disposable implements IRenderer { this._terminal.screenElement.appendChild(this._rowContainer); this._terminal.screenElement.appendChild(this._selectionContainer); - this._terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: ILinkHoverEvent) => this._onLinkHover(e)); - this._terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e)); + this._terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); + this._terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } public dispose(): void { @@ -370,11 +370,11 @@ export class DomRenderer extends Disposable implements IRenderer { public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; } public deregisterCharacterJoiner(joinerId: number): boolean { return false; } - private _onLinkHover(e: ILinkHoverEvent): void { + private _onLinkHover(e: ILinkifierEvent): void { this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true); } - private _onLinkLeave(e: ILinkHoverEvent): void { + private _onLinkLeave(e: ILinkifierEvent): void { this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false); } From b9bc219eccb1201acd76d0c3cb8b037a7245e133 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 20:52:18 -0700 Subject: [PATCH 24/25] Make events in d.ts alphabetical --- typings/xterm.d.ts | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2c4bb2ec..24a30b25 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -379,18 +379,6 @@ declare module 'xterm' { */ onCursorMove: IEvent; - /** - * Adds an event listener for when a line feed is added. - * @returns an `IDisposable` to stop listening. - */ - onLineFeed: IEvent; - - /** - * Adds an event listener for when a selection change occurs. - * @returns an `IDisposable` to stop listening. - */ - onSelectionChange: IEvent; - /** * Adds an event listener for when a data event fires. This happens for * example when the user types or pastes into the terminal. The event value @@ -401,11 +389,18 @@ declare module 'xterm' { onData: IEvent; /** - * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. - * The event value is the new title. + * Adds an event listener for 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. */ - onTitleChange: IEvent; + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + + /** + * Adds an event listener for when a line feed is added. + * @returns an `IDisposable` to stop listening. + */ + onLineFeed: IEvent; /** * Adds an event listener for when a scroll occurs. The event value is the @@ -415,12 +410,10 @@ declare module 'xterm' { onScroll: IEvent; /** - * Adds an event listener for 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. + * Adds an event listener for when a selection change occurs. * @returns an `IDisposable` to stop listening. */ - onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + onSelectionChange: IEvent; /** * Adds an event listener for when rows are rendered. The event value @@ -437,6 +430,13 @@ declare module 'xterm' { */ onResize: IEvent<{ cols: number, rows: number }>; + /** + * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. + * The event value is the new title. + * @returns an `IDisposable` to stop listening. + */ + onTitleChange: IEvent; + /** * Unfocus the terminal. */ From cf4b59567bbd19852155a7ebee5188dd30763537 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 21:01:55 -0700 Subject: [PATCH 25/25] Add tests for new events --- src/Terminal.test.ts | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index b4818347..dd8e32e1 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -68,6 +68,81 @@ describe('xterm.js', () => { }); }); + describe('events', () => { + it('should fire the onData evnet', (done) => { + term.onData(() => done()); + term.handler('fake'); + }); + it('should fire the onCursorMove event', (done) => { + term.on('cursormove', () => done()); + term.write('foo'); + }); + it('should fire the onLineFeed event', (done) => { + term.on('linefeed', () => done()); + term.write('\n'); + }); + it('should fire a scroll event when scrollback is created', (done) => { + term.on('scroll', () => done()); + term.write('\n'.repeat(INIT_ROWS)); + }); + it('should fire a scroll event when scrollback is cleared', (done) => { + term.write('\n'.repeat(INIT_ROWS)); + term.on('scroll', () => done()); + term.clear(); + }); + it('should fire a key event after a keypress DOM event', (done) => { + term.onKey(e => { + assert.equal(typeof e.key, 'string'); + expect(e.domEvent).to.be.an.instanceof(Object); + done(); + }); + const evKeyPress = { + preventDefault: () => { }, + stopPropagation: () => { }, + type: 'keypress', + keyCode: 13 + }; + term.keyPress(evKeyPress); + }); + it('should fire a key event after a keydown DOM event', (done) => { + term.onKey(e => { + assert.equal(typeof e.key, 'string'); + expect(e.domEvent).to.be.an.instanceof(Object); + done(); + }); + const evKeyDown = { + preventDefault: () => { }, + stopPropagation: () => { }, + type: 'keydown', + keyCode: 13 + }; + term.keyDown(evKeyDown); + }); + it('should fire the onResize event', (done) => { + term.onResize(e => { + expect(e).to.have.keys(['cols', 'rows']); + assert.equal(typeof e.cols, 'number'); + assert.equal(typeof e.rows, 'number'); + done(); + }); + term.resize(1, 1); + }); + it('should fire the onScroll event', (done) => { + term.onScroll(e => { + assert.equal(typeof e, 'number'); + done(); + }); + term.scroll(); + }); + it('should fire the onTitleChange event', (done) => { + term.onTitleChange(e => { + assert.equal(e, 'title'); + done(); + }); + term.handleTitle('title'); + }); + }); + describe('on', () => { beforeEach(() => { term.on('key', () => { });