diff --git a/lib/tput.js b/lib/tput.js index 1614c9e..e3aad48 100644 --- a/lib/tput.js +++ b/lib/tput.js @@ -81,6 +81,11 @@ Tput.prototype.term = function(is) { return this.terminal.indexOf(is) === 0; }; +Tput.prototype._debug = function() { + if (!this.debug) return; + return console.log.apply(console, arguments); +}; + /** * Fallback */ @@ -513,9 +518,7 @@ Tput.prototype.compile = function(info) { this.detectFeatures(info); - if (this.debug) { - console.log(info); - } + this._debug(info); info.all = {}; info.methods = {}; @@ -523,9 +526,7 @@ Tput.prototype.compile = function(info) { ['bools', 'numbers', 'strings'].forEach(function(type) { Object.keys(info[type]).forEach(function(key) { info.all[key] = info[type][key]; - if (self.debug) { - console.log('Compiling %s: %s', key, JSON.stringify(info.all[key])); - } + self._debug('Compiling %s: %s', key, JSON.stringify(info.all[key])); info.methods[key] = self._compile(info.all[key], key, info); }); }); @@ -593,42 +594,40 @@ Tput.prototype.inject = function(info) { // See: // ~/ncurses/ncurses/tinfo/lib_tparm.c // ~/ncurses/ncurses/tinfo/comp_scan.c -Tput.prototype._compile = function(val, key, info) { - var self = this - , _val = val - , _key = key; - - switch (typeof val) { +Tput.prototype._compile = function(str, key, info) { + switch (typeof str) { case 'boolean': - return val; + return str; case 'number': - return val; + return str; case 'string': break; default: return noop; } - if (!val) { + if (!str) { return noop; } // See: // ~/ncurses/progs/tput.c - tput() - L149 // ~/ncurses/progs/tset.c - set_init() - L992 - if (_key === 'init_file' || _key === 'reset_file') { + if (key === 'init_file' || key === 'reset_file') { try { // NOTE: Should we consider this code and parse it instead? - val = fs.readFileSync(_val, 'utf8'); - return function() { return val; }; + str = fs.readFileSync(str, 'utf8'); + return function() { return str; }; } catch (e) { return noop; } } - var header = 'var v, dyn = {}, stat = {}, stack = [], out = "";' + var tkey = info.name + '.' + key + , header = 'var v, dyn = {}, stat = {}, stack = [], out = "";' , footer = ';return out;' , code = header + , val = str , buff = '' , cap; @@ -654,7 +653,6 @@ Tput.prototype._compile = function(val, key, info) { if (!cap) return; val = val.substring(cap[0].length); ch = op = i = v = cap[1]; - // with old print(): clear(); if (!no) clear(); return cap; } @@ -680,12 +678,7 @@ Tput.prototype._compile = function(val, key, info) { } function print(c) { - // with old print(): echo('"' + c + '"'); - // NOTE: Instead of doing this, could simply change - // the strings that are passed to print() below. - buff += JSON.parse('"' - + c.replace(/\\x([0-9a-fA-F]{2})/g, '\\u00$1') - + '"'); + buff += c; } function push(c) { @@ -698,67 +691,26 @@ Tput.prototype._compile = function(val, key, info) { while (val) { // Ignore newlines - // if (read(/^\n /, true)) { - // continue; - // } - - // '\e' -> ^[ - // if (read(/^\\e/i, true)) { - // print('\\x1b'); - // continue; - // } + if (read(/^\n /, true)) { + continue; + } // '^A' -> ^A if (read(/^\^(.)/i, true)) { if (!(ch >= ' ' && ch <= '~')) { - ; + this._debug('%s: bad caret char.', tkey); + // NOTE: ncurses appears to simply + // continue in this situation, but + // I could be wrong. + print(cap[0]); + continue; } if (ch === '?') { - ch = '\\x7f'; + ch = '\x7f'; } else { ch = ch.charCodeAt(0) & 31; if (ch === 0) ch = 128; - ch = ch.toString(16); - if (ch.length < 2) ch = '0' + ch; - ch = '\\x' + ch; - } - print(ch); - continue; - - switch (ch) { - case '@': - ch = '\\200'; - break; - case '[': - ch = '\\x1b'; - break; - case '\\': - ch = '\\x1c'; - break; - case ']': - ch = '\\x1d'; - break; - case '^': - ch = '\\x1e'; - break; - case '_': - ch = '\\x1f'; - break; - case '?': - ch = '\\x7f'; - break; - default: - ch = ch.toUpperCase(); - if (ch >= 'A' && ch <= 'Z') { - ch = ch.charCodeAt(0) - 64; - ch = ch.toString(16); - if (ch.length < 2) ch = '0' + ch; - ch = '\\x' + ch; - } else { - //ch = cap[0]; - ch = ch; - } - break; + ch = String.fromCharCode(ch); } print(ch); continue; @@ -766,7 +718,7 @@ Tput.prototype._compile = function(val, key, info) { // 3 octal digits -> character if (read(/^\\([0-7]{3})/, true)) { - print('\\' + ch); + print(String.fromCharCode(parseInt(ch, 8))); continue; } @@ -778,25 +730,25 @@ Tput.prototype._compile = function(val, key, info) { switch (ch) { case 'e': case 'E': - ch = '\\x1b'; + ch = '\x1b'; break; case 'n': - ch = '\\n'; + ch = '\n'; break; case 'l': - ch = '\\x85'; + ch = '\x85'; break; case 'r': - ch = '\\r'; + ch = '\r'; break; case 't': - ch = '\\t'; + ch = '\t'; break; case 'b': - ch = '\\x08'; + ch = '\x08'; break; case 'f': - ch = '\\x0c'; + ch = '\x0c'; break; case 's': ch = ' '; @@ -805,7 +757,7 @@ Tput.prototype._compile = function(val, key, info) { ch = '^'; break; case '\\': - ch = '\\\\'; + ch = '\\'; break; case ',': ch = ','; @@ -814,14 +766,14 @@ Tput.prototype._compile = function(val, key, info) { ch = ':'; break; case '0': - ch = '\\200'; + ch = '\200'; break; case 'a': - ch = '\\x07'; + ch = '\x07'; break; default: - //ch = cap[0]; - ch = ch; + this._debug('%s: bad backslash char.', tkey); + ch = cap[0]; break; } print(ch); @@ -855,40 +807,14 @@ Tput.prototype._compile = function(val, key, info) { if (this.printf || cap[1] || cap[2] || ~'oxX'.indexOf(cap[3])) { echo('sprintf("'+ cap[0].replace(':-', '-') + '", stack.pop())'); } else if (cap[3] === 'c') { - echo('(v = stack.pop() || "",' - + ' isFinite(v) ? String.fromCharCode(v) : v[0])'); + echo('(v = stack.pop(), isFinite(v) ' + + '? String.fromCharCode(v || 0200) : "")'); } else { echo('stack.pop()'); } continue; } - // %c print pop() like %c in printf - // Example from screen terminfo: - // S0: "\u001b(%p1%c" - // if (read(/^%c/)) { - // if (this.printf) { - // echo('sprintf("%c", stack.pop())'); - // } else { - // echo('(v = stack.pop() || "",' - // + ' isFinite(v) ? String.fromCharCode(v) : v[0])'); - // } - // continue; - // } - - // %d print pop() - // "Print (e.g., "%d") is a special case." - // if (read(/^%d/)) { - // echo('stack.pop()'); - // continue; - // } - - // %s print pop() like %s in printf - // if (read(/^%s/)) { - // echo('stack.pop()'); - // continue; - // } - // %p[1-9] // push i'th parameter if (read(/^%p([1-9])/)) { @@ -930,8 +856,10 @@ Tput.prototype._compile = function(val, key, info) { } // %'c' char constant c - if (read(/^%'(\w)'/)) { - expr('(stack.push(v = "' + ch + '"), v)'); + // NOTE: These are stored as c chars, exemplified by: + // cursor_address: "\u001b=%p1%' '%+%c%p2%' '%+%c" + if (read(/^%'(.)'/)) { + expr('(stack.push(v = ' + ch.charCodeAt(0) + '), v)'); continue; } @@ -944,7 +872,7 @@ Tput.prototype._compile = function(val, key, info) { // %l push strlen(pop) if (read(/^%l/)) { - expr('(stack.push(v = stack.pop().length), v)'); + expr('(stack.push(v = (stack.pop() || "").length || 0), v)'); continue; } @@ -957,14 +885,13 @@ Tput.prototype._compile = function(val, key, info) { if (read(/^%([+\-*\/m&|\^=><])/)) { if (op === '=') op = '==='; else if (op === 'm') op = '%'; - expr('(stack.push(v = (stack.pop() ' + op + ' stack.pop())), v)'); + expr('(stack.push(v = (stack.pop() ' + op + ' stack.pop()) || 0), v)'); continue; } // %A, %O // logical AND and OR operations (for conditionals) if (read(/^%([AO])/)) { - // stmt(op === 'A' ? ' && ' : ' || '); // Are we supposed to store the result on the stack? expr('(stack.push(v = (stack.pop() ' + (op === 'A' ? '&&' : '||') @@ -981,11 +908,12 @@ Tput.prototype._compile = function(val, key, info) { // %i add 1 to first two parameters (for ANSI terminals) if (read(/^%i/)) { - // expr('(params[0] = params[0] || 0, params[1] = params[1] || 0)'); // Are these supposed to go on the stack in certain situations? // ncurses doesn't seem to put them on the stack, but xterm.user6 // seems to assume they're on the stack for some reason. Could // just be a bad terminfo string. + // user6: "\u001b[%i%d;%dR" - possibly a termcap-style string. + // expr('(params[0] |= 0, params[1] |= 0, params[0]++, params[1]++)'); expr('(params[0]++, params[1]++)'); continue; } @@ -1041,66 +969,11 @@ Tput.prototype._compile = function(val, key, info) { } if (read(/^%;/)) { - fi = then = els = end = null; + end = null; stmt('}'); continue; } - // Binary operations are in postfix form with the operands in the usual - // order. That is, to get x-5 one would use "%gx%{5}%-". %P and %g vari‐ - // ables are persistent across escape-string evaluations. - - // Consider the HP2645, which, to get to row 3 and column 12, needs to be - // sent \E&a12c03Y padded for 6 milliseconds. Note that the order of the - // rows and columns is inverted here, and that the row and column are - // printed as two digits. Thus its cup capability is - // “cup=6\E&%p2%2dc%p1%2dY”. - - // The Microterm ACT-IV needs the current row and column sent - // preceded by a ^T, with the row and column simply encoded in - // binary, “cup=^T%p1%c%p2%c”. Terminals which use “%c” need to be able - // to backspace the cursor (cub1), and to move the cursor up one line - // on the screen (cuu1). This is necessary because it is not always safe - // to transmit \n ^D and \r, as the system may change or discard them. - // (The library routines dealing with terminfo set tty modes so that tabs - // are never expanded, so \t is safe to send. This turns out to be - // essential for the Ann Arbor 4080.) - - // A final example is the LSI ADM-3a, which uses row and column offset - // by a blank character, thus “cup=\E=%p1%' '%+%c%p2%' '%+%c”. After - // sending `\E=', this pushes the first parameter, pushes the ASCII value - // for a space (32), adds them (pushing the sum on the stack in place of - // the two previous values) and outputs that value as a character. - // Then the same is done for the second parameter. More complex - // arithmetic is possible using the stack. - - // $ man termcap - // Disabled for now since we can convert termcap strings to terminfo. - if (0 && info.termcap) { - // %r Single parameter capability - if (read(/^%r/)) { - continue; - } - - // %+ Add value of next character to this parameter and do binary output - // if (read(/^%\+/)) { - // echo('stack.pop()'); - // continue; - // } - - // %2 Do ASCII output of this parameter with a field with of 2 - if (read(/^%2/)) { - echo('stack.pop()'); - continue; - } - - // %d Do ASCII output of this parameter with a field with of 3 - // if (read(/^%d/)) { - // echo('stack.pop()'); - // continue; - // } - } - buff += val[0]; val = val.substring(1); } @@ -1144,7 +1017,7 @@ Tput.prototype._compile = function(val, key, info) { // Terminfos `wyse350-vb`, and `wy350-w` // seem to have a few broken strings. - if (_val === '\u001b%?') { + if (str === '\u001b%?') { code = 'return "\\x1b";'; } @@ -1165,8 +1038,8 @@ Tput.prototype._compile = function(val, key, info) { : new Function('params', code); } catch (e) { console.error(''); - console.error('Error on `' + _key + '` (' + info.name + '):'); - console.error(JSON.stringify(_val)); + console.error('Error on %s:', tkey); + console.error(JSON.stringify(str)); console.error(''); console.error(code.replace(/(,|;)/g, '$1\n')); e.stack = e.stack.replace(/\x1b/g, '\\x1b'); @@ -1268,12 +1141,11 @@ Tput.prototype.readTermcap = function(term) { root.inherits = root.inherits || []; root.inherits.push(term.strings.tc); - if (self.debug) { - var names = terms[term.strings.tc] - ? terms[term.strings.tc].names - : [term.strings.tc]; - console.log(term.names.join('/') + ' inherits from ' + names.join('/')); - } + var names = terms[term.strings.tc] + ? terms[term.strings.tc].names + : [term.strings.tc]; + + self._debug(term.names.join('/') + ' inherits from ' + names.join('/')); var inherit = tc(terms[term.strings.tc]); if (inherit) { @@ -1417,9 +1289,7 @@ Tput.prototype.translateTermcap = function(info) { if (!info) return; - if (this.debug) { - console.log(info); - } + this._debug(info); ['name', 'names', 'desc', 'file', 'termcap'].forEach(function(key) { out[key] = info[key]; @@ -1450,7 +1320,7 @@ Tput.prototype.translateTermcap = function(info) { out[key] = {}; Object.keys(info[key]).forEach(function(cap) { if (key === 'strings') { - info.strings[cap] = captoinfo(cap, info.strings[cap], 1, self.debug); + info.strings[cap] = self._captoinfo(cap, info.strings[cap], 1); } if (talias[cap]) { out[key][talias[cap][0]] = info[key][cap]; @@ -1489,7 +1359,9 @@ Tput.prototype.injectTermcap = function(term) { * pad translations if >=0 */ -function captoinfo(cap, s, parameterized, debug) { +Tput.prototype._captoinfo = function(cap, s, parameterized) { + var self = this; + var capstart; if (parameterized == null) { @@ -1509,8 +1381,9 @@ function captoinfo(cap, s, parameterized, debug) { , out = ''; function warn() { - if (!debug) return; - console.log.apply(console, arguments); + var args = Array.prototype.slice.call(arguments); + args[0] = 'captoinfo: ' + (args[0] || ''); + return self._debug.apply(self, args); } function isdigit(ch) { @@ -1941,15 +1814,21 @@ function captoinfo(cap, s, parameterized, debug) { } return out; -} +}; /** * Compile All Terminfo */ -Tput.prototype.compileAll = function(start) { - var self = this - , all = {}; +Tput.prototype.getAll = function() { + var dir = this._prefix() + , list = asort(fs.readdirSync(dir)) + , infos = []; + + list.forEach(function(letter) { + var terms = asort(fs.readdirSync(path.resolve(dir, letter))); + infos.push.apply(infos, terms); + }); function asort(obj) { return obj.sort(function(a, b) { @@ -1959,19 +1838,20 @@ Tput.prototype.compileAll = function(start) { }); } - var dir = this._prefix() - , list = asort(fs.readdirSync(dir)); + return infos; +}; - list.forEach(function(letter) { - var infos = asort(fs.readdirSync(path.resolve(dir, letter))); - infos.forEach(function(name) { - if (start && name !== start) { - return; - } else { - start = null; - } - all[name] = self.compileTerminfo(name); - }); +Tput.prototype.compileAll = function(start) { + var self = this + , all = {}; + + this.getAll().forEach(function(name) { + if (start && name !== start) { + return; + } else { + start = null; + } + all[name] = self.compileTerminfo(name); }); return all; @@ -2150,12 +2030,14 @@ function sprintf(src) { return src.replace(rule, function(_, flag, width, type) { var flags = (flag || '').split('') - , param = params[i++] || '' + , param = params[i] != null ? params[i] : '' , initial = param , width = +width , opt = {} , pre = ''; + i++; + switch (type) { case 'd': // signed int param = (+param).toString(10); @@ -2172,11 +2054,9 @@ function sprintf(src) { case 's': // string break; case 'c': // char - // Should return here? param = isFinite(param) - ? String.fromCharCode(param) - : param[0]; - //: ''; + ? String.fromCharCode(param || 0200) + : ''; break; } diff --git a/test/terminfo b/test/terminfo index 8f2271b..7540c8f 100644 --- a/test/terminfo +++ b/test/terminfo @@ -595,7 +595,7 @@ return "\n"; Compiling scroll_reverse: "\u001bM" return "\x1bM"; Compiling set_attributes: "%?%p9%t\u001b(0%e\u001b(B%;\u001b[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m" -var v, stack = [], out = "";;if ((stack.push(v = params[8]), v)) {out += "\x1b(0"} else {out += "\x1b(B"}out += "\x1b[0";if ((stack.push(v = params[5]), v)) {out += ";1"};if ((stack.push(v = params[1]), v)) {out += ";4"};if ((stack.push(v = params[0]), v),(stack.push(v = params[2]), v),(stack.push(v = (stack.pop() | stack.pop())), v)) {out += ";7"};if ((stack.push(v = params[3]), v)) {out += ";5"};if ((stack.push(v = params[6]), v)) {out += ";8"}out += "m";return out; +var v, stack = [], out = "";;if ((stack.push(v = params[8]), v)) {out += "\x1b(0"} else {out += "\x1b(B"}out += "\x1b[0";if ((stack.push(v = params[5]), v)) {out += ";1"};if ((stack.push(v = params[1]), v)) {out += ";4"};if ((stack.push(v = params[0]), v),(stack.push(v = params[2]), v),(stack.push(v = (stack.pop() | stack.pop()) || 0), v)) {out += ";7"};if ((stack.push(v = params[3]), v)) {out += ";5"};if ((stack.push(v = params[6]), v)) {out += ";8"}out += "m";return out; Compiling set_tab: "\u001bH" return "\x1bH"; Compiling tab: "\t" @@ -749,9 +749,9 @@ return "\x1b[c"; Compiling orig_pair: "\u001b[39;49m" return "\x1b[39;49m"; Compiling set_foreground: "\u001b[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m" -var v, stack = [], out = "";out += "\x1b[3";if ((stack.push(v = params[0]), v),(stack.push(v = 1), v),(stack.push(v = (stack.pop() === stack.pop())), v)) {out += "4"} else if ((stack.push(v = params[0]), v),(stack.push(v = 3), v),(stack.push(v = (stack.pop() === stack.pop())), v)) {out += "6"} else if ((stack.push(v = params[0]), v),(stack.push(v = 4), v),(stack.push(v = (stack.pop() === stack.pop())), v)) {out += "1"} else if ((stack.push(v = params[0]), v),(stack.push(v = 6), v),(stack.push(v = (stack.pop() === stack.pop())), v)) {out += "3"} else {out += (params[0] || "")}out += "m";return out; +var v, stack = [], out = "";out += "\x1b[3";if ((stack.push(v = params[0]), v),(stack.push(v = 1), v),(stack.push(v = (stack.pop() === stack.pop()) || 0), v)) {out += "4"} else if ((stack.push(v = params[0]), v),(stack.push(v = 3), v),(stack.push(v = (stack.pop() === stack.pop()) || 0), v)) {out += "6"} else if ((stack.push(v = params[0]), v),(stack.push(v = 4), v),(stack.push(v = (stack.pop() === stack.pop()) || 0), v)) {out += "1"} else if ((stack.push(v = params[0]), v),(stack.push(v = 6), v),(stack.push(v = (stack.pop() === stack.pop()) || 0), v)) {out += "3"} else {out += (params[0] || "")}out += "m";return out; Compiling set_background: "\u001b[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m" -var v, stack = [], out = "";out += "\x1b[4";if ((stack.push(v = params[0]), v),(stack.push(v = 1), v),(stack.push(v = (stack.pop() === stack.pop())), v)) {out += "4"} else if ((stack.push(v = params[0]), v),(stack.push(v = 3), v),(stack.push(v = (stack.pop() === stack.pop())), v)) {out += "6"} else if ((stack.push(v = params[0]), v),(stack.push(v = 4), v),(stack.push(v = (stack.pop() === stack.pop())), v)) {out += "1"} else if ((stack.push(v = params[0]), v),(stack.push(v = 6), v),(stack.push(v = (stack.pop() === stack.pop())), v)) {out += "3"} else {out += (params[0] || "")}out += "m";return out; +var v, stack = [], out = "";out += "\x1b[4";if ((stack.push(v = params[0]), v),(stack.push(v = 1), v),(stack.push(v = (stack.pop() === stack.pop()) || 0), v)) {out += "4"} else if ((stack.push(v = params[0]), v),(stack.push(v = 3), v),(stack.push(v = (stack.pop() === stack.pop()) || 0), v)) {out += "6"} else if ((stack.push(v = params[0]), v),(stack.push(v = 4), v),(stack.push(v = (stack.pop() === stack.pop()) || 0), v)) {out += "1"} else if ((stack.push(v = params[0]), v),(stack.push(v = 6), v),(stack.push(v = (stack.pop() === stack.pop()) || 0), v)) {out += "3"} else {out += (params[0] || "")}out += "m";return out; Compiling key_mouse: "\u001b[M" return "\x1b[M"; Compiling set_a_foreground: "\u001b[3%p1%dm" diff --git a/test/tput.js b/test/tput.js index 0fdaae0..f2b4e8d 100644 --- a/test/tput.js +++ b/test/tput.js @@ -81,7 +81,7 @@ function parseArg() { var argv = parseArg(); var tput = blessed.tput({ - term: argv[0] && argv[0] !== 'all' ? argv[0] : 'xterm', + term: argv[0] && argv[0] !== 'all' && argv[0] !== 'rand' ? argv[0] : 'xterm', extended: true, debug: true, termcap: argv.termcap, @@ -122,6 +122,14 @@ if (!argv[0] || argv[0] === 'all') { return; } +if (argv[0] === 'rand') { + var terms = tput.getAll(); + var term = terms[(terms.length - 1) * Math.random() | 0]; + console.log('Compiling ' + term + '...'); + tput.compileTerminfo(term); + return; +} + // console.log('Max colors: %d.', tput.colors); // console.log(tput.strings.acs_chars.split('').map(function(ch) { return ch.charCodeAt(0); }));