From 87649ad9d8536e2abce1e3aeb8cab6300f6bef90 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 23 Jan 2022 11:54:17 +0800 Subject: [PATCH 1/3] readline: bind keystroke `ctrl`+`6` to redo 1. Any keystroke emits `0x1E` will do redo action. 2. Fix bug of undo/redo. 3. More detailed document. 4. Unit tests. --- doc/api/readline.md | 13 +++++++++- lib/internal/readline/interface.js | 31 +++++++++++++++++------- test/parallel/test-readline-interface.js | 20 ++++++++++++--- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/doc/api/readline.md b/doc/api/readline.md index b7c5a2f70e9b14..8448d3a0bb6ea1 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 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 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..89b60c5410f9e4 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 = this[kUndoStack].pop(); 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 = this[kRedoStack].pop(); 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(); } From 39c1a7ef586985374572b9fe3e56fe505783da48 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 23 Jan 2022 15:28:05 +0800 Subject: [PATCH 2/3] grammatical fixing --- doc/api/readline.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/readline.md b/doc/api/readline.md index 8448d3a0bb6ea1..5074b06bead2ba 100644 --- a/doc/api/readline.md +++ b/doc/api/readline.md @@ -1354,7 +1354,7 @@ const { createInterface } = require('readline'); Ctrl+- Undo previous change - Any keystroke emits key code 0x1F will 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+-. @@ -1362,7 +1362,7 @@ const { createInterface } = require('readline'); Ctrl+6 Redo previous change - Many terminals don't have default redo keystroke. + 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. From a6bf5b8646d4489c3ab2a02e6bb83cfeb9aafebb Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 23 Jan 2022 15:30:39 +0800 Subject: [PATCH 3/3] use primordials instead --- lib/internal/readline/interface.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index 89b60c5410f9e4..b5a5455ed3da85 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -894,7 +894,7 @@ class Interface extends InterfaceConstructor { { text: this.line, cursor: this.cursor }, ); - const entry = this[kUndoStack].pop(); + const entry = ArrayPrototypePop(this[kUndoStack]); this.line = entry.text; this.cursor = entry.cursor; @@ -909,7 +909,7 @@ class Interface extends InterfaceConstructor { { text: this.line, cursor: this.cursor }, ); - const entry = this[kRedoStack].pop(); + const entry = ArrayPrototypePop(this[kRedoStack]); this.line = entry.text; this.cursor = entry.cursor;