I need a wordwrap because I want to align the text (mostly build tools outputs) on a specific colon on the screen. It’s easy with a bit of regex.
const regex = /(.{1,19}[ \n/\\]|.{20})/g;
It wraps if there is at least a character of this list ' '
, '\n'
, '/'
or
'\\'
otherwise it cuts at the position 20.
Stupid example:
Build output for project: /home/foobar/devel/my_project
Compile /home/foobar/devel/my_project/toto.c
Compile /home/foobar/devel/my_project/lib/blabla.c
With a wrap for 20 chars max:
Build output for
project: /home/
foobar/devel/
my_project
Compile /home/
foobar/devel/
my_project/toto.c
Compile /home/
foobar/devel/
my_project/lib/
blabla.c
It’s very simple and the job is mostly done. Mostly because there is an other case where it’s a bit more difficult. I like colors in my outputs but I will preserve a correct wordwrap.
The same example with ANSI colors:
\u001b[31mBuild\u001b[0m output for project: \u001b[32m/home/foobar/devel/my_project\u001b[0m
\u001b[31mCompile\u001b[0m \u001b[32m/home/foobar/devel/my_project/toto.c\u001b[0m
\u001b[31mCompile\u001b[0m \u001b[32m/home/foobar/devel/my_project/lib/blabla.c\u001b[0m
But the result is weird because the colors should not be considered in the regex:
\u001b[31mBuild\
u001b[0m output for
project: \u001b[32m/
home/foobar/devel/
my_project\u001b[0m
\u001b[31mCompile\
u001b[0m \u001b[32m/
home/foobar/devel/
my_project/toto.c\
u001b[0m
\u001b[31mCompile\
u001b[0m \u001b[32m/
home/foobar/devel/
my_project/lib/
blabla.c\u001b[0m
The visible result:
Build
output for
project: /
home/foobar/devel/
my_project
Compile\
/
home/foobar/devel/
my_project/toto.c\
Compile\
home/foobar/devel/
my_project/lib/
blabla.c
The idea is to find all color positions in order to restore the patterns after the wordwrap. It means that the wordwrap must be done only on a text without ANSI colors.
Here the regex to strip the ANSI colors:
const regexAnsi = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
It comes from ansi-regex. Then it’s possible to generate an array of position with our original text.
const ansiRegex = require("ansi-regex");
function colorIndexes(text) {
const regex = ansiRegex();
const list = [];
let res;
while ((res = regex.exec(text))) {
list.push({
color: res[0],
index: res.index,
});
}
return list;
}
const colors = colorIndexes(text);
Here the output with our text:
[
{ color: "\u001b[31m", index: 0 },
{ color: "\u001b[0m", index: 10 },
{ color: "\u001b[32m", index: 35 },
{ color: "\u001b[0m", index: 69 },
{ color: "\u001b[31m", index: 74 },
{ color: "\u001b[0m", index: 86 },
{ color: "\u001b[32m", index: 91 },
{ color: "\u001b[0m", index: 132 },
{ color: "\u001b[31m", index: 137 },
{ color: "\u001b[0m", index: 149 },
{ color: "\u001b[32m", index: 154 },
{ color: "\u001b[0m", index: 201 },
];
Then we can strip the ANSI colors, apply the regex wordwrap and restore the colors.
Strip the ANSI colors
text = text.replace(ansiRegex(), "");
Apply the regex worwrap
let output = "";
const regex = /(.{1,19}[ \n/\\]|.{20})/g;
const matches = text.match(regex) || [text];
matches.forEach((part, index) => {
output += part;
if (index < matches.length - 1) {
output += "\n";
}
});
The forEach
is necessary in order to produce the output
with the new
linefeeds. Here we have a correct output but without the colors.
Restore the colors
const colors = colorIndexes(text);
let colorsOffset = 0;
let output = "";
const regex = /(.{1,19}[ \n/\\]|.{20})/g;
const matches = text.match(regex) || [text];
matches.forEach((part, index) => {
output += part;
/* Restore the colors */
while (colors.length) {
const offset = colors[0].index + colorsOffset;
if (offset >= output.length) {
break;
}
output = output.substr(0, offset) + colors[0].color + output.substr(offset);
colors.shift();
}
if (index < matches.length - 1) {
output += "\n";
colorsOffset++;
}
});
The colors are removed from the list colors
step by step accordingly to the
current offset.