1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
// 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';
/// Signature for [debugPrint] implementations.
typedef void DebugPrintCallback(String message, { int wrapWidth });
/// 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'.)
///
/// 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
/// interleaving calls to this function (directly or indirectly via, e.g.,
/// [debugDumpRenderTree] or [debugDumpApp]) and to the Dart [print] method can
/// result in out-of-order messages in the logs.
///
/// The implementation of this function can be replaced by setting the
/// [debugPrint] variable to a new implementation that matches the
/// [DebugPrintCallback] signature. For example, flutter_test does this.
///
/// 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);
}
}
/// 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 }) {
if (wrapWidth != null) {
_debugPrintBuffer.addAll(message.split('\n').expand((String line) => debugWordWrap(line, wrapWidth)));
} else {
_debugPrintBuffer.addAll(message.split('\n'));
}
if (!_debugPrintScheduled)
_debugPrintTask();
}
int _debugPrintedCharacters = 0;
const int _kDebugPrintCapacity = 12 * 1024;
const Duration _kDebugPrintPauseTime = const Duration(seconds: 1);
final Queue<String> _debugPrintBuffer = new Queue<String>();
final Stopwatch _debugPrintStopwatch = new Stopwatch();
Completer<Null> _debugPrintCompleter;
bool _debugPrintScheduled = false;
void _debugPrintTask() {
_debugPrintScheduled = false;
if (_debugPrintStopwatch.elapsed > _kDebugPrintPauseTime) {
_debugPrintStopwatch.stop();
_debugPrintStopwatch.reset();
_debugPrintedCharacters = 0;
}
while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.isNotEmpty) {
final String line = _debugPrintBuffer.removeFirst();
_debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead
print(line);
}
if (_debugPrintBuffer.isNotEmpty) {
_debugPrintScheduled = true;
_debugPrintedCharacters = 0;
new Timer(_kDebugPrintPauseTime, _debugPrintTask);
_debugPrintCompleter ??= new Completer<Null>();
} else {
_debugPrintStopwatch.start();
_debugPrintCompleter?.complete();
_debugPrintCompleter = null;
}
}
/// 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();
final RegExp _indentPattern = new RegExp('^ *(?:[-+*] |[0-9]+[.):] )?');
enum _WordWrapParseMode { inSpace, inWord, atBreak }
/// Wraps the given string at the given width.
///
/// 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).
///
/// 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.
///
/// 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.
Iterable<String> debugWordWrap(String message, int width, { String wrapIndent: '' }) sync* {
if (message.length < width || message.trimLeft()[0] == '#') {
yield message;
return;
}
final Match prefixMatch = _indentPattern.matchAsPrefix(message);
final String prefix = wrapIndent + ' ' * 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;
}
}
}