| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -680,6 +680,10 @@ A list of the names of some Node.js modules, e.g., `'http'`. | |||
| 680 | 680 | <!-- YAML | |
| 681 | 681 | added: v0.1.91 | |
| 682 | 682 | changes: | |
| 683 | + - version: REPLACEME | ||
| 684 | + pr-url: https://github.com/nodejs/node/pull/58003 | ||
| 685 | + description: Added the possibility to add/edit/remove multilines | ||
| 686 | + while adding a multiline command. | ||
| 683 | 687 | - version: v24.0.0 | |
| 684 | 688 | pr-url: https://github.com/nodejs/node/pull/57400 | |
| 685 | 689 | description: The multi-line indicator is now "|" instead of "...". | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -64,6 +64,7 @@ const { | |||
| 64 | 64 | charLengthLeft, | |
| 65 | 65 | commonPrefix, | |
| 66 | 66 | kSubstringSearch, | |
| 67 | + reverseString, | ||
| 67 | 68 | } = require('internal/readline/utils'); | |
| 68 | 69 | let emitKeypressEvents; | |
| 69 | 70 | let kFirstEventParam; | |
@@ -98,9 +99,7 @@ const ESCAPE_CODE_TIMEOUT = 500; | |||
| 98 | 99 | // Max length of the kill ring | |
| 99 | 100 | const kMaxLengthOfKillRing = 32; | |
| 100 | 101 | ||
| 101 | - // TODO(puskin94): make this configurable | ||
| 102 | 102 | const kMultilinePrompt = Symbol('| '); | |
| 103 | - const kLastCommandErrored = Symbol('_lastCommandErrored'); | ||
| 104 | 103 | ||
| 105 | 104 | const kAddHistory = Symbol('_addHistory'); | |
| 106 | 105 | const kBeforeEdit = Symbol('_beforeEdit'); | |
@@ -131,6 +130,7 @@ const kPrompt = Symbol('_prompt'); | |||
| 131 | 130 | const kPushToKillRing = Symbol('_pushToKillRing'); | |
| 132 | 131 | const kPushToUndoStack = Symbol('_pushToUndoStack'); | |
| 133 | 132 | const kQuestionCallback = Symbol('_questionCallback'); | |
| 133 | + const kLastCommandErrored = Symbol('_lastCommandErrored'); | ||
| 134 | 134 | const kQuestionReject = Symbol('_questionReject'); | |
| 135 | 135 | const kRedo = Symbol('_redo'); | |
| 136 | 136 | const kRedoStack = Symbol('_redoStack'); | |
@@ -151,6 +151,12 @@ const kYank = Symbol('_yank'); | |||
| 151 | 151 | const kYanking = Symbol('_yanking'); | |
| 152 | 152 | const kYankPop = Symbol('_yankPop'); | |
| 153 | 153 | const kNormalizeHistoryLineEndings = Symbol('_normalizeHistoryLineEndings'); | |
| 154 | + const kSavePreviousState = Symbol('_savePreviousState'); | ||
| 155 | + const kRestorePreviousState = Symbol('_restorePreviousState'); | ||
| 156 | + const kPreviousLine = Symbol('_previousLine'); | ||
| 157 | + const kPreviousCursor = Symbol('_previousCursor'); | ||
| 158 | + const kPreviousPrevRows = Symbol('_previousPrevRows'); | ||
| 159 | + const kAddNewLineOnTTY = Symbol('_addNewLineOnTTY'); | ||
| 154 | 160 | ||
| 155 | 161 | function InterfaceConstructor(input, output, completer, terminal) { | |
| 156 | 162 | this[kSawReturnAt] = 0; | |
@@ -430,7 +436,7 @@ class Interface extends InterfaceConstructor { | |||
| 430 | 436 | } | |
| 431 | 437 | } | |
| 432 | 438 | ||
| 433 | - [kSetLine](line) { | ||
| 439 | + [kSetLine](line = '') { | ||
| 434 | 440 | this.line = line; | |
| 435 | 441 | this[kIsMultiline] = StringPrototypeIncludes(line, '\n'); | |
| 436 | 442 | } | |
@@ -477,10 +483,7 @@ class Interface extends InterfaceConstructor { | |||
| 477 | 483 | // Reversing the multilines is necessary when adding / editing and displaying them | |
| 478 | 484 | if (reverse) { | |
| 479 | 485 | // First reverse the lines for proper order, then convert separators | |
| 480 | - return ArrayPrototypeJoin( | ||
| 481 | - ArrayPrototypeReverse(StringPrototypeSplit(line, from)), | ||
| 482 | - to, | ||
| 483 | - ); | ||
| 486 | + return reverseString(line, from, to); | ||
| 484 | 487 | } | |
| 485 | 488 | // For normal cases (saving to history or non-multiline entries) | |
| 486 | 489 | return StringPrototypeReplaceAll(line, from, to); | |
@@ -494,22 +497,28 @@ class Interface extends InterfaceConstructor { | |||
| 494 | 497 | ||
| 495 | 498 | // If the trimmed line is empty then return the line | |
| 496 | 499 | if (StringPrototypeTrim(this.line).length === 0) return this.line; | |
| 497 | - const normalizedLine = this[kNormalizeHistoryLineEndings](this.line, '\n', '\r', false); | ||
| 500 | + | ||
| 501 | + // This is necessary because each line would be saved in the history while creating | ||
| 502 | + // A new multiline, and we don't want that. | ||
| 503 | + if (this[kIsMultiline] && this.historyIndex === -1) { | ||
| 504 | + ArrayPrototypeShift(this.history); | ||
| 505 | + } else if (this[kLastCommandErrored]) { | ||
| 506 | + // If the last command errored and we are trying to edit the history to fix it | ||
| 507 | + // Remove the broken one from the history | ||
| 508 | + ArrayPrototypeShift(this.history); | ||
| 509 | + } | ||
| 510 | + | ||
| 511 | + const normalizedLine = this[kNormalizeHistoryLineEndings](this.line, '\n', '\r', true); | ||
| 498 | 512 | ||
| 499 | 513 | if (this.history.length === 0 || this.history[0] !== normalizedLine) { | |
| 500 | - if (this[kLastCommandErrored] && this.historyIndex === 0) { | ||
| 501 | - // If the last command errored, remove it from history. | ||
| 502 | - // The user is issuing a new command starting from the errored command, | ||
| 503 | - // Hopefully with the fix | ||
| 504 | - ArrayPrototypeShift(this.history); | ||
| 505 | - } | ||
| 506 | 514 | if (this.removeHistoryDuplicates) { | |
| 507 | 515 | // Remove older history line if identical to new one | |
| 508 | 516 | const dupIndex = ArrayPrototypeIndexOf(this.history, this.line); | |
| 509 | 517 | if (dupIndex !== -1) ArrayPrototypeSplice(this.history, dupIndex, 1); | |
| 510 | 518 | } | |
| 511 | 519 | ||
| 512 | - ArrayPrototypeUnshift(this.history, this.line); | ||
| 520 | + // Add the new line to the history | ||
| 521 | + ArrayPrototypeUnshift(this.history, normalizedLine); | ||
| 513 | 522 | ||
| 514 | 523 | // Only store so many | |
| 515 | 524 | if (this.history.length > this.historySize) | |
@@ -521,7 +530,7 @@ class Interface extends InterfaceConstructor { | |||
| 521 | 530 | // The listener could change the history object, possibly | |
| 522 | 531 | // to remove the last added entry if it is sensitive and should | |
| 523 | 532 | // not be persisted in the history, like a password | |
| 524 | - const line = this.history[0]; | ||
| 533 | + const line = this[kIsMultiline] ? reverseString(this.history[0]) : this.history[0]; | ||
| 525 | 534 | ||
| 526 | 535 | // Emit history event to notify listeners of update | |
| 527 | 536 | this.emit('history', this.history); | |
@@ -938,6 +947,18 @@ class Interface extends InterfaceConstructor { | |||
| 938 | 947 | } | |
| 939 | 948 | } | |
| 940 | 949 | ||
| 950 | + [kSavePreviousState]() { | ||
| 951 | + this[kPreviousLine] = this.line; | ||
| 952 | + this[kPreviousCursor] = this.cursor; | ||
| 953 | + this[kPreviousPrevRows] = this.prevRows; | ||
| 954 | + } | ||
| 955 | + | ||
| 956 | + [kRestorePreviousState]() { | ||
| 957 | + this[kSetLine](this[kPreviousLine]); | ||
| 958 | + this.cursor = this[kPreviousCursor]; | ||
| 959 | + this.prevRows = this[kPreviousPrevRows]; | ||
| 960 | + } | ||
| 961 | + | ||
| 941 | 962 | clearLine() { | |
| 942 | 963 | this[kMoveCursor](+Infinity); | |
| 943 | 964 | this[kWriteToOutput]('\r\n'); | |
@@ -947,13 +968,115 @@ class Interface extends InterfaceConstructor { | |||
| 947 | 968 | } | |
| 948 | 969 | ||
| 949 | 970 | [kLine]() { | |
| 971 | + this[kSavePreviousState](); | ||
| 950 | 972 | const line = this[kAddHistory](); | |
| 951 | 973 | this[kUndoStack] = []; | |
| 952 | 974 | this[kRedoStack] = []; | |
| 953 | 975 | this.clearLine(); | |
| 954 | 976 | this[kOnLine](line); | |
| 955 | 977 | } | |
| 956 | 978 | ||
| 979 | + | ||
| 980 | + // TODO(puskin94): edit [kTtyWrite] to make call this function on a new key combination | ||
| 981 | + // to make it add a new line in the middle of a "complete" multiline. | ||
| 982 | + // I tried with shift + enter but it is not detected. Find a new one. | ||
| 983 | + // Make sure to call this[kSavePreviousState](); && this.clearLine(); | ||
| 984 | + // before calling this[kAddNewLineOnTTY] to simulate what [kLine] is doing. | ||
| 985 | + | ||
| 986 | + // When this function is called, the actual cursor is at the very end of the whole string, | ||
| 987 | + // No matter where the new line was entered. | ||
| 988 | + // This function should only be used when the output is a TTY | ||
| 989 | + [kAddNewLineOnTTY]() { | ||
| 990 | + // Restore terminal state and store current line | ||
| 991 | + this[kRestorePreviousState](); | ||
| 992 | + const originalLine = this.line; | ||
| 993 | + | ||
| 994 | + // Split the line at the current cursor position | ||
| 995 | + const beforeCursor = StringPrototypeSlice(this.line, 0, this.cursor); | ||
| 996 | + let afterCursor = StringPrototypeSlice(this.line, this.cursor, this.line.length); | ||
| 997 | + | ||
| 998 | + // Add the new line where the cursor is at | ||
| 999 | + this[kSetLine](`${beforeCursor}\n${afterCursor}`); | ||
| 1000 | + | ||
| 1001 | + // To account for the new line | ||
| 1002 | + this.cursor += 1; | ||
| 1003 | + | ||
| 1004 | + const hasContentAfterCursor = afterCursor.length > 0; | ||
| 1005 | + const cursorIsNotOnFirstLine = this.prevRows > 0; | ||
| 1006 | + let needsRewriteFirstLine = false; | ||
| 1007 | + | ||
| 1008 | + // Handle cursor positioning based on different scenarios | ||
| 1009 | + if (hasContentAfterCursor) { | ||
| 1010 | + const splitBeg = StringPrototypeSplit(beforeCursor, '\n'); | ||
| 1011 | + // Determine if we need to rewrite the first line | ||
| 1012 | + needsRewriteFirstLine = splitBeg.length < 2; | ||
| 1013 | + | ||
| 1014 | + // If the cursor is not on the first line | ||
| 1015 | + if (cursorIsNotOnFirstLine) { | ||
| 1016 | + const splitEnd = StringPrototypeSplit(afterCursor, '\n'); | ||
| 1017 | + | ||
| 1018 | + // If the cursor when I pressed enter was at least on the second line | ||
| 1019 | + // I need to completely erase the line where the cursor was pressed because it is possible | ||
| 1020 | + // That it was pressed in the middle of the line, hence I need to write the whole line. | ||
| 1021 | + // To achieve that, I need to reach the line above the current line coming from the end | ||
| 1022 | + const dy = splitEnd.length + 1; | ||
| 1023 | + | ||
| 1024 | + // Calculate how many Xs we need to move on the right to get to the end of the line | ||
| 1025 | + const dxEndOfLineAbove = (splitBeg[splitBeg.length - 2] || '').length + kMultilinePrompt.description.length; | ||
| 1026 | + moveCursor(this.output, dxEndOfLineAbove, -dy); | ||
| 1027 | + | ||
| 1028 | + // This is the line that was split in the middle | ||
| 1029 | + // Just add it to the rest of the line that will be printed later | ||
| 1030 | + afterCursor = `${splitBeg[splitBeg.length - 1]}\n${afterCursor}`; | ||
| 1031 | + } else { | ||
| 1032 | + // Otherwise, go to the very beginning of the first line and erase everything | ||
| 1033 | + const dy = StringPrototypeSplit(originalLine, '\n').length; | ||
| 1034 | + moveCursor(this.output, 0, -dy); | ||
| 1035 | + } | ||
| 1036 | + | ||
| 1037 | + // Erase from the cursor to the end of the line | ||
| 1038 | + clearScreenDown(this.output); | ||
| 1039 | + | ||
| 1040 | + if (cursorIsNotOnFirstLine) { | ||
| 1041 | + this[kWriteToOutput]('\n'); | ||
| 1042 | + } | ||
| 1043 | + } | ||
| 1044 | + | ||
| 1045 | + if (needsRewriteFirstLine) { | ||
| 1046 | + this[kWriteToOutput](`${this[kPrompt]}${beforeCursor}\n${kMultilinePrompt.description}`); | ||
| 1047 | + } else { | ||
| 1048 | + this[kWriteToOutput](kMultilinePrompt.description); | ||
| 1049 | + } | ||
| 1050 | + | ||
| 1051 | + // Write the rest and restore the cursor to where the user left it | ||
| 1052 | + if (hasContentAfterCursor) { | ||
| 1053 | + // Save the cursor pos, we need to come back here | ||
| 1054 | + const oldCursor = this.getCursorPos(); | ||
| 1055 | + | ||
| 1056 | + // Write everything after the cursor which has been deleted by clearScreenDown | ||
| 1057 | + const formattedEndContent = StringPrototypeReplaceAll( | ||
| 1058 | + afterCursor, | ||
| 1059 | + '\n', | ||
| 1060 | + `\n${kMultilinePrompt.description}`, | ||
| 1061 | + ); | ||
| 1062 | + | ||
| 1063 | + this[kWriteToOutput](formattedEndContent); | ||
| 1064 | + | ||
| 1065 | + const newCursor = this[kGetDisplayPos](this.line); | ||
| 1066 | + | ||
| 1067 | + // Go back to where the cursor was, with relative movement | ||
| 1068 | + moveCursor(this.output, oldCursor.cols - newCursor.cols, oldCursor.rows - newCursor.rows); | ||
| 1069 | + | ||
| 1070 | + // Setting how many rows we have on top of the cursor | ||
| 1071 | + // Necessary for kRefreshLine | ||
| 1072 | + this.prevRows = oldCursor.rows; | ||
| 1073 | + } else { | ||
| 1074 | + // Setting how many rows we have on top of the cursor | ||
| 1075 | + // Necessary for kRefreshLine | ||
| 1076 | + this.prevRows = StringPrototypeSplit(this.line, '\n').length - 1; | ||
| 1077 | + } | ||
| 1078 | + } | ||
| 1079 | + | ||
| 957 | 1080 | [kPushToUndoStack](text, cursor) { | |
| 958 | 1081 | if (ArrayPrototypePush(this[kUndoStack], { text, cursor }) > | |
| 959 | 1082 | kMaxUndoRedoStackSize) { | |
@@ -1525,6 +1648,7 @@ module.exports = { | |||
| 1525 | 1648 | kWordRight, | |
| 1526 | 1649 | kWriteToOutput, | |
| 1527 | 1650 | kMultilinePrompt, | |
| 1651 | + kRestorePreviousState, | ||
| 1652 | + kAddNewLineOnTTY, | ||
| 1528 | 1653 | kLastCommandErrored, | |
| 1529 | - kNormalizeHistoryLineEndings, | ||
| 1530 | 1654 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,7 @@ const { | |||
| 7 | 7 | StringPrototypeCharCodeAt, | |
| 8 | 8 | StringPrototypeCodePointAt, | |
| 9 | 9 | StringPrototypeSlice, | |
| 10 | + StringPrototypeSplit, | ||
| 10 | 11 | StringPrototypeToLowerCase, | |
| 11 | 12 | Symbol, | |
| 12 | 13 | } = primordials; | |
@@ -395,11 +396,26 @@ function commonPrefix(strings) { | |||
| 395 | 396 | return min; | |
| 396 | 397 | } | |
| 397 | 398 | ||
| 399 | + function reverseString(line, from = '\r', to = '\r') { | ||
| 400 | + const parts = StringPrototypeSplit(line, from); | ||
| 401 | + | ||
| 402 | + // This implementation should be faster than | ||
| 403 | + // ArrayPrototypeJoin(ArrayPrototypeReverse(StringPrototypeSplit(line, from)), to); | ||
| 404 | + let result = ''; | ||
| 405 | + for (let i = parts.length - 1; i > 0; i--) { | ||
| 406 | + result += parts[i] + to; | ||
| 407 | + } | ||
| 408 | + result += parts[0]; | ||
| 409 | + | ||
| 410 | + return result; | ||
| 411 | + } | ||
| 412 | + | ||
| 398 | 413 | module.exports = { | |
| 399 | 414 | charLengthAt, | |
| 400 | 415 | charLengthLeft, | |
| 401 | 416 | commonPrefix, | |
| 402 | 417 | emitKeys, | |
| 418 | + reverseString, | ||
| 403 | 419 | kSubstringSearch, | |
| 404 | 420 | CSI, | |
| 405 | 421 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -53,7 +53,6 @@ const { | |||
| 53 | 53 | ArrayPrototypePop, | |
| 54 | 54 | ArrayPrototypePush, | |
| 55 | 55 | ArrayPrototypePushApply, | |
| 56 | - ArrayPrototypeReverse, | ||
| 57 | 56 | ArrayPrototypeShift, | |
| 58 | 57 | ArrayPrototypeSlice, | |
| 59 | 58 | ArrayPrototypeSome, | |
@@ -196,8 +195,8 @@ const { | |||
| 196 | 195 | } = require('internal/vm'); | |
| 197 | 196 | const { | |
| 198 | 197 | kMultilinePrompt, | |
| 198 | + kAddNewLineOnTTY, | ||
| 199 | 199 | kLastCommandErrored, | |
| 200 | - kNormalizeHistoryLineEndings, | ||
| 201 | 200 | } = require('internal/readline/interface'); | |
| 202 | 201 | let nextREPLResourceNumber = 1; | |
| 203 | 202 | // This prevents v8 code cache from getting confused and using a different | |
@@ -361,6 +360,7 @@ function REPLServer(prompt, | |||
| 361 | 360 | this.editorMode = false; | |
| 362 | 361 | // Context id for use with the inspector protocol. | |
| 363 | 362 | this[kContextId] = undefined; | |
| 363 | + this[kLastCommandErrored] = false; | ||
| 364 | 364 | ||
| 365 | 365 | if (this.breakEvalOnSigint && eval_) { | |
| 366 | 366 | // Allowing this would not reflect user expectations. | |
@@ -929,8 +929,6 @@ function REPLServer(prompt, | |||
| 929 | 929 | debug('finish', e, ret); | |
| 930 | 930 | ReflectApply(_memory, self, [cmd]); | |
| 931 | 931 | ||
| 932 | - self[kLastCommandErrored] = false; | ||
| 933 | - | ||
| 934 | 932 | if (e && !self[kBufferedCommandSymbol] && | |
| 935 | 933 | StringPrototypeStartsWith(StringPrototypeTrim(cmd), 'npm ') && | |
| 936 | 934 | !(e instanceof Recoverable) | |
@@ -943,33 +941,15 @@ function REPLServer(prompt, | |||
| 943 | 941 | } | |
| 944 | 942 | ||
| 945 | 943 | // If error was SyntaxError and not JSON.parse error | |
| 946 | - if (e) { | ||
| 947 | - if (e instanceof Recoverable && !sawCtrlD) { | ||
| 948 | - // Start buffering data like that: | ||
| 949 | - // { | ||
| 950 | - // ... x: 1 | ||
| 951 | - // ... } | ||
| 944 | + // We can start a multiline command | ||
| 945 | + if (e instanceof Recoverable && !sawCtrlD) { | ||
| 946 | + if (self.terminal) { | ||
| 947 | + self[kAddNewLineOnTTY](); | ||
| 948 | + } else { | ||
| 952 | 949 | self[kBufferedCommandSymbol] += cmd + '\n'; | |
| 953 | 950 | self.displayPrompt(); | |
| 954 | - return; | ||
| 955 | - } | ||
| 956 | - } | ||
| 957 | - | ||
| 958 | - // In the next two if blocks, we do not use os.EOL instead of '\n' | ||
| 959 | - // because on Windows it is '\r\n' | ||
| 960 | - if (StringPrototypeIncludes(cmd, '\n')) { // If you are editing a multiline command | ||
| 961 | - self.history[0] = self[kNormalizeHistoryLineEndings](cmd, '\n', '\r'); | ||
| 962 | - } else if (self[kBufferedCommandSymbol]) { // If a new multiline command was entered | ||
| 963 | - // Remove the first N lines from the self.history array | ||
| 964 | - // where N is the number of lines in the buffered command | ||
| 965 | - | ||
| 966 | - const lines = StringPrototypeSplit(self[kBufferedCommandSymbol], '\n'); | ||
| 967 | - self.history = ArrayPrototypeSlice(self.history, lines.length); | ||
| 968 | - lines[lines.length - 1] = cmd; | ||
| 969 | - const newHistoryLine = ArrayPrototypeJoin(ArrayPrototypeReverse(lines), '\r'); | ||
| 970 | - if (self.history[0] !== newHistoryLine) { | ||
| 971 | - ArrayPrototypeUnshift(self.history, newHistoryLine); | ||
| 972 | 951 | } | |
| 952 | + return; | ||
| 973 | 953 | } | |
| 974 | 954 | ||
| 975 | 955 | if (e) { | |
@@ -997,6 +977,7 @@ function REPLServer(prompt, | |||
| 997 | 977 | // Display prompt again (unless we already did by emitting the 'error' | |
| 998 | 978 | // event on the domain instance). | |
| 999 | 979 | if (!e) { | |
| 980 | + self[kLastCommandErrored] = false; | ||
| 1000 | 981 | self.displayPrompt(); | |
| 1001 | 982 | } | |
| 1002 | 983 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments