print.dart 7.3 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 DebugPrintCallback = void Function(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
///
/// 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) {
35
    print(message.split('\n').expand<String>((String line) => debugWordWrap(line, wrapWidth)).join('\n'));
36 37 38 39
  } 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
  final List<String> messageLines = message?.split('\n') ?? <String>['null'];
45
  if (wrapWidth != null) {
46
    _debugPrintBuffer.addAll(messageLines.expand<String>((String line) => debugWordWrap(line, wrapWidth)));
47
  } else {
48
    _debugPrintBuffer.addAll(messageLines);
49
  }
50 51 52 53
  if (!_debugPrintScheduled)
    _debugPrintTask();
}
int _debugPrintedCharacters = 0;
54
const int _kDebugPrintCapacity = 12 * 1024;
55
const Duration _kDebugPrintPauseTime = Duration(seconds: 1);
56 57
final Queue<String> _debugPrintBuffer = Queue<String>();
final Stopwatch _debugPrintStopwatch = Stopwatch();
58
Completer<void> _debugPrintCompleter;
59 60 61 62 63 64 65 66
bool _debugPrintScheduled = false;
void _debugPrintTask() {
  _debugPrintScheduled = false;
  if (_debugPrintStopwatch.elapsed > _kDebugPrintPauseTime) {
    _debugPrintStopwatch.stop();
    _debugPrintStopwatch.reset();
    _debugPrintedCharacters = 0;
  }
67
  while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.isNotEmpty) {
68
    final String line = _debugPrintBuffer.removeFirst();
69 70 71
    _debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead
    print(line);
  }
72
  if (_debugPrintBuffer.isNotEmpty) {
73 74
    _debugPrintScheduled = true;
    _debugPrintedCharacters = 0;
75
    Timer(_kDebugPrintPauseTime, _debugPrintTask);
76
    _debugPrintCompleter ??= Completer<void>();
77 78
  } else {
    _debugPrintStopwatch.start();
79 80
    _debugPrintCompleter?.complete();
    _debugPrintCompleter = null;
81 82
  }
}
83

84 85 86
/// 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).
87
Future<void> get debugPrintDone => _debugPrintCompleter?.future ?? Future<void>.value();
88

89
final RegExp _indentPattern = RegExp('^ *(?:[-+*] |[0-9]+[.):] )?');
90
enum _WordWrapParseMode { inSpace, inWord, atBreak }
91

92 93
/// Wraps the given string at the given width.
///
94 95 96
/// 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).
97
///
98 99 100 101
/// 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.
102
///
103 104 105 106 107
/// 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.
108
Iterable<String> debugWordWrap(String message, int width, { String wrapIndent = '' }) sync* {
109
  if (message.length < width || message.trimLeft()[0] == '#') {
110 111 112
    yield message;
    return;
  }
113 114
  final Match prefixMatch = _indentPattern.matchAsPrefix(message);
  final String prefix = wrapIndent + ' ' * prefixMatch.group(0).length;
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
  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)) {
139 140
            // 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
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 175 176 177
            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;
    }
  }
}