diff --git a/lib/readline.js b/lib/readline.js index 32737b37dccf14..fea190c08f34fd 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -632,9 +632,21 @@ 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 (StringPrototypeStartsWith(prefix, completeOn) && + prefix.length > completeOn.length) { this._insertString(StringPrototypeSlice(prefix, completeOn.length)); return; + } else if (!StringPrototypeStartsWith(completeOn, 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(); + }); + }); +}