diff --git a/doc/api/readline.md b/doc/api/readline.md index c2b77c524533bf..e48865ac04a40a 100644 --- a/doc/api/readline.md +++ b/doc/api/readline.md @@ -354,6 +354,13 @@ a `'resize'` event on the `output` if/when the columns ever change Move cursor to the specified position in a given TTY stream. +## readline.emitKeypressEvents(stream[, interface]) + +Causes `stream` to begin emitting `'keypress'` events corresponding to its +input. +Optionally, `interface` specifies a `readline.Interface` instance for which +autocompletion is disabled when copy-pasted input is detected. + ## readline.moveCursor(stream, dx, dy) Move cursor relative to it's current position in a given TTY stream. diff --git a/lib/readline.js b/lib/readline.js index 2e799e21a3233a..1ff09d1db66e5a 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -36,6 +36,7 @@ function Interface(input, output, completer, terminal) { } this._sawReturn = false; + this.isCompletionEnabled = true; EventEmitter.call(this); var historySize; @@ -122,7 +123,7 @@ function Interface(input, output, completer, terminal) { } else { - exports.emitKeypressEvents(input); + exports.emitKeypressEvents(input, this); // input usually refers to stdin input.on('keypress', onkeypress); @@ -868,7 +869,7 @@ Interface.prototype._ttyWrite = function(s, key) { case 'tab': // If tab completion enabled, do that... - if (typeof this.completer === 'function') { + if (typeof this.completer === 'function' && this.isCompletionEnabled) { this._tabComplete(); break; } @@ -902,7 +903,7 @@ exports.Interface = Interface; const KEYPRESS_DECODER = Symbol('keypress-decoder'); const ESCAPE_DECODER = Symbol('escape-decoder'); -function emitKeypressEvents(stream) { +function emitKeypressEvents(stream, iface) { if (stream[KEYPRESS_DECODER]) return; var StringDecoder = require('string_decoder').StringDecoder; // lazy load stream[KEYPRESS_DECODER] = new StringDecoder('utf8'); @@ -915,6 +916,10 @@ function emitKeypressEvents(stream) { var r = stream[KEYPRESS_DECODER].write(b); if (r) { for (var i = 0; i < r.length; i++) { + if (r[i] === '\t' && typeof r[i + 1] === 'string' && iface) { + iface.isCompletionEnabled = false; + } + try { stream[ESCAPE_DECODER].next(r[i]); } catch (err) { @@ -923,6 +928,10 @@ function emitKeypressEvents(stream) { stream[ESCAPE_DECODER] = emitKeys(stream); stream[ESCAPE_DECODER].next(); throw err; + } finally { + if (iface) { + iface.isCompletionEnabled = true; + } } } } diff --git a/test/parallel/test-readline-interface.js b/test/parallel/test-readline-interface.js index 5e9842acb00084..4997b6e9bdb60b 100644 --- a/test/parallel/test-readline-interface.js +++ b/test/parallel/test-readline-interface.js @@ -208,7 +208,9 @@ function isWarned(emitter) { assert.strictEqual(called, false); called = true; }); - fi.emit('data', '\tfo\to\t'); + for (var character of '\tfo\to\t') { + fi.emit('data', character); + } fi.emit('data', '\n'); assert.ok(called); rli.close();