2 * Copyright (c) 2016 The xterm.js authors. All rights reserved.
6 import { ICircularList } from 'common/Types';
7 import { EventEmitter, IEvent } from 'common/EventEmitter';
9 export interface IInsertEvent {
14 export interface IDeleteEvent {
20 * Represents a circular list; a list with a maximum size that wraps around when push is called,
21 * overriding values at the start of the list.
23 export class CircularList<T> implements ICircularList<T> {
24 protected _array: (T | undefined)[];
25 private _startIndex: number;
26 private _length: number;
28 public onDeleteEmitter = new EventEmitter<IDeleteEvent>();
29 public get onDelete(): IEvent<IDeleteEvent> { return this.onDeleteEmitter.event; }
30 public onInsertEmitter = new EventEmitter<IInsertEvent>();
31 public get onInsert(): IEvent<IInsertEvent> { return this.onInsertEmitter.event; }
32 public onTrimEmitter = new EventEmitter<number>();
33 public get onTrim(): IEvent<number> { return this.onTrimEmitter.event; }
36 private _maxLength: number
38 this._array = new Array<T>(this._maxLength);
43 public get maxLength(): number {
44 return this._maxLength;
47 public set maxLength(newMaxLength: number) {
48 // There was no change in maxLength, return early.
49 if (this._maxLength === newMaxLength) {
53 // Reconstruct array, starting at index 0. Only transfer values from the
54 // indexes 0 to length.
55 const newArray = new Array<T | undefined>(newMaxLength);
56 for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {
57 newArray[i] = this._array[this._getCyclicIndex(i)];
59 this._array = newArray;
60 this._maxLength = newMaxLength;
64 public get length(): number {
68 public set length(newLength: number) {
69 if (newLength > this._length) {
70 for (let i = this._length; i < newLength; i++) {
71 this._array[i] = undefined;
74 this._length = newLength;
78 * Gets the value at an index.
80 * Note that for performance reasons there is no bounds checking here, the index reference is
81 * circular so this should always return a value and never throw.
82 * @param index The index of the value to get.
83 * @return The value corresponding to the index.
85 public get(index: number): T | undefined {
86 return this._array[this._getCyclicIndex(index)];
90 * Sets the value at an index.
92 * Note that for performance reasons there is no bounds checking here, the index reference is
93 * circular so this should always return a value and never throw.
94 * @param index The index to set.
95 * @param value The value to set.
97 public set(index: number, value: T | undefined): void {
98 this._array[this._getCyclicIndex(index)] = value;
102 * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0
103 * if the maximum length is reached.
104 * @param value The value to push onto the list.
106 public push(value: T): void {
107 this._array[this._getCyclicIndex(this._length)] = value;
108 if (this._length === this._maxLength) {
109 this._startIndex = ++this._startIndex % this._maxLength;
110 this.onTrimEmitter.fire(1);
117 * Advance ringbuffer index and return current element for recycling.
118 * Note: The buffer must be full for this method to work.
119 * @throws When the buffer is not full.
121 public recycle(): T {
122 if (this._length !== this._maxLength) {
123 throw new Error('Can only recycle when the buffer is full');
125 this._startIndex = ++this._startIndex % this._maxLength;
126 this.onTrimEmitter.fire(1);
127 return this._array[this._getCyclicIndex(this._length - 1)]!;
131 * Ringbuffer is at max length.
133 public get isFull(): boolean {
134 return this._length === this._maxLength;
138 * Removes and returns the last value on the list.
139 * @return The popped value.
141 public pop(): T | undefined {
142 return this._array[this._getCyclicIndex(this._length-- - 1)];
146 * Deletes and/or inserts items at a particular index (in that order). Unlike
147 * Array.prototype.splice, this operation does not return the deleted items as a new array in
148 * order to save creating a new array. Note that this operation may shift all values in the list
150 * @param start The index to delete and/or insert.
151 * @param deleteCount The number of elements to delete.
152 * @param items The items to insert.
154 public splice(start: number, deleteCount: number, ...items: T[]): void {
157 for (let i = start; i < this._length - deleteCount; i++) {
158 this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];
160 this._length -= deleteCount;
164 for (let i = this._length - 1; i >= start; i--) {
165 this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
167 for (let i = 0; i < items.length; i++) {
168 this._array[this._getCyclicIndex(start + i)] = items[i];
171 // Adjust length as needed
172 if (this._length + items.length > this._maxLength) {
173 const countToTrim = (this._length + items.length) - this._maxLength;
174 this._startIndex += countToTrim;
175 this._length = this._maxLength;
176 this.onTrimEmitter.fire(countToTrim);
178 this._length += items.length;
183 * Trims a number of items from the start of the list.
184 * @param count The number of items to remove.
186 public trimStart(count: number): void {
187 if (count > this._length) {
188 count = this._length;
190 this._startIndex += count;
191 this._length -= count;
192 this.onTrimEmitter.fire(count);
195 public shiftElements(start: number, count: number, offset: number): void {
199 if (start < 0 || start >= this._length) {
200 throw new Error('start argument out of range');
202 if (start + offset < 0) {
203 throw new Error('Cannot shift elements in list beyond index 0');
207 for (let i = count - 1; i >= 0; i--) {
208 this.set(start + i + offset, this.get(start + i));
210 const expandListBy = (start + count + offset) - this._length;
211 if (expandListBy > 0) {
212 this._length += expandListBy;
213 while (this._length > this._maxLength) {
216 this.onTrimEmitter.fire(1);
220 for (let i = 0; i < count; i++) {
221 this.set(start + i + offset, this.get(start + i));
227 * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
228 * backing array to get the element associated with the regular index.
229 * @param index The regular index.
230 * @returns The cyclic index.
232 private _getCyclicIndex(index: number): number {
233 return (this._startIndex + index) % this._maxLength;