diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js index 476760a08d5934..a8b1e336d9aee8 100644 --- a/lib/internal/repl/utils.js +++ b/lib/internal/repl/utils.js @@ -138,6 +138,8 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { let wrapped = false; + let escaped = null; + function getPreviewPos() { const displayPos = repl._getDisplayPos(`${repl._prompt}${repl.line}`); const cursorPos = repl.line.length !== repl.cursor ? @@ -146,7 +148,13 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { return { displayPos, cursorPos }; } - const clearPreview = () => { + function isCursorAtInputEnd() { + const { cursorPos, displayPos } = getPreviewPos(); + return cursorPos.rows === displayPos.rows && + cursorPos.cols === displayPos.cols; + } + + const clearPreview = (key) => { if (inputPreview !== null) { const { displayPos, cursorPos } = getPreviewPos(); const rows = displayPos.rows - cursorPos.rows + 1; @@ -179,8 +187,23 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { cursorTo(repl.output, pos.cursorPos.cols); moveCursor(repl.output, 0, -rows); } + if (!key.ctrl && !key.shift) { + if (key.name === 'escape') { + if (escaped === null && key.meta) { + escaped = repl.line; + } + } else if ((key.name === 'return' || key.name === 'enter') && + !key.meta && + escaped !== repl.line && + isCursorAtInputEnd()) { + repl._insertString(completionPreview); + } + } completionPreview = null; } + if (escaped !== repl.line) { + escaped = null; + } }; function showCompletionPreview(line, insertPreview) { @@ -317,13 +340,6 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { } // Add the autocompletion preview. - // TODO(BridgeAR): Trigger the input preview after the completion preview. - // That way it's possible to trigger the input prefix including the - // potential completion suffix. To do so, we also have to change the - // behavior of `enter` and `escape`: - // Enter should automatically add the suffix to the current line as long as - // escape was not pressed. We might even remove the preview in case any - // cursor movement is triggered. const insertPreview = false; showCompletionPreview(repl.line, insertPreview); @@ -397,9 +413,17 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { moveCursor(repl.output, 0, -rows - 1); }; - getInputPreview(line, inputPreviewCallback); + let previewLine = line; + + if (completionPreview !== null && + isCursorAtInputEnd() && + escaped !== repl.line) { + previewLine += completionPreview; + } + + getInputPreview(previewLine, inputPreviewCallback); if (wrapped) { - getInputPreview(line, inputPreviewCallback); + getInputPreview(previewLine, inputPreviewCallback); } wrapped = false; }; diff --git a/lib/repl.js b/lib/repl.js index 3186e0a31b8c86..0f00632d35d515 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -849,7 +849,7 @@ function REPLServer(prompt, self.cursor === 0 && self.line.length === 0) { self.clearLine(); } - clearPreview(); + clearPreview(key); if (!reverseSearch(d, key)) { ttyWrite(d, key); showPreview(); diff --git a/test/parallel/test-repl-history-navigation.js b/test/parallel/test-repl-history-navigation.js index 640106f38ed4c7..275e92821a6bbf 100644 --- a/test/parallel/test-repl-history-navigation.js +++ b/test/parallel/test-repl-history-navigation.js @@ -57,6 +57,7 @@ const WORD_RIGHT = { name: 'right', ctrl: true }; const GO_TO_END = { name: 'end' }; const DELETE_WORD_LEFT = { name: 'backspace', ctrl: true }; const SIGINT = { name: 'c', ctrl: true }; +const ESCAPE = { name: 'escape', meta: true }; const prompt = '> '; const WAIT = '€'; @@ -182,8 +183,10 @@ const tests = [ 'veryLongName'.repeat(30), ENTER, `${'\x1B[90m \x1B[39m'.repeat(235)} fun`, + ESCAPE, ENTER, `${' '.repeat(236)} fun`, + ESCAPE, ENTER ], expected: [], @@ -318,6 +321,7 @@ const tests = [ env: { NODE_REPL_HISTORY: defaultHistoryPath }, showEscapeCodes: true, skip: !process.features.inspector, + checkTotal: true, test: [ 'fu', 'n', @@ -331,6 +335,12 @@ const tests = [ BACKSPACE, WORD_LEFT, WORD_RIGHT, + ESCAPE, + ENTER, + UP, + LEFT, + ENTER, + UP, ENTER ], // C = Cursor n forward @@ -379,12 +389,36 @@ const tests = [ '\x1B[0K', '\x1B[7D', '\x1B[10G', ' // n', '\x1B[3G', '\x1B[10G', // 10. Word right. Cleanup '\x1B[0K', '\x1B[3G', '\x1B[7C', ' // n', '\x1B[10G', - '\x1B[0K', - // 11. ENTER + // 11. ESCAPE + '\x1B[0K', ' // n', '\x1B[10G', '\x1B[0K', + // 12. ENTER '\r\n', 'Uncaught ReferenceError: functio is not defined\n', '\x1B[1G', '\x1B[0J', - prompt, '\x1B[3G', '\r\n' + // 13. UP + prompt, '\x1B[3G', '\x1B[1G', '\x1B[0J', + `${prompt}functio`, '\x1B[10G', + ' // n', '\x1B[10G', + ' // n', '\x1B[10G', + // 14. LEFT + '\x1B[0K', '\x1B[1D', + '\x1B[10G', ' // n', '\x1B[9G', '\x1B[10G', + // 15. ENTER + '\x1B[0K', '\x1B[9G', '\x1B[1C', + '\r\n', + 'Uncaught ReferenceError: functio is not defined\n', + '\x1B[1G', '\x1B[0J', + '> ', '\x1B[3G', + // 16. UP + '\x1B[1G', '\x1B[0J', + '> functio', '\x1B[10G', + ' // n', '\x1B[10G', + ' // n', '\x1B[10G', '\x1B[0K', + // 17. ENTER + 'n', '\r\n', + '\x1B[1G', '\x1B[0J', + '... ', '\x1B[5G', + '\r\n' ], clean: true }, diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js index 754963e08ebeb3..02c1ec31cd5020 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -91,7 +91,11 @@ async function tests(options) { input: 'koo', noPreview: '[Function: koo]', preview: [ - 'k\x1B[90moo\x1B[39m\x1B[9G\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G\x1B[0Ko', + 'k\x1B[90moo\x1B[39m\x1B[9G', + '\x1B[90m[Function: koo]\x1B[39m\x1B[9G\x1B[1A\x1B[1B\x1B[2K\x1B[1A' + + '\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G', + '\x1B[90m[Function: koo]\x1B[39m\x1B[10G\x1B[1A\x1B[1B\x1B[2K\x1B[1A' + + '\x1B[0Ko', '\x1B[90m[Function: koo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', '\x1B[36m[Function: koo]\x1B[39m' ]