Skip to content

Commit

Permalink
Update markers after a reflow
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyriar committed Dec 31, 2018
1 parent 40e8618 commit 840970e
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 10 deletions.
118 changes: 118 additions & 0 deletions src/Buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,124 @@ describe('Buffer', () => {
assert.equal(buffer.lines.get(0).translateToString(), 'ab');
assert.equal(buffer.lines.get(1).translateToString(), 'c😁');
});
it('should adjust markers when reflowing', () => {
buffer.fillViewportRows();
buffer.resize(10, 15);
for (let i = 0; i < 10; i++) {
const code = 'a'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
buffer.lines.get(0).set(i, [null, char, 1, code]);
}
for (let i = 0; i < 10; i++) {
const code = '0'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
buffer.lines.get(1).set(i, [null, char, 1, code]);
}
for (let i = 0; i < 10; i++) {
const code = 'k'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
buffer.lines.get(2).set(i, [null, char, 1, code]);
}
// Buffer:
// abcdefghij
// 0123456789
// abcdefghij
const firstMarker = buffer.addMarker(0);
const secondMarker = buffer.addMarker(1);
const thirdMarker = buffer.addMarker(2);
assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij');
assert.equal(buffer.lines.get(1).translateToString(), '0123456789');
assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst');
assert.equal(firstMarker.line, 0);
assert.equal(secondMarker.line, 1);
assert.equal(thirdMarker.line, 2);
buffer.resize(2, 15);
assert.equal(buffer.lines.get(0).translateToString(), 'ab');
assert.equal(buffer.lines.get(1).translateToString(), 'cd');
assert.equal(buffer.lines.get(2).translateToString(), 'ef');
assert.equal(buffer.lines.get(3).translateToString(), 'gh');
assert.equal(buffer.lines.get(4).translateToString(), 'ij');
assert.equal(buffer.lines.get(5).translateToString(), '01');
assert.equal(buffer.lines.get(6).translateToString(), '23');
assert.equal(buffer.lines.get(7).translateToString(), '45');
assert.equal(buffer.lines.get(8).translateToString(), '67');
assert.equal(buffer.lines.get(9).translateToString(), '89');
assert.equal(buffer.lines.get(10).translateToString(), 'kl');
assert.equal(buffer.lines.get(11).translateToString(), 'mn');
assert.equal(buffer.lines.get(12).translateToString(), 'op');
assert.equal(buffer.lines.get(13).translateToString(), 'qr');
assert.equal(buffer.lines.get(14).translateToString(), 'st');
assert.equal(firstMarker.line, 0, 'first marker should remain unchanged');
assert.equal(secondMarker.line, 5, 'second marker should be shifted since the first line wrapped');
assert.equal(thirdMarker.line, 10, 'third marker should be shifted since the first and second lines wrapped');
buffer.resize(10, 15);
assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij');
assert.equal(buffer.lines.get(1).translateToString(), '0123456789');
assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst');
assert.equal(firstMarker.line, 0, 'first marker should remain unchanged');
assert.equal(secondMarker.line, 1, 'second marker should be restored to it\'s original line');
assert.equal(thirdMarker.line, 2, 'third marker should be restored to it\'s original line');
assert.equal(firstMarker.isDisposed, false);
assert.equal(secondMarker.isDisposed, false);
assert.equal(thirdMarker.isDisposed, false);
});
it('should dispose markers whose rows are trimmed during a reflow', () => {
buffer.fillViewportRows();
terminal.options.scrollback = 1;
buffer.resize(10, 10);
for (let i = 0; i < 10; i++) {
const code = 'a'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
buffer.lines.get(0).set(i, [null, char, 1, code]);
}
for (let i = 0; i < 10; i++) {
const code = '0'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
buffer.lines.get(1).set(i, [null, char, 1, code]);
}
for (let i = 0; i < 10; i++) {
const code = 'k'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
buffer.lines.get(2).set(i, [null, char, 1, code]);
}
// Buffer:
// abcdefghij
// 0123456789
// abcdefghij
const firstMarker = buffer.addMarker(0);
const secondMarker = buffer.addMarker(1);
const thirdMarker = buffer.addMarker(2);
buffer.y = 2;
assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij');
assert.equal(buffer.lines.get(1).translateToString(), '0123456789');
assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst');
assert.equal(firstMarker.line, 0);
assert.equal(secondMarker.line, 1);
assert.equal(thirdMarker.line, 2);
buffer.resize(2, 10);
assert.equal(buffer.lines.get(0).translateToString(), 'ij');
assert.equal(buffer.lines.get(1).translateToString(), '01');
assert.equal(buffer.lines.get(2).translateToString(), '23');
assert.equal(buffer.lines.get(3).translateToString(), '45');
assert.equal(buffer.lines.get(4).translateToString(), '67');
assert.equal(buffer.lines.get(5).translateToString(), '89');
assert.equal(buffer.lines.get(6).translateToString(), 'kl');
assert.equal(buffer.lines.get(7).translateToString(), 'mn');
assert.equal(buffer.lines.get(8).translateToString(), 'op');
assert.equal(buffer.lines.get(9).translateToString(), 'qr');
assert.equal(buffer.lines.get(10).translateToString(), 'st');
assert.equal(secondMarker.line, 1, 'second marker should remain the same as it was shifted 4 and trimmed 4');
assert.equal(thirdMarker.line, 6, 'third marker should be shifted since the first and second lines wrapped');
assert.equal(firstMarker.isDisposed, true, 'first marker was trimmed');
assert.equal(secondMarker.isDisposed, false);
assert.equal(thirdMarker.isDisposed, false);
buffer.resize(10, 10);
assert.equal(buffer.lines.get(0).translateToString(), 'ij ');
assert.equal(buffer.lines.get(1).translateToString(), '0123456789');
assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst');
assert.equal(secondMarker.line, 1, 'second marker should be restored');
assert.equal(thirdMarker.line, 2, 'third marker should be restored');
});
});
});

