diff --git a/lib/readline.js b/lib/readline.js index 402923e9255160..9d34bb740dbb04 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -41,6 +41,7 @@ function Interface(input, output, completer, terminal) { this._sawReturn = false; this.isCompletionEnabled = true; + this._previousKey = null; EventEmitter.call(this); var historySize; @@ -391,7 +392,7 @@ Interface.prototype._insertString = function(c) { } }; -Interface.prototype._tabComplete = function() { +Interface.prototype._tabComplete = function(lastKeypressWasTab) { var self = this; self.pause(); @@ -407,9 +408,7 @@ Interface.prototype._tabComplete = function() { const completeOn = rv[1]; // the text that was completed if (completions && completions.length) { // Apply/show completions. - if (completions.length === 1) { - self._insertString(completions[0].slice(completeOn.length)); - } else { + if (lastKeypressWasTab) { self._writeToOutput('\r\n'); var width = completions.reduce(function(a, b) { return a.length > b.length ? a : b; @@ -429,16 +428,15 @@ Interface.prototype._tabComplete = function() { } } handleGroup(self, group, width, maxColumns); + } - // If there is a common prefix to all matches, then apply that - // portion. - var f = completions.filter(function(e) { if (e) return e; }); - var prefix = commonPrefix(f); - if (prefix.length > completeOn.length) { - self._insertString(prefix.slice(completeOn.length)); - } - + // If there is a common prefix to all matches, then apply that portion. + const f = completions.filter(function(e) { if (e) return e; }); + const prefix = commonPrefix(f); + if (prefix.length > completeOn.length) { + self._insertString(prefix.slice(completeOn.length)); } + self._refreshLine(); } }); @@ -474,6 +472,7 @@ function commonPrefix(strings) { if (!strings || strings.length == 0) { return ''; } + if (strings.length === 1) return strings[0]; var sorted = strings.slice().sort(); var min = sorted[0]; var max = sorted[sorted.length - 1]; @@ -688,7 +687,9 @@ Interface.prototype._moveCursor = function(dx) { // handle a write from the tty Interface.prototype._ttyWrite = function(s, key) { + const previousKey = this._previousKey; key = key || {}; + this._previousKey = key; // Ignore escape key - Fixes #2876 if (key.name == 'escape') return; @@ -892,7 +893,8 @@ Interface.prototype._ttyWrite = function(s, key) { case 'tab': // If tab completion enabled, do that... if (typeof this.completer === 'function' && this.isCompletionEnabled) { - this._tabComplete(); + const lastKeypressWasTab = previousKey && previousKey.name === 'tab'; + this._tabComplete(lastKeypressWasTab); break; } // falls through diff --git a/test/parallel/test-readline-undefined-columns.js b/test/parallel/test-readline-undefined-columns.js index f3197ff59a9a55..5f8a1c02d3a90b 100644 --- a/test/parallel/test-readline-undefined-columns.js +++ b/test/parallel/test-readline-undefined-columns.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const PassThrough = require('stream').PassThrough; const readline = require('readline'); @@ -26,12 +26,17 @@ oStream.on('data', function(data) { output += data; }); -oStream.on('end', function() { +oStream.on('end', common.mustCall(() => { const expect = 'process.stdout\r\n' + 'process.stdin\r\n' + 'process.stderr'; assert(new RegExp(expect).test(output)); -}); +})); + +iStream.write('process.s\t'); + +assert(/process.std\b/.test(output)); // Completion works. +assert(!/stdout/.test(output)); // Completion doesn’t show all results yet. -iStream.write('process.std\t'); +iStream.write('\t'); oStream.end();