Reland: Reword flutter run help screen. (#48423)

diff --git a/AUTHORS b/AUTHORS
index e0972f1..bfc7c6f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -49,3 +49,4 @@
 Jefferson Quesado <jeff.quesado@gmail.com>
 Mark Diener <rpzrpzrpz@gmail.com>
 Alek Åström <alek.astrom@gmail.com>
+Efthymios Sarpmpanis <e.sarbanis@gmail.com>
diff --git a/dev/devicelab/bin/tasks/flutter_attach_test.dart b/dev/devicelab/bin/tasks/flutter_attach_test.dart
index 11584ff..0f80aca 100644
--- a/dev/devicelab/bin/tasks/flutter_attach_test.dart
+++ b/dev/devicelab/bin/tasks/flutter_attach_test.dart
@@ -33,7 +33,7 @@
     stdout.add(line);
     if (line.contains('Waiting') && onListening != null)
       listening.complete(onListening());
-    if (line.contains('To detach, press "d"; to quit, press "q".'))
+    if (line.contains('Quit (terminate the application on the device)'))
       ready.complete();
     if (line.contains('Reloaded '))
       reloaded.complete();
diff --git a/dev/devicelab/bin/tasks/run_release_test.dart b/dev/devicelab/bin/tasks/run_release_test.dart
index 6049975..93dde4d 100644
--- a/dev/devicelab/bin/tasks/run_release_test.dart
+++ b/dev/devicelab/bin/tasks/run_release_test.dart
@@ -46,7 +46,7 @@
           ) {
             stdout.add(line);
           }
-          if (line.contains('To quit, press "q".')) {
+          if (line.contains('Quit (terminate the application on the device).')) {
             ready.complete();
           }
         });
