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