Unverified Commit 5d8dfa44 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

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

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