content attribute and tag handling.

- handle content attributes with better respect to element's style.
- add a more sophisticated tag parser.
This commit is contained in:
Christopher Jeffrey 2013-07-28 10:52:13 -05:00
parent 927c40d2f3
commit 084f28fcff
4 changed files with 217 additions and 39 deletions

View File

@ -1843,16 +1843,32 @@ Program.prototype.text = function(text, attr) {
// NOTE: sun-color may not allow multiple params for SGR. // NOTE: sun-color may not allow multiple params for SGR.
Program.prototype._attr = function(param, val) { Program.prototype._attr = function(param, val) {
var self = this var self = this
, param = param || 'normal' , param
, parts = param.split(/\s*[,;]\s*/) , parts
, color , color
, m; , m;
if (Array.isArray(param)) {
parts = param;
param = parts[0] || 'normal';
} else {
param = param || 'normal';
parts = param.split(/\s*[,;]\s*/);
}
if (parts.length > 1) { if (parts.length > 1) {
parts = parts.map(function(part) { var used = {}
return self._attr(part, val).slice(2, -1); , 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) { if (param.indexOf('no ') === 0) {
@ -1867,6 +1883,7 @@ Program.prototype._attr = function(param, val) {
// attributes // attributes
case 'normal': case 'normal':
case 'default': case 'default':
if (val === false) return '';
return '\x1b[m'; return '\x1b[m';
case 'bold': case 'bold':
return val === false return val === false
@ -1891,10 +1908,6 @@ Program.prototype._attr = function(param, val) {
return val === false return val === false
? '\x1b[28m' ? '\x1b[28m'
: '\x1b[8m'; : '\x1b[8m';
case 'invisible':
return val === false
? '\x1b[28m'
: '\x1b[8m';
// 8-color foreground // 8-color foreground
case 'black fg': case 'black fg':
@ -1930,6 +1943,7 @@ Program.prototype._attr = function(param, val) {
? '\x1b[39m' ? '\x1b[39m'
: '\x1b[37m'; : '\x1b[37m';
case 'default fg': case 'default fg':
if (val === false) return '';
return '\x1b[39m'; return '\x1b[39m';
// 8-color background // 8-color background
@ -1966,6 +1980,7 @@ Program.prototype._attr = function(param, val) {
? '\x1b[49m' ? '\x1b[49m'
: '\x1b[47m'; : '\x1b[47m';
case 'default bg': case 'default bg':
if (val === false) return '';
return '\x1b[49m'; return '\x1b[49m';
// 16-color foreground // 16-color foreground
@ -2038,6 +2053,7 @@ Program.prototype._attr = function(param, val) {
// non-16-color rxvt default fg and bg // non-16-color rxvt default fg and bg
case 'default fg bg': case 'default fg bg':
if (val === false) return '';
return this.term('rxvt') return this.term('rxvt')
? '\x1b[100m' ? '\x1b[100m'
: '\x1b[39;49m'; : '\x1b[39;49m';
@ -2081,8 +2097,13 @@ Program.prototype._attr = function(param, val) {
return '\x1b[48;5;' + color + 'm'; return '\x1b[48;5;' + color + 'm';
} }
} }
if (/^[\d;]*$/.test(param)) {
return '\x1b[' + param + 'm'; return '\x1b[' + param + 'm';
} }
return null;
}
}; };
Program.prototype.fg = Program.prototype.fg =

View File

@ -1143,60 +1143,75 @@ Screen.prototype._reduceColor = function(col) {
}; };
// Convert an SGR string to our own attribute format. // 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 var flags = (cur >> 18) & 0x1ff
, fg = (cur >> 9) & 0x1ff , fg = (cur >> 9) & 0x1ff
, bg = cur & 0x1ff , bg = cur & 0x1ff
, c , c
, i; , i;
code = /^\x1b\[([\d;]*)m$/.exec(code); code = code.slice(2, -1).split(';');
if (!code) return cur;
code = code[1].split(';');
if (!code[0]) code[0] = '0'; if (!code[0]) code[0] = '0';
for (i = 0; i < code.length; i++) { for (i = 0; i < code.length; i++) {
c = +code[i] || 0; c = +code[i] || 0;
switch (c) { switch (c) {
case 0: // normal case 0: // normal
bg = 0x1ff; //bg = 0x1ff;
fg = 0x1ff; //fg = 0x1ff;
flags = 0; //flags = 0;
bg = def & 0x1ff;
fg = (def >> 9) & 0x1ff;
flags = (def >> 18) & 0x1ff;
break; break;
case 1: // bold case 1: // bold
flags |= 1; flags |= 1;
break; break;
case 22: case 22:
flags &= ~1; //flags &= ~1;
flags = (def >> 18) & 0x1ff;
break; break;
case 4: // underline case 4: // underline
flags |= 2; flags |= 2;
break; break;
case 24: case 24:
flags &= ~2; //flags &= ~2;
flags = (def >> 18) & 0x1ff;
break; break;
case 5: // blink case 5: // blink
flags |= 4; flags |= 4;
break; break;
case 25: case 25:
flags &= ~4; //flags &= ~4;
flags = (def >> 18) & 0x1ff;
break; break;
case 7: // inverse case 7: // inverse
flags |= 8; flags |= 8;
break; break;
case 27: case 27:
flags &= ~8; //flags &= ~8;
flags = (def >> 18) & 0x1ff;
break; break;
case 8: // invisible case 8: // invisible
flags |= 16; flags |= 16;
break; break;
case 28: 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; break;
case 100: // default fg/bg case 100: // default fg/bg
bg = 0x1ff; //fg = 0x1ff;
fg = 0x1ff; //bg = 0x1ff;
fg = (def >> 9) & 0x1ff;
bg = def & 0x1ff;
break; break;
default: // color default: // color
if (c === 48 && +code[i+1] === 5) { if (c === 48 && +code[i+1] === 5) {
@ -1226,14 +1241,21 @@ Screen.prototype.attrCode = function(code, cur) {
bg = c - 100; bg = c - 100;
bg += 8; bg += 8;
} else if (c === 49) { } else if (c === 49) {
bg = 0x1ff; //bg = 0x1ff;
bg = def & 0x1ff;
} else if (c >= 30 && c <= 37) { } else if (c >= 30 && c <= 37) {
fg = c - 30; fg = c - 30;
} else if (c >= 90 && c <= 97) { } else if (c >= 90 && c <= 97) {
fg = c - 90; fg = c - 90;
fg += 8; fg += 8;
} else if (c === 39) { } 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; break;
} }
@ -1915,21 +1937,128 @@ Element.prototype._parseTags = function(text) {
var program = this.screen.program; var program = this.screen.program;
return text.replace(/{(\/?)([\w\-,;!#]*)}/g, function(tag, slash, color) { return text.replace(/{(\/?)([\w\-,;!#]*)}/g, function(tag, slash, color) {
if (!color) return slash ? '\x1b[m' : tag; if (!color) return slash ? '\x1b[m' : tag;
color = color.replace(/-/g, ' '); color = color.replace(/-/g, ' ');
var result = program._attr(color, !slash); var result = program._attr(color, !slash);
return result != null ? result : tag;
// Parse error. Just return the original text.
if (!/^\x1b\[[\d;]*m$/.test(result)) {
return tag;
}
return result;
}); });
}; };
// 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) { 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 = [] , attrs = []
, line , line
, i , i
@ -1946,7 +2075,7 @@ Element.prototype._parseAttr = function(lines) {
for (i = 0; i < line.length; i++) { for (i = 0; i < line.length; i++) {
if (line[i] === '\x1b') { if (line[i] === '\x1b') {
if (c = /^\x1b\[[\d;]*m/.exec(line.substring(i))) { 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; i += c[0].length - 1;
} }
} }
@ -2781,7 +2910,7 @@ Element.prototype.render = function() {
while (ch === '\x1b') { while (ch === '\x1b') {
if (c = /^\x1b\[[\d;]*m/.exec(content.substring(ci - 1))) { if (c = /^\x1b\[[\d;]*m/.exec(content.substring(ci - 1))) {
ci += c[0].length - 1; ci += c[0].length - 1;
attr = this.screen.attrCode(c[0], attr); attr = this.screen.attrCode(c[0], attr, dattr);
ch = content[ci] || ' '; ch = content[ci] || ' ';
ci++; ci++;
} else { } else {

View File

@ -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();

View File

@ -145,7 +145,7 @@ var stext = blessed.scrollabletext({
mouse: true, mouse: true,
content: lorem, content: lorem,
fg: 'blue', fg: 'blue',
bg: 'default', bg: 'black',
border: { border: {
type: 'ascii', type: 'ascii',
fg: 'default', fg: 'default',