306 lines
7.2 KiB
JavaScript
306 lines
7.2 KiB
JavaScript
var tty = require('tty');
|
|
var encode = require('./lib/encode');
|
|
var Stream = require('stream').Stream;
|
|
|
|
var exports = module.exports = function () {
|
|
var input = null;
|
|
function setInput (s) {
|
|
if (input) throw new Error('multiple inputs specified')
|
|
else input = s
|
|
}
|
|
|
|
var output = null;
|
|
function setOutput (s) {
|
|
if (output) throw new Error('multiple outputs specified')
|
|
else output = s
|
|
}
|
|
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
var arg = arguments[i];
|
|
if (!arg) continue;
|
|
if (arg.readable) setInput(arg)
|
|
else if (arg.stdin || arg.input) setInput(arg.stdin || arg.input)
|
|
|
|
if (arg.writable) setOutput(arg)
|
|
else if (arg.stdout || arg.output) setOutput(arg.stdout || arg.output)
|
|
|
|
}
|
|
|
|
if (input && typeof input.fd === 'number' && tty.isatty(input.fd)) {
|
|
if (process.stdin.setRawMode) {
|
|
process.stdin.setRawMode(true);
|
|
}
|
|
else tty.setRawMode(true);
|
|
}
|
|
|
|
var charm = new Charm;
|
|
if (input) {
|
|
input.pipe(charm);
|
|
}
|
|
|
|
if (output) {
|
|
charm.pipe(output);
|
|
}
|
|
|
|
charm.once('^C', process.exit);
|
|
charm.once('end', function () {
|
|
if (input) {
|
|
if (typeof input.fd === 'number' && tty.isatty(input.fd)) {
|
|
if (process.stdin.setRawMode) {
|
|
process.stdin.setRawMode(false);
|
|
}
|
|
else tty.setRawMode(false);
|
|
}
|
|
input.destroy();
|
|
}
|
|
});
|
|
|
|
return charm;
|
|
};
|
|
|
|
var Charm = exports.Charm = function Charm () {
|
|
this.writable = true;
|
|
this.readable = true;
|
|
this.pending = [];
|
|
}
|
|
|
|
Charm.prototype = new Stream;
|
|
|
|
Charm.prototype.write = function (buf) {
|
|
var self = this;
|
|
|
|
if (self.pending.length) {
|
|
var codes = extractCodes(buf);
|
|
var matched = false;
|
|
|
|
for (var i = 0; i < codes.length; i++) {
|
|
for (var j = 0; j < self.pending.length; j++) {
|
|
var cb = self.pending[j];
|
|
if (cb(codes[i])) {
|
|
matched = true;
|
|
self.pending.splice(j, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matched) return;
|
|
}
|
|
|
|
if (buf.length === 1) {
|
|
if (buf[0] === 3) self.emit('^C');
|
|
if (buf[0] === 4) self.emit('^D');
|
|
}
|
|
|
|
self.emit('data', buf);
|
|
|
|
return self;
|
|
};
|
|
|
|
|
|
Charm.prototype.destroy = function () {
|
|
this.end();
|
|
};
|
|
|
|
Charm.prototype.end = function (buf) {
|
|
if (buf) this.write(buf);
|
|
this.emit('end');
|
|
};
|
|
|
|
Charm.prototype.reset = function (cb) {
|
|
this.write(encode('c'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.position = function (x, y) {
|
|
// get/set absolute coordinates
|
|
if (typeof x === 'function') {
|
|
var cb = x;
|
|
this.pending.push(function (buf) {
|
|
if (buf[0] === 27 && buf[1] === encode.ord('[')
|
|
&& buf[buf.length-1] === encode.ord('R')) {
|
|
var pos = buf.toString()
|
|
.slice(2,-1)
|
|
.split(';')
|
|
.map(Number)
|
|
;
|
|
cb(pos[1], pos[0]);
|
|
return true;
|
|
}
|
|
});
|
|
this.write(encode('[6n'));
|
|
}
|
|
else {
|
|
this.write(encode(
|
|
'[' + Math.floor(y) + ';' + Math.floor(x) + 'f'
|
|
));
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.move = function (x, y) {
|
|
// set relative coordinates
|
|
var bufs = [];
|
|
|
|
if (y < 0) this.up(-y)
|
|
else if (y > 0) this.down(y)
|
|
|
|
if (x > 0) this.right(x)
|
|
else if (x < 0) this.left(-x)
|
|
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.up = function (y) {
|
|
if (y === undefined) y = 1;
|
|
this.write(encode('[' + Math.floor(y) + 'A'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.down = function (y) {
|
|
if (y === undefined) y = 1;
|
|
this.write(encode('[' + Math.floor(y) + 'B'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.right = function (x) {
|
|
if (x === undefined) x = 1;
|
|
this.write(encode('[' + Math.floor(x) + 'C'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.left = function (x) {
|
|
if (x === undefined) x = 1;
|
|
this.write(encode('[' + Math.floor(x) + 'D'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.column = function (x) {
|
|
this.write(encode('[' + Math.floor(x) + 'G'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.push = function (withAttributes) {
|
|
this.write(encode(withAttributes ? '7' : '[s'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.pop = function (withAttributes) {
|
|
this.write(encode(withAttributes ? '8' : '[u'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.erase = function (s) {
|
|
if (s === 'end' || s === '$') {
|
|
this.write(encode('[K'));
|
|
}
|
|
else if (s === 'start' || s === '^') {
|
|
this.write(encode('[1K'));
|
|
}
|
|
else if (s === 'line') {
|
|
this.write(encode('[2K'));
|
|
}
|
|
else if (s === 'down') {
|
|
this.write(encode('[J'));
|
|
}
|
|
else if (s === 'up') {
|
|
this.write(encode('[1J'));
|
|
}
|
|
else if (s === 'screen') {
|
|
this.write(encode('[1J'));
|
|
}
|
|
else {
|
|
this.emit('error', new Error('Unknown erase type: ' + s));
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.display = function (attr) {
|
|
var c = {
|
|
reset : 0,
|
|
bright : 1,
|
|
dim : 2,
|
|
underscore : 4,
|
|
blink : 5,
|
|
reverse : 7,
|
|
hidden : 8
|
|
}[attr];
|
|
if (c === undefined) {
|
|
this.emit('error', new Error('Unknown attribute: ' + attr));
|
|
}
|
|
this.write(encode('[' + c + 'm'));
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.foreground = function (color) {
|
|
if (typeof color === 'number') {
|
|
if (color < 0 || color >= 256) {
|
|
this.emit('error', new Error('Color out of range: ' + color));
|
|
}
|
|
this.write(encode('[38;5;' + color + 'm'));
|
|
}
|
|
else {
|
|
var c = {
|
|
black : 30,
|
|
red : 31,
|
|
green : 32,
|
|
yellow : 33,
|
|
blue : 34,
|
|
magenta : 35,
|
|
cyan : 36,
|
|
white : 37
|
|
}[color.toLowerCase()];
|
|
|
|
if (!c) this.emit('error', new Error('Unknown color: ' + color));
|
|
this.write(encode('[' + c + 'm'));
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.background = function (color) {
|
|
if (typeof color === 'number') {
|
|
if (color < 0 || color >= 256) {
|
|
this.emit('error', new Error('Color out of range: ' + color));
|
|
}
|
|
this.write(encode('[48;5;' + color + 'm'));
|
|
}
|
|
else {
|
|
var c = {
|
|
black : 40,
|
|
red : 41,
|
|
green : 42,
|
|
yellow : 43,
|
|
blue : 44,
|
|
magenta : 45,
|
|
cyan : 46,
|
|
white : 47
|
|
}[color.toLowerCase()];
|
|
|
|
if (!c) this.emit('error', new Error('Unknown color: ' + color));
|
|
this.write(encode('[' + c + 'm'));
|
|
}
|
|
return this;
|
|
};
|
|
|
|
Charm.prototype.cursor = function (visible) {
|
|
this.write(encode(visible ? '[?25h' : '[?25l'));
|
|
return this;
|
|
};
|
|
|
|
var extractCodes = exports.extractCodes = function (buf) {
|
|
var codes = [];
|
|
var start = -1;
|
|
|
|
for (var i = 0; i < buf.length; i++) {
|
|
if (buf[i] === 27) {
|
|
if (start >= 0) codes.push(buf.slice(start, i));
|
|
start = i;
|
|
}
|
|
else if (start >= 0 && i === buf.length - 1) {
|
|
codes.push(buf.slice(start));
|
|
}
|
|
}
|
|
|
|
return codes;
|
|
}
|