@@ -96,8 +96,8 @@
 
       _findNextMatcherInList(
         stdout,
-        (String line) => line == 'To quit, press "q".',
-        'To quit, press "q".',
+        (String line) => line.contains('Quit (terminate the application on the device).'),
+        'q Quit (terminate the application on the device)',
       );
 
       _findNextMatcherInList(
diff --git a/packages/flutter_tools/lib/src/base/command_help.dart b/packages/flutter_tools/lib/src/base/command_help.dart
new file mode 100644
index 0000000..c8797f4
--- /dev/null
+++ b/packages/flutter_tools/lib/src/base/command_help.dart
@@ -0,0 +1,76 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:math' as math;
+
+import '../globals.dart' as globals;
+import 'terminal.dart';
+
+const String fire = '🔥';
+const int maxLineWidth = 84;
+
+/// Encapsulates the help text construction and printing
+class CommandHelp {
+
+  const CommandHelp._(this.key, this.description, [this.inParenthesis = '']);
+
+  static const CommandHelp L = CommandHelp._('L', 'Dump layer tree to the console.', 'debugDumpLayerTree');
+  static const CommandHelp P = CommandHelp._('P', 'Toggle performance overlay.', 'WidgetsApp.showPerformanceOverlay');
+  static const CommandHelp R = CommandHelp._('R', 'Hot restart.');
+  static const CommandHelp S = CommandHelp._('S', 'Dump accessibility tree in traversal order.', 'debugDumpSemantics');
+  static const CommandHelp U = CommandHelp._('U', 'Dump accessibility tree in inverse hit test order.', 'debugDumpSemantics');
+  static const CommandHelp a = CommandHelp._('a', 'Toggle timeline events for all widget build methods.', 'debugProfileWidgetBuilds');
+  static const CommandHelp d = CommandHelp._('d', 'Detach (terminate "flutter run" but leave application running).');
+  static const CommandHelp h = CommandHelp._('h', 'Repeat this help message.');
+  static const CommandHelp i = CommandHelp._('i', 'Toggle widget inspector.', 'WidgetsApp.showWidgetInspectorOverride');
+  static const CommandHelp o = CommandHelp._('o', 'Simulate different operating systems.', 'defaultTargetPlatform');
+  static const CommandHelp p = CommandHelp._('p', 'Toggle the display of construction lines.', 'debugPaintSizeEnabled');
+  static const CommandHelp q = CommandHelp._('q', 'Quit (terminate the application on the device).');
+  static const CommandHelp r = CommandHelp._('r', 'Hot reload. $fire$fire$fire');
+  static const CommandHelp s = CommandHelp._('s', 'Save a screenshot to flutter.png.');
+  static const CommandHelp t = CommandHelp._('t', 'Dump rendering tree to the console.', 'debugDumpRenderTree');
+  static const CommandHelp w = CommandHelp._('w', 'Dump widget hierarchy to the console.', 'debugDumpApp');
+  static const CommandHelp z = CommandHelp._('z', 'Toggle elevation checker.');
+
+  /// The key associated with this command
+  final String key;
+  /// A description of what this command does
+  final String description;
+  /// Text shown in parenthesis to give the context
+  final String inParenthesis;
+
+  bool get _hasTextInParenthesis => inParenthesis != null && inParenthesis.isNotEmpty;
+
+  int get _rawMessageLength => key.length + description.length;
+
+  @override
+  String toString() {
+    final StringBuffer message = StringBuffer();
+    message.writeAll(<String>[globals.terminal.bolden(key), description], ' ');
+
+    if (_hasTextInParenthesis) {
+      bool wrap = false;
+      final int maxWidth = math.max(outputPreferences.wrapColumn ?? 0,  maxLineWidth);
+      int width = maxWidth - (globals.platform.stdoutSupportsAnsi ? _rawMessageLength + 1 : message.length);
+      final String parentheticalText = '($inParenthesis)';
+      if (width < parentheticalText.length) {
+        width = maxWidth;
+        wrap = true;
+      }
+
+      if (wrap) {
+        message.write('\n');
+      }
+      // pad according to the raw text
+      message.write(''.padLeft(width - parentheticalText.length));
+
+      message.write(globals.terminal.color(parentheticalText, TerminalColor.grey));
+    }
+    return message.toString();
+  }
+
+  void print() {
+    globals.printStatus(toString());
+  }
+}
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 9e46694..1ce14f4 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -9,6 +9,7 @@
 import 'application_package.dart';
 import 'artifacts.dart';
 import 'asset.dart';
+import 'base/command_help.dart';
 import 'base/common.dart';
 import 'base/file_system.dart';
 import 'base/io.dart' as io;
@@ -1003,23 +1004,30 @@
   void printHelp({ @required bool details });
 
   void printHelpDetails() {
+    if (flutterDevices.any((FlutterDevice d) => d.device.supportsScreenshot)) {
+      CommandHelp.s.print();
+    }
     if (supportsServiceProtocol) {
-      globals.printStatus('You can dump the widget hierarchy of the app (debugDumpApp) by pressing "w".');
-      globals.printStatus('To dump the rendering tree of the app (debugDumpRenderTree), press "t".');
+      CommandHelp.w.print();
+      CommandHelp.t.print();
       if (isRunningDebug) {
-        globals.printStatus('For layers (debugDumpLayerTree), use "L"; for accessibility (debugDumpSemantics), use "S" (for traversal order) or "U" (for inverse hit test order).');
-        globals.printStatus('To toggle the widget inspector (WidgetsApp.showWidgetInspectorOverride), press "i".');
-        globals.printStatus('To toggle the display of construction lines (debugPaintSizeEnabled), press "p".');
-        globals.printStatus('To simulate different operating systems, (defaultTargetPlatform), press "o".');
-        globals.printStatus('To toggle the elevation checker, press "z".');
+        CommandHelp.L.print();
+        CommandHelp.S.print();
+        CommandHelp.U.print();
+        CommandHelp.i.print();
+        CommandHelp.p.print();
+        CommandHelp.o.print();
+        CommandHelp.z.print();
       } else {
-        globals.printStatus('To dump the accessibility tree (debugDumpSemantics), press "S" (for traversal order) or "U" (for inverse hit test order).');
+        CommandHelp.S.print();
+        CommandHelp.U.print();
       }
-      globals.printStatus('To display the performance overlay (WidgetsApp.showPerformanceOverlay), press "P".');
-      globals.printStatus('To enable timeline events for all widget build methods, (debugProfileWidgetBuilds), press "a"');
+      // `P` should precede `a`
+      CommandHelp.P.print();
+      CommandHelp.a.print();
     }
     if (flutterDevices.any((FlutterDevice d) => d.device.supportsScreenshot)) {
-      globals.printStatus('To save a screenshot to flutter.png, press "s".');
+      CommandHelp.s.print();
     }
   }
 
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 1211452..26405b3 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -6,6 +6,7 @@
 
 import 'package:meta/meta.dart';
 
+import 'base/command_help.dart';
 import 'base/file_system.dart';
 import 'device.dart';
 import 'globals.dart' as globals;
@@ -179,32 +180,27 @@
 
   @override
   void printHelp({ @required bool details }) {
-    bool haveDetails = false;
-    bool haveAnything = false;
+    globals.printStatus('Flutter run key commands.');
+    if (supportsServiceProtocol) {
+      if (details) {
+        printHelpDetails();
+      }
+    }
+    CommandHelp.h.print();
+    if (_didAttach) {
+      CommandHelp.d.print();
+    }
+    CommandHelp.q.print();
     for (final FlutterDevice device in flutterDevices) {
       final String dname = device.device.name;
       if (device.vmService != null) {
-        globals.printStatus('An Observatory debugger and profiler on $dname is '
-          'available at: ${device.vmService .httpAddress}');
+        // Caution: This log line is parsed by device lab tests.
+        globals.printStatus(
+          'An Observatory debugger and profiler on $dname is available at: '
+          '${device.vmService.httpAddress}',
+        );
       }
     }
-    if (supportsServiceProtocol) {
-      haveDetails = true;
-      if (details) {
-        printHelpDetails();
-        haveAnything = true;
-      }
-    }
-    final String quitMessage = _didAttach
-      ? 'To detach, press "d"; to quit, press "q".'
-      : 'To quit, press "q".';
-    if (haveDetails && !details) {
-      globals.printStatus('For a more detailed help message, press "h". $quitMessage');
-    } else if (haveAnything) {
-      globals.printStatus('To repeat this help message, press "h". $quitMessage');
-    } else {
-      globals.printStatus(quitMessage);
-    }
   }
 
   @override
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 2eadea5..a5dd209 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -9,12 +9,12 @@
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:meta/meta.dart';
 import 'package:pool/pool.dart';
-
 import 'base/async_guard.dart';
+
+import 'base/command_help.dart';
 import 'base/context.dart';
 import 'base/file_system.dart';
 import 'base/logger.dart';
-import 'base/terminal.dart';
 import 'base/utils.dart';
 import 'build_info.dart';
 import 'compile.dart';
@@ -1045,29 +1045,26 @@
 
   @override
   void printHelp({ @required bool details }) {
-    const String fire = '🔥';
-    String rawMessage = '  To hot reload changes while running, press "r". ';
+    globals.printStatus('Flutter run key commands.');
+    CommandHelp.r.print();
     if (canHotRestart) {
-      rawMessage += 'To hot restart (and rebuild state), press "R".';
+      CommandHelp.R.print();
     }
-    final String message = globals.terminal.color(
-      fire + globals.terminal.bolden(rawMessage),
-      TerminalColor.red,
-    );
-    globals.printStatus(message);
-    for (final FlutterDevice device in flutterDevices) {
-      final String dname = device.device.name;
-      globals.printStatus('An Observatory debugger and profiler on $dname is '
-        'available at: ${device.vmService.httpAddress}');
+    CommandHelp.h.print();
+    if (_didAttach) {
+      CommandHelp.d.print();
     }
-    final String quitMessage = _didAttach
-        ? 'To detach, press "d"; to quit, press "q".'
-        : 'To quit, press "q".';
+    CommandHelp.q.print();
     if (details) {
       printHelpDetails();
-      globals.printStatus('To repeat this help message, press "h". $quitMessage');
-    } else {
-      globals.printStatus('For a more detailed help message, press "h". $quitMessage');
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      final String dname = device.device.name;
+      // Caution: This log line is parsed by device lab tests.
+      globals.printStatus(
+        'An Observatory debugger and profiler on $dname is available at: '
+        '${device.vmService.httpAddress}',
+      );
     }
   }
 
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
index 3ce4cd6..8e589ab 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -165,7 +165,10 @@
               if (message == '[stdout] Lost connection to device.') {
                 observatoryLogs.add(message);
               }
-              if (message.contains('To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".')) {
+              if (message.contains('Hot reload.')) {
+                observatoryLogs.add(message);
+              }
+              if (message.contains('Hot restart.')) {
                 observatoryLogs.add(message);
               }
             });
@@ -203,14 +206,16 @@
           }));
         });
 
