| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -645,14 +645,35 @@ buffered but not yet executed. This method is primarily intended to be | |||
| 645 | 645 | called from within the action function for commands registered using the | |
| 646 | 646 | `replServer.defineCommand()` method. | |
| 647 | 647 | ||
| 648 | - ### `replServer.setupHistory(historyPath, callback)` | ||
| 648 | + ### `replServer.setupHistory(historyConfig, callback)` | ||
| 649 | 649 | ||
| 650 | 650 | <!-- YAML | |
| 651 | 651 | added: v11.10.0 | |
| 652 | + changes: | ||
| 653 | + - version: REPLACEME | ||
| 654 | + pr-url: https://github.com/nodejs/node/pull/58225 | ||
| 655 | + description: Updated the `historyConfig` parameter to accept an object | ||
| 656 | + with `filePath`, `size`, `removeHistoryDuplicates` and | ||
| 657 | + `onHistoryFileLoaded` properties. | ||
| 652 | 658 | --> | |
| 653 | 659 | ||
| 654 | - * `historyPath` {string} the path to the history file | ||
| 660 | + * `historyConfig` {Object|string} the path to the history file | ||
| 661 | + If it is a string, it is the path to the history file. | ||
| 662 | + If it is an object, it can have the following properties: | ||
| 663 | + * `filePath` {string} the path to the history file | ||
| 664 | + * `size` {number} Maximum number of history lines retained. To disable | ||
| 665 | + the history set this value to `0`. This option makes sense only if | ||
| 666 | + `terminal` is set to `true` by the user or by an internal `output` check, | ||
| 667 | + otherwise the history caching mechanism is not initialized at all. | ||
| 668 | + **Default:** `30`. | ||
| 669 | + * `removeHistoryDuplicates` {boolean} If `true`, when a new input line added | ||
| 670 | + to the history list duplicates an older one, this removes the older line | ||
| 671 | + from the list. **Default:** `false`. | ||
| 672 | + * `onHistoryFileLoaded` {Function} called when history writes are ready or upon error | ||
| 673 | + * `err` {Error} | ||
| 674 | + * `repl` {repl.REPLServer} | ||
| 655 | 675 | * `callback` {Function} called when history writes are ready or upon error | |
| 676 | + (Optional if provided as `onHistoryFileLoaded` in `historyConfig`) | ||
| 656 | 677 | * `err` {Error} | |
| 657 | 678 | * `repl` {repl.REPLServer} | |
| 658 | 679 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,7 +44,7 @@ if (process.env.NODE_REPL_EXTERNAL_MODULE) { | |||
| 44 | 44 | throw err; | |
| 45 | 45 | } | |
| 46 | 46 | repl.on('exit', () => { | |
| 47 | - if (repl._flushing) { | ||
| 47 | + if (repl.historyManager.isFlushing) { | ||
| 48 | 48 | return repl.once('flushHistory', () => { | |
| 49 | 49 | process.exit(); | |
| 50 | 50 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,14 +3,12 @@ | |||
| 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 | |
| Back | FazBrowse Home | New Git URL |
0 commit comments