refactor. clean up. more colors.

This commit is contained in:
Christopher Jeffrey 2013-02-07 12:19:49 -06:00
parent 04f4145524
commit 1428b7137b

View File

@ -44,36 +44,38 @@ Program.prototype.term = function(is) {
}; };
Program.prototype.listen = function() { Program.prototype.listen = function() {
if (!this.input.isTTY || !this.output.isTTY) return; if (!this.input.isTTY || !this.output.isTTY) {
return;
}
var readline = require('readline') var readline = require('readline')
, self = this; , self = this;
this._raw = true; if (!this.input.isRaw) {
this.input.setRawMode(true);
this.input.setRawMode(true); this.input.on('keypress', function(ch, key) {
key = key || 0;
this.input.on('keypress', function(ch, key) { if (key.ctrl && key.name === 'c') {
key = key || 0; if (process.listeners('SIGINT').length) {
if (key.ctrl && key.name === 'c') { process.emit('SIGINT');
if (process.listeners('SIGINT').length) { }
process.emit('SIGINT'); if (self.listeners('SIGINT').length) {
self.emit('SIGINT');
}
return;
} }
if (self.listeners('SIGINT').length) { self.emit('keypress', ch, key);
self.emit('SIGINT'); });
}
return;
}
self.emit('keypress', ch, key);
});
this.input.on('data', function(data) { this.input.on('data', function(data) {
self.emit('data', data); self.emit('data', data);
}); });
readline.emitKeypressEvents(this.input); readline.emitKeypressEvents(this.input);
this.input.resume(); this.input.resume();
}
this.output.on('resize', function() { this.output.on('resize', function() {
self.cols = self.output.columns; self.cols = self.output.columns;
@ -81,18 +83,15 @@ Program.prototype.listen = function() {
self.emit('resize'); self.emit('resize');
}); });
// this.getCursor(function(err, cursor) {
// if (err) return;
// self.x = cursor.x;
// self.y = cursor.y;
// });
this.on('newListener', function fn(type) { this.on('newListener', function fn(type) {
if (type === 'mouse') { if (type === 'mouse') {
self.removeListener('newListener', fn); self.removeListener('newListener', fn);
self.bindMouse(); self.bindMouse();
} }
}); });
// self.bindMouse();
// self.bindResponse();
}; };
// XTerm mouse events // XTerm mouse events
@ -142,7 +141,7 @@ Program.prototype._bindMouse = function(s) {
} }
// XTerm / X10 // XTerm / X10
if (parts = /^\x1b\[M([\x00\u0020-\xffff]{3})/.exec(s)) { if (parts = /^\x1b\[M([\x00\u0020-\uffff]{3})/.exec(s)) {
var b = parts[1].charCodeAt(0) var b = parts[1].charCodeAt(0)
, x = parts[1].charCodeAt(1) , x = parts[1].charCodeAt(1)
, y = parts[1].charCodeAt(2) , y = parts[1].charCodeAt(2)
@ -352,6 +351,7 @@ Program.prototype._bindMouse = function(s) {
} }
}; };
// All possible responses from the terminal
Program.prototype.bindResponse = function() { Program.prototype.bindResponse = function() {
this.on('data', this._bindResponse.bind(this)); this.on('data', this._bindResponse.bind(this));
this.bindResponse = function() {}; this.bindResponse = function() {};
@ -435,33 +435,18 @@ Program.prototype._bindResponse = function(s) {
: 'unavailable' : 'unavailable'
}); });
} }
return this.emit('response', {
error: 'Unhandled: ' + JSON.stringify(parts)
});
} }
// CSI Ps n Device Status Report (DSR). // CSI Ps n Device Status Report (DSR).
// Ps = 5 -> Status Report. Result (``OK'') is
// CSI 0 n
// Ps = 6 -> Report Cursor Position (CPR) [row;column].
// Result is
// CSI r ; c R
// CSI ? Ps n
// Device Status Report (DSR, DEC-specific).
// Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI
// ? r ; c R (assumes page is zero).
// Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).
// or CSI ? 1 1 n (not ready).
// Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)
// or CSI ? 2 1 n (locked).
// Ps = 2 6 -> Report Keyboard status as
// CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
// The last two parameters apply to VT400 & up, and denote key-
// board ready and LK01 respectively.
// Ps = 5 3 -> Report Locator status as
// CSI ? 5 3 n Locator available, if compiled-in, or
// CSI ? 5 0 n No Locator, if not.
// Ps = 6 -> Report Cursor Position (CPR) [row;column]. // Ps = 6 -> Report Cursor Position (CPR) [row;column].
// Result is // Result is
// CSI r ; c R // CSI r ; c R
// CSI ? Ps n
// Device Status Report (DSR, DEC-specific).
// Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI // Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI
// ? r ; c R (assumes page is zero). // ? r ; c R (assumes page is zero).
if (parts = /^\x1b\[(\?)?(\d+);(\d+)R/.exec(s)) { if (parts = /^\x1b\[(\?)?(\d+);(\d+)R/.exec(s)) {
@ -534,6 +519,10 @@ Program.prototype._bindResponse = function(s) {
} }
}); });
} }
return this.emit('response', {
error: 'Unhandled: ' + JSON.stringify(parts)
});
} }
// CSI Ps ; Ps ; Ps t // CSI Ps ; Ps ; Ps t
@ -557,14 +546,19 @@ Program.prototype._bindResponse = function(s) {
windowTitle: parts[2] windowTitle: parts[2]
}); });
} }
return this.emit('response', {
error: 'Unhandled: ' + JSON.stringify(parts)
});
} }
}; };
Program.prototype.receive = function(text, callback) { Program.prototype.receive = function(text, callback) {
var listeners = this.listeners('keypress') var listeners = this.listeners('keypress')
, bak = listeners.slice(); , bak = listeners.slice()
, self = this;
if (!this._raw) { if (!this.input.isRaw) {
throw new Error('Input must be raw.'); throw new Error('Input must be raw.');
} }
@ -575,26 +569,53 @@ Program.prototype.receive = function(text, callback) {
text = null; text = null;
} }
this.input.once('data', function(data) { return process.nextTick(function() {
listeners.push.apply(listeners, bak); self.input.once('data', function(data) {
if (typeof data !== 'string') { listeners.push.apply(listeners, bak);
data = data.toString('utf8'); if (typeof data !== 'string') {
} data = data.toString('utf8');
return callback(null, data); }
}); return callback(null, data);
});
if (text) this.write(text); if (text) self.write(text);
});
};
Program.prototype.receive_ = function(text, callback) {
var listeners = this.listeners('keypress')
, bak = listeners.slice()
, self = this;
if (!this.input.isRaw) {
throw new Error('Input must be raw.');
}
listeners.length = 0;
if (!callback) {
callback = text;
text = null;
}
return process.nextTick(function() {
self.input.once('data', function(data) {
listeners.push.apply(listeners, bak);
self.once('response', function(event) {
return callback(null, event[Object.keys(event)[0]]);
});
self._bindResponse(data);
});
if (text) self.write(text);
});
}; };
Program.prototype.write = Program.prototype.write =
Program.prototype.echo = function(text, attr) { Program.prototype.echo = function(text, attr) {
if (attr) { return attr
this.attr(attr, true); ? this.output.write(this.text(text, attr))
this.write(text); : this.output.write(text);
this.attr(attr, false);
return;
}
return this.output.write(text);
}; };
Program.prototype.setx = function(x) { Program.prototype.setx = function(x) {
@ -1041,6 +1062,25 @@ Program.prototype.eraseInLine = function(param) {
Program.prototype.sgr = Program.prototype.sgr =
Program.prototype.attr = Program.prototype.attr =
Program.prototype.charAttributes = function(param, val) { Program.prototype.charAttributes = function(param, val) {
return this.write(this._attr(param, val));
};
Program.prototype.text = function(text, attr) {
return this._attr(attr, true) + text + this._attr(attr, false);
};
Program.prototype._attr = function(param, val) {
var self = this
, parts = param.split(/\s*[,;]\s*/)
, out = '';
if (parts.length > 1) {
parts.forEach(function(part) {
out += self._attr(part, val);
});
return out;
}
if (param.indexOf('no ') === 0) { if (param.indexOf('no ') === 0) {
param = param.substring(3); param = param.substring(3);
val = false; val = false;
@ -1048,118 +1088,233 @@ Program.prototype.charAttributes = function(param, val) {
param = param.substring(1); param = param.substring(1);
val = false; val = false;
} }
switch (param) { switch (param) {
// attributes
case 'normal': case 'normal':
return this.write('\x1b[m'); return '\x1b[m';
case 'bold': case 'bold':
return val === false return val === false
? this.write('\x1b[22m') ? '\x1b[22m'
: this.write('\x1b[1m'); : '\x1b[1m';
case 'underlined': case 'underlined':
return val === false return val === false
? this.write('\x1b[24m') ? '\x1b[24m'
: this.write('\x1b[4m'); : '\x1b[4m';
case 'blink': case 'blink':
return val === false return val === false
? this.write('\x1b[25m') ? '\x1b[25m'
: this.write('\x1b[5m'); : '\x1b[5m';
case 'inverse': case 'inverse':
return val === false return val === false
? this.write('\x1b[27m') ? '\x1b[27m'
: this.write('\x1b[7m'); : '\x1b[7m';
break; break;
case 'invisible': case 'invisible':
return val === false return val === false
? this.write('\x1b[28m') ? '\x1b[28m'
: this.write('\x1b[8m'); : '\x1b[8m';
case 'invisible': case 'invisible':
return val === false return val === false
? this.write('\x1b[28m') ? '\x1b[28m'
: this.write('\x1b[8m'); : '\x1b[8m';
// 8-color foreground
case 'black fg': case 'black fg':
return val === false return val === false
? this.write('\x1b[39m') ? '\x1b[39m'
: this.write('\x1b[30m'); : '\x1b[30m';
case 'red fg': case 'red fg':
return val === false return val === false
? this.write('\x1b[39m') ? '\x1b[39m'
: this.write('\x1b[31m'); : '\x1b[31m';
case 'green fg': case 'green fg':
return val === false return val === false
? this.write('\x1b[39m') ? '\x1b[39m'
: this.write('\x1b[32m'); : '\x1b[32m';
case 'yellow fg': case 'yellow fg':
return val === false return val === false
? this.write('\x1b[39m') ? '\x1b[39m'
: this.write('\x1b[33m'); : '\x1b[33m';
case 'blue fg': case 'blue fg':
return val === false return val === false
? this.write('\x1b[39m') ? '\x1b[39m'
: this.write('\x1b[34m'); : '\x1b[34m';
case 'magenta fg': case 'magenta fg':
return val === false return val === false
? this.write('\x1b[39m') ? '\x1b[39m'
: this.write('\x1b[35m'); : '\x1b[35m';
case 'cyan fg': case 'cyan fg':
return val === false return val === false
? this.write('\x1b[39m') ? '\x1b[39m'
: this.write('\x1b[36m'); : '\x1b[36m';
case 'white fg': case 'white fg':
return val === false return val === false
? this.write('\x1b[39m') ? '\x1b[39m'
: this.write('\x1b[37m'); : '\x1b[37m';
case 'default fg': case 'default fg':
return this.write('\x1b[39m'); return '\x1b[39m';
// 8-color background
case 'black bg': case 'black bg':
return val === false return val === false
? this.write('\x1b[49m') ? '\x1b[49m'
: this.write('\x1b[40m'); : '\x1b[40m';
case 'red bg': case 'red bg':
return val === false return val === false
? this.write('\x1b[49m') ? '\x1b[49m'
: this.write('\x1b[41m'); : '\x1b[41m';
case 'green bg': case 'green bg':
return val === false return val === false
? this.write('\x1b[49m') ? '\x1b[49m'
: this.write('\x1b[42m'); : '\x1b[42m';
case 'yellow bg': case 'yellow bg':
return val === false return val === false
? this.write('\x1b[49m') ? '\x1b[49m'
: this.write('\x1b[43m'); : '\x1b[43m';
case 'blue bg': case 'blue bg':
return val === false return val === false
? this.write('\x1b[49m') ? '\x1b[49m'
: this.write('\x1b[44m'); : '\x1b[44m';
case 'magenta bg': case 'magenta bg':
return val === false return val === false
? this.write('\x1b[49m') ? '\x1b[49m'
: this.write('\x1b[45m'); : '\x1b[45m';
case 'cyan bg': case 'cyan bg':
return val === false return val === false
? this.write('\x1b[49m') ? '\x1b[49m'
: this.write('\x1b[46m'); : '\x1b[46m';
case 'white bg': case 'white bg':
return val === false return val === false
? this.write('\x1b[49m') ? '\x1b[49m'
: this.write('\x1b[47m'); : '\x1b[47m';
case 'default bg': case 'default bg':
return this.write('\x1b[49m'); return '\x1b[49m';
// 16-color foreground
case 'light black fg':
return val === false
? '\x1b[39m'
: '\x1b[90m';
case 'light red fg':
return val === false
? '\x1b[39m'
: '\x1b[91m';
case 'light green fg':
return val === false
? '\x1b[39m'
: '\x1b[92m';
case 'light yellow fg':
return val === false
? '\x1b[39m'
: '\x1b[93m';
case 'light blue fg':
return val === false
? '\x1b[39m'
: '\x1b[94m';
case 'light magenta fg':
return val === false
? '\x1b[39m'
: '\x1b[95m';
case 'light cyan fg':
return val === false
? '\x1b[39m'
: '\x1b[96m';
case 'light white fg':
return val === false
? '\x1b[39m'
: '\x1b[97m';
// 16-color background
case 'light black bg':
return val === false
? '\x1b[49m'
: '\x1b[100m';
case 'light red bg':
return val === false
? '\x1b[49m'
: '\x1b[101m';
case 'light green bg':
return val === false
? '\x1b[49m'
: '\x1b[102m';
case 'light yellow bg':
return val === false
? '\x1b[49m'
: '\x1b[103m';
case 'light blue bg':
return val === false
? '\x1b[49m'
: '\x1b[104m';
case 'light magenta bg':
return val === false
? '\x1b[49m'
: '\x1b[105m';
case 'light cyan bg':
return val === false
? '\x1b[49m'
: '\x1b[106m';
case 'light white bg':
return val === false
? '\x1b[49m'
: '\x1b[107m';
// non-16-color rxvt default fg and bg
case 'default fg bg':
if (this.term('rxvt')) {
return '\x1b[100m';
}
return this._attr('default fg') + this._attr('default bg');
default: default:
return this.write('\x1b[' + param + 'm'); // 256-color fg and bg
var m = /^(\d+) (fg|bg)$/.exec(param);
if (m) {
var color = +m[1] - 1;
if (val === false) {
return this._attr('default ' + m[2]);
}
if (color < 16) {
if (m[2] === 'fg') {
if (color < 8) {
color += 30;
} else if (color < 16) {
color += 90;
}
} else if (m[2] === 'bg') {
if (color < 8) {
color += 40;
} else if (color < 16) {
color += 100;
}
}
return '\x1b[' + color + 'm';
}
if (m[2] === 'fg') {
return '\x1b[38;5;' + color + 'm';
}
if (m[2] === 'bg') {
return '\x1b[48;5;' + color + 'm';
}
}
return '\x1b[' + param + 'm';
} }
}; };
Program.prototype.fg = Program.prototype.fg =
Program.prototype.setForeground = function(color, val) { Program.prototype.setForeground = function(color, val) {
return this.attr(color + ' fg', val); color = color.split(/\s*[,;]\s*/).join(' fg, ') + ' fg';
return this.attr(color, val);
}; };
Program.prototype.bg = Program.prototype.bg =
Program.prototype.setBackground = function(color, val) { Program.prototype.setBackground = function(color, val) {
return this.attr(color + ' bg', val); color = color.split(/\s*[,;]\s*/).join(' bg, ') + ' bg';
return this.attr(color, val);
}; };
// CSI Ps n Device Status Report (DSR). // CSI Ps n Device Status Report (DSR).