Skip to content

Commit

Permalink
readline: show completions only after 2nd TAB
Browse files Browse the repository at this point in the history
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: nodejs#7665
  • Loading branch information
addaleax committed Aug 2, 2016
1 parent 93ac2ea commit dca1f27
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 17 deletions.
27 changes: 14 additions & 13 deletions lib/readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function Interface(input, output, completer, terminal) {

this._sawReturn = false;
this.isCompletionEnabled = true;
this._previousKey = null;

EventEmitter.call(this);
var historySize;
Expand Down Expand Up @@ -391,7 +392,7 @@ Interface.prototype._insertString = function(c) {
}
};

Interface.prototype._tabComplete = function() {
Interface.prototype._tabComplete = function(lastKeypressWasTab) {
var self = this;

self.pause();
Expand All @@ -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;
Expand All @@ -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();
}
});
Expand Down Expand Up @@ -688,7 +686,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;
Expand Down Expand Up @@ -892,7 +892,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
Expand Down
13 changes: 9 additions & 4 deletions test/parallel/test-readline-undefined-columns.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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 dca1f27

Please sign in to comment.