|
- 'use strict'
-
- var fs = require('fs');
- var stripAnsi = require('strip-ansi');
- var term = 13; // carriage return
-
- /**
- * create -- sync function for reading user input from stdin
- * @param {Object} config {
- * sigint: {Boolean} exit on ^C
- * autocomplete: {StringArray} function({String})
- * history: {String} a history control object (see `prompt-sync-history`)
- * }
- * @returns {Function} prompt function
- */
-
- // for ANSI escape codes reference see https://en.wikipedia.org/wiki/ANSI_escape_code
-
- function create(config) {
-
- config = config || {};
- var sigint = config.sigint;
- var eot = config.eot;
- var autocomplete = config.autocomplete =
- config.autocomplete || function(){return []};
- var history = config.history;
- prompt.history = history || {save: function(){}};
- prompt.hide = function (ask) { return prompt(ask, {echo: ''}) };
-
- return prompt;
-
-
- /**
- * prompt -- sync function for reading user input from stdin
- * @param {String} ask opening question/statement to prompt for
- * @param {String} value initial value for the prompt
- * @param {Object} opts {
- * echo: set to a character to be echoed, default is '*'. Use '' for no echo
- * value: {String} initial value for the prompt
- * ask: {String} opening question/statement to prompt for, does not override ask param
- * autocomplete: {StringArray} function({String})
- * }
- *
- * @returns {string} Returns the string input or (if sigint === false)
- * null if user terminates with a ^C
- */
-
-
- function prompt(ask, value, opts) {
- var insert = 0, savedinsert = 0, res, i, savedstr;
- opts = opts || {};
-
- if (Object(ask) === ask) {
- opts = ask;
- ask = opts.ask;
- } else if (Object(value) === value) {
- opts = value;
- value = opts.value;
- }
- ask = ask || '';
- var echo = opts.echo;
- var masked = 'echo' in opts;
- autocomplete = opts.autocomplete || autocomplete;
-
- var fd = (process.platform === 'win32') ?
- process.stdin.fd :
- fs.openSync('/dev/tty', 'rs');
-
- var wasRaw = process.stdin.isRaw;
- if (!wasRaw) { process.stdin.setRawMode && process.stdin.setRawMode(true); }
-
- var buf = Buffer.alloc(3);
- var str = '', character, read;
-
- savedstr = '';
-
- if (ask) {
- process.stdout.write(ask);
- }
-
- var cycle = 0;
- var prevComplete;
-
- while (true) {
- read = fs.readSync(fd, buf, 0, 3);
- if (read > 1) { // received a control sequence
- switch(buf.toString()) {
- case '\u001b[A': //up arrow
- if (masked) break;
- if (!history) break;
- if (history.atStart()) break;
-
- if (history.atEnd()) {
- savedstr = str;
- savedinsert = insert;
- }
- str = history.prev();
- insert = str.length;
- process.stdout.write('\u001b[2K\u001b[0G' + ask + str);
- break;
- case '\u001b[B': //down arrow
- if (masked) break;
- if (!history) break;
- if (history.pastEnd()) break;
-
- if (history.atPenultimate()) {
- str = savedstr;
- insert = savedinsert;
- history.next();
- } else {
- str = history.next();
- insert = str.length;
- }
- process.stdout.write('\u001b[2K\u001b[0G'+ ask + str + '\u001b['+(insert+ask.length+1)+'G');
- break;
- case '\u001b[D': //left arrow
- if (masked) break;
- var before = insert;
- insert = (--insert < 0) ? 0 : insert;
- if (before - insert)
- process.stdout.write('\u001b[1D');
- break;
- case '\u001b[C': //right arrow
- if (masked) break;
- insert = (++insert > str.length) ? str.length : insert;
- process.stdout.write('\u001b[' + (insert+ask.length+1) + 'G');
- break;
- default:
- if (buf.toString()) {
- str = str + buf.toString();
- str = str.replace(/\0/g, '');
- insert = str.length;
- promptPrint(masked, ask, echo, str, insert);
- process.stdout.write('\u001b[' + (insert+ask.length+1) + 'G');
- buf = Buffer.alloc(3);
- }
- }
- continue; // any other 3 character sequence is ignored
- }
-
- // if it is not a control character seq, assume only one character is read
- character = buf[read-1];
-
- // catch a ^C and return null
- if (character == 3){
- process.stdout.write('^C\n');
- fs.closeSync(fd);
-
- if (sigint) process.exit(130);
-
- process.stdin.setRawMode && process.stdin.setRawMode(wasRaw);
-
- return null;
- }
-
- // catch a ^D and exit
- if (character == 4) {
- if (str.length == 0 && eot) {
- process.stdout.write('exit\n');
- process.exit(0);
- }
- }
-
- // catch the terminating character
- if (character == term) {
- fs.closeSync(fd);
- if (!history) break;
- if (!masked && str.length) history.push(str);
- history.reset();
- break;
- }
-
- // catch a TAB and implement autocomplete
- if (character == 9) { // TAB
- res = autocomplete(str);
-
- if (str == res[0]) {
- res = autocomplete('');
- } else {
- prevComplete = res.length;
- }
-
- if (res.length == 0) {
- process.stdout.write('\t');
- continue;
- }
-
- var item = res[cycle++] || res[cycle = 0, cycle++];
-
- if (item) {
- process.stdout.write('\r\u001b[K' + ask + item);
- str = item;
- insert = item.length;
- }
- }
-
- if (character == 127 || (process.platform == 'win32' && character == 8)) { //backspace
- if (!insert) continue;
- str = str.slice(0, insert-1) + str.slice(insert);
- insert--;
- process.stdout.write('\u001b[2D');
- } else {
- if ((character < 32 ) || (character > 126))
- continue;
- str = str.slice(0, insert) + String.fromCharCode(character) + str.slice(insert);
- insert++;
- };
-
- promptPrint(masked, ask, echo, str, insert);
-
- }
-
- process.stdout.write('\n')
-
- process.stdin.setRawMode && process.stdin.setRawMode(wasRaw);
-
- return str || value || '';
- };
-
-
- function promptPrint(masked, ask, echo, str, insert) {
- if (masked) {
- process.stdout.write('\u001b[2K\u001b[0G' + ask + Array(str.length+1).join(echo));
- } else {
- process.stdout.write('\u001b[s');
- if (insert == str.length) {
- process.stdout.write('\u001b[2K\u001b[0G'+ ask + str);
- } else {
- if (ask) {
- process.stdout.write('\u001b[2K\u001b[0G'+ ask + str);
- } else {
- process.stdout.write('\u001b[2K\u001b[0G'+ str + '\u001b[' + (str.length - insert) + 'D');
- }
- }
-
- // Reposition the cursor to the right of the insertion point
- var askLength = stripAnsi(ask).length;
- process.stdout.write(`\u001b[${askLength+1+(echo==''? 0:insert)}G`);
- }
- }
- };
-
- module.exports = create;
|