Merge pull request #2008 from Tyriar/event_emitter_2

Explicit event API
This commit is contained in:
Daniel Imms
2019-04-11 09:08:15 -07:00
committed by GitHub
30 changed files with 569 additions and 170 deletions
+6 -9
View File
@@ -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;
}
@@ -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,13 +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);
}));
});
}
function initOptions(term: TerminalType): void {
+6 -6
View File
@@ -72,20 +72,20 @@ 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.addDisposableListener('scroll', data => this._refreshRows()));
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)));
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.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
// 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()));
+2 -2
View File
@@ -1074,7 +1074,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', () => {
@@ -1085,7 +1085,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);
});
+15 -13
View File
@@ -3,14 +3,14 @@
* @license MIT
*/
import { CircularList, IInsertEvent, IDeleteEvent } from './common/CircularList';
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);
@@ -455,7 +455,7 @@ export class Buffer implements IBuffer {
insertEvents.push({
index: originalLineIndex + 1,
amount: nextToInsert.newLines.length
} as IInsertEvent);
});
countInsertedSoFar += nextToInsert.newLines.length;
nextToInsert = toInsert[++nextToInsertIndex];
@@ -468,12 +468,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);
}
}
}
@@ -593,19 +593,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();
@@ -616,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;
}
@@ -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++;
@@ -637,6 +637,9 @@ export class Marker extends EventEmitter implements IMarker {
public get id(): number { return this._id; }
private _onDispose = new EventEmitter2<void>();
public get onDispose(): IEvent<void> { return this._onDispose.event; }
constructor(
public line: number
) {
@@ -649,8 +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');
super.dispose();
this._onDispose.fire();
}
}
+3 -3
View File
@@ -4,7 +4,7 @@
*/
import { BufferLine } from './BufferLine';
import { CircularList, IDeleteEvent } from './common/CircularList';
import { CircularList } from './common/CircularList';
import { IBufferLine, ICellData } from './Types';
export interface INewLayoutResult {
@@ -118,10 +118,10 @@ export function reflowLargerCreateNewLayout(lines: CircularList<IBufferLine>, 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;
+9 -6
View File
@@ -3,25 +3,28 @@
* @license MIT
*/
import { ITerminal, IBufferSet, IAttributeData } from './Types';
import { ITerminal, IBufferSet, IAttributeData, IBuffer } from './Types';
import { Buffer } from './Buffer';
import { EventEmitter } from './common/EventEmitter';
import { EventEmitter2, IEvent } from './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
});
+17 -11
View File
@@ -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,15 @@ export class InputHandler extends Disposable implements IInputHandler {
private _stringDecoder: StringToUtf32 = new StringToUtf32();
private _workCell: CellData = new CellData();
private _onCursorMove = new EventEmitter2<void>();
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onData = new EventEmitter2<string>();
public get onData(): IEvent<string> { return this._onData.event; }
private _onLineFeed = new EventEmitter2<void>();
public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
private _onScroll = new EventEmitter2<number>();
public get onScroll(): IEvent<number> { return this._onScroll.event; }
constructor(
protected _terminal: IInputHandlingTerminal,
private _parser: IEscapeSequenceParser = new EscapeSequenceParser())
@@ -305,7 +315,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();
}
}
@@ -461,12 +471,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();
}
/**
@@ -769,7 +775,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;
}
@@ -1728,13 +1734,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 === '?') {
@@ -1745,7 +1751,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
+15 -9
View File
@@ -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<ILinkifierEvent>();
public get onLinkHover(): IEvent<ILinkifierEvent> { return this._onLinkHover.event; }
private _onLinkLeave = new EventEmitter2<ILinkifierEvent>();
public get onLinkLeave(): IEvent<ILinkifierEvent> { return this._onLinkLeave.event; }
private _onLinkTooltip = new EventEmitter2<ILinkifierEvent>();
public get onLinkTooltip(): IEvent<ILinkifierEvent> { 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 };
}
}
+28 -22
View File
@@ -3,15 +3,15 @@
* @license MIT
*/
import { ITerminal, ISelectionManager, IBuffer, IBufferLine } from './Types';
import { XtermListener } from './common/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';
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
@@ -67,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;
/**
@@ -102,16 +102,22 @@ 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;
private _onLinuxMouseSelection = new EventEmitter2<string>();
public get onLinuxMouseSelection(): IEvent<string> { return this._onLinuxMouseSelection.event; }
private _onRedrawRequest = new EventEmitter2<ISelectionRedrawRequestEvent>();
public get onRedrawRequest(): IEvent<ISelectionRedrawRequestEvent> { return this._onRedrawRequest.event; }
private _onSelectionChange = new EventEmitter2<void>();
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
constructor(
private _terminal: ITerminal,
private _charMeasure: CharMeasure
) {
super();
this._initListeners();
this.enable();
@@ -120,7 +126,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
}
public dispose(): void {
super.dispose();
this._removeMouseDownListeners();
}
@@ -134,14 +139,13 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _initListeners(): void {
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
this._trimListener = (amount: number) => this._onTrim(amount);
this.initBuffersListeners();
}
public initBuffersListeners(): void {
this._terminal.buffer.lines.on('trim', this._trimListener);
this._terminal.buffers.on('activate', e => this._onBufferActivate(e));
this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount));
this._terminal.buffers.onBufferActivate(e => this._onBufferActivate(e));
}
/**
@@ -245,10 +249,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 isLinuxMouseSelection Whether the selection should be registered as a new
* selection on Linux.
*/
public refresh(isNewSelection?: boolean): void {
public refresh(isLinuxMouseSelection?: boolean): void {
// Queue the refresh for the renderer
if (!this._refreshAnimationFrame) {
this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());
@@ -256,10 +260,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 && isLinuxMouseSelection) {
const selectionText = this.selectionText;
if (selectionText.length) {
this.emit('newselection', this.selectionText);
this._onLinuxMouseSelection.fire(this.selectionText);
}
}
}
@@ -270,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
@@ -319,7 +323,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 {
@@ -329,7 +333,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();
}
/**
@@ -647,7 +651,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();
}
}
@@ -657,8 +661,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));
}
/**
+110 -1
View File
@@ -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,
@@ -68,6 +68,81 @@ describe('term.js addons', () => {
});
});
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 = <KeyboardEvent>{
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 = <KeyboardEvent>{
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', () => { });
@@ -85,6 +160,40 @@ describe('term.js addons', () => {
});
});
describe('cursormove', () => {
it('should emit a cursormove event', (done) => {
term.on('cursormove', () => {
done();
});
term.write('foo');
});
});
describe('linefeed', () => {
it('should emit a linefeed event', (done) => {
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;
+58 -32
View File
@@ -51,6 +51,7 @@ import { IKeyboardEvent } from './common/Types';
import { evaluateKeyboardEvent } from './core/input/Keyboard';
import { KeyboardResultType, ICharset } from './core/Types';
import { clone } from './common/Clone';
import { EventEmitter2, IEvent } from './common/EventEmitter2';
import { Attributes } from './BufferLine';
import { applyWindowsMode } from './WindowsMode';
@@ -221,6 +222,25 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
public cols: number;
public rows: number;
private _onCursorMove = new EventEmitter2<void>();
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onData = new EventEmitter2<string>();
public get onData(): IEvent<string> { 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<void>();
public get onLineFeed(): IEvent<void> { 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<number>();
public get onScroll(): IEvent<number> { return this._onScroll.event; }
private _onSelectionChange = new EventEmitter2<void>();
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
private _onTitleChange = new EventEmitter2<string>();
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
/**
* Creates a new `Terminal` object.
*
@@ -239,6 +259,18 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
super();
this.options = clone(options);
this._setup();
// 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));
this.onTitleChange(e => this.emit('title', e));
}
public dispose(): void {
@@ -279,7 +311,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;
@@ -315,8 +347,12 @@ 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.
this.renderer = this.renderer || null;
this.selectionManager = this.selectionManager || null;
@@ -649,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)));
}
/**
@@ -702,7 +738,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');
@@ -735,21 +771,22 @@ 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.addDisposableListener('resize', () => this.renderer.onResize(this.cols, this.rows)));
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()));
this.register(this.addDisposableListener('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio)));
// 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.renderer.addDisposableListener('resize', (dimensions) => this.viewport.syncScrollArea()));
this.register(this.charMeasure.onCharSizeChanged(() => this.renderer.onCharSizeChanged()));
this.register(this.renderer.onCanvasResize(() => 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.addDisposableListener('newselection', text => {
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
// only on Linux to enable middle click to paste selection.
@@ -757,7 +794,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();
}));
@@ -799,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);
}
@@ -1264,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);
}
/**
@@ -1299,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);
@@ -1588,7 +1620,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);
@@ -1667,7 +1699,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);
@@ -1744,7 +1776,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 });
}
/**
@@ -1787,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);
}
/**
@@ -1799,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 {
@@ -1817,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);
}
/**
@@ -1825,13 +1857,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);
}
/**
+19 -13
View File
@@ -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 './common/EventEmitter2';
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
@@ -22,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
@@ -193,7 +188,7 @@ export interface ILinkMatcher {
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
}
export interface ILinkHoverEvent {
export interface ILinkifierEvent {
x1: number;
y1: number;
x2: number;
@@ -220,10 +215,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;
@@ -251,6 +242,9 @@ export interface IMouseHelper {
export interface ICharMeasure {
width: number;
height: number;
onCharSizeChanged: IEvent<void>;
measure(options: ITerminalOptions): void;
}
@@ -301,11 +295,13 @@ export interface IBuffer {
getWhitespaceCell(attr?: IAttributeData): 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?: IAttributeData): void;
}
@@ -322,7 +318,17 @@ export interface ISelectionManager {
selectWordAtCursor(event: MouseEvent): void;
}
export interface ILinkifier extends IEventEmitter {
export interface ISelectionRedrawRequestEvent {
start: [number, number];
end: [number, number];
columnSelectMode: boolean;
}
export interface ILinkifier {
onLinkHover: IEvent<ILinkifierEvent>;
onLinkLeave: IEvent<ILinkifierEvent>;
onLinkTooltip: IEvent<ILinkifierEvent>;
attachToDom(mouseZoneManager: IMouseZoneManager): void;
linkifyRows(start: number, end: number): void;
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
+1 -1
View File
@@ -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);
+1
View File
@@ -14,6 +14,7 @@ export interface IAttachAddonTerminal extends Terminal {
__socket?: WebSocket;
__attachSocketBuffer?: string;
__dataListener?: IDisposable;
__getMessage?(ev: MessageEvent): void;
__flushBuffer?(): void;
+4 -2
View File
@@ -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 = <IAttachAddonTerminal>term;
addonTerminal.off('data', addonTerminal.__sendData);
addonTerminal.__dataListener.dispose();
addonTerminal.__dataListener = undefined;
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
+7 -2
View File
@@ -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);
@@ -182,7 +184,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);
@@ -191,7 +193,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;
+6 -1
View File
@@ -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<T extends IDisposable>(d: T): void;
};
__socket?: WebSocket;
__attachSocketBuffer?: string;
__dataListener?: IDisposable;
__getMessage?(ev: MessageEvent): void;
__flushBuffer?(): void;
+4 -3
View File
@@ -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._core.register(addonTerminal.onData(addonTerminal.__sendData));
}
addonTerminal.on('resize', 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 = <ITerminadoAddonTerminal>term;
addonTerminal.off('data', addonTerminal.__sendData);
addonTerminal.__dataListener.dispose();
addonTerminal.__dataListener = undefined;
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
+14 -8
View File
@@ -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<T> extends EventEmitter implements ICircularList<T> {
export class CircularList<T> implements ICircularList<T> {
protected _array: (T | undefined)[];
private _startIndex: number;
private _length: number;
public onDeleteEmitter = new EventEmitter2<IDeleteEvent>();
public get onDelete(): IEvent<IDeleteEvent> { return this.onDeleteEmitter.event; }
public onInsertEmitter = new EventEmitter2<IInsertEvent>();
public get onInsert(): IEvent<IInsertEvent> { return this.onInsertEmitter.event; }
public onTrimEmitter = new EventEmitter2<number>();
public get onTrim(): IEvent<number> { return this.onTrimEmitter.event; }
constructor(
private _maxLength: number
) {
super();
this._array = new Array<T>(this._maxLength);
this._startIndex = 0;
this._length = 0;
@@ -101,7 +107,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
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<T> extends EventEmitter implements ICircularList<T> {
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<T> extends EventEmitter implements ICircularList<T> {
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<T> extends EventEmitter implements ICircularList<T> {
}
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<T> extends EventEmitter implements ICircularList<T> {
while (this._length > this._maxLength) {
this._length--;
this._startIndex++;
this.emitMayRemoveListeners('trim', 1);
this.onTrimEmitter.fire(1);
}
}
} else {
+31
View File
@@ -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<number>();
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<number>();
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' ]);
});
});

Some files were not shown because too many files have changed in this diff Show More