diff --git a/doc/api/readline.md b/doc/api/readline.md index ccc9ee92fe4f63..3d00e4ec64351d 100644 --- a/doc/api/readline.md +++ b/doc/api/readline.md @@ -375,6 +375,11 @@ changes: * `removeHistoryDuplicates` {boolean} If `true`, when a new input line added to the history list duplicates an older one, this removes the older line from the list. **Default:** `false`. + * `escapeCodeTimeout` {number} The duration `readline` will wait for a + character (when reading an ambiguous key sequence in milliseconds one that + can both form a complete key sequence using the input read so far and can + take additional input to complete a longer key sequence). + **Default:** `500`. The `readline.createInterface()` method creates a new `readline.Interface` instance. diff --git a/lib/readline.js b/lib/readline.js index cec88845d12cae..20317f46b536f6 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -82,6 +82,7 @@ function Interface(input, output, completer, terminal) { this.isCompletionEnabled = true; this._sawKeyPress = false; this._previousKey = null; + this.escapeCodeTimeout = ESCAPE_CODE_TIMEOUT; EventEmitter.call(this); var historySize; @@ -99,6 +100,16 @@ function Interface(input, output, completer, terminal) { if (input.prompt !== undefined) { prompt = input.prompt; } + if (input.escapeCodeTimeout !== undefined) { + if (Number.isFinite(input.escapeCodeTimeout)) { + this.escapeCodeTimeout = input.escapeCodeTimeout; + } else { + throw new ERR_INVALID_OPT_VALUE( + 'escapeCodeTimeout', + this.escapeCodeTimeout + ); + } + } crlfDelay = input.crlfDelay; input = input.input; } @@ -131,7 +142,6 @@ function Interface(input, output, completer, terminal) { this.removeHistoryDuplicates = !!removeHistoryDuplicates; this.crlfDelay = crlfDelay ? Math.max(kMincrlfDelay, crlfDelay) : kMincrlfDelay; - // Check arity, 2 - for async, 1 for sync if (typeof completer === 'function') { this.completer = completer.length === 2 ? @@ -1022,7 +1032,10 @@ function emitKeypressEvents(stream, iface) { stream[ESCAPE_DECODER].next(r[i]); // Escape letter at the tail position if (r[i] === kEscape && i + 1 === r.length) { - timeoutId = setTimeout(escapeCodeTimeout, ESCAPE_CODE_TIMEOUT); + timeoutId = setTimeout( + escapeCodeTimeout, + iface ? iface.escapeCodeTimeout : ESCAPE_CODE_TIMEOUT + ); } } catch (err) { // if the generator throws (it could happen in the `keypress` diff --git a/test/parallel/test-readline-interface-escapecodetimeout.js b/test/parallel/test-readline-interface-escapecodetimeout.js new file mode 100644 index 00000000000000..0e25423e8550c0 --- /dev/null +++ b/test/parallel/test-readline-interface-escapecodetimeout.js @@ -0,0 +1,46 @@ +'use strict'; +const common = require('../common'); + +// This test ensures that the escapeCodeTimeout option set correctly + +const assert = require('assert'); +const readline = require('readline'); +const EventEmitter = require('events').EventEmitter; + +class FakeInput extends EventEmitter { + resume() {} + pause() {} + write() {} + end() {} +} + +{ + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + escapeCodeTimeout: 50 + }); + assert.strictEqual(rli.escapeCodeTimeout, 50); + rli.close(); +} + +[ + null, + {}, + NaN, + '50' +].forEach((invalidInput) => { + common.expectsError(() => { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + escapeCodeTimeout: invalidInput + }); + rli.close(); + }, { + type: TypeError, + code: 'ERR_INVALID_OPT_VALUE' + }); +});