diff --git a/doc/api/readline.md b/doc/api/readline.md
index b7c5a2f70e9b14..5074b06bead2ba 100644
--- a/doc/api/readline.md
+++ b/doc/api/readline.md
@@ -1354,7 +1354,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();
}