Wordwrap for console outputs
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
Handling ANSI colors
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.