refactor responses.

This commit is contained in:
Christopher Jeffrey 2013-07-18 01:03:47 -05:00
parent 7b7330ec5a
commit b46d1161c5
2 changed files with 374 additions and 143 deletions

View File

@ -264,18 +264,17 @@ Program.prototype.bindMouse = function() {
};
Program.prototype._bindMouse = function(s) {
var self = this;
// FROM: node/lib/readline.js
var ch
, key = {
name: undefined,
ctrl: false,
meta: false,
shift: false
}
var self = this
, key
, parts;
key = {
name: undefined,
ctrl: false,
meta: false,
shift: false
};
if (Buffer.isBuffer(s)) {
if (s[0] > 127 && s[1] === undefined) {
s[0] -= 128;
@ -447,6 +446,8 @@ Program.prototype._bindMouse = function(s) {
}
// DEC
// The xterm mouse documentation says there is a
// `<` prefix, the DECRQLP says there is no prefix.
if (parts = /^\x1b\[<(\d+;\d+;\d+;\d+)&w/.exec(s)) {
var parts = parts[1].split(';')
, b = +parts[0]
@ -525,16 +526,8 @@ Program.prototype.bindResponse = function() {
};
Program.prototype._bindResponse = function(s) {
var self = this;
// FROM: node/lib/readline.js
var ch
, key = {
name: undefined,
ctrl: false,
meta: false,
shift: false
}
var self = this
, out = {}
, parts;
if (Buffer.isBuffer(s)) {
@ -551,11 +544,14 @@ Program.prototype._bindResponse = function(s) {
// CSI > P s c
// Send Device Attributes (Secondary DA).
if (parts = /^\x1b\[(\?|>)(\d*(?:;\d*)*)c/.exec(s)) {
var primary = parts[1] === '?'
, parts = parts[2].split(';').map(function(ch) { return +ch || 0; })
, out = {};
parts = parts[2].split(';').map(function(ch) {
return +ch || 0;
});
if (primary) {
out.event = 'device-attributes';
out.code = 'DA';
if (parts[1] === '?') {
out.type = 'primary-attribute';
// VT100-style params:
if (parts[0] === 1 && parts[2] === 2) {
@ -645,9 +641,13 @@ Program.prototype._bindResponse = function(s) {
out.romCartridgeRegistrationNumber = parts[2];
}
return this.emit('response', {
deviceAttributes: out
});
// LEGACY
out.deviceAttributes = out;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
// CSI Ps n Device Status Report (DSR).
@ -667,26 +667,50 @@ Program.prototype._bindResponse = function(s) {
// CSI ? 5 3 n Locator available, if compiled-in, or
// CSI ? 5 0 n No Locator, if not.
if (parts = /^\x1b\[(\?)?(\d+)(?:;(\d+);(\d+);(\d+))?n/.exec(s)) {
out.event = 'device-status';
out.code = 'DSR';
if (!parts[1] && parts[2] === '0' && !parts[3]) {
return this.emit('response', {
deviceStatus: 'OK'
});
out.type = 'device-status';
out.status = 'OK';
// LEGACY
out.deviceStatus = out.status;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1] && (parts[2] === '10' || parts[2] === '11') && !parts[3]) {
return this.emit('response', {
printerStatus: parts[2] === '10'
? 'ready'
: 'not ready'
});
out.type = 'printer-status';
out.status = parts[2] === '10'
? 'ready'
: 'not ready';
// LEGACY
out.printerStatus = out.status;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1] && (parts[2] === '20' || parts[2] === '21') && !parts[3]) {
return this.emit('response', {
UDKStatus: parts[2] === '20'
? 'unlocked'
: 'locked'
});
out.type = 'udk-status';
out.status = parts[2] === '20'
? 'unlocked'
: 'locked';
// LEGACY
out.UDKStatus = out.status;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1]
@ -694,22 +718,43 @@ Program.prototype._bindResponse = function(s) {
&& parts[3] === '1'
&& parts[4] === '0'
&& parts[5] === '0') {
return this.emit('response', {
keyboardStatus: 'OK'
});
out.type = 'keyboard-status';
out.status = 'OK';
// LEGACY
out.keyboardStatus = out.status;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1] && (parts[2] === '53' || parts[2] === '50') && !parts[3]) {
return this.emit('response', {
locator: parts[2] === '53'
out.type = 'locator-status';
out.status = parts[2] === '53'
? 'available'
: 'unavailable'
});
: 'unavailable';
// LEGACY
out.locator = out.status;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
return this.emit('response', {
error: 'Unhandled: ' + JSON.stringify(parts)
});
out.type = 'error';
out.text = 'Unhandled: ' + JSON.stringify(parts);
// LEGACY
out.error = out.text;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
// CSI Ps n Device Status Report (DSR).
@ -721,13 +766,27 @@ Program.prototype._bindResponse = function(s) {
// Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI
// ? r ; c R (assumes page is zero).
if (parts = /^\x1b\[(\?)?(\d+);(\d+)R/.exec(s)) {
return this.emit('response', {
cursor: {
x: +parts[3],
y: +parts[2],
page: !parts[1] ? undefined : 0
}
});
out.event = 'device-status-report';
out.code = 'DSR';
out.type = 'cursor-status';
out.status = {
x: +parts[3],
y: +parts[2],
page: !parts[1] ? undefined : 0
};
out.x = out.status.x;
out.y = out.status.y;
out.page = out.status.page;
// LEGACY
out.cursor = out.status;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
// CSI Ps ; Ps ; Ps t
@ -747,53 +806,99 @@ Program.prototype._bindResponse = function(s) {
// Ps = 1 9 -> Report the size of the screen in characters.
// Result is CSI 9 ; height ; width t
if (parts = /^\x1b\[(\d+)(?:;(\d+);(\d+))?t/.exec(s)) {
out.event = 'window-manipulation';
out.code = '';
if ((parts[1] === '1' || parts[1] === '2') && !parts[2]) {
return this.emit('response', {
windowState: parts[1] === '1'
? 'non-iconified'
: 'iconified'
});
out.type = 'window-state';
out.state = parts[1] === '1'
? 'non-iconified'
: 'iconified'
// LEGACY
out.windowState = out.state;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1] === '3' && parts[2]) {
return this.emit('response', {
windowPosition: {
x: +parts[2],
y: +parts[3]
}
});
out.type = 'window-position';
out.position = {
x: +parts[2],
y: +parts[3]
};
// LEGACY
out.windowPosition = out.position;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1] === '4' && parts[2]) {
return this.emit('response', {
windowSizePixels: {
height: +parts[2],
width: +parts[3]
}
});
out.type = 'window-size-pixels';
out.size = {
height: +parts[2],
width: +parts[3]
};
// LEGACY
out.windowSizePixels = out.size;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1] === '8' && parts[2]) {
return this.emit('response', {
textAreaSizeCharacters: {
height: +parts[2],
width: +parts[3]
}
});
out.type = 'textarea-size';
out.size = {
height: +parts[2],
width: +parts[3]
};
// LEGACY
out.textAreaSizeCharacters = out.size;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1] === '9' && parts[2]) {
return this.emit('response', {
screenSizeCharacters: {
height: +parts[2],
width: +parts[3]
}
});
out.type = 'screen-size';
out.size = {
height: +parts[2],
width: +parts[3]
};
// LEGACY
out.screenSizeCharacters = out.size;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
return this.emit('response', {
error: 'Unhandled: ' + JSON.stringify(parts)
});
out.type = 'error';
out.text = 'Unhandled: ' + JSON.stringify(parts);
// LEGACY
out.error = out.text;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
// CSI Ps ; Ps ; Ps t
@ -806,21 +911,136 @@ Program.prototype._bindResponse = function(s) {
// Ps = 2 1 -> Report xterm window's title. Result is OSC l
// label ST
if (parts = /^\x1b\](l|L)([^\x07\x1b]*)(?:\x07|\x1b\\)/.exec(s)) {
out.type = 'window-manipulation';
out.code = '';
if (parts[1] === 'L') {
return this.emit('response', {
windowIconLabel: parts[2]
});
out.type = 'window-icon-label';
out.text = parts[2];
// LEGACY
out.windowIconLabel = out.text;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
if (parts[1] === 'l') {
return this.emit('response', {
windowTitle: parts[2]
});
out.type = 'window-title';
out.text = parts[2];
// LEGACY
out.windowTitle = out.text;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
return this.emit('response', {
error: 'Unhandled: ' + JSON.stringify(parts)
out.type = 'error';
out.text = 'Unhandled: ' + JSON.stringify(parts);
// LEGACY
out.error = out.text;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
// CSI Ps ' |
// Request Locator Position (DECRQLP).
// -> CSI Pe ; Pb ; Pr ; Pc ; Pp & w
// Parameters are [event;button;row;column;page].
// Valid values for the event:
// Pe = 0 -> locator unavailable - no other parameters sent.
// Pe = 1 -> request - xterm received a DECRQLP.
// Pe = 2 -> left button down.
// Pe = 3 -> left button up.
// Pe = 4 -> middle button down.
// Pe = 5 -> middle button up.
// Pe = 6 -> right button down.
// Pe = 7 -> right button up.
// Pe = 8 -> M4 button down.
// Pe = 9 -> M4 button up.
// Pe = 1 0 -> locator outside filter rectangle.
// ``button'' parameter is a bitmask indicating which buttons are
// pressed:
// Pb = 0 <- no buttons down.
// Pb & 1 <- right button down.
// Pb & 2 <- middle button down.
// Pb & 4 <- left button down.
// Pb & 8 <- M4 button down.
// ``row'' and ``column'' parameters are the coordinates of the
// locator position in the xterm window, encoded as ASCII deci-
// mal.
// The ``page'' parameter is not used by xterm, and will be omit-
// ted.
// NOTE:
// This is already implemented in the _bindMouse
// method, but it might make more sense here.
// The xterm mouse documentation says there is a
// `<` prefix, the DECRQLP says there is no prefix.
if (parts = /^\x1b\[(\d+(?:;\d+){4})&w/.exec(s)) {
parts = parts[1].split(';').map(function(ch) {
return +ch;
});
out.event = 'locator-position';
out.code = 'DECRQLP';
switch (parts[0]) {
case 0:
out.status = 'locator-unavailable';
break;
case 1:
out.status = 'request';
break;
case 2:
out.status = 'left-button-down';
break;
case 3:
out.status = 'left-button-up';
break;
case 4:
out.status = 'middle-button-down';
break;
case 5:
out.status = 'middle-button-up';
break;
case 6:
out.status = 'right-button-down';
break;
case 7:
out.status = 'right-button-up';
break;
case 8:
out.status = 'm4-button-down';
break;
case 9:
out.status = 'm4-button-up';
break;
case 10:
out.status = 'locator-outside';
break;
}
out.mask = parts[1];
out.row = parts[2];
out.col = parts[3];
out.page = parts[4];
// LEGACY
out.locatorPosition = out;
this.emit('response', out);
this.emit('response ' + out.event, out);
return;
}
};
@ -853,7 +1073,17 @@ Program.prototype.receive = function(text, callback) {
});
};
Program.prototype.receive_ = function(text, callback) {
Program.prototype.response_ = function(name, text, callback) {
if (arguments.length === 2) {
callback = text;
text = name;
name = null;
}
if (!callback) {
callback = function() {};
}
var listeners = (this._events && this._events['keypress']) || []
, bak = listeners.slice()
, self = this;
@ -864,16 +1094,14 @@ Program.prototype.receive_ = function(text, callback) {
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.once('response' + (name ? ' ' + name : ''), function(event) {
if (event.type === 'error') {
return callback(new Error(event.event + ': ' + event.text));
}
return callback(null, event);
});
self._bindResponse(data);
});
@ -882,6 +1110,29 @@ Program.prototype.receive_ = function(text, callback) {
});
};
Program.prototype.response = function(name, text, callback) {
if (arguments.length === 2) {
callback = text;
text = name;
name = null;
}
if (!callback) {
callback = function() {};
}
this.bindResponse();
this.once('response' + (name ? ' ' + name : ''), function(event) {
if (event.type === 'error') {
return callback(new Error(event.event + ': ' + event.text));
}
return callback(null, event);
});
return this.write(text);
};
Program.prototype.write =
Program.prototype.echo = function(text, attr) {
// if (this.output === process.stdout) {
@ -1286,7 +1537,7 @@ Program.prototype.setTitle = function(title) {
// OSC Ps ; Pt BEL
// Reset colors
Program.prototype.resetColors = function(param) {
if (this.tput && this.tput.Cr) {
if (this.tput.has('Cr')) {
return this.put.Cr(param);
}
return this.osc('112;' + param + '\x07');
@ -1296,7 +1547,7 @@ Program.prototype.resetColors = function(param) {
// OSC Ps ; Pt BEL
// Change dynamic colors
Program.prototype.dynamicColors = function(param) {
if (this.tput && this.tput.Cs) {
if (this.has('Cs')) {
return this.put.Cs(param);
}
return this.osc('12;' + param + '\x07');
@ -1306,7 +1557,7 @@ Program.prototype.dynamicColors = function(param) {
// OSC Ps ; Pt BEL
// Sel data
Program.prototype.selData = function(a, b) {
if (this.tput && this.tput.Ms) {
if (this.has('Ms')) {
return this.put.Ms(a, b);
}
return this.osc('52;' + a + ';' + b + '\x07');
@ -1833,20 +2084,13 @@ Program.prototype.setBackground = function(color, val) {
Program.prototype.dsr =
Program.prototype.deviceStatus = function(param, callback, dec) {
if (dec) {
return this.receive('\x1b[?' + (param || '0') + 'n', callback);
return this.response('device-status', '\x1b[?' + (param || '0') + 'n', callback);
}
return this.receive('\x1b[' + (param || '0') + 'n', callback);
return this.response('device-status', '\x1b[' + (param || '0') + 'n', callback);
};
Program.prototype.getCursor = function(callback) {
return this.deviceStatus('6', function(err, data) {
if (err) return callback(err);
data = data.slice(2, -1).split(';');
return callback(null, {
x: +data[1],
y: +data[0]
});
});
return this.deviceStatus('6', callback);
};
/**
@ -1994,13 +2238,7 @@ Program.prototype.HPositionRelative = function(param) {
// vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)
Program.prototype.da =
Program.prototype.sendDeviceAttributes = function(param, callback) {
return this.receive('\x1b[' + (param || '') + 'c', function(err, response) {
if (err) return callback(err);
if (response === '\x1b[?1;2c') {
return callback(null, 'VT100 with Advanced Video Option');
}
return callback(null, 'Unknown');
});
return this.response('device-attributes', '\x1b[' + (param || '') + 'c', callback);
};
// CSI Pm d
@ -2717,7 +2955,7 @@ Program.prototype.setCursorStyle = function(param) {
break;
}
// extended tput.Se for param=2
if (this.tput && this.tput.Ss) {
if (this.has('Ss')) {
return this.put.Ss(param);
}
return this.write('\x1b[' + (param || 1) + ' q');
@ -2811,18 +3049,11 @@ Program.prototype.manipulateWindow = function() {
? args.pop()
: function() {};
return this.receive('\x1b[' + args.join(';') + 't', callback);
return this.response('window-manipulation', '\x1b[' + args.join(';') + 't', callback);
};
Program.prototype.getWindowSize = function(callback) {
return this.manipulateWindow('18', function(err, data) {
if (err) return callback(err);
data = data.slice(2, -1).split(';');
return callback(null, {
height: +data[0],
width: +data[1]
});
});
return this.manipulateWindow('18', callback);
};
// CSI Pt; Pl; Pb; Pr; Ps$ t
@ -3025,15 +3256,15 @@ Program.prototype.decrqlp =
Program.prototype.req_mouse_pos =
Program.prototype.reqmp =
Program.prototype.requestLocatorPosition = function(params, callback) {
if (this.tput && this.tput.req_mouse_pos) {
// See also:
// get_mouse / getm / Gm
// mouse_info / minfo / Mi
// Correct for tput?
// See also:
// get_mouse / getm / Gm
// mouse_info / minfo / Mi
// Correct for tput?
if (this.has('req_mouse_pos')) {
var code = this.tput.req_mouse_pos.apply(this.tput, params);
return this.receive(code, callback);
return this.response('locator-position', code, callback);
}
return this.receive('\x1b[' + (param || '') + '\'|', callback);
return this.response('locator-position', '\x1b[' + (param || '') + '\'|', callback);
};
// CSI P m SP }

View File

@ -335,7 +335,7 @@ function Screen(options) {
process.on('uncaughtException', function(err) {
reset();
if (err) console.error(err.stack ? err.stack + '' : err + '');
return process.exit(0);
return process.exit(1);
});
process.on('exit', function() {