2017-01-03 13:16:48 -08:00
|
|
|
// Copyright Joyent, Inc. and other Node contributors.
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
// copy of this software and associated documentation files (the
|
|
|
|
// "Software"), to deal in the Software without restriction, including
|
|
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
|
|
// following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included
|
|
|
|
// in all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
2010-06-07 16:03:57 -07:00
|
|
|
// Inspiration for this code comes from Salvatore Sanfilippo's linenoise.
|
2011-11-01 20:52:35 +01:00
|
|
|
// https://github.com/antirez/linenoise
|
2010-05-31 11:50:35 -07:00
|
|
|
// Reference:
|
|
|
|
// * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
|
|
|
// * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
|
|
|
|
|
2014-11-22 16:59:48 +01:00
|
|
|
'use strict';
|
|
|
|
|
2019-11-22 18:04:46 +01:00
|
|
|
const {
|
2019-11-27 21:38:49 +01:00
|
|
|
DateNow,
|
2019-11-22 18:04:46 +01:00
|
|
|
MathCeil,
|
|
|
|
MathFloor,
|
|
|
|
MathMax,
|
2019-11-27 19:59:29 +01:00
|
|
|
NumberIsFinite,
|
|
|
|
NumberIsNaN,
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty,
|
|
|
|
ObjectSetPrototypeOf,
|
2019-11-30 16:55:29 +01:00
|
|
|
Symbol,
|
2019-12-13 22:01:47 +01:00
|
|
|
SymbolAsyncIterator,
|
2019-11-22 18:04:46 +01:00
|
|
|
} = primordials;
|
2019-03-31 13:30:12 +02:00
|
|
|
|
2018-02-27 14:55:32 +01:00
|
|
|
const {
|
2019-07-11 12:35:34 -04:00
|
|
|
ERR_INVALID_CALLBACK,
|
2018-02-27 14:55:32 +01:00
|
|
|
ERR_INVALID_CURSOR_POS,
|
|
|
|
ERR_INVALID_OPT_VALUE
|
|
|
|
} = require('internal/errors').codes;
|
2018-12-11 23:24:22 +08:00
|
|
|
const { validateString } = require('internal/validators');
|
2019-03-20 14:58:33 +01:00
|
|
|
const { inspect } = require('internal/util/inspect');
|
2017-04-29 13:46:26 -07:00
|
|
|
const EventEmitter = require('events');
|
|
|
|
const {
|
2019-12-11 19:21:40 +01:00
|
|
|
commonPrefix,
|
2017-04-29 17:05:04 -07:00
|
|
|
CSI,
|
2017-04-29 13:46:26 -07:00
|
|
|
emitKeys,
|
|
|
|
getStringWidth,
|
|
|
|
isFullWidthCodePoint,
|
2019-12-27 15:32:39 +01:00
|
|
|
kSubstringSearch,
|
2019-07-11 09:31:30 -04:00
|
|
|
kUTF16SurrogateThreshold,
|
2017-04-29 13:46:26 -07:00
|
|
|
stripVTControlCharacters
|
2019-06-23 11:54:43 -04:00
|
|
|
} = require('internal/readline/utils');
|
2017-04-29 13:46:26 -07:00
|
|
|
|
2019-04-06 05:56:00 +08:00
|
|
|
const { clearTimeout, setTimeout } = require('timers');
|
2017-04-29 17:05:04 -07:00
|
|
|
const {
|
|
|
|
kEscape,
|
2019-12-11 19:21:40 +01:00
|
|
|
kClearToLineBeginning,
|
|
|
|
kClearToLineEnd,
|
2017-04-29 17:05:04 -07:00
|
|
|
kClearLine,
|
|
|
|
kClearScreenDown
|
|
|
|
} = CSI;
|
|
|
|
|
2019-12-05 07:51:47 +01:00
|
|
|
const { StringDecoder } = require('string_decoder');
|
2018-05-06 23:08:00 +02:00
|
|
|
|
2018-10-26 17:53:04 -07:00
|
|
|
// Lazy load Readable for startup performance.
|
|
|
|
let Readable;
|
|
|
|
|
2015-01-21 11:36:59 -05:00
|
|
|
const kHistorySize = 30;
|
2016-08-15 17:37:01 +05:30
|
|
|
const kMincrlfDelay = 100;
|
2017-04-29 13:46:26 -07:00
|
|
|
// \r\n, \n, or \r followed by something other than \n
|
|
|
|
const lineEnding = /\r?\n|\r(?!\n)/;
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2018-10-26 17:53:04 -07:00
|
|
|
const kLineObjectStream = Symbol('line object stream');
|
|
|
|
|
2017-04-29 13:46:26 -07:00
|
|
|
const KEYPRESS_DECODER = Symbol('keypress-decoder');
|
|
|
|
const ESCAPE_DECODER = Symbol('escape-decoder');
|
|
|
|
|
|
|
|
// GNU readline library - keyseq-timeout is 500ms (default)
|
|
|
|
const ESCAPE_CODE_TIMEOUT = 500;
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2017-04-29 13:33:35 -07:00
|
|
|
function createInterface(input, output, completer, terminal) {
|
2016-01-18 17:51:15 +08:00
|
|
|
return new Interface(input, output, completer, terminal);
|
2017-04-29 13:46:26 -07:00
|
|
|
}
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2010-11-11 22:36:39 -08:00
|
|
|
|
2012-03-26 15:21:25 -07:00
|
|
|
function Interface(input, output, completer, terminal) {
|
2011-01-19 11:46:14 -08:00
|
|
|
if (!(this instanceof Interface)) {
|
2016-01-18 17:51:15 +08:00
|
|
|
return new Interface(input, output, completer, terminal);
|
2011-01-19 11:46:14 -08:00
|
|
|
}
|
2012-03-26 15:21:25 -07:00
|
|
|
|
2016-08-15 17:37:01 +05:30
|
|
|
this._sawReturnAt = 0;
|
2016-05-13 10:54:31 +03:00
|
|
|
this.isCompletionEnabled = true;
|
2016-08-24 00:05:59 +05:30
|
|
|
this._sawKeyPress = false;
|
2016-07-15 23:33:16 +02:00
|
|
|
this._previousKey = null;
|
2018-04-04 00:00:35 +04:30
|
|
|
this.escapeCodeTimeout = ESCAPE_CODE_TIMEOUT;
|
2013-01-29 17:50:44 -08:00
|
|
|
|
2012-06-12 21:53:08 +02:00
|
|
|
EventEmitter.call(this);
|
2019-11-12 15:56:22 +01:00
|
|
|
let historySize;
|
|
|
|
let removeHistoryDuplicates = false;
|
2016-08-15 17:37:01 +05:30
|
|
|
let crlfDelay;
|
2016-06-02 20:31:23 -05:00
|
|
|
let prompt = '> ';
|
2012-06-12 21:53:08 +02:00
|
|
|
|
2016-01-18 17:51:15 +08:00
|
|
|
if (input && input.input) {
|
2019-03-22 03:44:26 +01:00
|
|
|
// An options object was given
|
2012-03-26 15:21:25 -07:00
|
|
|
output = input.output;
|
|
|
|
completer = input.completer;
|
|
|
|
terminal = input.terminal;
|
2015-04-23 00:35:53 -07:00
|
|
|
historySize = input.historySize;
|
2017-03-20 18:30:41 -04:00
|
|
|
removeHistoryDuplicates = input.removeHistoryDuplicates;
|
2016-06-02 20:31:23 -05:00
|
|
|
if (input.prompt !== undefined) {
|
|
|
|
prompt = input.prompt;
|
|
|
|
}
|
2018-04-04 00:00:35 +04:30
|
|
|
if (input.escapeCodeTimeout !== undefined) {
|
2019-11-27 19:59:29 +01:00
|
|
|
if (NumberIsFinite(input.escapeCodeTimeout)) {
|
2018-04-04 00:00:35 +04:30
|
|
|
this.escapeCodeTimeout = input.escapeCodeTimeout;
|
|
|
|
} else {
|
|
|
|
throw new ERR_INVALID_OPT_VALUE(
|
|
|
|
'escapeCodeTimeout',
|
|
|
|
this.escapeCodeTimeout
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2016-08-15 17:37:01 +05:30
|
|
|
crlfDelay = input.crlfDelay;
|
2012-03-26 15:21:25 -07:00
|
|
|
input = input.input;
|
|
|
|
}
|
|
|
|
|
2019-06-03 00:48:53 +02:00
|
|
|
if (completer !== undefined && typeof completer !== 'function') {
|
2018-02-27 14:55:32 +01:00
|
|
|
throw new ERR_INVALID_OPT_VALUE('completer', completer);
|
2011-09-14 17:07:58 +02:00
|
|
|
}
|
|
|
|
|
2016-04-22 18:58:40 -04:00
|
|
|
if (historySize === undefined) {
|
|
|
|
historySize = kHistorySize;
|
|
|
|
}
|
|
|
|
|
2015-04-23 00:35:53 -07:00
|
|
|
if (typeof historySize !== 'number' ||
|
2019-11-27 19:59:29 +01:00
|
|
|
NumberIsNaN(historySize) ||
|
2015-04-23 00:35:53 -07:00
|
|
|
historySize < 0) {
|
2018-02-27 14:55:32 +01:00
|
|
|
throw new ERR_INVALID_OPT_VALUE.RangeError('historySize', historySize);
|
2015-04-23 00:35:53 -07:00
|
|
|
}
|
|
|
|
|
2018-12-10 13:27:32 +01:00
|
|
|
// Backwards compat; check the isTTY prop of the output stream
|
2012-03-26 15:21:25 -07:00
|
|
|
// when `terminal` was not specified
|
2015-01-28 20:05:53 -05:00
|
|
|
if (terminal === undefined && !(output === null || output === undefined)) {
|
2012-03-26 15:21:25 -07:00
|
|
|
terminal = !!output.isTTY;
|
|
|
|
}
|
|
|
|
|
2019-03-26 05:21:27 +01:00
|
|
|
const self = this;
|
2011-01-19 11:46:14 -08:00
|
|
|
|
2019-12-27 15:32:39 +01:00
|
|
|
this[kSubstringSearch] = null;
|
2010-05-31 11:50:35 -07:00
|
|
|
this.output = output;
|
2011-01-19 11:46:14 -08:00
|
|
|
this.input = input;
|
2015-04-23 00:35:53 -07:00
|
|
|
this.historySize = historySize;
|
2017-03-20 18:30:41 -04:00
|
|
|
this.removeHistoryDuplicates = !!removeHistoryDuplicates;
|
2017-06-06 20:22:22 +08:00
|
|
|
this.crlfDelay = crlfDelay ?
|
2019-11-22 18:04:46 +01:00
|
|
|
MathMax(kMincrlfDelay, crlfDelay) : kMincrlfDelay;
|
2011-09-07 14:39:49 +07:00
|
|
|
// Check arity, 2 - for async, 1 for sync
|
2015-05-20 21:17:10 -07:00
|
|
|
if (typeof completer === 'function') {
|
2017-07-20 16:05:39 +08:00
|
|
|
this.completer = completer.length === 2 ?
|
|
|
|
completer :
|
|
|
|
function completerWrapper(v, cb) {
|
|
|
|
cb(null, completer(v));
|
|
|
|
};
|
2015-05-20 21:17:10 -07:00
|
|
|
}
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2016-06-02 20:31:23 -05:00
|
|
|
this.setPrompt(prompt);
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2012-03-26 15:21:25 -07:00
|
|
|
this.terminal = !!terminal;
|
2010-06-08 13:05:21 -07:00
|
|
|
|
2019-02-22 16:07:43 +04:00
|
|
|
if (process.env.TERM === 'dumb') {
|
|
|
|
this._ttyWrite = _ttyWriteDumb.bind(this);
|
|
|
|
}
|
|
|
|
|
2012-07-23 20:44:12 -07:00
|
|
|
function ondata(data) {
|
|
|
|
self._normalWrite(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onend() {
|
2015-01-28 20:05:53 -05:00
|
|
|
if (typeof self._line_buffer === 'string' &&
|
|
|
|
self._line_buffer.length > 0) {
|
2014-01-22 09:53:23 +08:00
|
|
|
self.emit('line', self._line_buffer);
|
|
|
|
}
|
2012-07-23 20:44:12 -07:00
|
|
|
self.close();
|
|
|
|
}
|
|
|
|
|
2014-05-08 00:05:39 +08:00
|
|
|
function ontermend() {
|
2015-01-28 20:05:53 -05:00
|
|
|
if (typeof self.line === 'string' && self.line.length > 0) {
|
2014-05-08 00:05:39 +08:00
|
|
|
self.emit('line', self.line);
|
|
|
|
}
|
|
|
|
self.close();
|
|
|
|
}
|
|
|
|
|
2012-07-23 20:44:12 -07:00
|
|
|
function onkeypress(s, key) {
|
|
|
|
self._ttyWrite(s, key);
|
2016-10-11 15:20:03 -07:00
|
|
|
if (key && key.sequence) {
|
2019-01-21 01:22:27 +01:00
|
|
|
// If the key.sequence is half of a surrogate pair
|
2016-10-11 15:20:03 -07:00
|
|
|
// (>= 0xd800 and <= 0xdfff), refresh the line so
|
|
|
|
// the character is displayed appropriately.
|
|
|
|
const ch = key.sequence.codePointAt(0);
|
|
|
|
if (ch >= 0xd800 && ch <= 0xdfff)
|
|
|
|
self._refreshLine();
|
|
|
|
}
|
2012-07-23 20:44:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function onresize() {
|
|
|
|
self._refreshLine();
|
|
|
|
}
|
|
|
|
|
2018-10-26 17:53:04 -07:00
|
|
|
this[kLineObjectStream] = undefined;
|
|
|
|
|
2012-03-26 15:21:25 -07:00
|
|
|
if (!this.terminal) {
|
2017-07-20 16:05:39 +08:00
|
|
|
function onSelfCloseWithoutTerminal() {
|
2012-07-23 20:44:12 -07:00
|
|
|
input.removeListener('data', ondata);
|
|
|
|
input.removeListener('end', onend);
|
2017-07-20 16:05:39 +08:00
|
|
|
}
|
2011-01-19 11:46:14 -08:00
|
|
|
|
2017-07-20 16:05:39 +08:00
|
|
|
input.on('data', ondata);
|
|
|
|
input.on('end', onend);
|
|
|
|
self.once('close', onSelfCloseWithoutTerminal);
|
|
|
|
this._decoder = new StringDecoder('utf8');
|
2011-01-19 11:46:14 -08:00
|
|
|
} else {
|
2017-07-20 16:05:39 +08:00
|
|
|
function onSelfCloseWithTerminal() {
|
|
|
|
input.removeListener('keypress', onkeypress);
|
|
|
|
input.removeListener('end', ontermend);
|
|
|
|
if (output !== null && output !== undefined) {
|
|
|
|
output.removeListener('resize', onresize);
|
|
|
|
}
|
|
|
|
}
|
2011-01-19 11:46:14 -08:00
|
|
|
|
2016-05-13 10:54:31 +03:00
|
|
|
emitKeypressEvents(input, this);
|
2012-03-26 15:21:25 -07:00
|
|
|
|
2019-03-22 03:44:26 +01:00
|
|
|
// `input` usually refers to stdin
|
2012-07-23 20:44:12 -07:00
|
|
|
input.on('keypress', onkeypress);
|
2014-05-08 00:05:39 +08:00
|
|
|
input.on('end', ontermend);
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2010-06-08 13:05:21 -07:00
|
|
|
// Current line
|
2010-12-01 18:07:20 -08:00
|
|
|
this.line = '';
|
2010-06-08 13:05:21 -07:00
|
|
|
|
2012-05-21 19:41:56 -03:00
|
|
|
this._setRawMode(true);
|
2012-03-26 15:21:25 -07:00
|
|
|
this.terminal = true;
|
2010-05-31 11:50:35 -07:00
|
|
|
|
|
|
|
// Cursor position on the line.
|
|
|
|
this.cursor = 0;
|
|
|
|
|
|
|
|
this.history = [];
|
|
|
|
this.historyIndex = -1;
|
2010-09-12 21:23:53 -07:00
|
|
|
|
2015-01-28 20:05:53 -05:00
|
|
|
if (output !== null && output !== undefined)
|
2014-09-22 13:21:11 -07:00
|
|
|
output.on('resize', onresize);
|
|
|
|
|
2017-07-20 16:05:39 +08:00
|
|
|
self.once('close', onSelfCloseWithTerminal);
|
2010-05-31 11:50:35 -07:00
|
|
|
}
|
2012-10-24 02:42:57 +02:00
|
|
|
|
|
|
|
input.resume();
|
2010-05-31 11:50:35 -07:00
|
|
|
}
|
|
|
|
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectSetPrototypeOf(Interface.prototype, EventEmitter.prototype);
|
|
|
|
ObjectSetPrototypeOf(Interface, EventEmitter);
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(Interface.prototype, 'columns', {
|
2016-05-14 22:29:19 -07:00
|
|
|
configurable: true,
|
|
|
|
enumerable: true,
|
|
|
|
get: function() {
|
|
|
|
if (this.output && this.output.columns)
|
2019-11-12 15:56:22 +01:00
|
|
|
return this.output.columns;
|
|
|
|
return Infinity;
|
2016-05-14 22:29:19 -07:00
|
|
|
}
|
2010-08-11 23:14:12 -07:00
|
|
|
});
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2013-03-15 16:18:30 -10:00
|
|
|
Interface.prototype.setPrompt = function(prompt) {
|
2010-05-31 11:50:35 -07:00
|
|
|
this._prompt = prompt;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-05-21 19:41:56 -03:00
|
|
|
Interface.prototype._setRawMode = function(mode) {
|
2016-05-08 03:29:43 +02:00
|
|
|
const wasInRawMode = this.input.isRaw;
|
|
|
|
|
2015-01-28 20:05:53 -05:00
|
|
|
if (typeof this.input.setRawMode === 'function') {
|
2016-05-08 03:29:43 +02:00
|
|
|
this.input.setRawMode(mode);
|
2012-05-21 19:41:56 -03:00
|
|
|
}
|
2016-05-08 03:29:43 +02:00
|
|
|
|
|
|
|
return wasInRawMode;
|
2012-06-11 07:48:02 -07:00
|
|
|
};
|
2012-05-21 19:41:56 -03:00
|
|
|
|
|
|
|
|
2011-12-09 15:24:15 +06:00
|
|
|
Interface.prototype.prompt = function(preserveCursor) {
|
2012-02-15 09:08:26 -05:00
|
|
|
if (this.paused) this.resume();
|
2019-02-22 16:07:43 +04:00
|
|
|
if (this.terminal && process.env.TERM !== 'dumb') {
|
2011-12-09 15:24:15 +06:00
|
|
|
if (!preserveCursor) this.cursor = 0;
|
2010-05-31 11:50:35 -07:00
|
|
|
this._refreshLine();
|
|
|
|
} else {
|
2014-09-22 13:21:11 -07:00
|
|
|
this._writeToOutput(this._prompt);
|
2010-05-31 11:50:35 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2010-12-30 01:34:31 -08:00
|
|
|
Interface.prototype.question = function(query, cb) {
|
2015-01-28 20:05:53 -05:00
|
|
|
if (typeof cb === 'function') {
|
2010-12-30 18:28:30 -08:00
|
|
|
if (this._questionCallback) {
|
|
|
|
this.prompt();
|
|
|
|
} else {
|
|
|
|
this._oldPrompt = this._prompt;
|
|
|
|
this.setPrompt(query);
|
|
|
|
this._questionCallback = cb;
|
|
|
|
this.prompt();
|
|
|
|
}
|
2010-12-30 01:34:31 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Interface.prototype._onLine = function(line) {
|
|
|
|
if (this._questionCallback) {
|
2019-11-12 15:56:22 +01:00
|
|
|
const cb = this._questionCallback;
|
2010-12-30 01:34:31 -08:00
|
|
|
this._questionCallback = null;
|
|
|
|
this.setPrompt(this._oldPrompt);
|
2011-01-06 16:06:27 -08:00
|
|
|
cb(line);
|
2010-12-30 01:34:31 -08:00
|
|
|
} else {
|
|
|
|
this.emit('line', line);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-09-22 13:21:11 -07:00
|
|
|
Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) {
|
2018-12-11 23:24:22 +08:00
|
|
|
validateString(stringToWrite, 'stringToWrite');
|
2014-09-22 13:21:11 -07:00
|
|
|
|
2017-06-20 14:37:00 -07:00
|
|
|
if (this.output !== null && this.output !== undefined) {
|
2014-09-22 13:21:11 -07:00
|
|
|
this.output.write(stringToWrite);
|
2017-06-20 14:37:00 -07:00
|
|
|
}
|
2014-09-22 13:21:11 -07:00
|
|
|
};
|
2010-12-30 01:34:31 -08:00
|
|
|
|
2010-12-01 18:07:20 -08:00
|
|
|
Interface.prototype._addHistory = function() {
|
|
|
|
if (this.line.length === 0) return '';
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2019-01-21 01:22:27 +01:00
|
|
|
// If the history is disabled then return the line
|
2016-04-22 18:58:40 -04:00
|
|
|
if (this.historySize === 0) return this.line;
|
|
|
|
|
2019-01-21 01:22:27 +01:00
|
|
|
// If the trimmed line is empty then return the line
|
2016-08-24 00:05:59 +05:30
|
|
|
if (this.line.trim().length === 0) return this.line;
|
|
|
|
|
2012-06-13 21:37:31 +04:00
|
|
|
if (this.history.length === 0 || this.history[0] !== this.line) {
|
2017-03-20 18:30:41 -04:00
|
|
|
if (this.removeHistoryDuplicates) {
|
2015-09-21 10:08:36 -04:00
|
|
|
// Remove older history line if identical to new one
|
|
|
|
const dupIndex = this.history.indexOf(this.line);
|
|
|
|
if (dupIndex !== -1) this.history.splice(dupIndex, 1);
|
|
|
|
}
|
|
|
|
|
2012-06-13 21:37:31 +04:00
|
|
|
this.history.unshift(this.line);
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2012-06-13 21:37:31 +04:00
|
|
|
// Only store so many
|
2015-04-23 00:35:53 -07:00
|
|
|
if (this.history.length > this.historySize) this.history.pop();
|
2012-06-13 21:37:31 +04:00
|
|
|
}
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2012-06-13 21:37:31 +04:00
|
|
|
this.historyIndex = -1;
|
2010-06-07 21:19:25 -07:00
|
|
|
return this.history[0];
|
2010-05-31 11:50:35 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2010-12-01 18:07:20 -08:00
|
|
|
Interface.prototype._refreshLine = function() {
|
2012-03-20 15:14:40 -07:00
|
|
|
// line length
|
2019-03-26 05:21:27 +01:00
|
|
|
const line = this._prompt + this.line;
|
|
|
|
const dispPos = this._getDisplayPos(line);
|
|
|
|
const lineCols = dispPos.cols;
|
|
|
|
const lineRows = dispPos.rows;
|
2012-03-20 15:14:40 -07:00
|
|
|
|
|
|
|
// cursor position
|
2019-11-27 11:02:02 -09:00
|
|
|
const cursorPos = this.getCursorPos();
|
2012-03-20 15:14:40 -07:00
|
|
|
|
2018-12-03 17:15:45 +01:00
|
|
|
// First move to the bottom of the current line, based on cursor pos
|
2019-03-26 05:21:27 +01:00
|
|
|
const prevRows = this.prevRows || 0;
|
2012-03-20 15:14:40 -07:00
|
|
|
if (prevRows > 0) {
|
2017-04-29 13:33:35 -07:00
|
|
|
moveCursor(this.output, 0, -prevRows);
|
2012-03-20 15:14:40 -07:00
|
|
|
}
|
|
|
|
|
2010-05-31 11:50:35 -07:00
|
|
|
// Cursor to left edge.
|
2017-04-29 13:33:35 -07:00
|
|
|
cursorTo(this.output, 0);
|
2012-03-20 15:14:40 -07:00
|
|
|
// erase data
|
2017-04-29 13:33:35 -07:00
|
|
|
clearScreenDown(this.output);
|
2010-05-31 11:50:35 -07:00
|
|
|
|
|
|
|
// Write the prompt and the current buffer content.
|
2014-09-22 13:21:11 -07:00
|
|
|
this._writeToOutput(line);
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2012-03-20 15:14:40 -07:00
|
|
|
// Force terminal to allocate a new line
|
|
|
|
if (lineCols === 0) {
|
2014-09-22 13:21:11 -07:00
|
|
|
this._writeToOutput(' ');
|
2012-03-20 15:14:40 -07:00
|
|
|
}
|
2010-05-31 11:50:35 -07:00
|
|
|
|
|
|
|
// Move cursor to original position.
|
2017-04-29 13:33:35 -07:00
|
|
|
cursorTo(this.output, cursorPos.cols);
|
2012-03-20 15:14:40 -07:00
|
|
|
|
2019-03-26 05:21:27 +01:00
|
|
|
const diff = lineRows - cursorPos.rows;
|
2012-03-20 15:14:40 -07:00
|
|
|
if (diff > 0) {
|
2017-04-29 13:33:35 -07:00
|
|
|
moveCursor(this.output, 0, -diff);
|
2012-03-20 15:14:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
this.prevRows = cursorPos.rows;
|
2010-05-31 11:50:35 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-04-17 11:31:07 -07:00
|
|
|
Interface.prototype.close = function() {
|
|
|
|
if (this.closed) return;
|
2013-07-30 14:43:31 +01:00
|
|
|
this.pause();
|
2012-03-26 15:21:25 -07:00
|
|
|
if (this.terminal) {
|
2012-05-21 19:41:56 -03:00
|
|
|
this._setRawMode(false);
|
2010-12-30 15:46:47 -08:00
|
|
|
}
|
2012-04-17 11:31:07 -07:00
|
|
|
this.closed = true;
|
|
|
|
this.emit('close');
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Interface.prototype.pause = function() {
|
|
|
|
if (this.paused) return;
|
2012-02-15 09:08:26 -05:00
|
|
|
this.input.pause();
|
|
|
|
this.paused = true;
|
|
|
|
this.emit('pause');
|
2013-08-27 18:59:58 -07:00
|
|
|
return this;
|
2010-12-30 15:46:47 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Interface.prototype.resume = function() {
|
2012-04-17 11:31:07 -07:00
|
|
|
if (!this.paused) return;
|
2012-02-15 09:08:26 -05:00
|
|
|
this.input.resume();
|
|
|
|
this.paused = false;
|
|
|
|
this.emit('resume');
|
2013-08-27 18:59:58 -07:00
|
|
|
return this;
|
2010-12-30 15:46:47 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
Interface.prototype.write = function(d, key) {
|
2012-02-15 09:08:26 -05:00
|
|
|
if (this.paused) this.resume();
|
2019-02-22 16:07:43 +04:00
|
|
|
if (this.terminal) {
|
|
|
|
this._ttyWrite(d, key);
|
|
|
|
} else {
|
|
|
|
this._normalWrite(d);
|
|
|
|
}
|
2010-05-31 11:50:35 -07:00
|
|
|
};
|
|
|
|
|
2010-12-01 18:07:20 -08:00
|
|
|
Interface.prototype._normalWrite = function(b) {
|
2015-01-28 20:05:53 -05:00
|
|
|
if (b === undefined) {
|
2012-04-06 11:41:59 -07:00
|
|
|
return;
|
|
|
|
}
|
2019-11-12 15:56:22 +01:00
|
|
|
let string = this._decoder.write(b);
|
2016-08-15 17:37:01 +05:30
|
|
|
if (this._sawReturnAt &&
|
2019-11-27 21:38:49 +01:00
|
|
|
DateNow() - this._sawReturnAt <= this.crlfDelay) {
|
2013-01-29 17:50:44 -08:00
|
|
|
string = string.replace(/^\n/, '');
|
2016-08-15 17:37:01 +05:30
|
|
|
this._sawReturnAt = 0;
|
2013-01-29 17:50:44 -08:00
|
|
|
}
|
|
|
|
|
2014-09-14 23:40:49 +08:00
|
|
|
// Run test() on the new string chunk, not on the entire line buffer.
|
2019-03-26 05:21:27 +01:00
|
|
|
const newPartContainsEnding = lineEnding.test(string);
|
2014-09-14 23:40:49 +08:00
|
|
|
|
2012-04-06 14:33:58 -07:00
|
|
|
if (this._line_buffer) {
|
|
|
|
string = this._line_buffer + string;
|
|
|
|
this._line_buffer = null;
|
|
|
|
}
|
2014-09-14 23:40:49 +08:00
|
|
|
if (newPartContainsEnding) {
|
2019-11-27 21:38:49 +01:00
|
|
|
this._sawReturnAt = string.endsWith('\r') ? DateNow() : 0;
|
2013-01-29 17:50:44 -08:00
|
|
|
|
2018-12-10 13:27:32 +01:00
|
|
|
// Got one or more newlines; process into "line" events
|
2019-11-12 15:56:22 +01:00
|
|
|
const lines = string.split(lineEnding);
|
2018-12-03 17:15:45 +01:00
|
|
|
// Either '' or (conceivably) the unfinished portion of the next line
|
2012-04-06 14:33:58 -07:00
|
|
|
string = lines.pop();
|
|
|
|
this._line_buffer = string;
|
2019-11-12 15:56:22 +01:00
|
|
|
for (let n = 0; n < lines.length; n++)
|
2017-02-26 18:15:36 -08:00
|
|
|
this._onLine(lines[n]);
|
2012-04-06 14:33:58 -07:00
|
|
|
} else if (string) {
|
2018-12-10 13:27:32 +01:00
|
|
|
// No newlines this time, save what we have for next time
|
2012-04-06 14:33:58 -07:00
|
|
|
this._line_buffer = string;
|
2012-04-06 11:41:59 -07:00
|
|
|
}
|
2010-05-31 11:50:35 -07:00
|
|
|
};
|
|
|
|
|
2010-12-01 18:07:20 -08:00
|
|
|
Interface.prototype._insertString = function(c) {
|
2010-08-09 02:18:32 -07:00
|
|
|
if (this.cursor < this.line.length) {
|
2019-11-12 15:56:22 +01:00
|
|
|
const beg = this.line.slice(0, this.cursor);
|
|
|
|
const end = this.line.slice(this.cursor, this.line.length);
|
2010-08-09 02:18:32 -07:00
|
|
|
this.line = beg + c + end;
|
|
|
|
this.cursor += c.length;
|
|
|
|
this._refreshLine();
|
|
|
|
} else {
|
|
|
|
this.line += c;
|
|
|
|
this.cursor += c.length;
|
2012-03-23 11:24:06 -07:00
|
|
|
|
2019-11-27 11:02:02 -09:00
|
|
|
if (this.getCursorPos().cols === 0) {
|
2012-03-29 01:27:57 +02:00
|
|
|
this._refreshLine();
|
2012-03-23 11:24:06 -07:00
|
|
|
} else {
|
2014-09-22 13:21:11 -07:00
|
|
|
this._writeToOutput(c);
|
2012-03-23 11:24:06 -07:00
|
|
|
}
|
2012-03-20 15:14:40 -07:00
|
|
|
|
2019-01-21 01:22:27 +01:00
|
|
|
// A hack to get the line refreshed if it's needed
|
2012-03-20 15:14:40 -07:00
|
|
|
this._moveCursor(0);
|
2010-08-09 02:18:32 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-07-15 23:33:16 +02:00
|
|
|
Interface.prototype._tabComplete = function(lastKeypressWasTab) {
|
2019-03-26 05:21:27 +01:00
|
|
|
const self = this;
|
2010-08-09 02:18:32 -07:00
|
|
|
|
2011-09-08 16:03:27 +07:00
|
|
|
self.pause();
|
2017-07-20 16:05:39 +08:00
|
|
|
self.completer(self.line.slice(0, self.cursor), function onComplete(err, rv) {
|
2011-09-07 14:39:49 +07:00
|
|
|
self.resume();
|
|
|
|
|
|
|
|
if (err) {
|
2019-12-11 19:26:47 +01:00
|
|
|
self._writeToOutput(`Tab completion error: ${inspect(err)}`);
|
2011-09-07 14:39:49 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
// Result and the text that was completed.
|
|
|
|
const [completions, completeOn] = rv;
|
|
|
|
|
|
|
|
if (!completions || completions.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
2011-09-07 14:39:49 +07:00
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
// Apply/show completions.
|
|
|
|
if (lastKeypressWasTab) {
|
|
|
|
self._writeToOutput('\r\n');
|
|
|
|
const width = completions.reduce((a, b) => {
|
|
|
|
return a.length > b.length ? a : b;
|
|
|
|
}).length + 2; // 2 space padding
|
|
|
|
let maxColumns = MathFloor(self.columns / width);
|
|
|
|
if (!maxColumns || maxColumns === Infinity) {
|
|
|
|
maxColumns = 1;
|
2011-09-07 14:39:49 +07:00
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
let group = [];
|
|
|
|
for (const c of completions) {
|
|
|
|
if (c === '') {
|
|
|
|
handleGroup(self, group, width, maxColumns);
|
|
|
|
group = [];
|
|
|
|
} else {
|
|
|
|
group.push(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
handleGroup(self, group, width, maxColumns);
|
|
|
|
}
|
2016-07-15 23:33:16 +02:00
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
// If there is a common prefix to all matches, then apply that portion.
|
|
|
|
const f = completions.filter((e) => e);
|
|
|
|
const prefix = commonPrefix(f);
|
|
|
|
if (prefix.length > completeOn.length) {
|
|
|
|
self._insertString(prefix.slice(completeOn.length));
|
2010-08-09 02:18:32 -07:00
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
|
|
|
|
self._refreshLine();
|
2011-09-07 14:39:49 +07:00
|
|
|
});
|
2010-08-09 02:18:32 -07:00
|
|
|
};
|
|
|
|
|
2012-07-04 23:01:03 +02:00
|
|
|
// this = Interface instance
|
2012-07-06 19:41:01 -07:00
|
|
|
function handleGroup(self, group, width, maxColumns) {
|
2017-01-31 00:40:19 -05:00
|
|
|
if (group.length === 0) {
|
2012-07-04 23:01:03 +02:00
|
|
|
return;
|
|
|
|
}
|
2019-11-22 18:04:46 +01:00
|
|
|
const minRows = MathCeil(group.length / maxColumns);
|
2019-11-12 15:56:22 +01:00
|
|
|
for (let row = 0; row < minRows; row++) {
|
|
|
|
for (let col = 0; col < maxColumns; col++) {
|
|
|
|
const idx = row * maxColumns + col;
|
2012-07-04 23:01:03 +02:00
|
|
|
if (idx >= group.length) {
|
|
|
|
break;
|
|
|
|
}
|
2019-11-12 15:56:22 +01:00
|
|
|
const item = group[idx];
|
2014-09-22 13:21:11 -07:00
|
|
|
self._writeToOutput(item);
|
2012-07-04 23:01:03 +02:00
|
|
|
if (col < maxColumns - 1) {
|
2019-11-12 15:56:22 +01:00
|
|
|
for (let s = 0; s < width - item.length; s++) {
|
2014-09-22 13:21:11 -07:00
|
|
|
self._writeToOutput(' ');
|
2012-07-04 23:01:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-09-22 13:21:11 -07:00
|
|
|
self._writeToOutput('\r\n');
|
2012-07-04 23:01:03 +02:00
|
|
|
}
|
2014-09-22 13:21:11 -07:00
|
|
|
self._writeToOutput('\r\n');
|
2012-07-04 23:01:03 +02:00
|
|
|
}
|
2011-01-24 10:55:30 -08:00
|
|
|
|
2011-01-25 03:54:26 +01:00
|
|
|
Interface.prototype._wordLeft = function() {
|
|
|
|
if (this.cursor > 0) {
|
2019-03-19 14:19:38 +01:00
|
|
|
// Reverse the string and match a word near beginning
|
|
|
|
// to avoid quadratic time complexity
|
2019-11-12 15:56:22 +01:00
|
|
|
const leading = this.line.slice(0, this.cursor);
|
|
|
|
const reversed = leading.split('').reverse().join('');
|
|
|
|
const match = reversed.match(/^\s*(?:[^\w\s]+|\w+)?/);
|
2012-03-20 15:14:40 -07:00
|
|
|
this._moveCursor(-match[0].length);
|
2011-01-25 03:54:26 +01:00
|
|
|
}
|
2011-07-29 10:03:05 -07:00
|
|
|
};
|
2011-01-25 03:54:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
Interface.prototype._wordRight = function() {
|
|
|
|
if (this.cursor < this.line.length) {
|
2019-11-12 15:56:22 +01:00
|
|
|
const trailing = this.line.slice(this.cursor);
|
|
|
|
const match = trailing.match(/^(?:\s+|[^\w\s]+|\w+)\s*/);
|
2012-03-20 15:14:40 -07:00
|
|
|
this._moveCursor(match[0].length);
|
2011-01-25 03:54:26 +01:00
|
|
|
}
|
2011-07-29 10:03:05 -07:00
|
|
|
};
|
2011-01-25 03:54:26 +01:00
|
|
|
|
2019-01-25 21:15:06 -05:00
|
|
|
function charLengthLeft(str, i) {
|
|
|
|
if (i <= 0)
|
|
|
|
return 0;
|
2019-10-04 00:35:41 +08:00
|
|
|
if ((i > 1 && str.codePointAt(i - 2) >= kUTF16SurrogateThreshold) ||
|
2019-07-11 09:31:30 -04:00
|
|
|
str.codePointAt(i - 1) >= kUTF16SurrogateThreshold) {
|
2019-01-25 21:15:06 -05:00
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function charLengthAt(str, i) {
|
2019-12-11 19:33:53 +01:00
|
|
|
if (str.length <= i) {
|
|
|
|
// Pretend to move to the right. This is necessary to autocomplete while
|
|
|
|
// moving to the right.
|
|
|
|
return 1;
|
|
|
|
}
|
2019-07-11 09:31:30 -04:00
|
|
|
return str.codePointAt(i) >= kUTF16SurrogateThreshold ? 2 : 1;
|
2019-01-25 21:15:06 -05:00
|
|
|
}
|
2011-01-25 03:54:26 +01:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
Interface.prototype._deleteLeft = function() {
|
|
|
|
if (this.cursor > 0 && this.line.length > 0) {
|
2019-01-25 21:15:06 -05:00
|
|
|
// The number of UTF-16 units comprising the character to the left
|
|
|
|
const charSize = charLengthLeft(this.line, this.cursor);
|
|
|
|
this.line = this.line.slice(0, this.cursor - charSize) +
|
2011-01-14 03:44:05 +01:00
|
|
|
this.line.slice(this.cursor, this.line.length);
|
|
|
|
|
2019-01-25 21:15:06 -05:00
|
|
|
this.cursor -= charSize;
|
2011-01-14 03:44:05 +01:00
|
|
|
this._refreshLine();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-01-24 10:55:30 -08:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
Interface.prototype._deleteRight = function() {
|
2019-01-25 21:15:06 -05:00
|
|
|
if (this.cursor < this.line.length) {
|
|
|
|
// The number of UTF-16 units comprising the character to the left
|
|
|
|
const charSize = charLengthAt(this.line, this.cursor);
|
|
|
|
this.line = this.line.slice(0, this.cursor) +
|
|
|
|
this.line.slice(this.cursor + charSize, this.line.length);
|
|
|
|
this._refreshLine();
|
|
|
|
}
|
2011-01-24 10:55:30 -08:00
|
|
|
};
|
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
|
2011-01-25 03:54:26 +01:00
|
|
|
Interface.prototype._deleteWordLeft = function() {
|
|
|
|
if (this.cursor > 0) {
|
2019-03-19 14:19:38 +01:00
|
|
|
// Reverse the string and match a word near beginning
|
|
|
|
// to avoid quadratic time complexity
|
2019-11-12 15:56:22 +01:00
|
|
|
let leading = this.line.slice(0, this.cursor);
|
|
|
|
const reversed = leading.split('').reverse().join('');
|
|
|
|
const match = reversed.match(/^\s*(?:[^\w\s]+|\w+)?/);
|
2011-01-25 03:54:26 +01:00
|
|
|
leading = leading.slice(0, leading.length - match[0].length);
|
|
|
|
this.line = leading + this.line.slice(this.cursor, this.line.length);
|
|
|
|
this.cursor = leading.length;
|
|
|
|
this._refreshLine();
|
|
|
|
}
|
2011-07-29 10:03:05 -07:00
|
|
|
};
|
2011-01-25 03:54:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
Interface.prototype._deleteWordRight = function() {
|
|
|
|
if (this.cursor < this.line.length) {
|
2019-11-12 15:56:22 +01:00
|
|
|
const trailing = this.line.slice(this.cursor);
|
|
|
|
const match = trailing.match(/^(?:\s+|\W+|\w+)\s*/);
|
2011-01-25 03:54:26 +01:00
|
|
|
this.line = this.line.slice(0, this.cursor) +
|
|
|
|
trailing.slice(match[0].length);
|
|
|
|
this._refreshLine();
|
|
|
|
}
|
2011-07-29 10:03:05 -07:00
|
|
|
};
|
2011-01-25 03:54:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
Interface.prototype._deleteLineLeft = function() {
|
|
|
|
this.line = this.line.slice(this.cursor);
|
|
|
|
this.cursor = 0;
|
|
|
|
this._refreshLine();
|
2011-07-29 10:03:05 -07:00
|
|
|
};
|
2011-01-25 03:54:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
Interface.prototype._deleteLineRight = function() {
|
|
|
|
this.line = this.line.slice(0, this.cursor);
|
|
|
|
this._refreshLine();
|
2011-07-29 10:03:05 -07:00
|
|
|
};
|
2011-01-25 03:54:26 +01:00
|
|
|
|
|
|
|
|
2012-03-20 15:14:40 -07:00
|
|
|
Interface.prototype.clearLine = function() {
|
|
|
|
this._moveCursor(+Infinity);
|
2014-09-22 13:21:11 -07:00
|
|
|
this._writeToOutput('\r\n');
|
2012-03-20 15:14:40 -07:00
|
|
|
this.line = '';
|
|
|
|
this.cursor = 0;
|
|
|
|
this.prevRows = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
Interface.prototype._line = function() {
|
2019-03-26 05:21:27 +01:00
|
|
|
const line = this._addHistory();
|
2012-03-20 15:14:40 -07:00
|
|
|
this.clearLine();
|
2011-01-14 03:44:05 +01:00
|
|
|
this._onLine(line);
|
|
|
|
};
|
|
|
|
|
2019-12-27 15:32:39 +01:00
|
|
|
// TODO(BridgeAR): Add underscores to the search part and a red background in
|
|
|
|
// case no match is found. This should only be the visual part and not the
|
|
|
|
// actual line content!
|
|
|
|
// TODO(BridgeAR): In case the substring based search is active and the end is
|
|
|
|
// reached, show a comment how to search the history as before. E.g., using
|
|
|
|
// <ctrl> + N. Only show this after two/three UPs or DOWNs, not on the first
|
|
|
|
// one.
|
2010-12-01 18:07:20 -08:00
|
|
|
Interface.prototype._historyNext = function() {
|
2019-12-27 15:32:39 +01:00
|
|
|
if (this.historyIndex >= 0) {
|
|
|
|
const search = this[kSubstringSearch] || '';
|
|
|
|
let index = this.historyIndex - 1;
|
|
|
|
while (index >= 0 &&
|
2019-12-27 19:33:49 +01:00
|
|
|
(!this.history[index].startsWith(search) ||
|
|
|
|
this.line === this.history[index])) {
|
2019-12-27 15:32:39 +01:00
|
|
|
index--;
|
|
|
|
}
|
|
|
|
if (index === -1) {
|
|
|
|
this.line = search;
|
|
|
|
} else {
|
|
|
|
this.line = this.history[index];
|
|
|
|
}
|
|
|
|
this.historyIndex = index;
|
2019-03-22 03:44:26 +01:00
|
|
|
this.cursor = this.line.length; // Set cursor to end of line.
|
2010-06-07 16:43:50 -07:00
|
|
|
this._refreshLine();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2010-12-01 18:07:20 -08:00
|
|
|
Interface.prototype._historyPrev = function() {
|
2019-12-29 13:09:45 +01:00
|
|
|
if (this.historyIndex < this.history.length && this.history.length) {
|
2019-12-27 15:32:39 +01:00
|
|
|
const search = this[kSubstringSearch] || '';
|
|
|
|
let index = this.historyIndex + 1;
|
|
|
|
while (index < this.history.length &&
|
2019-12-27 19:33:49 +01:00
|
|
|
(!this.history[index].startsWith(search) ||
|
|
|
|
this.line === this.history[index])) {
|
2019-12-27 15:32:39 +01:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
if (index === this.history.length) {
|
2019-12-29 13:09:45 +01:00
|
|
|
this.line = search;
|
2019-12-27 15:32:39 +01:00
|
|
|
} else {
|
|
|
|
this.line = this.history[index];
|
|
|
|
}
|
|
|
|
this.historyIndex = index;
|
2019-03-22 03:44:26 +01:00
|
|
|
this.cursor = this.line.length; // Set cursor to end of line.
|
2010-06-07 16:43:50 -07:00
|
|
|
this._refreshLine();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-15 16:18:30 -10:00
|
|
|
// Returns the last character's display position of the given string
|
|
|
|
Interface.prototype._getDisplayPos = function(str) {
|
2019-11-12 15:56:22 +01:00
|
|
|
let offset = 0;
|
2019-03-26 05:21:27 +01:00
|
|
|
const col = this.columns;
|
2019-12-17 18:01:09 +01:00
|
|
|
let rows = 0;
|
2013-06-04 17:01:14 +02:00
|
|
|
str = stripVTControlCharacters(str);
|
2019-11-12 15:56:22 +01:00
|
|
|
for (let i = 0, len = str.length; i < len; i++) {
|
|
|
|
const code = str.codePointAt(i);
|
2019-07-11 09:31:30 -04:00
|
|
|
if (code >= kUTF16SurrogateThreshold) { // Surrogates.
|
2013-03-15 16:18:30 -10:00
|
|
|
i++;
|
|
|
|
}
|
2014-03-09 14:46:54 +08:00
|
|
|
if (code === 0x0a) { // new line \n
|
2019-12-17 18:01:09 +01:00
|
|
|
// rows must be incremented by 1 even if offset = 0 or col = +Infinity
|
|
|
|
rows += MathCeil(offset / col) || 1;
|
2014-03-09 14:46:54 +08:00
|
|
|
offset = 0;
|
|
|
|
continue;
|
|
|
|
}
|
2017-06-26 14:34:21 +08:00
|
|
|
const width = getStringWidth(code);
|
|
|
|
if (width === 0 || width === 1) {
|
|
|
|
offset += width;
|
|
|
|
} else { // width === 2
|
2013-03-15 16:18:30 -10:00
|
|
|
if ((offset + 1) % col === 0) {
|
|
|
|
offset++;
|
|
|
|
}
|
|
|
|
offset += 2;
|
|
|
|
}
|
|
|
|
}
|
2019-03-26 05:21:27 +01:00
|
|
|
const cols = offset % col;
|
2019-12-17 18:01:09 +01:00
|
|
|
rows += (offset - cols) / col;
|
|
|
|
return { cols, rows };
|
2013-03-15 16:18:30 -10:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-03-20 15:14:40 -07:00
|
|
|
// Returns current cursor's position and line
|
2019-11-27 11:02:02 -09:00
|
|
|
Interface.prototype.getCursorPos = function() {
|
2019-03-26 05:21:27 +01:00
|
|
|
const columns = this.columns;
|
|
|
|
const strBeforeCursor = this._prompt + this.line.substring(0, this.cursor);
|
2019-12-17 18:01:09 +01:00
|
|
|
const dispPos = this._getDisplayPos(strBeforeCursor);
|
2019-11-12 15:56:22 +01:00
|
|
|
let cols = dispPos.cols;
|
|
|
|
let rows = dispPos.rows;
|
2013-03-15 16:18:30 -10:00
|
|
|
// If the cursor is on a full-width character which steps over the line,
|
|
|
|
// move the cursor to the beginning of the next line.
|
|
|
|
if (cols + 1 === columns &&
|
|
|
|
this.cursor < this.line.length &&
|
2015-02-12 23:09:34 +03:00
|
|
|
isFullWidthCodePoint(this.line.codePointAt(this.cursor))) {
|
2013-03-15 16:18:30 -10:00
|
|
|
rows++;
|
|
|
|
cols = 0;
|
|
|
|
}
|
2019-12-17 18:01:09 +01:00
|
|
|
return { cols, rows };
|
2012-03-20 15:14:40 -07:00
|
|
|
};
|
2019-11-27 11:02:02 -09:00
|
|
|
Interface.prototype._getCursorPos = Interface.prototype.getCursorPos;
|
2012-03-20 15:14:40 -07:00
|
|
|
|
|
|
|
|
|
|
|
// This function moves cursor dx places to the right
|
|
|
|
// (-dx for left) and refreshes the line if it is needed
|
|
|
|
Interface.prototype._moveCursor = function(dx) {
|
2019-03-26 05:21:27 +01:00
|
|
|
const oldcursor = this.cursor;
|
2019-11-27 11:02:02 -09:00
|
|
|
const oldPos = this.getCursorPos();
|
2012-03-20 15:14:40 -07:00
|
|
|
this.cursor += dx;
|
|
|
|
|
|
|
|
// bounds check
|
|
|
|
if (this.cursor < 0) this.cursor = 0;
|
2013-03-15 16:18:30 -10:00
|
|
|
else if (this.cursor > this.line.length) this.cursor = this.line.length;
|
2012-03-20 15:14:40 -07:00
|
|
|
|
2019-11-27 11:02:02 -09:00
|
|
|
const newPos = this.getCursorPos();
|
2012-03-20 15:14:40 -07:00
|
|
|
|
2019-03-07 01:03:53 +01:00
|
|
|
// Check if cursors are in the same line
|
2012-03-23 11:24:06 -07:00
|
|
|
if (oldPos.rows === newPos.rows) {
|
2019-11-12 15:56:22 +01:00
|
|
|
const diffCursor = this.cursor - oldcursor;
|
|
|
|
let diffWidth;
|
2013-03-15 16:18:30 -10:00
|
|
|
if (diffCursor < 0) {
|
|
|
|
diffWidth = -getStringWidth(
|
2017-07-05 09:21:40 -07:00
|
|
|
this.line.substring(this.cursor, oldcursor)
|
|
|
|
);
|
2013-03-15 16:18:30 -10:00
|
|
|
} else if (diffCursor > 0) {
|
|
|
|
diffWidth = getStringWidth(
|
2017-07-05 09:21:40 -07:00
|
|
|
this.line.substring(this.cursor, oldcursor)
|
|
|
|
);
|
2013-03-15 16:18:30 -10:00
|
|
|
}
|
2017-04-29 13:33:35 -07:00
|
|
|
moveCursor(this.output, diffWidth, 0);
|
2012-03-20 15:14:40 -07:00
|
|
|
this.prevRows = newPos.rows;
|
|
|
|
} else {
|
|
|
|
this._refreshLine();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-02-22 16:07:43 +04:00
|
|
|
function _ttyWriteDumb(s, key) {
|
|
|
|
key = key || {};
|
|
|
|
|
|
|
|
if (key.name === 'escape') return;
|
|
|
|
|
|
|
|
if (this._sawReturnAt && key.name !== 'enter')
|
|
|
|
this._sawReturnAt = 0;
|
|
|
|
|
2019-08-15 12:37:29 -04:00
|
|
|
if (key.ctrl) {
|
|
|
|
if (key.name === 'c') {
|
|
|
|
if (this.listenerCount('SIGINT') > 0) {
|
|
|
|
this.emit('SIGINT');
|
|
|
|
} else {
|
|
|
|
// This readline instance is finished
|
|
|
|
this.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
} else if (key.name === 'd') {
|
2019-02-22 16:07:43 +04:00
|
|
|
this.close();
|
2019-08-15 12:37:29 -04:00
|
|
|
return;
|
2019-02-22 16:07:43 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (key.name) {
|
2019-03-22 03:44:26 +01:00
|
|
|
case 'return': // Carriage return, i.e. \r
|
2019-11-27 21:38:49 +01:00
|
|
|
this._sawReturnAt = DateNow();
|
2019-02-22 16:07:43 +04:00
|
|
|
this._line();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'enter':
|
|
|
|
// When key interval > crlfDelay
|
|
|
|
if (this._sawReturnAt === 0 ||
|
2019-11-27 21:38:49 +01:00
|
|
|
DateNow() - this._sawReturnAt > this.crlfDelay) {
|
2019-02-22 16:07:43 +04:00
|
|
|
this._line();
|
|
|
|
}
|
|
|
|
this._sawReturnAt = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
if (typeof s === 'string' && s) {
|
|
|
|
this.line += s;
|
|
|
|
this.cursor += s.length;
|
|
|
|
this._writeToOutput(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-03-20 15:14:40 -07:00
|
|
|
|
2019-03-22 03:44:26 +01:00
|
|
|
// Handle a write from the tty
|
2011-01-14 03:44:05 +01:00
|
|
|
Interface.prototype._ttyWrite = function(s, key) {
|
2016-07-15 23:33:16 +02:00
|
|
|
const previousKey = this._previousKey;
|
2011-01-14 03:44:05 +01:00
|
|
|
key = key || {};
|
2016-07-15 23:33:16 +02:00
|
|
|
this._previousKey = key;
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2019-12-27 15:32:39 +01:00
|
|
|
// Activate or deactivate substring search.
|
|
|
|
if ((key.name === 'up' || key.name === 'down') &&
|
|
|
|
!key.ctrl && !key.meta && !key.shift) {
|
|
|
|
if (this[kSubstringSearch] === null) {
|
|
|
|
this[kSubstringSearch] = this.line.slice(0, this.cursor);
|
|
|
|
}
|
|
|
|
} else if (this[kSubstringSearch] !== null) {
|
|
|
|
this[kSubstringSearch] = null;
|
|
|
|
// Reset the index in case there's no match.
|
|
|
|
if (this.history.length === this.historyIndex) {
|
|
|
|
this.historyIndex = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-31 18:06:40 +01:00
|
|
|
// Ignore escape key, fixes
|
|
|
|
// https://github.com/nodejs/node-v0.x-archive/issues/2876.
|
2017-01-31 00:40:19 -05:00
|
|
|
if (key.name === 'escape') return;
|
2012-03-06 01:22:51 -05:00
|
|
|
|
2011-01-25 03:54:26 +01:00
|
|
|
if (key.ctrl && key.shift) {
|
|
|
|
/* Control and shift pressed */
|
|
|
|
switch (key.name) {
|
2011-07-29 10:03:05 -07:00
|
|
|
case 'backspace':
|
2011-01-25 03:54:26 +01:00
|
|
|
this._deleteLineLeft();
|
|
|
|
break;
|
|
|
|
|
2011-07-29 10:03:05 -07:00
|
|
|
case 'delete':
|
2011-01-25 03:54:26 +01:00
|
|
|
this._deleteLineRight();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (key.ctrl) {
|
2011-01-14 03:44:05 +01:00
|
|
|
/* Control key pressed */
|
2010-12-01 18:07:20 -08:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
switch (key.name) {
|
|
|
|
case 'c':
|
2015-08-12 00:01:50 +05:30
|
|
|
if (this.listenerCount('SIGINT') > 0) {
|
2011-01-14 03:44:05 +01:00
|
|
|
this.emit('SIGINT');
|
|
|
|
} else {
|
2012-04-17 11:31:07 -07:00
|
|
|
// This readline instance is finished
|
|
|
|
this.close();
|
2011-01-14 03:44:05 +01:00
|
|
|
}
|
|
|
|
break;
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'h': // delete left
|
|
|
|
this._deleteLeft();
|
|
|
|
break;
|
2010-05-31 11:50:35 -07:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'd': // delete right or EOF
|
|
|
|
if (this.cursor === 0 && this.line.length === 0) {
|
2012-04-17 11:31:07 -07:00
|
|
|
// This readline instance is finished
|
|
|
|
this.close();
|
2011-01-14 03:44:05 +01:00
|
|
|
} else if (this.cursor < this.line.length) {
|
|
|
|
this._deleteRight();
|
|
|
|
}
|
|
|
|
break;
|
2010-12-01 18:07:20 -08:00
|
|
|
|
2019-03-07 01:03:53 +01:00
|
|
|
case 'u': // Delete from current to start of line
|
2018-05-12 12:46:33 +05:30
|
|
|
this._deleteLineLeft();
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-06-07 16:43:50 -07:00
|
|
|
|
2019-03-07 01:03:53 +01:00
|
|
|
case 'k': // Delete from current to end of line
|
2011-01-25 03:54:26 +01:00
|
|
|
this._deleteLineRight();
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-09-03 21:53:53 -07:00
|
|
|
|
2019-03-22 03:44:26 +01:00
|
|
|
case 'a': // Go to the start of the line
|
2012-03-20 15:14:40 -07:00
|
|
|
this._moveCursor(-Infinity);
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-09-12 21:47:56 -07:00
|
|
|
|
2019-03-22 03:44:26 +01:00
|
|
|
case 'e': // Go to the end of the line
|
2012-03-20 15:14:40 -07:00
|
|
|
this._moveCursor(+Infinity);
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-09-12 21:47:56 -07:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'b': // back one character
|
2019-01-25 21:15:06 -05:00
|
|
|
this._moveCursor(-charLengthLeft(this.line, this.cursor));
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
|
|
|
|
2019-03-22 03:44:26 +01:00
|
|
|
case 'f': // Forward one character
|
2019-01-25 21:15:06 -05:00
|
|
|
this._moveCursor(+charLengthAt(this.line, this.cursor));
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
|
|
|
|
2019-03-22 03:44:26 +01:00
|
|
|
case 'l': // Clear the whole screen
|
2017-04-29 13:33:35 -07:00
|
|
|
cursorTo(this.output, 0, 0);
|
|
|
|
clearScreenDown(this.output);
|
2013-06-15 15:48:36 +08:00
|
|
|
this._refreshLine();
|
|
|
|
break;
|
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'n': // next history item
|
|
|
|
this._historyNext();
|
|
|
|
break;
|
|
|
|
|
2019-03-22 03:44:26 +01:00
|
|
|
case 'p': // Previous history item
|
2011-01-14 03:44:05 +01:00
|
|
|
this._historyPrev();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'z':
|
2017-01-31 00:40:19 -05:00
|
|
|
if (process.platform === 'win32') break;
|
2015-08-12 00:01:50 +05:30
|
|
|
if (this.listenerCount('SIGTSTP') > 0) {
|
2012-02-15 09:08:26 -05:00
|
|
|
this.emit('SIGTSTP');
|
|
|
|
} else {
|
2019-07-11 09:52:25 -04:00
|
|
|
process.once('SIGCONT', () => {
|
|
|
|
// Don't raise events if stream has already been abandoned.
|
|
|
|
if (!this.paused) {
|
|
|
|
// Stream must be paused and resumed after SIGCONT to catch
|
|
|
|
// SIGINT, SIGTSTP, and EOF.
|
|
|
|
this.pause();
|
|
|
|
this.emit('SIGCONT');
|
|
|
|
}
|
|
|
|
// Explicitly re-enable "raw mode" and move the cursor to
|
|
|
|
// the correct position.
|
|
|
|
// See https://github.com/joyent/node/issues/3295.
|
|
|
|
this._setRawMode(true);
|
|
|
|
this._refreshLine();
|
|
|
|
});
|
2012-05-21 19:43:26 -03:00
|
|
|
this._setRawMode(false);
|
2012-02-15 09:08:26 -05:00
|
|
|
process.kill(process.pid, 'SIGTSTP');
|
|
|
|
}
|
2012-02-17 08:53:24 -05:00
|
|
|
break;
|
2011-01-25 03:54:26 +01:00
|
|
|
|
2019-12-11 19:33:53 +01:00
|
|
|
// TODO(BridgeAR): This seems broken?
|
2019-03-07 01:03:53 +01:00
|
|
|
case 'w': // Delete backwards to a word boundary
|
2011-01-25 03:54:26 +01:00
|
|
|
case 'backspace':
|
|
|
|
this._deleteWordLeft();
|
|
|
|
break;
|
|
|
|
|
2019-03-07 01:03:53 +01:00
|
|
|
case 'delete': // Delete forward to a word boundary
|
2011-01-25 03:54:26 +01:00
|
|
|
this._deleteWordRight();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'left':
|
|
|
|
this._wordLeft();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'right':
|
|
|
|
this._wordRight();
|
2012-02-17 08:53:24 -05:00
|
|
|
break;
|
2011-01-14 03:44:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} else if (key.meta) {
|
|
|
|
/* Meta key pressed */
|
|
|
|
|
|
|
|
switch (key.name) {
|
|
|
|
case 'b': // backward word
|
2011-01-25 03:54:26 +01:00
|
|
|
this._wordLeft();
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'f': // forward word
|
2011-01-25 03:54:26 +01:00
|
|
|
this._wordRight();
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'd': // delete forward word
|
2011-01-24 16:09:41 -08:00
|
|
|
case 'delete':
|
2011-01-25 03:54:26 +01:00
|
|
|
this._deleteWordRight();
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2011-01-24 16:09:41 -08:00
|
|
|
|
2019-03-07 01:03:53 +01:00
|
|
|
case 'backspace': // Delete backwards to a word boundary
|
2011-01-25 03:54:26 +01:00
|
|
|
this._deleteWordLeft();
|
2011-01-24 16:09:41 -08:00
|
|
|
break;
|
2011-01-14 03:44:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* No modifier keys used */
|
|
|
|
|
2013-01-29 17:50:44 -08:00
|
|
|
// \r bookkeeping is only relevant if a \n comes right after.
|
2016-08-15 17:37:01 +05:30
|
|
|
if (this._sawReturnAt && key.name !== 'enter')
|
|
|
|
this._sawReturnAt = 0;
|
2013-01-29 17:50:44 -08:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
switch (key.name) {
|
2019-03-22 03:44:26 +01:00
|
|
|
case 'return': // Carriage return, i.e. \r
|
2019-11-27 21:38:49 +01:00
|
|
|
this._sawReturnAt = DateNow();
|
2011-01-14 03:44:05 +01:00
|
|
|
this._line();
|
|
|
|
break;
|
|
|
|
|
2013-01-29 17:50:44 -08:00
|
|
|
case 'enter':
|
2016-08-15 17:37:01 +05:30
|
|
|
// When key interval > crlfDelay
|
|
|
|
if (this._sawReturnAt === 0 ||
|
2019-11-27 21:38:49 +01:00
|
|
|
DateNow() - this._sawReturnAt > this.crlfDelay) {
|
2013-01-29 17:50:44 -08:00
|
|
|
this._line();
|
2016-08-15 17:37:01 +05:30
|
|
|
}
|
|
|
|
this._sawReturnAt = 0;
|
2013-01-29 17:50:44 -08:00
|
|
|
break;
|
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'backspace':
|
|
|
|
this._deleteLeft();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'delete':
|
|
|
|
this._deleteRight();
|
|
|
|
break;
|
2010-09-12 21:47:56 -07:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'left':
|
2019-03-07 01:03:53 +01:00
|
|
|
// Obtain the code point to the left
|
2019-01-25 21:15:06 -05:00
|
|
|
this._moveCursor(-charLengthLeft(this.line, this.cursor));
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-09-12 21:47:56 -07:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'right':
|
2019-01-25 21:15:06 -05:00
|
|
|
this._moveCursor(+charLengthAt(this.line, this.cursor));
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-09-12 21:47:56 -07:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'home':
|
2012-03-20 15:14:40 -07:00
|
|
|
this._moveCursor(-Infinity);
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'end':
|
2012-03-20 15:14:40 -07:00
|
|
|
this._moveCursor(+Infinity);
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-09-12 21:47:56 -07:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'up':
|
2010-06-07 16:43:50 -07:00
|
|
|
this._historyPrev();
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-09-12 21:47:56 -07:00
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
case 'down':
|
2010-06-07 16:43:50 -07:00
|
|
|
this._historyNext();
|
2011-01-14 03:44:05 +01:00
|
|
|
break;
|
2010-09-12 21:47:56 -07:00
|
|
|
|
2015-05-20 21:17:10 -07:00
|
|
|
case 'tab':
|
|
|
|
// If tab completion enabled, do that...
|
2016-05-13 10:54:31 +03:00
|
|
|
if (typeof this.completer === 'function' && this.isCompletionEnabled) {
|
2016-07-15 23:33:16 +02:00
|
|
|
const lastKeypressWasTab = previousKey && previousKey.name === 'tab';
|
|
|
|
this._tabComplete(lastKeypressWasTab);
|
2015-05-20 21:17:10 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
// falls through
|
|
|
|
|
2011-01-14 03:44:05 +01:00
|
|
|
default:
|
2019-02-22 16:07:43 +04:00
|
|
|
if (typeof s === 'string' && s) {
|
2019-11-12 15:56:22 +01:00
|
|
|
const lines = s.split(/\r\n|\n|\r/);
|
|
|
|
for (let i = 0, len = lines.length; i < len; i++) {
|
2011-01-14 03:44:05 +01:00
|
|
|
if (i > 0) {
|
|
|
|
this._line();
|
|
|
|
}
|
|
|
|
this._insertString(lines[i]);
|
|
|
|
}
|
2010-08-09 02:18:32 -07:00
|
|
|
}
|
2011-01-14 03:44:05 +01:00
|
|
|
}
|
2010-05-31 11:50:35 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-12-13 22:01:47 +01:00
|
|
|
Interface.prototype[SymbolAsyncIterator] = function() {
|
2018-10-26 17:53:04 -07:00
|
|
|
if (this[kLineObjectStream] === undefined) {
|
|
|
|
if (Readable === undefined) {
|
|
|
|
Readable = require('stream').Readable;
|
|
|
|
}
|
|
|
|
const readable = new Readable({
|
|
|
|
objectMode: true,
|
|
|
|
read: () => {
|
|
|
|
this.resume();
|
|
|
|
},
|
|
|
|
destroy: (err, cb) => {
|
|
|
|
this.off('line', lineListener);
|
|
|
|
this.off('close', closeListener);
|
|
|
|
this.close();
|
|
|
|
cb(err);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const lineListener = (input) => {
|
|
|
|
if (!readable.push(input)) {
|
|
|
|
this.pause();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const closeListener = () => {
|
|
|
|
readable.push(null);
|
|
|
|
};
|
|
|
|
this.on('line', lineListener);
|
|
|
|
this.on('close', closeListener);
|
|
|
|
this[kLineObjectStream] = readable;
|
|
|
|
}
|
|
|
|
|
2019-12-13 22:01:47 +01:00
|
|
|
return this[kLineObjectStream][SymbolAsyncIterator]();
|
2018-10-26 17:53:04 -07:00
|
|
|
};
|
|
|
|
|
2012-03-26 15:21:25 -07:00
|
|
|
/**
|
|
|
|
* accepts a readable Stream instance and makes it emit "keypress" events
|
|
|
|
*/
|
|
|
|
|
2016-05-13 10:54:31 +03:00
|
|
|
function emitKeypressEvents(stream, iface) {
|
2015-05-03 18:11:33 +00:00
|
|
|
if (stream[KEYPRESS_DECODER]) return;
|
2018-05-06 23:08:00 +02:00
|
|
|
|
2015-05-03 18:11:33 +00:00
|
|
|
stream[KEYPRESS_DECODER] = new StringDecoder('utf8');
|
|
|
|
|
|
|
|
stream[ESCAPE_DECODER] = emitKeys(stream);
|
|
|
|
stream[ESCAPE_DECODER].next();
|
2012-03-26 15:21:25 -07:00
|
|
|
|
2016-06-23 08:42:57 +05:30
|
|
|
const escapeCodeTimeout = () => stream[ESCAPE_DECODER].next('');
|
|
|
|
let timeoutId;
|
|
|
|
|
2012-03-26 15:21:25 -07:00
|
|
|
function onData(b) {
|
2015-08-12 00:01:50 +05:30
|
|
|
if (stream.listenerCount('keypress') > 0) {
|
2019-11-12 15:56:22 +01:00
|
|
|
const r = stream[KEYPRESS_DECODER].write(b);
|
2015-05-03 18:11:33 +00:00
|
|
|
if (r) {
|
2016-06-23 08:42:57 +05:30
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
2019-12-05 07:51:47 +01:00
|
|
|
let escapeTimeout = ESCAPE_CODE_TIMEOUT;
|
|
|
|
|
2016-08-24 00:05:59 +05:30
|
|
|
if (iface) {
|
|
|
|
iface._sawKeyPress = r.length === 1;
|
2019-12-05 07:51:47 +01:00
|
|
|
escapeTimeout = iface.escapeCodeTimeout;
|
2016-08-24 00:05:59 +05:30
|
|
|
}
|
|
|
|
|
2019-11-12 15:56:22 +01:00
|
|
|
for (let i = 0; i < r.length; i++) {
|
2016-05-13 10:54:31 +03:00
|
|
|
if (r[i] === '\t' && typeof r[i + 1] === 'string' && iface) {
|
|
|
|
iface.isCompletionEnabled = false;
|
|
|
|
}
|
|
|
|
|
2015-07-05 19:16:47 +03:00
|
|
|
try {
|
|
|
|
stream[ESCAPE_DECODER].next(r[i]);
|
2016-06-23 08:42:57 +05:30
|
|
|
// Escape letter at the tail position
|
2017-04-29 17:05:04 -07:00
|
|
|
if (r[i] === kEscape && i + 1 === r.length) {
|
2019-12-05 07:51:47 +01:00
|
|
|
timeoutId = setTimeout(escapeCodeTimeout, escapeTimeout);
|
2016-06-23 08:42:57 +05:30
|
|
|
}
|
2015-07-05 19:16:47 +03:00
|
|
|
} catch (err) {
|
2018-12-10 13:27:32 +01:00
|
|
|
// If the generator throws (it could happen in the `keypress`
|
2015-07-05 19:16:47 +03:00
|
|
|
// event), we need to restart it.
|
|
|
|
stream[ESCAPE_DECODER] = emitKeys(stream);
|
|
|
|
stream[ESCAPE_DECODER].next();
|
|
|
|
throw err;
|
2016-05-13 10:54:31 +03:00
|
|
|
} finally {
|
|
|
|
if (iface) {
|
|
|
|
iface.isCompletionEnabled = true;
|
|
|
|
}
|
2015-07-05 19:16:47 +03:00
|
|
|
}
|
2015-05-03 18:11:33 +00:00
|
|
|
}
|
|
|
|
}
|
2012-03-26 15:21:25 -07:00
|
|
|
} else {
|
|
|
|
// Nobody's watching anyway
|
|
|
|
stream.removeListener('data', onData);
|
|
|
|
stream.on('newListener', onNewListener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onNewListener(event) {
|
2017-01-31 00:40:19 -05:00
|
|
|
if (event === 'keypress') {
|
2012-03-26 15:21:25 -07:00
|
|
|
stream.on('data', onData);
|
|
|
|
stream.removeListener('newListener', onNewListener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-12 00:01:50 +05:30
|
|
|
if (stream.listenerCount('keypress') > 0) {
|
2012-03-26 15:21:25 -07:00
|
|
|
stream.on('data', onData);
|
|
|
|
} else {
|
|
|
|
stream.on('newListener', onNewListener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* moves the cursor to the x and y coordinate on the given stream
|
|
|
|
*/
|
|
|
|
|
2019-07-13 20:10:17 -04:00
|
|
|
function cursorTo(stream, x, y, callback) {
|
|
|
|
if (callback !== undefined && typeof callback !== 'function')
|
|
|
|
throw new ERR_INVALID_CALLBACK(callback);
|
2014-09-22 13:21:11 -07:00
|
|
|
|
2019-08-14 22:35:41 +02:00
|
|
|
if (typeof y === 'function') {
|
|
|
|
callback = y;
|
|
|
|
y = undefined;
|
|
|
|
}
|
|
|
|
|
2019-07-13 20:10:17 -04:00
|
|
|
if (stream == null || (typeof x !== 'number' && typeof y !== 'number')) {
|
|
|
|
if (typeof callback === 'function')
|
2019-12-18 19:58:49 +01:00
|
|
|
process.nextTick(callback, null);
|
2019-07-13 20:10:17 -04:00
|
|
|
return true;
|
|
|
|
}
|
2012-03-26 15:21:25 -07:00
|
|
|
|
2015-01-28 20:05:53 -05:00
|
|
|
if (typeof x !== 'number')
|
2018-02-27 14:55:32 +01:00
|
|
|
throw new ERR_INVALID_CURSOR_POS();
|
2012-03-26 15:21:25 -07:00
|
|
|
|
2019-07-13 20:10:17 -04:00
|
|
|
const data = typeof y !== 'number' ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`;
|
|
|
|
return stream.write(data, callback);
|
2012-03-26 15:21:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* moves the cursor relative to its current location
|
|
|
|
*/
|
|
|
|
|
2019-07-13 19:22:46 -04:00
|
|
|
function moveCursor(stream, dx, dy, callback) {
|
|
|
|
if (callback !== undefined && typeof callback !== 'function')
|
|
|
|
throw new ERR_INVALID_CALLBACK(callback);
|
|
|
|
|
|
|
|
if (stream == null || !(dx || dy)) {
|
|
|
|
if (typeof callback === 'function')
|
2019-12-18 19:58:49 +01:00
|
|
|
process.nextTick(callback, null);
|
2019-07-13 19:22:46 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let data = '';
|
2014-09-22 13:21:11 -07:00
|
|
|
|
2012-03-26 15:21:25 -07:00
|
|
|
if (dx < 0) {
|
2019-07-13 19:22:46 -04:00
|
|
|
data += CSI`${-dx}D`;
|
2012-03-26 15:21:25 -07:00
|
|
|
} else if (dx > 0) {
|
2019-07-13 19:22:46 -04:00
|
|
|
data += CSI`${dx}C`;
|
2012-03-26 15:21:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dy < 0) {
|
2019-07-13 19:22:46 -04:00
|
|
|
data += CSI`${-dy}A`;
|
2012-03-26 15:21:25 -07:00
|
|
|
} else if (dy > 0) {
|
2019-07-13 19:22:46 -04:00
|
|
|
data += CSI`${dy}B`;
|
2012-03-26 15:21:25 -07:00
|
|
|
}
|
2019-07-13 19:22:46 -04:00
|
|
|
|
|
|
|
return stream.write(data, callback);
|
2012-03-26 15:21:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* clears the current line the cursor is on:
|
|
|
|
* -1 for left of the cursor
|
|
|
|
* +1 for right of the cursor
|
|
|
|
* 0 for the entire line
|
|
|
|
*/
|
|
|
|
|
2019-07-13 18:08:19 -04:00
|
|
|
function clearLine(stream, dir, callback) {
|
|
|
|
if (callback !== undefined && typeof callback !== 'function')
|
|
|
|
throw new ERR_INVALID_CALLBACK(callback);
|
2014-09-22 13:21:11 -07:00
|
|
|
|
2019-07-13 18:08:19 -04:00
|
|
|
if (stream === null || stream === undefined) {
|
|
|
|
if (typeof callback === 'function')
|
2019-12-18 19:58:49 +01:00
|
|
|
process.nextTick(callback, null);
|
2019-07-13 18:08:19 -04:00
|
|
|
return true;
|
2012-03-26 15:21:25 -07:00
|
|
|
}
|
2019-07-13 18:08:19 -04:00
|
|
|
|
2019-12-11 19:21:40 +01:00
|
|
|
const type = dir < 0 ?
|
|
|
|
kClearToLineBeginning :
|
|
|
|
dir > 0 ?
|
|
|
|
kClearToLineEnd :
|
|
|
|
kClearLine;
|
2019-07-13 18:08:19 -04:00
|
|
|
return stream.write(type, callback);
|
2012-03-26 15:21:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* clears the screen from the current position of the cursor down
|
|
|
|
*/
|
|
|
|
|
2019-07-11 12:35:34 -04:00
|
|
|
function clearScreenDown(stream, callback) {
|
|
|
|
if (callback !== undefined && typeof callback !== 'function')
|
|
|
|
throw new ERR_INVALID_CALLBACK(callback);
|
|
|
|
|
|
|
|
if (stream === null || stream === undefined) {
|
|
|
|
if (typeof callback === 'function')
|
2019-12-18 19:58:49 +01:00
|
|
|
process.nextTick(callback, null);
|
2019-07-11 12:35:34 -04:00
|
|
|
return true;
|
|
|
|
}
|
2014-09-22 13:21:11 -07:00
|
|
|
|
2019-07-11 12:35:34 -04:00
|
|
|
return stream.write(kClearScreenDown, callback);
|
2012-03-26 15:21:25 -07:00
|
|
|
}
|
2017-04-29 13:33:35 -07:00
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
Interface,
|
|
|
|
clearLine,
|
|
|
|
clearScreenDown,
|
|
|
|
createInterface,
|
|
|
|
cursorTo,
|
|
|
|
emitKeypressEvents,
|
|
|
|
moveCursor
|
|
|
|
};
|