Better print-out of semantics tree (#15302)

diff --git a/packages/flutter/lib/src/foundation/diagnostics.dart b/packages/flutter/lib/src/foundation/diagnostics.dart
index f78abc2..fdf1eac 100644
--- a/packages/flutter/lib/src/foundation/diagnostics.dart
+++ b/packages/flutter/lib/src/foundation/diagnostics.dart
@@ -883,6 +883,8 @@
 
         builder.write(
             config.isNameOnOwnLine || description.contains('\n') ? '\n' : ' ');
+        if (description.contains('\n') && style == DiagnosticsTreeStyle.singleLine)
+          builder.prefixOtherLines += '  ';
       }
       builder.prefixOtherLines += children.isEmpty ?
           config.propertyPrefixNoChildren : config.propertyPrefixIfChildren;
diff --git a/packages/flutter/lib/src/painting/matrix_utils.dart b/packages/flutter/lib/src/painting/matrix_utils.dart
index c9623ea..2b87271 100644
--- a/packages/flutter/lib/src/painting/matrix_utils.dart
+++ b/packages/flutter/lib/src/painting/matrix_utils.dart
@@ -255,7 +255,7 @@
 List<String> debugDescribeTransform(Matrix4 transform) {
   if (transform == null)
     return const <String>['null'];
-  final List<String> matrix = transform.toString().split('\n').map((String s) => '  $s').toList();
+  final List<String> matrix = transform.toString().split('\n').toList();
   matrix.removeLast();
   return matrix;
 }
diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart
index 1f4adc7..2318fdd 100644
--- a/packages/flutter/lib/src/semantics/semantics.dart
+++ b/packages/flutter/lib/src/semantics/semantics.dart
@@ -1378,15 +1378,8 @@
     }
     final List<String> actions = _actions.keys.map((SemanticsAction action) => describeEnum(action)).toList()..sort();
     properties.add(new IterableProperty<String>('actions', actions, ifEmpty: null));
-    if (_hasFlag(SemanticsFlag.hasEnabledState))
-      properties.add(new FlagProperty('isEnabled', value: _hasFlag(SemanticsFlag.isEnabled), ifFalse: 'disabled'));
-    if (_hasFlag(SemanticsFlag.hasCheckedState))
-      properties.add(new FlagProperty('isChecked', value: _hasFlag(SemanticsFlag.isChecked), ifTrue: 'checked', ifFalse: 'unchecked'));
-    properties.add(new FlagProperty('isInMutuallyExcusiveGroup', value: _hasFlag(SemanticsFlag.isInMutuallyExclusiveGroup), ifTrue: 'mutually-exclusive'));
-    properties.add(new FlagProperty('isSelected', value: _hasFlag(SemanticsFlag.isSelected), ifTrue: 'selected'));
-    properties.add(new FlagProperty('isFocused', value: _hasFlag(SemanticsFlag.isFocused), ifTrue: 'focused'));
-    properties.add(new FlagProperty('isButton', value: _hasFlag(SemanticsFlag.isButton), ifTrue: 'button'));
-    properties.add(new FlagProperty('isTextField', value: _hasFlag(SemanticsFlag.isTextField), ifTrue: 'textField'));
+    final List<String> flags = SemanticsFlag.values.values.where((SemanticsFlag flag) => _hasFlag(flag)).map((SemanticsFlag flag) => flag.toString().substring('SemanticsFlag.'.length)).toList();
+    properties.add(new IterableProperty<String>('flags', flags, ifEmpty: null));
     properties.add(new FlagProperty('isInvisible', value: isInvisible, ifTrue: 'invisible'));
     properties.add(new StringProperty('label', _label, defaultValue: ''));
     properties.add(new StringProperty('value', _value, defaultValue: ''));
