Skip to content

Commit

Permalink
readline: bind keystroke ctrl+6 to redo
Browse files Browse the repository at this point in the history
1. Any keystroke emits `0x1E` will do redo action.
2. Fix bug of undo/redo.
3. More detailed document.
4. Unit tests.

PR-URL: nodejs#41662
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
rayw000 authored and bengl committed Feb 21, 2022
1 parent 0a6f0b4 commit 3243701
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 14 deletions.
13 changes: 12 additions & 1 deletion doc/api/readline.md
Expand Up @@ -1354,7 +1354,18 @@ const { createInterface } = require('readline');
<tr>
<td><kbd>Ctrl</kbd>+<kbd>-</kbd></td>
<td>Undo previous change</td>
<td>Any keystroke emits key code <code>0x1F</code> would do this action.</td>
<td>Any keystroke that emits key code <code>0x1F</code> will do this action.
In many terminals, for example <code>xterm</code>,
this is bound to <kbd>Ctrl</kbd>+<kbd>-</kbd>.</td>
<td></td>
</tr>
<tr>
<td><kbd>Ctrl</kbd>+<kbd>6</kbd></td>
<td>Redo previous change</td>
<td>Many terminals don't have a default redo keystroke.
We choose key code <code>0x1E</code> to perform redo.
In <code>xterm</code>, it is bound to <kbd>Ctrl</kbd>+<kbd>6</kbd>
by default.</td>
<td></td>
</tr>
<tr>
Expand Down
31 changes: 22 additions & 9 deletions lib/internal/readline/interface.js
Expand Up @@ -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]();
}

Expand Down Expand Up @@ -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
Expand Down
20 changes: 16 additions & 4 deletions test/parallel/test-readline-interface.js
Expand Up @@ -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();
}
Expand Down

0 comments on commit 3243701

Please sign in to comment.