获取指定索引处字符的 ANSI 颜色

3

我开发了一个couleurs NPM包,可以设置为将rgb方法附加到String.prototype

> console.log("Hello World!".rgb(255, 0, 0)) // "Hello World!" in red
Hello World!
undefined
> "Hello World!".rgb(255, 0, 0)
'\u001b[38;5;196mHello World!\u001b[0m'

这个工作良好。如何正确地获取索引 i 处字符的 ANSI 颜色/样式?

可能可以使用一些正则表达式来解决,但我不确定这是否真的好(但如果有正确的实现可用,我不反对)... 我更喜欢通过访问 tty 解释的字符来获得颜色/样式的本地化方法。

> function getStyle (input, i) { /* get style at index `i` */ return style; }

> getStyle("Hello World!".rgb(255, 0, 0), 0); // Get style of the first char
{
   start: "\u001b[38;5;196m",
   end: "\u001b[0m",
   char: "H"
}
> getStyle("Hello " + "World!".rgb(255, 0, 0), 0); // Get style of the first char
{
   start: "",
   end: "",
   char: "H"
}

当我们有多种组合样式时,事情变得复杂了:

> console.log("Green and Italic".rgb(0, 255, 0).italic())
Green and Italic
undefined
> getStyle("Green and Italic".rgb(0, 255, 0).italic(), 0);
{
   start: "\u001b[3m\u001b[38;5;46m",
   end: "\u001b[0m\u001b[23m",
   char: "G"
}
> getStyle(("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0), 0);
{
   start: "\u001b[38;5;196m\u001b[1m",
   end: "\u001b[22m\u001b[0m",
   char: "B"
}
> getStyle(("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0), 11);
{
   start: "\u001b[38;5;196m",
   end: "\u001b[0m",
   char: "u"
}
> ("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0)
'\u001b[38;5;196m\u001b[1mBold & Red\u001b[22m but this one is only red\u001b[0m'

如我所说,我正在寻找一种本地方式(可能使用子进程)。

那么,如何获取索引为i的字符的完整ANSI样式?

2个回答

2

有几种方法可以向文本添加格式,这是其中一种方法。问题在于,您将文本和样式混合到同一个对象中——文本字符串。这类似于RTF。

Here is some \b bold\b0 and {\i italic} text\par

但与 Word 的本地格式 .DOC 文件不同,其使用的是文本运行 text runs

(text) Here is some bold and italic text\r
(chp)  13 None
       4  sprmCFBold
       5  None
       6  sprmCFItalic
       6  None

-- 左侧数字是具有特定格式的字符数计数。
后一种格式才是您要查找的,因为您要索引纯文本中的字符。减去格式长度将显示您所感兴趣的内容。根据您期望多少次需要格式化,你可以仅进行单次运行,或在某个地方缓存格式化文本。
单次运行需要检查编码字符串的每个元素,在不在颜色字符串内时递增“文本”索引,并在上次看到颜色字符串时更新颜色字符串。我添加了一个兼容的 getCharAt 函数以用于调试目的。
var str = '\u001b[38;5;196m\u001b[1mBo\x1B[22mld & Red\u001b[22m but this one is only red\u001b[0m';

const map = {
    bold: ["\x1B[1m", "\x1B[22m" ]
  , italic: ["\x1B[3m", "\x1B[23m" ]
  , underline: ["\x1B[4m", "\x1B[24m" ]
  , inverse: ["\x1B[7m", "\x1B[27m" ]
  , strikethrough: ["\x1B[9m", "\x1B[29m" ]
};

String.prototype.getColorAt = function(index)
{
    var strindex=0, color=[], cmatch, i,j;

    while (strindex < this.length)
    {
        cmatch = this.substr(strindex).match(/^(\u001B\[[^m]*m)/);
        if (cmatch)
        {
            // Global reset?
            if (cmatch[0] == '\x1B[0m')
            {
                color = [];
            } else
            {
                // Off code?
                for (i=0; i<map.length; i++)
                {
                    if (map[i][1] == cmatch[0])
                    {
                        // Remove On code?
                        for (j=color.length-1; j>=0; j--)
                        {
                            if (color[j] == map[i][0])
                                color.splice (j,1);
                        }
                        break;
                    }
                }
                if (j==map.length)
                    color.push (cmatch[0]);
            }
            strindex += cmatch[0].length;
        } else
        {
            /* a regular character! */
            if (!index)
                break;
            strindex++;
            index--;
        }
    }
    return color.join('');
}

String.prototype.getCharAt = function(index)
{
    var strindex=0, cmatch;

    while (strindex < this.length)
    {
        cmatch = this.substr(strindex).match(/^(\u001B\[[^m]*m)/);
        if (cmatch)
        {
            strindex += cmatch[0].length;
        } else
        {
            /* a regular character! */
            if (!index)
                return this.substr(strindex,1);
            strindex++;
            index--;
        }
    }
    return '';
}

console.log (str);

color = str.getColorAt (1);
text = str.getCharAt (1);
console.log ('color is '+color+color.length+', char is '+text);

返回的color仍然保持其原始转义编码。您可以通过将以下内容添加到原始map数组中使其返回某种常量。


据我所见,返回的字符串应该是在“index”处的字符之前设置的最后一个ANSI颜色序列。难道不是这样吗? - Jongware
我的之前的代码在没有进一步检查的情况下连接了所有匹配项,我现在已经添加了这个。更好了吗? - Jongware
很好,为什么你在map.length总是undefined的情况下使用它?我猜我们可以删除与之相关的map对象和for循环。 - Ionică Bizău
你有 GitHub 账户吗?我将在 NPM 模块 中使用此代码,并将您添加为贡献者。 - Ionică Bizău
我已经把你添加到这里。 :-) 非常感谢! - Ionică Bizău
显示剩余3条评论

1

我无法为您提供完整的解决方案,但以下是一个草图:

  • 维护一个堆栈来累积当前格式
  • 将字符串分成块 转义序列 | 一个字符
  • 遍历这个块列表
  • 如果它只是一个字符,则保存其索引+堆栈的当前状态
  • 如果它是一个转义符,则将相应的格式推入堆栈或从堆栈中弹出格式

您也可以使用此算法将转义字符串转换为html,并使用XML方法遍历结果树。

顺便说一句,反过来也很好,请问您怎么看:

console.log("<font color='red'>hi <b>there</b></font>".toANSI())

有意思的想法,于是我尝试将它加入我的解决方案中。对于标准属性,它只需要在map数组中添加其名称;但是我在尝试寻找颜色的简单解决方案时遇到了问题,因为没有清晰的“关闭”代码。(HTML也没有inverse但可以用span进行模拟。) - Jongware

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接