debugWordWrap function

Iterable<String> debugWordWrap(
  1. String message,
  2. int width,
  3. {String wrapIndent = ''}
)

Wraps the given string at the given width.

The message should not contain newlines (\n, U+000A). Strings that may contain newlines should be String.split before being wrapped.

Wrapping occurs at space characters (U+0020). Lines that start with an octothorpe ("#", U+0023) are not wrapped (so for example, Dart stack traces won't be wrapped).

Subsequent lines attempt to duplicate the indentation of the first line, for example if the first line starts with multiple spaces. In addition, if a wrapIndent argument is provided, each line after the first is prefixed by that string.

This is not suitable for use with arbitrary Unicode text. For example, it doesn't implement UAX #14, can't handle ideographic text, doesn't hyphenate, and so forth. It is only intended for formatting error messages.

The default debugPrint implementation uses this for its line wrapping.

Implementation

Iterable<String> debugWordWrap(String message, int width, { String wrapIndent = '' }) {
  if (message.length < width || message.trimLeft()[0] == '#') {
    return <String>[message];
  }
  final List<String> wrapped = <String>[];
  final Match prefixMatch = _indentPattern.matchAsPrefix(message)!;
  final String prefix = wrapIndent + ' ' * prefixMatch.group(0)!.length;
  int start = 0;
  int startForLengthCalculations = 0;
  bool addPrefix = false;
  int index = prefix.length;
  _WordWrapParseMode mode = _WordWrapParseMode.inSpace;
  late int lastWordStart;
  int? lastWordEnd;
  while (true) {
    switch (mode) {
      case _WordWrapParseMode.inSpace: // at start of break point (or start of line); can't break until next break
        while ((index < message.length) && (message[index] == ' ')) {
          index += 1;
        }
        lastWordStart = index;
        mode = _WordWrapParseMode.inWord;
      case _WordWrapParseMode.inWord: // looking for a good break point
        while ((index < message.length) && (message[index] != ' ')) {
          index += 1;
        }
        mode = _WordWrapParseMode.atBreak;
      case _WordWrapParseMode.atBreak: // at start of break point
        if ((index - startForLengthCalculations > width) || (index == message.length)) {
          // we are over the width line, so break
          if ((index - startForLengthCalculations <= width) || (lastWordEnd == null)) {
            // we should use this point, because either it doesn't actually go over the
            // end (last line), or it does, but there was no earlier break point
            lastWordEnd = index;
          }
          if (addPrefix) {
            wrapped.add(prefix + message.substring(start, lastWordEnd));
          } else {
            wrapped.add(message.substring(start, lastWordEnd));
            addPrefix = true;
          }
          if (lastWordEnd >= message.length) {
            return wrapped;
          }
          // just yielded a line
          if (lastWordEnd == index) {
            // we broke at current position
            // eat all the spaces, then set our start point
            while ((index < message.length) && (message[index] == ' ')) {
              index += 1;
            }
            start = index;
            mode = _WordWrapParseMode.inWord;
          } else {
            // we broke at the previous break point, and we're at the start of a new one
            assert(lastWordStart > lastWordEnd);
            start = lastWordStart;
            mode = _WordWrapParseMode.atBreak;
          }
          startForLengthCalculations = start - prefix.length;
          assert(addPrefix);
          lastWordEnd = null;
        } else {
          // save this break point, we're not yet over the line width
          lastWordEnd = index;
          // skip to the end of this break point
          mode = _WordWrapParseMode.inSpace;
        }
    }
  }
}