Expand Down
54 changes: 49 additions & 5 deletions src/Buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @license MIT
*/

import { CircularList } from './common/CircularList';
import { CircularList, IInsertEvent, IDeleteEvent } from './common/CircularList';
import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, IBufferLineConstructor } from './Types';
import { EventEmitter } from './common/EventEmitter';
import { IMarker } from 'xterm';
Expand Down Expand Up @@ -238,7 +238,7 @@ export class Buffer implements IBuffer {

this.scrollBottom = newRows - 1;

if (this.hasScrollback && this._bufferLineConstructor === BufferLine) {
if (this._hasScrollback && this._bufferLineConstructor === BufferLine) {
this._reflow(newCols);

// Trim the end of the line off if cols shrunk
Expand Down Expand Up @@ -338,6 +338,13 @@ export class Buffer implements IBuffer {
for (let i = 0; i < this.lines.length; i++) {
if (nextToRemoveStart === i) {
const countToRemove = toRemove[++nextToRemoveIndex];

// Tell markers that there was a deletion
this.lines.emit('delete', {
index: i - countRemovedSoFar,
amount: countToRemove
} as IDeleteEvent);

i += countToRemove - 1;
countRemovedSoFar += countToRemove;
nextToRemoveStart = toRemove[++nextToRemoveIndex];
Expand Down Expand Up @@ -474,6 +481,10 @@ export class Buffer implements IBuffer {
// than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
// costly calls to CircularList.splice.
if (toInsert.length > 0) {
// Record buffer insert events and then play them back backwards so that the indexes are
// correct
const insertEvents: IInsertEvent[] = [];

// Record original lines so they don't get overridden when we rearrange the list
const originalLines: BufferLine[] = [];
for (let i = 0; i < this.lines.length; i++) {
Expand All @@ -492,14 +503,32 @@ export class Buffer implements IBuffer {
for (let nextI = nextToInsert.newLines.length - 1; nextI >= 0; nextI--) {
this.lines.set(i--, nextToInsert.newLines[nextI]);
}
i++; // Don't skip for the first row
i++;

// Create insert events for later
insertEvents.push({
index: originalLineIndex + 1,
amount: nextToInsert.newLines.length
} as IInsertEvent);

countInsertedSoFar += nextToInsert.newLines.length;
nextToInsert = toInsert[++nextToInsertIndex];
} else {
this.lines.set(i, originalLines[originalLineIndex--]);
}
}
// TODO: Throw trim event

// Update markers
let insertCountEmitted = 0;
for (let i = insertEvents.length - 1; i >= 0; i--) {
insertEvents[i].index += insertCountEmitted;
this.lines.emit('insert', insertEvents[i]);
insertCountEmitted += insertEvents[i].amount;
}
const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength);
if (amountToTrim > 0) {
this.lines.emitMayRemoveListeners('trim', amountToTrim);
}
}
}

Expand Down Expand Up @@ -618,12 +647,27 @@ export class Buffer implements IBuffer {
marker.dispose();
}
}));
marker.register(this.lines.addDisposableListener('insert', (event: IInsertEvent) => {
if (marker.line >= event.index) {
marker.line += event.amount;
}
}));
marker.register(this.lines.addDisposableListener('delete', (event: IDeleteEvent) => {
// Delete the marker if it's within the range
if (marker.line >= event.index && marker.line < event.index + event.amount) {
marker.dispose();
}

// Shift the marker if it's after the deleted range
if (marker.line > event.index) {
marker.line -= event.amount;
}
}));
marker.register(marker.addDisposableListener('dispose', () => this._removeMarker(marker)));
return marker;
}

private _removeMarker(marker: Marker): void {
// TODO: This could probably be optimized by relying on sort order and trimming the array using .length
this.markers.splice(this.markers.indexOf(marker), 1);
}

Expand Down
20 changes: 15 additions & 5 deletions src/common/CircularList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
import { EventEmitter } from './EventEmitter';
import { ICircularList } from './Types';

export interface IInsertEvent {
index: number;
amount: number;
}

export interface IDeleteEvent {
index: number;
amount: number;
}

/**
* 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.
Expand Down Expand Up @@ -91,7 +101,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.emit('trim', 1);
this.emitMayRemoveListeners('trim', 1);
} else {
this._length++;
}
Expand All @@ -107,7 +117,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.emit('trim', 1);
this.emitMayRemoveListeners('trim', 1);
return this._array[this._getCyclicIndex(this._length - 1)]!;
}

Expand Down Expand Up @@ -158,7 +168,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.emit('trim', countToTrim);
this.emitMayRemoveListeners('trim', countToTrim);
} else {
this._length += items.length;
}
Expand All @@ -175,7 +185,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
}
this._startIndex += count;
this._length -= count;
this.emit('trim', count);
this.emitMayRemoveListeners('trim', count);
}

public shiftElements(start: number, count: number, offset: number): void {
Expand All @@ -199,7 +209,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
while (this._length > this._maxLength) {
this._length--;
this._startIndex++;
this.emit('trim', 1);
this.emitMayRemoveListeners('trim', 1);
}
}
} else {
Expand Down
13 changes: 13 additions & 0 deletions src/common/EventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ export class EventEmitter extends Disposable implements IEventEmitter, IDisposab
}
}

public emitMayRemoveListeners(type: string, ...args: any[]): void {
if (!this._events[type]) {
return;
}
const obj = this._events[type];
let length = obj.length;
for (let i = 0; i < obj.length; i++) {
obj[i].apply(this, args);
i -= length - obj.length;
length = obj.length;
}
}

public listeners(type: string): XtermListener[] {
return this._events[type] || [];
}
Expand Down

0 comments on commit 840970e

Please sign in to comment.