-        expect(observatoryLogs.length, 7);
+        expect(observatoryLogs.length, 9);
         expect(observatoryLogs[0], '[stdout] Waiting for a connection from Flutter on MockAndroidDevice...');
         expect(observatoryLogs[1], '[verbose] Observatory URL on device: http://127.0.0.1:1234');
         expect(observatoryLogs[2], '[stdout] Lost connection to device.');
-        expect(observatoryLogs[3].contains('To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R"'), isTrue);
-        expect(observatoryLogs[4], '[verbose] Observatory URL on device: http://127.0.0.1:1235');
-        expect(observatoryLogs[5], '[stdout] Lost connection to device.');
-        expect(observatoryLogs[6].contains('To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R"'), isTrue);
+        expect(observatoryLogs[3].contains('Hot reload.'), isTrue);
+        expect(observatoryLogs[4].contains('Hot restart.'), isTrue);
+        expect(observatoryLogs[5], '[verbose] Observatory URL on device: http://127.0.0.1:1235');
+        expect(observatoryLogs[6], '[stdout] Lost connection to device.');
+        expect(observatoryLogs[7].contains('Hot reload.'), isTrue);
+        expect(observatoryLogs[8].contains('Hot restart.'), isTrue);
 
         verify(portForwarder.forward(1234, hostPort: anyNamed('hostPort'))).called(1);
         verify(portForwarder.forward(1235, hostPort: anyNamed('hostPort'))).called(1);
