From 4d5636e10498a8e2a11745b728c9462b9d467c6c Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 28 Jun 2021 15:02:07 +0200 Subject: [PATCH 1/5] readline: allow completer to rewrite existing input Sometimes, it makes sense for a completer to change the existing input, e.g. by adjusting the casing (imagine a completer that corrects `Number.isNan` to `Number.IsNaN`, for example). This commit allows that in the readline implemention. --- lib/readline.js | 9 +++++- test/parallel/test-readline-tab-complete.js | 36 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/readline.js b/lib/readline.js index b40c8019746120..d1d6a062bb4998 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -705,9 +705,16 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) { // If there is a common prefix to all matches, then apply that portion. const prefix = commonPrefix(ArrayPrototypeFilter(completions, (e) => e !== '')); - if (prefix.length > completeOn.length) { + if (prefix.startsWith(completeOn) && prefix.length > completeOn.length) { this._insertString(StringPrototypeSlice(prefix, completeOn.length)); return; + } else if (!completeOn.startsWith(prefix)) { + this.line = StringPrototypeSlice(this.line, 0, this.cursor - completeOn.length) + + prefix + + StringPrototypeSlice(this.line, this.cursor, this.line.length); + this.cursor = this.cursor - completeOn.length + prefix.length; + this._refreshLine(); + return; } if (!lastKeypressWasTab) { diff --git a/test/parallel/test-readline-tab-complete.js b/test/parallel/test-readline-tab-complete.js index c283d446f9af28..be993911c6fe16 100644 --- a/test/parallel/test-readline-tab-complete.js +++ b/test/parallel/test-readline-tab-complete.js @@ -100,3 +100,39 @@ common.skipIfDumbTerminal(); }); rli.close(); } + +{ + let output = ''; + class FakeInput extends EventEmitter { + columns = 80 + + write = common.mustCall((data) => { + output += data; + }, 9) + + resume() {} + pause() {} + end() {} + } + + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + terminal: true, + completer: common.mustCall((input, cb) => { + cb(null, [[input[0].toUpperCase() + input.slice(1)], input]); + }), + }); + + rli.on('line', common.mustNotCall()); + fi.emit('data', 'input'); + queueMicrotask(() => { + fi.emit('data', '\t'); + queueMicrotask(() => { + assert.match(output, /> Input/); + output = ''; + rli.close(); + }); + }); +} From e69cd1da8f0c30788589d8b430138e8f705c3986 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 28 Jun 2021 15:34:13 +0200 Subject: [PATCH 2/5] fixup! readline: allow completer to rewrite existing input --- lib/readline.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index d1d6a062bb4998..f1a7e60cf27b04 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -709,9 +709,13 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) { this._insertString(StringPrototypeSlice(prefix, completeOn.length)); return; } else if (!completeOn.startsWith(prefix)) { - this.line = StringPrototypeSlice(this.line, 0, this.cursor - completeOn.length) + + this.line = StringPrototypeSlice(this.line, + 0, + this.cursor - completeOn.length) + prefix + - StringPrototypeSlice(this.line, this.cursor, this.line.length); + StringPrototypeSlice(this.line, + this.cursor, + this.line.length); this.cursor = this.cursor - completeOn.length + prefix.length; this._refreshLine(); return; From 4b4e6c3b81aa2f4562a3db60e77379f05f12922f Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 28 Jun 2021 15:43:05 +0200 Subject: [PATCH 3/5] fixup! readline: allow completer to rewrite existing input --- lib/readline.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index f1a7e60cf27b04..b25b09594cf76c 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -709,12 +709,12 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) { this._insertString(StringPrototypeSlice(prefix, completeOn.length)); return; } else if (!completeOn.startsWith(prefix)) { - this.line = StringPrototypeSlice(this.line, - 0, + this.line = StringPrototypeSlice(this.line, + 0, this.cursor - completeOn.length) + prefix + - StringPrototypeSlice(this.line, - this.cursor, + StringPrototypeSlice(this.line, + this.cursor, this.line.length); this.cursor = this.cursor - completeOn.length + prefix.length; this._refreshLine(); From 697cf5d46ed43dd0488f4b85ed549a553dd1ff7a Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 28 Jun 2021 16:38:05 +0200 Subject: [PATCH 4/5] fixup! readline: allow completer to rewrite existing input Co-authored-by: Antoine du Hamel --- lib/readline.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index b25b09594cf76c..3f407fdcca9cd6 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -705,10 +705,11 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) { // If there is a common prefix to all matches, then apply that portion. const prefix = commonPrefix(ArrayPrototypeFilter(completions, (e) => e !== '')); - if (prefix.startsWith(completeOn) && prefix.length > completeOn.length) { + if (StringPrototypeStartsWith(prefix, completeOn) && + prefix.length > completeOn.length) { this._insertString(StringPrototypeSlice(prefix, completeOn.length)); return; - } else if (!completeOn.startsWith(prefix)) { + } else if (!StringPrototypeStartsWith(completeOn, prefix)) { this.line = StringPrototypeSlice(this.line, 0, this.cursor - completeOn.length) + From 0a38e282ecbf7ff90c0c86f5a08054951c028557 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 28 Jun 2021 16:46:07 +0200 Subject: [PATCH 5/5] fixup! readline: allow completer to rewrite existing input --- lib/readline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/readline.js b/lib/readline.js index 3f407fdcca9cd6..1b8b276a6d6db0 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -705,7 +705,7 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) { // If there is a common prefix to all matches, then apply that portion. const prefix = commonPrefix(ArrayPrototypeFilter(completions, (e) => e !== '')); - if (StringPrototypeStartsWith(prefix, completeOn) && + if (StringPrototypeStartsWith(prefix, completeOn) && prefix.length > completeOn.length) { this._insertString(StringPrototypeSlice(prefix, completeOn.length)); return;