Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRay <ray@isrc.iscas.ac.cn>2022-01-21 13:24:06 +0300
committerGitHub <noreply@github.com>2022-01-21 13:24:06 +0300
commit271725a36359003b9a26ad8522688c3a319a750d (patch)
tree318d81f2be675412e355afe0f0405910c91a3804 /lib
parent5badf46f2a9363e6762900e18d4f85541738f738 (diff)
readline: undo previous edit when get key code 0x1F
1. Undo previous edit on keystroke `ctrl -` (emit 0x1F) 2. unittests 3. documentation PR-URL: https://github.com/nodejs/node/pull/41392 Fixes: https://github.com/nodejs/node/issues/41308 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Qingyu Deng <i@ayase-lab.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/internal/readline/interface.js66
1 files changed, 66 insertions, 0 deletions
diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js
index e50172f5628..4ef4fe2ffa6 100644
--- a/lib/internal/readline/interface.js
+++ b/lib/internal/readline/interface.js
@@ -7,8 +7,10 @@ const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypePop,
+ ArrayPrototypePush,
ArrayPrototypeReverse,
ArrayPrototypeSplice,
+ ArrayPrototypeShift,
ArrayPrototypeUnshift,
DateNow,
FunctionPrototypeCall,
@@ -68,6 +70,7 @@ const { StringDecoder } = require('string_decoder');
let Readable;
const kHistorySize = 30;
+const kMaxUndoRedoStackSize = 2048;
const kMincrlfDelay = 100;
// \r\n, \n, or \r followed by something other than \n
const lineEnding = /\r?\n|\r(?!\n)/;
@@ -79,6 +82,7 @@ const kQuestionCancel = Symbol('kQuestionCancel');
const ESCAPE_CODE_TIMEOUT = 500;
const kAddHistory = Symbol('_addHistory');
+const kBeforeEdit = Symbol('_beforeEdit');
const kDecoder = Symbol('_decoder');
const kDeleteLeft = Symbol('_deleteLeft');
const kDeleteLineLeft = Symbol('_deleteLineLeft');
@@ -98,7 +102,10 @@ const kOldPrompt = Symbol('_oldPrompt');
const kOnLine = Symbol('_onLine');
const kPreviousKey = Symbol('_previousKey');
const kPrompt = Symbol('_prompt');
+const kPushToUndoStack = Symbol('_pushToUndoStack');
const kQuestionCallback = Symbol('_questionCallback');
+const kRedo = Symbol('_redo');
+const kRedoStack = Symbol('_redoStack');
const kRefreshLine = Symbol('_refreshLine');
const kSawKeyPress = Symbol('_sawKeyPress');
const kSawReturnAt = Symbol('_sawReturnAt');
@@ -106,6 +113,8 @@ const kSetRawMode = Symbol('_setRawMode');
const kTabComplete = Symbol('_tabComplete');
const kTabCompleter = Symbol('_tabCompleter');
const kTtyWrite = Symbol('_ttyWrite');
+const kUndo = Symbol('_undo');
+const kUndoStack = Symbol('_undoStack');
const kWordLeft = Symbol('_wordLeft');
const kWordRight = Symbol('_wordRight');
const kWriteToOutput = Symbol('_writeToOutput');
@@ -198,6 +207,8 @@ function InterfaceConstructor(input, output, completer, terminal) {
this[kSubstringSearch] = null;
this.output = output;
this.input = input;
+ this[kUndoStack] = [];
+ this[kRedoStack] = [];
this.history = history;
this.historySize = historySize;
this.removeHistoryDuplicates = !!removeHistoryDuplicates;
@@ -390,6 +401,10 @@ class Interface extends InterfaceConstructor {
}
}
+ [kBeforeEdit](oldText, oldCursor) {
+ this[kPushToUndoStack](oldText, oldCursor);
+ }
+
[kQuestionCancel]() {
if (this[kQuestionCallback]) {
this[kQuestionCallback] = null;
@@ -579,6 +594,7 @@ class Interface extends InterfaceConstructor {
}
[kInsertString](c) {
+ this[kBeforeEdit](this.line, this.cursor);
if (this.cursor < this.line.length) {
const beg = StringPrototypeSlice(this.line, 0, this.cursor);
const end = StringPrototypeSlice(
@@ -648,6 +664,8 @@ class Interface extends InterfaceConstructor {
return;
}
+ this[kBeforeEdit](this.line, this.cursor);
+
// Apply/show completions.
const completionsWidth = ArrayPrototypeMap(completions, (e) =>
getStringWidth(e)
@@ -708,6 +726,7 @@ class Interface extends InterfaceConstructor {
[kDeleteLeft]() {
if (this.cursor > 0 && this.line.length > 0) {
+ this[kBeforeEdit](this.line, this.cursor);
// The number of UTF-16 units comprising the character to the left
const charSize = charLengthLeft(this.line, this.cursor);
this.line =
@@ -721,6 +740,7 @@ class Interface extends InterfaceConstructor {
[kDeleteRight]() {
if (this.cursor < this.line.length) {
+ this[kBeforeEdit](this.line, this.cursor);
// The number of UTF-16 units comprising the character to the left
const charSize = charLengthAt(this.line, this.cursor);
this.line =
@@ -736,6 +756,7 @@ class Interface extends InterfaceConstructor {
[kDeleteWordLeft]() {
if (this.cursor > 0) {
+ this[kBeforeEdit](this.line, this.cursor);
// Reverse the string and match a word near beginning
// to avoid quadratic time complexity
let leading = StringPrototypeSlice(this.line, 0, this.cursor);
@@ -759,6 +780,7 @@ class Interface extends InterfaceConstructor {
[kDeleteWordRight]() {
if (this.cursor < this.line.length) {
+ this[kBeforeEdit](this.line, this.cursor);
const trailing = StringPrototypeSlice(this.line, this.cursor);
const match = StringPrototypeMatch(trailing, /^(?:\s+|\W+|\w+)\s*/);
this.line =
@@ -769,12 +791,14 @@ class Interface extends InterfaceConstructor {
}
[kDeleteLineLeft]() {
+ this[kBeforeEdit](this.line, this.cursor);
this.line = StringPrototypeSlice(this.line, this.cursor);
this.cursor = 0;
this[kRefreshLine]();
}
[kDeleteLineRight]() {
+ this[kBeforeEdit](this.line, this.cursor);
this.line = StringPrototypeSlice(this.line, 0, this.cursor);
this[kRefreshLine]();
}
@@ -789,10 +813,43 @@ class Interface extends InterfaceConstructor {
[kLine]() {
const line = this[kAddHistory]();
+ this[kUndoStack] = [];
+ this[kRedoStack] = [];
this.clearLine();
this[kOnLine](line);
}
+ [kPushToUndoStack](text, cursor) {
+ if (ArrayPrototypePush(this[kUndoStack], { text, cursor }) >
+ kMaxUndoRedoStackSize) {
+ ArrayPrototypeShift(this[kUndoStack]);
+ }
+ }
+
+ [kUndo]() {
+ if (this[kUndoStack].length <= 0) return;
+
+ 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();
+
+ this.line = entry.text;
+ this.cursor = entry.cursor;
+
+ ArrayPrototypePush(this[kUndoStack], entry);
+ this[kRefreshLine]();
+ }
+
// TODO(BridgeAR): Add underscores to the search part and a red background in
// case no match is found. This should only be the visual part and not the
// actual line content!
@@ -802,6 +859,7 @@ class Interface extends InterfaceConstructor {
// one.
[kHistoryNext]() {
if (this.historyIndex >= 0) {
+ this[kBeforeEdit](this.line, this.cursor);
const search = this[kSubstringSearch] || '';
let index = this.historyIndex - 1;
while (
@@ -824,6 +882,7 @@ class Interface extends InterfaceConstructor {
[kHistoryPrev]() {
if (this.historyIndex < this.history.length && this.history.length) {
+ this[kBeforeEdit](this.line, this.cursor);
const search = this[kSubstringSearch] || '';
let index = this.historyIndex + 1;
while (
@@ -947,6 +1006,13 @@ class Interface extends InterfaceConstructor {
}
}
+ // Undo
+ if (typeof key.sequence === 'string' &&
+ StringPrototypeCodePointAt(key.sequence, 0) === 0x1f) {
+ this[kUndo]();
+ return;
+ }
+
// Ignore escape key, fixes
// https://github.com/nodejs/node-v0.x-archive/issues/2876.
if (key.name === 'escape') return;