1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'dart:developer'; |
6 | /// |
7 | /// @docImport 'package:flutter/rendering.dart'; |
8 | /// @docImport 'package:flutter/widgets.dart'; |
9 | library; |
10 | |
11 | import 'dart:math' as math; |
12 | import 'dart:ui' show clampDouble; |
13 | |
14 | import 'package:meta/meta.dart' ; |
15 | |
16 | import 'assertions.dart'; |
17 | import 'constants.dart'; |
18 | import 'debug.dart'; |
19 | import 'object.dart'; |
20 | |
21 | // Examples can assume: |
22 | // late int rows, columns; |
23 | // late String _name; |
24 | // late bool inherit; |
25 | // abstract class ExampleSuperclass with Diagnosticable { } |
26 | // late String message; |
27 | // late double stepWidth; |
28 | // late double scale; |
29 | // late double hitTestExtent; |
30 | // late double paintExtent; |
31 | // late double maxWidth; |
32 | // late double progress; |
33 | // late int maxLines; |
34 | // late Duration duration; |
35 | // late int depth; |
36 | // late bool primary; |
37 | // late bool isCurrent; |
38 | // late bool keepAlive; |
39 | // late bool obscureText; |
40 | // late TextAlign textAlign; |
41 | // late ImageRepeat repeat; |
42 | // late Widget widget; |
43 | // late List boxShadow; |
44 | // late Size size; |
45 | // late bool hasSize; |
46 | // late Matrix4 transform; |
47 | // late Color color; |
48 | // late Map? handles; |
49 | // late DiagnosticsTreeStyle style; |
50 | // late IconData icon; |
51 | // late double devicePixelRatio; |
52 | |
53 | /// The various priority levels used to filter which diagnostics are shown and |
54 | /// omitted. |
55 | /// |
56 | /// Trees of Flutter diagnostics can be very large so filtering the diagnostics |
57 | /// shown matters. Typically filtering to only show diagnostics with at least |
58 | /// level [debug] is appropriate. |
59 | /// |
60 | /// In release mode, this level may not have any effect, as diagnostics in |
61 | /// release mode are compacted or truncated to reduce binary size. |
62 | enum DiagnosticLevel { |
63 | /// Diagnostics that should not be shown. |
64 | /// |
65 | /// If a user chooses to display [hidden] diagnostics, they should not expect |
66 | /// the diagnostics to be formatted consistently with other diagnostics and |
67 | /// they should expect them to sometimes be misleading. For example, |
68 | /// [FlagProperty] and [ObjectFlagProperty] have uglier formatting when the |
69 | /// property `value` does not match a value with a custom flag |
70 | /// description. An example of a misleading diagnostic is a diagnostic for |
71 | /// a property that has no effect because some other property of the object is |
72 | /// set in a way that causes the hidden property to have no effect. |
73 | hidden, |
74 | |
75 | /// A diagnostic that is likely to be low value but where the diagnostic |
76 | /// display is just as high quality as a diagnostic with a higher level. |
77 | /// |
78 | /// Use this level for diagnostic properties that match their default value |
79 | /// and other cases where showing a diagnostic would not add much value such |
80 | /// as an [IterableProperty] where the value is empty. |
81 | fine, |
82 | |
83 | /// Diagnostics that should only be shown when performing fine grained |
84 | /// debugging of an object. |
85 | /// |
86 | /// Unlike a [fine] diagnostic, these diagnostics provide important |
87 | /// information about the object that is likely to be needed to debug. Used by |
88 | /// properties that are important but where the property value is too verbose |
89 | /// (e.g. 300+ characters long) to show with a higher diagnostic level. |
90 | debug, |
91 | |
92 | /// Interesting diagnostics that should be typically shown. |
93 | info, |
94 | |
95 | /// Very important diagnostics that indicate problematic property values. |
96 | /// |
97 | /// For example, use if you would write the property description |
98 | /// message in ALL CAPS. |
99 | warning, |
100 | |
101 | /// Diagnostics that provide a hint about best practices. |
102 | /// |
103 | /// For example, a diagnostic describing best practices for fixing an error. |
104 | hint, |
105 | |
106 | /// Diagnostics that summarize other diagnostics present. |
107 | /// |
108 | /// For example, use this level for a short one or two line summary |
109 | /// describing other diagnostics present. |
110 | summary, |
111 | |
112 | /// Diagnostics that indicate errors or unexpected conditions. |
113 | /// |
114 | /// For example, use for property values where computing the value throws an |
115 | /// exception. |
116 | error, |
117 | |
118 | /// Special level indicating that no diagnostics should be shown. |
119 | /// |
120 | /// Do not specify this level for diagnostics. This level is only used to |
121 | /// filter which diagnostics are shown. |
122 | off, |
123 | } |
124 | |
125 | /// Styles for displaying a node in a [DiagnosticsNode] tree. |
126 | /// |
127 | /// In release mode, these styles may be ignored, as diagnostics are compacted |
128 | /// or truncated to save on binary size. |
129 | /// |
130 | /// See also: |
131 | /// |
132 | /// * [DiagnosticsNode.toStringDeep], which dumps text art trees for these |
133 | /// styles. |
134 | enum DiagnosticsTreeStyle { |
135 | /// A style that does not display the tree, for release mode. |
136 | none, |
137 | |
138 | /// Sparse style for displaying trees. |
139 | /// |
140 | /// See also: |
141 | /// |
142 | /// * [RenderObject], which uses this style. |
143 | sparse, |
144 | |
145 | /// Connects a node to its parent with a dashed line. |
146 | /// |
147 | /// See also: |
148 | /// |
149 | /// * [RenderSliverMultiBoxAdaptor], which uses this style to distinguish |
150 | /// offstage children from onstage children. |
151 | offstage, |
152 | |
153 | /// Slightly more compact version of the [sparse] style. |
154 | /// |
155 | /// See also: |
156 | /// |
157 | /// * [Element], which uses this style. |
158 | dense, |
159 | |
160 | /// Style that enables transitioning from nodes of one style to children of |
161 | /// another. |
162 | /// |
163 | /// See also: |
164 | /// |
165 | /// * [RenderParagraph], which uses this style to display a [TextSpan] child |
166 | /// in a way that is compatible with the [DiagnosticsTreeStyle.sparse] |
167 | /// style of the [RenderObject] tree. |
168 | transition, |
169 | |
170 | /// Style for displaying content describing an error. |
171 | /// |
172 | /// See also: |
173 | /// |
174 | /// * [FlutterError], which uses this style for the root node in a tree |
175 | /// describing an error. |
176 | error, |
177 | |
178 | /// Render the tree just using whitespace without connecting parents to |
179 | /// children using lines. |
180 | /// |
181 | /// See also: |
182 | /// |
183 | /// * [SliverGeometry], which uses this style. |
184 | whitespace, |
185 | |
186 | /// Render the tree without indenting children at all. |
187 | /// |
188 | /// See also: |
189 | /// |
190 | /// * [DiagnosticsStackTrace], which uses this style. |
191 | flat, |
192 | |
193 | /// Render the tree on a single line without showing children. |
194 | singleLine, |
195 | |
196 | /// Render the tree using a style appropriate for properties that are part |
197 | /// of an error message. |
198 | /// |
199 | /// The name is placed on one line with the value and properties placed on |
200 | /// the following line. |
201 | /// |
202 | /// See also: |
203 | /// |
204 | /// * [singleLine], which displays the same information but keeps the |
205 | /// property and value on the same line. |
206 | errorProperty, |
207 | |
208 | /// Render only the immediate properties of a node instead of the full tree. |
209 | /// |
210 | /// See also: |
211 | /// |
212 | /// * [DebugOverflowIndicatorMixin], which uses this style to display just |
213 | /// the immediate children of a node. |
214 | shallow, |
215 | |
216 | /// Render only the children of a node truncating before the tree becomes too |
217 | /// large. |
218 | truncateChildren, |
219 | } |
220 | |
221 | /// Configuration specifying how a particular [DiagnosticsTreeStyle] should be |
222 | /// rendered as text art. |
223 | /// |
224 | /// In release mode, these configurations may be ignored, as diagnostics are |
225 | /// compacted or truncated to save on binary size. |
226 | /// |
227 | /// See also: |
228 | /// |
229 | /// * [sparseTextConfiguration], which is a typical style. |
230 | /// * [transitionTextConfiguration], which is an example of a complex tree style. |
231 | /// * [DiagnosticsNode.toStringDeep], for code using [TextTreeConfiguration] |
232 | /// to render text art for arbitrary trees of [DiagnosticsNode] objects. |
233 | class TextTreeConfiguration { |
234 | /// Create a configuration object describing how to render a tree as text. |
235 | TextTreeConfiguration({ |
236 | required this.prefixLineOne, |
237 | required this.prefixOtherLines, |
238 | required this.prefixLastChildLineOne, |
239 | required this.prefixOtherLinesRootNode, |
240 | required this.linkCharacter, |
241 | required this.propertyPrefixIfChildren, |
242 | required this.propertyPrefixNoChildren, |
243 | this.lineBreak = '\n' , |
244 | this.lineBreakProperties = true, |
245 | this.afterName = ':' , |
246 | this.afterDescriptionIfBody = '' , |
247 | this.afterDescription = '' , |
248 | this.beforeProperties = '' , |
249 | this.afterProperties = '' , |
250 | this.mandatoryAfterProperties = '' , |
251 | this.propertySeparator = '' , |
252 | this.bodyIndent = '' , |
253 | this.footer = '' , |
254 | this.showChildren = true, |
255 | this.addBlankLineIfNoChildren = true, |
256 | this.isNameOnOwnLine = false, |
257 | this.isBlankLineBetweenPropertiesAndChildren = true, |
258 | this.beforeName = '' , |
259 | this.suffixLineOne = '' , |
260 | this.mandatoryFooter = '' , |
261 | }) : childLinkSpace = ' ' * linkCharacter.length; |
262 | |
263 | /// Prefix to add to the first line to display a child with this style. |
264 | final String prefixLineOne; |
265 | |
266 | /// Suffix to add to end of the first line to make its length match the footer. |
267 | final String suffixLineOne; |
268 | |
269 | /// Prefix to add to other lines to display a child with this style. |
270 | /// |
271 | /// [prefixOtherLines] should typically be one character shorter than |
272 | /// [prefixLineOne] is. |
273 | final String prefixOtherLines; |
274 | |
275 | /// Prefix to add to the first line to display the last child of a node with |
276 | /// this style. |
277 | final String prefixLastChildLineOne; |
278 | |
279 | /// Additional prefix to add to other lines of a node if this is the root node |
280 | /// of the tree. |
281 | final String prefixOtherLinesRootNode; |
282 | |
283 | /// Prefix to add before each property if the node as children. |
284 | /// |
285 | /// Plays a similar role to [linkCharacter] except that some configurations |
286 | /// intentionally use a different line style than the [linkCharacter]. |
287 | final String propertyPrefixIfChildren; |
288 | |
289 | /// Prefix to add before each property if the node does not have children. |
290 | /// |
291 | /// This string is typically a whitespace string the same length as |
292 | /// [propertyPrefixIfChildren] but can have a different length. |
293 | final String propertyPrefixNoChildren; |
294 | |
295 | /// Character to use to draw line linking parent to child. |
296 | /// |
297 | /// The first child does not require a line but all subsequent children do |
298 | /// with the line drawn immediately before the left edge of the previous |
299 | /// sibling. |
300 | final String linkCharacter; |
301 | |
302 | /// Whitespace to draw instead of the childLink character if this node is the |
303 | /// last child of its parent so no link line is required. |
304 | final String childLinkSpace; |
305 | |
306 | /// Character(s) to use to separate lines. |
307 | /// |
308 | /// Typically leave set at the default value of '\n' unless this style needs |
309 | /// to treat lines differently as is the case for |
310 | /// [singleLineTextConfiguration]. |
311 | final String lineBreak; |
312 | |
313 | /// Whether to place line breaks between properties or to leave all |
314 | /// properties on one line. |
315 | final bool lineBreakProperties; |
316 | |
317 | |
318 | /// Text added immediately before the name of the node. |
319 | /// |
320 | /// See [errorTextConfiguration] for an example of using this to achieve a |
321 | /// custom line art style. |
322 | final String beforeName; |
323 | |
324 | /// Text added immediately after the name of the node. |
325 | /// |
326 | /// See [transitionTextConfiguration] for an example of using a value other |
327 | /// than ':' to achieve a custom line art style. |
328 | final String afterName; |
329 | |
330 | /// Text to add immediately after the description line of a node with |
331 | /// properties and/or children if the node has a body. |
332 | final String afterDescriptionIfBody; |
333 | |
334 | /// Text to add immediately after the description line of a node with |
335 | /// properties and/or children. |
336 | final String afterDescription; |
337 | |
338 | /// Optional string to add before the properties of a node. |
339 | /// |
340 | /// Only displayed if the node has properties. |
341 | /// See [singleLineTextConfiguration] for an example of using this field |
342 | /// to enclose the property list with parenthesis. |
343 | final String beforeProperties; |
344 | |
345 | /// Optional string to add after the properties of a node. |
346 | /// |
347 | /// See documentation for [beforeProperties]. |
348 | final String afterProperties; |
349 | |
350 | /// Mandatory string to add after the properties of a node regardless of |
351 | /// whether the node has any properties. |
352 | final String mandatoryAfterProperties; |
353 | |
354 | /// Property separator to add between properties. |
355 | /// |
356 | /// See [singleLineTextConfiguration] for an example of using this field |
357 | /// to render properties as a comma separated list. |
358 | final String propertySeparator; |
359 | |
360 | /// Prefix to add to all lines of the body of the tree node. |
361 | /// |
362 | /// The body is all content in the node other than the name and description. |
363 | final String bodyIndent; |
364 | |
365 | /// Whether the children of a node should be shown. |
366 | /// |
367 | /// See [singleLineTextConfiguration] for an example of using this field to |
368 | /// hide all children of a node. |
369 | final bool showChildren; |
370 | |
371 | /// Whether to add a blank line at the end of the output for a node if it has |
372 | /// no children. |
373 | /// |
374 | /// See [denseTextConfiguration] for an example of setting this to false. |
375 | final bool addBlankLineIfNoChildren; |
376 | |
377 | /// Whether the name should be displayed on the same line as the description. |
378 | final bool isNameOnOwnLine; |
379 | |
380 | /// Footer to add as its own line at the end of a non-root node. |
381 | /// |
382 | /// See [transitionTextConfiguration] for an example of using footer to draw a box |
383 | /// around the node. [footer] is indented the same amount as [prefixOtherLines]. |
384 | final String footer; |
385 | |
386 | /// Footer to add even for root nodes. |
387 | final String mandatoryFooter; |
388 | |
389 | /// Add a blank line between properties and children if both are present. |
390 | final bool isBlankLineBetweenPropertiesAndChildren; |
391 | } |
392 | |
393 | /// Default text tree configuration. |
394 | /// |
395 | /// Example: |
396 | /// |
397 | /// <root_name>: <root_description> |
398 | /// │ <property1> |
399 | /// │ <property2> |
400 | /// │ ... |
401 | /// │ <propertyN> |
402 | /// ├─<child_name>: <child_description> |
403 | /// │ │ <property1> |
404 | /// │ │ <property2> |
405 | /// │ │ ... |
406 | /// │ │ <propertyN> |
407 | /// │ │ |
408 | /// │ └─<child_name>: <child_description> |
409 | /// │ <property1> |
410 | /// │ <property2> |
411 | /// │ ... |
412 | /// │ <propertyN> |
413 | /// │ |
414 | /// └─<child_name>: <child_description>' |
415 | /// <property1> |
416 | /// <property2> |
417 | /// ... |
418 | /// <propertyN> |
419 | /// |
420 | /// See also: |
421 | /// |
422 | /// * [DiagnosticsTreeStyle.sparse], uses this style for ASCII art display. |
423 | final TextTreeConfiguration sparseTextConfiguration = TextTreeConfiguration( |
424 | prefixLineOne: '├─' , |
425 | prefixOtherLines: ' ' , |
426 | prefixLastChildLineOne: '└─' , |
427 | linkCharacter: '│' , |
428 | propertyPrefixIfChildren: '│ ' , |
429 | propertyPrefixNoChildren: ' ' , |
430 | prefixOtherLinesRootNode: ' ' , |
431 | ); |
432 | |
433 | /// Identical to [sparseTextConfiguration] except that the lines connecting |
434 | /// parent to children are dashed. |
435 | /// |
436 | /// Example: |
437 | /// |
438 | /// <root_name>: <root_description> |
439 | /// │ <property1> |
440 | /// │ <property2> |
441 | /// │ ... |
442 | /// │ <propertyN> |
443 | /// ├─<normal_child_name>: <child_description> |
444 | /// ╎ │ <property1> |
445 | /// ╎ │ <property2> |
446 | /// ╎ │ ... |
447 | /// ╎ │ <propertyN> |
448 | /// ╎ │ |
449 | /// ╎ └─<child_name>: <child_description> |
450 | /// ╎ <property1> |
451 | /// ╎ <property2> |
452 | /// ╎ ... |
453 | /// ╎ <propertyN> |
454 | /// ╎ |
455 | /// ╎╌<dashed_child_name>: <child_description> |
456 | /// ╎ │ <property1> |
457 | /// ╎ │ <property2> |
458 | /// ╎ │ ... |
459 | /// ╎ │ <propertyN> |
460 | /// ╎ │ |
461 | /// ╎ └─<child_name>: <child_description> |
462 | /// ╎ <property1> |
463 | /// ╎ <property2> |
464 | /// ╎ ... |
465 | /// ╎ <propertyN> |
466 | /// ╎ |
467 | /// └╌<dashed_child_name>: <child_description>' |
468 | /// <property1> |
469 | /// <property2> |
470 | /// ... |
471 | /// <propertyN> |
472 | /// |
473 | /// See also: |
474 | /// |
475 | /// * [DiagnosticsTreeStyle.offstage], uses this style for ASCII art display. |
476 | final TextTreeConfiguration dashedTextConfiguration = TextTreeConfiguration( |
477 | prefixLineOne: '╎╌' , |
478 | prefixLastChildLineOne: '└╌' , |
479 | prefixOtherLines: ' ' , |
480 | linkCharacter: '╎' , |
481 | // Intentionally not set as a dashed line as that would make the properties |
482 | // look like they were disabled. |
483 | propertyPrefixIfChildren: '│ ' , |
484 | propertyPrefixNoChildren: ' ' , |
485 | prefixOtherLinesRootNode: ' ' , |
486 | ); |
487 | |
488 | /// Dense text tree configuration that minimizes horizontal whitespace. |
489 | /// |
490 | /// Example: |
491 | /// |
492 | /// <root_name>: <root_description>(<property1>; <property2> <propertyN>) |
493 | /// ├<child_name>: <child_description>(<property1>, <property2>, <propertyN>) |
494 | /// └<child_name>: <child_description>(<property1>, <property2>, <propertyN>) |
495 | /// |
496 | /// See also: |
497 | /// |
498 | /// * [DiagnosticsTreeStyle.dense], uses this style for ASCII art display. |
499 | final TextTreeConfiguration denseTextConfiguration = TextTreeConfiguration( |
500 | propertySeparator: ', ' , |
501 | beforeProperties: '(' , |
502 | afterProperties: ')' , |
503 | lineBreakProperties: false, |
504 | prefixLineOne: '├' , |
505 | prefixOtherLines: '' , |
506 | prefixLastChildLineOne: '└' , |
507 | linkCharacter: '│' , |
508 | propertyPrefixIfChildren: '│' , |
509 | propertyPrefixNoChildren: ' ' , |
510 | prefixOtherLinesRootNode: '' , |
511 | addBlankLineIfNoChildren: false, |
512 | isBlankLineBetweenPropertiesAndChildren: false, |
513 | ); |
514 | |
515 | /// Configuration that draws a box around a leaf node. |
516 | /// |
517 | /// Used by leaf nodes such as [TextSpan] to draw a clear border around the |
518 | /// contents of a node. |
519 | /// |
520 | /// Example: |
521 | /// |
522 | /// <parent_node> |
523 | /// ╞═╦══ <name> ═══ |
524 | /// │ ║ <description>: |
525 | /// │ ║ <body> |
526 | /// │ ║ ... |
527 | /// │ ╚═══════════ |
528 | /// ╘═╦══ <name> ═══ |
529 | /// ║ <description>: |
530 | /// ║ <body> |
531 | /// ║ ... |
532 | /// ╚═══════════ |
533 | /// |
534 | /// See also: |
535 | /// |
536 | /// * [DiagnosticsTreeStyle.transition], uses this style for ASCII art display. |
537 | final TextTreeConfiguration transitionTextConfiguration = TextTreeConfiguration( |
538 | prefixLineOne: '╞═╦══ ' , |
539 | prefixLastChildLineOne: '╘═╦══ ' , |
540 | prefixOtherLines: ' ║ ' , |
541 | footer: ' ╚═══════════' , |
542 | linkCharacter: '│' , |
543 | // Subtree boundaries are clear due to the border around the node so omit the |
544 | // property prefix. |
545 | propertyPrefixIfChildren: '' , |
546 | propertyPrefixNoChildren: '' , |
547 | prefixOtherLinesRootNode: '' , |
548 | afterName: ' ═══' , |
549 | // Add a colon after the description if the node has a body to make the |
550 | // connection between the description and the body clearer. |
551 | afterDescriptionIfBody: ':' , |
552 | // Members are indented an extra two spaces to disambiguate as the description |
553 | // is placed within the box instead of along side the name as is the case for |
554 | // other styles. |
555 | bodyIndent: ' ' , |
556 | isNameOnOwnLine: true, |
557 | // No need to add a blank line as the footer makes the boundary of this |
558 | // subtree unambiguous. |
559 | addBlankLineIfNoChildren: false, |
560 | isBlankLineBetweenPropertiesAndChildren: false, |
561 | ); |
562 | |
563 | /// Configuration that draws a box around a node ignoring the connection to the |
564 | /// parents. |
565 | /// |
566 | /// If nested in a tree, this node is best displayed in the property box rather |
567 | /// than as a traditional child. |
568 | /// |
569 | /// Used to draw a decorative box around detailed descriptions of an exception. |
570 | /// |
571 | /// Example: |
572 | /// |
573 | /// ══╡ <name>: <description> ╞═════════════════════════════════════ |
574 | /// <body> |
575 | /// ... |
576 | /// ├─<normal_child_name>: <child_description> |
577 | /// ╎ │ <property1> |
578 | /// ╎ │ <property2> |
579 | /// ╎ │ ... |
580 | /// ╎ │ <propertyN> |
581 | /// ╎ │ |
582 | /// ╎ └─<child_name>: <child_description> |
583 | /// ╎ <property1> |
584 | /// ╎ <property2> |
585 | /// ╎ ... |
586 | /// ╎ <propertyN> |
587 | /// ╎ |
588 | /// ╎╌<dashed_child_name>: <child_description> |
589 | /// ╎ │ <property1> |
590 | /// ╎ │ <property2> |
591 | /// ╎ │ ... |
592 | /// ╎ │ <propertyN> |
593 | /// ╎ │ |
594 | /// ╎ └─<child_name>: <child_description> |
595 | /// ╎ <property1> |
596 | /// ╎ <property2> |
597 | /// ╎ ... |
598 | /// ╎ <propertyN> |
599 | /// ╎ |
600 | /// └╌<dashed_child_name>: <child_description>' |
601 | /// ════════════════════════════════════════════════════════════════ |
602 | /// |
603 | /// See also: |
604 | /// |
605 | /// * [DiagnosticsTreeStyle.error], uses this style for ASCII art display. |
606 | final TextTreeConfiguration errorTextConfiguration = TextTreeConfiguration( |
607 | prefixLineOne: '╞═╦' , |
608 | prefixLastChildLineOne: '╘═╦' , |
609 | prefixOtherLines: ' ║ ' , |
610 | footer: ' ╚═══════════' , |
611 | linkCharacter: '│' , |
612 | // Subtree boundaries are clear due to the border around the node so omit the |
613 | // property prefix. |
614 | propertyPrefixIfChildren: '' , |
615 | propertyPrefixNoChildren: '' , |
616 | prefixOtherLinesRootNode: '' , |
617 | beforeName: '══╡ ' , |
618 | suffixLineOne: ' ╞══' , |
619 | mandatoryFooter: '═════' , |
620 | // No need to add a blank line as the footer makes the boundary of this |
621 | // subtree unambiguous. |
622 | addBlankLineIfNoChildren: false, |
623 | isBlankLineBetweenPropertiesAndChildren: false, |
624 | ); |
625 | |
626 | /// Whitespace only configuration where children are consistently indented |
627 | /// two spaces. |
628 | /// |
629 | /// Use this style for displaying properties with structured values or for |
630 | /// displaying children within a [transitionTextConfiguration] as using a style that |
631 | /// draws line art would be visually distracting for those cases. |
632 | /// |
633 | /// Example: |
634 | /// |
635 | /// <parent_node> |
636 | /// <name>: <description>: |
637 | /// <properties> |
638 | /// <children> |
639 | /// <name>: <description>: |
640 | /// <properties> |
641 | /// <children> |
642 | /// |
643 | /// See also: |
644 | /// |
645 | /// * [DiagnosticsTreeStyle.whitespace], uses this style for ASCII art display. |
646 | final TextTreeConfiguration whitespaceTextConfiguration = TextTreeConfiguration( |
647 | prefixLineOne: '' , |
648 | prefixLastChildLineOne: '' , |
649 | prefixOtherLines: ' ' , |
650 | prefixOtherLinesRootNode: ' ' , |
651 | propertyPrefixIfChildren: '' , |
652 | propertyPrefixNoChildren: '' , |
653 | linkCharacter: ' ' , |
654 | addBlankLineIfNoChildren: false, |
655 | // Add a colon after the description and before the properties to link the |
656 | // properties to the description line. |
657 | afterDescriptionIfBody: ':' , |
658 | isBlankLineBetweenPropertiesAndChildren: false, |
659 | ); |
660 | |
661 | /// Whitespace only configuration where children are not indented. |
662 | /// |
663 | /// Use this style when indentation is not needed to disambiguate parents from |
664 | /// children as in the case of a [DiagnosticsStackTrace]. |
665 | /// |
666 | /// Example: |
667 | /// |
668 | /// <parent_node> |
669 | /// <name>: <description>: |
670 | /// <properties> |
671 | /// <children> |
672 | /// <name>: <description>: |
673 | /// <properties> |
674 | /// <children> |
675 | /// |
676 | /// See also: |
677 | /// |
678 | /// * [DiagnosticsTreeStyle.flat], uses this style for ASCII art display. |
679 | final TextTreeConfiguration flatTextConfiguration = TextTreeConfiguration( |
680 | prefixLineOne: '' , |
681 | prefixLastChildLineOne: '' , |
682 | prefixOtherLines: '' , |
683 | prefixOtherLinesRootNode: '' , |
684 | propertyPrefixIfChildren: '' , |
685 | propertyPrefixNoChildren: '' , |
686 | linkCharacter: '' , |
687 | addBlankLineIfNoChildren: false, |
688 | // Add a colon after the description and before the properties to link the |
689 | // properties to the description line. |
690 | afterDescriptionIfBody: ':' , |
691 | isBlankLineBetweenPropertiesAndChildren: false, |
692 | ); |
693 | /// Render a node as a single line omitting children. |
694 | /// |
695 | /// Example: |
696 | /// `<name>: <description>(<property1>, <property2>, ..., <propertyN>)` |
697 | /// |
698 | /// See also: |
699 | /// |
700 | /// * [DiagnosticsTreeStyle.singleLine], uses this style for ASCII art display. |
701 | final TextTreeConfiguration singleLineTextConfiguration = TextTreeConfiguration( |
702 | propertySeparator: ', ' , |
703 | beforeProperties: '(' , |
704 | afterProperties: ')' , |
705 | prefixLineOne: '' , |
706 | prefixOtherLines: '' , |
707 | prefixLastChildLineOne: '' , |
708 | lineBreak: '' , |
709 | lineBreakProperties: false, |
710 | addBlankLineIfNoChildren: false, |
711 | showChildren: false, |
712 | propertyPrefixIfChildren: ' ' , |
713 | propertyPrefixNoChildren: ' ' , |
714 | linkCharacter: '' , |
715 | prefixOtherLinesRootNode: '' , |
716 | ); |
717 | |
718 | /// Render the name on a line followed by the body and properties on the next |
719 | /// line omitting the children. |
720 | /// |
721 | /// Example: |
722 | /// |
723 | /// <name>: |
724 | /// <description>(<property1>, <property2>, ..., <propertyN>) |
725 | /// |
726 | /// See also: |
727 | /// |
728 | /// * [DiagnosticsTreeStyle.errorProperty], uses this style for ASCII art |
729 | /// display. |
730 | final TextTreeConfiguration errorPropertyTextConfiguration = TextTreeConfiguration( |
731 | propertySeparator: ', ' , |
732 | beforeProperties: '(' , |
733 | afterProperties: ')' , |
734 | prefixLineOne: '' , |
735 | prefixOtherLines: '' , |
736 | prefixLastChildLineOne: '' , |
737 | lineBreakProperties: false, |
738 | addBlankLineIfNoChildren: false, |
739 | showChildren: false, |
740 | propertyPrefixIfChildren: ' ' , |
741 | propertyPrefixNoChildren: ' ' , |
742 | linkCharacter: '' , |
743 | prefixOtherLinesRootNode: '' , |
744 | isNameOnOwnLine: true, |
745 | ); |
746 | |
747 | /// Render a node on multiple lines omitting children. |
748 | /// |
749 | /// Example: |
750 | /// `<name>: <description> |
751 | /// <property1> |
752 | /// <property2> |
753 | /// <propertyN>` |
754 | /// |
755 | /// See also: |
756 | /// |
757 | /// * [DiagnosticsTreeStyle.shallow] |
758 | final TextTreeConfiguration shallowTextConfiguration = TextTreeConfiguration( |
759 | prefixLineOne: '' , |
760 | prefixLastChildLineOne: '' , |
761 | prefixOtherLines: ' ' , |
762 | prefixOtherLinesRootNode: ' ' , |
763 | propertyPrefixIfChildren: '' , |
764 | propertyPrefixNoChildren: '' , |
765 | linkCharacter: ' ' , |
766 | addBlankLineIfNoChildren: false, |
767 | // Add a colon after the description and before the properties to link the |
768 | // properties to the description line. |
769 | afterDescriptionIfBody: ':' , |
770 | isBlankLineBetweenPropertiesAndChildren: false, |
771 | showChildren: false, |
772 | ); |
773 | |
774 | enum _WordWrapParseMode { inSpace, inWord, atBreak } |
775 | |
776 | /// Builder that builds a String with specified prefixes for the first and |
777 | /// subsequent lines. |
778 | /// |
779 | /// Allows for the incremental building of strings using `write*()` methods. |
780 | /// The strings are concatenated into a single string with the first line |
781 | /// prefixed by [prefixLineOne] and subsequent lines prefixed by |
782 | /// [prefixOtherLines]. |
783 | class _PrefixedStringBuilder { |
784 | _PrefixedStringBuilder({ |
785 | required this.prefixLineOne, |
786 | required String? prefixOtherLines, |
787 | this.wrapWidth, |
788 | }) : _prefixOtherLines = prefixOtherLines; |
789 | |
790 | /// Prefix to add to the first line. |
791 | final String prefixLineOne; |
792 | |
793 | /// Prefix to add to subsequent lines. |
794 | /// |
795 | /// The prefix can be modified while the string is being built in which case |
796 | /// subsequent lines will be added with the modified prefix. |
797 | String? get prefixOtherLines => _nextPrefixOtherLines ?? _prefixOtherLines; |
798 | String? _prefixOtherLines; |
799 | set prefixOtherLines(String? prefix) { |
800 | _prefixOtherLines = prefix; |
801 | _nextPrefixOtherLines = null; |
802 | } |
803 | |
804 | String? _nextPrefixOtherLines; |
805 | void incrementPrefixOtherLines(String suffix, {required bool updateCurrentLine}) { |
806 | if (_currentLine.isEmpty || updateCurrentLine) { |
807 | _prefixOtherLines = prefixOtherLines! + suffix; |
808 | _nextPrefixOtherLines = null; |
809 | } else { |
810 | _nextPrefixOtherLines = prefixOtherLines! + suffix; |
811 | } |
812 | } |
813 | |
814 | final int? wrapWidth; |
815 | |
816 | /// Buffer containing lines that have already been completely laid out. |
817 | final StringBuffer _buffer = StringBuffer(); |
818 | /// Buffer containing the current line that has not yet been wrapped. |
819 | final StringBuffer _currentLine = StringBuffer(); |
820 | /// List of pairs of integers indicating the start and end of each block of |
821 | /// text within _currentLine that can be wrapped. |
822 | final List<int> _wrappableRanges = <int>[]; |
823 | |
824 | /// Whether the string being built already has more than 1 line. |
825 | bool get requiresMultipleLines => _numLines > 1 || (_numLines == 1 && _currentLine.isNotEmpty) || |
826 | (_currentLine.length + _getCurrentPrefix(true)!.length > wrapWidth!); |
827 | |
828 | bool get isCurrentLineEmpty => _currentLine.isEmpty; |
829 | |
830 | int _numLines = 0; |
831 | |
832 | void _finalizeLine(bool addTrailingLineBreak) { |
833 | final bool firstLine = _buffer.isEmpty; |
834 | final String text = _currentLine.toString(); |
835 | _currentLine.clear(); |
836 | |
837 | if (_wrappableRanges.isEmpty) { |
838 | // Fast path. There were no wrappable spans of text. |
839 | _writeLine( |
840 | text, |
841 | includeLineBreak: addTrailingLineBreak, |
842 | firstLine: firstLine, |
843 | ); |
844 | return; |
845 | } |
846 | final Iterable<String> lines = _wordWrapLine( |
847 | text, |
848 | _wrappableRanges, |
849 | wrapWidth!, |
850 | startOffset: firstLine ? prefixLineOne.length : _prefixOtherLines!.length, |
851 | otherLineOffset: _prefixOtherLines!.length, |
852 | ); |
853 | int i = 0; |
854 | final int length = lines.length; |
855 | for (final String line in lines) { |
856 | i++; |
857 | _writeLine( |
858 | line, |
859 | includeLineBreak: addTrailingLineBreak || i < length, |
860 | firstLine: firstLine, |
861 | ); |
862 | } |
863 | _wrappableRanges.clear(); |
864 | } |
865 | |
866 | /// Wraps the given string at the given width. |
867 | /// |
868 | /// Wrapping occurs at space characters (U+0020). |
869 | /// |
870 | /// This is not suitable for use with arbitrary Unicode text. For example, it |
871 | /// doesn't implement UAX #14, can't handle ideographic text, doesn't hyphenate, |
872 | /// and so forth. It is only intended for formatting error messages. |
873 | /// |
874 | /// This method wraps a sequence of text where only some spans of text can be |
875 | /// used as wrap boundaries. |
876 | static Iterable<String> _wordWrapLine(String message, List<int> wrapRanges, int width, { int startOffset = 0, int otherLineOffset = 0}) { |
877 | if (message.length + startOffset < width) { |
878 | // Nothing to do. The line doesn't wrap. |
879 | return <String>[message]; |
880 | } |
881 | final List<String> wrappedLine = <String>[]; |
882 | int startForLengthCalculations = -startOffset; |
883 | bool addPrefix = false; |
884 | int index = 0; |
885 | _WordWrapParseMode mode = _WordWrapParseMode.inSpace; |
886 | late int lastWordStart; |
887 | int? lastWordEnd; |
888 | int start = 0; |
889 | |
890 | int currentChunk = 0; |
891 | |
892 | // This helper is called with increasing indexes. |
893 | bool noWrap(int index) { |
894 | while (true) { |
895 | if (currentChunk >= wrapRanges.length) { |
896 | return true; |
897 | } |
898 | |
899 | if (index < wrapRanges[currentChunk + 1]) { |
900 | break; // Found nearest chunk. |
901 | } |
902 | currentChunk+= 2; |
903 | } |
904 | return index < wrapRanges[currentChunk]; |
905 | } |
906 | while (true) { |
907 | switch (mode) { |
908 | case _WordWrapParseMode.inSpace: // at start of break point (or start of line); can't break until next break |
909 | while ((index < message.length) && (message[index] == ' ' )) { |
910 | index += 1; |
911 | } |
912 | lastWordStart = index; |
913 | mode = _WordWrapParseMode.inWord; |
914 | case _WordWrapParseMode.inWord: // looking for a good break point. Treat all text |
915 | while ((index < message.length) && (message[index] != ' ' || noWrap(index))) { |
916 | index += 1; |
917 | } |
918 | mode = _WordWrapParseMode.atBreak; |
919 | case _WordWrapParseMode.atBreak: // at start of break point |
920 | if ((index - startForLengthCalculations > width) || (index == message.length)) { |
921 | // we are over the width line, so break |
922 | if ((index - startForLengthCalculations <= width) || (lastWordEnd == null)) { |
923 | // we should use this point, because either it doesn't actually go over the |
924 | // end (last line), or it does, but there was no earlier break point |
925 | lastWordEnd = index; |
926 | } |
927 | final String line = message.substring(start, lastWordEnd); |
928 | wrappedLine.add(line); |
929 | addPrefix = true; |
930 | if (lastWordEnd >= message.length) { |
931 | return wrappedLine; |
932 | } |
933 | // just yielded a line |
934 | if (lastWordEnd == index) { |
935 | // we broke at current position |
936 | // eat all the wrappable spaces, then set our start point |
937 | // Even if some of the spaces are not wrappable that is ok. |
938 | while ((index < message.length) && (message[index] == ' ' )) { |
939 | index += 1; |
940 | } |
941 | start = index; |
942 | mode = _WordWrapParseMode.inWord; |
943 | } else { |
944 | // we broke at the previous break point, and we're at the start of a new one |
945 | assert(lastWordStart > lastWordEnd); |
946 | start = lastWordStart; |
947 | mode = _WordWrapParseMode.atBreak; |
948 | } |
949 | startForLengthCalculations = start - otherLineOffset; |
950 | assert(addPrefix); |
951 | lastWordEnd = null; |
952 | } else { |
953 | // save this break point, we're not yet over the line width |
954 | lastWordEnd = index; |
955 | // skip to the end of this break point |
956 | mode = _WordWrapParseMode.inSpace; |
957 | } |
958 | } |
959 | } |
960 | } |
961 | |
962 | /// Write text ensuring the specified prefixes for the first and subsequent |
963 | /// lines. |
964 | /// |
965 | /// If [allowWrap] is true, the text may be wrapped to stay within the |
966 | /// allow `wrapWidth`. |
967 | void write(String s, {bool allowWrap = false}) { |
968 | if (s.isEmpty) { |
969 | return; |
970 | } |
971 | |
972 | final List<String> lines = s.split('\n' ); |
973 | for (int i = 0; i < lines.length; i += 1) { |
974 | if (i > 0) { |
975 | _finalizeLine(true); |
976 | _updatePrefix(); |
977 | } |
978 | final String line = lines[i]; |
979 | if (line.isNotEmpty) { |
980 | if (allowWrap && wrapWidth != null) { |
981 | final int wrapStart = _currentLine.length; |
982 | final int wrapEnd = wrapStart + line.length; |
983 | if (_wrappableRanges.lastOrNull == wrapStart) { |
984 | // Extend last range. |
985 | _wrappableRanges.last = wrapEnd; |
986 | } else { |
987 | _wrappableRanges..add(wrapStart)..add(wrapEnd); |
988 | } |
989 | } |
990 | _currentLine.write(line); |
991 | } |
992 | } |
993 | } |
994 | void _updatePrefix() { |
995 | if (_nextPrefixOtherLines != null) { |
996 | _prefixOtherLines = _nextPrefixOtherLines; |
997 | _nextPrefixOtherLines = null; |
998 | } |
999 | } |
1000 | |
1001 | void _writeLine( |
1002 | String line, { |
1003 | required bool includeLineBreak, |
1004 | required bool firstLine, |
1005 | }) { |
1006 | line = ' ${_getCurrentPrefix(firstLine)}$line' ; |
1007 | _buffer.write(line.trimRight()); |
1008 | if (includeLineBreak) { |
1009 | _buffer.write('\n' ); |
1010 | } |
1011 | _numLines++; |
1012 | } |
1013 | |
1014 | String? _getCurrentPrefix(bool firstLine) { |
1015 | return _buffer.isEmpty ? prefixLineOne : _prefixOtherLines; |
1016 | } |
1017 | |
1018 | /// Write lines assuming the lines obey the specified prefixes. Ensures that |
1019 | /// a newline is added if one is not present. |
1020 | void writeRawLines(String lines) { |
1021 | if (lines.isEmpty) { |
1022 | return; |
1023 | } |
1024 | |
1025 | if (_currentLine.isNotEmpty) { |
1026 | _finalizeLine(true); |
1027 | } |
1028 | assert (_currentLine.isEmpty); |
1029 | |
1030 | _buffer.write(lines); |
1031 | if (!lines.endsWith('\n' )) { |
1032 | _buffer.write('\n' ); |
1033 | } |
1034 | _numLines++; |
1035 | _updatePrefix(); |
1036 | } |
1037 | |
1038 | /// Finishes the current line with a stretched version of text. |
1039 | void writeStretched(String text, int targetLineLength) { |
1040 | write(text); |
1041 | final int currentLineLength = _currentLine.length + _getCurrentPrefix(_buffer.isEmpty)!.length; |
1042 | assert (_currentLine.length > 0); |
1043 | final int targetLength = targetLineLength - currentLineLength; |
1044 | if (targetLength > 0) { |
1045 | assert(text.isNotEmpty); |
1046 | final String lastChar = text[text.length - 1]; |
1047 | assert(lastChar != '\n' ); |
1048 | _currentLine.write(lastChar * targetLength); |
1049 | } |
1050 | // Mark the entire line as not wrappable. |
1051 | _wrappableRanges.clear(); |
1052 | } |
1053 | |
1054 | String build() { |
1055 | if (_currentLine.isNotEmpty) { |
1056 | _finalizeLine(false); |
1057 | } |
1058 | |
1059 | return _buffer.toString(); |
1060 | } |
1061 | } |
1062 | |
1063 | class _NoDefaultValue { |
1064 | const _NoDefaultValue(); |
1065 | } |
1066 | |
1067 | /// Marker object indicating that a [DiagnosticsNode] has no default value. |
1068 | const Object kNoDefaultValue = _NoDefaultValue(); |
1069 | |
1070 | bool _isSingleLine(DiagnosticsTreeStyle? style) { |
1071 | return style == DiagnosticsTreeStyle.singleLine; |
1072 | } |
1073 | |
1074 | /// Renderer that creates ASCII art representations of trees of |
1075 | /// [DiagnosticsNode] objects. |
1076 | /// |
1077 | /// See also: |
1078 | /// |
1079 | /// * [DiagnosticsNode.toStringDeep], which uses a [TextTreeRenderer] to return a |
1080 | /// string representation of this node and its descendants. |
1081 | class TextTreeRenderer { |
1082 | /// Creates a [TextTreeRenderer] object with the given arguments specifying |
1083 | /// how the tree is rendered. |
1084 | /// |
1085 | /// Lines are wrapped to at the maximum of [wrapWidth] and the current indent |
1086 | /// plus [wrapWidthProperties] characters. This ensures that wrapping does not |
1087 | /// become too excessive when displaying very deep trees and that wrapping |
1088 | /// only occurs at the overall [wrapWidth] when the tree is not very indented. |
1089 | /// If [maxDescendentsTruncatableNode] is specified, [DiagnosticsNode] objects |
1090 | /// with `allowTruncate` set to `true` are truncated after including |
1091 | /// [maxDescendentsTruncatableNode] descendants of the node to be truncated. |
1092 | TextTreeRenderer({ |
1093 | DiagnosticLevel minLevel = DiagnosticLevel.debug, |
1094 | int wrapWidth = 100, |
1095 | int wrapWidthProperties = 65, |
1096 | int maxDescendentsTruncatableNode = -1, |
1097 | }) : _minLevel = minLevel, |
1098 | _wrapWidth = wrapWidth, |
1099 | _wrapWidthProperties = wrapWidthProperties, |
1100 | _maxDescendentsTruncatableNode = maxDescendentsTruncatableNode; |
1101 | |
1102 | final int _wrapWidth; |
1103 | final int _wrapWidthProperties; |
1104 | final DiagnosticLevel _minLevel; |
1105 | final int _maxDescendentsTruncatableNode; |
1106 | |
1107 | /// Text configuration to use to connect this node to a `child`. |
1108 | /// |
1109 | /// The singleLine styles are special cased because the connection from the |
1110 | /// parent to the child should be consistent with the parent's style as the |
1111 | /// single line style does not provide any meaningful style for how children |
1112 | /// should be connected to their parents. |
1113 | TextTreeConfiguration? _childTextConfiguration( |
1114 | DiagnosticsNode child, |
1115 | TextTreeConfiguration textStyle, |
1116 | ) { |
1117 | final DiagnosticsTreeStyle? childStyle = child.style; |
1118 | return (_isSingleLine(childStyle) || childStyle == DiagnosticsTreeStyle.errorProperty) ? textStyle : child.textTreeConfiguration; |
1119 | } |
1120 | |
1121 | /// Renders a [node] to a String. |
1122 | String render( |
1123 | DiagnosticsNode node, { |
1124 | String prefixLineOne = '' , |
1125 | String? prefixOtherLines, |
1126 | TextTreeConfiguration? parentConfiguration, |
1127 | }) { |
1128 | if (kReleaseMode) { |
1129 | return '' ; |
1130 | } else { |
1131 | return _debugRender( |
1132 | node, |
1133 | prefixLineOne: prefixLineOne, |
1134 | prefixOtherLines: prefixOtherLines, |
1135 | parentConfiguration: parentConfiguration, |
1136 | ); |
1137 | } |
1138 | } |
1139 | |
1140 | String _debugRender( |
1141 | DiagnosticsNode node, { |
1142 | String prefixLineOne = '' , |
1143 | String? prefixOtherLines, |
1144 | TextTreeConfiguration? parentConfiguration, |
1145 | }) { |
1146 | final bool isSingleLine = _isSingleLine(node.style) && parentConfiguration?.lineBreakProperties != true; |
1147 | prefixOtherLines ??= prefixLineOne; |
1148 | if (node.linePrefix != null) { |
1149 | prefixLineOne += node.linePrefix!; |
1150 | prefixOtherLines += node.linePrefix!; |
1151 | } |
1152 | |
1153 | final TextTreeConfiguration config = node.textTreeConfiguration!; |
1154 | if (prefixOtherLines.isEmpty) { |
1155 | prefixOtherLines += config.prefixOtherLinesRootNode; |
1156 | } |
1157 | |
1158 | if (node.style == DiagnosticsTreeStyle.truncateChildren) { |
1159 | // This style is different enough that it isn't worthwhile to reuse the |
1160 | // existing logic. |
1161 | final List<String> descendants = <String>[]; |
1162 | const int maxDepth = 5; |
1163 | int depth = 0; |
1164 | const int maxLines = 25; |
1165 | int lines = 0; |
1166 | void visitor(DiagnosticsNode node) { |
1167 | for (final DiagnosticsNode child in node.getChildren()) { |
1168 | if (lines < maxLines) { |
1169 | depth += 1; |
1170 | descendants.add(' $prefixOtherLines${" " * depth}$child' ); |
1171 | if (depth < maxDepth) {
|
1172 | visitor(child);
|
1173 | }
|
1174 | depth -= 1;
|
1175 | } else if (lines == maxLines) {
|
1176 | descendants.add(' $prefixOtherLines ...(descendants list truncated after $lines lines)' );
|
1177 | }
|
1178 | lines += 1;
|
1179 | }
|
1180 | }
|
1181 | visitor(node);
|
1182 | final StringBuffer information = StringBuffer(prefixLineOne);
|
1183 | if (lines > 1) {
|
1184 | information.writeln('This ${node.name} had the following descendants (showing up to depth $maxDepth):' );
|
1185 | } else if (descendants.length == 1) {
|
1186 | information.writeln('This ${node.name} had the following child:' );
|
1187 | } else {
|
1188 | information.writeln('This ${node.name} has no descendants.' );
|
1189 | }
|
1190 | information.writeAll(descendants, '\n' );
|
1191 | return information.toString();
|
1192 | }
|
1193 | final _PrefixedStringBuilder builder = _PrefixedStringBuilder(
|
1194 | prefixLineOne: prefixLineOne,
|
1195 | prefixOtherLines: prefixOtherLines,
|
1196 | wrapWidth: math.max(_wrapWidth, prefixOtherLines.length + _wrapWidthProperties),
|
1197 | );
|
1198 |
|
1199 | List<DiagnosticsNode> children = node.getChildren();
|
1200 |
|
1201 | String description = node.toDescription(parentConfiguration: parentConfiguration);
|
1202 | if (config.beforeName.isNotEmpty) {
|
1203 | builder.write(config.beforeName);
|
1204 | }
|
1205 | final bool wrapName = !isSingleLine && node.allowNameWrap;
|
1206 | final bool wrapDescription = !isSingleLine && node.allowWrap;
|
1207 | final bool uppercaseTitle = node.style == DiagnosticsTreeStyle.error;
|
1208 | String? name = node.name;
|
1209 | if (uppercaseTitle) {
|
1210 | name = name?.toUpperCase();
|
1211 | }
|
1212 | if (description.isEmpty) {
|
1213 | if (node.showName && name != null) {
|
1214 | builder.write(name, allowWrap: wrapName);
|
1215 | }
|
1216 | } else {
|
1217 | bool includeName = false;
|
1218 | if (name != null && name.isNotEmpty && node.showName) {
|
1219 | includeName = true;
|
1220 | builder.write(name, allowWrap: wrapName);
|
1221 | if (node.showSeparator) {
|
1222 | builder.write(config.afterName, allowWrap: wrapName);
|
1223 | }
|
1224 |
|
1225 | builder.write(
|
1226 | config.isNameOnOwnLine || description.contains('\n' ) ? '\n' : ' ' ,
|
1227 | allowWrap: wrapName,
|
1228 | );
|
1229 | }
|
1230 | if (!isSingleLine && builder.requiresMultipleLines && !builder.isCurrentLineEmpty) {
|
1231 | // Make sure there is a break between the current line and next one if
|
1232 | // there is not one already.
|
1233 | builder.write('\n' );
|
1234 | }
|
1235 | if (includeName) {
|
1236 | builder.incrementPrefixOtherLines(
|
1237 | children.isEmpty ? config.propertyPrefixNoChildren : config.propertyPrefixIfChildren,
|
1238 | updateCurrentLine: true,
|
1239 | );
|
1240 | }
|
1241 |
|
1242 | if (uppercaseTitle) {
|
1243 | description = description.toUpperCase();
|
1244 | }
|
1245 | builder.write(description.trimRight(), allowWrap: wrapDescription);
|
1246 |
|
1247 | if (!includeName) {
|
1248 | builder.incrementPrefixOtherLines(
|
1249 | children.isEmpty ? config.propertyPrefixNoChildren : config.propertyPrefixIfChildren,
|
1250 | updateCurrentLine: false,
|
1251 | );
|
1252 | }
|
1253 | }
|
1254 | if (config.suffixLineOne.isNotEmpty) {
|
1255 | builder.writeStretched(config.suffixLineOne, builder.wrapWidth!);
|
1256 | }
|
1257 |
|
1258 | final Iterable<DiagnosticsNode> propertiesIterable = node.getProperties().where(
|
1259 | (DiagnosticsNode n) => !n.isFiltered(_minLevel),
|
1260 | );
|
1261 | List<DiagnosticsNode> properties;
|
1262 | if (_maxDescendentsTruncatableNode >= 0 && node.allowTruncate) {
|
1263 | if (propertiesIterable.length < _maxDescendentsTruncatableNode) {
|
1264 | properties =
|
1265 | propertiesIterable.take(_maxDescendentsTruncatableNode).toList();
|
1266 | properties.add(DiagnosticsNode.message('...' ));
|
1267 | } else {
|
1268 | properties = propertiesIterable.toList();
|
1269 | }
|
1270 | if (_maxDescendentsTruncatableNode < children.length) {
|
1271 | children = children.take(_maxDescendentsTruncatableNode).toList();
|
1272 | children.add(DiagnosticsNode.message('...' ));
|
1273 | }
|
1274 | } else {
|
1275 | properties = propertiesIterable.toList();
|
1276 | }
|
1277 |
|
1278 | // If the node does not show a separator and there is no description then
|
1279 | // we should not place a separator between the name and the value.
|
1280 | // Essentially in this case the properties are treated a bit like a value.
|
1281 | if ((properties.isNotEmpty || children.isNotEmpty || node.emptyBodyDescription != null) &&
|
1282 | (node.showSeparator || description.isNotEmpty)) {
|
1283 | builder.write(config.afterDescriptionIfBody);
|
1284 | }
|
1285 |
|
1286 | if (config.lineBreakProperties) {
|
1287 | builder.write(config.lineBreak);
|
1288 | }
|
1289 |
|
1290 | if (properties.isNotEmpty) {
|
1291 | builder.write(config.beforeProperties);
|
1292 | }
|
1293 |
|
1294 | builder.incrementPrefixOtherLines(config.bodyIndent, updateCurrentLine: false);
|
1295 |
|
1296 | if (node.emptyBodyDescription != null &&
|
1297 | properties.isEmpty &&
|
1298 | children.isEmpty &&
|
1299 | prefixLineOne.isNotEmpty) {
|
1300 | builder.write(node.emptyBodyDescription!);
|
1301 | if (config.lineBreakProperties) {
|
1302 | builder.write(config.lineBreak);
|
1303 | }
|
1304 | }
|
1305 |
|
1306 | for (int i = 0; i < properties.length; ++i) {
|
1307 | final DiagnosticsNode property = properties[i];
|
1308 | if (i > 0) {
|
1309 | builder.write(config.propertySeparator);
|
1310 | }
|
1311 |
|
1312 | final TextTreeConfiguration propertyStyle = property.textTreeConfiguration!;
|
1313 | if (_isSingleLine(property.style)) {
|
1314 | // We have to treat single line properties slightly differently to deal
|
1315 | // with cases where a single line properties output may not have single
|
1316 | // linebreak.
|
1317 | final String propertyRender = render(property,
|
1318 | prefixLineOne: propertyStyle.prefixLineOne,
|
1319 | prefixOtherLines: ' ${propertyStyle.childLinkSpace}${propertyStyle.prefixOtherLines}' ,
|
1320 | parentConfiguration: config,
|
1321 | );
|
1322 | final List<String> propertyLines = propertyRender.split('\n' );
|
1323 | if (propertyLines.length == 1 && !config.lineBreakProperties) {
|
1324 | builder.write(propertyLines.first);
|
1325 | } else {
|
1326 | builder.write(propertyRender);
|
1327 | if (!propertyRender.endsWith('\n' )) {
|
1328 | builder.write('\n' );
|
1329 | }
|
1330 | }
|
1331 | } else {
|
1332 | final String propertyRender = render(property,
|
1333 | prefixLineOne: ' ${builder.prefixOtherLines}${propertyStyle.prefixLineOne}' ,
|
1334 | prefixOtherLines: ' ${builder.prefixOtherLines}${propertyStyle.childLinkSpace}${propertyStyle.prefixOtherLines}' ,
|
1335 | parentConfiguration: config,
|
1336 | );
|
1337 | builder.writeRawLines(propertyRender);
|
1338 | }
|
1339 | }
|
1340 | if (properties.isNotEmpty) {
|
1341 | builder.write(config.afterProperties);
|
1342 | }
|
1343 |
|
1344 | builder.write(config.mandatoryAfterProperties);
|
1345 |
|
1346 | if (!config.lineBreakProperties) {
|
1347 | builder.write(config.lineBreak);
|
1348 | }
|
1349 |
|
1350 | final String prefixChildren = config.bodyIndent;
|
1351 | final String prefixChildrenRaw = ' $prefixOtherLines$prefixChildren' ;
|
1352 | if (children.isEmpty &&
|
1353 | config.addBlankLineIfNoChildren &&
|
1354 | builder.requiresMultipleLines &&
|
1355 | builder.prefixOtherLines!.trimRight().isNotEmpty
|
1356 | ) {
|
1357 | builder.write(config.lineBreak);
|
1358 | }
|
1359 |
|
1360 | if (children.isNotEmpty && config.showChildren) {
|
1361 | if (config.isBlankLineBetweenPropertiesAndChildren &&
|
1362 | properties.isNotEmpty &&
|
1363 | children.first.textTreeConfiguration!.isBlankLineBetweenPropertiesAndChildren) {
|
1364 | builder.write(config.lineBreak);
|
1365 | }
|
1366 |
|
1367 | builder.prefixOtherLines = prefixOtherLines;
|
1368 |
|
1369 | for (int i = 0; i < children.length; i++) {
|
1370 | final DiagnosticsNode child = children[i];
|
1371 | final TextTreeConfiguration childConfig = _childTextConfiguration(child, config)!;
|
1372 | if (i == children.length - 1) {
|
1373 | final String lastChildPrefixLineOne = ' $prefixChildrenRaw${childConfig.prefixLastChildLineOne}' ;
|
1374 | final String childPrefixOtherLines = ' $prefixChildrenRaw${childConfig.childLinkSpace}${childConfig.prefixOtherLines}' ;
|
1375 | builder.writeRawLines(render(
|
1376 | child,
|
1377 | prefixLineOne: lastChildPrefixLineOne,
|
1378 | prefixOtherLines: childPrefixOtherLines,
|
1379 | parentConfiguration: config,
|
1380 | ));
|
1381 | if (childConfig.footer.isNotEmpty) {
|
1382 | builder.prefixOtherLines = prefixChildrenRaw;
|
1383 | builder.write(' ${childConfig.childLinkSpace}${childConfig.footer}' );
|
1384 | if (childConfig.mandatoryFooter.isNotEmpty) {
|
1385 | builder.writeStretched(
|
1386 | childConfig.mandatoryFooter,
|
1387 | math.max(builder.wrapWidth!, _wrapWidthProperties + childPrefixOtherLines.length),
|
1388 | );
|
1389 | }
|
1390 | builder.write(config.lineBreak);
|
1391 | }
|
1392 | } else {
|
1393 | final TextTreeConfiguration nextChildStyle = _childTextConfiguration(children[i + 1], config)!;
|
1394 | final String childPrefixLineOne = ' $prefixChildrenRaw${childConfig.prefixLineOne}' ;
|
1395 | final String childPrefixOtherLines =' $prefixChildrenRaw${nextChildStyle.linkCharacter}${childConfig.prefixOtherLines}' ;
|
1396 | builder.writeRawLines(render(
|
1397 | child,
|
1398 | prefixLineOne: childPrefixLineOne,
|
1399 | prefixOtherLines: childPrefixOtherLines,
|
1400 | parentConfiguration: config,
|
1401 | ));
|
1402 | if (childConfig.footer.isNotEmpty) {
|
1403 | builder.prefixOtherLines = prefixChildrenRaw;
|
1404 | builder.write(' ${childConfig.linkCharacter}${childConfig.footer}' );
|
1405 | if (childConfig.mandatoryFooter.isNotEmpty) {
|
1406 | builder.writeStretched(
|
1407 | childConfig.mandatoryFooter,
|
1408 | math.max(builder.wrapWidth!, _wrapWidthProperties + childPrefixOtherLines.length),
|
1409 | );
|
1410 | }
|
1411 | builder.write(config.lineBreak);
|
1412 | }
|
1413 | }
|
1414 | }
|
1415 | }
|
1416 | if (parentConfiguration == null && config.mandatoryFooter.isNotEmpty) {
|
1417 | builder.writeStretched(config.mandatoryFooter, builder.wrapWidth!);
|
1418 | builder.write(config.lineBreak);
|
1419 | }
|
1420 | return builder.build();
|
1421 | }
|
1422 | }
|
1423 |
|
1424 | /// Defines diagnostics data for a [value].
|
1425 | ///
|
1426 | /// For debug and profile modes, [DiagnosticsNode] provides a high quality
|
1427 | /// multiline string dump via [toStringDeep]. The core members are the [name],
|
1428 | /// [toDescription], [getProperties], [value], and [getChildren]. All other
|
1429 | /// members exist typically to provide hints for how [toStringDeep] and
|
1430 | /// debugging tools should format output.
|
1431 | ///
|
1432 | /// In release mode, far less information is retained and some information may
|
1433 | /// not print at all.
|
1434 | abstract class DiagnosticsNode {
|
1435 | /// Initializes the object.
|
1436 | ///
|
1437 | /// The [style], [showName], and [showSeparator] arguments must not
|
1438 | /// be null.
|
1439 | DiagnosticsNode({
|
1440 | required this.name,
|
1441 | this.style,
|
1442 | this.showName = true,
|
1443 | this.showSeparator = true,
|
1444 | this.linePrefix,
|
1445 | }) : assert(
|
1446 | // A name ending with ':' indicates that the user forgot that the ':' will
|
1447 | // be automatically added for them when generating descriptions of the
|
1448 | // property.
|
1449 | name == null || !name.endsWith(':' ),
|
1450 | 'Names of diagnostic nodes must not end with colons.\n'
|
1451 | 'name:\n'
|
1452 | ' " $name"' ,
|
1453 | );
|
1454 |
|
1455 | /// Diagnostics containing just a string `message` and not a concrete name or
|
1456 | /// value.
|
1457 | ///
|
1458 | /// See also:
|
1459 | ///
|
1460 | /// * [MessageProperty], which is better suited to messages that are to be
|
1461 | /// formatted like a property with a separate name and message.
|
1462 | factory DiagnosticsNode.message(
|
1463 | String message, {
|
1464 | DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
|
1465 | DiagnosticLevel level = DiagnosticLevel.info,
|
1466 | bool allowWrap = true,
|
1467 | }) {
|
1468 | return DiagnosticsProperty<void>(
|
1469 | '' ,
|
1470 | null,
|
1471 | description: message,
|
1472 | style: style,
|
1473 | showName: false,
|
1474 | allowWrap: allowWrap,
|
1475 | level: level,
|
1476 | );
|
1477 | }
|
1478 |
|
1479 | /// Label describing the [DiagnosticsNode], typically shown before a separator
|
1480 | /// (see [showSeparator]).
|
1481 | ///
|
1482 | /// The name will be omitted if the [showName] property is false.
|
1483 | final String? name;
|
1484 |
|
1485 | /// Returns a description with a short summary of the node itself not
|
1486 | /// including children or properties.
|
1487 | ///
|
1488 | /// `parentConfiguration` specifies how the parent is rendered as text art.
|
1489 | /// For example, if the parent does not line break between properties, the
|
1490 | /// description of a property should also be a single line if possible.
|
1491 | String toDescription({ TextTreeConfiguration? parentConfiguration });
|
1492 |
|
1493 | /// Whether to show a separator between [name] and description.
|
1494 | ///
|
1495 | /// If false, name and description should be shown with no separation.
|
1496 | /// `:` is typically used as a separator when displaying as text.
|
1497 | final bool showSeparator;
|
1498 |
|
1499 | /// Whether the diagnostic should be filtered due to its [level] being lower
|
1500 | /// than `minLevel`.
|
1501 | ///
|
1502 | /// If `minLevel` is [DiagnosticLevel.hidden] no diagnostics will be filtered.
|
1503 | /// If `minLevel` is [DiagnosticLevel.off] all diagnostics will be filtered.
|
1504 | bool isFiltered(DiagnosticLevel minLevel) => kReleaseMode || level.index < minLevel.index;
|
1505 |
|
1506 | /// Priority level of the diagnostic used to control which diagnostics should
|
1507 | /// be shown and filtered.
|
1508 | ///
|
1509 | /// Typically this only makes sense to set to a different value than
|
1510 | /// [DiagnosticLevel.info] for diagnostics representing properties. Some
|
1511 | /// subclasses have a [level] argument to their constructor which influences
|
1512 | /// the value returned here but other factors also influence it. For example,
|
1513 | /// whether an exception is thrown computing a property value
|
1514 | /// [DiagnosticLevel.error] is returned.
|
1515 | DiagnosticLevel get level => kReleaseMode ? DiagnosticLevel.hidden : DiagnosticLevel.info;
|
1516 |
|
1517 | /// Whether the name of the property should be shown when showing the default
|
1518 | /// view of the tree.
|
1519 | ///
|
1520 | /// This could be set to false (hiding the name) if the value's description
|
1521 | /// will make the name self-evident.
|
1522 | final bool showName;
|
1523 |
|
1524 | /// Prefix to include at the start of each line.
|
1525 | final String? linePrefix;
|
1526 |
|
1527 | /// Description to show if the node has no displayed properties or children.
|
1528 | String? get emptyBodyDescription => null;
|
1529 |
|
1530 | /// The actual object this is diagnostics data for.
|
1531 | Object? get value;
|
1532 |
|
1533 | /// Hint for how the node should be displayed.
|
1534 | final DiagnosticsTreeStyle? style;
|
1535 |
|
1536 | /// Whether to wrap text on onto multiple lines or not.
|
1537 | bool get allowWrap => false;
|
1538 |
|
1539 | /// Whether to wrap the name onto multiple lines or not.
|
1540 | bool get allowNameWrap => false;
|
1541 |
|
1542 | /// Whether to allow truncation when displaying the node and its children.
|
1543 | bool get allowTruncate => false;
|
1544 |
|
1545 | /// Properties of this [DiagnosticsNode].
|
1546 | ///
|
1547 | /// Properties and children are kept distinct even though they are both
|
1548 | /// [List<DiagnosticsNode>] because they should be grouped differently.
|
1549 | List<DiagnosticsNode> getProperties();
|
1550 |
|
1551 | /// Children of this [DiagnosticsNode].
|
1552 | ///
|
1553 | /// See also:
|
1554 | ///
|
1555 | /// * [getProperties], which returns the properties of the [DiagnosticsNode]
|
1556 | /// object.
|
1557 | List<DiagnosticsNode> getChildren();
|
1558 |
|
1559 | String get _separator => showSeparator ? ':' : '' ;
|
1560 |
|
1561 | /// Converts the properties ([getProperties]) of this node to a form useful
|
1562 | /// for [Timeline] event arguments (as in [Timeline.startSync]).
|
1563 | ///
|
1564 | /// Children ([getChildren]) are omitted.
|
1565 | ///
|
1566 | /// This method is only valid in debug builds. In profile builds, this method
|
1567 | /// throws an exception. In release builds it returns null.
|
1568 | ///
|
1569 | /// See also:
|
1570 | ///
|
1571 | /// * [toJsonMap], which converts this node to a structured form intended for
|
1572 | /// data exchange (e.g. with an IDE).
|
1573 | Map<String, String>? toTimelineArguments() {
|
1574 | if (!kReleaseMode) {
|
1575 | // We don't throw in release builds, to avoid hurting users. We also don't do anything useful.
|
1576 | if (kProfileMode) {
|
1577 | throw FlutterError(
|
1578 | // Parts of this string are searched for verbatim by a test in dev/bots/test.dart.
|
1579 | ' $DiagnosticsNode.toTimelineArguments used in non-debug build.\n'
|
1580 | 'The $DiagnosticsNode.toTimelineArguments API is expensive and causes timeline traces '
|
1581 | 'to be non-representative. As such, it should not be used in profile builds. However, '
|
1582 | 'this application is compiled in profile mode and yet still invoked the method.'
|
1583 | );
|
1584 | }
|
1585 | final Map<String, String> result = <String, String>{};
|
1586 | for (final DiagnosticsNode property in getProperties()) {
|
1587 | if (property.name != null) {
|
1588 | result[property.name!] = property.toDescription(parentConfiguration: singleLineTextConfiguration);
|
1589 | }
|
1590 | }
|
1591 | return result;
|
1592 | }
|
1593 | return null;
|
1594 | }
|
1595 |
|
1596 | /// Serialize the node to a JSON map according to the configuration provided
|
1597 | /// in the [DiagnosticsSerializationDelegate].
|
1598 | ///
|
1599 | /// Subclasses should override if they have additional properties that are
|
1600 | /// useful for the GUI tools that consume this JSON.
|
1601 | ///
|
1602 | /// See also:
|
1603 | ///
|
1604 | /// * [WidgetInspectorService], which forms the bridge between JSON returned
|
1605 | /// by this method and interactive tree views in the Flutter IntelliJ
|
1606 | /// plugin.
|
1607 | @mustCallSuper
|
1608 | Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
1609 | Map<String, Object?> result = <String, Object?>{};
|
1610 | assert(() {
|
1611 | final bool hasChildren = getChildren().isNotEmpty;
|
1612 | result = <String, Object?>{
|
1613 | 'description' : toDescription(),
|
1614 | 'type' : runtimeType.toString(),
|
1615 | if (name != null)
|
1616 | 'name' : name,
|
1617 | if (!showSeparator)
|
1618 | 'showSeparator' : showSeparator,
|
1619 | if (level != DiagnosticLevel.info)
|
1620 | 'level' : level.name,
|
1621 | if (!showName)
|
1622 | 'showName' : showName,
|
1623 | if (emptyBodyDescription != null)
|
1624 | 'emptyBodyDescription' : emptyBodyDescription,
|
1625 | if (style != DiagnosticsTreeStyle.sparse)
|
1626 | 'style' : style!.name,
|
1627 | if (allowTruncate)
|
1628 | 'allowTruncate' : allowTruncate,
|
1629 | if (hasChildren)
|
1630 | 'hasChildren' : hasChildren,
|
1631 | if (linePrefix?.isNotEmpty ?? false)
|
1632 | 'linePrefix' : linePrefix,
|
1633 | if (!allowWrap)
|
1634 | 'allowWrap' : allowWrap,
|
1635 | if (allowNameWrap)
|
1636 | 'allowNameWrap' : allowNameWrap,
|
1637 | ...delegate.additionalNodeProperties(this),
|
1638 | if (delegate.includeProperties)
|
1639 | 'properties' : toJsonList(
|
1640 | delegate.filterProperties(getProperties(), this),
|
1641 | this,
|
1642 | delegate,
|
1643 | ),
|
1644 | if (delegate.subtreeDepth > 0)
|
1645 | 'children' : toJsonList(
|
1646 | delegate.filterChildren(getChildren(), this),
|
1647 | this,
|
1648 | delegate,
|
1649 | ),
|
1650 | };
|
1651 | return true;
|
1652 | }());
|
1653 | return result;
|
1654 | }
|
1655 |
|
1656 | /// Serializes a [List] of [DiagnosticsNode]s to a JSON list according to
|
1657 | /// the configuration provided by the [DiagnosticsSerializationDelegate].
|
1658 | ///
|
1659 | /// The provided `nodes` may be properties or children of the `parent`
|
1660 | /// [DiagnosticsNode].
|
1661 | static List<Map<String, Object?>> toJsonList(
|
1662 | List<DiagnosticsNode>? nodes,
|
1663 | DiagnosticsNode? parent,
|
1664 | DiagnosticsSerializationDelegate delegate,
|
1665 | ) {
|
1666 | bool truncated = false;
|
1667 | if (nodes == null) {
|
1668 | return const <Map<String, Object?>>[];
|
1669 | }
|
1670 | final int originalNodeCount = nodes.length;
|
1671 | nodes = delegate.truncateNodesList(nodes, parent);
|
1672 | if (nodes.length != originalNodeCount) {
|
1673 | nodes.add(DiagnosticsNode.message('...' ));
|
1674 | truncated = true;
|
1675 | }
|
1676 | final List<Map<String, Object?>> json = nodes.map<Map<String, Object?>>((DiagnosticsNode node) {
|
1677 | return node.toJsonMap(delegate.delegateForNode(node));
|
1678 | }).toList();
|
1679 | if (truncated) {
|
1680 | json.last['truncated' ] = true;
|
1681 | }
|
1682 | return json;
|
1683 | }
|
1684 |
|
1685 | /// Returns a string representation of this diagnostic that is compatible with
|
1686 | /// the style of the parent if the node is not the root.
|
1687 | ///
|
1688 | /// `parentConfiguration` specifies how the parent is rendered as text art.
|
1689 | /// For example, if the parent places all properties on one line, the
|
1690 | /// [toString] for each property should avoid line breaks if possible.
|
1691 | ///
|
1692 | /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
|
1693 | /// in the output.
|
1694 | ///
|
1695 | /// In release mode, far less information is retained and some information may
|
1696 | /// not print at all.
|
1697 | @override
|
1698 | String toString({
|
1699 | TextTreeConfiguration? parentConfiguration,
|
1700 | DiagnosticLevel minLevel = DiagnosticLevel.info,
|
1701 | }) {
|
1702 | String result = super.toString();
|
1703 | assert(style != null);
|
1704 | assert(() {
|
1705 | if (_isSingleLine(style)) {
|
1706 | result = toStringDeep(parentConfiguration: parentConfiguration, minLevel: minLevel);
|
1707 | } else {
|
1708 | final String description = toDescription(parentConfiguration: parentConfiguration);
|
1709 |
|
1710 | if (name == null || name!.isEmpty || !showName) {
|
1711 | result = description;
|
1712 | } else {
|
1713 | result = description.contains('\n' ) ? ' $name$_separator\n $description'
|
1714 | : ' $name$_separator $description' ;
|
1715 | }
|
1716 | }
|
1717 | return true;
|
1718 | }());
|
1719 | return result;
|
1720 | }
|
1721 |
|
1722 | /// Returns a configuration specifying how this object should be rendered
|
1723 | /// as text art.
|
1724 | @protected
|
1725 | TextTreeConfiguration? get textTreeConfiguration {
|
1726 | assert(style != null);
|
1727 | return switch (style!) {
|
1728 | DiagnosticsTreeStyle.none => null,
|
1729 | DiagnosticsTreeStyle.dense => denseTextConfiguration,
|
1730 | DiagnosticsTreeStyle.sparse => sparseTextConfiguration,
|
1731 | DiagnosticsTreeStyle.offstage => dashedTextConfiguration,
|
1732 | DiagnosticsTreeStyle.whitespace => whitespaceTextConfiguration,
|
1733 | DiagnosticsTreeStyle.transition => transitionTextConfiguration,
|
1734 | DiagnosticsTreeStyle.singleLine => singleLineTextConfiguration,
|
1735 | DiagnosticsTreeStyle.errorProperty => errorPropertyTextConfiguration,
|
1736 | DiagnosticsTreeStyle.shallow => shallowTextConfiguration,
|
1737 | DiagnosticsTreeStyle.error => errorTextConfiguration,
|
1738 | DiagnosticsTreeStyle.flat => flatTextConfiguration,
|
1739 |
|
1740 | // Truncate children doesn't really need its own text style as the
|
1741 | // rendering is quite custom.
|
1742 | DiagnosticsTreeStyle.truncateChildren => whitespaceTextConfiguration,
|
1743 | };
|
1744 | }
|
1745 |
|
1746 | /// Returns a string representation of this node and its descendants.
|
1747 | ///
|
1748 | /// `prefixLineOne` will be added to the front of the first line of the
|
1749 | /// output. `prefixOtherLines` will be added to the front of each other line.
|
1750 | /// If `prefixOtherLines` is null, the `prefixLineOne` is used for every line.
|
1751 | /// By default, there is no prefix.
|
1752 | ///
|
1753 | /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
|
1754 | /// in the output.
|
1755 | ///
|
1756 | /// `wrapWidth` specifies the column number where word wrapping will be
|
1757 | /// applied.
|
1758 | ///
|
1759 | /// The [toStringDeep] method takes other arguments, but those are intended
|
1760 | /// for internal use when recursing to the descendants, and so can be ignored.
|
1761 | ///
|
1762 | /// In release mode, far less information is retained and some information may
|
1763 | /// not print at all.
|
1764 | ///
|
1765 | /// See also:
|
1766 | ///
|
1767 | /// * [toString], for a brief description of the [value] but not its
|
1768 | /// children.
|
1769 | String toStringDeep({
|
1770 | String prefixLineOne = '' ,
|
1771 | String? prefixOtherLines,
|
1772 | TextTreeConfiguration? parentConfiguration,
|
1773 | DiagnosticLevel minLevel = DiagnosticLevel.debug,
|
1774 | int wrapWidth = 65,
|
1775 | }) {
|
1776 | String result = '' ;
|
1777 | assert(() {
|
1778 | result = TextTreeRenderer(
|
1779 | minLevel: minLevel,
|
1780 | wrapWidth: wrapWidth,
|
1781 | ).render(
|
1782 | this,
|
1783 | prefixLineOne: prefixLineOne,
|
1784 | prefixOtherLines: prefixOtherLines,
|
1785 | parentConfiguration: parentConfiguration,
|
1786 | );
|
1787 | return true;
|
1788 | }());
|
1789 | return result;
|
1790 | }
|
1791 | }
|
1792 |
|
1793 | /// Debugging message displayed like a property.
|
1794 | ///
|
1795 | /// {@tool snippet}
|
1796 | ///
|
1797 | /// The following two properties are better expressed using this
|
1798 | /// [MessageProperty] class, rather than [StringProperty], as the intent is to
|
1799 | /// show a message with property style display rather than to describe the value
|
1800 | /// of an actual property of the object:
|
1801 | ///
|
1802 | /// ```dart
|
1803 | /// MessageProperty table = MessageProperty('table size', '$columns\u00D7$rows');
|
1804 | /// MessageProperty usefulness = MessageProperty('usefulness ratio', 'no metrics collected yet (never painted)');
|
1805 | /// ```
|
1806 | /// {@end-tool}
|
1807 | /// {@tool snippet}
|
1808 | ///
|
1809 | /// On the other hand, [StringProperty] is better suited when the property has a
|
1810 | /// concrete value that is a string:
|
1811 | ///
|
1812 | /// ```dart
|
1813 | /// StringProperty name = StringProperty('name', _name);
|
1814 | /// ```
|
1815 | /// {@end-tool}
|
1816 | ///
|
1817 | /// See also:
|
1818 | ///
|
1819 | /// * [DiagnosticsNode.message], which serves the same role for messages
|
1820 | /// without a clear property name.
|
1821 | /// * [StringProperty], which is a better fit for properties with string values.
|
1822 | class MessageProperty extends DiagnosticsProperty<void> {
|
1823 | /// Create a diagnostics property that displays a message.
|
1824 | ///
|
1825 | /// Messages have no concrete [value] (so [value] will return null). The
|
1826 | /// message is stored as the description.
|
1827 | MessageProperty(
|
1828 | String name,
|
1829 | String message, {
|
1830 | DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
|
1831 | DiagnosticLevel level = DiagnosticLevel.info,
|
1832 | }) : super(name, null, description: message, style: style, level: level);
|
1833 | }
|
1834 |
|
1835 | /// Property which encloses its string [value] in quotes.
|
1836 | ///
|
1837 | /// See also:
|
1838 | ///
|
1839 | /// * [MessageProperty], which is a better fit for showing a message
|
1840 | /// instead of describing a property with a string value.
|
1841 | class StringProperty extends DiagnosticsProperty<String> {
|
1842 | /// Create a diagnostics property for strings.
|
1843 | StringProperty(
|
1844 | String super.name,
|
1845 | super.value, {
|
1846 | super.description,
|
1847 | super.tooltip,
|
1848 | super.showName,
|
1849 | super.defaultValue,
|
1850 | this.quoted = true,
|
1851 | super.ifEmpty,
|
1852 | super.style,
|
1853 | super.level,
|
1854 | });
|
1855 |
|
1856 | /// Whether the value is enclosed in double quotes.
|
1857 | final bool quoted;
|
1858 |
|
1859 | @override
|
1860 | Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
1861 | final Map<String, Object?> json = super.toJsonMap(delegate);
|
1862 | json['quoted' ] = quoted;
|
1863 | return json;
|
1864 | }
|
1865 |
|
1866 | @override
|
1867 | String valueToString({ TextTreeConfiguration? parentConfiguration }) {
|
1868 | String? text = _description ?? value;
|
1869 | if (parentConfiguration != null &&
|
1870 | !parentConfiguration.lineBreakProperties &&
|
1871 | text != null) {
|
1872 | // Escape linebreaks in multiline strings to avoid confusing output when
|
1873 | // the parent of this node is trying to display all properties on the same
|
1874 | // line.
|
1875 | text = text.replaceAll('\n' , r'\n' );
|
1876 | }
|
1877 |
|
1878 | if (quoted && text != null) {
|
1879 | // An empty value would not appear empty after being surrounded with
|
1880 | // quotes so we have to handle this case separately.
|
1881 | if (ifEmpty != null && text.isEmpty) {
|
1882 | return ifEmpty!;
|
1883 | }
|
1884 | return '" $text"' ;
|
1885 | }
|
1886 | return text.toString();
|
1887 | }
|
1888 | }
|
1889 |
|
1890 | abstract class _NumProperty<T extends num> extends DiagnosticsProperty<T> {
|
1891 | _NumProperty(
|
1892 | String super.name,
|
1893 | super.value, {
|
1894 | super.ifNull,
|
1895 | this.unit,
|
1896 | super.showName,
|
1897 | super.defaultValue,
|
1898 | super.tooltip,
|
1899 | super.style,
|
1900 | super.level,
|
1901 | });
|
1902 |
|
1903 | _NumProperty.lazy(
|
1904 | String super.name,
|
1905 | super.computeValue, {
|
1906 | super.ifNull,
|
1907 | this.unit,
|
1908 | super.showName,
|
1909 | super.defaultValue,
|
1910 | super.tooltip,
|
1911 | super.style,
|
1912 | super.level,
|
1913 | }) : super.lazy();
|
1914 |
|
1915 | @override
|
1916 | Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
1917 | final Map<String, Object?> json = super.toJsonMap(delegate);
|
1918 | if (unit != null) {
|
1919 | json['unit' ] = unit;
|
1920 | }
|
1921 |
|
1922 | json['numberToString' ] = numberToString();
|
1923 | return json;
|
1924 | }
|
1925 |
|
1926 | /// Optional unit the [value] is measured in.
|
1927 | ///
|
1928 | /// Unit must be acceptable to display immediately after a number with no
|
1929 | /// spaces. For example: 'physical pixels per logical pixel' should be a
|
1930 | /// [tooltip] not a [unit].
|
1931 | final String? unit;
|
1932 |
|
1933 | /// String describing just the numeric [value] without a unit suffix.
|
1934 | String numberToString();
|
1935 |
|
1936 | @override
|
1937 | String valueToString({ TextTreeConfiguration? parentConfiguration }) {
|
1938 | if (value == null) {
|
1939 | return value.toString();
|
1940 | }
|
1941 |
|
1942 | return unit != null ? ' ${numberToString()}$unit' : numberToString();
|
1943 | }
|
1944 | }
|
1945 | /// Property describing a [double] [value] with an optional [unit] of measurement.
|
1946 | ///
|
1947 | /// Numeric formatting is optimized for debug message readability.
|
1948 | class DoubleProperty extends _NumProperty<double> {
|
1949 | /// If specified, [unit] describes the unit for the [value] (e.g. px).
|
1950 | DoubleProperty(
|
1951 | super.name,
|
1952 | super.value, {
|
1953 | super.ifNull,
|
1954 | super.unit,
|
1955 | super.tooltip,
|
1956 | super.defaultValue,
|
1957 | super.showName,
|
1958 | super.style,
|
1959 | super.level,
|
1960 | });
|
1961 |
|
1962 | /// Property with a [value] that is computed only when needed.
|
1963 | ///
|
1964 | /// Use if computing the property [value] may throw an exception or is
|
1965 | /// expensive.
|
1966 | DoubleProperty.lazy(
|
1967 | super.name,
|
1968 | super.computeValue, {
|
1969 | super.ifNull,
|
1970 | super.showName,
|
1971 | super.unit,
|
1972 | super.tooltip,
|
1973 | super.defaultValue,
|
1974 | super.level,
|
1975 | }) : super.lazy();
|
1976 |
|
1977 | @override
|
1978 | String numberToString() => debugFormatDouble(value);
|
1979 | }
|
1980 |
|
1981 | /// An int valued property with an optional unit the value is measured in.
|
1982 | ///
|
1983 | /// Examples of units include 'px' and 'ms'.
|
1984 | class IntProperty extends _NumProperty<int> {
|
1985 | /// Create a diagnostics property for integers.
|
1986 | IntProperty(
|
1987 | super.name,
|
1988 | super.value, {
|
1989 | super.ifNull,
|
1990 | super.showName,
|
1991 | super.unit,
|
1992 | super.defaultValue,
|
1993 | super.style,
|
1994 | super.level,
|
1995 | });
|
1996 |
|
1997 | @override
|
1998 | String numberToString() => value.toString();
|
1999 | }
|
2000 |
|
2001 | /// Property which clamps a [double] to between 0 and 1 and formats it as a
|
2002 | /// percentage.
|
2003 | class PercentProperty extends DoubleProperty {
|
2004 | /// Create a diagnostics property for doubles that represent percentages or
|
2005 | /// fractions.
|
2006 | ///
|
2007 | /// Setting [showName] to false is often reasonable for [PercentProperty]
|
2008 | /// objects, as the fact that the property is shown as a percentage tends to
|
2009 | /// be sufficient to disambiguate its meaning.
|
2010 | PercentProperty(
|
2011 | super.name,
|
2012 | super.fraction, {
|
2013 | super.ifNull,
|
2014 | super.showName,
|
2015 | super.tooltip,
|
2016 | super.unit,
|
2017 | super.level,
|
2018 | });
|
2019 |
|
2020 | @override
|
2021 | String valueToString({ TextTreeConfiguration? parentConfiguration }) {
|
2022 | if (value == null) {
|
2023 | return value.toString();
|
2024 | }
|
2025 | return unit != null ? ' ${numberToString()} $unit' : numberToString();
|
2026 | }
|
2027 |
|
2028 | @override
|
2029 | String numberToString() {
|
2030 | final double? v = value;
|
2031 | if (v == null) {
|
2032 | return value.toString();
|
2033 | }
|
2034 | return ' ${(clampDouble(v, 0.0, 1.0) * 100.0).toStringAsFixed(1)}%' ;
|
2035 | }
|
2036 | }
|
2037 |
|
2038 | /// Property where the description is either [ifTrue] or [ifFalse] depending on
|
2039 | /// whether [value] is true or false.
|
2040 | ///
|
2041 | /// Using [FlagProperty] instead of [DiagnosticsProperty<bool>] can make
|
2042 | /// diagnostics display more polished. For example, given a property named
|
2043 | /// `visible` that is typically true, the following code will return 'hidden'
|
2044 | /// when `visible` is false and nothing when visible is true, in contrast to
|
2045 | /// `visible: true` or `visible: false`.
|
2046 | ///
|
2047 | /// {@tool snippet}
|
2048 | ///
|
2049 | /// ```dart
|
2050 | /// FlagProperty(
|
2051 | /// 'visible',
|
2052 | /// value: true,
|
2053 | /// ifFalse: 'hidden',
|
2054 | /// )
|
2055 | /// ```
|
2056 | /// {@end-tool}
|
2057 | /// {@tool snippet}
|
2058 | ///
|
2059 | /// [FlagProperty] should also be used instead of [DiagnosticsProperty<bool>]
|
2060 | /// if showing the bool value would not clearly indicate the meaning of the
|
2061 | /// property value.
|
2062 | ///
|
2063 | /// ```dart
|
2064 | /// FlagProperty(
|
2065 | /// 'inherit',
|
2066 | /// value: inherit,
|
2067 | /// ifTrue: '<all styles inherited>',
|
2068 | /// ifFalse: '<no style specified>',
|
2069 | /// )
|
2070 | /// ```
|
2071 | /// {@end-tool}
|
2072 | ///
|
2073 | /// See also:
|
2074 | ///
|
2075 | /// * [ObjectFlagProperty], which provides similar behavior describing whether
|
2076 | /// a [value] is null.
|
2077 | class FlagProperty extends DiagnosticsProperty<bool> {
|
2078 | /// Constructs a FlagProperty with the given descriptions with the specified descriptions.
|
2079 | ///
|
2080 | /// [showName] defaults to false as typically [ifTrue] and [ifFalse] should
|
2081 | /// be descriptions that make the property name redundant.
|
2082 | FlagProperty(
|
2083 | String name, {
|
2084 | required bool? value,
|
2085 | this.ifTrue,
|
2086 | this.ifFalse,
|
2087 | bool showName = false,
|
2088 | Object? defaultValue,
|
2089 | DiagnosticLevel level = DiagnosticLevel.info,
|
2090 | }) : assert(ifTrue != null || ifFalse != null),
|
2091 | super(
|
2092 | name,
|
2093 | value,
|
2094 | showName: showName,
|
2095 | defaultValue: defaultValue,
|
2096 | level: level,
|
2097 | );
|
2098 |
|
2099 | @override
|
2100 | Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
2101 | final Map<String, Object?> json = super.toJsonMap(delegate);
|
2102 | if (ifTrue != null) {
|
2103 | json['ifTrue' ] = ifTrue;
|
2104 | }
|
2105 | if (ifFalse != null) {
|
2106 | json['ifFalse' ] = ifFalse;
|
2107 | }
|
2108 |
|
2109 | return json;
|
2110 | }
|
2111 |
|
2112 | /// Description to use if the property [value] is true.
|
2113 | ///
|
2114 | /// If not specified and [value] equals true the property's priority [level]
|
2115 | /// will be [DiagnosticLevel.hidden].
|
2116 | final String? ifTrue;
|
2117 |
|
2118 | /// Description to use if the property value is false.
|
2119 | ///
|
2120 | /// If not specified and [value] equals false, the property's priority [level]
|
2121 | /// will be [DiagnosticLevel.hidden].
|
2122 | final String? ifFalse;
|
2123 |
|
2124 | @override
|
2125 | String valueToString({ TextTreeConfiguration? parentConfiguration }) {
|
2126 | return switch (value) {
|
2127 | true when ifTrue != null => ifTrue!,
|
2128 | false when ifFalse != null => ifFalse!,
|
2129 | _ => super.valueToString(parentConfiguration: parentConfiguration),
|
2130 | };
|
2131 | }
|
2132 |
|
2133 | @override
|
2134 | bool get showName {
|
2135 | if (value == null || ((value ?? false) && ifTrue == null) || (!(value ?? true) && ifFalse == null)) {
|
2136 | // We are missing a description for the flag value so we need to show the
|
2137 | // flag name. The property will have DiagnosticLevel.hidden for this case
|
2138 | // so users will not see this property in this case unless they are
|
2139 | // displaying hidden properties.
|
2140 | return true;
|
2141 | }
|
2142 | return super.showName;
|
2143 | }
|
2144 |
|
2145 | @override
|
2146 | DiagnosticLevel get level {
|
2147 | return switch (value) {
|
2148 | true when ifTrue == null => DiagnosticLevel.hidden,
|
2149 | false when ifFalse == null => DiagnosticLevel.hidden,
|
2150 | _ => super.level,
|
2151 | };
|
2152 | }
|
2153 | }
|
2154 |
|
2155 | /// Property with an `Iterable<T>` [value] that can be displayed with
|
2156 | /// different [DiagnosticsTreeStyle] for custom rendering.
|
2157 | ///
|
2158 | /// If [style] is [DiagnosticsTreeStyle.singleLine], the iterable is described
|
2159 | /// as a comma separated list, otherwise the iterable is described as a line
|
2160 | /// break separated list.
|
2161 | class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> {
|
2162 | /// Create a diagnostics property for iterables (e.g. lists).
|
2163 | ///
|
2164 | /// The [ifEmpty] argument is used to indicate how an iterable [value] with 0
|
2165 | /// elements is displayed. If [ifEmpty] equals null that indicates that an
|
2166 | /// empty iterable [value] is not interesting to display similar to how
|
2167 | /// [defaultValue] is used to indicate that a specific concrete value is not
|
2168 | /// interesting to display.
|
2169 | IterableProperty(
|
2170 | String super.name,
|
2171 | super.value, {
|
2172 | super.defaultValue,
|
2173 | super.ifNull,
|
2174 | super.ifEmpty = '[]' ,
|
2175 | super.style,
|
2176 | super.showName,
|
2177 | super.showSeparator,
|
2178 | super.level,
|
2179 | });
|
2180 |
|
2181 | @override
|
2182 | String valueToString({TextTreeConfiguration? parentConfiguration}) {
|
2183 | if (value == null) {
|
2184 | return value.toString();
|
2185 | }
|
2186 |
|
2187 | if (value!.isEmpty) {
|
2188 | return ifEmpty ?? '[]' ;
|
2189 | }
|
2190 |
|
2191 | final Iterable<String> formattedValues = value!.map((T v) {
|
2192 | if (T == double && v is double) {
|
2193 | return debugFormatDouble(v);
|
2194 | } else {
|
2195 | return v.toString();
|
2196 | }
|
2197 | });
|
2198 |
|
2199 | if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
|
2200 | // Always display the value as a single line and enclose the iterable
|
2201 | // value in brackets to avoid ambiguity.
|
2202 | return '[ ${formattedValues.join(', ' )}]' ;
|
2203 | }
|
2204 |
|
2205 | return formattedValues.join(_isSingleLine(style) ? ', ' : '\n' );
|
2206 | }
|
2207 |
|
2208 | /// Priority level of the diagnostic used to control which diagnostics should
|
2209 | /// be shown and filtered.
|
2210 | ///
|
2211 | /// If [ifEmpty] is null and the [value] is an empty [Iterable] then level
|
2212 | /// [DiagnosticLevel.fine] is returned in a similar way to how an
|
2213 | /// [ObjectFlagProperty] handles when [ifNull] is null and the [value] is
|
2214 | /// null.
|
2215 | @override
|
2216 | DiagnosticLevel get level {
|
2217 | if (ifEmpty == null && value != null && value!.isEmpty && super.level != DiagnosticLevel.hidden) {
|
2218 | return DiagnosticLevel.fine;
|
2219 | }
|
2220 | return super.level;
|
2221 | }
|
2222 |
|
2223 | @override
|
2224 | Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
2225 | final Map<String, Object?> json = super.toJsonMap(delegate);
|
2226 | if (value != null) {
|
2227 | json['values' ] = value!.map<String>((T value) => value.toString()).toList();
|
2228 | }
|
2229 | return json;
|
2230 | }
|
2231 | }
|
2232 |
|
2233 | /// [DiagnosticsProperty] that has an [Enum] as value.
|
2234 | ///
|
2235 | /// The enum value is displayed with the enum name stripped. For example:
|
2236 | /// [HitTestBehavior.deferToChild] is shown as `deferToChild`.
|
2237 | ///
|
2238 | /// This class can be used with enums and returns the enum's name getter. It
|
2239 | /// can also be used with nullable properties; the null value is represented as
|
2240 | /// `null`.
|
2241 | ///
|
2242 | /// See also:
|
2243 | ///
|
2244 | /// * [DiagnosticsProperty] which documents named parameters common to all
|
2245 | /// [DiagnosticsProperty].
|
2246 | class EnumProperty<T extends Enum?> extends DiagnosticsProperty<T> {
|
2247 | /// Create a diagnostics property that displays an enum.
|
2248 | ///
|
2249 | /// The [level] argument must also not be null.
|
2250 | EnumProperty(
|
2251 | String super.name,
|
2252 | super.value, {
|
2253 | super.defaultValue,
|
2254 | super.level,
|
2255 | });
|
2256 |
|
2257 | @override
|
2258 | String valueToString({ TextTreeConfiguration? parentConfiguration }) {
|
2259 | return value?.name ?? 'null' ;
|
2260 | }
|
2261 | }
|
2262 |
|
2263 | /// A property where the important diagnostic information is primarily whether
|
2264 | /// the [value] is present (non-null) or absent (null), rather than the actual
|
2265 | /// value of the property itself.
|
2266 | ///
|
2267 | /// The [ifPresent] and [ifNull] strings describe the property [value] when it
|
2268 | /// is non-null and null respectively. If one of [ifPresent] or [ifNull] is
|
2269 | /// omitted, that is taken to mean that [level] should be
|
2270 | /// [DiagnosticLevel.hidden] when [value] is non-null or null respectively.
|
2271 | ///
|
2272 | /// This kind of diagnostics property is typically used for opaque
|
2273 | /// values, like closures, where presenting the actual object is of dubious
|
2274 | /// value but where reporting the presence or absence of the value is much more
|
2275 | /// useful.
|
2276 | ///
|
2277 | /// See also:
|
2278 | ///
|
2279 | ///
|
2280 | /// * [FlagsSummary], which provides similar functionality but accepts multiple
|
2281 | /// flags under the same name, and is preferred if there are multiple such
|
2282 | /// values that can fit into a same category (such as "listeners").
|
2283 | /// * [FlagProperty], which provides similar functionality describing whether
|
2284 | /// a [value] is true or false.
|
2285 | class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
|
2286 | /// Create a diagnostics property for values that can be present (non-null) or
|
2287 | /// absent (null), but for which the exact value's [Object.toString]
|
2288 | /// representation is not very transparent (e.g. a callback).
|
2289 | ///
|
2290 | /// At least one of [ifPresent] or [ifNull] must be non-null.
|
2291 | ObjectFlagProperty(
|
2292 | String super.name,
|
2293 | super.value, {
|
2294 | this.ifPresent,
|
2295 | super.ifNull,
|
2296 | super.showName = false,
|
2297 | super.level,
|
2298 | }) : assert(ifPresent != null || ifNull != null);
|
2299 |
|
2300 | /// Shorthand constructor to describe whether the property has a value.
|
2301 | ///
|
2302 | /// Only use if prefixing the property name with the word 'has' is a good
|
2303 | /// flag name.
|
2304 | ObjectFlagProperty.has(
|
2305 | String super.name,
|
2306 | super.value, {
|
2307 | super.level,
|
2308 | }) : ifPresent = 'has $name' ,
|
2309 | super(
|
2310 | showName: false,
|
2311 | );
|
2312 |
|
2313 | /// Description to use if the property [value] is not null.
|
2314 | ///
|
2315 | /// If the property [value] is not null and [ifPresent] is null, the
|
2316 | /// [level] for the property is [DiagnosticLevel.hidden] and the description
|
2317 | /// from superclass is used.
|
2318 | final String? ifPresent;
|
2319 |
|
2320 | @override
|
2321 | String valueToString({ TextTreeConfiguration? parentConfiguration }) {
|
2322 | if (value != null) {
|
2323 | if (ifPresent != null) {
|
2324 | return ifPresent!;
|
2325 | }
|
2326 | } else {
|
2327 | if (ifNull != null) {
|
2328 | return ifNull!;
|
2329 | }
|
2330 | }
|
2331 | return super.valueToString(parentConfiguration: parentConfiguration);
|
2332 | }
|
2333 |
|
2334 | @override
|
2335 | bool get showName {
|
2336 | if ((value != null && ifPresent == null) || (value == null && ifNull == null)) {
|
2337 | // We are missing a description for the flag value so we need to show the
|
2338 | // flag name. The property will have DiagnosticLevel.hidden for this case
|
2339 | // so users will not see this property in this case unless they are
|
2340 | // displaying hidden properties.
|
2341 | return true;
|
2342 | }
|
2343 | return super.showName;
|
2344 | }
|
2345 |
|
2346 | @override
|
2347 | DiagnosticLevel get level {
|
2348 | if (value != null) {
|
2349 | if (ifPresent == null) {
|
2350 | return DiagnosticLevel.hidden;
|
2351 | }
|
2352 | } else {
|
2353 | if (ifNull == null) {
|
2354 | return DiagnosticLevel.hidden;
|
2355 | }
|
2356 | }
|
2357 |
|
2358 | return super.level;
|
2359 | }
|
2360 |
|
2361 | @override
|
2362 | Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
2363 | final Map<String, Object?> json = super.toJsonMap(delegate);
|
2364 | if (ifPresent != null) {
|
2365 | json['ifPresent' ] = ifPresent;
|
2366 | }
|
2367 | return json;
|
2368 | }
|
2369 | }
|
2370 |
|
2371 | /// A summary of multiple properties, indicating whether each of them is present
|
2372 | /// (non-null) or absent (null).
|
2373 | ///
|
2374 | /// Each entry of [value] is described by its key. The eventual description will
|
2375 | /// be a list of keys of non-null entries.
|
2376 | ///
|
2377 | /// The [ifEmpty] describes the entire collection of [value] when it contains no
|
2378 | /// non-null entries. If [ifEmpty] is omitted, [level] will be
|
2379 | /// [DiagnosticLevel.hidden] when [value] contains no non-null entries.
|
2380 | ///
|
2381 | /// This kind of diagnostics property is typically used for opaque
|
2382 | /// values, like closures, where presenting the actual object is of dubious
|
2383 | /// value but where reporting the presence or absence of the value is much more
|
2384 | /// useful.
|
2385 | ///
|
2386 | /// See also:
|
2387 | ///
|
2388 | /// * [ObjectFlagProperty], which provides similar functionality but accepts
|
2389 | /// only one flag, and is preferred if there is only one entry.
|
2390 | /// * [IterableProperty], which provides similar functionality describing
|
2391 | /// the values a collection of objects.
|
2392 | class FlagsSummary<T> extends DiagnosticsProperty<Map<String, T?>> {
|
2393 | /// Create a summary for multiple properties, indicating whether each of them
|
2394 | /// is present (non-null) or absent (null).
|
2395 | ///
|
2396 | /// The [value], [showName], [showSeparator] and [level] arguments must not be
|
2397 | /// null.
|
2398 | FlagsSummary(
|
2399 | String super.name,
|
2400 | Map<String, T?> super.value, {
|
2401 | super.ifEmpty,
|
2402 | super.showName,
|
2403 | super.showSeparator,
|
2404 | super.level,
|
2405 | });
|
2406 |
|
2407 | @override
|
2408 | Map<String, T?> get value => super.value!;
|
2409 |
|
2410 | @override
|
2411 | String valueToString({TextTreeConfiguration? parentConfiguration}) {
|
2412 | if (!_hasNonNullEntry() && ifEmpty != null) {
|
2413 | return ifEmpty!;
|
2414 | }
|
2415 |
|
2416 | final Iterable<String> formattedValues = _formattedValues();
|
2417 | if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
|
2418 | // Always display the value as a single line and enclose the iterable
|
2419 | // value in brackets to avoid ambiguity.
|
2420 | return '[ ${formattedValues.join(', ' )}]' ;
|
2421 | }
|
2422 |
|
2423 | return formattedValues.join(_isSingleLine(style) ? ', ' : '\n' );
|
2424 | }
|
2425 |
|
2426 | /// Priority level of the diagnostic used to control which diagnostics should
|
2427 | /// be shown and filtered.
|
2428 | ///
|
2429 | /// If [ifEmpty] is null and the [value] contains no non-null entries, then
|
2430 | /// level [DiagnosticLevel.hidden] is returned.
|
2431 | @override
|
2432 | DiagnosticLevel get level {
|
2433 | if (!_hasNonNullEntry() && ifEmpty == null) {
|
2434 | return DiagnosticLevel.hidden;
|
2435 | }
|
2436 | return super.level;
|
2437 | }
|
2438 |
|
2439 | @override
|
2440 | Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
2441 | final Map<String, Object?> json = super.toJsonMap(delegate);
|
2442 | if (value.isNotEmpty) {
|
2443 | json['values' ] = _formattedValues().toList();
|
2444 | }
|
2445 | return json;
|
2446 | }
|
2447 |
|
2448 | bool _hasNonNullEntry() => value.values.any((T? o) => o != null);
|
2449 |
|
2450 | // An iterable of each entry's description in [value].
|
2451 | //
|
2452 | // For a non-null value, its description is its key.
|
2453 | //
|
2454 | // For a null value, it is omitted unless `includeEmpty` is true and
|
2455 | // [ifEntryNull] contains a corresponding description.
|
2456 | Iterable<String> _formattedValues() {
|
2457 | return value.entries
|
2458 | .where((MapEntry<String, T?> entry) => entry.value != null)
|
2459 | .map((MapEntry<String, T?> entry) => entry.key);
|
2460 | }
|
2461 | }
|
2462 |
|
2463 | /// Signature for computing the value of a property.
|
2464 | ///
|
2465 | /// May throw exception if accessing the property would throw an exception
|
2466 | /// and callers must handle that case gracefully. For example, accessing a
|
2467 | /// property may trigger an assert that layout constraints were violated.
|
2468 | typedef ComputePropertyValueCallback<T> = T? Function();
|
2469 |
|
2470 | /// Property with a [value] of type [T].
|
2471 | ///
|
2472 | /// If the default `value.toString()` does not provide an adequate description
|
2473 | /// of the value, specify `description` defining a custom description.
|
2474 | ///
|
2475 | /// The [showSeparator] property indicates whether a separator should be placed
|
2476 | /// between the property [name] and its [value].
|
2477 | class DiagnosticsProperty<T> extends DiagnosticsNode {
|
2478 | /// Create a diagnostics property.
|
2479 | ///
|
2480 | /// The [level] argument is just a suggestion and can be overridden if
|
2481 | /// something else about the property causes it to have a lower or higher
|
2482 | /// level. For example, if the property value is null and [missingIfNull] is
|
2483 | /// true, [level] is raised to [DiagnosticLevel.warning].
|
2484 | DiagnosticsProperty(
|
2485 | String? name,
|
2486 | T? value, {
|
2487 | String? description,
|
2488 | String? ifNull,
|
2489 | this.ifEmpty,
|
2490 | super.showName,
|
2491 | super.showSeparator,
|
2492 | this.defaultValue = kNoDefaultValue,
|
2493 | this.tooltip,
|
2494 | this.missingIfNull = false,
|
2495 | super.linePrefix,
|
2496 | this.expandableValue = false,
|
2497 | this.allowWrap = true,
|
2498 | this.allowNameWrap = true,
|
2499 | DiagnosticsTreeStyle super.style = DiagnosticsTreeStyle.singleLine,
|
2500 | DiagnosticLevel level = DiagnosticLevel.info,
|
2501 | }) : _description = description,
|
2502 | _valueComputed = true,
|
2503 | _value = value,
|
2504 | _computeValue = null,
|
2505 | ifNull = ifNull ?? (missingIfNull ? 'MISSING' : null),
|
2506 | _defaultLevel = level,
|
2507 | super(
|
2508 | name: name,
|
2509 | );
|
2510 |
|
2511 | /// Property with a [value] that is computed only when needed.
|
2512 | ///
|
2513 | /// Use if computing the property [value] may throw an exception or is
|
2514 | /// expensive.
|
2515 | ///
|
2516 | /// The [level] argument is just a suggestion and can be overridden
|
2517 | /// if something else about the property causes it to have a lower or higher
|
2518 | /// level. For example, if calling `computeValue` throws an exception, [level]
|
2519 | /// will always return [DiagnosticLevel.error].
|
2520 | DiagnosticsProperty.lazy(
|
2521 | String? name,
|
2522 | ComputePropertyValueCallback<T> computeValue, {
|
2523 | String? description,
|
2524 | String? ifNull,
|
2525 | this.ifEmpty,
|
2526 | super.showName,
|
2527 | super.showSeparator,
|
2528 | this.defaultValue = kNoDefaultValue,
|
2529 | this.tooltip,
|
2530 | this.missingIfNull = false,
|
2531 | this.expandableValue = false,
|
2532 | this.allowWrap = true,
|
2533 | this.allowNameWrap = true,
|
2534 | DiagnosticsTreeStyle super.style = DiagnosticsTreeStyle.singleLine,
|
2535 | DiagnosticLevel level = DiagnosticLevel.info,
|
2536 | }) : assert(defaultValue == kNoDefaultValue || defaultValue is T?),
|
2537 | _description = description,
|
2538 | _valueComputed = false,
|
2539 | _value = null,
|
2540 | _computeValue = computeValue,
|
2541 | _defaultLevel = level,
|
2542 | ifNull = ifNull ?? (missingIfNull ? 'MISSING' : null),
|
2543 | super(
|
2544 | name: name,
|
2545 | );
|
2546 |
|
2547 | final String? _description;
|
2548 |
|
2549 | /// Whether to expose properties and children of the value as properties and
|
2550 | /// children.
|
2551 | final bool expandableValue;
|
2552 |
|
2553 | @override
|
2554 | final bool allowWrap;
|
2555 |
|
2556 | @override
|
2557 | final bool allowNameWrap;
|
2558 |
|
2559 | @override
|
2560 | Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
2561 | final T? v = value;
|
2562 | List<Map<String, Object?>>? properties;
|
2563 | if (delegate.expandPropertyValues && delegate.includeProperties && v is Diagnosticable && getProperties().isEmpty) {
|
2564 | // Exclude children for expanded nodes to avoid cycles.
|
2565 | delegate = delegate.copyWith(subtreeDepth: 0, includeProperties: false);
|
2566 | properties = DiagnosticsNode.toJsonList(
|
2567 | delegate.filterProperties(v.toDiagnosticsNode().getProperties(), this),
|
2568 | this,
|
2569 | delegate,
|
2570 | );
|
2571 | }
|
2572 | final Map<String, Object?> json = super.toJsonMap(delegate);
|
2573 | if (properties != null) {
|
2574 | json['properties' ] = properties;
|
2575 | }
|
2576 | if (defaultValue != kNoDefaultValue) {
|
2577 | json['defaultValue' ] = defaultValue.toString();
|
2578 | }
|
2579 | if (ifEmpty != null) {
|
2580 | json['ifEmpty' ] = ifEmpty;
|
2581 | }
|
2582 | if (ifNull != null) {
|
2583 | json['ifNull' ] = ifNull;
|
2584 | }
|
2585 | if (tooltip != null) {
|
2586 | json['tooltip' ] = tooltip;
|
2587 | }
|
2588 | json['missingIfNull' ] = missingIfNull;
|
2589 | if (exception != null) {
|
2590 | json['exception' ] = exception.toString();
|
2591 | }
|
2592 | json['propertyType' ] = propertyType.toString();
|
2593 | json['defaultLevel' ] = _defaultLevel.name;
|
2594 | if (value is Diagnosticable || value is DiagnosticsNode) {
|
2595 | json['isDiagnosticableValue' ] = true;
|
2596 | }
|
2597 | if (v is num) {
|
2598 | // TODO(jacob314): Workaround, since JSON.stringify replaces infinity and NaN with null,
|
2599 | // https://github.com/flutter/flutter/issues/39937#issuecomment-529558033)
|
2600 | json['value' ] = v.isFinite ? v : v.toString();
|
2601 | }
|
2602 | if (value is String || value is bool || value == null) {
|
2603 | json['value' ] = value;
|
2604 | }
|
2605 | return json;
|
2606 | }
|
2607 |
|
2608 | /// Returns a string representation of the property value.
|
2609 | ///
|
2610 | /// Subclasses should override this method instead of [toDescription] to
|
2611 | /// customize how property values are converted to strings.
|
2612 | ///
|
2613 | /// Overriding this method ensures that behavior controlling how property
|
2614 | /// values are decorated to generate a nice [toDescription] are consistent
|
2615 | /// across all implementations. Debugging tools may also choose to use
|
2616 | /// [valueToString] directly instead of [toDescription].
|
2617 | ///
|
2618 | /// `parentConfiguration` specifies how the parent is rendered as text art.
|
2619 | /// For example, if the parent places all properties on one line, the value
|
2620 | /// of the property should be displayed without line breaks if possible.
|
2621 | String valueToString({ TextTreeConfiguration? parentConfiguration }) {
|
2622 | final T? v = value;
|
2623 | // DiagnosticableTree values are shown using the shorter toStringShort()
|
2624 | // instead of the longer toString() because the toString() for a
|
2625 | // DiagnosticableTree value is likely too large to be useful.
|
2626 | return v is DiagnosticableTree ? v.toStringShort() : v.toString();
|
2627 | }
|
2628 |
|
2629 | @override
|
2630 | String toDescription({ TextTreeConfiguration? parentConfiguration }) {
|
2631 | if (_description != null) {
|
2632 | return _addTooltip(_description);
|
2633 | }
|
2634 |
|
2635 | if (exception != null) {
|
2636 | return 'EXCEPTION ( ${exception.runtimeType})' ;
|
2637 | }
|
2638 |
|
2639 | if (ifNull != null && value == null) {
|
2640 | return _addTooltip(ifNull!);
|
2641 | }
|
2642 |
|
2643 | String result = valueToString(parentConfiguration: parentConfiguration);
|
2644 | if (result.isEmpty && ifEmpty != null) {
|
2645 | result = ifEmpty!;
|
2646 | }
|
2647 | return _addTooltip(result);
|
2648 | }
|
2649 |
|
2650 | /// If a [tooltip] is specified, add the tooltip it to the end of `text`
|
2651 | /// enclosing it parenthesis to disambiguate the tooltip from the rest of
|
2652 | /// the text.
|
2653 | String _addTooltip(String text) {
|
2654 | return tooltip == null ? text : ' $text ( $tooltip)' ;
|
2655 | }
|
2656 |
|
2657 | /// Description if the property [value] is null.
|
2658 | final String? ifNull;
|
2659 |
|
2660 | /// Description if the property description would otherwise be empty.
|
2661 | final String? ifEmpty;
|
2662 |
|
2663 | /// Optional tooltip typically describing the property.
|
2664 | ///
|
2665 | /// Example tooltip: 'physical pixels per logical pixel'
|
2666 | ///
|
2667 | /// If present, the tooltip is added in parenthesis after the raw value when
|
2668 | /// generating the string description.
|
2669 | final String? tooltip;
|
2670 |
|
2671 | /// Whether a [value] of null causes the property to have [level]
|
2672 | /// [DiagnosticLevel.warning] warning that the property is missing a [value].
|
2673 | final bool missingIfNull;
|
2674 |
|
2675 | /// The type of the property [value].
|
2676 | ///
|
2677 | /// This is determined from the type argument `T` used to instantiate the
|
2678 | /// [DiagnosticsProperty] class. This means that the type is available even if
|
2679 | /// [value] is null, but it also means that the [propertyType] is only as
|
2680 | /// accurate as the type provided when invoking the constructor.
|
2681 | ///
|
2682 | /// Generally, this is only useful for diagnostic tools that should display
|
2683 | /// null values in a manner consistent with the property type. For example, a
|
2684 | /// tool might display a null [Color] value as an empty rectangle instead of
|
2685 | /// the word "null".
|
2686 | Type get propertyType => T;
|
2687 |
|
2688 | /// Returns the value of the property either from cache or by invoking a
|
2689 | /// [ComputePropertyValueCallback].
|
2690 | ///
|
2691 | /// If an exception is thrown invoking the [ComputePropertyValueCallback],
|
2692 | /// [value] returns null and the exception thrown can be found via the
|
2693 | /// [exception] property.
|
2694 | ///
|
2695 | /// See also:
|
2696 | ///
|
2697 | /// * [valueToString], which converts the property value to a string.
|
2698 | @override
|
2699 | T? get value {
|
2700 | _maybeCacheValue();
|
2701 | return _value;
|
2702 | }
|
2703 |
|
2704 | T? _value;
|
2705 |
|
2706 | bool _valueComputed;
|
2707 |
|
2708 | Object? _exception;
|
2709 |
|
2710 | /// Exception thrown if accessing the property [value] threw an exception.
|
2711 | ///
|
2712 | /// Returns null if computing the property value did not throw an exception.
|
2713 | Object? get exception {
|
2714 | _maybeCacheValue();
|
2715 | return _exception;
|
2716 | }
|
2717 |
|
2718 | void _maybeCacheValue() {
|
2719 | if (_valueComputed) {
|
2720 | return;
|
2721 | }
|
2722 |
|
2723 | _valueComputed = true;
|
2724 | assert(_computeValue != null);
|
2725 | try {
|
2726 | _value = _computeValue!();
|
2727 | } catch (exception) {
|
2728 | // The error is reported to inspector; rethrowing would destroy the
|
2729 | // debugging experience.
|
2730 | _exception = exception;
|
2731 | _value = null;
|
2732 | }
|
2733 | }
|
2734 |
|
2735 | /// The default value of this property, when it has not been set to a specific
|
2736 | /// value.
|
2737 | ///
|
2738 | /// For most [DiagnosticsProperty] classes, if the [value] of the property
|
2739 | /// equals [defaultValue], then the priority [level] of the property is
|
2740 | /// downgraded to [DiagnosticLevel.fine] on the basis that the property value
|
2741 | /// is uninteresting. This is implemented by [isInteresting].
|
2742 | ///
|
2743 | /// The [defaultValue] is [kNoDefaultValue] by default. Otherwise it must be of
|
2744 | /// type `T?`.
|
2745 | final Object? defaultValue;
|
2746 |
|
2747 | /// Whether to consider the property's value interesting. When a property is
|
2748 | /// uninteresting, its [level] is downgraded to [DiagnosticLevel.fine]
|
2749 | /// regardless of the value provided as the constructor's `level` argument.
|
2750 | bool get isInteresting => defaultValue == kNoDefaultValue || value != defaultValue;
|
2751 |
|
2752 | final DiagnosticLevel _defaultLevel;
|
2753 |
|
2754 | /// Priority level of the diagnostic used to control which diagnostics should
|
2755 | /// be shown and filtered.
|
2756 | ///
|
2757 | /// The property level defaults to the value specified by the [level]
|
2758 | /// constructor argument. The level is raised to [DiagnosticLevel.error] if
|
2759 | /// an [exception] was thrown getting the property [value]. The level is
|
2760 | /// raised to [DiagnosticLevel.warning] if the property [value] is null and
|
2761 | /// the property is not allowed to be null due to [missingIfNull]. The
|
2762 | /// priority level is lowered to [DiagnosticLevel.fine] if the property
|
2763 | /// [value] equals [defaultValue].
|
2764 | @override
|
2765 | DiagnosticLevel get level {
|
2766 | if (_defaultLevel == DiagnosticLevel.hidden) {
|
2767 | return _defaultLevel;
|
2768 | }
|
2769 |
|
2770 | if (exception != null) {
|
2771 | return DiagnosticLevel.error;
|
2772 | }
|
2773 |
|
2774 | if (value == null && missingIfNull) {
|
2775 | return DiagnosticLevel.warning;
|
2776 | }
|
2777 |
|
2778 | if (!isInteresting) {
|
2779 | return DiagnosticLevel.fine;
|
2780 | }
|
2781 |
|
2782 | return _defaultLevel;
|
2783 | }
|
2784 |
|
2785 | final ComputePropertyValueCallback<T>? _computeValue;
|
2786 |
|
2787 | @override
|
2788 | List<DiagnosticsNode> getProperties() {
|
2789 | if (expandableValue) {
|
2790 | final T? object = value;
|
2791 | if (object is DiagnosticsNode) {
|
2792 | return object.getProperties();
|
2793 | }
|
2794 | if (object is Diagnosticable) {
|
2795 | return object.toDiagnosticsNode(style: style).getProperties();
|
2796 | }
|
2797 | }
|
2798 | return const <DiagnosticsNode>[];
|
2799 | }
|
2800 |
|
2801 | @override
|
2802 | List<DiagnosticsNode> getChildren() {
|
2803 | if (expandableValue) {
|
2804 | final T? object = value;
|
2805 | if (object is DiagnosticsNode) {
|
2806 | return object.getChildren();
|
2807 | }
|
2808 | if (object is Diagnosticable) {
|
2809 | return object.toDiagnosticsNode(style: style).getChildren();
|
2810 | }
|
2811 | }
|
2812 | return const <DiagnosticsNode>[];
|
2813 | }
|
2814 | }
|
2815 |
|
2816 | /// [DiagnosticsNode] that lazily calls the associated [Diagnosticable] [value]
|
2817 | /// to implement [getChildren] and [getProperties].
|
2818 | class DiagnosticableNode<T extends Diagnosticable> extends DiagnosticsNode {
|
2819 | /// Create a diagnostics describing a [Diagnosticable] value.
|
2820 | DiagnosticableNode({
|
2821 | super.name,
|
2822 | required this.value,
|
2823 | required super.style,
|
2824 | });
|
2825 |
|
2826 | @override
|
2827 | final T value;
|
2828 |
|
2829 | DiagnosticPropertiesBuilder? _cachedBuilder;
|
2830 |
|
2831 | /// Retrieve the [DiagnosticPropertiesBuilder] of current node.
|
2832 | ///
|
2833 | /// It will cache the result to prevent duplicate operation.
|
2834 | DiagnosticPropertiesBuilder? get builder {
|
2835 | if (kReleaseMode) {
|
2836 | return null;
|
2837 | } else {
|
2838 | assert(() {
|
2839 | if (_cachedBuilder == null) {
|
2840 | _cachedBuilder = DiagnosticPropertiesBuilder();
|
2841 | value.debugFillProperties(_cachedBuilder!);
|
2842 | }
|
2843 | return true;
|
2844 | }());
|
2845 | return _cachedBuilder;
|
2846 | }
|
2847 | }
|
2848 |
|
2849 | @override
|
2850 | DiagnosticsTreeStyle get style {
|
2851 | return kReleaseMode ? DiagnosticsTreeStyle.none : super.style ?? builder!.defaultDiagnosticsTreeStyle;
|
2852 | }
|
2853 |
|
2854 | @override
|
2855 | String? get emptyBodyDescription => (kReleaseMode || kProfileMode) ? '' : builder!.emptyBodyDescription;
|
2856 |
|
2857 | @override
|
2858 | List<DiagnosticsNode> getProperties() => (kReleaseMode || kProfileMode) ? const <DiagnosticsNode>[] : builder!.properties;
|
2859 |
|
2860 | @override
|
2861 | List<DiagnosticsNode> getChildren() {
|
2862 | return const<DiagnosticsNode>[];
|
2863 | }
|
2864 |
|
2865 | @override
|
2866 | String toDescription({ TextTreeConfiguration? parentConfiguration }) {
|
2867 | String result = '' ;
|
2868 | assert(() {
|
2869 | result = value.toStringShort();
|
2870 | return true;
|
2871 | }());
|
2872 | return result;
|
2873 | }
|
2874 | }
|
2875 |
|
2876 | /// [DiagnosticsNode] for an instance of [DiagnosticableTree].
|
2877 | class DiagnosticableTreeNode extends DiagnosticableNode<DiagnosticableTree> {
|
2878 | /// Creates a [DiagnosticableTreeNode].
|
2879 | DiagnosticableTreeNode({
|
2880 | super.name,
|
2881 | required super.value,
|
2882 | required super.style,
|
2883 | });
|
2884 |
|
2885 | @override
|
2886 | List<DiagnosticsNode> getChildren() => value.debugDescribeChildren();
|
2887 | }
|
2888 |
|
2889 | /// Returns a 5 character long hexadecimal string generated from
|
2890 | /// [Object.hashCode]'s 20 least-significant bits.
|
2891 | String shortHash(Object? object) {
|
2892 | return object.hashCode.toUnsigned(20).toRadixString(16).padLeft(5, '0' );
|
2893 | }
|
2894 |
|
2895 | /// Returns a summary of the runtime type and hash code of `object`.
|
2896 | ///
|
2897 | /// See also:
|
2898 | ///
|
2899 | /// * [Object.hashCode], a value used when placing an object in a [Map] or
|
2900 | /// other similar data structure, and which is also used in debug output to
|
2901 | /// distinguish instances of the same class (hash collisions are
|
2902 | /// possible, but rare enough that its use in debug output is useful).
|
2903 | /// * [Object.runtimeType], the [Type] of an object.
|
2904 | String describeIdentity(Object? object) => ' ${objectRuntimeType(object, '<optimized out>' )}# ${shortHash(object)}' ;
|
2905 |
|
2906 | /// Returns a short description of an enum value.
|
2907 | ///
|
2908 | /// Strips off the enum class name from the `enumEntry.toString()`.
|
2909 | ///
|
2910 | /// For real enums, this is redundant with calling the `name` getter on the enum
|
2911 | /// value (see [EnumName.name]), a feature that was added to Dart 2.15.
|
2912 | ///
|
2913 | /// This function can also be used with classes whose `toString` return a value
|
2914 | /// in the same form as an enum (the class name, a dot, then the value name).
|
2915 | /// For example, it's used with [SemanticsAction], which is written to appear to
|
2916 | /// be an enum but is actually a bespoke class so that the index values can be
|
2917 | /// set as powers of two instead of as sequential integers.
|
2918 | ///
|
2919 | /// {@tool snippet}
|
2920 | ///
|
2921 | /// ```dart
|
2922 | /// enum Day {
|
2923 | /// monday, tuesday, wednesday, thursday, friday, saturday, sunday
|
2924 | /// }
|
2925 | ///
|
2926 | /// void validateDescribeEnum() {
|
2927 | /// assert(Day.monday.toString() == 'Day.monday');
|
2928 | /// assert(describeEnum(Day.monday) == 'monday');
|
2929 | /// assert(Day.monday.name == 'monday'); // preferred for real enums
|
2930 | /// }
|
2931 | /// ```
|
2932 | /// {@end-tool}
|
2933 | @Deprecated(
|
2934 | 'Use the `name` getter on enums instead. '
|
2935 | 'This feature was deprecated after v3.14.0-2.0.pre.'
|
2936 | )
|
2937 | String describeEnum(Object enumEntry) {
|
2938 | if (enumEntry is Enum) {
|
2939 | return enumEntry.name;
|
2940 | }
|
2941 | final String description = enumEntry.toString();
|
2942 | final int indexOfDot = description.indexOf('.' );
|
2943 | assert(
|
2944 | indexOfDot != -1 && indexOfDot < description.length - 1,
|
2945 | 'The provided object " $enumEntry" is not an enum.' ,
|
2946 | );
|
2947 | return description.substring(indexOfDot + 1);
|
2948 | }
|
2949 |
|
2950 | /// Builder to accumulate properties and configuration used to assemble a
|
2951 | /// [DiagnosticsNode] from a [Diagnosticable] object.
|
2952 | class DiagnosticPropertiesBuilder {
|
2953 | /// Creates a [DiagnosticPropertiesBuilder] with [properties] initialize to
|
2954 | /// an empty array.
|
2955 | DiagnosticPropertiesBuilder() : properties = <DiagnosticsNode>[];
|
2956 |
|
2957 | /// Creates a [DiagnosticPropertiesBuilder] with a given [properties].
|
2958 | DiagnosticPropertiesBuilder.fromProperties(this.properties);
|
2959 |
|
2960 | /// Add a property to the list of properties.
|
2961 | void add(DiagnosticsNode property) {
|
2962 | assert(() {
|
2963 | properties.add(property);
|
2964 | return true;
|
2965 | }());
|
2966 | }
|
2967 |
|
2968 | /// List of properties accumulated so far.
|
2969 | final List<DiagnosticsNode> properties;
|
2970 |
|
2971 | /// Default style to use for the [DiagnosticsNode] if no style is specified.
|
2972 | DiagnosticsTreeStyle defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.sparse;
|
2973 |
|
2974 | /// Description to show if the node has no displayed properties or children.
|
2975 | String? emptyBodyDescription;
|
2976 | }
|
2977 |
|
2978 | // Examples can assume:
|
2979 | // class ExampleSuperclass with Diagnosticable { late String message; late double stepWidth; late double scale; late double paintExtent; late double hitTestExtent; late double paintExtend; late double maxWidth; late bool primary; late double progress; late int maxLines; late Duration duration; late int depth; Iterable? boxShadow; late DiagnosticsTreeStyle style; late bool hasSize; late Matrix4 transform; Map? handles; late Color color; late bool obscureText; late ImageRepeat repeat; late Size size; late Widget widget; late bool isCurrent; late bool keepAlive; late TextAlign textAlign; }
|
2980 |
|
2981 | /// A mixin class for providing string and [DiagnosticsNode] debug
|
2982 | /// representations describing the properties of an object.
|
2983 | ///
|
2984 | /// The string debug representation is generated from the intermediate
|
2985 | /// [DiagnosticsNode] representation. The [DiagnosticsNode] representation is
|
2986 | /// also used by debugging tools displaying interactive trees of objects and
|
2987 | /// properties.
|
2988 | ///
|
2989 | /// See also:
|
2990 | ///
|
2991 | /// * [debugFillProperties], which lists best practices for specifying the
|
2992 | /// properties of a [DiagnosticsNode]. The most common use case is to
|
2993 | /// override [debugFillProperties] defining custom properties for a subclass
|
2994 | /// of [DiagnosticableTreeMixin] using the existing [DiagnosticsProperty]
|
2995 | /// subclasses.
|
2996 | /// * [DiagnosticableTree], which extends this class to also describe the
|
2997 | /// children of a tree structured object.
|
2998 | /// * [DiagnosticableTree.debugDescribeChildren], which lists best practices
|
2999 | /// for describing the children of a [DiagnosticsNode]. Typically the base
|
3000 | /// class already describes the children of a node properly or a node has
|
3001 | /// no children.
|
3002 | /// * [DiagnosticsProperty], which should be used to create leaf diagnostic
|
3003 | /// nodes without properties or children. There are many
|
3004 | /// [DiagnosticsProperty] subclasses to handle common use cases.
|
3005 | mixin Diagnosticable {
|
3006 | /// A brief description of this object, usually just the [runtimeType] and the
|
3007 | /// [hashCode].
|
3008 | ///
|
3009 | /// See also:
|
3010 | ///
|
3011 | /// * [toString], for a detailed description of the object.
|
3012 | String toStringShort() => describeIdentity(this);
|
3013 |
|
3014 | @override
|
3015 | String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
3016 | String? fullString;
|
3017 | assert(() {
|
3018 | fullString = toDiagnosticsNode(style: DiagnosticsTreeStyle.singleLine).toString(minLevel: minLevel);
|
3019 | return true;
|
3020 | }());
|
3021 | return fullString ?? toStringShort();
|
3022 | }
|
3023 |
|
3024 | /// Returns a debug representation of the object that is used by debugging
|
3025 | /// tools and by [DiagnosticsNode.toStringDeep].
|
3026 | ///
|
3027 | /// Leave [name] as null if there is not a meaningful description of the
|
3028 | /// relationship between the this node and its parent.
|
3029 | ///
|
3030 | /// Typically the [style] argument is only specified to indicate an atypical
|
3031 | /// relationship between the parent and the node. For example, pass
|
3032 | /// [DiagnosticsTreeStyle.offstage] to indicate that a node is offstage.
|
3033 | DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
|
3034 | return DiagnosticableNode<Diagnosticable>(
|
3035 | name: name,
|
3036 | value: this,
|
3037 | style: style,
|
3038 | );
|
3039 | }
|
3040 |
|
3041 | /// Add additional properties associated with the node.
|
3042 | ///
|
3043 | /// {@youtube 560 315 https://www.youtube.com/watch?v=DnC7eT-vh1k}
|
3044 | ///
|
3045 | /// Use the most specific [DiagnosticsProperty] existing subclass to describe
|
3046 | /// each property instead of the [DiagnosticsProperty] base class. There are
|
3047 | /// only a small number of [DiagnosticsProperty] subclasses each covering a
|
3048 | /// common use case. Consider what values a property is relevant for users
|
3049 | /// debugging as users debugging large trees are overloaded with information.
|
3050 | /// Common named parameters in [DiagnosticsNode] subclasses help filter when
|
3051 | /// and how properties are displayed.
|
3052 | ///
|
3053 | /// `defaultValue`, `showName`, `showSeparator`, and `level` keep string
|
3054 | /// representations of diagnostics terse and hide properties when they are not
|
3055 | /// very useful.
|
3056 | ///
|
3057 | /// * Use `defaultValue` any time the default value of a property is
|
3058 | /// uninteresting. For example, specify a default value of null any time
|
3059 | /// a property being null does not indicate an error.
|
3060 | /// * Avoid specifying the `level` parameter unless the result you want
|
3061 | /// cannot be achieved by using the `defaultValue` parameter or using
|
3062 | /// the [ObjectFlagProperty] class to conditionally display the property
|
3063 | /// as a flag.
|
3064 | /// * Specify `showName` and `showSeparator` in rare cases where the string
|
3065 | /// output would look clumsy if they were not set.
|
3066 | /// ```dart
|
3067 | /// DiagnosticsProperty<Object>('child(3, 4)', null, ifNull: 'is null', showSeparator: false).toString()
|
3068 | /// ```
|
3069 | /// Shows using `showSeparator` to get output `child(3, 4) is null` which
|
3070 | /// is more polished than `child(3, 4): is null`.
|
3071 | /// ```dart
|
3072 | /// DiagnosticsProperty<IconData>('icon', icon, ifNull: '<empty>', showName: false).toString()
|
3073 | /// ```
|
3074 | /// Shows using `showName` to omit the property name as in this context the
|
3075 | /// property name does not add useful information.
|
3076 | ///
|
3077 | /// `ifNull`, `ifEmpty`, `unit`, and `tooltip` make property
|
3078 | /// descriptions clearer. The examples in the code sample below illustrate
|
3079 | /// good uses of all of these parameters.
|
3080 | ///
|
3081 | /// ## DiagnosticsProperty subclasses for primitive types
|
3082 | ///
|
3083 | /// * [StringProperty], which supports automatically enclosing a [String]
|
3084 | /// value in quotes.
|
3085 | /// * [DoubleProperty], which supports specifying a unit of measurement for
|
3086 | /// a [double] value.
|
3087 | /// * [PercentProperty], which clamps a [double] to between 0 and 1 and
|
3088 | /// formats it as a percentage.
|
3089 | /// * [IntProperty], which supports specifying a unit of measurement for an
|
3090 | /// [int] value.
|
3091 | /// * [FlagProperty], which formats a [bool] value as one or more flags.
|
3092 | /// Depending on the use case it is better to format a bool as
|
3093 | /// `DiagnosticsProperty<bool>` instead of using [FlagProperty] as the
|
3094 | /// output is more verbose but unambiguous.
|
3095 | ///
|
3096 | /// ## Other important [DiagnosticsProperty] variants
|
3097 | ///
|
3098 | /// * [EnumProperty], which provides terse descriptions of enum values
|
3099 | /// working around limitations of the `toString` implementation for Dart
|
3100 | /// enum types.
|
3101 | /// * [IterableProperty], which handles iterable values with display
|
3102 | /// customizable depending on the [DiagnosticsTreeStyle] used.
|
3103 | /// * [ObjectFlagProperty], which provides terse descriptions of whether a
|
3104 | /// property value is present or not. For example, whether an `onClick`
|
3105 | /// callback is specified or an animation is in progress.
|
3106 | /// * [ColorProperty], which must be used if the property value is
|
3107 | /// a [Color] or one of its subclasses.
|
3108 | /// * [IconDataProperty], which must be used if the property value
|
3109 | /// is of type [IconData].
|
3110 | ///
|
3111 | /// If none of these subclasses apply, use the [DiagnosticsProperty]
|
3112 | /// constructor or in rare cases create your own [DiagnosticsProperty]
|
3113 | /// subclass as in the case for [TransformProperty] which handles [Matrix4]
|
3114 | /// that represent transforms. Generally any property value with a good
|
3115 | /// `toString` method implementation works fine using [DiagnosticsProperty]
|
3116 | /// directly.
|
3117 | ///
|
3118 | /// {@tool snippet}
|
3119 | ///
|
3120 | /// This example shows best practices for implementing [debugFillProperties]
|
3121 | /// illustrating use of all common [DiagnosticsProperty] subclasses and all
|
3122 | /// common [DiagnosticsProperty] parameters.
|
3123 | ///
|
3124 | /// ```dart
|
3125 | /// class ExampleObject extends ExampleSuperclass {
|
3126 | ///
|
3127 | /// // ...various members and properties...
|
3128 | ///
|
3129 | /// @override
|
3130 | /// void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
3131 | /// // Always add properties from the base class first.
|
3132 | /// super.debugFillProperties(properties);
|
3133 | ///
|
3134 | /// // Omit the property name 'message' when displaying this String property
|
3135 | /// // as it would just add visual noise.
|
3136 | /// properties.add(StringProperty('message', message, showName: false));
|
3137 | ///
|
3138 | /// properties.add(DoubleProperty('stepWidth', stepWidth));
|
3139 | ///
|
3140 | /// // A scale of 1.0 does nothing so should be hidden.
|
3141 | /// properties.add(DoubleProperty('scale', scale, defaultValue: 1.0));
|
3142 | ///
|
3143 | /// // If the hitTestExtent matches the paintExtent, it is just set to its
|
3144 | /// // default value so is not relevant.
|
3145 | /// properties.add(DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent));
|
3146 | ///
|
3147 | /// // maxWidth of double.infinity indicates the width is unconstrained and
|
3148 | /// // so maxWidth has no impact.
|
3149 | /// properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity));
|
3150 | ///
|
3151 | /// // Progress is a value between 0 and 1 or null. Showing it as a
|
3152 | /// // percentage makes the meaning clear enough that the name can be
|
3153 | /// // hidden.
|
3154 | /// properties.add(PercentProperty(
|
3155 | /// 'progress',
|
3156 | /// progress,
|
3157 | /// showName: false,
|
3158 | /// ifNull: '<indeterminate>',
|
3159 | /// ));
|
3160 | ///
|
3161 | /// // Most text fields have maxLines set to 1.
|
3162 | /// properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
|
3163 | ///
|
3164 | /// // Specify the unit as otherwise it would be unclear that time is in
|
3165 | /// // milliseconds.
|
3166 | /// properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
|
3167 | ///
|
3168 | /// // Tooltip is used instead of unit for this case as a unit should be a
|
3169 | /// // terse description appropriate to display directly after a number
|
3170 | /// // without a space.
|
3171 | /// properties.add(DoubleProperty(
|
3172 | /// 'device pixel ratio',
|
3173 | /// devicePixelRatio,
|
3174 | /// tooltip: 'physical pixels per logical pixel',
|
3175 | /// ));
|
3176 | ///
|
3177 | /// // Displaying the depth value would be distracting. Instead only display
|
3178 | /// // if the depth value is missing.
|
3179 | /// properties.add(ObjectFlagProperty<int>('depth', depth, ifNull: 'no depth'));
|
3180 | ///
|
3181 | /// // bool flag that is only shown when the value is true.
|
3182 | /// properties.add(FlagProperty('using primary controller', value: primary));
|
3183 | ///
|
3184 | /// properties.add(FlagProperty(
|
3185 | /// 'isCurrent',
|
3186 | /// value: isCurrent,
|
3187 | /// ifTrue: 'active',
|
3188 | /// ifFalse: 'inactive',
|
3189 | /// ));
|
3190 | ///
|
3191 | /// properties.add(DiagnosticsProperty<bool>('keepAlive', keepAlive));
|
3192 | ///
|
3193 | /// // FlagProperty could have also been used in this case.
|
3194 | /// // This option results in the text "obscureText: true" instead
|
3195 | /// // of "obscureText" which is a bit more verbose but a bit clearer.
|
3196 | /// properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
|
3197 | ///
|
3198 | /// properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
|
3199 | /// properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
|
3200 | ///
|
3201 | /// // Warn users when the widget is missing but do not show the value.
|
3202 | /// properties.add(ObjectFlagProperty<Widget>('widget', widget, ifNull: 'no widget'));
|
3203 | ///
|
3204 | /// properties.add(IterableProperty<BoxShadow>(
|
3205 | /// 'boxShadow',
|
3206 | /// boxShadow,
|
3207 | /// defaultValue: null,
|
3208 | /// style: style,
|
3209 | /// ));
|
3210 | ///
|
3211 | /// // Getting the value of size throws an exception unless hasSize is true.
|
3212 | /// properties.add(DiagnosticsProperty<Size>.lazy(
|
3213 | /// 'size',
|
3214 | /// () => size,
|
3215 | /// description: '${ hasSize ? size : "MISSING" }',
|
3216 | /// ));
|
3217 | ///
|
3218 | /// // If the `toString` method for the property value does not provide a
|
3219 | /// // good terse description, write a DiagnosticsProperty subclass as in
|
3220 | /// // the case of TransformProperty which displays a nice debugging view
|
3221 | /// // of a Matrix4 that represents a transform.
|
3222 | /// properties.add(TransformProperty('transform', transform));
|
3223 | ///
|
3224 | /// // If the value class has a good `toString` method, use
|
3225 | /// // DiagnosticsProperty. Specifying the value type ensures
|
3226 | /// // that debugging tools always know the type of the field and so can
|
3227 | /// // provide the right UI affordances. For example, in this case even
|
3228 | /// // if color is null, a debugging tool still knows the value is a Color
|
3229 | /// // and can display relevant color related UI.
|
3230 | /// properties.add(DiagnosticsProperty<Color>('color', color));
|
3231 | ///
|
3232 | /// // Use a custom description to generate a more terse summary than the
|
3233 | /// // `toString` method on the map class.
|
3234 | /// properties.add(DiagnosticsProperty<Map<Listenable, VoidCallback>>(
|
3235 | /// 'handles',
|
3236 | /// handles,
|
3237 | /// description: handles != null
|
3238 | /// ? '${handles!.length} active client${ handles!.length == 1 ? "" : "s" }'
|
3239 | /// : null,
|
3240 | /// ifNull: 'no notifications ever received',
|
3241 | /// showName: false,
|
3242 | /// ));
|
3243 | /// }
|
3244 | /// }
|
3245 | /// ```
|
3246 | /// {@end-tool}
|
3247 | ///
|
3248 | /// Used by [toDiagnosticsNode] and [toString].
|
3249 | ///
|
3250 | /// Do not add values that have lifetime shorter than the object.
|
3251 | @protected
|
3252 | @mustCallSuper
|
3253 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { }
|
3254 | }
|
3255 |
|
3256 | /// A base class for providing string and [DiagnosticsNode] debug
|
3257 | /// representations describing the properties and children of an object.
|
3258 | ///
|
3259 | /// The string debug representation is generated from the intermediate
|
3260 | /// [DiagnosticsNode] representation. The [DiagnosticsNode] representation is
|
3261 | /// also used by debugging tools displaying interactive trees of objects and
|
3262 | /// properties.
|
3263 | ///
|
3264 | /// See also:
|
3265 | ///
|
3266 | /// * [DiagnosticableTreeMixin], a mixin that implements this class.
|
3267 | /// * [Diagnosticable], which should be used instead of this class to
|
3268 | /// provide diagnostics for objects without children.
|
3269 | abstract class DiagnosticableTree with Diagnosticable {
|
3270 | /// Abstract const constructor. This constructor enables subclasses to provide
|
3271 | /// const constructors so that they can be used in const expressions.
|
3272 | const DiagnosticableTree();
|
3273 |
|
3274 | /// Returns a one-line detailed description of the object.
|
3275 | ///
|
3276 | /// This description is often somewhat long. This includes the same
|
3277 | /// information given by [toStringDeep], but does not recurse to any children.
|
3278 | ///
|
3279 | /// `joiner` specifies the string which is place between each part obtained
|
3280 | /// from [debugFillProperties]. Passing a string such as `'\n '` will result
|
3281 | /// in a multiline string that indents the properties of the object below its
|
3282 | /// name (as per [toString]).
|
3283 | ///
|
3284 | /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
|
3285 | /// in the output.
|
3286 | ///
|
3287 | /// See also:
|
3288 | ///
|
3289 | /// * [toString], for a brief description of the object.
|
3290 | /// * [toStringDeep], for a description of the subtree rooted at this object.
|
3291 | String toStringShallow({
|
3292 | String joiner = ', ' ,
|
3293 | DiagnosticLevel minLevel = DiagnosticLevel.debug,
|
3294 | }) {
|
3295 | String? shallowString;
|
3296 | assert(() {
|
3297 | final StringBuffer result = StringBuffer();
|
3298 | result.write(toString());
|
3299 | result.write(joiner);
|
3300 | final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
3301 | debugFillProperties(builder);
|
3302 | result.write(
|
3303 | builder.properties.where((DiagnosticsNode n) => !n.isFiltered(minLevel))
|
3304 | .join(joiner),
|
3305 | );
|
3306 | shallowString = result.toString();
|
3307 | return true;
|
3308 | }());
|
3309 | return shallowString ?? toString();
|
3310 | }
|
3311 |
|
3312 | /// Returns a string representation of this node and its descendants.
|
3313 | ///
|
3314 | /// `prefixLineOne` will be added to the front of the first line of the
|
3315 | /// output. `prefixOtherLines` will be added to the front of each other line.
|
3316 | /// If `prefixOtherLines` is null, the `prefixLineOne` is used for every line.
|
3317 | /// By default, there is no prefix.
|
3318 | ///
|
3319 | /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
|
3320 | /// in the output.
|
3321 | ///
|
3322 | /// `wrapWidth` specifies the column number where word wrapping will be
|
3323 | /// applied.
|
3324 | ///
|
3325 | /// The [toStringDeep] method takes other arguments, but those are intended
|
3326 | /// for internal use when recursing to the descendants, and so can be ignored.
|
3327 | ///
|
3328 | /// See also:
|
3329 | ///
|
3330 | /// * [toString], for a brief description of the object but not its children.
|
3331 | /// * [toStringShallow], for a detailed description of the object but not its
|
3332 | /// children.
|
3333 | String toStringDeep({
|
3334 | String prefixLineOne = '' ,
|
3335 | String? prefixOtherLines,
|
3336 | DiagnosticLevel minLevel = DiagnosticLevel.debug,
|
3337 | int wrapWidth = 65,
|
3338 | }) {
|
3339 | return toDiagnosticsNode().toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel, wrapWidth: wrapWidth);
|
3340 | }
|
3341 |
|
3342 | @override
|
3343 | String toStringShort() => describeIdentity(this);
|
3344 |
|
3345 | @override
|
3346 | DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
|
3347 | return DiagnosticableTreeNode(
|
3348 | name: name,
|
3349 | value: this,
|
3350 | style: style,
|
3351 | );
|
3352 | }
|
3353 |
|
3354 | /// Returns a list of [DiagnosticsNode] objects describing this node's
|
3355 | /// children.
|
3356 | ///
|
3357 | /// Children that are offstage should be added with `style` set to
|
3358 | /// [DiagnosticsTreeStyle.offstage] to indicate that they are offstage.
|
3359 | ///
|
3360 | /// The list must not contain any null entries. If there are explicit null
|
3361 | /// children to report, consider [DiagnosticsNode.message] or
|
3362 | /// [DiagnosticsProperty<Object>] as possible [DiagnosticsNode] objects to
|
3363 | /// provide.
|
3364 | ///
|
3365 | /// Used by [toStringDeep], [toDiagnosticsNode] and [toStringShallow].
|
3366 | ///
|
3367 | /// See also:
|
3368 | ///
|
3369 | /// * [RenderTable.debugDescribeChildren], which provides high quality custom
|
3370 | /// descriptions for its child nodes.
|
3371 | @protected
|
3372 | List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];
|
3373 | }
|
3374 |
|
3375 | /// A mixin that helps dump string and [DiagnosticsNode] representations of trees.
|
3376 | ///
|
3377 | /// This mixin is identical to class [DiagnosticableTree].
|
3378 | mixin DiagnosticableTreeMixin implements DiagnosticableTree {
|
3379 | @override
|
3380 | String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
3381 | return toDiagnosticsNode(style: DiagnosticsTreeStyle.singleLine).toString(minLevel: minLevel);
|
3382 | }
|
3383 |
|
3384 | @override
|
3385 | String toStringShallow({
|
3386 | String joiner = ', ' ,
|
3387 | DiagnosticLevel minLevel = DiagnosticLevel.debug,
|
3388 | }) {
|
3389 | String? shallowString;
|
3390 | assert(() {
|
3391 | final StringBuffer result = StringBuffer();
|
3392 | result.write(toStringShort());
|
3393 | result.write(joiner);
|
3394 | final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
3395 | debugFillProperties(builder);
|
3396 | result.write(
|
3397 | builder.properties.where((DiagnosticsNode n) => !n.isFiltered(minLevel))
|
3398 | .join(joiner),
|
3399 | );
|
3400 | shallowString = result.toString();
|
3401 | return true;
|
3402 | }());
|
3403 | return shallowString ?? toString();
|
3404 | }
|
3405 |
|
3406 | @override
|
3407 | String toStringDeep({
|
3408 | String prefixLineOne = '' ,
|
3409 | String? prefixOtherLines,
|
3410 | DiagnosticLevel minLevel = DiagnosticLevel.debug,
|
3411 | int wrapWidth = 65,
|
3412 | }) {
|
3413 | return toDiagnosticsNode().toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel, wrapWidth: wrapWidth);
|
3414 | }
|
3415 |
|
3416 | @override
|
3417 | String toStringShort() => describeIdentity(this);
|
3418 |
|
3419 | @override
|
3420 | DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
|
3421 | return DiagnosticableTreeNode(
|
3422 | name: name,
|
3423 | value: this,
|
3424 | style: style,
|
3425 | );
|
3426 | }
|
3427 |
|
3428 | @override
|
3429 | List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];
|
3430 |
|
3431 | @override
|
3432 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { }
|
3433 | }
|
3434 |
|
3435 |
|
3436 | /// [DiagnosticsNode] that exists mainly to provide a container for other
|
3437 | /// diagnostics that typically lacks a meaningful value of its own.
|
3438 | ///
|
3439 | /// This class is typically used for displaying complex nested error messages.
|
3440 | class DiagnosticsBlock extends DiagnosticsNode {
|
3441 | /// Creates a diagnostic with properties specified by [properties] and
|
3442 | /// children specified by [children].
|
3443 | DiagnosticsBlock({
|
3444 | super.name,
|
3445 | DiagnosticsTreeStyle super.style = DiagnosticsTreeStyle.whitespace,
|
3446 | bool showName = true,
|
3447 | super.showSeparator,
|
3448 | super.linePrefix,
|
3449 | this.value,
|
3450 | String? description,
|
3451 | this.level = DiagnosticLevel.info,
|
3452 | this.allowTruncate = false,
|
3453 | List<DiagnosticsNode> children = const<DiagnosticsNode>[],
|
3454 | List<DiagnosticsNode> properties = const <DiagnosticsNode>[],
|
3455 | }) : _description = description ?? '' ,
|
3456 | _children = children,
|
3457 | _properties = properties,
|
3458 | super(
|
3459 | showName: showName && name != null,
|
3460 | );
|
3461 |
|
3462 | final List<DiagnosticsNode> _children;
|
3463 | final List<DiagnosticsNode> _properties;
|
3464 |
|
3465 | @override
|
3466 | final DiagnosticLevel level;
|
3467 |
|
3468 | final String _description;
|
3469 |
|
3470 | @override
|
3471 | final Object? value;
|
3472 |
|
3473 | @override
|
3474 | final bool allowTruncate;
|
3475 |
|
3476 | @override
|
3477 | List<DiagnosticsNode> getChildren() => _children;
|
3478 |
|
3479 | @override
|
3480 | List<DiagnosticsNode> getProperties() => _properties;
|
3481 |
|
3482 | @override
|
3483 | String toDescription({TextTreeConfiguration? parentConfiguration}) => _description;
|
3484 | }
|
3485 |
|
3486 | /// A delegate that configures how a hierarchy of [DiagnosticsNode]s should be
|
3487 | /// serialized.
|
3488 | ///
|
3489 | /// Implement this class in a subclass to fully configure how [DiagnosticsNode]s
|
3490 | /// get serialized.
|
3491 | abstract class DiagnosticsSerializationDelegate {
|
3492 | /// Creates a simple [DiagnosticsSerializationDelegate] that controls the
|
3493 | /// [subtreeDepth] and whether to [includeProperties].
|
3494 | ///
|
3495 | /// For additional configuration options, extend
|
3496 | /// [DiagnosticsSerializationDelegate] and provide custom implementations
|
3497 | /// for the methods of this class.
|
3498 | const factory DiagnosticsSerializationDelegate({
|
3499 | int subtreeDepth,
|
3500 | bool includeProperties,
|
3501 | }) = _DefaultDiagnosticsSerializationDelegate;
|
3502 |
|
3503 | /// Returns a serializable map of additional information that will be included
|
3504 | /// in the serialization of the given [DiagnosticsNode].
|
3505 | ///
|
3506 | /// This method is called for every [DiagnosticsNode] that's included in
|
3507 | /// the serialization.
|
3508 | Map<String, Object?> additionalNodeProperties(DiagnosticsNode node);
|
3509 |
|
3510 | /// Filters the list of [DiagnosticsNode]s that will be included as children
|
3511 | /// for the given `owner` node.
|
3512 | ///
|
3513 | /// The callback may return a subset of the children in the provided list
|
3514 | /// or replace the entire list with new child nodes.
|
3515 | ///
|
3516 | /// See also:
|
3517 | ///
|
3518 | /// * [subtreeDepth], which controls how many levels of children will be
|
3519 | /// included in the serialization.
|
3520 | List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> nodes, DiagnosticsNode owner);
|
3521 |
|
3522 | /// Filters the list of [DiagnosticsNode]s that will be included as properties
|
3523 | /// for the given `owner` node.
|
3524 | ///
|
3525 | /// The callback may return a subset of the properties in the provided list
|
3526 | /// or replace the entire list with new property nodes.
|
3527 | ///
|
3528 | /// By default, `nodes` is returned as-is.
|
3529 | ///
|
3530 | /// See also:
|
3531 | ///
|
3532 | /// * [includeProperties], which controls whether properties will be included
|
3533 | /// at all.
|
3534 | List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> nodes, DiagnosticsNode owner);
|
3535 |
|
3536 | /// Truncates the given list of [DiagnosticsNode] that will be added to the
|
3537 | /// serialization as children or properties of the `owner` node.
|
3538 | ///
|
3539 | /// The method must return a subset of the provided nodes and may
|
3540 | /// not replace any nodes. While [filterProperties] and [filterChildren]
|
3541 | /// completely hide a node from the serialization, truncating a node will
|
3542 | /// leave a hint in the serialization that there were additional nodes in the
|
3543 | /// result that are not included in the current serialization.
|
3544 | ///
|
3545 | /// By default, `nodes` is returned as-is.
|
3546 | List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode? owner);
|
3547 |
|
3548 | /// Returns the [DiagnosticsSerializationDelegate] to be used
|
3549 | /// for adding the provided [DiagnosticsNode] to the serialization.
|
3550 | ///
|
3551 | /// By default, this will return a copy of this delegate, which has the
|
3552 | /// [subtreeDepth] reduced by one.
|
3553 | ///
|
3554 | /// This is called for nodes that will be added to the serialization as
|
3555 | /// property or child of another node. It may return the same delegate if no
|
3556 | /// changes to it are necessary.
|
3557 | DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node);
|
3558 |
|
3559 | /// Controls how many levels of children will be included in the serialized
|
3560 | /// hierarchy of [DiagnosticsNode]s.
|
3561 | ///
|
3562 | /// Defaults to zero.
|
3563 | ///
|
3564 | /// See also:
|
3565 | ///
|
3566 | /// * [filterChildren], which provides a way to filter the children that
|
3567 | /// will be included.
|
3568 | int get subtreeDepth;
|
3569 |
|
3570 | /// Whether to include the properties of a [DiagnosticsNode] in the
|
3571 | /// serialization.
|
3572 | ///
|
3573 | /// Defaults to false.
|
3574 | ///
|
3575 | /// See also:
|
3576 | ///
|
3577 | /// * [filterProperties], which provides a way to filter the properties that
|
3578 | /// will be included.
|
3579 | bool get includeProperties;
|
3580 |
|
3581 | /// Whether properties that have a [Diagnosticable] as value should be
|
3582 | /// expanded.
|
3583 | bool get expandPropertyValues;
|
3584 |
|
3585 | /// Creates a copy of this [DiagnosticsSerializationDelegate] with the
|
3586 | /// provided values.
|
3587 | DiagnosticsSerializationDelegate copyWith({
|
3588 | int subtreeDepth,
|
3589 | bool includeProperties,
|
3590 | });
|
3591 | }
|
3592 |
|
3593 | class _DefaultDiagnosticsSerializationDelegate implements DiagnosticsSerializationDelegate {
|
3594 | const _DefaultDiagnosticsSerializationDelegate({
|
3595 | this.includeProperties = false,
|
3596 | this.subtreeDepth = 0,
|
3597 | });
|
3598 |
|
3599 | @override
|
3600 | Map<String, Object?> additionalNodeProperties(DiagnosticsNode node) {
|
3601 | return const <String, Object?>{};
|
3602 | }
|
3603 |
|
3604 | @override
|
3605 | DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node) {
|
3606 | return subtreeDepth > 0 ? copyWith(subtreeDepth: subtreeDepth - 1) : this;
|
3607 | }
|
3608 |
|
3609 | @override
|
3610 | bool get expandPropertyValues => false;
|
3611 |
|
3612 | @override
|
3613 | List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
|
3614 | return nodes;
|
3615 | }
|
3616 |
|
3617 | @override
|
3618 | List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
|
3619 | return nodes;
|
3620 | }
|
3621 |
|
3622 | @override
|
3623 | final bool includeProperties;
|
3624 |
|
3625 | @override
|
3626 | final int subtreeDepth;
|
3627 |
|
3628 | @override
|
3629 | List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode? owner) {
|
3630 | return nodes;
|
3631 | }
|
3632 |
|
3633 | @override
|
3634 | DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties}) {
|
3635 | return _DefaultDiagnosticsSerializationDelegate(
|
3636 | subtreeDepth: subtreeDepth ?? this.subtreeDepth,
|
3637 | includeProperties: includeProperties ?? this.includeProperties,
|
3638 | );
|
3639 | }
|
3640 | }
|
3641 |
|