diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 8f10e622..b0075d88 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -56,25 +56,25 @@ describe('Terminal', () => { // term.handler('fake'); // }); it('should fire the onCursorMove event', () => { - return new Promise(async r => { + return new Promise(async r => { term.onCursorMove(() => r()); await term.writeP('foo'); }); }); it('should fire the onLineFeed event', () => { - return new Promise(async r => { + return new Promise(async r => { term.onLineFeed(() => r()); await term.writeP('\n'); }); }); it('should fire a scroll event when scrollback is created', () => { - return new Promise(async r => { + return new Promise(async r => { term.onScroll(() => r()); await term.writeP('\n'.repeat(INIT_ROWS)); }); }); it('should fire a scroll event when scrollback is cleared', () => { - return new Promise(async r => { + return new Promise(async r => { await term.writeP('\n'.repeat(INIT_ROWS)); term.onScroll(() => r()); term.clear(); @@ -233,7 +233,7 @@ describe('Terminal', () => { term.paste('\r\nfoo\nbar\r'); }); it('should respect bracketed paste mode', () => { - return new Promise(async r => { + return new Promise(async r => { term.onData(e => { assert.equal(e, '\x1b[200~foo\x1b[201~'); r(); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9d9246e3..f14bffe0 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -147,7 +147,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestReset(() => this.reset())); - this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this._bufferService.scroll(eraseAttr, isWrapped || undefined))); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event))); this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 1d076692..555994e4 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -123,7 +123,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(forwardEvent(this._coreService.onData, this._onData)); this.register(forwardEvent(this._coreService.onBinary, this._onBinary)); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); - this.register(this._bufferService.onScroll(event => this._onScroll.fire(event))); + this.register(this._bufferService.onScroll(event => { + this._onScroll.fire(event); + this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + })); // Setup WriteBuffer this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index ecd476fc..cb60aec3 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -71,115 +71,108 @@ describe('InputHandler', () => { inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); }); - describe('Terminal InputHandler integration', () => { - function getLines(limit: number): string[] { - const res: string[] = []; - for (let i = 0; i < limit; ++i) { - res.push(bufferService.buffer.lines.get(i)!.translateToString(true)); - } - return res; - } - - function reset(): void { - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - } - - // This suite cannot live in InputHandler unless Terminal.scroll moved into IBufferService - describe('SL/SR/DECIC/DECDC', () => { - - it('SL (scrollLeft)', async () => { - inputHandler.parseP('12345'.repeat(6)); - assert.deepEqual(getLines(6), ['12345', '2345', '2345', '2345', '2345', '2345']); - inputHandler.parseP('\x1b[0 @'); - assert.deepEqual(getLines(6), ['12345', '345', '345', '345', '345', '345']); - inputHandler.parseP('\x1b[2 @'); - assert.deepEqual(getLines(6), ['12345', '5', '5', '5', '5', '5']); - }); - it('SR (scrollRight)', async () => { - inputHandler.parseP('12345'.repeat(6)); - inputHandler.parseP('\x1b[ A'); - assert.deepEqual(getLines(6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); - inputHandler.parseP('\x1b[0 A'); - assert.deepEqual(getLines(6), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']); - inputHandler.parseP('\x1b[2 A'); - assert.deepEqual(getLines(6), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); - }); - it('insertColumns (DECIC)', async () => { - inputHandler.parseP('12345'.repeat(6)); - inputHandler.parseP('\x1b[3;3H'); - inputHandler.parseP('\x1b[\'}'); - assert.deepEqual(getLines(6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); - reset(); - inputHandler.parseP('12345'.repeat(6)); - inputHandler.parseP('\x1b[3;3H'); - inputHandler.parseP('\x1b[1\'}'); - assert.deepEqual(getLines(6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); - reset(); - inputHandler.parseP('12345'.repeat(6)); - inputHandler.parseP('\x1b[3;3H'); - inputHandler.parseP('\x1b[2\'}'); - assert.deepEqual(getLines(6), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); - }); - it('deleteColumns (DECDC)', async () => { - inputHandler.parseP('12345'.repeat(6)); - inputHandler.parseP('\x1b[3;3H'); - inputHandler.parseP('\x1b[\'~'); - assert.deepEqual(getLines(6), ['12345', '1245', '1245', '1245', '1245', '1245']); - reset(); - inputHandler.parseP('12345'.repeat(6)); - inputHandler.parseP('\x1b[3;3H'); - inputHandler.parseP('\x1b[1\'~'); - assert.deepEqual(getLines(6), ['12345', '1245', '1245', '1245', '1245', '1245']); - reset(); - inputHandler.parseP('12345'.repeat(6)); - inputHandler.parseP('\x1b[3;3H'); - inputHandler.parseP('\x1b[2\'~'); - assert.deepEqual(getLines(6), ['12345', '125', '125', '125', '125', '125']); - }); + describe('SL/SR/DECIC/DECDC', () => { + beforeEach(() => { + bufferService.resize(5, 5); + optionsService.options.scrollback = 1; + bufferService.reset(); }); + it('SL (scrollLeft)', async () => { + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[ @'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '2345', '2345', '2345', '2345', '2345']); + inputHandler.parseP('\x1b[0 @'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '345', '345', '345', '345', '345']); + inputHandler.parseP('\x1b[2 @'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '5', '5', '5', '5', '5']); + }); + it('SR (scrollRight)', async () => { + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[ A'); + assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); + inputHandler.parseP('\x1b[0 A'); + assert.deepEqual(getLines(bufferService, 6), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']); + inputHandler.parseP('\x1b[2 A'); + assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); + }); + it('insertColumns (DECIC)', async () => { + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[\'}'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); + bufferService.reset(); + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[1\'}'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); + bufferService.reset(); + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[2\'}'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); + }); + it('deleteColumns (DECDC)', async () => { + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[\'~'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']); + bufferService.reset(); + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[1\'~'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']); + bufferService.reset(); + inputHandler.parseP('12345'.repeat(6)); + inputHandler.parseP('\x1b[3;3H'); + inputHandler.parseP('\x1b[2\'~'); + assert.deepEqual(getLines(bufferService, 6), ['12345', '125', '125', '125', '125', '125']); + }); + }); - describe('BS with reverseWraparound set/unset', () => { - const ttyBS = '\x08 \x08'; // tty ICANON sends on pressing BS + describe('BS with reverseWraparound set/unset', () => { + const ttyBS = '\x08 \x08'; // tty ICANON sends on pressing BS + beforeEach(() => { + bufferService.resize(5, 5); + optionsService.options.scrollback = 1; + bufferService.reset(); + }); + describe('reverseWraparound set', () => { + it('should not reverse outside of scroll margins', async () => { + // prepare buffer content + inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy'); + assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); + assert.equal(bufferService.buffers.active.ydisp, 1); + assert.equal(bufferService.buffers.active.x, 5); + assert.equal(bufferService.buffers.active.y, 4); + inputHandler.parseP(ttyBS.repeat(100)); + assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']); - describe('reverseWraparound set', () => { - it('should not reverse outside of scroll margins', async () => { - // prepare buffer content - inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy'); - assert.deepEqual(getLines(5), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); - assert.equal(bufferService.buffers.active.ydisp, 1); - assert.equal(bufferService.buffers.active.x, 5); - assert.equal(bufferService.buffers.active.y, 4); - inputHandler.parseP(ttyBS.repeat(100)); - assert.deepEqual(getLines(5), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']); + inputHandler.parseP('\x1b[?45h'); + inputHandler.parseP('uvwxy'); - inputHandler.parseP('\x1b[?45h'); - inputHandler.parseP('uvwxy'); + // set top/bottom to 1/3 (0-based) + inputHandler.parseP('\x1b[2;4r'); + // place cursor below scroll bottom + bufferService.buffers.active.x = 5; + bufferService.buffers.active.y = 4; + inputHandler.parseP(ttyBS.repeat(100)); + assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']); - // set top/bottom to 1/3 (0-based) - inputHandler.parseP('\x1b[2;4r'); - // place cursor below scroll bottom - bufferService.buffers.active.x = 5; - bufferService.buffers.active.y = 4; - inputHandler.parseP(ttyBS.repeat(100)); - assert.deepEqual(getLines(5), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']); + inputHandler.parseP('uvwxy'); + // place cursor within scroll margins + bufferService.buffers.active.x = 5; + bufferService.buffers.active.y = 3; + inputHandler.parseP(ttyBS.repeat(100)); + assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']); + assert.equal(bufferService.buffers.active.x, 0); + assert.equal(bufferService.buffers.active.y, bufferService.buffers.active.scrollTop); // stops at 0, scrollTop - inputHandler.parseP('uvwxy'); - // place cursor within scroll margins - bufferService.buffers.active.x = 5; - bufferService.buffers.active.y = 3; - inputHandler.parseP(ttyBS.repeat(100)); - assert.deepEqual(getLines(5), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']); - assert.equal(bufferService.buffers.active.x, 0); - assert.equal(bufferService.buffers.active.y, bufferService.buffers.active.scrollTop); // stops at 0, scrollTop - - inputHandler.parseP('fghijklmnopqrst'); - // place cursor above scroll top - bufferService.buffers.active.x = 5; - bufferService.buffers.active.y = 0; - inputHandler.parseP(ttyBS.repeat(100)); - assert.deepEqual(getLines(5), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']); - }); + inputHandler.parseP('fghijklmnopqrst'); + // place cursor above scroll top + bufferService.buffers.active.x = 5; + bufferService.buffers.active.y = 0; + inputHandler.parseP(ttyBS.repeat(100)); + assert.deepEqual(getLines(bufferService, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']); }); }); }); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 13b0a0e7..3af2f9d7 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -240,8 +240,6 @@ export class InputHandler extends Disposable implements IInputHandler { public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } private _onRequestReset = new EventEmitter(); public get onRequestReset(): IEvent { return this._onRequestReset.event; } - private _onRequestScroll = new EventEmitter(); - public get onRequestScroll(): IEvent { return this._onRequestScroll.event; } private _onRequestSyncScrollBar = new EventEmitter(); public get onRequestSyncScrollBar(): IEvent { return this._onRequestSyncScrollBar.event; } private _onRequestWindowsOptionsReport = new EventEmitter(); @@ -651,7 +649,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onRequestScroll.fire(this._eraseAttrData(), true); + this._bufferService.scroll(this._eraseAttrData(), true); } else { if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; @@ -791,7 +789,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onRequestScroll.fire(this._eraseAttrData()); + this._bufferService.scroll(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } @@ -2987,7 +2985,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onRequestScroll.fire(this._eraseAttrData()); + this._bufferService.scroll(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 2dd3f4b8..df299195 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -357,7 +357,6 @@ export interface IAnsiColorChangeEvent { */ export interface IInputHandler { onTitleChange: IEvent; - onRequestScroll: IEvent; parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise; print(data: Uint32Array, start: number, end: number): void; diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 7fe0cdc3..3833bb44 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -3,13 +3,12 @@ * @license MIT */ -import { IBufferService, IDirtyRowService, IInstantiationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine } from 'common/Types'; -import { DirtyRowService } from 'common/services/DirtyRowService'; export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars export const MINIMUM_ROWS = 1; @@ -33,8 +32,6 @@ export class BufferService extends Disposable implements IBufferService { /** An IBufferline to clone/copy from for new blank lines */ private _cachedBlankLine: IBufferLine | undefined; - private _dirtyRowService: IDirtyRowService | undefined; - constructor( @IOptionsService private _optionsService: IOptionsService ) { @@ -123,12 +120,6 @@ export class BufferService extends Disposable implements IBufferService { buffer.ydisp = buffer.ybase; } - // Flag rows that need updating - if (!this._dirtyRowService) { - this._dirtyRowService = new DirtyRowService(this); - } - this._dirtyRowService?.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); - this._onScroll.fire(buffer.ydisp); }