wrapAnsiPreserving function
Wraps a string to width while preserving ANSI pen state (SGR + OSC 8)
across inserted and existing newlines.
Implementation
String wrapAnsiPreserving(String input, int width, {String breakpoints = ''}) {
if (width <= 0) return input;
final out = StringBuffer();
var style = const UvStyle();
var link = const Link();
void resetIfNeeded() {
if (!style.isZero) out.write(UvAnsi.resetStyle);
if (!link.isZero) out.write(UvAnsi.resetHyperlink());
}
void reapplyIfNeeded() {
if (!link.isZero) out.write(UvAnsi.setHyperlink(link.url, link.params));
if (!style.isZero) out.write(_styleToSgr(style));
}
// Tokenize input into a stream of "units" with display widths.
final tokens = _tokenize(input);
final breakChars = <int>{
0x20, // space
...uni.codePoints(breakpoints),
};
var lineStart = 0;
var lineWidth = 0;
int? lastBreakToken;
int? widthAtBreak;
void emitRange(int start, int endExclusive) {
for (var i = start; i < endExclusive; i++) {
final t = tokens[i];
if (t.kind == _TokenKind.csiSgr) {
style = _applySgr(t.payload, style);
out.write(t.raw);
} else if (t.kind == _TokenKind.osc8) {
link = _applyOsc8(t.payload);
out.write(t.raw);
} else if (t.kind == _TokenKind.newline) {
resetIfNeeded();
out.write('\n');
reapplyIfNeeded();
} else {
out.write(t.raw);
}
}
}
var i = 0;
while (i < tokens.length) {
final t = tokens[i];
if (t.kind == _TokenKind.newline) {
emitRange(lineStart, i + 1);
i++;
lineStart = i;
lineWidth = 0;
lastBreakToken = null;
widthAtBreak = null;
continue;
}
final w = t.visibleWidth;
if (t.kind == _TokenKind.text && breakChars.contains(t.rune)) {
lastBreakToken = i;
widthAtBreak = lineWidth + w;
}
if (lineWidth + w > width) {
if (lastBreakToken != null &&
widthAtBreak != null &&
lastBreakToken >= lineStart) {
// Emit up to break, drop the break token itself.
emitRange(lineStart, lastBreakToken);
resetIfNeeded();
out.write('\n');
reapplyIfNeeded();
i = lastBreakToken + 1;
lineStart = i;
lineWidth = 0;
lastBreakToken = null;
widthAtBreak = null;
continue;
}
// Hard break at current token.
emitRange(lineStart, i);
resetIfNeeded();
out.write('\n');
reapplyIfNeeded();
lineStart = i;
lineWidth = 0;
lastBreakToken = null;
widthAtBreak = null;
continue;
}
lineWidth += w;
i++;
}
emitRange(lineStart, tokens.length);
resetIfNeeded();
return out.toString();
}