diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index d62eae22..619ded94 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -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; diff --git a/core/art.js b/core/art.js index fa5da30e..9a68f720 100644 --- a/core/art.js +++ b/core/art.js @@ -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); } \ No newline at end of file diff --git a/mods/art/demo_main.ans b/mods/art/demo_selection_vm.ans similarity index 100% rename from mods/art/demo_main.ans rename to mods/art/demo_selection_vm.ans diff --git a/mods/menu.json b/mods/menu.json index 917a24e9..55d74b34 100644 --- a/mods/menu.json +++ b/mods/menu.json @@ -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