Skip to content

Commit 2836a92

Browse files
authored
History - ensure that history items a unique (microsoft#178694)
* History - ensure that history items a unique * Pull request feedback * Add tests * Add set to track the values in the history * More tweaks
1 parent 48d3110 commit 2836a92

File tree

2 files changed

+136
-9
lines changed

2 files changed

+136
-9
lines changed

src/vs/base/common/history.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ interface HistoryNode<T> {
118118

119119
export class HistoryNavigator2<T> {
120120

121+
private valueSet: Set<T>;
121122
private head: HistoryNode<T>;
122123
private tail: HistoryNode<T>;
123124
private cursor: HistoryNode<T>;
@@ -135,6 +136,7 @@ export class HistoryNavigator2<T> {
135136
next: undefined
136137
};
137138

139+
this.valueSet = new Set<T>([history[0]]);
138140
for (let i = 1; i < history.length; i++) {
139141
this.add(history[i]);
140142
}
@@ -152,7 +154,15 @@ export class HistoryNavigator2<T> {
152154
this.cursor = this.tail;
153155
this.size++;
154156

157+
if (this.valueSet.has(value)) {
158+
this._deleteFromList(value);
159+
} else {
160+
this.valueSet.add(value);
161+
}
162+
155163
while (this.size > this.capacity) {
164+
this.valueSet.delete(this.head.value);
165+
156166
this.head = this.head.next!;
157167
this.head.previous = undefined;
158168
this.size--;
@@ -163,8 +173,20 @@ export class HistoryNavigator2<T> {
163173
* @returns old last value
164174
*/
165175
replaceLast(value: T): T {
176+
if (this.tail.value === value) {
177+
return value;
178+
}
179+
166180
const oldValue = this.tail.value;
181+
this.valueSet.delete(oldValue);
167182
this.tail.value = value;
183+
184+
if (this.valueSet.has(value)) {
185+
this._deleteFromList(value);
186+
} else {
187+
this.valueSet.add(value);
188+
}
189+
168190
return oldValue;
169191
}
170192

@@ -193,14 +215,7 @@ export class HistoryNavigator2<T> {
193215
}
194216

195217
has(t: T): boolean {
196-
let temp: HistoryNode<T> | undefined = this.head;
197-
while (temp) {
198-
if (temp.value === t) {
199-
return true;
200-
}
201-
temp = temp.next;
202-
}
203-
return false;
218+
return this.valueSet.has(t);
204219
}
205220

206221
resetCursor(): T {
@@ -216,4 +231,24 @@ export class HistoryNavigator2<T> {
216231
node = node.next;
217232
}
218233
}
234+
235+
private _deleteFromList(value: T): void {
236+
let temp = this.head;
237+
238+
while (temp !== this.tail) {
239+
if (temp.value === value) {
240+
if (temp === this.head) {
241+
this.head = this.head.next!;
242+
this.head.previous = undefined;
243+
} else {
244+
temp.previous!.next = temp.next;
245+
temp.next!.previous = temp.previous;
246+
}
247+
248+
this.size--;
249+
}
250+
251+
temp = temp.next!;
252+
}
253+
}
219254
}

src/vs/base/test/common/history.test.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55
import * as assert from 'assert';
6-
import { HistoryNavigator } from 'vs/base/common/history';
6+
import { HistoryNavigator, HistoryNavigator2 } from 'vs/base/common/history';
77

88
suite('History Navigator', () => {
99

@@ -176,3 +176,95 @@ suite('History Navigator', () => {
176176
return result;
177177
}
178178
});
179+
180+
suite('History Navigator 2', () => {
181+
182+
test('constructor', () => {
183+
const testObject = new HistoryNavigator2(['1', '2', '3', '4']);
184+
185+
assert.strictEqual(testObject.current(), '4');
186+
assert.strictEqual(testObject.isAtEnd(), true);
187+
});
188+
189+
test('constructor - initial history is not empty', () => {
190+
assert.throws(() => new HistoryNavigator2([]));
191+
});
192+
193+
test('constructor - capacity limit', () => {
194+
const testObject = new HistoryNavigator2(['1', '2', '3', '4'], 3);
195+
196+
assert.strictEqual(testObject.current(), '4');
197+
assert.strictEqual(testObject.isAtEnd(), true);
198+
assert.strictEqual(testObject.has('1'), false);
199+
});
200+
201+
test('constructor - duplicate values', () => {
202+
const testObject = new HistoryNavigator2(['1', '2', '3', '4', '3', '2', '1']);
203+
204+
assert.strictEqual(testObject.current(), '1');
205+
assert.strictEqual(testObject.isAtEnd(), true);
206+
});
207+
208+
test('navigation', () => {
209+
const testObject = new HistoryNavigator2(['1', '2', '3', '4']);
210+
211+
assert.strictEqual(testObject.current(), '4');
212+
assert.strictEqual(testObject.isAtEnd(), true);
213+
214+
assert.strictEqual(testObject.next(), '4');
215+
assert.strictEqual(testObject.previous(), '3');
216+
assert.strictEqual(testObject.previous(), '2');
217+
assert.strictEqual(testObject.previous(), '1');
218+
assert.strictEqual(testObject.previous(), '1');
219+
220+
assert.strictEqual(testObject.current(), '1');
221+
assert.strictEqual(testObject.next(), '2');
222+
assert.strictEqual(testObject.resetCursor(), '4');
223+
});
224+
225+
test('add', () => {
226+
const testObject = new HistoryNavigator2(['1', '2', '3', '4']);
227+
testObject.add('5');
228+
229+
assert.strictEqual(testObject.current(), '5');
230+
assert.strictEqual(testObject.isAtEnd(), true);
231+
});
232+
233+
test('add - existing value', () => {
234+
const testObject = new HistoryNavigator2(['1', '2', '3', '4']);
235+
testObject.add('2');
236+
237+
assert.strictEqual(testObject.current(), '2');
238+
assert.strictEqual(testObject.isAtEnd(), true);
239+
240+
assert.strictEqual(testObject.previous(), '4');
241+
assert.strictEqual(testObject.previous(), '3');
242+
assert.strictEqual(testObject.previous(), '1');
243+
});
244+
245+
test('replaceLast', () => {
246+
const testObject = new HistoryNavigator2(['1', '2', '3', '4']);
247+
testObject.replaceLast('5');
248+
249+
assert.strictEqual(testObject.current(), '5');
250+
assert.strictEqual(testObject.isAtEnd(), true);
251+
assert.strictEqual(testObject.has('4'), false);
252+
253+
assert.strictEqual(testObject.previous(), '3');
254+
assert.strictEqual(testObject.previous(), '2');
255+
assert.strictEqual(testObject.previous(), '1');
256+
});
257+
258+
test('replaceLast - existing value', () => {
259+
const testObject = new HistoryNavigator2(['1', '2', '3', '4']);
260+
testObject.replaceLast('2');
261+
262+
assert.strictEqual(testObject.current(), '2');
263+
assert.strictEqual(testObject.isAtEnd(), true);
264+
assert.strictEqual(testObject.has('4'), false);
265+
266+
assert.strictEqual(testObject.previous(), '3');
267+
assert.strictEqual(testObject.previous(), '1');
268+
});
269+
270+
});

0 commit comments

Comments
 (0)