Skip to content

Commit 1a9e247

Browse files
committed
readline: show completions only after 2nd TAB
Show `TAB` completion suggestions only after the user has pressed `TAB` twice in a row, so that the full list of suggestions doesn’t present a distraction. The first time a `TAB` key is pressed, only partial longest-common-prefix completion is performed. This moves the `readline` autocompletion a lot closer to what e.g. `bash` does. Fixes: #7665 PR-URL: #7754 Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Trevor Norris <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent c809b88 commit 1a9e247

File tree

2 files changed

+24
-17
lines changed

2 files changed

+24
-17
lines changed

lib/readline.js

+15-13
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function Interface(input, output, completer, terminal) {
4141

4242
this._sawReturn = false;
4343
this.isCompletionEnabled = true;
44+
this._previousKey = null;
4445

4546
EventEmitter.call(this);
4647
var historySize;
@@ -391,7 +392,7 @@ Interface.prototype._insertString = function(c) {
391392
}
392393
};
393394

394-
Interface.prototype._tabComplete = function() {
395+
Interface.prototype._tabComplete = function(lastKeypressWasTab) {
395396
var self = this;
396397

397398
self.pause();
@@ -407,9 +408,7 @@ Interface.prototype._tabComplete = function() {
407408
const completeOn = rv[1]; // the text that was completed
408409
if (completions && completions.length) {
409410
// Apply/show completions.
410-
if (completions.length === 1) {
411-
self._insertString(completions[0].slice(completeOn.length));
412-
} else {
411+
if (lastKeypressWasTab) {
413412
self._writeToOutput('\r\n');
414413
var width = completions.reduce(function(a, b) {
415414
return a.length > b.length ? a : b;
@@ -429,16 +428,15 @@ Interface.prototype._tabComplete = function() {
429428
}
430429
}
431430
handleGroup(self, group, width, maxColumns);
431+
}
432432

433-
// If there is a common prefix to all matches, then apply that
434-
// portion.
435-
var f = completions.filter(function(e) { if (e) return e; });
436-
var prefix = commonPrefix(f);
437-
if (prefix.length > completeOn.length) {
438-
self._insertString(prefix.slice(completeOn.length));
439-
}
440-
433+
// If there is a common prefix to all matches, then apply that portion.
434+
const f = completions.filter(function(e) { if (e) return e; });
435+
const prefix = commonPrefix(f);
436+
if (prefix.length > completeOn.length) {
437+
self._insertString(prefix.slice(completeOn.length));
441438
}
439+
442440
self._refreshLine();
443441
}
444442
});
@@ -474,6 +472,7 @@ function commonPrefix(strings) {
474472
if (!strings || strings.length == 0) {
475473
return '';
476474
}
475+
if (strings.length === 1) return strings[0];
477476
var sorted = strings.slice().sort();
478477
var min = sorted[0];
479478
var max = sorted[sorted.length - 1];
@@ -688,7 +687,9 @@ Interface.prototype._moveCursor = function(dx) {
688687

689688
// handle a write from the tty
690689
Interface.prototype._ttyWrite = function(s, key) {
690+
const previousKey = this._previousKey;
691691
key = key || {};
692+
this._previousKey = key;
692693

693694
// Ignore escape key - Fixes #2876
694695
if (key.name == 'escape') return;
@@ -892,7 +893,8 @@ Interface.prototype._ttyWrite = function(s, key) {
892893
case 'tab':
893894
// If tab completion enabled, do that...
894895
if (typeof this.completer === 'function' && this.isCompletionEnabled) {
895-
this._tabComplete();
896+
const lastKeypressWasTab = previousKey && previousKey.name === 'tab';
897+
this._tabComplete(lastKeypressWasTab);
896898
break;
897899
}
898900
// falls through

test/parallel/test-readline-undefined-columns.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
require('../common');
3+
const common = require('../common');
44
const assert = require('assert');
55
const PassThrough = require('stream').PassThrough;
66
const readline = require('readline');
@@ -26,12 +26,17 @@ oStream.on('data', function(data) {
2626
output += data;
2727
});
2828

29-
oStream.on('end', function() {
29+
oStream.on('end', common.mustCall(() => {
3030
const expect = 'process.stdout\r\n' +
3131
'process.stdin\r\n' +
3232
'process.stderr';
3333
assert(new RegExp(expect).test(output));
34-
});
34+
}));
35+
36+
iStream.write('process.s\t');
37+
38+
assert(/process.std\b/.test(output)); // Completion works.
39+
assert(!/stdout/.test(output)); // Completion doesn’t show all results yet.
3540

36-
iStream.write('process.std\t');
41+
iStream.write('\t');
3742
oStream.end();

0 commit comments

Comments
 (0)