* Start work on pausable ANSI display/etc.

This commit is contained in:
Bryan Ashby 2015-05-03 17:35:55 -06:00
parent 3f92a7949d
commit 5a00d219f8
4 changed files with 133 additions and 10 deletions

View File

@ -13,6 +13,11 @@ exports.ANSIEscapeParser = ANSIEscapeParser;
var CR = 0x0d;
var LF = 0x0a;
//
// Resources, Specs, etc.
//
// * https://github.com/M-griffin/EtherTerm/blob/master/ansiParser.cpp
function ANSIEscapeParser(options) {
var self = this;
@ -23,6 +28,10 @@ function ANSIEscapeParser(options) {
this.scrollBack = 0;
this.graphicRendition = {};
this.parseState = {
re : /(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g,
};
options = miscUtil.valueWithDefault(options, {
mciReplaceChar : '',
termHeight : 25,
@ -187,6 +196,60 @@ function ANSIEscapeParser(options) {
}
}
self.reset = function(buffer) {
self.parseState = {
// ignore anything past EOF marker, if any
buffer : buffer.split(String.fromCharCode(0x1a), 1)[0],
re : /(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g,
stop : false,
};
};
self.stop = function() {
self.parseState.stop = true;
};
self.parse = function() {
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
var pos;
var match;
var opCode;
var args;
var re = self.parseState.re;
var buffer = self.parseState.buffer;
self.parseState.stop = false;
do {
if(self.parseState.stop) {
return;
}
pos = re.lastIndex;
match = re.exec(buffer);
if(null !== match) {
if(match.index > pos) {
parseMCI(buffer.slice(pos, match.index));
}
opCode = match[2];
args = getArgArray(match[1].split(';'));
escape(opCode, args);
self.emit('chunk', match[0]);
}
} while(0 !== re.lastIndex);
if(pos < buffer.length) {
parseMCI(buffer.slice(pos));
}
self.emit('complete');
};
/*
self.parse = function(buffer, savedRe) {
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
// :TODO: move this to "constants" section @ top
@ -216,6 +279,8 @@ function ANSIEscapeParser(options) {
self.emit('chunk', match[0]);
}
} while(0 !== re.lastIndex);
if(pos < buffer.length) {
@ -224,6 +289,7 @@ function ANSIEscapeParser(options) {
self.emit('complete');
};
*/
function escape(opCode, args) {
var arg;

View File

@ -382,17 +382,17 @@ function defaultEofFromExtension(ext) {
// :TODO: display({ art : art, client : client, ...}, cb)
function display(options, cb) {
assert(
'undefined' !== typeof options &&
'undefined' !== typeof options.client &&
'undefined' !== typeof options.art,
'Missing required options');
assert(_.isObject(options));
assert(_.isObject(options.client));
assert(!_.isUndefined(options.art));
if(0 === options.art.length) {
cb(new Error('Empty art'));
return;
}
// pause = none/off | end | termHeight | [ "key1", "key2", ... ]
var cancelKeys = miscUtil.valueWithDefault(options.cancelKeys, []);
var pauseKeys = miscUtil.valueWithDefault(options.pauseKeys, []);
var pauseAtTermHeight = miscUtil.valueWithDefault(options.pauseAtTermHeight, false);
@ -426,7 +426,7 @@ function display(options, cb) {
var generatedId = 100;
var onCPR = function(pos) {
var cprListener = function(pos) {
if(mciPosQueue.length > 0) {
var forMapItem = mciPosQueue.shift();
mciMap[forMapItem].position = pos;
@ -438,7 +438,7 @@ function display(options, cb) {
};
function completed() {
options.client.removeListener('cursor position report', onCPR);
options.client.removeListener('cursor position report', cprListener);
parser.removeAllListeners(); // :TODO: Necessary???
if(iceColors) {
@ -448,7 +448,22 @@ function display(options, cb) {
cb(null, mciMap);
}
options.client.on('cursor position report', onCPR);
options.client.on('cursor position report', cprListener);
//options.pause = 'termHeight'; // :TODO: remove!!
var nextTermHeight = options.client.termHeight;
parser.on('row update', function rowUpdate(row) {
if(row >= nextTermHeight) {
if('termHeight' === options.pause) {
options.client.waitForKeyPress(function kp(k) {
parser.parse();
});
parser.stop();
}
nextTermHeight *= 2;
}
});
parser.on('mci', function mciEncountered(mciInfo) {
@ -463,6 +478,7 @@ function display(options, cb) {
*/
// :TODO: ensure generatedId's do not conflict with any |id|
// :TODO: Bug here - should only generate & increment ID's for the initial entry, not the "focus" version
var id = !_.isNumber(mciInfo.id) ? generatedId++ : mciInfo.id;
var mapKey = mciInfo.mci + id;
var mapEntry = mciMap[mapKey];
@ -522,5 +538,7 @@ function display(options, cb) {
options.client.term.write(ansi.blinkToBrightIntensity());
}
parser.parse(options.art);
parser.reset(options.art);
parser.parse();
//parser.parse(options.art);
}

View File

@ -217,7 +217,7 @@
}
},
"demoMain" : {
"art" : "demo_main",
"art" : "demo_selection_vm.ans",
"options" : { "cls" : true },
"form" : {
"0" : {
@ -227,6 +227,7 @@
"items" : [
"Single Line Text Editing Views",
"Spinner & Toggle Views",
"Art Display",
"Other"
],
// :TODO: justify not working??
@ -242,6 +243,10 @@
{
"value" : { "1" : 1 },
"action" : "@menu:demoSpinAndToggleView"
},
{
"value" : { "1" : 2 },
"action" : "@menu:demoArtDisplay"
}
]
}
@ -327,7 +332,41 @@
}
}
}
},
"demoArtDisplay" : {
"art" : "demo_selection_vm.ans",
"options" : { "cls" : true },
"form" : {
"0" : {
"VM1" : {
"mci" : {
"VM1" : {
"items" : [
"Defaults - DOS ANSI",
"Defaults - Amiga",
"Pause at Term Height"
],
// :TODO: justify not working??
"focusTextStyle" : "small i"
}
},
"submit" : {
"*" : [
{
"value" : { "1" : 0 },
"action" : "@menu:demoDefaultsDosAnsi"
}
]
}
}
}
}
},
"demoDefaultsDosAnsi" : {
"art" : "bw_bc.ans",
"options" : { "cls" : true }
}
/*
:TODO: conceptual simplified menus -- actions/etc. without forms