@@ -1422,7 +1415,7 @@
   @override
   DiagnosticsNode toDiagnosticsNode({
     String name,
-    DiagnosticsTreeStyle style: DiagnosticsTreeStyle.dense,
+    DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse,
     DebugSemanticsDumpOrder childOrder: DebugSemanticsDumpOrder.geometricOrder,
   }) {
     return new _SemanticsDiagnosticableNode(
diff --git a/packages/flutter/test/foundation/diagnostics_test.dart b/packages/flutter/test/foundation/diagnostics_test.dart
index 76adcc5..30d858d 100644
--- a/packages/flutter/test/foundation/diagnostics_test.dart
+++ b/packages/flutter/test/foundation/diagnostics_test.dart
@@ -404,9 +404,9 @@
       ' │\n'
       ' └─child node C: TestTree#00000\n'
       '     foo:\n'
-      '     multi\n'
-      '     line\n'
-      '     value!\n',
+      '       multi\n'
+      '       line\n'
+      '       value!\n',
     );
 
     goldenStyleTest(
@@ -448,9 +448,9 @@
       ' ╎\n'
       ' └╌child node C: TestTree#00000\n'
       '     foo:\n'
-      '     multi\n'
-      '     line\n'
-      '     value!\n',
+      '       multi\n'
+      '       line\n'
+      '       value!\n',
     );
 
     goldenStyleTest(
@@ -482,9 +482,9 @@
       ' ╘═╦══ child node C ═══\n'
       '   ║ TestTree#00000:\n'
       '   ║   foo:\n'
-      '   ║   multi\n'
-      '   ║   line\n'
-      '   ║   value!\n'
+      '   ║     multi\n'
+      '   ║     line\n'
+      '   ║     value!\n'
       '   ╚═══════════\n',
     );
 
@@ -523,9 +523,9 @@
       '  ╘═╦══ child node C ═══\n'
       '    ║ TestTree#00000:\n'
       '    ║   foo:\n'
-      '    ║   multi\n'
-      '    ║   line\n'
-      '    ║   value!\n'
+      '    ║     multi\n'
+      '    ║     line\n'
+      '    ║     value!\n'
       '    ╚═══════════\n',
     );
 
@@ -551,9 +551,9 @@
         '      foo: 42\n'
         '  child node C: TestTree#00000:\n'
         '    foo:\n'
-        '    multi\n'
-        '    line\n'
-        '    value!\n',
+        '      multi\n'
+        '      line\n'
+        '      value!\n',
     );
 
     // Single line mode does not display children.
@@ -657,9 +657,9 @@
         ' │ ╚═══════════\n'
         ' └─child node C: TestTree#00000\n'
         '     foo:\n'
-        '     multi\n'
-        '     line\n'
-        '     value!\n',
+        '       multi\n'
+        '       line\n'
+        '       value!\n',
       ),
     );
   });
diff --git a/packages/flutter/test/rendering/debug_test.dart b/packages/flutter/test/rendering/debug_test.dart
index 20e8d26..1b35885 100644
--- a/packages/flutter/test/rendering/debug_test.dart
+++ b/packages/flutter/test/rendering/debug_test.dart
@@ -15,10 +15,10 @@
     final Matrix4 identity = new Matrix4.identity();
     final List<String> description = debugDescribeTransform(identity);
     expect(description, equals(<String>[
-      '  [0] 1.0,0.0,0.0,0.0',
-      '  [1] 0.0,1.0,0.0,0.0',
-      '  [2] 0.0,0.0,1.0,0.0',
-      '  [3] 0.0,0.0,0.0,1.0',
+      '[0] 1.0,0.0,0.0,0.0',
+      '[1] 0.0,1.0,0.0,0.0',
+      '[2] 0.0,0.0,1.0,0.0',
+      '[3] 0.0,0.0,0.0,1.0',
     ]));
   });
 
diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart
index df8abd6..7d426af 100644
--- a/packages/flutter/test/semantics/semantics_test.dart
+++ b/packages/flutter/test/semantics/semantics_test.dart
@@ -126,9 +126,20 @@
 
     expect(
       root.toStringDeep(childOrder: DebugSemanticsDumpOrder.geometricOrder),
-      'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
-      '├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n'
-      '└SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n',
+      'SemanticsNode#3\n'
+      ' │ STALE\n'
+      ' │ owner: null\n'
+      ' │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n'
+      ' │\n'
+      ' ├─SemanticsNode#1\n'
+      ' │   STALE\n'
+      ' │   owner: null\n'
+      ' │   Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n'
+      ' │\n'
+      ' └─SemanticsNode#2\n'
+      '     STALE\n'
+      '     owner: null\n'
+      '     Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n'
     );
   });
 
@@ -287,16 +298,38 @@
     );
     expect(
       root.toStringDeep(childOrder: DebugSemanticsDumpOrder.geometricOrder),
-      'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
-      '├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
-      '└SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n',
+      'SemanticsNode#3\n'
+      ' │ STALE\n'
+      ' │ owner: null\n'
+      ' │ Rect.fromLTRB(0.0, 0.0, 20.0, 5.0)\n'
+      ' │\n'
+      ' ├─SemanticsNode#2\n'
+      ' │   STALE\n'
+      ' │   owner: null\n'
+      ' │   Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n'
+      ' │\n'
+      ' └─SemanticsNode#1\n'
+      '     STALE\n'
+      '     owner: null\n'
+      '     Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n'
     );
 
     expect(
       root.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest),
-      'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
-      '├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n'
-      '└SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n',
+      'SemanticsNode#3\n'
+      ' │ STALE\n'
+      ' │ owner: null\n'
+      ' │ Rect.fromLTRB(0.0, 0.0, 20.0, 5.0)\n'
+      ' │\n'
+      ' ├─SemanticsNode#1\n'
+      ' │   STALE\n'
+      ' │   owner: null\n'
+      ' │   Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n'
+      ' │\n'
+      ' └─SemanticsNode#2\n'
+      '     STALE\n'
+      '     owner: null\n'
+      '     Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n'
     );
 
     final SemanticsNode child3 = new SemanticsNode()
@@ -320,22 +353,68 @@
 
     expect(
       rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.geometricOrder),
-      'SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n'
-      '├SemanticsNode#4(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
-      '│├SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n'
-      '│└SemanticsNode#5(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n'
-      '├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
-      '└SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n',
+      'SemanticsNode#7\n'
+      ' │ STALE\n'
+      ' │ owner: null\n'
+      ' │ Rect.fromLTRB(0.0, 0.0, 25.0, 5.0)\n'
+      ' │\n'
+      ' ├─SemanticsNode#4\n'
+      ' │ │ STALE\n'
+      ' │ │ owner: null\n'
+      ' │ │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n'
+      ' │ │\n'
+      ' │ ├─SemanticsNode#6\n'
+      ' │ │   STALE\n'
+      ' │ │   owner: null\n'
+      ' │ │   Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n'
+      ' │ │\n'
+      ' │ └─SemanticsNode#5\n'
+      ' │     STALE\n'
+      ' │     owner: null\n'
+      ' │     Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n'
+      ' │\n'
+      ' ├─SemanticsNode#2\n'
+      ' │   STALE\n'
+      ' │   owner: null\n'
+      ' │   Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n'
+      ' │\n'
+      ' └─SemanticsNode#1\n'
+      '     STALE\n'
+      '     owner: null\n'
+      '     Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n'
     );
 
     expect(
       rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest),
