diff --git a/doc/api/readline.md b/doc/api/readline.md index b7201907949c4b..c0407a77171431 100644 --- a/doc/api/readline.md +++ b/doc/api/readline.md @@ -1374,7 +1374,18 @@ const { createInterface } = require('readline'); Ctrl+- Undo previous change - Any keystroke emits key code 0x1F would do this action. + Any keystroke that emits key code 0x1F will do this action. + In many terminals, for example xterm, + this is bound to Ctrl+-. + + + + Ctrl+6 + Redo previous change + Many terminals don't have a default redo keystroke. + We choose key code 0x1E to perform redo. + In xterm, it is bound to Ctrl+6 + by default. diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index 5c9cb94ced5817..b5a5455ed3da85 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -889,24 +889,30 @@ class Interface extends InterfaceConstructor { [kUndo]() { if (this[kUndoStack].length <= 0) return; - const entry = this[kUndoStack].pop(); + ArrayPrototypePush( + this[kRedoStack], + { text: this.line, cursor: this.cursor }, + ); + const entry = ArrayPrototypePop(this[kUndoStack]); this.line = entry.text; this.cursor = entry.cursor; - ArrayPrototypePush(this[kRedoStack], entry); this[kRefreshLine](); } [kRedo]() { if (this[kRedoStack].length <= 0) return; - const entry = this[kRedoStack].pop(); + ArrayPrototypePush( + this[kUndoStack], + { text: this.line, cursor: this.cursor }, + ); + const entry = ArrayPrototypePop(this[kRedoStack]); this.line = entry.text; this.cursor = entry.cursor; - ArrayPrototypePush(this[kUndoStack], entry); this[kRefreshLine](); } @@ -1071,11 +1077,18 @@ class Interface extends InterfaceConstructor { } } - // Undo - if (typeof key.sequence === 'string' && - StringPrototypeCodePointAt(key.sequence, 0) === 0x1f) { - this[kUndo](); - return; + // Undo & Redo + if (typeof key.sequence === 'string') { + switch (StringPrototypeCodePointAt(key.sequence, 0)) { + case 0x1f: + this[kUndo](); + return; + case 0x1e: + this[kRedo](); + return; + default: + break; + } } // Ignore escape key, fixes diff --git a/test/parallel/test-readline-interface.js b/test/parallel/test-readline-interface.js index 04bacf39309168..ad7eee7c42e485 100644 --- a/test/parallel/test-readline-interface.js +++ b/test/parallel/test-readline-interface.js @@ -792,24 +792,36 @@ function assertCursorRowsAndCols(rli, rows, cols) { rli.close(); } -// Undo +// Undo & Redo { const [rli, fi] = getInterface({ terminal: true, prompt: '' }); fi.emit('data', 'the quick brown fox'); assertCursorRowsAndCols(rli, 0, 19); - // Delete right line from the 5th char + // Delete the last eight chars fi.emit('keypress', '.', { ctrl: true, shift: false, name: 'b' }); fi.emit('keypress', '.', { ctrl: true, shift: false, name: 'b' }); fi.emit('keypress', '.', { ctrl: true, shift: false, name: 'b' }); fi.emit('keypress', '.', { ctrl: true, shift: false, name: 'b' }); fi.emit('keypress', ',', { ctrl: true, shift: false, name: 'k' }); - fi.emit('keypress', ',', { ctrl: true, shift: false, name: 'u' }); - assertCursorRowsAndCols(rli, 0, 0); + + fi.emit('keypress', '.', { ctrl: true, shift: false, name: 'b' }); + fi.emit('keypress', '.', { ctrl: true, shift: false, name: 'b' }); + fi.emit('keypress', '.', { ctrl: true, shift: false, name: 'b' }); + fi.emit('keypress', '.', { ctrl: true, shift: false, name: 'b' }); + fi.emit('keypress', ',', { ctrl: true, shift: false, name: 'k' }); + + assertCursorRowsAndCols(rli, 0, 11); + // Perform undo twice fi.emit('keypress', ',', { sequence: '\x1F' }); assert.strictEqual(rli.line, 'the quick brown'); fi.emit('keypress', ',', { sequence: '\x1F' }); assert.strictEqual(rli.line, 'the quick brown fox'); + // Perform redo twice + fi.emit('keypress', ',', { sequence: '\x1E' }); + assert.strictEqual(rli.line, 'the quick brown'); + fi.emit('keypress', ',', { sequence: '\x1E' }); + assert.strictEqual(rli.line, 'the quick b'); fi.emit('data', '\n'); rli.close(); }