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' ) ;
@@ -64,7 +60,6 @@ const {
64
60
charLengthLeft,
65
61
commonPrefix,
66
62
kSubstringSearch,
67
- reverseString,
68
63
} = require ( 'internal/readline/utils' ) ;
69
64
let emitKeypressEvents ;
70
65
let kFirstEventParam ;
@@ -75,8 +70,8 @@ const {
75
70
} = require ( 'internal/readline/callbacks' ) ;
76
71
77
72
const { StringDecoder } = require ( 'string_decoder' ) ;
73
+ const { ReplHistory } = require ( 'internal/repl/history' ) ;
78
74
79
- const kHistorySize = 30 ;
80
75
const kMaxUndoRedoStackSize = 2048 ;
81
76
const kMincrlfDelay = 100 ;
82
77
/**
@@ -150,7 +145,6 @@ const kWriteToOutput = Symbol('_writeToOutput');
150
145
const kYank = Symbol ( '_yank' ) ;
151
146
const kYanking = Symbol ( '_yanking' ) ;
152
147
const kYankPop = Symbol ( '_yankPop' ) ;
153
- const kNormalizeHistoryLineEndings = Symbol ( '_normalizeHistoryLineEndings' ) ;
154
148
const kSavePreviousState = Symbol ( '_savePreviousState' ) ;
155
149
const kRestorePreviousState = Symbol ( '_restorePreviousState' ) ;
156
150
const kPreviousLine = Symbol ( '_previousLine' ) ;
@@ -172,9 +166,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
172
166
173
167
FunctionPrototypeCall ( EventEmitter , this ) ;
174
168
175
- let history ;
176
- let historySize ;
177
- let removeHistoryDuplicates = false ;
178
169
let crlfDelay ;
179
170
let prompt = '> ' ;
180
171
let signal ;
@@ -184,14 +175,17 @@ function InterfaceConstructor(input, output, completer, terminal) {
184
175
output = input . output ;
185
176
completer = input . completer ;
186
177
terminal = input . terminal ;
187
- history = input . history ;
188
- historySize = input . historySize ;
189
178
signal = input . signal ;
179
+
180
+ // It is possible to configure the history through the input object
181
+ const historySize = input . historySize ;
182
+ const history = input . history ;
183
+ const removeHistoryDuplicates = input . removeHistoryDuplicates ;
184
+
190
185
if ( input . tabSize !== undefined ) {
191
186
validateUint32 ( input . tabSize , 'tabSize' , true ) ;
192
187
this . tabSize = input . tabSize ;
193
188
}
194
- removeHistoryDuplicates = input . removeHistoryDuplicates ;
195
189
if ( input . prompt !== undefined ) {
196
190
prompt = input . prompt ;
197
191
}
@@ -212,24 +206,18 @@ function InterfaceConstructor(input, output, completer, terminal) {
212
206
213
207
crlfDelay = input . crlfDelay ;
214
208
input = input . input ;
215
- }
216
209
217
- if ( completer !== undefined && typeof completer !== 'function' ) {
218
- throw new ERR_INVALID_ARG_VALUE ( 'completer' , completer ) ;
210
+ input . historySize = historySize ;
211
+ input . history = history ;
212
+ input . removeHistoryDuplicates = removeHistoryDuplicates ;
219
213
}
220
214
221
- if ( history === undefined ) {
222
- history = [ ] ;
223
- } else {
224
- validateArray ( history , 'history' ) ;
225
- }
215
+ this . setupHistoryManager ( input ) ;
226
216
227
- if ( historySize === undefined ) {
228
- historySize = kHistorySize ;
217
+ if ( completer !== undefined && typeof completer !== 'function' ) {
218
+ throw new ERR_INVALID_ARG_VALUE ( 'completer' , completer ) ;
229
219
}
230
220
231
- validateNumber ( historySize , 'historySize' , 0 ) ;
232
-
233
221
// Backwards compat; check the isTTY prop of the output stream
234
222
// when `terminal` was not specified
235
223
if ( terminal === undefined && ! ( output === null || output === undefined ) ) {
@@ -245,8 +233,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
245
233
this . input = input ;
246
234
this [ kUndoStack ] = [ ] ;
247
235
this [ kRedoStack ] = [ ] ;
248
- this . history = history ;
249
- this . historySize = historySize ;
250
236
this [ kPreviousCursorCols ] = - 1 ;
251
237
252
238
// The kill ring is a global list of blocks of text that were previously
@@ -257,7 +243,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
257
243
this [ kKillRing ] = [ ] ;
258
244
this [ kKillRingCursor ] = 0 ;
259
245
260
- this . removeHistoryDuplicates = ! ! removeHistoryDuplicates ;
261
246
this . crlfDelay = crlfDelay ?
262
247
MathMax ( kMincrlfDelay , crlfDelay ) :
263
248
kMincrlfDelay ;
@@ -267,7 +252,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
267
252
268
253
this . terminal = ! ! terminal ;
269
254
270
-
271
255
function onerror ( err ) {
272
256
self . emit ( 'error' , err ) ;
273
257
}
@@ -346,8 +330,6 @@ function InterfaceConstructor(input, output, completer, terminal) {
346
330
// Cursor position on the line.
347
331
this . cursor = 0 ;
348
332
349
- this . historyIndex = - 1 ;
350
-
351
333
if ( output !== null && output !== undefined )
352
334
output . on ( 'resize' , onresize ) ;
353
335
@@ -400,6 +382,36 @@ class Interface extends InterfaceConstructor {
400
382
return this [ kPrompt ] ;
401
383
}
402
384
385
+ setupHistoryManager ( options ) {
386
+ this . historyManager = new ReplHistory ( this , options ) ;
387
+
388
+ if ( options . onHistoryFileLoaded ) {
389
+ this . historyManager . initialize ( options . onHistoryFileLoaded ) ;
390
+ }
391
+
392
+ ObjectDefineProperty ( this , 'history' , {
393
+ __proto__ : null , configurable : true , enumerable : true ,
394
+ get ( ) { return this . historyManager . getHistory ( ) ; } ,
395
+ set ( newHistory ) { return this . historyManager . setHistory ( newHistory ) ; } ,
396
+ } ) ;
397
+
398
+ ObjectDefineProperty ( this , 'historyIndex' , {
399
+ __proto__ : null , configurable : true , enumerable : true ,
400
+ get ( ) { return this . historyManager . getHistoryIndex ( ) ; } ,
401
+ set ( historyIndex ) { return this . historyManager . setHistoryIndex ( historyIndex ) ; } ,
402
+ } ) ;
403
+
404
+ ObjectDefineProperty ( this , 'historySize' , {
405
+ __proto__ : null , configurable : true , enumerable : true ,
406
+ get ( ) { return this . historyManager . getSize ( ) ; } ,
407
+ } ) ;
408
+
409
+ ObjectDefineProperty ( this , '_flushing' , {
410
+ __proto__ : null , configurable : true , enumerable : true ,
411
+ get ( ) { return this . historyManager . isFlushing ( ) ; } ,
412
+ } ) ;
413
+ }
414
+
403
415
[ kSetRawMode ] ( mode ) {
404
416
const wasInRawMode = this . input . isRaw ;
405
417
@@ -475,70 +487,8 @@ class Interface extends InterfaceConstructor {
475
487
}
476
488
}
477
489
478
- // Convert newlines to a consistent format for history storage
479
- [ kNormalizeHistoryLineEndings ] ( line , from , to , reverse = true ) {
480
- // Multiline history entries are saved reversed
481
- // History is structured with the newest entries at the top
482
- // and the oldest at the bottom. Multiline histories, however, only occupy
483
- // one line in the history file. When loading multiline history with
484
- // an old node binary, the history will be saved in the old format.
485
- // This is why we need to reverse the multilines.
486
- // Reversing the multilines is necessary when adding / editing and displaying them
487
- if ( reverse ) {
488
- // First reverse the lines for proper order, then convert separators
489
- return reverseString ( line , from , to ) ;
490
- }
491
- // For normal cases (saving to history or non-multiline entries)
492
- return StringPrototypeReplaceAll ( line , from , to ) ;
493
- }
494
-
495
490
[ kAddHistory ] ( ) {
496
- if ( this . line . length === 0 ) return '' ;
497
-
498
- // If the history is disabled then return the line
499
- if ( this . historySize === 0 ) return this . line ;
500
-
501
- // If the trimmed line is empty then return the line
502
- if ( StringPrototypeTrim ( this . line ) . length === 0 ) return this . line ;
503
-
504
- // This is necessary because each line would be saved in the history while creating
505
- // A new multiline, and we don't want that.
506
- if ( this [ kIsMultiline ] && this . historyIndex === - 1 ) {
507
- ArrayPrototypeShift ( this . history ) ;
508
- } else if ( this [ kLastCommandErrored ] ) {
509
- // If the last command errored and we are trying to edit the history to fix it
510
- // Remove the broken one from the history
511
- ArrayPrototypeShift ( this . history ) ;
512
- }
513
-
514
- const normalizedLine = this [ kNormalizeHistoryLineEndings ] ( this . line , '\n' , '\r' , true ) ;
515
-
516
- if ( this . history . length === 0 || this . history [ 0 ] !== normalizedLine ) {
517
- if ( this . removeHistoryDuplicates ) {
518
- // Remove older history line if identical to new one
519
- const dupIndex = ArrayPrototypeIndexOf ( this . history , this . line ) ;
520
- if ( dupIndex !== - 1 ) ArrayPrototypeSplice ( this . history , dupIndex , 1 ) ;
521
- }
522
-
523
- // Add the new line to the history
524
- ArrayPrototypeUnshift ( this . history , normalizedLine ) ;
525
-
526
- // Only store so many
527
- if ( this . history . length > this . historySize )
528
- ArrayPrototypePop ( this . history ) ;
529
- }
530
-
531
- this . historyIndex = - 1 ;
532
-
533
- // The listener could change the history object, possibly
534
- // to remove the last added entry if it is sensitive and should
535
- // not be persisted in the history, like a password
536
- const line = this [ kIsMultiline ] ? reverseString ( this . history [ 0 ] ) : this . history [ 0 ] ;
537
-
538
- // Emit history event to notify listeners of update
539
- this . emit ( 'history' , this . history ) ;
540
-
541
- return line ;
491
+ return this . historyManager . addHistory ( this [ kIsMultiline ] , this [ kLastCommandErrored ] ) ;
542
492
}
543
493
544
494
[ kRefreshLine ] ( ) {
@@ -1172,26 +1122,12 @@ class Interface extends InterfaceConstructor {
1172
1122
// <ctrl> + N. Only show this after two/three UPs or DOWNs, not on the first
1173
1123
// one.
1174
1124
[ kHistoryNext ] ( ) {
1175
- if ( this . historyIndex >= 0 ) {
1176
- this [ kBeforeEdit ] ( this . line , this . cursor ) ;
1177
- const search = this [ kSubstringSearch ] || '' ;
1178
- let index = this . historyIndex - 1 ;
1179
- while (
1180
- index >= 0 &&
1181
- ( ! StringPrototypeStartsWith ( this . history [ index ] , search ) ||
1182
- this . line === this . history [ index ] )
1183
- ) {
1184
- index -- ;
1185
- }
1186
- if ( index === - 1 ) {
1187
- this [ kSetLine ] ( search ) ;
1188
- } else {
1189
- this [ kSetLine ] ( this [ kNormalizeHistoryLineEndings ] ( this . history [ index ] , '\r' , '\n' ) ) ;
1190
- }
1191
- this . historyIndex = index ;
1192
- this . cursor = this . line . length ; // Set cursor to end of line.
1193
- this [ kRefreshLine ] ( ) ;
1194
- }
1125
+ if ( ! this . historyManager . canNavigateToNext ( ) ) { return ; }
1126
+
1127
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
1128
+ this [ kSetLine ] ( this . historyManager . navigateToNext ( this [ kSubstringSearch ] ) ) ;
1129
+ this . cursor = this . line . length ; // Set cursor to end of line.
1130
+ this [ kRefreshLine ] ( ) ;
1195
1131
}
1196
1132
1197
1133
[ kMoveUpOrHistoryPrev ] ( ) {
@@ -1206,26 +1142,12 @@ class Interface extends InterfaceConstructor {
1206
1142
}
1207
1143
1208
1144
[ kHistoryPrev ] ( ) {
1209
- if ( this . historyIndex < this . history . length && this . history . length ) {
1210
- this [ kBeforeEdit ] ( this . line , this . cursor ) ;
1211
- const search = this [ kSubstringSearch ] || '' ;
1212
- let index = this . historyIndex + 1 ;
1213
- while (
1214
- index < this . history . length &&
1215
- ( ! StringPrototypeStartsWith ( this . history [ index ] , search ) ||
1216
- this . line === this . history [ index ] )
1217
- ) {
1218
- index ++ ;
1219
- }
1220
- if ( index === this . history . length ) {
1221
- this [ kSetLine ] ( search ) ;
1222
- } else {
1223
- this [ kSetLine ] ( this [ kNormalizeHistoryLineEndings ] ( this . history [ index ] , '\r' , '\n' ) ) ;
1224
- }
1225
- this . historyIndex = index ;
1226
- this . cursor = this . line . length ; // Set cursor to end of line.
1227
- this [ kRefreshLine ] ( ) ;
1228
- }
1145
+ if ( ! this . historyManager . canNavigateToPrevious ( ) ) { return ; }
1146
+
1147
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
1148
+ this [ kSetLine ] ( this . historyManager . navigateToPrevious ( this [ kSubstringSearch ] ) ) ;
1149
+ this . cursor = this . line . length ; // Set cursor to end of line.
1150
+ this [ kRefreshLine ] ( ) ;
1229
1151
}
1230
1152
1231
1153
// Returns the last character's display position of the given string
0 commit comments