print.dart 7.22 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:collection';

8
/// Signature for [debugPrint] implementations.
9
typedef void DebugPrintCallback(String message, { int wrapWidth });
10

11 12 13
/// Prints a message to the console, which you can access using the "flutter"
/// tool's "logs" command ("flutter logs").
///
14 15 16
/// 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'.)
///
17 18
/// By default, this function very crudely attempts to throttle the rate at
/// which messages are sent to avoid data loss on Android. This means that
19
/// interleaving calls to this function (directly or indirectly via, e.g.,
20 21
/// [debugDumpRenderTree] or [debugDumpApp]) and to the Dart [print] method can
/// result in out-of-order messages in the logs.
22 23
///
/// The implementation of this function can be replaced by setting the
24
/// [debugPrint] variable to a new implementation that matches the
25
/// [DebugPrintCallback] signature. For example, flutter_test does this.
26 27 28 29 30 31 32 33 34 35 36 37 38 39
///
/// The default value is [debugPrintThrottled]. For a version that acts
/// identically but does not throttle, use [debugPrintSynchronously].
DebugPrintCallback debugPrint = debugPrintThrottled;

/// Alternative implementation of [debugPrint] that does not throttle.
/// Used by tests.
void debugPrintSynchronously(String message, { int wrapWidth }) {
  if (wrapWidth != null) {
    print(message.split('\n').expand((String line) => debugWordWrap(line, wrapWidth)).join('\n'));
  } else {
    print(message);
  }
}
40

41 42 43
/// Implementation of [debugPrint] that throttles messages. This avoids dropping
/// messages on platforms that rate-limit their logging (for example, Android).
void debugPrintThrottled(String message, { int wrapWidth }) {
44
  if (wrapWidth != null) {
45
    _debugPrintBuffer.addAll(message.split('\n').expand((String line) => debugWordWrap(line, wrapWidth)));
46 47 48
  } else {
    _debugPrintBuffer.addAll(message.split('\n'));
  }
49 50 51 52
  if (!_debugPrintScheduled)
    _debugPrintTask();
}
int _debugPrintedCharacters = 0;
53
const int _kDebugPrintCapacity = 12 * 1024;
Adam Barth's avatar
Adam Barth committed
54 55 56
const Duration _kDebugPrintPauseTime = const Duration(seconds: 1);
final Queue<String> _debugPrintBuffer = new Queue<String>();
final Stopwatch _debugPrintStopwatch = new Stopwatch();
57
Completer<Null> _debugPrintCompleter;
58 59 60 61 62 63 64 65
bool _debugPrintScheduled = false;
void _debugPrintTask() {
  _debugPrintScheduled = false;
  if (_debugPrintStopwatch.elapsed > _kDebugPrintPauseTime) {
    _debugPrintStopwatch.stop();
    _debugPrintStopwatch.reset();
    _debugPrintedCharacters = 0;
  }
66
  while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.isNotEmpty) {
67
    final String line = _debugPrintBuffer.removeFirst();
68 69 70
    _debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead
    print(line);
  }
71
  if (_debugPrintBuffer.isNotEmpty) {
72 73 74
    _debugPrintScheduled = true;
    _debugPrintedCharacters = 0;
    new Timer(_kDebugPrintPauseTime, _debugPrintTask);
75
    _debugPrintCompleter ??= new Completer<Null>();
76 77
  } else {
    _debugPrintStopwatch.start();
78 79
    _debugPrintCompleter?.complete();
    _debugPrintCompleter = null;
80 81
  }
}
82

83 84 85 86 87
/// A Future that resolves when there is no longer any buffered content being
/// printed by [debugPrintThrottled] (which is the default implementation for
/// [debugPrint], which is used to report errors to the console).
Future<Null> get debugPrintDone => _debugPrintCompleter?.future ?? new Future<Null>.value();

88 89
final RegExp _indentPattern = new RegExp('^ *(?:[-+*] |[0-9]+[.):] )?');
enum _WordWrapParseMode { inSpace, inWord, atBreak }
90 91
/// Wraps the given string at the given width.
///
92 93 94
/// 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).
95
///
96 97 98 99
/// 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.
100
///
101 102 103 104 105
/// 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.
106
Iterable<String> debugWordWrap(String message, int width, { String wrapIndent = '' }) sync* {
107
  if (message.length < width || message.trimLeft()[0] == '#') {
108 109 110
    yield message;
    return;
  }
111 112
  final Match prefixMatch = _indentPattern.matchAsPrefix(message);
  final String prefix = wrapIndent + ' ' * prefixMatch.group(0).length;
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
  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;
    }
  }
}