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';
9library;
10
11import 'dart:collection';
12import 'dart:math' as math;
13import 'dart:ui' show clampDouble;
14
15import 'package:meta/meta.dart';
16
17import 'assertions.dart';
18import 'constants.dart';
19import 'debug.dart';
20import '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 boxShadow;
45// late Size size;
46// late bool hasSize;
47// late Matrix4 transform;
48// late Color color;
49// late Map? handles;
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.
63enum 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.
135enum 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.
234class 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.
423final 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.
476final 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.
499final 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.
537final 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.
606final 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.
646final 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.
679final 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.
702final 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.
731final 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]
759final 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
775enum _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].
784class _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
1067class _NoDefaultValue {
1068 const _NoDefaultValue();
1069}
1070
1071/// Marker object indicating that a [DiagnosticsNode] has no default value.
1072const Object kNoDefaultValue = _NoDefaultValue();
1073
1074bool _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.
1085class 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 $lines lines)',
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].
1446typedef _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.
1453typedef _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.
1465abstract 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.
1923class 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.
1942class 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
1989abstract 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.
2048class 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'.
2084class 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.
2103class 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.
2177class 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.
2255class 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].
2343class 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.
2377class 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.
2479class 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.
2555typedef 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].
2564class 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].
2904class 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].
2963class 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.
2973String 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.
2986String 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)
3020String 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.
3035class 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? boxShadow; late DiagnosticsTreeStyle style; late bool hasSize; late Matrix4 transform; Map? handles; late Color color; late bool obscureText; late ImageRepeat repeat; late Size size; late Widget widget; late bool isCurrent; late bool keepAlive; late TextAlign textAlign; }
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.
3088mixin 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. Specifying the value type ensures
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.
3350abstract 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].
3456mixin 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.
3514class 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.
3563abstract 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
3660class _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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com