3
3
const {
4
4
ArrayFrom,
5
5
ArrayPrototypeFilter,
6
-
ArrayPrototypeIndexOf,
7
6
ArrayPrototypeJoin,
8
7
ArrayPrototypeMap,
9
8
ArrayPrototypePop,
10
9
ArrayPrototypePush,
11
10
ArrayPrototypeReverse,
12
11
ArrayPrototypeShift,
13
-
ArrayPrototypeSplice,
14
12
ArrayPrototypeUnshift,
15
13
DateNow,
16
14
FunctionPrototypeCall,
@@ -19,6 +17,7 @@ const {
19
17
MathMax,
20
18
MathMaxApply,
21
19
NumberIsFinite,
20
+
ObjectDefineProperty,
22
21
ObjectSetPrototypeOf,
23
22
RegExpPrototypeExec,
24
23
SafeStringIterator,
@@ -30,7 +29,6 @@ const {
30
29
StringPrototypeSlice,
31
30
StringPrototypeSplit,
32
31
StringPrototypeStartsWith,
33
-
StringPrototypeTrim,
34
32
Symbol,
35
33
SymbolAsyncIterator,
36
34
SymbolDispose,
@@ -46,8 +44,6 @@ const {
46
44
47
45
const {
48
46
validateAbortSignal,
49
-
validateArray,
50
-
validateNumber,
51
47
validateString,
52
48
validateUint32,
53
49
} = require('internal/validators');
@@ -67,7 +63,6 @@ const {
67
63
charLengthLeft,
68
64
commonPrefix,
69
65
kSubstringSearch,
70
-
reverseString,
71
66
} = require('internal/readline/utils');
72
67
let emitKeypressEvents;
73
68
let kFirstEventParam;
@@ -78,8 +73,8 @@ const {
78
73
} = require('internal/readline/callbacks');
79
74
80
75
const { StringDecoder } = require('string_decoder');
76
+
const { ReplHistory } = require('internal/repl/history');
81
77
82
-
const kHistorySize = 30;
83
78
const kMaxUndoRedoStackSize = 2048;
84
79
const kMincrlfDelay = 100;
85
80
/**
@@ -153,7 +148,6 @@ const kWriteToOutput = Symbol('_writeToOutput');
153
148
const kYank = Symbol('_yank');
154
149
const kYanking = Symbol('_yanking');
155
150
const kYankPop = Symbol('_yankPop');
156
-
const kNormalizeHistoryLineEndings = Symbol('_normalizeHistoryLineEndings');
157
151
const kSavePreviousState = Symbol('_savePreviousState');
158
152
const kRestorePreviousState = Symbol('_restorePreviousState');
159
153
const kPreviousLine = Symbol('_previousLine');
@@ -175,9 +169,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
175
169
176
170
FunctionPrototypeCall(EventEmitter, this);
177
171
178
-
let history;
179
-
let historySize;
180
-
let removeHistoryDuplicates = false;
181
172
let crlfDelay;
182
173
let prompt = '> ';
183
174
let signal;
@@ -187,14 +178,17 @@ function InterfaceConstructor(input, output, completer, terminal) {
187
178
output = input.output;
188
179
completer = input.completer;
189
180
terminal = input.terminal;
190
-
history = input.history;
191
-
historySize = input.historySize;
192
181
signal = input.signal;
182
+
183
+
// It is possible to configure the history through the input object
184
+
const historySize = input.historySize;
185
+
const history = input.history;
186
+
const removeHistoryDuplicates = input.removeHistoryDuplicates;
187
+
193
188
if (input.tabSize !== undefined) {
194
189
validateUint32(input.tabSize, 'tabSize', true);
195
190
this.tabSize = input.tabSize;
196
191
}
197
-
removeHistoryDuplicates = input.removeHistoryDuplicates;
198
192
if (input.prompt !== undefined) {
199
193
prompt = input.prompt;
200
194
}
@@ -215,24 +209,18 @@ function InterfaceConstructor(input, output, completer, terminal) {
215
209
216
210
crlfDelay = input.crlfDelay;
217
211
input = input.input;
218
-
}
219
212
220
-
if (completer !== undefined && typeof completer !== 'function') {
221
-
throw new ERR_INVALID_ARG_VALUE('completer', completer);
213
+
input.size = historySize;
214
+
input.history = history;
215
+
input.removeHistoryDuplicates = removeHistoryDuplicates;
222
216
}
223
217
224
-
if (history === undefined) {
225
-
history = [];
226
-
} else {
227
-
validateArray(history, 'history');
228
-
}
218
+
this.setupHistoryManager(input);
229
219
230
-
if (historySize === undefined) {
231
-
historySize = kHistorySize;
220
+
if (completer !== undefined && typeof completer !== 'function') {
221
+
throw new ERR_INVALID_ARG_VALUE('completer', completer);
232
222
}
233
223
234
-
validateNumber(historySize, 'historySize', 0);
235
-
236
224
// Backwards compat; check the isTTY prop of the output stream
237
225
// when `terminal` was not specified
238
226
if (terminal === undefined && !(output === null || output === undefined)) {
@@ -248,8 +236,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
248
236
this.input = input;
249
237
this[kUndoStack] = [];
250
238
this[kRedoStack] = [];
251
-
this.history = history;
252
-
this.historySize = historySize;
253
239
this[kPreviousCursorCols] = -1;
254
240
255
241
// The kill ring is a global list of blocks of text that were previously
@@ -260,7 +246,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
260
246
this[kKillRing] = [];
261
247
this[kKillRingCursor] = 0;
262
248
263
-
this.removeHistoryDuplicates = !!removeHistoryDuplicates;
264
249
this.crlfDelay = crlfDelay ?
265
250
MathMax(kMincrlfDelay, crlfDelay) :
266
251
kMincrlfDelay;
@@ -270,7 +255,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
270
255
271
256
this.terminal = !!terminal;
272
257
273
-
274
258
function onerror(err) {
275
259
self.emit('error', err);
276
260
}
@@ -349,8 +333,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
349
333
// Cursor position on the line.
350
334
this.cursor = 0;
351
335
352
-
this.historyIndex = -1;
353
-
354
336
if (output !== null && output !== undefined)
355
337
output.on('resize', onresize);
356
338
@@ -403,6 +385,36 @@ class Interface extends InterfaceConstructor {
403
385
return this[kPrompt];
404
386
}
405
387
388
+
setupHistoryManager(options) {
389
+
this.historyManager = new ReplHistory(this, options);
390
+
391
+
if (options.onHistoryFileLoaded) {
392
+
this.historyManager.initialize(options.onHistoryFileLoaded);
393
+
}
394
+
395
+
ObjectDefineProperty(this, 'history', {
396
+
__proto__: null, configurable: true, enumerable: true,
397
+
get() { return this.historyManager.history; },
398
+
set(newHistory) { return this.historyManager.history = newHistory; },
399
+
});
400
+
401
+
ObjectDefineProperty(this, 'historyIndex', {
402
+
__proto__: null, configurable: true, enumerable: true,
403
+
get() { return this.historyManager.index; },
404
+
set(historyIndex) { return this.historyManager.index = historyIndex; },
405
+
});
406
+
407
+
ObjectDefineProperty(this, 'historySize', {
408
+
__proto__: null, configurable: true, enumerable: true,
409
+
get() { return this.historyManager.size; },
410
+
});
411
+
412
+
ObjectDefineProperty(this, 'isFlushing', {
413
+
__proto__: null, configurable: true, enumerable: true,
414
+
get() { return this.historyManager.isFlushing; },
415
+
});
416
+
}
417
+
406
418
[kSetRawMode](mode) {
407
419
const wasInRawMode = this.input.isRaw;
408
420
@@ -478,70 +490,8 @@ class Interface extends InterfaceConstructor {
478
490
}
479
491
}
480
492
481
-
// Convert newlines to a consistent format for history storage
482
-
[kNormalizeHistoryLineEndings](line, from, to, reverse = true) {
483
-
// Multiline history entries are saved reversed
484
-
// History is structured with the newest entries at the top
485
-
// and the oldest at the bottom. Multiline histories, however, only occupy
486
-
// one line in the history file. When loading multiline history with
487
-
// an old node binary, the history will be saved in the old format.
488
-
// This is why we need to reverse the multilines.
489
-
// Reversing the multilines is necessary when adding / editing and displaying them
490
-
if (reverse) {
491
-
// First reverse the lines for proper order, then convert separators
492
-
return reverseString(line, from, to);
493
-
}
494
-
// For normal cases (saving to history or non-multiline entries)
495
-
return StringPrototypeReplaceAll(line, from, to);
496
-
}
497
-
498
493
[kAddHistory]() {
499
-
if (this.line.length === 0) return '';
500
-
501
-
// If the history is disabled then return the line
502
-
if (this.historySize === 0) return this.line;
503
-
504
-
// If the trimmed line is empty then return the line
505
-
if (StringPrototypeTrim(this.line).length === 0) return this.line;
506
-
507
-
// This is necessary because each line would be saved in the history while creating
508
-
// A new multiline, and we don't want that.
509
-
if (this[kIsMultiline] && this.historyIndex === -1) {
510
-
ArrayPrototypeShift(this.history);
511
-
} else if (this[kLastCommandErrored]) {
512
-
// If the last command errored and we are trying to edit the history to fix it
513
-
// Remove the broken one from the history
514
-
ArrayPrototypeShift(this.history);
515
-
}
516
-
517
-
const normalizedLine = this[kNormalizeHistoryLineEndings](this.line, '\n', '\r', true);
518
-
519
-
if (this.history.length === 0 || this.history[0] !== normalizedLine) {
520
-
if (this.removeHistoryDuplicates) {
521
-
// Remove older history line if identical to new one
522
-
const dupIndex = ArrayPrototypeIndexOf(this.history, this.line);
523
-
if (dupIndex !== -1) ArrayPrototypeSplice(this.history, dupIndex, 1);
524
-
}
525
-
526
-
// Add the new line to the history
527
-
ArrayPrototypeUnshift(this.history, normalizedLine);
528
-
529
-
// Only store so many
530
-
if (this.history.length > this.historySize)
531
-
ArrayPrototypePop(this.history);
532
-
}
533
-
534
-
this.historyIndex = -1;
535
-
536
-
// The listener could change the history object, possibly
537
-
// to remove the last added entry if it is sensitive and should
538
-
// not be persisted in the history, like a password
539
-
const line = this[kIsMultiline] ? reverseString(this.history[0]) : this.history[0];
540
-
541
-
// Emit history event to notify listeners of update
542
-
this.emit('history', this.history);
543
-
544
-
return line;
494
+
return this.historyManager.addHistory(this[kIsMultiline], this[kLastCommandErrored]);
545
495
}
546
496
547
497
[kRefreshLine]() {
@@ -1184,26 +1134,12 @@ class Interface extends InterfaceConstructor {
1184
1134
// <ctrl> + N. Only show this after two/three UPs or DOWNs, not on the first
1185
1135
// one.
1186
1136
[kHistoryNext]() {
1187
-
if (this.historyIndex >= 0) {
1188
-
this[kBeforeEdit](this.line, this.cursor);
1189
-
const search = this[kSubstringSearch] || '';
1190
-
let index = this.historyIndex - 1;
1191
-
while (
1192
-
index >= 0 &&
1193
-
(!StringPrototypeStartsWith(this.history[index], search) ||
1194
-
this.line === this.history[index])
1195
-
) {
1196
-
index--;
1197
-
}
1198
-
if (index === -1) {
1199
-
this[kSetLine](search);
1200
-
} else {
1201
-
this[kSetLine](this[kNormalizeHistoryLineEndings](this.history[index], '\r', '\n'));
1202
-
}
1203
-
this.historyIndex = index;
1204
-
this.cursor = this.line.length; // Set cursor to end of line.
1205
-
this[kRefreshLine]();
1206
-
}
1137
+
if (!this.historyManager.canNavigateToNext()) { return; }
1138
+
1139
+
this[kBeforeEdit](this.line, this.cursor);
1140
+
this[kSetLine](this.historyManager.navigateToNext(this[kSubstringSearch]));
1141
+
this.cursor = this.line.length; // Set cursor to end of line.
1142
+
this[kRefreshLine]();
1207
1143
}
1208
1144
1209
1145
[kMoveUpOrHistoryPrev]() {
@@ -1218,26 +1154,12 @@ class Interface extends InterfaceConstructor {
1218
1154
}
1219
1155
1220
1156
[kHistoryPrev]() {
1221
-
if (this.historyIndex < this.history.length && this.history.length) {
1222
-
this[kBeforeEdit](this.line, this.cursor);
1223
-
const search = this[kSubstringSearch] || '';
1224
-
let index = this.historyIndex + 1;
1225
-
while (
1226
-
index < this.history.length &&
1227
-
(!StringPrototypeStartsWith(this.history[index], search) ||
1228
-
this.line === this.history[index])
1229
-
) {
1230
-
index++;
1231
-
}
1232
-
if (index === this.history.length) {
1233
-
this[kSetLine](search);
1234
-
} else {
1235
-
this[kSetLine](this[kNormalizeHistoryLineEndings](this.history[index], '\r', '\n'));
1236
-
}
1237
-
this.historyIndex = index;
1238
-
this.cursor = this.line.length; // Set cursor to end of line.
1239
-
this[kRefreshLine]();
1240
-
}
1157
+
if (!this.historyManager.canNavigateToPrevious()) { return; }
1158
+
1159
+
this[kBeforeEdit](this.line, this.cursor);
1160
+
this[kSetLine](this.historyManager.navigateToPrevious(this[kSubstringSearch]));
1161
+
this.cursor = this.line.length; // Set cursor to end of line.
1162
+
this[kRefreshLine]();
1241
1163
}
1242
1164
1243
1165
// Returns the last character's display position of the given string
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4