1 // Inspiration for this code comes from Salvatore Sanfilippo's linenoise.
2 // https://github.com/antirez/linenoise
4 // * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
5 // * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
9 const kHistorySize = 30;
11 const util = require('util');
12 const internalUtil = require('internal/util');
13 const inherits = util.inherits;
14 const Buffer = require('buffer').Buffer;
15 const EventEmitter = require('events');
18 exports.createInterface = function(input, output, completer, terminal) {
20 if (arguments.length === 1) {
21 rl = new Interface(input);
23 rl = new Interface(input, output, completer, terminal);
29 function Interface(input, output, completer, terminal) {
30 if (!(this instanceof Interface)) {
31 // call the constructor preserving original number of arguments
32 const self = Object.create(Interface.prototype);
33 Interface.apply(self, arguments);
37 this._sawReturn = false;
39 EventEmitter.call(this);
42 if (arguments.length === 1) {
43 // an options object was given
44 output = input.output;
45 completer = input.completer;
46 terminal = input.terminal;
47 historySize = input.historySize;
50 historySize = historySize || kHistorySize;
52 if (completer && typeof completer !== 'function') {
53 throw new TypeError('Argument \'completer\' must be a function');
56 if (typeof historySize !== 'number' ||
59 throw new TypeError('Argument \'historySize\' must be a positive number');
62 // backwards compat; check the isTTY prop of the output stream
63 // when `terminal` was not specified
64 if (terminal === undefined && !(output === null || output === undefined)) {
65 terminal = !!output.isTTY;
72 this.historySize = historySize;
74 // Check arity, 2 - for async, 1 for sync
75 if (typeof completer === 'function') {
76 this.completer = completer.length === 2 ? completer : function(v, cb) {
77 cb(null, completer(v));
83 this.terminal = !!terminal;
85 function ondata(data) {
86 self._normalWrite(data);
90 if (typeof self._line_buffer === 'string' &&
91 self._line_buffer.length > 0) {
92 self.emit('line', self._line_buffer);
97 function ontermend() {
98 if (typeof self.line === 'string' && self.line.length > 0) {
99 self.emit('line', self.line);
104 function onkeypress(s, key) {
105 self._ttyWrite(s, key);
108 function onresize() {
112 if (!this.terminal) {
113 input.on('data', ondata);
114 input.on('end', onend);
115 self.once('close', function() {
116 input.removeListener('data', ondata);
117 input.removeListener('end', onend);
119 var StringDecoder = require('string_decoder').StringDecoder; // lazy load
120 this._decoder = new StringDecoder('utf8');
124 exports.emitKeypressEvents(input);
126 // input usually refers to stdin
127 input.on('keypress', onkeypress);
128 input.on('end', ontermend);
133 this._setRawMode(true);
134 this.terminal = true;
136 // Cursor position on the line.
140 this.historyIndex = -1;
142 if (output !== null && output !== undefined)
143 output.on('resize', onresize);
145 self.once('close', function() {
146 input.removeListener('keypress', onkeypress);
147 input.removeListener('end', ontermend);
148 if (output !== null && output !== undefined) {
149 output.removeListener('resize', onresize);
157 inherits(Interface, EventEmitter);
159 Interface.prototype.__defineGetter__('columns', function() {
160 var columns = Infinity;
161 if (this.output && this.output.columns)
162 columns = this.output.columns;
166 Interface.prototype.setPrompt = function(prompt) {
167 this._prompt = prompt;
171 Interface.prototype._setRawMode = function(mode) {
172 if (typeof this.input.setRawMode === 'function') {
173 return this.input.setRawMode(mode);
178 Interface.prototype.prompt = function(preserveCursor) {
179 if (this.paused) this.resume();
181 if (!preserveCursor) this.cursor = 0;
184 this._writeToOutput(this._prompt);
189 Interface.prototype.question = function(query, cb) {
190 if (typeof cb === 'function') {
191 if (this._questionCallback) {
194 this._oldPrompt = this._prompt;
195 this.setPrompt(query);
196 this._questionCallback = cb;
203 Interface.prototype._onLine = function(line) {
204 if (this._questionCallback) {
205 var cb = this._questionCallback;
206 this._questionCallback = null;
207 this.setPrompt(this._oldPrompt);
210 this.emit('line', line);
214 Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) {
215 if (typeof stringToWrite !== 'string')
216 throw new TypeError('stringToWrite must be a string');
218 if (this.output !== null && this.output !== undefined)
219 this.output.write(stringToWrite);
222 Interface.prototype._addHistory = function() {
223 if (this.line.length === 0) return '';
225 if (this.history.length === 0 || this.history[0] !== this.line) {
226 this.history.unshift(this.line);
228 // Only store so many
229 if (this.history.length > this.historySize) this.history.pop();
232 this.historyIndex = -1;
233 return this.history[0];
237 Interface.prototype._refreshLine = function() {
239 var line = this._prompt + this.line;
240 var dispPos = this._getDisplayPos(line);
241 var lineCols = dispPos.cols;
242 var lineRows = dispPos.rows;
245 var cursorPos = this._getCursorPos();
247 // first move to the bottom of the current line, based on cursor pos
248 var prevRows = this.prevRows || 0;
250 exports.moveCursor(this.output, 0, -prevRows);
253 // Cursor to left edge.
254 exports.cursorTo(this.output, 0);
256 exports.clearScreenDown(this.output);
258 // Write the prompt and the current buffer content.
259 this._writeToOutput(line);
261 // Force terminal to allocate a new line
262 if (lineCols === 0) {
263 this._writeToOutput(' ');
266 // Move cursor to original position.
267 exports.cursorTo(this.output, cursorPos.cols);
269 var diff = lineRows - cursorPos.rows;
271 exports.moveCursor(this.output, 0, -diff);
274 this.prevRows = cursorPos.rows;
278 Interface.prototype.close = function() {
279 if (this.closed) return;
282 this._setRawMode(false);
289 Interface.prototype.pause = function() {
290 if (this.paused) return;
298 Interface.prototype.resume = function() {
299 if (!this.paused) return;
307 Interface.prototype.write = function(d, key) {
308 if (this.paused) this.resume();
309 this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d);
312 // \r\n, \n, or \r followed by something other than \n
313 const lineEnding = /\r?\n|\r(?!\n)/;
314 Interface.prototype._normalWrite = function(b) {
315 if (b === undefined) {
318 var string = this._decoder.write(b);
319 if (this._sawReturn) {
320 string = string.replace(/^\n/, '');
321 this._sawReturn = false;
324 // Run test() on the new string chunk, not on the entire line buffer.
325 var newPartContainsEnding = lineEnding.test(string);
327 if (this._line_buffer) {
328 string = this._line_buffer + string;
329 this._line_buffer = null;
331 if (newPartContainsEnding) {
332 this._sawReturn = /\r$/.test(string);
334 // got one or more newlines; process into "line" events
335 var lines = string.split(lineEnding);
336 // either '' or (concievably) the unfinished portion of the next line
337 string = lines.pop();
338 this._line_buffer = string;
339 lines.forEach(function(line) {
343 // no newlines this time, save what we have for next time
344 this._line_buffer = string;
348 Interface.prototype._insertString = function(c) {
349 if (this.cursor < this.line.length) {
350 var beg = this.line.slice(0, this.cursor);
351 var end = this.line.slice(this.cursor, this.line.length);
352 this.line = beg + c + end;
353 this.cursor += c.length;
357 this.cursor += c.length;
359 if (this._getCursorPos().cols === 0) {
362 this._writeToOutput(c);
365 // a hack to get the line refreshed if it's needed
370 Interface.prototype._tabComplete = function() {
374 self.completer(self.line.slice(0, self.cursor), function(err, rv) {
378 // XXX Log it somewhere?
382 var completions = rv[0],
383 completeOn = rv[1]; // the text that was completed
384 if (completions && completions.length) {
385 // Apply/show completions.
386 if (completions.length === 1) {
387 self._insertString(completions[0].slice(completeOn.length));
389 self._writeToOutput('\r\n');
390 var width = completions.reduce(function(a, b) {
391 return a.length > b.length ? a : b;
392 }).length + 2; // 2 space padding
393 var maxColumns = Math.floor(self.columns / width);
394 if (!maxColumns || maxColumns === Infinity) {
398 for (var i = 0, compLen = completions.length; i < compLen; i++) {
401 handleGroup(self, group, width, maxColumns);
407 handleGroup(self, group, width, maxColumns);
409 // If there is a common prefix to all matches, then apply that
411 var f = completions.filter(function(e) { if (e) return e; });
412 var prefix = commonPrefix(f);
413 if (prefix.length > completeOn.length) {
414 self._insertString(prefix.slice(completeOn.length));
423 // this = Interface instance
424 function handleGroup(self, group, width, maxColumns) {
425 if (group.length == 0) {
428 var minRows = Math.ceil(group.length / maxColumns);
429 for (var row = 0; row < minRows; row++) {
430 for (var col = 0; col < maxColumns; col++) {
431 var idx = row * maxColumns + col;
432 if (idx >= group.length) {
435 var item = group[idx];
436 self._writeToOutput(item);
437 if (col < maxColumns - 1) {
438 for (var s = 0, itemLen = item.length; s < width - itemLen;
440 self._writeToOutput(' ');
444 self._writeToOutput('\r\n');
446 self._writeToOutput('\r\n');
449 function commonPrefix(strings) {
450 if (!strings || strings.length == 0) {
453 var sorted = strings.slice().sort();
455 var max = sorted[sorted.length - 1];
456 for (var i = 0, len = min.length; i < len; i++) {
457 if (min[i] != max[i]) {
458 return min.slice(0, i);
465 Interface.prototype._wordLeft = function() {
466 if (this.cursor > 0) {
467 var leading = this.line.slice(0, this.cursor);
468 var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
469 this._moveCursor(-match[0].length);
474 Interface.prototype._wordRight = function() {
475 if (this.cursor < this.line.length) {
476 var trailing = this.line.slice(this.cursor);
477 var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
478 this._moveCursor(match[0].length);
483 Interface.prototype._deleteLeft = function() {
484 if (this.cursor > 0 && this.line.length > 0) {
485 this.line = this.line.slice(0, this.cursor - 1) +
486 this.line.slice(this.cursor, this.line.length);
494 Interface.prototype._deleteRight = function() {
495 this.line = this.line.slice(0, this.cursor) +
496 this.line.slice(this.cursor + 1, this.line.length);
501 Interface.prototype._deleteWordLeft = function() {
502 if (this.cursor > 0) {
503 var leading = this.line.slice(0, this.cursor);
504 var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
505 leading = leading.slice(0, leading.length - match[0].length);
506 this.line = leading + this.line.slice(this.cursor, this.line.length);
507 this.cursor = leading.length;
513 Interface.prototype._deleteWordRight = function() {
514 if (this.cursor < this.line.length) {
515 var trailing = this.line.slice(this.cursor);
516 var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
517 this.line = this.line.slice(0, this.cursor) +
518 trailing.slice(match[0].length);
524 Interface.prototype._deleteLineLeft = function() {
525 this.line = this.line.slice(this.cursor);
531 Interface.prototype._deleteLineRight = function() {
532 this.line = this.line.slice(0, this.cursor);
537 Interface.prototype.clearLine = function() {
538 this._moveCursor(+Infinity);
539 this._writeToOutput('\r\n');
546 Interface.prototype._line = function() {
547 var line = this._addHistory();
553 Interface.prototype._historyNext = function() {
554 if (this.historyIndex > 0) {
556 this.line = this.history[this.historyIndex];
557 this.cursor = this.line.length; // set cursor to end of line.
560 } else if (this.historyIndex === 0) {
561 this.historyIndex = -1;
569 Interface.prototype._historyPrev = function() {
570 if (this.historyIndex + 1 < this.history.length) {
572 this.line = this.history[this.historyIndex];
573 this.cursor = this.line.length; // set cursor to end of line.
580 // Returns the last character's display position of the given string
581 Interface.prototype._getDisplayPos = function(str) {
583 var col = this.columns;
586 str = stripVTControlCharacters(str);
587 for (var i = 0, len = str.length; i < len; i++) {
588 code = str.codePointAt(i);
589 if (code >= 0x10000) { // surrogates
592 if (code === 0x0a) { // new line \n
597 if (isFullWidthCodePoint(code)) {
598 if ((offset + 1) % col === 0) {
606 var cols = offset % col;
607 var rows = row + (offset - cols) / col;
608 return {cols: cols, rows: rows};
612 // Returns current cursor's position and line
613 Interface.prototype._getCursorPos = function() {
614 var columns = this.columns;
615 var strBeforeCursor = this._prompt + this.line.substring(0, this.cursor);
616 var dispPos = this._getDisplayPos(stripVTControlCharacters(strBeforeCursor));
617 var cols = dispPos.cols;
618 var rows = dispPos.rows;
619 // If the cursor is on a full-width character which steps over the line,
620 // move the cursor to the beginning of the next line.
621 if (cols + 1 === columns &&
622 this.cursor < this.line.length &&
623 isFullWidthCodePoint(this.line.codePointAt(this.cursor))) {
627 return {cols: cols, rows: rows};
631 // This function moves cursor dx places to the right
632 // (-dx for left) and refreshes the line if it is needed
633 Interface.prototype._moveCursor = function(dx) {
634 var oldcursor = this.cursor;
635 var oldPos = this._getCursorPos();
639 if (this.cursor < 0) this.cursor = 0;
640 else if (this.cursor > this.line.length) this.cursor = this.line.length;
642 var newPos = this._getCursorPos();
644 // check if cursors are in the same line
645 if (oldPos.rows === newPos.rows) {
646 var diffCursor = this.cursor - oldcursor;
648 if (diffCursor < 0) {
649 diffWidth = -getStringWidth(
650 this.line.substring(this.cursor, oldcursor)
652 } else if (diffCursor > 0) {
653 diffWidth = getStringWidth(
654 this.line.substring(this.cursor, oldcursor)
657 exports.moveCursor(this.output, diffWidth, 0);
658 this.prevRows = newPos.rows;
665 // handle a write from the tty
666 Interface.prototype._ttyWrite = function(s, key) {
669 // Ignore escape key - Fixes #2876
670 if (key.name == 'escape') return;
672 if (key.ctrl && key.shift) {
673 /* Control and shift pressed */
676 this._deleteLineLeft();
680 this._deleteLineRight();
684 } else if (key.ctrl) {
685 /* Control key pressed */
689 if (this.listenerCount('SIGINT') > 0) {
692 // This readline instance is finished
697 case 'h': // delete left
701 case 'd': // delete right or EOF
702 if (this.cursor === 0 && this.line.length === 0) {
703 // This readline instance is finished
705 } else if (this.cursor < this.line.length) {
710 case 'u': // delete the whole line
716 case 'k': // delete from current to end of line
717 this._deleteLineRight();
720 case 'a': // go to the start of the line
721 this._moveCursor(-Infinity);
724 case 'e': // go to the end of the line
725 this._moveCursor(+Infinity);
728 case 'b': // back one character
729 this._moveCursor(-1);
732 case 'f': // forward one character
733 this._moveCursor(+1);
736 case 'l': // clear the whole screen
737 exports.cursorTo(this.output, 0, 0);
738 exports.clearScreenDown(this.output);
742 case 'n': // next history item
746 case 'p': // previous history item
751 if (process.platform == 'win32') break;
752 if (this.listenerCount('SIGTSTP') > 0) {
753 this.emit('SIGTSTP');
755 process.once('SIGCONT', (function(self) {
757 // Don't raise events if stream has already been abandoned.
759 // Stream must be paused and resumed after SIGCONT to catch
760 // SIGINT, SIGTSTP, and EOF.
762 self.emit('SIGCONT');
764 // explicitly re-enable "raw mode" and move the cursor to
765 // the correct position.
766 // See https://github.com/joyent/node/issues/3295.
767 self._setRawMode(true);
771 this._setRawMode(false);
772 process.kill(process.pid, 'SIGTSTP');
776 case 'w': // delete backwards to a word boundary
778 this._deleteWordLeft();
781 case 'delete': // delete forward to a word boundary
782 this._deleteWordRight();
794 } else if (key.meta) {
795 /* Meta key pressed */
798 case 'b': // backward word
802 case 'f': // forward word
806 case 'd': // delete forward word
808 this._deleteWordRight();
811 case 'backspace': // delete backwards to a word boundary
812 this._deleteWordLeft();
817 /* No modifier keys used */
819 // \r bookkeeping is only relevant if a \n comes right after.
820 if (this._sawReturn && key.name !== 'enter')
821 this._sawReturn = false;
824 case 'return': // carriage return, i.e. \r
825 this._sawReturn = true;
831 this._sawReturn = false;
845 this._moveCursor(-1);
849 this._moveCursor(+1);
853 this._moveCursor(-Infinity);
857 this._moveCursor(+Infinity);
869 // If tab completion enabled, do that...
870 if (typeof this.completer === 'function') {
877 if (s instanceof Buffer)
878 s = s.toString('utf-8');
881 var lines = s.split(/\r\n|\n|\r/);
882 for (var i = 0, len = lines.length; i < len; i++) {
886 this._insertString(lines[i]);
894 exports.Interface = Interface;
898 * accepts a readable Stream instance and makes it emit "keypress" events
901 const KEYPRESS_DECODER = Symbol('keypress-decoder');
902 const ESCAPE_DECODER = Symbol('escape-decoder');
904 function emitKeypressEvents(stream) {
905 if (stream[KEYPRESS_DECODER]) return;
906 var StringDecoder = require('string_decoder').StringDecoder; // lazy load
907 stream[KEYPRESS_DECODER] = new StringDecoder('utf8');
909 stream[ESCAPE_DECODER] = emitKeys(stream);
910 stream[ESCAPE_DECODER].next();
913 if (stream.listenerCount('keypress') > 0) {
914 var r = stream[KEYPRESS_DECODER].write(b);
916 for (var i = 0; i < r.length; i++) {
918 stream[ESCAPE_DECODER].next(r[i]);
920 // if the generator throws (it could happen in the `keypress`
921 // event), we need to restart it.
922 stream[ESCAPE_DECODER] = emitKeys(stream);
923 stream[ESCAPE_DECODER].next();
929 // Nobody's watching anyway
930 stream.removeListener('data', onData);
931 stream.on('newListener', onNewListener);
935 function onNewListener(event) {
936 if (event == 'keypress') {
937 stream.on('data', onData);
938 stream.removeListener('newListener', onNewListener);
942 if (stream.listenerCount('keypress') > 0) {
943 stream.on('data', onData);
945 stream.on('newListener', onNewListener);
948 exports.emitKeypressEvents = emitKeypressEvents;
951 Some patterns seen in terminal key escape codes, derived from combos seen
952 at http://www.midnight-commander.org/browser/lib/tty/key.c
956 ESC [ modifier letter
957 ESC [ 1 ; modifier letter
959 ESC [ num ; modifier char
961 ESC O modifier letter
962 ESC O 1 ; modifier letter
964 ESC [ [ num ; modifier char
965 ESC [ [ 1 ; modifier letter
969 - char is usually ~ but $ and ^ also happen with rxvt
975 - two leading ESCs apparently mean the same as one leading ESC
978 // Regexes used for ansi escape code splitting
979 const metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/;
980 const functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [
981 '(\\d+)(?:;(\\d+))?([~^$])',
982 '(?:M([@ #!a`])(.)(.))', // mouse
983 '(?:1;)?(\\d+)?([a-zA-Z])'
987 function* emitKeys(stream) {
1000 if (ch === '\x1b') {
1004 if (ch === '\x1b') {
1009 if (escaped && (ch === 'O' || ch === '[')) {
1010 // ansi escape sequence
1016 // ESC O modifier letter
1019 if (ch >= '0' && ch <= '9') {
1020 modifier = (ch >> 0) - 1;
1026 } else if (ch === '[') {
1028 // ESC [ modifier letter
1029 // ESC [ [ modifier letter
1035 // ^--- escape codes might have a second bracket
1041 * Here and later we try to buffer just enough data to get
1042 * a complete ascii sequence.
1044 * We have basically two classes of ascii characters to process:
1047 * 1. `\x1b[24;5~` should be parsed as { code: '[24~', modifier: 5 }
1049 * This particular example is featuring Ctrl+F12 in xterm.
1051 * - `;5` part is optional, e.g. it could be `\x1b[24~`
1052 * - first part can contain one or two digits
1054 * So the generic regexp is like /^\d\d?(;\d)?[~^$]$/
1057 * 2. `\x1b[1;5H` should be parsed as { code: '[H', modifier: 5 }
1059 * This particular example is featuring Ctrl+Home in xterm.
1061 * - `1;5` part is optional, e.g. it could be `\x1b[H`
1062 * - `1;` part is optional, e.g. it could be `\x1b[5H`
1064 * So the generic regexp is like /^((\d;)?\d)?[A-Za-z]$/
1067 const cmdStart = s.length - 1;
1069 // skip one or two leading digits
1070 if (ch >= '0' && ch <= '9') {
1073 if (ch >= '0' && ch <= '9') {
1082 if (ch >= '0' && ch <= '9') {
1088 * We buffered enough data, now trying to extract code
1089 * and modifier from it
1091 const cmd = s.slice(cmdStart);
1094 if ((match = cmd.match(/^(\d\d?)(;(\d))?([~^$])$/))) {
1095 code += match[1] + match[4];
1096 modifier = (match[3] || 1) - 1;
1097 } else if ((match = cmd.match(/^((\d;)?(\d))?([A-Za-z])$/))) {
1099 modifier = (match[3] || 1) - 1;
1105 // Parse the key modifier
1106 key.ctrl = !!(modifier & 4);
1107 key.meta = !!(modifier & 10);
1108 key.shift = !!(modifier & 1);
1111 // Parse the key itself
1113 /* xterm/gnome ESC O letter */
1114 case 'OP': key.name = 'f1'; break;
1115 case 'OQ': key.name = 'f2'; break;
1116 case 'OR': key.name = 'f3'; break;
1117 case 'OS': key.name = 'f4'; break;
1119 /* xterm/rxvt ESC [ number ~ */
1120 case '[11~': key.name = 'f1'; break;
1121 case '[12~': key.name = 'f2'; break;
1122 case '[13~': key.name = 'f3'; break;
1123 case '[14~': key.name = 'f4'; break;
1125 /* from Cygwin and used in libuv */
1126 case '[[A': key.name = 'f1'; break;
1127 case '[[B': key.name = 'f2'; break;
1128 case '[[C': key.name = 'f3'; break;
1129 case '[[D': key.name = 'f4'; break;
1130 case '[[E': key.name = 'f5'; break;
1133 case '[15~': key.name = 'f5'; break;
1134 case '[17~': key.name = 'f6'; break;
1135 case '[18~': key.name = 'f7'; break;
1136 case '[19~': key.name = 'f8'; break;
1137 case '[20~': key.name = 'f9'; break;
1138 case '[21~': key.name = 'f10'; break;
1139 case '[23~': key.name = 'f11'; break;
1140 case '[24~': key.name = 'f12'; break;
1142 /* xterm ESC [ letter */
1143 case '[A': key.name = 'up'; break;
1144 case '[B': key.name = 'down'; break;
1145 case '[C': key.name = 'right'; break;
1146 case '[D': key.name = 'left'; break;
1147 case '[E': key.name = 'clear'; break;
1148 case '[F': key.name = 'end'; break;
1149 case '[H': key.name = 'home'; break;
1151 /* xterm/gnome ESC O letter */
1152 case 'OA': key.name = 'up'; break;
1153 case 'OB': key.name = 'down'; break;
1154 case 'OC': key.name = 'right'; break;
1155 case 'OD': key.name = 'left'; break;
1156 case 'OE': key.name = 'clear'; break;
1157 case 'OF': key.name = 'end'; break;
1158 case 'OH': key.name = 'home'; break;
1160 /* xterm/rxvt ESC [ number ~ */
1161 case '[1~': key.name = 'home'; break;
1162 case '[2~': key.name = 'insert'; break;
1163 case '[3~': key.name = 'delete'; break;
1164 case '[4~': key.name = 'end'; break;
1165 case '[5~': key.name = 'pageup'; break;
1166 case '[6~': key.name = 'pagedown'; break;
1169 case '[[5~': key.name = 'pageup'; break;
1170 case '[[6~': key.name = 'pagedown'; break;
1173 case '[7~': key.name = 'home'; break;
1174 case '[8~': key.name = 'end'; break;
1176 /* rxvt keys with modifiers */
1177 case '[a': key.name = 'up'; key.shift = true; break;
1178 case '[b': key.name = 'down'; key.shift = true; break;
1179 case '[c': key.name = 'right'; key.shift = true; break;
1180 case '[d': key.name = 'left'; key.shift = true; break;
1181 case '[e': key.name = 'clear'; key.shift = true; break;
1183 case '[2$': key.name = 'insert'; key.shift = true; break;
1184 case '[3$': key.name = 'delete'; key.shift = true; break;
1185 case '[5$': key.name = 'pageup'; key.shift = true; break;
1186 case '[6$': key.name = 'pagedown'; key.shift = true; break;
1187 case '[7$': key.name = 'home'; key.shift = true; break;
1188 case '[8$': key.name = 'end'; key.shift = true; break;
1190 case 'Oa': key.name = 'up'; key.ctrl = true; break;
1191 case 'Ob': key.name = 'down'; key.ctrl = true; break;
1192 case 'Oc': key.name = 'right'; key.ctrl = true; break;
1193 case 'Od': key.name = 'left'; key.ctrl = true; break;
1194 case 'Oe': key.name = 'clear'; key.ctrl = true; break;
1196 case '[2^': key.name = 'insert'; key.ctrl = true; break;
1197 case '[3^': key.name = 'delete'; key.ctrl = true; break;
1198 case '[5^': key.name = 'pageup'; key.ctrl = true; break;
1199 case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
1200 case '[7^': key.name = 'home'; key.ctrl = true; break;
1201 case '[8^': key.name = 'end'; key.ctrl = true; break;
1204 case '[Z': key.name = 'tab'; key.shift = true; break;
1205 default: key.name = 'undefined'; break;
1208 } else if (ch === '\r') {
1210 key.name = 'return';
1212 } else if (ch === '\n') {
1213 // enter, should have been called linefeed
1216 } else if (ch === '\t') {
1220 } else if (ch === '\b' || ch === '\x7f') {
1221 // backspace or ctrl+h
1222 key.name = 'backspace';
1225 } else if (ch === '\x1b') {
1227 key.name = 'escape';
1230 } else if (ch === ' ') {
1234 } else if (!escaped && ch <= '\x1a') {
1236 key.name = String.fromCharCode(ch.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
1239 } else if (/^[0-9A-Za-z]$/.test(ch)) {
1240 // letter, number, shift+letter
1241 key.name = ch.toLowerCase();
1242 key.shift = /^[A-Z]$/.test(ch);
1248 if (key.name !== undefined) {
1249 /* Named character or sequence */
1250 stream.emit('keypress', escaped ? undefined : s, key);
1251 } else if (s.length === 1) {
1252 /* Single unnamed character, e.g. "." */
1253 stream.emit('keypress', s);
1255 /* Unrecognized or broken escape sequence, don't emit anything */
1262 * moves the cursor to the x and y coordinate on the given stream
1265 function cursorTo(stream, x, y) {
1266 if (stream === null || stream === undefined)
1269 if (typeof x !== 'number' && typeof y !== 'number')
1272 if (typeof x !== 'number')
1273 throw new Error("Can't set cursor row without also setting it's column");
1275 if (typeof y !== 'number') {
1276 stream.write('\x1b[' + (x + 1) + 'G');
1278 stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
1281 exports.cursorTo = cursorTo;
1285 * moves the cursor relative to its current location
1288 function moveCursor(stream, dx, dy) {
1289 if (stream === null || stream === undefined)
1293 stream.write('\x1b[' + (-dx) + 'D');
1294 } else if (dx > 0) {
1295 stream.write('\x1b[' + dx + 'C');
1299 stream.write('\x1b[' + (-dy) + 'A');
1300 } else if (dy > 0) {
1301 stream.write('\x1b[' + dy + 'B');
1304 exports.moveCursor = moveCursor;
1308 * clears the current line the cursor is on:
1309 * -1 for left of the cursor
1310 * +1 for right of the cursor
1311 * 0 for the entire line
1314 function clearLine(stream, dir) {
1315 if (stream === null || stream === undefined)
1320 stream.write('\x1b[1K');
1321 } else if (dir > 0) {
1323 stream.write('\x1b[0K');
1326 stream.write('\x1b[2K');
1329 exports.clearLine = clearLine;
1333 * clears the screen from the current position of the cursor down
1336 function clearScreenDown(stream) {
1337 if (stream === null || stream === undefined)
1340 stream.write('\x1b[0J');
1342 exports.clearScreenDown = clearScreenDown;
1346 * Returns the number of columns required to display the given string.
1349 function getStringWidth(str) {
1351 str = stripVTControlCharacters(str);
1352 for (var i = 0, len = str.length; i < len; i++) {
1353 var code = str.codePointAt(i);
1354 if (code >= 0x10000) { // surrogates
1357 if (isFullWidthCodePoint(code)) {
1365 exports.getStringWidth = getStringWidth;
1369 * Returns true if the character represented by a given
1370 * Unicode code point is full-width. Otherwise returns false.
1373 function isFullWidthCodePoint(code) {
1378 // Code points are derived from:
1379 // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
1380 if (code >= 0x1100 && (
1381 code <= 0x115f || // Hangul Jamo
1382 0x2329 === code || // LEFT-POINTING ANGLE BRACKET
1383 0x232a === code || // RIGHT-POINTING ANGLE BRACKET
1384 // CJK Radicals Supplement .. Enclosed CJK Letters and Months
1385 (0x2e80 <= code && code <= 0x3247 && code !== 0x303f) ||
1386 // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
1387 0x3250 <= code && code <= 0x4dbf ||
1388 // CJK Unified Ideographs .. Yi Radicals
1389 0x4e00 <= code && code <= 0xa4c6 ||
1390 // Hangul Jamo Extended-A
1391 0xa960 <= code && code <= 0xa97c ||
1393 0xac00 <= code && code <= 0xd7a3 ||
1394 // CJK Compatibility Ideographs
1395 0xf900 <= code && code <= 0xfaff ||
1397 0xfe10 <= code && code <= 0xfe19 ||
1398 // CJK Compatibility Forms .. Small Form Variants
1399 0xfe30 <= code && code <= 0xfe6b ||
1400 // Halfwidth and Fullwidth Forms
1401 0xff01 <= code && code <= 0xff60 ||
1402 0xffe0 <= code && code <= 0xffe6 ||
1404 0x1b000 <= code && code <= 0x1b001 ||
1405 // Enclosed Ideographic Supplement
1406 0x1f200 <= code && code <= 0x1f251 ||
1407 // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
1408 0x20000 <= code && code <= 0x3fffd)) {
1413 exports.isFullWidthCodePoint = isFullWidthCodePoint;
1417 * Returns the Unicode code point for the character at the
1418 * given index in the given string. Similar to String.charCodeAt(),
1419 * but this function handles surrogates (code point >= 0x10000).
1422 function codePointAt(str, index) {
1423 var code = str.charCodeAt(index);
1425 if (0xd800 <= code && code <= 0xdbff) { // High surrogate
1426 low = str.charCodeAt(index + 1);
1428 code = 0x10000 + (code - 0xd800) * 0x400 + (low - 0xdc00);
1433 exports.codePointAt = internalUtil.deprecate(codePointAt,
1434 'readline.codePointAt is deprecated. ' +
1435 'Use String.prototype.codePointAt instead.');
1439 * Tries to remove all VT control characters. Use to estimate displayed
1440 * string width. May be buggy due to not running a real state machine
1442 function stripVTControlCharacters(str) {
1443 str = str.replace(new RegExp(functionKeyCodeReAnywhere.source, 'g'), '');
1444 return str.replace(new RegExp(metaKeyCodeReAnywhere.source, 'g'), '');
1446 exports.stripVTControlCharacters = stripVTControlCharacters;