wrapAnsiPreserving function

String wrapAnsiPreserving(
  1. String input,
  2. int width, {
  3. String breakpoints = '',
})

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