diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 2d28f55d55..6cf615fe99 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -7,6 +7,7 @@ import { assert } from 'chai'; import { CompositionHelper } from './CompositionHelper'; import { ITerminal } from './Types'; import { MockCharSizeService } from 'browser/TestUtils.test'; +import { MockCoreService } from '../out/common/TestUtils.test'; describe('CompositionHelper', () => { let terminal: ITerminal; @@ -54,7 +55,7 @@ describe('CompositionHelper', () => { } } as any; handledText = ''; - compositionHelper = new CompositionHelper(textarea, compositionView, terminal, new MockCharSizeService(10, 10)); + compositionHelper = new CompositionHelper(textarea, compositionView, terminal, new MockCharSizeService(10, 10), new MockCoreService()); }); describe('Input', () => { diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index 2b2d3042f3..010585f8b1 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -5,6 +5,7 @@ import { ITerminal } from './Types'; import { ICharSizeService } from 'browser/services/Services'; +import { ICoreService } from 'common/services/Services'; interface IPosition { start: number; @@ -41,10 +42,11 @@ export class CompositionHelper { * @param _terminal The Terminal to forward the finished composition to. */ constructor( - private _textarea: HTMLTextAreaElement, - private _compositionView: HTMLElement, - private _terminal: ITerminal, - private _charSizeService: ICharSizeService + private readonly _textarea: HTMLTextAreaElement, + private readonly _compositionView: HTMLElement, + private readonly _terminal: ITerminal, + private readonly _charSizeService: ICharSizeService, + private readonly _coreService: ICoreService ) { this._isComposing = false; this._isSendingComposition = false; @@ -127,7 +129,7 @@ export class CompositionHelper { // Cancel any delayed composition send requests and send the input immediately. this._isSendingComposition = false; const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end); - this._terminal.handler(input); + this._coreService.triggerDataEvent(input, true); } else { // Make a deep copy of the composition position here as a new compositionstart event may // fire before the setTimeout executes. @@ -159,7 +161,7 @@ export class CompositionHelper { // (eg. 2) after a composition character. input = this._textarea.value.substring(currentCompositionPosition.start); } - this._terminal.handler(input); + this._coreService.triggerDataEvent(input, true); } }, 0); } @@ -179,7 +181,7 @@ export class CompositionHelper { const newValue = this._textarea.value; const diff = newValue.replace(oldValue, ''); if (diff.length > 0) { - this._terminal.handler(diff); + this._coreService.triggerDataEvent(diff, true); } } }, 0); diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 19d5f6a2cf..e93fc945d6 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -12,6 +12,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; +import { MockCoreService } from 'common/TestUtils.test'; describe('InputHandler', () => { describe('save and restore cursor', () => { @@ -20,7 +21,7 @@ describe('InputHandler', () => { terminal.buffer.y = 2; terminal.buffer.ybase = 0; terminal.curAttrData.fg = 3; - const inputHandler = new InputHandler(terminal); + const inputHandler = new InputHandler(terminal, new MockCoreService()); // Save cursor position inputHandler.saveCursor([]); assert.equal(terminal.buffer.x, 1); @@ -39,7 +40,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const terminal = new MockInputHandlingTerminal(); - const inputHandler = new InputHandler(terminal); + const inputHandler = new InputHandler(terminal, new MockCoreService()); const collect = ' '; inputHandler.setCursorStyle([0], collect); @@ -82,7 +83,7 @@ describe('InputHandler', () => { const terminal = new MockInputHandlingTerminal(); const collect = '?'; terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal); + const inputHandler = new InputHandler(terminal, new MockCoreService()); // Set bracketed paste mode inputHandler.setMode([2004], collect); assert.equal(terminal.bracketedPasteMode, true); @@ -100,7 +101,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const term = new Terminal(); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); // insert some data in first and second line inputHandler.parse(Array(term.cols - 9).join('a')); @@ -137,7 +138,7 @@ describe('InputHandler', () => { }); it('deleteChars', function(): void { const term = new Terminal(); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); // insert some data in first and second line inputHandler.parse(Array(term.cols - 9).join('a')); @@ -177,7 +178,7 @@ describe('InputHandler', () => { }); it('eraseInLine', function(): void { const term = new Terminal(); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); // fill 6 lines to test 3 different states inputHandler.parse(Array(term.cols + 1).join('a')); @@ -205,7 +206,7 @@ describe('InputHandler', () => { }); it('eraseInDisplay', function(): void { const term = new Terminal({cols: 80, rows: 7}); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); // fill display with a's for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); @@ -340,7 +341,7 @@ describe('InputHandler', () => { describe('print', () => { it('should not cause an infinite loop (regression test)', () => { const term = new Terminal(); - const inputHandler = new InputHandler(term); + const inputHandler = new InputHandler(term, new MockCoreService()); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -353,7 +354,7 @@ describe('InputHandler', () => { beforeEach(() => { term = new Terminal(); - handler = new InputHandler(term); + handler = new InputHandler(term, new MockCoreService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a2ae4e0899..194d2e84c0 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -19,6 +19,7 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; +import { ICoreService } from 'common/services/Services'; /** * Map collect to glevel. Used in `selectCharset`. @@ -113,8 +114,6 @@ export class InputHandler extends Disposable implements IInputHandler { private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } - private _onData = new EventEmitter(); - public get onData(): IEvent { return this._onData.event; } private _onLineFeed = new EventEmitter(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onScroll = new EventEmitter(); @@ -122,6 +121,7 @@ export class InputHandler extends Disposable implements IInputHandler { constructor( protected _terminal: IInputHandlingTerminal, + private _coreService: ICoreService, private _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); @@ -1098,24 +1098,24 @@ export class InputHandler extends Disposable implements IInputHandler { if (!collect) { if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { - this._terminal.handler(C0.ESC + '[?1;2c'); + this._coreService.triggerDataEvent(C0.ESC + '[?1;2c'); } else if (this._terminal.is('linux')) { - this._terminal.handler(C0.ESC + '[?6c'); + this._coreService.triggerDataEvent(C0.ESC + '[?6c'); } } else if (collect === '>') { // xterm and urxvt // seem to spit this // out around ~370 times (?). if (this._terminal.is('xterm')) { - this._terminal.handler(C0.ESC + '[>0;276;0c'); + this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c'); } else if (this._terminal.is('rxvt-unicode')) { - this._terminal.handler(C0.ESC + '[>85;95;0c'); + this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c'); } else if (this._terminal.is('linux')) { // not supported by linux console. // linux console echoes parameters. - this._terminal.handler(params[0] + 'c'); + this._coreService.triggerDataEvent(params[0] + 'c'); } else if (this._terminal.is('screen')) { - this._terminal.handler(C0.ESC + '[>83;40003;0c'); + this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c'); } } } @@ -1799,13 +1799,13 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params[0]) { case 5: // status report - this._onData.fire(`${C0.ESC}[0n`); + this._coreService.triggerDataEvent(`${C0.ESC}[0n`); break; case 6: // cursor position const y = this._terminal.buffer.y + 1; const x = this._terminal.buffer.x + 1; - this._onData.fire(`${C0.ESC}[${y};${x}R`); + this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`); break; } } else if (collect === '?') { @@ -1816,7 +1816,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._onData.fire(`${C0.ESC}[?${y};${x}R`); + this._coreService.triggerDataEvent(`${C0.ESC}[?${y};${x}R`); break; case 15: // no printer diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 814cb5fe06..6b11698f62 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,7 +10,7 @@ import { ITerminal } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import { MockTerminal } from './TestUtils.test'; -import { MockBufferService, MockOptionsService } from 'common/TestUtils.test'; +import { MockBufferService, MockOptionsService, MockCoreService } from 'common/TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { MockCharSizeService, MockMouseService } from 'browser/TestUtils.test'; @@ -26,7 +26,7 @@ class TestSelectionManager extends SelectionManager { bufferService: IBufferService, optionsService: IOptionsService ) { - super(terminal, null, new MockCharSizeService(10, 10), bufferService, new MockMouseService(), optionsService); + super(terminal, null, new MockCharSizeService(10, 10), bufferService, new MockCoreService(), new MockMouseService(), optionsService); } public get model(): SelectionModel { return this._model; } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 4133fcccc4..700238367b 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -13,7 +13,7 @@ import { CellData } from 'common/buffer/CellData'; import { IDisposable } from 'xterm'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICharSizeService, IMouseService } from 'browser/services/Services'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; import { moveToCellSequence } from 'browser/input/MoveToCell'; @@ -117,6 +117,7 @@ export class SelectionManager implements ISelectionManager { private readonly _screenElement: HTMLElement, private readonly _charSizeService: ICharSizeService, private readonly _bufferService: IBufferService, + private readonly _coreService: ICoreService, private readonly _mouseService: IMouseService, private readonly _optionsService: IOptionsService ) { @@ -137,7 +138,11 @@ export class SelectionManager implements ISelectionManager { private _initListeners(): void { this._mouseMoveListener = event => this._onMouseMove(event); this._mouseUpListener = event => this._onMouseUp(event); - + this._coreService.onUserInput(() => { + if (this.hasSelection) { + this.clearSelection(); + } + }); this.initBuffersListeners(); } @@ -661,7 +666,7 @@ export class SelectionManager implements ISelectionManager { ); if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) { const sequence = moveToCellSequence(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._terminal.applicationCursor); - this._terminal.handler(sequence); + this._coreService.triggerDataEvent(sequence, true); } } } else if (this.hasSelection) { diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index f3f7fc586b..f2f02eec23 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -53,10 +53,11 @@ describe('Terminal', () => { }); describe('events', () => { - it('should fire the onData evnet', (done) => { - term.onData(() => done()); - term.handler('fake'); - }); + // TODO: Add an onData test back + // it('should fire the onData evnet', (done) => { + // term.onData(() => done()); + // term.handler('fake'); + // }); it('should fire the onCursorMove event', (done) => { term.onCursorMove(() => done()); term.write('foo'); @@ -142,7 +143,6 @@ describe('Terminal', () => { }; beforeEach(() => { - term.handler = () => { }; term.showCursor = () => { }; term.clearSelection = () => { }; }); @@ -520,7 +520,6 @@ describe('Terminal', () => { let evKeyPress: any; beforeEach(() => { - term.handler = () => { }; term.showCursor = () => { }; term.clearSelection = () => { }; // term.compositionHelper = { diff --git a/src/Terminal.ts b/src/Terminal.ts index 72cecb199b..a9f11b4b78 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,7 +47,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { IOptionsService, IBufferService } from 'common/services/Services'; +import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService, IRenderService, IMouseService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; @@ -56,6 +56,7 @@ import { Disposable } from 'common/Lifecycle'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { Attributes } from 'common/buffer/Constants'; import { MouseService } from 'browser/services/MouseService'; +import { CoreService } from 'common/services/CoreService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -107,6 +108,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // common services private _bufferService: IBufferService; + private _coreService: ICoreService; public optionsService: IOptionsService; // browser services @@ -237,6 +239,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Setup and initialize common services this.optionsService = new OptionsService(options); this._bufferService = new BufferService(this.optionsService); + this._coreService = new CoreService(() => this.scrollToBottom(), this._bufferService, this.optionsService); + this._coreService.onData(e => this._onData.fire(e)); + this._setupOptionsListeners(); this._setup(); } @@ -249,7 +254,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } this._customKeyEventHandler = null; removeTerminalFromCache(this); - this.handler = () => {}; this.write = () => {}; if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); @@ -294,10 +298,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._userScrolling = false; // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this); + this._inputHandler = new InputHandler(this, this._coreService); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); - this._inputHandler.onData(e => this._onData.fire(e)); this.register(this._inputHandler); this.selectionManager = this.selectionManager || null; @@ -434,7 +437,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp */ private _onTextAreaFocus(ev: KeyboardEvent): void { if (this.sendFocus) { - this.handler(C0.ESC + '[I'); + this._coreService.triggerDataEvent(C0.ESC + '[I'); } this.updateCursorStyle(ev); this.element.classList.add('focus'); @@ -459,7 +462,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.textarea.value = ''; this.refresh(this.buffer.y, this.buffer.y); if (this.sendFocus) { - this.handler(C0.ESC + '[O'); + this._coreService.triggerDataEvent(C0.ESC + '[O'); } this.element.classList.remove('focus'); this._onBlur.fire(); @@ -480,7 +483,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } copyHandler(event, this.selectionManager); })); - const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this.textarea, this.bracketedPasteMode, e => this.handler(e)); + const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this.textarea, this.bracketedPasteMode, e => this._coreService.triggerDataEvent(e, true)); this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper)); this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper)); @@ -607,7 +610,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); - this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this, this._charSizeService); + this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this, this._charSizeService, this._coreService); this._helperContainer.appendChild(this._compositionView); // Performance: Add viewport and helper elements from the fragment @@ -640,7 +643,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.register(this.onFocus(() => this._renderService.onFocus())); this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea())); - this.selectionManager = new SelectionManager(this, this.screenElement, this._charSizeService, this._bufferService, this._mouseService, this.optionsService); + this.selectionManager = new SelectionManager(this, this.screenElement, this._charSizeService, this._bufferService, this._coreService, this._mouseService, this.optionsService); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode))); @@ -814,7 +817,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp else if (button === 3) return; else data += '0'; data += '~[' + pos.x + ',' + pos.y + ']\r'; - self.handler(data); + self._coreService.triggerDataEvent(data, true); return; } @@ -827,7 +830,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp else if (button === 1) button = 4; else if (button === 2) button = 6; else if (button === 3) button = 3; - self.handler(C0.ESC + '[' + self._coreService.triggerDataEvent(C0.ESC + '[' + button + ';' + (button === 3 ? 4 : 0) @@ -838,7 +841,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp + ';' // Not sure what page is meant to be + (pos).page || 0 - + '&w'); + + '&w', true); return; } @@ -847,20 +850,20 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp pos.y -= 32; pos.x++; pos.y++; - self.handler(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); + self._coreService.triggerDataEvent(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M', true); return; } if (self.sgrMouse) { pos.x -= 32; pos.y -= 32; - self.handler(C0.ESC + '[<' + self._coreService.triggerDataEvent(C0.ESC + '[<' + (((button & 3) === 3 ? button & ~3 : button) - 32) + ';' + pos.x + ';' + pos.y - + ((button & 3) === 3 ? 'm' : 'M')); + + ((button & 3) === 3 ? 'm' : 'M'), true); return; } @@ -870,7 +873,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp encode(data, pos.x); encode(data, pos.y); - self.handler(C0.ESC + '[M' + String.fromCharCode.apply(String, data)); + self._coreService.triggerDataEvent(C0.ESC + '[M' + String.fromCharCode.apply(String, data), true); } function getButton(ev: MouseEvent): number { @@ -1015,7 +1018,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp for (let i = 0; i < Math.abs(amount); i++) { data += sequence; } - this.handler(data); + this._coreService.triggerDataEvent(data, true); } return; } @@ -1240,7 +1243,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBufferUtf8.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { // XOFF - stop pty pipe // XON will be triggered by emulator before processing data chunk - this.handler(C0.DC3); + this._coreService.triggerDataEvent(C0.DC3); this._xoffSentToCatchUp = true; } @@ -1268,7 +1271,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // If XOFF was sent in order to catch up with the pty process, resume it if // we reached the end of the writeBuffer to allow more data to come in. if (this._xoffSentToCatchUp && this.writeBufferUtf8.length === bufferOffset) { - this.handler(C0.DC1); + this._coreService.triggerDataEvent(C0.DC1); this._xoffSentToCatchUp = false; } @@ -1327,7 +1330,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { // XOFF - stop pty pipe // XON will be triggered by emulator before processing data chunk - this.handler(C0.DC3); + this._coreService.triggerDataEvent(C0.DC3); this._xoffSentToCatchUp = true; } @@ -1355,7 +1358,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // If XOFF was sent in order to catch up with the pty process, resume it if // we reached the end of the writeBuffer to allow more data to come in. if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) { - this.handler(C0.DC1); + this._coreService.triggerDataEvent(C0.DC1); this._xoffSentToCatchUp = false; } @@ -1587,7 +1590,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._onKey.fire({ key: result.key, domEvent: event }); this.showCursor(); - this.handler(result.key); + this._coreService.triggerDataEvent(result.key, true); return this.cancel(event, true); } @@ -1665,7 +1668,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._onKey.fire({ key, domEvent: ev }); this.showCursor(); - this.handler(key); + this._coreService.triggerDataEvent(key, true); return true; } @@ -1796,23 +1799,23 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * Emit the data event and populate the given data. * @param data The data to populate in the event. */ - public handler(data: string): void { - // Prevents all events to pty process if stdin is disabled - if (this.options.disableStdin) { - return; - } - - // Clear the selection if the selection manager is available and has an active selection - if (this.selectionManager && this.selectionManager.hasSelection) { - this.selectionManager.clearSelection(); - } - - // Input is being sent to the terminal, the terminal should focus the prompt. - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - this._onData.fire(data); - } + // public handler(data: string): void { + // // Prevents all events to pty process if stdin is disabled + // if (this.options.disableStdin) { + // return; + // } + + // // Clear the selection if the selection manager is available and has an active selection + // if (this.selectionManager && this.selectionManager.hasSelection) { + // this.selectionManager.clearSelection(); + // } + + // // Input is being sent to the terminal, the terminal should focus the prompt. + // if (this.buffer.ybase !== this.buffer.ydisp) { + // this.scrollToBottom(); + // } + // this._onData.fire(data); + // } /** * Emit the 'title' event and populate the given title. diff --git a/src/Types.d.ts b/src/Types.d.ts index c3957ab719..20e6c08283 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -73,7 +73,6 @@ export interface IInputHandlingTerminal { refresh(start: number, end: number): void; error(text: string, data?: any): void; tabSet(): void; - handler(data: string): void; handleTitle(title: string): void; index(): void; reverseIndex(): void; @@ -217,7 +216,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc onA11yChar: IEvent; onA11yTab: IEvent; - handler(data: string): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; cancel(ev: Event, force?: boolean): boolean | void; log(text: string): void; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index c6402fb164..86098d33a7 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferService, IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services'; +import { IBufferService, ICoreService, IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -27,6 +27,12 @@ export class MockBufferService implements IBufferService { reset(): void {} } +export class MockCoreService implements ICoreService { + onData: IEvent = new EventEmitter().event; + onUserInput: IEvent = new EventEmitter().event; + triggerDataEvent(data: string, wasUserInput?: boolean): void {} +} + export class MockOptionsService implements IOptionsService { options: ITerminalOptions = clone(DEFAULT_OPTIONS); onOptionChange: IEvent = new EventEmitter().event; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts new file mode 100644 index 0000000000..bd731e8dce --- /dev/null +++ b/src/common/services/CoreService.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ICoreService, IOptionsService, IBufferService } from 'common/services/Services'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; + +export class CoreService implements ICoreService { + private _onData = new EventEmitter(); + public get onData(): IEvent { return this._onData.event; } + private _onUserInput = new EventEmitter(); + public get onUserInput(): IEvent { return this._onUserInput.event; } + + constructor( + // TODO: Move this into a service + private readonly _scrollToBottom: () => void, + private readonly _bufferService: IBufferService, + private readonly _optionsService: IOptionsService + ) { + } + + public triggerDataEvent(data: string, wasUserInput: boolean = false): void { + // Prevents all events to pty process if stdin is disabled + if (this._optionsService.options.disableStdin) { + return; + } + + // Input is being sent to the terminal, the terminal should focus the prompt. + const buffer = this._bufferService.buffer; + if (buffer.ybase !== buffer.ydisp) { + this._scrollToBottom(); + } + + // Fire onUserInput so listeners can react as well (eg. clear selection) + if (wasUserInput) { + this._onUserInput.fire(); + } + + // Fire onData API + this._onData.fire(data); + } +} diff --git a/src/common/services/Services.d.ts b/src/common/services/Services.d.ts index b8276f5150..d9903e70a2 100644 --- a/src/common/services/Services.d.ts +++ b/src/common/services/Services.d.ts @@ -18,6 +18,21 @@ export interface IBufferService { reset(): void; } +export interface ICoreService { + readonly onData: IEvent; + readonly onUserInput: IEvent; + + /** + * Triggers the onData event in the public API. + * @param data The data that is being emitted. + * @param wasFromUser Whether the data originated from the user (as opposed to + * resulting from parsing incoming data). When true this will also: + * - Scroll to the bottom of the buffer.s + * - Fire the `onUserInput` event (so selection can be cleared). + */ + triggerDataEvent(data: string, wasUserInput?: boolean): void; +} + export interface IOptionsService { readonly options: ITerminalOptions;