diff --git a/packages/flutter/lib/src/gestures/binding.dart b/packages/flutter/lib/src/gestures/binding.dart index b1d9ee56e2..567d08aea1 100644 --- a/packages/flutter/lib/src/gestures/binding.dart +++ b/packages/flutter/lib/src/gestures/binding.dart @@ -135,7 +135,7 @@ class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails { this.event, this.hitTestEntry, FlutterInformationCollector informationCollector, - bool silent + bool silent: false }) : super( exception: exception, stack: stack, diff --git a/packages/flutter/lib/src/gestures/pointer_router.dart b/packages/flutter/lib/src/gestures/pointer_router.dart index b791b55df1..37d5cf7865 100644 --- a/packages/flutter/lib/src/gestures/pointer_router.dart +++ b/packages/flutter/lib/src/gestures/pointer_router.dart @@ -90,7 +90,7 @@ class FlutterErrorDetailsForPointerRouter extends FlutterErrorDetails { this.route, this.event, FlutterInformationCollector informationCollector, - bool silent + bool silent: false }) : super( exception: exception, stack: stack, diff --git a/packages/flutter/lib/src/material/debug.dart b/packages/flutter/lib/src/material/debug.dart index ce8532913e..c890f0c01d 100644 --- a/packages/flutter/lib/src/material/debug.dart +++ b/packages/flutter/lib/src/material/debug.dart @@ -20,7 +20,7 @@ bool debugCheckHasMaterial(BuildContext context) { 'To introduce a Material widget, you can either directly include one, or use a widget that contains Material itself, ' 'such as a Card, Dialog, Drawer, or Scaffold.\n' 'The specific widget that could not find a Material ancestor was:\n' - ' ${context.widget}' + ' ${context.widget}\n' 'The ownership chain for the affected widget is:\n' ' ${element.debugGetCreatorChain(10)}' ); @@ -39,7 +39,7 @@ bool debugCheckHasScaffold(BuildContext context) { 'No Scaffold widget found.\n' '${context.widget.runtimeType} widgets require a Scaffold widget ancestor.\n' 'The specific widget that could not find a Scaffold ancestor was:\n' - ' ${context.widget}' + ' ${context.widget}\n' 'The ownership chain for the affected widget is:\n' ' ${element.debugGetCreatorChain(10)}' ); diff --git a/packages/flutter/lib/src/rendering/custom_layout.dart b/packages/flutter/lib/src/rendering/custom_layout.dart index e87c145544..57b73c257c 100644 --- a/packages/flutter/lib/src/rendering/custom_layout.dart +++ b/packages/flutter/lib/src/rendering/custom_layout.dart @@ -46,7 +46,7 @@ abstract class MultiChildLayoutDelegate { assert(() { if (child == null) { throw new FlutterError( - 'The $this custom multichild layout delegate tried to lay out a non-existent child:\n' + 'The $this custom multichild layout delegate tried to lay out a non-existent child.\n' 'There is no child with the id "$childId".' ); } @@ -60,7 +60,7 @@ abstract class MultiChildLayoutDelegate { assert(constraints.debugAssertIsNormalized); } on AssertionError catch (exception) { throw new FlutterError( - 'The $this custom multichild layout delegate provided invalid box constraints for the child with id "$childId":\n' + 'The $this custom multichild layout delegate provided invalid box constraints for the child with id "$childId".\n' '$exception\n' 'The minimum width and height must be greater than or equal to zero.\n' 'The maximum width must be greater than or equal to the minimum width.\n' diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index f38fc662b6..439a7ac3f1 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -889,7 +889,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { informationCollector: (StringBuffer information) { information.writeln('The following RenderObject was being processed when the exception was fired:\n${this}'); if (debugCreator != null) - information.writeln('This RenderObject had the following creator:\n$debugCreator'); + information.writeln('This RenderObject had the following creator:\n $debugCreator'); List descendants = []; const int maxDepth = 5; int depth = 0; @@ -897,8 +897,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { int lines = 0; void visitor(RenderObject child) { if (lines < maxLines) { - descendants.add('${" " * depth}$child'); depth += 1; + descendants.add('${" " * depth}$child'); if (depth < maxDepth) child.visitChildren(visitor); depth -= 1; @@ -2155,7 +2155,7 @@ class FlutterErrorDetailsForRendering extends FlutterErrorDetails { String context, this.renderObject, FlutterInformationCollector informationCollector, - bool silent + bool silent: false }) : super( exception: exception, stack: stack, diff --git a/packages/flutter/lib/src/services/assertions.dart b/packages/flutter/lib/src/services/assertions.dart index d312ebb214..9cc2b5b7c9 100644 --- a/packages/flutter/lib/src/services/assertions.dart +++ b/packages/flutter/lib/src/services/assertions.dart @@ -124,6 +124,8 @@ class FlutterError extends AssertionError { static int _errorCount = 0; + static const int _kWrapWidth = 120; + /// Prints the given exception details to the console. /// /// The first time this is called, it dumps a very verbose message to the @@ -135,7 +137,7 @@ class FlutterError extends AssertionError { static void dumpErrorToConsole(FlutterErrorDetails details) { assert(details != null); assert(details.exception != null); - bool reportError = !details.silent; + bool reportError = details.silent != true; // could be null assert(() { // In checked mode, we ignore the "silent" flag. reportError = true; @@ -144,19 +146,26 @@ class FlutterError extends AssertionError { if (!reportError) return; if (_errorCount == 0) { - final String header = '-- EXCEPTION CAUGHT BY ${details.library} '.toUpperCase(); - const String footer = '------------------------------------------------------------------------'; - debugPrint('$header${"-" * (footer.length - header.length)}'); - debugPrint('The following exception was raised${ details.context != null ? " ${details.context}" : ""}:'); - debugPrint('${details.exception}'); + final String header = '\u2550\u2550\u2561 EXCEPTION CAUGHT BY ${details.library} \u255E'.toUpperCase(); + final String footer = '\u2501' * _kWrapWidth; + debugPrint('$header${"\u2550" * (footer.length - header.length)}'); + debugPrint('The following exception was raised${ details.context != null ? " ${details.context}" : ""}:', wrapWidth: _kWrapWidth); + debugPrint('${details.exception}', wrapWidth: _kWrapWidth); + if ((details.exception is AssertionError) && (details.exception is! FlutterError)) { + debugPrint('Either the assertion indicates an error in the framework itself, or we should ' + 'provide substantially more information in this error message to help you determine ' + 'and fix the underlying cause.', wrapWidth: _kWrapWidth); + debugPrint('In either case, please report this assertion by filing a bug on GitHub:', wrapWidth: _kWrapWidth); + debugPrint(' https://github.com/flutter/flutter/issues/new'); + } if (details.informationCollector != null) { StringBuffer information = new StringBuffer(); details.informationCollector(information); - debugPrint(information.toString()); + debugPrint(information.toString(), wrapWidth: _kWrapWidth); } if (details.stack != null) { - debugPrint('Stack trace:'); - debugPrint('${details.stack}$footer'); + debugPrint('Stack trace:', wrapWidth: _kWrapWidth); + debugPrint('${details.stack}$footer'); // StackTrace objects include a trailing newline } else { debugPrint(footer); } diff --git a/packages/flutter/lib/src/services/print.dart b/packages/flutter/lib/src/services/print.dart index 4d594a6e01..c96ad379c0 100644 --- a/packages/flutter/lib/src/services/print.dart +++ b/packages/flutter/lib/src/services/print.dart @@ -8,13 +8,20 @@ import 'dart:collection'; /// Prints a message to the console, which you can access using the "flutter" /// tool's "logs" command ("flutter logs"). /// +/// If a wrapWidth is provided, each line of the message is word-wrapped to that +/// width. (Lines may be separated by newline characters, as in '\n'.) +/// /// This function very crudely attempts to throttle the rate at which messages /// are sent to avoid data loss on Android. This means that interleaving calls /// to this function (directly or indirectly via [debugDumpRenderTree] or /// [debugDumpApp]) and to the Dart [print] method can result in out-of-order /// messages in the logs. -void debugPrint(String message) { - _debugPrintBuffer.addAll(message.split('\n')); +void debugPrint(String message, { int wrapWidth }) { + if (wrapWidth != null) { + _debugPrintBuffer.addAll(message.split('\n').expand((String line) => _wordWrap(line, wrapWidth))); + } else { + _debugPrintBuffer.addAll(message.split('\n')); + } if (!_debugPrintScheduled) _debugPrintTask(); } @@ -44,7 +51,81 @@ void _debugPrintTask() { _debugPrintStopwatch.start(); } } +final RegExp _indentPattern = new RegExp('^ *(?:[-+*] |[0-9]+[.):] )?'); +enum _WordWrapParseMode { inSpace, inWord, atBreak } +Iterable _wordWrap(String message, int width) sync* { + if (message.length < width) { + yield message; + return; + } + Match prefixMatch = _indentPattern.matchAsPrefix(message); + String prefix = ' ' * prefixMatch.group(0).length; + int start = 0; + int startForLengthCalculations = 0; + bool addPrefix = false; + int index = prefix.length; + _WordWrapParseMode mode = _WordWrapParseMode.inSpace; + 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; + break; + case _WordWrapParseMode.inWord: // looking for a good break point + while ((index < message.length) && (message[index] != ' ')) + index += 1; + mode = _WordWrapParseMode.atBreak; + break; + 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, before 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) { + yield prefix + message.substring(start, lastWordEnd); + } else { + yield message.substring(start, lastWordEnd); + addPrefix = true; + } + if (lastWordEnd >= message.length) + return; + // 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; + } + break; + } + } +} +/// Dump the current stack to the console using [debugPrint]. +/// +/// The current stack is obtained using [StackTrace.current]. void debugPrintStack() { debugPrint(StackTrace.current.toString()); }