1 // Copyright Joyent, Inc. and other Node contributors.
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // Inspiration for this code comes from Salvatore Sanfilippo's linenoise.
23 // https://github.com/antirez/linenoise
25 // * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
26 // * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
28 var kHistorySize = 30;
30 var util = require('util');
31 var inherits = require('util').inherits;
32 var EventEmitter = require('events').EventEmitter;
35 exports.createInterface = function(input, output, completer, terminal) {
37 if (arguments.length === 1) {
38 rl = new Interface(input);
40 rl = new Interface(input, output, completer, terminal);
46 function Interface(input, output, completer, terminal) {
47 if (!(this instanceof Interface)) {
48 return new Interface(input, output, completer, terminal);
51 this._sawReturn = false;
53 EventEmitter.call(this);
55 if (arguments.length === 1) {
56 // an options object was given
57 output = input.output;
58 completer = input.completer;
59 terminal = input.terminal;
63 completer = completer || function() { return []; };
65 if (!util.isFunction(completer)) {
66 throw new TypeError('Argument \'completer\' must be a function');
69 // backwards compat; check the isTTY prop of the output stream
70 // when `terminal` was not specified
71 if (util.isUndefined(terminal) && !util.isNullOrUndefined(output)) {
72 terminal = !!output.isTTY;
80 // Check arity, 2 - for async, 1 for sync
81 this.completer = completer.length === 2 ? completer : function(v, callback) {
82 callback(null, completer(v));
87 this.terminal = !!terminal;
89 function ondata(data) {
90 self._normalWrite(data);
94 if (util.isString(self._line_buffer) && self._line_buffer.length > 0) {
95 self.emit('line', self._line_buffer);
100 function ontermend() {
101 if (util.isString(self.line) && self.line.length > 0) {
102 self.emit('line', self.line);
107 function onkeypress(s, key) {
108 self._ttyWrite(s, key);
111 function onresize() {
115 if (!this.terminal) {
116 input.on('data', ondata);
117 input.on('end', onend);
118 self.once('close', function() {
119 input.removeListener('data', ondata);
120 input.removeListener('end', onend);
122 var StringDecoder = require('string_decoder').StringDecoder; // lazy load
123 this._decoder = new StringDecoder('utf8');
127 exports.emitKeypressEvents(input);
129 // input usually refers to stdin
130 input.on('keypress', onkeypress);
131 input.on('end', ontermend);
136 this._setRawMode(true);
137 this.terminal = true;
139 // Cursor position on the line.
143 this.historyIndex = -1;
145 if (!util.isNullOrUndefined(output))
146 output.on('resize', onresize);
148 self.once('close', function() {
149 input.removeListener('keypress', onkeypress);
150 input.removeListener('end', ontermend);
151 if (!util.isNullOrUndefined(output)) {
152 output.removeListener('resize', onresize);
160 inherits(Interface, EventEmitter);
162 Interface.prototype.__defineGetter__('columns', function() {
163 var columns = Infinity;
164 if (this.output && this.output.columns)
165 columns = this.output.columns;
169 Interface.prototype.setPrompt = function(prompt) {
170 this._prompt = prompt;
174 Interface.prototype._setRawMode = function(mode) {
175 if (util.isFunction(this.input.setRawMode)) {
176 return this.input.setRawMode(mode);
181 Interface.prototype.prompt = function(preserveCursor) {
182 if (this.paused) this.resume();
184 if (!preserveCursor) this.cursor = 0;
187 this._writeToOutput(this._prompt);
192 Interface.prototype.question = function(query, cb) {
193 if (util.isFunction(cb)) {
194 if (this._questionCallback) {
197 this._oldPrompt = this._prompt;
198 this.setPrompt(query);
199 this._questionCallback = cb;
206 Interface.prototype._onLine = function(line) {
207 if (this._questionCallback) {
208 var cb = this._questionCallback;
209 this._questionCallback = null;
210 this.setPrompt(this._oldPrompt);
213 this.emit('line', line);
217 Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) {
218 if (!util.isString(stringToWrite))
219 throw new TypeError('stringToWrite must be a string');
221 if (!util.isNullOrUndefined(this.output))
222 this.output.write(stringToWrite);
225 Interface.prototype._addHistory = function() {
226 if (this.line.length === 0) return '';
228 if (this.history.length === 0 || this.history[0] !== this.line) {
229 this.history.unshift(this.line);
231 // Only store so many
232 if (this.history.length > kHistorySize) this.history.pop();
235 this.historyIndex = -1;
236 return this.history[0];
240 Interface.prototype._refreshLine = function() {
242 var line = this._prompt + this.line;
243 var dispPos = this._getDisplayPos(line);
244 var lineCols = dispPos.cols;
245 var lineRows = dispPos.rows;
248 var cursorPos = this._getCursorPos();
250 // first move to the bottom of the current line, based on cursor pos
251 var prevRows = this.prevRows || 0;
253 exports.moveCursor(this.output, 0, -prevRows);
256 // Cursor to left edge.
257 exports.cursorTo(this.output, 0);
259 exports.clearScreenDown(this.output);
261 // Write the prompt and the current buffer content.
262 this._writeToOutput(line);
264 // Force terminal to allocate a new line
265 if (lineCols === 0) {
266 this._writeToOutput(' ');
269 // Move cursor to original position.
270 exports.cursorTo(this.output, cursorPos.cols);
272 var diff = lineRows - cursorPos.rows;
274 exports.moveCursor(this.output, 0, -diff);
277 this.prevRows = cursorPos.rows;
281 Interface.prototype.close = function() {
282 if (this.closed) return;
285 this._setRawMode(false);
292 Interface.prototype.pause = function() {
293 if (this.paused) return;
301 Interface.prototype.resume = function() {
302 if (!this.paused) return;
310 Interface.prototype.write = function(d, key) {
311 if (this.paused) this.resume();
312 this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d);
315 // \r\n, \n, or \r followed by something other than \n
316 var lineEnding = /\r?\n|\r(?!\n)/;
317 Interface.prototype._normalWrite = function(b) {
318 if (util.isUndefined(b)) {
321 var string = this._decoder.write(b);
322 if (this._sawReturn) {
323 string = string.replace(/^\n/, '');
324 this._sawReturn = false;
327 // Run test() on the new string chunk, not on the entire line buffer.
328 var newPartContainsEnding = lineEnding.test(string);
330 if (this._line_buffer) {
331 string = this._line_buffer + string;
332 this._line_buffer = null;
334 if (newPartContainsEnding) {
335 this._sawReturn = /\r$/.test(string);
337 // got one or more newlines; process into "line" events
338 var lines = string.split(lineEnding);
339 // either '' or (concievably) the unfinished portion of the next line
340 string = lines.pop();
341 this._line_buffer = string;
342 lines.forEach(function(line) {
346 // no newlines this time, save what we have for next time
347 this._line_buffer = string;
351 Interface.prototype._insertString = function(c) {
352 //BUG: Problem when adding tabs with following content.
353 // Perhaps the bug is in _refreshLine(). Not sure.
354 // A hack would be to insert spaces instead of literal '\t'.
355 if (this.cursor < this.line.length) {
356 var beg = this.line.slice(0, this.cursor);
357 var end = this.line.slice(this.cursor, this.line.length);
358 this.line = beg + c + end;
359 this.cursor += c.length;
363 this.cursor += c.length;
365 if (this._getCursorPos().cols === 0) {
368 this._writeToOutput(c);
371 // a hack to get the line refreshed if it's needed
376 Interface.prototype._tabComplete = function() {
380 self.completer(self.line.slice(0, self.cursor), function(err, rv) {
384 // XXX Log it somewhere?
388 var completions = rv[0],
389 completeOn = rv[1]; // the text that was completed
390 if (completions && completions.length) {
391 // Apply/show completions.
392 if (completions.length === 1) {
393 self._insertString(completions[0].slice(completeOn.length));
395 self._writeToOutput('\r\n');
396 var width = completions.reduce(function(a, b) {
397 return a.length > b.length ? a : b;
398 }).length + 2; // 2 space padding
399 var maxColumns = Math.floor(self.columns / width) || 1;
401 for (var i = 0, compLen = completions.length; i < compLen; i++) {
404 handleGroup(self, group, width, maxColumns);
410 handleGroup(self, group, width, maxColumns);
412 // If there is a common prefix to all matches, then apply that
414 var f = completions.filter(function(e) { if (e) return e; });
415 var prefix = commonPrefix(f);
416 if (prefix.length > completeOn.length) {
417 self._insertString(prefix.slice(completeOn.length));
426 // this = Interface instance
427 function handleGroup(self, group, width, maxColumns) {
428 if (group.length == 0) {
431 var minRows = Math.ceil(group.length / maxColumns);
432 for (var row = 0; row < minRows; row++) {
433 for (var col = 0; col < maxColumns; col++) {
434 var idx = row * maxColumns + col;
435 if (idx >= group.length) {
438 var item = group[idx];
439 self._writeToOutput(item);
440 if (col < maxColumns - 1) {
441 for (var s = 0, itemLen = item.length; s < width - itemLen;
443 self._writeToOutput(' ');
447 self._writeToOutput('\r\n');
449 self._writeToOutput('\r\n');
452 function commonPrefix(strings) {
453 if (!strings || strings.length == 0) {
456 var sorted = strings.slice().sort();
458 var max = sorted[sorted.length - 1];
459 for (var i = 0, len = min.length; i < len; i++) {
460 if (min[i] != max[i]) {
461 return min.slice(0, i);
468 Interface.prototype._wordLeft = function() {
469 if (this.cursor > 0) {
470 var leading = this.line.slice(0, this.cursor);
471 var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
472 this._moveCursor(-match[0].length);
477 Interface.prototype._wordRight = function() {
478 if (this.cursor < this.line.length) {
479 var trailing = this.line.slice(this.cursor);
480 var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
481 this._moveCursor(match[0].length);
486 Interface.prototype._deleteLeft = function() {
487 if (this.cursor > 0 && this.line.length > 0) {
488 this.line = this.line.slice(0, this.cursor - 1) +
489 this.line.slice(this.cursor, this.line.length);
497 Interface.prototype._deleteRight = function() {
498 this.line = this.line.slice(0, this.cursor) +
499 this.line.slice(this.cursor + 1, this.line.length);
504 Interface.prototype._deleteWordLeft = function() {
505 if (this.cursor > 0) {
506 var leading = this.line.slice(0, this.cursor);
507 var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
508 leading = leading.slice(0, leading.length - match[0].length);
509 this.line = leading + this.line.slice(this.cursor, this.line.length);
510 this.cursor = leading.length;
516 Interface.prototype._deleteWordRight = function() {
517 if (this.cursor < this.line.length) {
518 var trailing = this.line.slice(this.cursor);
519 var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
520 this.line = this.line.slice(0, this.cursor) +
521 trailing.slice(match[0].length);
527 Interface.prototype._deleteLineLeft = function() {
528 this.line = this.line.slice(this.cursor);
534 Interface.prototype._deleteLineRight = function() {
535 this.line = this.line.slice(0, this.cursor);
540 Interface.prototype.clearLine = function() {
541 this._moveCursor(+Infinity);
542 this._writeToOutput('\r\n');
549 Interface.prototype._line = function() {
550 var line = this._addHistory();
556 Interface.prototype._historyNext = function() {
557 if (this.historyIndex > 0) {
559 this.line = this.history[this.historyIndex];
560 this.cursor = this.line.length; // set cursor to end of line.
563 } else if (this.historyIndex === 0) {
564 this.historyIndex = -1;
572 Interface.prototype._historyPrev = function() {
573 if (this.historyIndex + 1 < this.history.length) {
575 this.line = this.history[this.historyIndex];
576 this.cursor = this.line.length; // set cursor to end of line.
583 // Returns the last character's display position of the given string
584 Interface.prototype._getDisplayPos = function(str) {
586 var col = this.columns;
589 str = stripVTControlCharacters(str);
590 for (var i = 0, len = str.length; i < len; i++) {
591 code = codePointAt(str, i);
592 if (code >= 0x10000) { // surrogates
595 if (code === 0x0a) { // new line \n
600 if (isFullWidthCodePoint(code)) {
601 if ((offset + 1) % col === 0) {
609 var cols = offset % col;
610 var rows = row + (offset - cols) / col;
611 return {cols: cols, rows: rows};
615 // Returns current cursor's position and line
616 Interface.prototype._getCursorPos = function() {
617 var columns = this.columns;
618 var strBeforeCursor = this._prompt + this.line.substring(0, this.cursor);
619 var dispPos = this._getDisplayPos(stripVTControlCharacters(strBeforeCursor));
620 var cols = dispPos.cols;
621 var rows = dispPos.rows;
622 // If the cursor is on a full-width character which steps over the line,
623 // move the cursor to the beginning of the next line.
624 if (cols + 1 === columns &&
625 this.cursor < this.line.length &&
626 isFullWidthCodePoint(codePointAt(this.line, this.cursor))) {
630 return {cols: cols, rows: rows};
634 // This function moves cursor dx places to the right
635 // (-dx for left) and refreshes the line if it is needed
636 Interface.prototype._moveCursor = function(dx) {
637 var oldcursor = this.cursor;
638 var oldPos = this._getCursorPos();
642 if (this.cursor < 0) this.cursor = 0;
643 else if (this.cursor > this.line.length) this.cursor = this.line.length;
645 var newPos = this._getCursorPos();
647 // check if cursors are in the same line
648 if (oldPos.rows === newPos.rows) {
649 var diffCursor = this.cursor - oldcursor;
651 if (diffCursor < 0) {
652 diffWidth = -getStringWidth(
653 this.line.substring(this.cursor, oldcursor)
655 } else if (diffCursor > 0) {
656 diffWidth = getStringWidth(
657 this.line.substring(this.cursor, oldcursor)
660 exports.moveCursor(this.output, diffWidth, 0);
661 this.prevRows = newPos.rows;
668 // handle a write from the tty
669 Interface.prototype._ttyWrite = function(s, key) {
672 // Ignore escape key - Fixes #2876
673 if (key.name == 'escape') return;
675 if (key.ctrl && key.shift) {
676 /* Control and shift pressed */
679 this._deleteLineLeft();
683 this._deleteLineRight();
687 } else if (key.ctrl) {
688 /* Control key pressed */
692 if (EventEmitter.listenerCount(this, 'SIGINT') > 0) {
695 // This readline instance is finished
700 case 'h': // delete left
704 case 'd': // delete right or EOF
705 if (this.cursor === 0 && this.line.length === 0) {
706 // This readline instance is finished
708 } else if (this.cursor < this.line.length) {
713 case 'u': // delete the whole line
719 case 'k': // delete from current to end of line
720 this._deleteLineRight();
723 case 'a': // go to the start of the line
724 this._moveCursor(-Infinity);
727 case 'e': // go to the end of the line
728 this._moveCursor(+Infinity);
731 case 'b': // back one character
732 this._moveCursor(-1);
735 case 'f': // forward one character
736 this._moveCursor(+1);
739 case 'l': // clear the whole screen
740 exports.cursorTo(this.output, 0, 0);
741 exports.clearScreenDown(this.output);
745 case 'n': // next history item
749 case 'p': // previous history item
754 if (process.platform == 'win32') break;
755 if (EventEmitter.listenerCount(this, 'SIGTSTP') > 0) {
756 this.emit('SIGTSTP');
758 process.once('SIGCONT', (function(self) {
760 // Don't raise events if stream has already been abandoned.
762 // Stream must be paused and resumed after SIGCONT to catch
763 // SIGINT, SIGTSTP, and EOF.
765 self.emit('SIGCONT');
767 // explicitly re-enable "raw mode" and move the cursor to
768 // the correct position.
769 // See https://github.com/joyent/node/issues/3295.
770 self._setRawMode(true);
774 this._setRawMode(false);
775 process.kill(process.pid, 'SIGTSTP');
779 case 'w': // delete backwards to a word boundary
781 this._deleteWordLeft();
784 case 'delete': // delete forward to a word boundary
785 this._deleteWordRight();
797 } else if (key.meta) {
798 /* Meta key pressed */
801 case 'b': // backward word
805 case 'f': // forward word
809 case 'd': // delete forward word
811 this._deleteWordRight();
814 case 'backspace': // delete backwards to a word boundary
815 this._deleteWordLeft();
820 /* No modifier keys used */
822 // \r bookkeeping is only relevant if a \n comes right after.
823 if (this._sawReturn && key.name !== 'enter')
824 this._sawReturn = false;
827 case 'return': // carriage return, i.e. \r
828 this._sawReturn = true;
834 this._sawReturn = false;
847 case 'tab': // tab completion
852 this._moveCursor(-1);
856 this._moveCursor(+1);
860 this._moveCursor(-Infinity);
864 this._moveCursor(+Infinity);
876 if (util.isBuffer(s))
877 s = s.toString('utf-8');
880 var lines = s.split(/\r\n|\n|\r/);
881 for (var i = 0, len = lines.length; i < len; i++) {
885 this._insertString(lines[i]);
893 exports.Interface = Interface;
898 * accepts a readable Stream instance and makes it emit "keypress" events
901 function emitKeypressEvents(stream) {
902 if (stream._keypressDecoder) return;
903 var StringDecoder = require('string_decoder').StringDecoder; // lazy load
904 stream._keypressDecoder = new StringDecoder('utf8');
907 if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
908 var r = stream._keypressDecoder.write(b);
909 if (r) emitKeys(stream, r);
911 // Nobody's watching anyway
912 stream.removeListener('data', onData);
913 stream.on('newListener', onNewListener);
917 function onNewListener(event) {
918 if (event == 'keypress') {
919 stream.on('data', onData);
920 stream.removeListener('newListener', onNewListener);
924 if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
925 stream.on('data', onData);
927 stream.on('newListener', onNewListener);
930 exports.emitKeypressEvents = emitKeypressEvents;
933 Some patterns seen in terminal key escape codes, derived from combos seen
934 at http://www.midnight-commander.org/browser/lib/tty/key.c
938 ESC [ modifier letter
939 ESC [ 1 ; modifier letter
941 ESC [ num ; modifier char
943 ESC O modifier letter
944 ESC O 1 ; modifier letter
946 ESC [ [ num ; modifier char
947 ESC [ [ 1 ; modifier letter
951 - char is usually ~ but $ and ^ also happen with rxvt
957 - two leading ESCs apparently mean the same as one leading ESC
960 // Regexes used for ansi escape code splitting
961 var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/;
962 var metaKeyCodeRe = new RegExp('^' + metaKeyCodeReAnywhere.source + '$');
963 var functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [
964 '(\\d+)(?:;(\\d+))?([~^$])',
965 '(?:M([@ #!a`])(.)(.))', // mouse
966 '(?:1;)?(\\d+)?([a-zA-Z])'
968 var functionKeyCodeRe = new RegExp('^' + functionKeyCodeReAnywhere.source);
969 var escapeCodeReAnywhere = new RegExp([
970 functionKeyCodeReAnywhere.source, metaKeyCodeReAnywhere.source, /\x1b./.source
973 function emitKeys(stream, s) {
974 if (util.isBuffer(s)) {
975 if (s[0] > 127 && util.isUndefined(s[1])) {
977 s = '\x1b' + s.toString(stream.encoding || 'utf-8');
979 s = s.toString(stream.encoding || 'utf-8');
985 while (match = escapeCodeReAnywhere.exec(s)) {
986 buffer = buffer.concat(s.slice(0, match.index).split(''));
987 buffer.push(match[0]);
988 s = s.slice(match.index + match[0].length);
990 buffer = buffer.concat(s.split(''));
992 buffer.forEach(function(s) {
1005 key.name = 'return';
1007 } else if (s === '\n') {
1008 // enter, should have been called linefeed
1011 } else if (s === '\t') {
1015 } else if (s === '\b' || s === '\x7f' ||
1016 s === '\x1b\x7f' || s === '\x1b\b') {
1017 // backspace or ctrl+h
1018 key.name = 'backspace';
1019 key.meta = (s.charAt(0) === '\x1b');
1021 } else if (s === '\x1b' || s === '\x1b\x1b') {
1023 key.name = 'escape';
1024 key.meta = (s.length === 2);
1026 } else if (s === ' ' || s === '\x1b ') {
1028 key.meta = (s.length === 2);
1030 } else if (s.length === 1 && s <= '\x1a') {
1032 key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
1035 } else if (s.length === 1 && s >= 'a' && s <= 'z') {
1039 } else if (s.length === 1 && s >= 'A' && s <= 'Z') {
1041 key.name = s.toLowerCase();
1044 } else if (parts = metaKeyCodeRe.exec(s)) {
1045 // meta+character key
1046 key.name = parts[1].toLowerCase();
1048 key.shift = /^[A-Z]$/.test(parts[1]);
1050 } else if (parts = functionKeyCodeRe.exec(s)) {
1051 // ansi escape sequence
1053 // reassemble the key code leaving out leading \x1b's,
1054 // the modifier key bitflag and any meaningless "1;" sequence
1055 var code = (parts[1] || '') + (parts[2] || '') +
1056 (parts[4] || '') + (parts[9] || ''),
1057 modifier = (parts[3] || parts[8] || 1) - 1;
1059 // Parse the key modifier
1060 key.ctrl = !!(modifier & 4);
1061 key.meta = !!(modifier & 10);
1062 key.shift = !!(modifier & 1);
1065 // Parse the key itself
1067 /* xterm/gnome ESC O letter */
1068 case 'OP': key.name = 'f1'; break;
1069 case 'OQ': key.name = 'f2'; break;
1070 case 'OR': key.name = 'f3'; break;
1071 case 'OS': key.name = 'f4'; break;
1073 /* xterm/rxvt ESC [ number ~ */
1074 case '[11~': key.name = 'f1'; break;
1075 case '[12~': key.name = 'f2'; break;
1076 case '[13~': key.name = 'f3'; break;
1077 case '[14~': key.name = 'f4'; break;
1079 /* from Cygwin and used in libuv */
1080 case '[[A': key.name = 'f1'; break;
1081 case '[[B': key.name = 'f2'; break;
1082 case '[[C': key.name = 'f3'; break;
1083 case '[[D': key.name = 'f4'; break;
1084 case '[[E': key.name = 'f5'; break;
1087 case '[15~': key.name = 'f5'; break;
1088 case '[17~': key.name = 'f6'; break;
1089 case '[18~': key.name = 'f7'; break;
1090 case '[19~': key.name = 'f8'; break;
1091 case '[20~': key.name = 'f9'; break;
1092 case '[21~': key.name = 'f10'; break;
1093 case '[23~': key.name = 'f11'; break;
1094 case '[24~': key.name = 'f12'; break;
1096 /* xterm ESC [ letter */
1097 case '[A': key.name = 'up'; break;
1098 case '[B': key.name = 'down'; break;
1099 case '[C': key.name = 'right'; break;
1100 case '[D': key.name = 'left'; break;
1101 case '[E': key.name = 'clear'; break;
1102 case '[F': key.name = 'end'; break;
1103 case '[H': key.name = 'home'; break;
1105 /* xterm/gnome ESC O letter */
1106 case 'OA': key.name = 'up'; break;
1107 case 'OB': key.name = 'down'; break;
1108 case 'OC': key.name = 'right'; break;
1109 case 'OD': key.name = 'left'; break;
1110 case 'OE': key.name = 'clear'; break;
1111 case 'OF': key.name = 'end'; break;
1112 case 'OH': key.name = 'home'; break;
1114 /* xterm/rxvt ESC [ number ~ */
1115 case '[1~': key.name = 'home'; break;
1116 case '[2~': key.name = 'insert'; break;
1117 case '[3~': key.name = 'delete'; break;
1118 case '[4~': key.name = 'end'; break;
1119 case '[5~': key.name = 'pageup'; break;
1120 case '[6~': key.name = 'pagedown'; break;
1123 case '[[5~': key.name = 'pageup'; break;
1124 case '[[6~': key.name = 'pagedown'; break;
1127 case '[7~': key.name = 'home'; break;
1128 case '[8~': key.name = 'end'; break;
1130 /* rxvt keys with modifiers */
1131 case '[a': key.name = 'up'; key.shift = true; break;
1132 case '[b': key.name = 'down'; key.shift = true; break;
1133 case '[c': key.name = 'right'; key.shift = true; break;
1134 case '[d': key.name = 'left'; key.shift = true; break;
1135 case '[e': key.name = 'clear'; key.shift = true; break;
1137 case '[2$': key.name = 'insert'; key.shift = true; break;
1138 case '[3$': key.name = 'delete'; key.shift = true; break;
1139 case '[5$': key.name = 'pageup'; key.shift = true; break;
1140 case '[6$': key.name = 'pagedown'; key.shift = true; break;
1141 case '[7$': key.name = 'home'; key.shift = true; break;
1142 case '[8$': key.name = 'end'; key.shift = true; break;
1144 case 'Oa': key.name = 'up'; key.ctrl = true; break;
1145 case 'Ob': key.name = 'down'; key.ctrl = true; break;
1146 case 'Oc': key.name = 'right'; key.ctrl = true; break;
1147 case 'Od': key.name = 'left'; key.ctrl = true; break;
1148 case 'Oe': key.name = 'clear'; key.ctrl = true; break;
1150 case '[2^': key.name = 'insert'; key.ctrl = true; break;
1151 case '[3^': key.name = 'delete'; key.ctrl = true; break;
1152 case '[5^': key.name = 'pageup'; key.ctrl = true; break;
1153 case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
1154 case '[7^': key.name = 'home'; key.ctrl = true; break;
1155 case '[8^': key.name = 'end'; key.ctrl = true; break;
1158 case '[Z': key.name = 'tab'; key.shift = true; break;
1159 default: key.name = 'undefined'; break;
1164 // Don't emit a key if no name was found
1165 if (util.isUndefined(key.name)) {
1169 if (s.length === 1) {
1174 stream.emit('keypress', ch, key);
1181 * moves the cursor to the x and y coordinate on the given stream
1184 function cursorTo(stream, x, y) {
1185 if (util.isNullOrUndefined(stream))
1188 if (!util.isNumber(x) && !util.isNumber(y))
1191 if (!util.isNumber(x))
1192 throw new Error("Can't set cursor row without also setting it's column");
1194 if (!util.isNumber(y)) {
1195 stream.write('\x1b[' + (x + 1) + 'G');
1197 stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
1200 exports.cursorTo = cursorTo;
1204 * moves the cursor relative to its current location
1207 function moveCursor(stream, dx, dy) {
1208 if (util.isNullOrUndefined(stream))
1212 stream.write('\x1b[' + (-dx) + 'D');
1213 } else if (dx > 0) {
1214 stream.write('\x1b[' + dx + 'C');
1218 stream.write('\x1b[' + (-dy) + 'A');
1219 } else if (dy > 0) {
1220 stream.write('\x1b[' + dy + 'B');
1223 exports.moveCursor = moveCursor;
1227 * clears the current line the cursor is on:
1228 * -1 for left of the cursor
1229 * +1 for right of the cursor
1230 * 0 for the entire line
1233 function clearLine(stream, dir) {
1234 if (util.isNullOrUndefined(stream))
1239 stream.write('\x1b[1K');
1240 } else if (dir > 0) {
1242 stream.write('\x1b[0K');
1245 stream.write('\x1b[2K');
1248 exports.clearLine = clearLine;
1252 * clears the screen from the current position of the cursor down
1255 function clearScreenDown(stream) {
1256 if (util.isNullOrUndefined(stream))
1259 stream.write('\x1b[0J');
1261 exports.clearScreenDown = clearScreenDown;
1265 * Returns the number of columns required to display the given string.
1268 function getStringWidth(str) {
1270 str = stripVTControlCharacters(str);
1271 for (var i = 0, len = str.length; i < len; i++) {
1272 var code = codePointAt(str, i);
1273 if (code >= 0x10000) { // surrogates
1276 if (isFullWidthCodePoint(code)) {
1284 exports.getStringWidth = getStringWidth;
1288 * Returns true if the character represented by a given
1289 * Unicode code point is full-width. Otherwise returns false.
1292 function isFullWidthCodePoint(code) {
1297 // Code points are derived from:
1298 // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
1299 if (code >= 0x1100 && (
1300 code <= 0x115f || // Hangul Jamo
1301 0x2329 === code || // LEFT-POINTING ANGLE BRACKET
1302 0x232a === code || // RIGHT-POINTING ANGLE BRACKET
1303 // CJK Radicals Supplement .. Enclosed CJK Letters and Months
1304 (0x2e80 <= code && code <= 0x3247 && code !== 0x303f) ||
1305 // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
1306 0x3250 <= code && code <= 0x4dbf ||
1307 // CJK Unified Ideographs .. Yi Radicals
1308 0x4e00 <= code && code <= 0xa4c6 ||
1309 // Hangul Jamo Extended-A
1310 0xa960 <= code && code <= 0xa97c ||
1312 0xac00 <= code && code <= 0xd7a3 ||
1313 // CJK Compatibility Ideographs
1314 0xf900 <= code && code <= 0xfaff ||
1316 0xfe10 <= code && code <= 0xfe19 ||
1317 // CJK Compatibility Forms .. Small Form Variants
1318 0xfe30 <= code && code <= 0xfe6b ||
1319 // Halfwidth and Fullwidth Forms
1320 0xff01 <= code && code <= 0xff60 ||
1321 0xffe0 <= code && code <= 0xffe6 ||
1323 0x1b000 <= code && code <= 0x1b001 ||
1324 // Enclosed Ideographic Supplement
1325 0x1f200 <= code && code <= 0x1f251 ||
1326 // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
1327 0x20000 <= code && code <= 0x3fffd)) {
1332 exports.isFullWidthCodePoint = isFullWidthCodePoint;
1336 * Returns the Unicode code point for the character at the
1337 * given index in the given string. Similar to String.charCodeAt(),
1338 * but this function handles surrogates (code point >= 0x10000).
1341 function codePointAt(str, index) {
1342 var code = str.charCodeAt(index);
1344 if (0xd800 <= code && code <= 0xdbff) { // High surrogate
1345 low = str.charCodeAt(index + 1);
1347 code = 0x10000 + (code - 0xd800) * 0x400 + (low - 0xdc00);
1352 exports.codePointAt = codePointAt;
1356 * Tries to remove all VT control characters. Use to estimate displayed
1357 * string width. May be buggy due to not running a real state machine
1359 function stripVTControlCharacters(str) {
1360 str = str.replace(new RegExp(functionKeyCodeReAnywhere.source, 'g'), '');
1361 return str.replace(new RegExp(metaKeyCodeReAnywhere.source, 'g'), '');
1363 exports.stripVTControlCharacters = stripVTControlCharacters;