]> git.proxmox.com Git - mirror_xterm.js.git/blame - src/utils/CircularList.ts
Resolve TODOs
[mirror_xterm.js.git] / src / utils / CircularList.ts
CommitLineData
49b76baf
DI
1/**
2 * Represents a circular list; a list with a maximum size that wraps around when push is called,
3 * overriding values at the start of the list.
50cfe2b2
DI
4 * @module xterm/utils/CircularList
5 * @license MIT
49b76baf 6 */
70fda994
DI
7import { EventEmitter } from '../EventEmitter';
8
207c4cf9 9// TODO: Do we need the ID here?
65256c87
DI
10interface ListEntry<T> {
11 id: number;
12 value: T;
13}
14
70fda994 15export class CircularList<T> extends EventEmitter {
65256c87 16 private _array: ListEntry<T>[];
49b76baf
DI
17 private _startIndex: number;
18 private _length: number;
19
65256c87
DI
20 private _nextId = 0;
21
49b76baf 22 constructor(maxLength: number) {
70fda994 23 super();
65256c87 24 this._array = new Array<ListEntry<T>>(maxLength);
49b76baf
DI
25 this._startIndex = 0;
26 this._length = 0;
27 }
28
29 public get maxLength(): number {
30 return this._array.length;
31 }
32
33 public set maxLength(newMaxLength: number) {
34 // Reconstruct array, starting at index 0. Only transfer values from the
35 // indexes 0 to length.
65256c87
DI
36 let newArray = new Array<ListEntry<T>>(newMaxLength);
37 // Reset ids when maxLength is changed
38 this._nextId = 0;
49b76baf 39 for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {
65256c87
DI
40 newArray[i] = {
41 id: this._nextId++,
42 value: this._array[this._getCyclicIndex(i)].value
43 };
49b76baf
DI
44 }
45 this._array = newArray;
46 this._startIndex = 0;
47 }
48
49 public get length(): number {
50 return this._length;
51 }
52
53 public set length(newLength: number) {
49b76baf
DI
54 if (newLength > this._length) {
55 for (let i = this._length; i < newLength; i++) {
56 this._array[i] = undefined;
57 }
58 }
59 this._length = newLength;
60 }
61
65256c87
DI
62 public get forEach(): (callbackfn: (value: T, index: number) => void) => void {
63 return (callbackfn: (value: T, index: number) => void) => {
64 let i = 0;
65 let length = this.length;
66 for (let i = 0; i < length; i++) {
67 callbackfn(this.get(i), i);
68 }
69 };
49b76baf
DI
70 }
71
72 /**
73 * Gets the value at an index.
74 *
75 * Note that for performance reasons there is no bounds checking here, the index reference is
76 * circular so this should always return a value and never throw.
77 * @param index The index of the value to get.
78 * @return The value corresponding to the index.
79 */
80 public get(index: number): T {
65256c87
DI
81 return this.getEntry(index).value;
82 }
83
84 public getEntry(index: number): ListEntry<T> {
49b76baf
DI
85 return this._array[this._getCyclicIndex(index)];
86 }
87
88 /**
89 * Sets the value at an index.
90 *
91 * Note that for performance reasons there is no bounds checking here, the index reference is
92 * circular so this should always return a value and never throw.
93 * @param index The index to set.
94 * @param value The value to set.
95 */
96 public set(index: number, value: T): void {
65256c87
DI
97 this._array[this._getCyclicIndex(index)].value = value;
98 }
99
100 private _setEntry(index: number, entry: ListEntry<T>): void {
101 this._array[this._getCyclicIndex(index)] = entry;
49b76baf
DI
102 }
103
104 /**
105 * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0
106 * if the maximum length is reached.
107 * @param value The value to push onto the list.
108 */
109 public push(value: T): void {
65256c87
DI
110 this._array[this._getCyclicIndex(this._length)] = {
111 id: this._nextId,
112 value
113 };
49b76baf
DI
114 if (this._length === this.maxLength) {
115 this._startIndex++;
3b35d12e
DI
116 if (this._startIndex === this.maxLength) {
117 this._startIndex = 0;
118 }
70fda994 119 this.emit('trim', 1);
49b76baf
DI
120 } else {
121 this._length++;
122 }
123 }
124
bfaf9cd6
DI
125 /**
126 * Removes and returns the last value on the list.
127 * @return The popped value.
128 */
61fbbb00 129 public pop(): T {
65256c87 130 return this._array[this._getCyclicIndex(this._length-- - 1)].value;
61fbbb00
DI
131 }
132
bfaf9cd6
DI
133 /**
134 * Deletes and/or inserts items at a particular index (in that order). Unlike
135 * Array.prototype.splice, this operation does not return the deleted items as a new array in
136 * order to save creating a new array. Note that this operation may shift all values in the list
137 * in the worst case.
138 * @param start The index to delete and/or insert.
139 * @param deleteCount The number of elements to delete.
140 * @param items The items to insert.
141 */
be2e4b5d 142 public splice(start: number, deleteCount: number, ...items: T[]): void {
65256c87 143 // Delete items
61fbbb00
DI
144 if (deleteCount) {
145 for (let i = start; i < this._length - deleteCount; i++) {
146 this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];
147 }
148 this._length -= deleteCount;
149 }
65256c87 150
61fbbb00 151 if (items && items.length) {
65256c87 152 // Add items
61fbbb00
DI
153 for (let i = this._length - 1; i >= start; i--) {
154 this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
155 }
156 for (let i = 0; i < items.length; i++) {
65256c87
DI
157 this._array[this._getCyclicIndex(start + i)] = {
158 id: this._nextId,
159 value: items[i]
160 };
61fbbb00
DI
161 }
162
65256c87 163 // Adjust length as needed
61fbbb00 164 if (this._length + items.length > this.maxLength) {
70fda994
DI
165 const countToTrim = (this._length + items.length) - this.maxLength;
166 this._startIndex += countToTrim;
61fbbb00 167 this._length = this.maxLength;
70fda994 168 this.emit('trim', countToTrim);
61fbbb00
DI
169 } else {
170 this._length += items.length;
171 }
172 }
173 }
174
bfaf9cd6
DI
175 /**
176 * Trims a number of items from the start of the list.
177 * @param count The number of items to remove.
178 */
3b35d12e 179 public trimStart(count: number): void {
bfaf9cd6
DI
180 if (count > this._length) {
181 count = this._length;
182 }
3b35d12e 183 this._startIndex += count;
bfaf9cd6 184 this._length -= count;
70fda994 185 this.emit('trim', count);
3b35d12e
DI
186 }
187
969a2e95
DI
188 public shiftElements(start: number, count: number, offset: number): void {
189 if (count <= 0) {
190 return;
191 }
192 if (start < 0 || start >= this._length) {
193 throw new Error('start argument out of range');
194 }
195 if (start + offset < 0) {
196 throw new Error('Cannot shift elements in list beyond index 0');
197 }
198
199 if (offset > 0) {
200 for (let i = count - 1; i >= 0; i--) {
fe11837a
DI
201 this._setEntry(start + i + offset, {
202 id: this._nextId++,
203 value: this.get(start + i)
204 });
969a2e95
DI
205 }
206 const expandListBy = (start + count + offset) - this._length;
207 if (expandListBy > 0) {
208 this._length += expandListBy;
209 while (this._length > this.maxLength) {
210 this._length--;
211 this._startIndex++;
70fda994 212 this.emit('trim', 1);
969a2e95
DI
213 }
214 }
215 } else {
216 for (let i = 0; i < count; i++) {
217 this.set(start + i + offset, this.get(start + i));
218 }
219 }
220 }
221
bfaf9cd6
DI
222 /**
223 * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
224 * backing array to get the element associated with the regular index.
225 * @param index The regular index.
226 * @returns The cyclic index.
227 */
49b76baf
DI
228 private _getCyclicIndex(index: number): number {
229 return (this._startIndex + index) % this.maxLength;
230 }
231}