259 lines
7 KiB
JavaScript
259 lines
7 KiB
JavaScript
/*
|
|
* © Mikko Ahlroth 2014
|
|
* WeeCRApp is open source software. For licensing information, please check
|
|
* the LICENCE file.
|
|
*/
|
|
|
|
// Utilities for parsing WeeChat color codes sent
|
|
// in the relay protocol
|
|
|
|
.pragma library
|
|
|
|
// Protocol specific constants
|
|
// Starting bytes
|
|
var COLOR_START = '\x19';
|
|
var SET_ATTR = '\x1A';
|
|
var REMOVE_ATTR = '\x1B';
|
|
var RESET = '\x1C';
|
|
|
|
var START_RE = /(\x19|\x1A|\x1B|\x1C)/;
|
|
|
|
// Because Javascript does not have named captures, this regex is going to look
|
|
// a bit hairy. Here be dragons.
|
|
var STD_RE = '\\d{2}'; // STD color
|
|
var EXT_RE = '\\d{5}'; // EXT color
|
|
var EXT_CHAR = '@'; // Character which begins EXT color
|
|
var ATTR_CC = '[*!/_|]'; // Attribute character class for regex
|
|
|
|
var COLOR_RE_STR = '^(?:(?:' + EXT_CHAR + '(' + EXT_RE + '))'
|
|
+ '|(' + STD_RE + ')'
|
|
+ '|(?:F' + EXT_CHAR + '?(' + ATTR_CC + '*)(?:(' + EXT_RE + ')|(' + STD_RE + ')))'
|
|
+ '|(?:B(?:' + EXT_CHAR + '(' + EXT_RE + ')|(' + STD_RE + ')))'
|
|
+ '|(?:\\*' + EXT_CHAR + '?(' + ATTR_CC + '*)(?:(' + EXT_RE + ')|(' + STD_RE + '))(?:,(?:' + EXT_CHAR + '(' + EXT_RE + ')|(' + STD_RE + ')))?)'
|
|
+ '|(bF)|(bD)|(bB)|(b_)|(b-)|(b#)|(bi)|(bl)|(E)|(\\x1C))(.*)$';
|
|
|
|
var COLOR_RE = new RegExp(COLOR_RE_STR);
|
|
|
|
// Different indexes to the above regex
|
|
var STD_IDX = 2;
|
|
var EXT_IDX = 1;
|
|
var F_A_IDX = 3;
|
|
var F_STD_IDX = 5;
|
|
var F_EXT_IDX = 4;
|
|
var B_STD_IDX = 7;
|
|
var B_EXT_IDX = 6;
|
|
var AST_A_IDX = 8;
|
|
var AST_STD_IDX = 10;
|
|
var AST_EXT_IDX = 9;
|
|
var AST_BG_STD_IDX = 12;
|
|
var AST_BG_EXT_IDX = 11;
|
|
var E_IDX = 21;
|
|
var RST_IDX = 22;
|
|
// The rest are unused for now
|
|
var REST_IDX = 23;
|
|
|
|
// Attributes
|
|
var ATTR_BOLD = '*';
|
|
var ATTR_REVERSE = '!';
|
|
var ATTR_ITALIC = '/';
|
|
var ATTR_UNDERLINE = '_';
|
|
var ATTR_KEEP = '|';
|
|
|
|
// A WeeChat color
|
|
// is_ext is true if the color is extended,
|
|
// otherwise it is a standard color
|
|
function CodedTextColor(color, is_ext) {
|
|
this.color = color;
|
|
this.is_ext = is_ext;
|
|
}
|
|
|
|
// A section of text which has the given attributes:
|
|
// fgColor: Foreground color as CodedTextColor object
|
|
// bgColor: Background color --//--
|
|
// attributes: List of attributes from the selection above,
|
|
// except ATTR_KEEP
|
|
// emphasize: Should this section be emphasized
|
|
function CodedTextSection(str, fgColor, bgColor, attributes, emphasize) {
|
|
this.str = str;
|
|
this.fgColor = fgColor;
|
|
this.bgColor = bgColor;
|
|
this.attributes = attributes;
|
|
this.emphasize = emphasize;
|
|
|
|
// Clone this object
|
|
this.clone = function () {
|
|
var ret = new CodedTextSection();
|
|
ret.str = this.str;
|
|
ret.fgColor = this.fgColor;
|
|
ret.bgColor = this.bgColor;
|
|
ret.attributes = this.attributes;
|
|
ret.emphasize = this.emphasize;
|
|
|
|
return ret;
|
|
};
|
|
}
|
|
|
|
// Parse coded text and return list of CodedTextSection
|
|
function parseString(str) {
|
|
var out = [];
|
|
var currentMode = null;
|
|
var currentSection = null;
|
|
var previousSection = null;
|
|
var currentStr = null;
|
|
var parts = str.split(START_RE);
|
|
|
|
for (var i = 0; i < parts.length; ++i) {
|
|
// Every even index is a content part
|
|
if ((i % 2) === 0) {
|
|
// The first is just a plain string so we will handle it in the else part
|
|
if (i > 0) {
|
|
switch (currentMode) {
|
|
case COLOR_START:
|
|
currentSection = parseColorStart(parts[i], previousSection);
|
|
break;
|
|
|
|
case SET_ATTR:
|
|
currentSection = parseSetAttr(parts[i], previousSection);
|
|
break;
|
|
|
|
case REMOVE_ATTR:
|
|
currentSection = parseRemoveAttr(parts[i], previousSection);
|
|
break;
|
|
|
|
case RESET:
|
|
currentSection = new CodedTextSection(parts[i]);
|
|
currentSection.fgColor = null;
|
|
currentSection.bgColor = null;
|
|
currentSection.attributes = null;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
currentSection = new CodedTextSection(parts[i]);
|
|
}
|
|
|
|
out.push(currentSection);
|
|
previousSection = currentSection;
|
|
}
|
|
// The odd parts give the next mode
|
|
else {
|
|
currentMode = parts[i];
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
// Parse color code
|
|
function parseColorStart(part, previousSection) {
|
|
var ret = previousSection.clone();
|
|
|
|
var match = COLOR_RE.exec(part);
|
|
if (!match) {
|
|
ret.str = part;
|
|
return ret;
|
|
}
|
|
|
|
ret.str = match[REST_IDX];
|
|
|
|
if (match[STD_IDX]) {
|
|
ret.fgColor = new CodedTextColor(match[STD_IDX], false);
|
|
}
|
|
else if (match[EXT_IDX]) {
|
|
ret.fgColor = new CodedTextColor(match[EXT_IDX], true);
|
|
}
|
|
else if (match[F_A_IDX] !== undefined) {
|
|
ret.attributes = match[F_A_IDX];
|
|
|
|
if (match[F_STD_IDX]) {
|
|
ret.fgColor = new CodedTextColor(match[F_STD_IDX], false);
|
|
}
|
|
else if (match[F_EXT_IDX]) {
|
|
ret.fgColor = new CodedTextColor(match[F_EXT_IDX], true);
|
|
}
|
|
}
|
|
else if (match[B_STD_IDX]) {
|
|
ret.bgColor = new CodedTextColor(match[B_STD_IDX], false);
|
|
}
|
|
else if (match[B_EXT_IDX]) {
|
|
ret.bgColor = new CodedTextColor(match[B_EXT_IDX], true);
|
|
}
|
|
else if (match[AST_A_IDX] !== undefined) {
|
|
ret.attributes = match[AST_A_IDX];
|
|
|
|
if (match[AST_STD_IDX]) {
|
|
ret.fgColor = new CodedTextColor(match[AST_STD_IDX], false);
|
|
}
|
|
else if (match[AST_EXT_IDX]) {
|
|
ret.fgColor = new CodedTextColor(match[AST_EXT_IDX], true);
|
|
}
|
|
|
|
if (match[AST_BG_STD_IDX]) {
|
|
ret.bgColor = new CodedTextColor(match[AST_BG_STD_IDX], false);
|
|
}
|
|
else if (match[AST_BG_EXT_IDX]) {
|
|
ret.bgColor = new CodedTextColor(match[AST_BG_EXT_IDX], true);
|
|
}
|
|
}
|
|
else if (match[E_IDX]) {
|
|
ret.emphasize = true;
|
|
}
|
|
else if (match[RST_IDX]) {
|
|
ret.fgColor = null;
|
|
ret.bgColor = null;
|
|
ret.attributes = null;
|
|
ret.emphasize = null;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Parse attribute setting
|
|
function parseSetAttr(part, previousSection) {
|
|
var attr = part[0];
|
|
var ret = previousSection.clone();
|
|
ret.str = part.substr(1);
|
|
|
|
if (ret.attributes === null) {
|
|
ret.attributes = [attr];
|
|
}
|
|
else {
|
|
ret.attributes.push(attr);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Parse attribute removal
|
|
function parseRemoveAttr(part, previousSection) {
|
|
var attr = part[0];
|
|
var ret = previousSection.clone();
|
|
ret.str = part.substr(1);
|
|
|
|
if (ret.attributes === null) {
|
|
return ret;
|
|
}
|
|
else {
|
|
var i = ret.attributes.find(attr);
|
|
|
|
if (i >= 0) {
|
|
ret.attributes.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Convert a list of sections into Qt styled text and
|
|
// return as a string
|
|
// TODO: Currently strips all styling
|
|
function codedText2StyledText(sectionList) {
|
|
var ret = '';
|
|
|
|
for (var i = 0; i < sectionList.length; ++i) {
|
|
var section = sectionList[i];
|
|
ret += section.str;
|
|
}
|
|
|
|
return ret;
|
|
}
|