diff --git a/packages/flutter_tools/test/general.shard/base/command_help_test.dart b/packages/flutter_tools/test/general.shard/base/command_help_test.dart
new file mode 100644
index 0000000..5fdbc84
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/base/command_help_test.dart
@@ -0,0 +1,176 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_tools/src/base/command_help.dart';
+import 'package:flutter_tools/src/base/terminal.dart' show OutputPreferences, outputPreferences;
+import 'package:flutter_tools/src/globals.dart' as globals;
+import 'package:mockito/mockito.dart';
+import 'package:platform/platform.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+
+// Used to use the message length in different scenarios in a DRY way
+Future<void> Function() _testMessageLength(bool stdoutSupportsAnsi, int maxTestLineLength) => () async {
+  when(globals.platform.stdoutSupportsAnsi).thenReturn(stdoutSupportsAnsi);
+
+  int expectedWidth = maxTestLineLength;
+
+  if (stdoutSupportsAnsi) {
+    const int ansiMetaCharactersLength = 33;
+    expectedWidth += ansiMetaCharactersLength;
+  }
+
+  expect(CommandHelp.L.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.P.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.R.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.S.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.U.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.a.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.d.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.h.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.i.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.o.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.p.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.q.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.r.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.s.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.t.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.w.toString().length, lessThanOrEqualTo(expectedWidth));
+  expect(CommandHelp.z.toString().length, lessThanOrEqualTo(expectedWidth));
+};
+
+void main() {
+  group('CommandHelp', () {
+    group('toString', () {
+
+      testUsingContext('should have a bold command key', () async {
+        when(globals.platform.stdoutSupportsAnsi).thenReturn(true);
+
+        expect(CommandHelp.L.toString(), startsWith('\x1B[1mL\x1B[22m'));
+        expect(CommandHelp.P.toString(), startsWith('\x1B[1mP\x1B[22m'));
+        expect(CommandHelp.R.toString(), startsWith('\x1B[1mR\x1B[22m'));
+        expect(CommandHelp.S.toString(), startsWith('\x1B[1mS\x1B[22m'));
+        expect(CommandHelp.U.toString(), startsWith('\x1B[1mU\x1B[22m'));
+        expect(CommandHelp.a.toString(), startsWith('\x1B[1ma\x1B[22m'));
+        expect(CommandHelp.d.toString(), startsWith('\x1B[1md\x1B[22m'));
+        expect(CommandHelp.h.toString(), startsWith('\x1B[1mh\x1B[22m'));
+        expect(CommandHelp.i.toString(), startsWith('\x1B[1mi\x1B[22m'));
+        expect(CommandHelp.o.toString(), startsWith('\x1B[1mo\x1B[22m'));
+        expect(CommandHelp.p.toString(), startsWith('\x1B[1mp\x1B[22m'));
+        expect(CommandHelp.q.toString(), startsWith('\x1B[1mq\x1B[22m'));
+        expect(CommandHelp.r.toString(), startsWith('\x1B[1mr\x1B[22m'));
+        expect(CommandHelp.s.toString(), startsWith('\x1B[1ms\x1B[22m'));
+        expect(CommandHelp.t.toString(), startsWith('\x1B[1mt\x1B[22m'));
+        expect(CommandHelp.w.toString(), startsWith('\x1B[1mw\x1B[22m'));
+        expect(CommandHelp.z.toString(), startsWith('\x1B[1mz\x1B[22m'));
+      }, overrides: <Type, Generator>{
+        OutputPreferences: () => OutputPreferences(wrapColumn: maxLineWidth),
+        Platform: () => MockPlatform(),
+      });
+
+      testUsingContext('commands L,P,S,U,a,i,o,p,t,w should have a grey bolden parenthetical text', () async {
+        when(globals.platform.stdoutSupportsAnsi).thenReturn(true);
+
+        expect(CommandHelp.L.toString(), endsWith('\x1B[1;30m(debugDumpLayerTree)\x1B[39m'));
+        expect(CommandHelp.P.toString(), endsWith('\x1B[1;30m(WidgetsApp.showPerformanceOverlay)\x1B[39m'));
+        expect(CommandHelp.S.toString(), endsWith('\x1B[1;30m(debugDumpSemantics)\x1B[39m'));
+        expect(CommandHelp.U.toString(), endsWith('\x1B[1;30m(debugDumpSemantics)\x1B[39m'));
+        expect(CommandHelp.a.toString(), endsWith('\x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m'));
+        expect(CommandHelp.i.toString(), endsWith('\x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m'));
+        expect(CommandHelp.o.toString(), endsWith('\x1B[1;30m(defaultTargetPlatform)\x1B[39m'));
+        expect(CommandHelp.p.toString(), endsWith('\x1B[1;30m(debugPaintSizeEnabled)\x1B[39m'));
+        expect(CommandHelp.t.toString(), endsWith('\x1B[1;30m(debugDumpRenderTree)\x1B[39m'));
+        expect(CommandHelp.w.toString(), endsWith('\x1B[1;30m(debugDumpApp)\x1B[39m'));
+      }, overrides: <Type, Generator>{
+        OutputPreferences: () => OutputPreferences(wrapColumn: maxLineWidth),
+        Platform: () => MockPlatform(),
+      });
+
+      testUsingContext('should not create a help text longer than maxLineWidth without ansi support',
+        _testMessageLength(false, maxLineWidth),
+        overrides: <Type, Generator>{
+          OutputPreferences: () => OutputPreferences(wrapColumn: 0),
+          Platform: () => MockPlatform(),
+      });
+
+      testUsingContext('should not create a help text longer than maxLineWidth with ansi support',
+        _testMessageLength(true, maxLineWidth),
+        overrides: <Type, Generator>{
+          OutputPreferences: () => OutputPreferences(wrapColumn: 0),
+          Platform: () => MockPlatform(),
+      });
+
+      testUsingContext('should not create a help text longer than outputPreferences.wrapColumn without ansi support',
+        _testMessageLength(false, outputPreferences.wrapColumn),
+          overrides: <Type, Generator>{
+          Platform: () => MockPlatform(),
+      });
+
+      testUsingContext('should not create a help text longer than outputPreferences.wrapColumn with ansi support',
+        _testMessageLength(true, outputPreferences.wrapColumn),
+        overrides: <Type, Generator>{
+          Platform: () => MockPlatform(),
+        });
+
+      testUsingContext('should create the correct help text with ansi support', () async {
+        when(globals.platform.stdoutSupportsAnsi).thenReturn(true);
+
+        expect(CommandHelp.L.toString(), equals('\x1B[1mL\x1B[22m Dump layer tree to the console.                               \x1B[1;30m(debugDumpLayerTree)\x1B[39m'));
+        expect(CommandHelp.P.toString(), equals('\x1B[1mP\x1B[22m Toggle performance overlay.                    \x1B[1;30m(WidgetsApp.showPerformanceOverlay)\x1B[39m'));
+        expect(CommandHelp.R.toString(), equals('\x1B[1mR\x1B[22m Hot restart.'));
+        expect(CommandHelp.S.toString(), equals('\x1B[1mS\x1B[22m Dump accessibility tree in traversal order.                   \x1B[1;30m(debugDumpSemantics)\x1B[39m'));
+        expect(CommandHelp.U.toString(), equals('\x1B[1mU\x1B[22m Dump accessibility tree in inverse hit test order.            \x1B[1;30m(debugDumpSemantics)\x1B[39m'));
+        expect(CommandHelp.a.toString(), equals('\x1B[1ma\x1B[22m Toggle timeline events for all widget build methods.    \x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m'));
+        expect(CommandHelp.d.toString(), equals('\x1B[1md\x1B[22m Detach (terminate "flutter run" but leave application running).'));
+        expect(CommandHelp.h.toString(), equals('\x1B[1mh\x1B[22m Repeat this help message.'));
+        expect(CommandHelp.i.toString(), equals('\x1B[1mi\x1B[22m Toggle widget inspector.                  \x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m'));
+        expect(CommandHelp.o.toString(), equals('\x1B[1mo\x1B[22m Simulate different operating systems.                      \x1B[1;30m(defaultTargetPlatform)\x1B[39m'));
+        expect(CommandHelp.p.toString(), equals('\x1B[1mp\x1B[22m Toggle the display of construction lines.                  \x1B[1;30m(debugPaintSizeEnabled)\x1B[39m'));
+        expect(CommandHelp.q.toString(), equals('\x1B[1mq\x1B[22m Quit (terminate the application on the device).'));
+        expect(CommandHelp.r.toString(), equals('\x1B[1mr\x1B[22m Hot reload. $fire$fire$fire'));
+        expect(CommandHelp.s.toString(), equals('\x1B[1ms\x1B[22m Save a screenshot to flutter.png.'));
+        expect(CommandHelp.t.toString(), equals('\x1B[1mt\x1B[22m Dump rendering tree to the console.                          \x1B[1;30m(debugDumpRenderTree)\x1B[39m'));
+        expect(CommandHelp.w.toString(), equals('\x1B[1mw\x1B[22m Dump widget hierarchy to the console.                               \x1B[1;30m(debugDumpApp)\x1B[39m'));
+        expect(CommandHelp.z.toString(), equals('\x1B[1mz\x1B[22m Toggle elevation checker.'));
+      }, overrides: <Type, Generator>{
+        OutputPreferences: () => OutputPreferences(wrapColumn: maxLineWidth),
+        Platform: () => MockPlatform(),
+      });
+
+      testUsingContext('should create the correct help text without ansi support', () async {
+        when(globals.platform.stdoutSupportsAnsi).thenReturn(false);
+
+        expect(CommandHelp.L.toString(), equals('L Dump layer tree to the console.                               (debugDumpLayerTree)'));
+        expect(CommandHelp.P.toString(), equals('P Toggle performance overlay.                    (WidgetsApp.showPerformanceOverlay)'));
+        expect(CommandHelp.R.toString(), equals('R Hot restart.'));
+        expect(CommandHelp.S.toString(), equals('S Dump accessibility tree in traversal order.                   (debugDumpSemantics)'));
+        expect(CommandHelp.U.toString(), equals('U Dump accessibility tree in inverse hit test order.            (debugDumpSemantics)'));
+        expect(CommandHelp.a.toString(), equals('a Toggle timeline events for all widget build methods.    (debugProfileWidgetBuilds)'));
+        expect(CommandHelp.d.toString(), equals('d Detach (terminate "flutter run" but leave application running).'));
+        expect(CommandHelp.h.toString(), equals('h Repeat this help message.'));
+        expect(CommandHelp.i.toString(), equals('i Toggle widget inspector.                  (WidgetsApp.showWidgetInspectorOverride)'));
+        expect(CommandHelp.o.toString(), equals('o Simulate different operating systems.                      (defaultTargetPlatform)'));
+        expect(CommandHelp.p.toString(), equals('p Toggle the display of construction lines.                  (debugPaintSizeEnabled)'));
+        expect(CommandHelp.q.toString(), equals('q Quit (terminate the application on the device).'));
+        expect(CommandHelp.r.toString(), equals('r Hot reload. $fire$fire$fire'));
+        expect(CommandHelp.s.toString(), equals('s Save a screenshot to flutter.png.'));
+        expect(CommandHelp.t.toString(), equals('t Dump rendering tree to the console.                          (debugDumpRenderTree)'));
+        expect(CommandHelp.w.toString(), equals('w Dump widget hierarchy to the console.                               (debugDumpApp)'));
+        expect(CommandHelp.z.toString(), equals('z Toggle elevation checker.'));
+      }, overrides: <Type, Generator>{
+        OutputPreferences: () => OutputPreferences(wrapColumn: maxLineWidth),
+        Platform: () => MockPlatform(),
+      });
+
+    });
+  });
+}
+
+class MockPlatform extends Mock implements Platform {
+  @override
+  Map<String, String> environment = <String, String>{
+    'FLUTTER_ROOT': '/',
+  };
+}
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
index 19c4593..1e33692 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -6,6 +6,7 @@
 
 import 'package:file/memory.dart';
 import 'package:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/base/command_help.dart';
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
@@ -365,21 +366,33 @@
 
     // supports service protocol
     expect(residentRunner.supportsServiceProtocol, true);
