mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge pull request #2008 from Tyriar/event_emitter_2
Explicit event API
This commit is contained in:
+6
-9
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface IAttachAddonTerminal extends Terminal {
|
||||
|
||||
__socket?: WebSocket;
|
||||
__attachSocketBuffer?: string;
|
||||
__dataListener?: IDisposable;
|
||||
|
||||
__getMessage?(ev: MessageEvent): void;
|
||||
__flushBuffer?(): void;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user