Don't crash on narrow window widths (#74365)

This commit is contained in:
Ian Hickson 2021-01-25 11:39:45 -08:00 committed by GitHub
parent b400534dc7
commit 5d8dfa44b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 17 deletions

View file

@ -3,7 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:math' show max;
import 'dart:math' as math;
import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
@ -159,9 +159,10 @@ const int kMinColumnWidth = 10;
/// Wraps a block of text into lines no longer than [columnWidth].
///
/// Tries to split at whitespace, but if that's not good enough to keep it
/// under the limit, then it splits in the middle of a word. If [columnWidth] is
/// smaller than 10 columns, will wrap at 10 columns.
/// Tries to split at whitespace, but if that's not good enough to keep it under
/// the limit, then it splits in the middle of a word. If [columnWidth] (minus
/// any indent) is smaller than [kMinColumnWidth], the text is wrapped at that
/// [kMinColumnWidth] instead.
///
/// Preserves indentation (leading whitespace) for each line (delimited by '\n')
/// in the input, and will indent wrapped lines that same amount, adding
@ -190,21 +191,20 @@ const int kMinColumnWidth = 10;
/// unchanged. If [shouldWrap] is specified, then it overrides the
/// [outputPreferences.wrapText] setting.
///
/// The [indent] and [hangingIndent] must be smaller than [columnWidth] when
/// added together.
/// If the amount of indentation (from the text, [indent], and [hangingIndent])
/// is such that less than [kMinColumnWidth] characters can fit in the
/// [columnWidth], then the indent is truncated to allow the text to fit.
String wrapText(String text, {
@required int columnWidth,
@required bool shouldWrap,
int hangingIndent,
int indent,
}) {
assert(columnWidth >= 0);
if (text == null || text.isEmpty) {
return '';
}
indent ??= 0;
columnWidth -= indent;
assert(columnWidth >= 0);
hangingIndent ??= 0;
final List<String> splitText = text.split('\n');
final List<String> result = <String>[];
@ -218,34 +218,38 @@ String wrapText(String text, {
// them twice and recombine.
final List<String> firstLineWrap = _wrapTextAsLines(
trimmedText,
columnWidth: columnWidth - leadingWhitespace.length,
columnWidth: columnWidth - leadingWhitespace.length - indent,
shouldWrap: shouldWrap,
);
notIndented = <String>[firstLineWrap.removeAt(0)];
trimmedText = trimmedText.substring(notIndented[0].length).trimLeft();
if (firstLineWrap.isNotEmpty) {
if (trimmedText.isNotEmpty) {
notIndented.addAll(_wrapTextAsLines(
trimmedText,
columnWidth: columnWidth - leadingWhitespace.length - hangingIndent,
columnWidth: columnWidth - leadingWhitespace.length - indent - hangingIndent,
shouldWrap: shouldWrap,
));
}
} else {
notIndented = _wrapTextAsLines(
trimmedText,
columnWidth: columnWidth - leadingWhitespace.length,
columnWidth: columnWidth - leadingWhitespace.length - indent,
shouldWrap: shouldWrap,
);
}
String hangingIndentString;
final String indentString = ' ' * indent;
result.addAll(notIndented.map(
result.addAll(notIndented.map<String>(
(String line) {
// Don't return any lines with just whitespace on them.
if (line.isEmpty) {
return '';
}
final String result = '$indentString${hangingIndentString ?? ''}$leadingWhitespace$line';
String truncatedIndent = '$indentString${hangingIndentString ?? ''}$leadingWhitespace';
if (truncatedIndent.length > columnWidth - kMinColumnWidth) {
truncatedIndent = truncatedIndent.substring(0, math.max(columnWidth - kMinColumnWidth, 0));
}
final String result = '$truncatedIndent$line';
hangingIndentString ??= ' ' * hangingIndent;
return result;
},
@ -274,6 +278,9 @@ class _AnsiRun {
/// terminal window by default. If the stdout is not a terminal window, then the
/// default will be [outputPreferences.wrapColumn].
///
/// The [columnWidth] is clamped to [kMinColumnWidth] at minimum (so passing negative
/// widths is fine, for instance).
///
/// If [outputPreferences.wrapText] is false, then the text will be returned
/// simply split at the newlines, but not wrapped. If [shouldWrap] is specified,
/// then it overrides the [outputPreferences.wrapText] setting.
@ -286,7 +293,6 @@ List<String> _wrapTextAsLines(String text, {
return <String>[''];
}
assert(columnWidth != null);
assert(columnWidth >= 0);
assert(start >= 0);
// Splits a string so that the resulting list has the same number of elements
@ -325,7 +331,7 @@ List<String> _wrapTextAsLines(String text, {
}
final List<String> result = <String>[];
final int effectiveLength = max(columnWidth - start, kMinColumnWidth);
final int effectiveLength = math.max(columnWidth - start, kMinColumnWidth);
for (final String line in text.split('\n')) {
// If the line is short enough, even with ANSI codes, then we can just add
// add it and move on.

View file

@ -248,5 +248,78 @@ needs to be wrapped.
01234567890123456789012345678901234567
890123456789'''));
});
testWithoutContext('', () {
expect(wrapText(
' ' * 7 + 'abc def ghi', columnWidth: 20, hangingIndent: 5, indent: 3, shouldWrap: true),
equals(
' abc def\n'
' ghi'
),
);
expect(wrapText(
'abc def ghi', columnWidth: 0, hangingIndent: 5, shouldWrap: true),
equals(
'abc def\n'
'ghi'
),
);
expect(wrapText(
'abc def ghi', columnWidth: 0, indent: 5, shouldWrap: true),
equals(
'abc def\n'
'ghi'
),
);
expect(wrapText(
' abc def ghi', columnWidth: 0, shouldWrap: true),
equals(
'abc def\n'
'ghi'
),
);
expect(wrapText(
'abc def ghi', columnWidth: kMinColumnWidth - 2, hangingIndent: 5, shouldWrap: true),
equals(
'abc def\n'
'ghi'
),
);
expect(wrapText(
'abc def ghi', columnWidth: kMinColumnWidth - 2, indent: 5, shouldWrap: true),
equals(
'abc def\n'
'ghi'
),
);
expect(wrapText(
' abc def ghi', columnWidth: kMinColumnWidth - 2, shouldWrap: true),
equals(
'abc def\n'
'ghi'
),
);
expect(wrapText(
'abc def ghi jkl', columnWidth: kMinColumnWidth + 2, hangingIndent: 5, shouldWrap: true),
equals(
'abc def ghi\n'
' jkl'
),
);
expect(wrapText(
'abc def ghi', columnWidth: kMinColumnWidth + 2, indent: 5, shouldWrap: true),
equals(
' abc def\n'
' ghi'
),
);
expect(wrapText(
' abc def ghi', columnWidth: kMinColumnWidth + 2, shouldWrap: true),
equals(
' abc def\n'
' ghi'
),
);
});
});
}