From 363bc44caaf752a39fa9a70779a4c22d8b8130c4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 14 Jul 2013 03:14:48 -0500 Subject: [PATCH] fix terminfo if/else. detect features. --- lib/program.js | 38 ---------- lib/tput.js | 191 ++++++++++++++++++++++++++++++++++++++++++++++--- lib/widget.js | 60 +++------------- test/tput.js | 3 + 4 files changed, 195 insertions(+), 97 deletions(-) diff --git a/lib/program.js b/lib/program.js index 4933b21..3b6f98e 100644 --- a/lib/program.js +++ b/lib/program.js @@ -54,8 +54,6 @@ function Program(options) { this.terminal = options.terminal || process.env.TERM || 'xterm'; - this.unicode = this.detectUnicode(); - // if (!Program.global) { // Program._write = process.stdout.write; // process.stdout.write = function() {}; @@ -783,42 +781,6 @@ Program.prototype._bindResponse = function(s) { } }; -// Not as foolproof as I'd like it to be. -Program.prototype.detectUnicode = function() { - var LANG = process.env.LANG - + ':' + process.env.LANGUAGE - + ':' + process.env.LC_ALL - + ':' + process.env.LC_CTYPE; - - return /utf-?8/i.test(LANG); -}; - -// Could do something goofy like this: -Program.prototype.detectUnicode_ = function(callback) { - var out = String.fromCharCode((0x1b << 8) | '['.charCodeAt(0)) + 'c' - , done; - - this.once('response', function(data) { - if (done) return; - done = true; - if (data.deviceAttributes) { - return callback(null, false); - } - }); - - this.write(out); - - this.cub(2); - this.write(' '); - this.cub(2); - - setTimeout(function() { - if (done) return; - done = true; - return callback(null, true); - }, 100); -}; - Program.prototype.receive = function(text, callback) { var listeners = (this._events && this._events['keypress']) || [] , bak = listeners.slice() diff --git a/lib/tput.js b/lib/tput.js index b403918..860570f 100644 --- a/lib/tput.js +++ b/lib/tput.js @@ -65,6 +65,8 @@ function Tput(options) { } this._useXtermI(); } + + this.detectFeatures(); } /** @@ -477,7 +479,7 @@ Tput.prototype.compile = function(info, inject) { if (self.debug) { console.log('Compiling %s: %s', key, JSON.stringify(info.all[key])); } - info.methods[key] = self._compile(info.all[key]); + info.methods[key] = self._compile(info.all[key], key); }); }); @@ -536,8 +538,10 @@ Tput.prototype.inject = function(info) { this.strings = info.strings; }; -Tput.prototype._compile = function(val) { - var self = this; +Tput.prototype._compile = function(val, key) { + var self = this + , _val = val + , _key = key; switch (typeof val) { case 'boolean': @@ -563,7 +567,8 @@ Tput.prototype._compile = function(val) { , i , v - var then + var fi + , then , els , end; @@ -902,15 +907,17 @@ Tput.prototype._compile = function(val) { continue; } + // if/then/else/end + // Terminfo does elseif's like + // this: %?[expr]%t...%e[expr]%t...%; if (read(/^%e/)) { + fi = val.indexOf('%?'); then = val.indexOf('%t'); els = val.indexOf('%e'); end = val.indexOf('%;'); - // Terminfo does elseif's like - // this: %?[expr]%t...%e[expr]%t...%; - // if (then < end && then < els) { - // if (then !== -1 && then < end && then < els) { - if (then !== -1 && then < end && (els !== -1 && then < els)) { + if (then !== -1 && then < end + && (fi === -1 || then < fi) + && (els === -1 || then < els)) { stmt('} else if ('); } else { stmt('} else {'); @@ -1002,11 +1009,21 @@ Tput.prototype._compile = function(val) { process.stdout.write(v + '\n'); } + // Possibly do: + // if (code.indexOf('return ') === 0) { + // return new Function('', code)(); + // } + try { return this.printf ? new Function('sprintf, params', code).bind(null, sprintf) : new Function('params', code); } catch (e) { + console.error(''); + console.error('Error on `' + _key + '`:'); + console.error(JSON.stringify(_val)); + console.error(''); + console.error(code.replace(/(,|;)/g, '$1\n')); e.stack = e.stack.replace(/\x1b/g, '\\x1b'); throw e; } @@ -1261,6 +1278,122 @@ Tput.prototype.compileTermcap = function(info, inject) { return this.compile(info, inject); }; +/** + * Detect Features / Quirks + */ + +Tput.prototype.detectFeatures = function() { + this.unicode = this.detectUnicode(); + this.brokenACS = this.detectBrokenACS(); + this.PCRomSet = this.detectPCRomSet(); + + this.magicCookie = this.detectMagicCookie(); + this.padding = this.detectPadding(); + this.setbuf = this.detectSetbuf(); + + this.parseACS(); +}; + +Tput.prototype.detectUnicode = function() { + var LANG = process.env.LANG + + ':' + process.env.LANGUAGE + + ':' + process.env.LC_ALL + + ':' + process.env.LC_CTYPE; + + return /utf-?8/i.test(LANG); +}; + +// For some reason TERM=linux has smacs/rmacs, but it maps to `^[[11m` +// and it does not switch to the DEC SCLD character set. What the hell? +// xterm: \x1b(0, screen: \x0e, linux: \x1b[11m (doesn't work) +// `man console_codes` says: +// 11 select null mapping, set display control flag, reset tog‐ +// gle meta flag (ECMA-48 says "first alternate font"). +// See ncurses: +// ~/ncurses/ncurses/tinfo/lib_acs.c +// ~/ncurses/ncurses/tinfo/tinfo_driver.c +// ~/ncurses/ncurses/tinfo/lib_setup.c +Tput.prototype.detectBrokenACS = function() { + // ncurses-compatible env variable. + if (process.env.NCURSES_NO_UTF8_ACS != null) { + return true; + } + + // An extended terminfo number. + if (this.numbers['U8'] !== -1) { + return !!this.numbers['U8']; + } + + // The linux console is just broken for some reason. + if (this.term === 'linux') { + return true; + } + + // screen termcap is bugged? + if (this.term.indexOf('screen') == 0 + && process.env.TERMCAP + && ~process.env.TERMCAP.indexOf('screen') + && ~process.env.TERMCAP.indexOf('hhII00')) { + if (~this.strings.enter_alt_charset_mode.indexOf('\016') + || ~this.strings.enter_alt_charset_mode.indexOf('\017') + || ~this.strings.set_attributes.indexOf('\016') + || ~this.strings.set_attributes.indexOf('\017')) { + return true; + } + } + + return false; +}; + +// Apparently the Linux console does not support ACS, +// but it does support the PC ROM character set. +// See: ~/ncurses/ncurses/tinfo/lib_acs.c +Tput.prototype.detectPCRomSet = function() { + var str = this.strings; + if (str.enter_pc_charset_mode === str.enter_alt_charset_mode + && str.exit_pc_charset_mode === str.exit_alt_charset_mode) { + return true; + } + return false; +}; + +Tput.prototype.detectMagicCookie = function() { + return process.env.NCURSES_NO_MAGIC_COOKIE == null; +}; + +Tput.prototype.detectPadding = function() { + return process.env.NCURSES_NO_PADDING == null; +}; + +Tput.prototype.detectSetbuf = function() { + return process.env.NCURSES_NO_SETBUF == null; +}; + +Tput.prototype.parseACS = function() { + var self = this; + + this.acsc = {}; + this.acscr = {}; + + if (this.PCRomSet) { + ; + } + + // See: ~/ncurses/ncurses/tinfo/lib_acs.c: L208 + Object.keys(acsc).forEach(function(ch) { + var acs_chars = self.strings.acs_chars || '' + , i = acs_chars.indexOf(ch) + , next = acs_chars[i + 1]; + + if (!next || i === -1) { + return; + } + + self.acsc[ch] = acsc[next]; + self.acscr[acsc[next]] = ch; + }); +}; + /** * Helpers */ @@ -1987,6 +2120,46 @@ Tput.strings = [ 'box_chars_1' ]; +// DEC Special Character and Line Drawing Set. +// Taken from tty.js. +var acsc = { // (0 + '`': '\u25c6', // '◆' + 'a': '\u2592', // '▒' + 'b': '\u0009', // '\t' + 'c': '\u000c', // '\f' + 'd': '\u000d', // '\r' + 'e': '\u000a', // '\n' + 'f': '\u00b0', // '°' + 'g': '\u00b1', // '±' + 'h': '\u2424', // '\u2424' (NL) + 'i': '\u000b', // '\v' + 'j': '\u2518', // '┘' + 'k': '\u2510', // '┐' + 'l': '\u250c', // '┌' + 'm': '\u2514', // '└' + 'n': '\u253c', // '┼' + 'o': '\u23ba', // '⎺' + 'p': '\u23bb', // '⎻' + 'q': '\u2500', // '─' + 'r': '\u23bc', // '⎼' + 's': '\u23bd', // '⎽' + 't': '\u251c', // '├' + 'u': '\u2524', // '┤' + 'v': '\u2534', // '┴' + 'w': '\u252c', // '┬' + 'x': '\u2502', // '│' + 'y': '\u2264', // '≤' + 'z': '\u2265', // '≥' + '{': '\u03c0', // 'π' + '|': '\u2260', // '≠' + '}': '\u00a3', // '£' + '~': '\u00b7' // '·' +}; + +// ['b', 'c', 'd', 'e', 'h', 'i'].forEach(function(ch) { +// delete acsc[ch]; +// }); + /** * Expose */ diff --git a/lib/widget.js b/lib/widget.js index 7aba25f..765a58a 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -766,14 +766,16 @@ Screen.prototype.draw = function(start, end) { } } - if (!this.program.unicode && this.tput - && this.tput.strings.enter_alt_charset_mode && SCLD[ch] - && this.tput.strings.enter_alt_charset_mode !== '\x1b[11m') { - // TODO: Possibly do not check for unicode above. - // For some reason TERM=linux has smacs/rmacs, but it maps to `^[[11m` - // and it does not switch to the DEC SCLD character set. What the hell? - // xterm: \x1b(0, screen: \x0e - ch = this.tput.smacs() + SCLD[ch] + this.tput.rmacs(); + // Attempt to use ACS for supported characters. + // XXX We may not want to check tput.unicode here. + if (this.tput + && this.tput.strings.enter_alt_charset_mode + && this.tput.acscr[ch] + && !this.tput.brokenACS + && !this.tput.unicode) { + ch = this.tput.smacs() + + this.tput.acscr[ch] + + this.tput.rmacs(); } out += ch; @@ -4084,48 +4086,6 @@ function sattr(obj, fg, bg) { | bg; } -// DEC Special Character and Line Drawing Set. -// Taken from tty.js. -var SCLD = { // (0 - '`': '\u25c6', // '◆' - 'a': '\u2592', // '▒' - // 'b': '\u0009', // '\t' - // 'c': '\u000c', // '\f' - // 'd': '\u000d', // '\r' - // 'e': '\u000a', // '\n' - 'f': '\u00b0', // '°' - 'g': '\u00b1', // '±' - 'h': '\u2424', // '\u2424' (NL) - // 'i': '\u000b', // '\v' - 'j': '\u2518', // '┘' - 'k': '\u2510', // '┐' - 'l': '\u250c', // '┌' - 'm': '\u2514', // '└' - 'n': '\u253c', // '┼' - 'o': '\u23ba', // '⎺' - 'p': '\u23bb', // '⎻' - 'q': '\u2500', // '─' - 'r': '\u23bc', // '⎼' - 's': '\u23bd', // '⎽' - 't': '\u251c', // '├' - 'u': '\u2524', // '┤' - 'v': '\u2534', // '┴' - 'w': '\u252c', // '┬' - 'x': '\u2502', // '│' - 'y': '\u2264', // '≤' - 'z': '\u2265', // '≥' - '{': '\u03c0', // 'π' - '|': '\u2260', // '≠' - '}': '\u00a3', // '£' - '~': '\u00b7' // '·' -}; - -Object.keys(SCLD).forEach(function(key) { - var val = SCLD[key]; - delete SCLD[key]; - SCLD[val] = key; -}); - /** * Expose */ diff --git a/test/tput.js b/test/tput.js index 9ba1f4e..8ea0100 100644 --- a/test/tput.js +++ b/test/tput.js @@ -92,6 +92,9 @@ var tput = Tput({ console.log('Max colors: %d.', tput.colors); +// console.log(tput.strings.acs_chars.split('').map(function(ch) { return ch.charCodeAt(0); })); +// console.log(JSON.stringify(tput.strings.acs_chars)); + // process.stdout.write(Tput.sprintf('%-10s\n', 'hello')); // tput._compile('%?%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');