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