debugWordWrap function
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;
}
}
}
}