--- /dev/null
+import { assert } from 'chai';
+import { CircularList } from './CircularList';
+
+describe('CircularList', () => {
+ describe('push', () => {
+ it('should push values onto the array', () => {
+ const list = new CircularList<string>(5);
+ list.push('1');
+ list.push('2');
+ list.push('3');
+ list.push('4');
+ list.push('5');
+ assert.equal(list.get(0), '1');
+ assert.equal(list.get(1), '2');
+ assert.equal(list.get(2), '3');
+ assert.equal(list.get(3), '4');
+ assert.equal(list.get(4), '5');
+ });
+
+ it('should push old values from the start out of the array when max length is reached', () => {
+ const list = new CircularList<string>(2);
+ list.push('1');
+ list.push('2');
+ assert.equal(list.get(0), '1');
+ assert.equal(list.get(1), '2');
+ list.push('3');
+ assert.equal(list.get(0), '2');
+ assert.equal(list.get(1), '3');
+ list.push('4');
+ assert.equal(list.get(0), '3');
+ assert.equal(list.get(1), '4');
+ });
+ });
+
+ describe('maxLength', () => {
+ it('should increase the size of the list', () => {
+ const list = new CircularList<string>(2);
+ list.push('1');
+ list.push('2');
+ assert.equal(list.get(0), '1');
+ assert.equal(list.get(1), '2');
+ list.maxLength = 4;
+ list.push('3');
+ list.push('4');
+ assert.equal(list.get(0), '1');
+ assert.equal(list.get(1), '2');
+ assert.equal(list.get(2), '3');
+ assert.equal(list.get(3), '4');
+ list.push('wrapped');
+ assert.equal(list.get(0), '2');
+ assert.equal(list.get(1), '3');
+ assert.equal(list.get(2), '4');
+ assert.equal(list.get(3), 'wrapped');
+ });
+
+ it('should return the maximum length of the list', () => {
+ const list = new CircularList<string>(2);
+ assert.equal(list.maxLength, 2);
+ list.push('1');
+ list.push('2');
+ assert.equal(list.maxLength, 2);
+ list.push('3');
+ assert.equal(list.maxLength, 2);
+ list.maxLength = 4;
+ assert.equal(list.maxLength, 4);
+ });
+ });
+
+ describe('length', () => {
+ it('should return the current length of the list, capped at the maximum length', () => {
+ const list = new CircularList<string>(2);
+ assert.equal(list.length, 0);
+ list.push('1');
+ assert.equal(list.length, 1);
+ list.push('2');
+ assert.equal(list.length, 2);
+ list.push('3');
+ assert.equal(list.length, 2);
+ });
+ });
+});
--- /dev/null
+/**
+ * xterm.js: xterm, in the browser
+ * Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License)
+ */
+
+/**
+ * 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> {
+ private _array: T[];
+ private _startIndex: number;
+ private _length: number;
+
+ constructor(maxLength: number) {
+ this._array = new Array<T>(maxLength);
+ this._startIndex = 0;
+ this._length = 0;
+ }
+
+ public get maxLength(): number {
+ return this._array.length;
+ }
+
+ public set maxLength(newMaxLength: number) {
+ // Reconstruct array, starting at index 0. Only transfer values from the
+ // indexes 0 to length.
+ let newArray = new Array<T>(newMaxLength);
+ for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {
+ newArray[i] = this._array[this._getCyclicIndex(i)];
+ }
+ this._array = newArray;
+ this._startIndex = 0;
+ }
+
+ public get length(): number {
+ return this._length;
+ }
+
+ public set length(newLength: number) {
+ // TODO: Is this auto fill is needed or can it be
+ if (newLength > this._length) {
+ for (let i = this._length; i < newLength; i++) {
+ this._array[i] = undefined;
+ }
+ }
+ this._length = newLength;
+ }
+
+ public get forEach(): (callbackfn: (value: T, index: number, array: T[]) => void) => void {
+ return this._array.forEach;
+ }
+
+ /**
+ * Gets the value at an index.
+ *
+ * Note that for performance reasons there is no bounds checking here, the index reference is
+ * circular so this should always return a value and never throw.
+ * @param index The index of the value to get.
+ * @return The value corresponding to the index.
+ */
+ public get(index: number): T {
+ return this._array[this._getCyclicIndex(index)];
+ }
+
+ /**
+ * Sets the value at an index.
+ *
+ * Note that for performance reasons there is no bounds checking here, the index reference is
+ * circular so this should always return a value and never throw.
+ * @param index The index to set.
+ * @param value The value to set.
+ */
+ public set(index: number, value: T): void {
+ this._array[this._getCyclicIndex(index)] = value;
+ }
+
+ /**
+ * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0
+ * if the maximum length is reached.
+ * @param value The value to push onto the list.
+ */
+ public push(value: T): void {
+ this._array[this._getCyclicIndex(this._length)] = value;
+ if (this._length === this.maxLength) {
+ this._startIndex++;
+ } else {
+ this._length++;
+ }
+ }
+
+ private _getCyclicIndex(index: number): number {
+ return (this._startIndex + index) % this.maxLength;
+ }
+}