-      'SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n'
-      '├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n'
-      '├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
-      '└SemanticsNode#4(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
-      ' ├SemanticsNode#5(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n'
-      ' └SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n',
+      'SemanticsNode#7\n'
+      ' │ STALE\n'
+      ' │ owner: null\n'
+      ' │ Rect.fromLTRB(0.0, 0.0, 25.0, 5.0)\n'
+      ' │\n'
+      ' ├─SemanticsNode#1\n'
+      ' │   STALE\n'
+      ' │   owner: null\n'
+      ' │   Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n'
+      ' │\n'
+      ' ├─SemanticsNode#2\n'
+      ' │   STALE\n'
+      ' │   owner: null\n'
+      ' │   Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n'
+      ' │\n'
+      ' └─SemanticsNode#4\n'
+      '   │ STALE\n'
+      '   │ owner: null\n'
+      '   │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n'
+      '   │\n'
+      '   ├─SemanticsNode#5\n'
+      '   │   STALE\n'
+      '   │   owner: null\n'
+      '   │   Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n'
+      '   │\n'
+      '   └─SemanticsNode#6\n'
+      '       STALE\n'
+      '       owner: null\n'
+      '       Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n'
     );
   });
 
@@ -343,12 +422,33 @@
     final SemanticsNode minimalProperties = new SemanticsNode();
     expect(
       minimalProperties.toStringDeep(),
-      'SemanticsNode#1(Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible)\n',
+      'SemanticsNode#1\n'
+      '   Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)\n'
+      '   invisible\n'
     );
 
     expect(
       minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
-      'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isInMutuallyExcusiveGroup: false, isSelected: false, isFocused: false, isButton: false, isTextField: false, invisible, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null, nextNodeId: null, previousNodeId: null, sortOrder: null, scrollExtentMin: null, scrollPosition: null, scrollExtentMax: null)\n'
+      'SemanticsNode#1\n'
+      '   owner: null\n'
+      '   isMergedIntoParent: false\n'
+      '   mergeAllDescendantsIntoThisNode: false\n'
+      '   Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)\n'
+      '   actions: []\n'
+      '   flags: []\n'
+      '   invisible\n'
+      '   label: ""\n'
+      '   value: ""\n'
+      '   increasedValue: ""\n'
+      '   decreasedValue: ""\n'
+      '   hint: ""\n'
+      '   textDirection: null\n'
+      '   nextNodeId: null\n'
+      '   previousNodeId: null\n'
+      '   sortOrder: null\n'
+      '   scrollExtentMin: null\n'
+      '   scrollPosition: null\n'
+      '   scrollExtentMax: null\n'
     );
 
     final SemanticsConfiguration config = new SemanticsConfiguration()
@@ -369,7 +469,19 @@
       ..updateWith(config: config, childrenInInversePaintOrder: null);
     expect(
       allProperties.toStringDeep(),
-      equalsIgnoringHashCodes('SemanticsNode#2(STALE, owner: null, merge boundary ⛔️, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), actions: [longPress, scrollUp, showOnScreen], unchecked, selected, button, label: "Use all the properties", textDirection: rtl, sortOrder: SemanticsSortOrder#8e690(keys: [OrdinalSortKey#ca2b8(order: 1.0)]))\n'),
+      equalsIgnoringHashCodes(
+          'SemanticsNode#2\n'
+          '   STALE\n'
+          '   owner: null\n'
+          '   merge boundary ⛔️\n'
+          '   Rect.fromLTRB(60.0, 20.0, 80.0, 50.0)\n'
+          '   actions: longPress, scrollUp, showOnScreen\n'
+          '   flags: hasCheckedState, isSelected, isButton\n'
+          '   label: "Use all the properties"\n'
+          '   textDirection: rtl\n'
+          '   sortOrder: SemanticsSortOrder#b555b(keys:\n'
+          '     [OrdinalSortKey#19df5(order: 1.0)])\n'
+      ),
     );
     expect(
       allProperties.getSemanticsData().toString(),
@@ -381,7 +493,10 @@
       ..transform = new Matrix4.diagonal3(new Vector3(10.0, 10.0, 1.0));
     expect(
       scaled.toStringDeep(),
-      'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x)\n',
+      'SemanticsNode#3\n'
+      '   STALE\n'
+      '   owner: null\n'
+      '   Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x\n',
     );
     expect(
       scaled.getSemanticsData().toString(),