-    expect(testLogger.statusText, contains('"w"'));
-    expect(testLogger.statusText, contains('"t"'));
-    expect(testLogger.statusText, contains('"P"'));
-    expect(testLogger.statusText, contains('"a"'));
     // isRunningDebug
     expect(residentRunner.isRunningDebug, true);
-    expect(testLogger.statusText, contains('"L"'));
-    expect(testLogger.statusText, contains('"S"'));
-    expect(testLogger.statusText, contains('"U"'));
-    expect(testLogger.statusText, contains('"i"'));
-    expect(testLogger.statusText, contains('"p"'));
-    expect(testLogger.statusText, contains('"o"'));
-    expect(testLogger.statusText, contains('"z"'));
-    // screenshot
-    expect(testLogger.statusText, contains('"s"'));
+    // commands
+    expect(testLogger.statusText, equals(
+        <dynamic>[
+          'Flutter run key commands.',
+          CommandHelp.r,
+          CommandHelp.R,
+          CommandHelp.h,
+          CommandHelp.q,
+          CommandHelp.s,
+          CommandHelp.w,
+          CommandHelp.t,
+          CommandHelp.L,
+          CommandHelp.S,
+          CommandHelp.U,
+          CommandHelp.i,
+          CommandHelp.p,
+          CommandHelp.o,
+          CommandHelp.z,
+          CommandHelp.P,
+          CommandHelp.a,
+          CommandHelp.s,
+          'An Observatory debugger and profiler on null is available at: null',
+          ''
+        ].join('\n')
+    ));
   }));
 
   test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async {