Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/common/history.ts
3291 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { SetWithKey } from './collections.js';
7
import { Event } from './event.js';
8
import { IDisposable } from './lifecycle.js';
9
import { ArrayNavigator, INavigator } from './navigator.js';
10
11
export interface IHistory<T> {
12
delete(t: T): boolean;
13
add(t: T): this;
14
has(t: T): boolean;
15
clear(): void;
16
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void;
17
replace?(t: T[]): void;
18
onDidChange?: Event<string[]>;
19
}
20
21
export class HistoryNavigator<T> implements INavigator<T> {
22
private _limit: number;
23
private _navigator!: ArrayNavigator<T>;
24
private _disposable: IDisposable | undefined;
25
26
constructor(
27
private _history: IHistory<T> = new Set(),
28
limit: number = 10,
29
) {
30
this._limit = limit;
31
this._onChange();
32
if (this._history.onDidChange) {
33
this._disposable = this._history.onDidChange(() => this._onChange());
34
}
35
}
36
37
public getHistory(): T[] {
38
return this._elements;
39
}
40
41
public add(t: T) {
42
this._history.delete(t);
43
this._history.add(t);
44
this._onChange();
45
}
46
47
public next(): T | null {
48
// This will navigate past the end of the last element, and in that case the input should be cleared
49
return this._navigator.next();
50
}
51
52
public previous(): T | null {
53
if (this._currentPosition() !== 0) {
54
return this._navigator.previous();
55
}
56
return null;
57
}
58
59
public current(): T | null {
60
return this._navigator.current();
61
}
62
63
public first(): T | null {
64
return this._navigator.first();
65
}
66
67
public last(): T | null {
68
return this._navigator.last();
69
}
70
71
public isFirst(): boolean {
72
return this._currentPosition() === 0;
73
}
74
75
public isLast(): boolean {
76
return this._currentPosition() >= this._elements.length - 1;
77
}
78
79
public isNowhere(): boolean {
80
return this._navigator.current() === null;
81
}
82
83
public has(t: T): boolean {
84
return this._history.has(t);
85
}
86
87
public clear(): void {
88
this._history.clear();
89
this._onChange();
90
}
91
92
private _onChange() {
93
this._reduceToLimit();
94
const elements = this._elements;
95
this._navigator = new ArrayNavigator(elements, 0, elements.length, elements.length);
96
}
97
98
private _reduceToLimit() {
99
const data = this._elements;
100
if (data.length > this._limit) {
101
const replaceValue = data.slice(data.length - this._limit);
102
if (this._history.replace) {
103
this._history.replace(replaceValue);
104
} else {
105
this._history = new Set(replaceValue);
106
}
107
}
108
}
109
110
private _currentPosition(): number {
111
const currentElement = this._navigator.current();
112
if (!currentElement) {
113
return -1;
114
}
115
116
return this._elements.indexOf(currentElement);
117
}
118
119
private get _elements(): T[] {
120
const elements: T[] = [];
121
this._history.forEach(e => elements.push(e));
122
return elements;
123
}
124
125
public dispose(): void {
126
if (this._disposable) {
127
this._disposable.dispose();
128
this._disposable = undefined;
129
}
130
}
131
}
132
133
interface HistoryNode<T> {
134
value: T;
135
previous: HistoryNode<T> | undefined;
136
next: HistoryNode<T> | undefined;
137
}
138
139
/**
140
* The right way to use HistoryNavigator2 is for the last item in the list to be the user's uncommitted current text. eg empty string, or whatever has been typed. Then
141
* the user can navigate away from the last item through the list, and back to it. When updating the last item, call replaceLast.
142
*/
143
export class HistoryNavigator2<T> {
144
145
private valueSet: Set<T>;
146
private head: HistoryNode<T>;
147
private tail: HistoryNode<T>;
148
private cursor: HistoryNode<T>;
149
private _size: number;
150
get size(): number { return this._size; }
151
152
constructor(history: readonly T[], private capacity: number = 10, private identityFn: (t: T) => unknown = t => t) {
153
if (history.length < 1) {
154
throw new Error('not supported');
155
}
156
157
this._size = 1;
158
this.head = this.tail = this.cursor = {
159
value: history[0],
160
previous: undefined,
161
next: undefined
162
};
163
164
this.valueSet = new SetWithKey<T>([history[0]], identityFn);
165
for (let i = 1; i < history.length; i++) {
166
this.add(history[i]);
167
}
168
}
169
170
add(value: T): void {
171
const node: HistoryNode<T> = {
172
value,
173
previous: this.tail,
174
next: undefined
175
};
176
177
this.tail.next = node;
178
this.tail = node;
179
this.cursor = this.tail;
180
this._size++;
181
182
if (this.valueSet.has(value)) {
183
this._deleteFromList(value);
184
} else {
185
this.valueSet.add(value);
186
}
187
188
while (this._size > this.capacity) {
189
this.valueSet.delete(this.head.value);
190
191
this.head = this.head.next!;
192
this.head.previous = undefined;
193
this._size--;
194
}
195
}
196
197
/**
198
* @returns old last value
199
*/
200
replaceLast(value: T): T {
201
if (this.identityFn(this.tail.value) === this.identityFn(value)) {
202
return value;
203
}
204
205
const oldValue = this.tail.value;
206
this.valueSet.delete(oldValue);
207
this.tail.value = value;
208
209
if (this.valueSet.has(value)) {
210
this._deleteFromList(value);
211
} else {
212
this.valueSet.add(value);
213
}
214
215
return oldValue;
216
}
217
218
prepend(value: T): void {
219
if (this._size === this.capacity || this.valueSet.has(value)) {
220
return;
221
}
222
223
const node: HistoryNode<T> = {
224
value,
225
previous: undefined,
226
next: this.head
227
};
228
229
this.head.previous = node;
230
this.head = node;
231
this._size++;
232
233
this.valueSet.add(value);
234
}
235
236
isAtEnd(): boolean {
237
return this.cursor === this.tail;
238
}
239
240
current(): T {
241
return this.cursor.value;
242
}
243
244
previous(): T {
245
if (this.cursor.previous) {
246
this.cursor = this.cursor.previous;
247
}
248
249
return this.cursor.value;
250
}
251
252
next(): T {
253
if (this.cursor.next) {
254
this.cursor = this.cursor.next;
255
}
256
257
return this.cursor.value;
258
}
259
260
has(t: T): boolean {
261
return this.valueSet.has(t);
262
}
263
264
resetCursor(): T {
265
this.cursor = this.tail;
266
return this.cursor.value;
267
}
268
269
*[Symbol.iterator](): Iterator<T> {
270
let node: HistoryNode<T> | undefined = this.head;
271
272
while (node) {
273
yield node.value;
274
node = node.next;
275
}
276
}
277
278
private _deleteFromList(value: T): void {
279
let temp = this.head;
280
281
const valueKey = this.identityFn(value);
282
while (temp !== this.tail) {
283
if (this.identityFn(temp.value) === valueKey) {
284
if (temp === this.head) {
285
this.head = this.head.next!;
286
this.head.previous = undefined;
287
} else {
288
temp.previous!.next = temp.next;
289
temp.next!.previous = temp.previous;
290
}
291
292
this._size--;
293
}
294
295
temp = temp.next!;
296
}
297
}
298
}
299
300