harbour-weechatrelay/qml/js/weechatcolors.js

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;
}