From 084f28fcff0bc37fac6e040e3957d865774e33f4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 28 Jul 2013 10:52:13 -0500 Subject: [PATCH] content attribute and tag handling. - handle content attributes with better respect to element's style. - add a more sophisticated tag parser. --- lib/program.js | 41 ++++++-- lib/widget.js | 185 +++++++++++++++++++++++++++++++------ test/widget-nested-attr.js | 28 ++++++ test/widget.js | 2 +- 4 files changed, 217 insertions(+), 39 deletions(-) create mode 100644 test/widget-nested-attr.js diff --git a/lib/program.js b/lib/program.js index cc292cf..82cadf3 100644 --- a/lib/program.js +++ b/lib/program.js @@ -1843,16 +1843,32 @@ Program.prototype.text = function(text, attr) { // NOTE: sun-color may not allow multiple params for SGR. Program.prototype._attr = function(param, val) { var self = this - , param = param || 'normal' - , parts = param.split(/\s*[,;]\s*/) + , param + , parts , color , m; + if (Array.isArray(param)) { + parts = param; + param = parts[0] || 'normal'; + } else { + param = param || 'normal'; + parts = param.split(/\s*[,;]\s*/); + } + if (parts.length > 1) { - parts = parts.map(function(part) { - return self._attr(part, val).slice(2, -1); + var used = {} + , out = []; + + parts.forEach(function(part) { + part = self._attr(part, val).slice(2, -1); + if (part === '') return; + if (used[part]) return; + used[part] = true; + out.push(part); }); - return '\x1b[' + parts.join(';') + 'm'; + + return '\x1b[' + out.join(';') + 'm'; } if (param.indexOf('no ') === 0) { @@ -1867,6 +1883,7 @@ Program.prototype._attr = function(param, val) { // attributes case 'normal': case 'default': + if (val === false) return ''; return '\x1b[m'; case 'bold': return val === false @@ -1891,10 +1908,6 @@ Program.prototype._attr = function(param, val) { return val === false ? '\x1b[28m' : '\x1b[8m'; - case 'invisible': - return val === false - ? '\x1b[28m' - : '\x1b[8m'; // 8-color foreground case 'black fg': @@ -1930,6 +1943,7 @@ Program.prototype._attr = function(param, val) { ? '\x1b[39m' : '\x1b[37m'; case 'default fg': + if (val === false) return ''; return '\x1b[39m'; // 8-color background @@ -1966,6 +1980,7 @@ Program.prototype._attr = function(param, val) { ? '\x1b[49m' : '\x1b[47m'; case 'default bg': + if (val === false) return ''; return '\x1b[49m'; // 16-color foreground @@ -2038,6 +2053,7 @@ Program.prototype._attr = function(param, val) { // non-16-color rxvt default fg and bg case 'default fg bg': + if (val === false) return ''; return this.term('rxvt') ? '\x1b[100m' : '\x1b[39;49m'; @@ -2081,7 +2097,12 @@ Program.prototype._attr = function(param, val) { return '\x1b[48;5;' + color + 'm'; } } - return '\x1b[' + param + 'm'; + + if (/^[\d;]*$/.test(param)) { + return '\x1b[' + param + 'm'; + } + + return null; } }; diff --git a/lib/widget.js b/lib/widget.js index 822b367..c31b2e5 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -1143,60 +1143,75 @@ Screen.prototype._reduceColor = function(col) { }; // Convert an SGR string to our own attribute format. -Screen.prototype.attrCode = function(code, cur) { +Screen.prototype.attrCode = function(code, cur, def) { var flags = (cur >> 18) & 0x1ff , fg = (cur >> 9) & 0x1ff , bg = cur & 0x1ff , c , i; - code = /^\x1b\[([\d;]*)m$/.exec(code); - if (!code) return cur; - - code = code[1].split(';'); + code = code.slice(2, -1).split(';'); if (!code[0]) code[0] = '0'; for (i = 0; i < code.length; i++) { c = +code[i] || 0; switch (c) { case 0: // normal - bg = 0x1ff; - fg = 0x1ff; - flags = 0; + //bg = 0x1ff; + //fg = 0x1ff; + //flags = 0; + bg = def & 0x1ff; + fg = (def >> 9) & 0x1ff; + flags = (def >> 18) & 0x1ff; break; case 1: // bold flags |= 1; break; case 22: - flags &= ~1; + //flags &= ~1; + flags = (def >> 18) & 0x1ff; break; case 4: // underline flags |= 2; break; case 24: - flags &= ~2; + //flags &= ~2; + flags = (def >> 18) & 0x1ff; break; case 5: // blink flags |= 4; break; case 25: - flags &= ~4; + //flags &= ~4; + flags = (def >> 18) & 0x1ff; break; case 7: // inverse flags |= 8; break; case 27: - flags &= ~8; + //flags &= ~8; + flags = (def >> 18) & 0x1ff; break; case 8: // invisible flags |= 16; break; case 28: - flags &= ~16; + //flags &= ~16; + flags = (def >> 18) & 0x1ff; + break; + case 39: // default fg + //fg = 0x1ff; + fg = (def >> 9) & 0x1ff; + break; + case 49: // default bg + //bg = 0x1ff; + bg = def & 0x1ff; break; case 100: // default fg/bg - bg = 0x1ff; - fg = 0x1ff; + //fg = 0x1ff; + //bg = 0x1ff; + fg = (def >> 9) & 0x1ff; + bg = def & 0x1ff; break; default: // color if (c === 48 && +code[i+1] === 5) { @@ -1226,14 +1241,21 @@ Screen.prototype.attrCode = function(code, cur) { bg = c - 100; bg += 8; } else if (c === 49) { - bg = 0x1ff; + //bg = 0x1ff; + bg = def & 0x1ff; } else if (c >= 30 && c <= 37) { fg = c - 30; } else if (c >= 90 && c <= 97) { fg = c - 90; fg += 8; } else if (c === 39) { - fg = 0x1ff; + //fg = 0x1ff; + fg = (def >> 9) & 0x1ff; + } else if (c === 100) { + //fg = 0x1ff; + //bg = 0x1ff; + fg = (def >> 9) & 0x1ff; + bg = def & 0x1ff; } break; } @@ -1915,21 +1937,128 @@ Element.prototype._parseTags = function(text) { var program = this.screen.program; return text.replace(/{(\/?)([\w\-,;!#]*)}/g, function(tag, slash, color) { if (!color) return slash ? '\x1b[m' : tag; - color = color.replace(/-/g, ' '); var result = program._attr(color, !slash); - - // Parse error. Just return the original text. - if (!/^\x1b\[[\d;]*m$/.test(result)) { - return tag; - } - - return result; + return result != null ? result : tag; }); }; +// Convert `{red-fg}foo{/red-fg}` to `\x1b[31mfoo\x1b[39m`. +Element.prototype._parseTags_ = function(text) { + if (!this.parseTags) return text; + if (!/{\/?[\w\-,;!#]*}/.test(text)) return text; + + var out = '' + , state + , bg = [] + , fg = [] + , flag = [] + , cap + , slash + , param + , attr; + + for (;;) { + if (cap = /^{(\/?)([\w\-,;!#]*)}/.exec(text)) { + text = text.substring(cap[0].length); + slash = cap[1] === '/'; + param = cap[2].replace(/-/g, ' '); + + if (param.slice(-3) === ' bg') state = bg; + else if (param.slice(-3) === ' fg') state = fg; + else state = flag; + + if (slash) { + if (!param) { + out += this._attr('normal'); + bg.length = 0; + fg.length = 0; + flag.length = 0; + } else { + attr = this._attr(param, false); + if (attr == null) { + out += cap[0]; + } else { + if (param !== state[state.length-1]) { + throw new Error('Misnested tags.'); + } + state.pop(); + if (state.length) { + out += this._attr(state[state.length-1]); + } else { + out += attr; + } + } + } + } else { + if (!param) { + out += cap[0]; + } else { + attr = this._attr(param); + if (attr == null) { + out += cap[0]; + } else { + state.push(param); + out += attr; + } + } + } + + continue; + } + + if (cap = /^[\s\S]+?(?={\/?[\w\-,;!#]*})/.exec(text)) { + text = text.substring(cap[0].length); + out += cap[0]; + continue; + } + + out += text; + break; + } + + return out; +}; + +Element.prototype._attr = function(param, val) { + return this.screen.program._attr(param, val); +}; + +// If we want the resetting of attributes to the element's current style to +// *only* be for tags, we can do this and set the codes above to start at 9000: +Element.prototype._attr_ = function(param, val) { + var param = this.screen.program._attr(param, val) + , i + , c; + + if (!param) return param; + + param = param.slice(2, -1).split(';'); + if (!param[0]) param[0] = '0'; + + for (i = 0; i < param.length; i++) { + c = +param[i] || 0; + switch (c) { + case 0: // reset + case 22: // reset bold + case 24: // reset underline + case 25: // reset blink + case 27: // reset inverse + case 28: // reset invisible + case 39: // reset fg + case 49: // reset bg + case 100: // reset fg/bg + param[i] = 9000 + c; + break; + } + } + + return '\x1b[' + out.join(';') + 'm'; +}; + Element.prototype._parseAttr = function(lines) { - var attr = this.sattr(this.style, this.style.fg, this.style.bg) + var dattr = this.sattr(this.style, this.style.fg, this.style.bg) + , attr = dattr , attrs = [] , line , i @@ -1946,7 +2075,7 @@ Element.prototype._parseAttr = function(lines) { for (i = 0; i < line.length; i++) { if (line[i] === '\x1b') { if (c = /^\x1b\[[\d;]*m/.exec(line.substring(i))) { - attr = this.screen.attrCode(c[0], attr); + attr = this.screen.attrCode(c[0], attr, dattr); i += c[0].length - 1; } } @@ -2781,7 +2910,7 @@ Element.prototype.render = function() { while (ch === '\x1b') { if (c = /^\x1b\[[\d;]*m/.exec(content.substring(ci - 1))) { ci += c[0].length - 1; - attr = this.screen.attrCode(c[0], attr); + attr = this.screen.attrCode(c[0], attr, dattr); ch = content[ci] || ' '; ci++; } else { diff --git a/test/widget-nested-attr.js b/test/widget-nested-attr.js new file mode 100644 index 0000000..c52cd9d --- /dev/null +++ b/test/widget-nested-attr.js @@ -0,0 +1,28 @@ +var blessed = require('../') + , screen; + +screen = blessed.screen({ + dump: __dirname + '/nested-attr.log', + smartCSR: true +}); + +blessed.box({ + parent: screen, + left: 'center', + top: 'center', + width: '80%', + height: '80%', + bg: 'black', + fg: 'yellow', + tags: true, + border: { + type: 'ascii' + }, + content: '{red-fg}hello {blue-fg}how{/blue-fg} are you?{/red-fg}' +}); + +screen.key('q', function() { + return process.exit(0); +}); + +screen.render(); diff --git a/test/widget.js b/test/widget.js index 6a9aa87..b253327 100644 --- a/test/widget.js +++ b/test/widget.js @@ -145,7 +145,7 @@ var stext = blessed.scrollabletext({ mouse: true, content: lorem, fg: 'blue', - bg: 'default', + bg: 'black', border: { type: 'ascii', fg: 'default',