diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 9bdc783e..8626be09 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -11,6 +11,7 @@ import { IBufferService, IUnicodeService } from 'common/services/Services'; import { Linkifier } from 'browser/Linkifier'; import { MockLogService, MockUnicodeService } from 'common/TestUtils.test'; import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; +import { IMarker } from 'common/Types'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -1533,6 +1534,71 @@ describe('Terminal', () => { }); }); }); + + // FIXME: move to common/CoreTerminal.test once the trimming is moved over + describe('marker lifecycle', () => { + // create a 10x5 terminal with markers on every line + // to test marker lifecycle under various terminal actions + let markers: IMarker[]; + let disposeStack: IMarker[]; + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal({}); + markers = []; + disposeStack = []; + term.optionsService.setOption('scrollback', 1); + term.resize(10, 5); + markers.push(term.buffers.active.addMarker(term.buffers.active.y)); + term.writeSync('\x1b[r0\r\n'); + markers.push(term.buffers.active.addMarker(term.buffers.active.y)); + term.writeSync('1\r\n'); + markers.push(term.buffers.active.addMarker(term.buffers.active.y)); + term.writeSync('2\r\n'); + markers.push(term.buffers.active.addMarker(term.buffers.active.y)); + term.writeSync('3\r\n'); + markers.push(term.buffers.active.addMarker(term.buffers.active.y)); + term.writeSync('4'); + for (let i = 0; i < markers.length; ++i) { + const marker = markers[i]; + marker.onDispose(() => disposeStack.push(marker)); + } + }); + it('initial', () => { + assert.deepEqual(markers.map(m => m.line), [0, 1, 2, 3, 4]); + }); + it('should dispose on normal trim off the top', () => { + // moves top line into scrollback + term.writeSync('\n'); + assert.deepEqual(disposeStack, []); + // trims first marker + term.writeSync('\n'); + assert.deepEqual(disposeStack, [markers[0]]); + // trims second marker + term.writeSync('\n'); + assert.deepEqual(disposeStack, [markers[0], markers[1]]); + // trimmed marker objs should be disposed + assert.deepEqual(disposeStack.map(el => el.isDisposed), [true, true]); + assert.deepEqual(disposeStack.map(el => (el as any)._isDisposed), [true, true]); + // trimmed markers should contain line -1 + assert.deepEqual(disposeStack.map(el => el.line), [-1, -1]); + }); + it('should dispose on DL', () => { + term.writeSync('\x1b[3;1H'); // move cursor to 0, 2 + term.writeSync('\x1b[2M'); // delete 2 lines + assert.deepEqual(disposeStack, [markers[2], markers[3]]); + }); + it('should dispose on IL', () => { + term.writeSync('\x1b[3;1H'); // move cursor to 0, 2 + term.writeSync('\x1b[2L'); // insert 2 lines + assert.deepEqual(disposeStack, [markers[4], markers[3]]); + assert.deepEqual(markers.map(el => el.line), [0, 1, 4, -1, -1]); + }); + it('should dispose on resize', () => { + term.resize(10, 2); + assert.deepEqual(disposeStack, [markers[0], markers[1]]); + assert.deepEqual(markers.map(el => el.line), [-1, -1, 0, 1, 2]); + }); + }); }); class TestLinkifier extends Linkifier { diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index d4ad3fce..ab00e681 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -158,6 +158,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; } this._length -= deleteCount; + this.onDeleteEmitter.fire({index: start, amount: deleteCount}); } // Add items @@ -167,6 +168,9 @@ export class CircularList implements ICircularList { for (let i = 0; i < items.length; i++) { this._array[this._getCyclicIndex(start + i)] = items[i]; } + if (items.length) { + this.onInsertEmitter.fire({index: start, amount: items.length}); + } // Adjust length as needed if (this._length + items.length > this._maxLength) { diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 6ce69483..bd0d11c6 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -183,6 +183,7 @@ export interface IMarker extends IDisposable { readonly id: number; readonly isDisposed: boolean; readonly line: number; + onDispose: IEvent; } export interface IModes { insertMode: boolean; diff --git a/src/common/buffer/Buffer.test.ts b/src/common/buffer/Buffer.test.ts index 35aade4a..e3a724fa 100644 --- a/src/common/buffer/Buffer.test.ts +++ b/src/common/buffer/Buffer.test.ts @@ -1085,6 +1085,20 @@ describe('Buffer', () => { assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); }); + it('should call onDispose', () => { + const eventStack: string[] = []; + buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); + buffer.fillViewportRows(); + assert.equal(buffer.markers.length, 0); + const marker = buffer.addMarker(0); + marker.onDispose(() => eventStack.push('disposed')); + assert.equal(marker.isDisposed, false); + assert.equal(buffer.markers.length, 1); + buffer.lines.onTrimEmitter.fire(1); + assert.equal(marker.isDisposed, true); + assert.equal(buffer.markers.length, 0); + assert.deepEqual(eventStack, ['disposed']); + }); }); describe ('translateBufferLineToString', () => { diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 9a4256d3..72c4085c 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -32,5 +32,6 @@ export class Marker extends Disposable implements IMarker { this.line = -1; // Emit before super.dispose such that dispose listeners get a change to react this._onDispose.fire(); + super.dispose(); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 1fc0c853..b5a736a7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -382,6 +382,13 @@ declare module 'xterm' { * -1 if the marker has been disposed. */ readonly line: number; + + /** + * Event listener to get notified when the marker gets disposed. Automatic disposal + * might happen for a marker, that got invalidated by scrolling out or removal of + * a line from the buffer. + */ + onDispose: IEvent; } /**