Permalink
Browse files

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 <rtrott@gmail.com>
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information...
addaleax committed Jul 15, 2016
1 parent c809b88 commit 1a9e247c794274942683d2e77db098c9494ddde5
Showing with 24 additions and 17 deletions.
  1. +15 −13 lib/readline.js
  2. +9 −4 test/parallel/test-readline-undefined-columns.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
@@ -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();

0 comments on commit 1a9e247

Please sign in to comment.