Add option to clear the terminal by pressing "c" (#50034)

diff --git a/packages/flutter_tools/lib/src/base/command_help.dart b/packages/flutter_tools/lib/src/base/command_help.dart
index 5194a25..0b52404 100644
--- a/packages/flutter_tools/lib/src/base/command_help.dart
+++ b/packages/flutter_tools/lib/src/base/command_help.dart
@@ -53,6 +53,9 @@
   CommandHelpOption _a;
   CommandHelpOption get a => _a ??= _makeOption('a', 'Toggle timeline events for all widget build methods.', 'debugProfileWidgetBuilds');
 
+  CommandHelpOption _c;
+  CommandHelpOption get c => _c ??= _makeOption('c', 'Clear the screen');
+
   CommandHelpOption _d;
   CommandHelpOption get d => _d ??= _makeOption('d', 'Detach (terminate "flutter run" but leave application running).');
 
diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart
index 11060db..fbe9d76 100644
--- a/packages/flutter_tools/lib/src/base/logger.dart
+++ b/packages/flutter_tools/lib/src/base/logger.dart
@@ -168,6 +168,9 @@
   /// Only surfaces a value in machine modes, Loggers may ignore this message in
   /// non-machine modes.
   void sendEvent(String name, [Map<String, dynamic> args]) { }
+
+  /// Clears all output.
+  void clear();
 }
 
 class StdoutLogger extends Logger {
@@ -326,6 +329,13 @@
 
   @override
   void sendEvent(String name, [Map<String, dynamic> args]) { }
+
+  @override
+  void clear() {
+    _status?.pause();
+    writeToStdOut(_terminal.clearScreen() + '\n');
+    _status?.resume();
+  }
 }
 
 /// A [StdoutLogger] which replaces Unicode characters that cannot be printed to
@@ -470,7 +480,7 @@
     )..start();
   }
 
-  /// Clears all buffers.
+  @override
   void clear() {
     _error.clear();
     _status.clear();
@@ -627,6 +637,9 @@
 
   @override
   bool get hasTerminal => parent.hasTerminal;
+
+  @override
+  void clear() => parent.clear();
 }
 
 enum _LogType { error, status, trace }
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index a9e43eb..9427164 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -1000,6 +1000,10 @@
 
   @override
   bool get hasTerminal => false;
+
+  // This method is only relevant for terminals.
+  @override
+  void clear() { }
 }
 
 /// A running application, started by this daemon.
@@ -1230,6 +1234,10 @@
 
   @override
   bool get hasTerminal => false;
+
+  // This method is only relevant for terminals.
+  @override
+  void clear() { }
 }
 
 class LogMessage {
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index c660ece..97d36d8 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -1052,9 +1052,6 @@
       commandHelp.P.print();
       commandHelp.a.print();
     }
-    if (flutterDevices.any((FlutterDevice d) => d.device.supportsScreenshot)) {
-      commandHelp.s.print();
-    }
   }
 
   /// Called when a signal has requested we exit.
@@ -1062,6 +1059,9 @@
 
   /// Called right before we exit.
   Future<void> cleanupAtFinish();
+
+  // Clears the screen.
+  void clearScreen() => globals.logger.clear();
 }
 
 class OperationResult {
@@ -1167,6 +1167,9 @@
           return true;
         }
         return false;
+      case 'c':
+        residentRunner.clearScreen();
+        return true;
       case 'd':
       case 'D':
         await residentRunner.detach();
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index b4a74ce..6dbc7d6 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -189,6 +189,7 @@
     if (_didAttach) {
       commandHelp.d.print();
     }
+    commandHelp.c.print();
     commandHelp.q.print();
     for (final FlutterDevice device in flutterDevices) {
       final String dname = device.device.name;
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 111918e..67a93d3 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -1053,6 +1053,7 @@
     if (_didAttach) {
       commandHelp.d.print();
     }
+    commandHelp.c.print();
     commandHelp.q.print();
     if (details) {
       printHelpDetails();
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 2231ed7..4f40715 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -737,6 +737,9 @@
 
   @override
   bool get hasTerminal => false;
+
+  @override
+  void clear() => _log('[stdout] ${globals.terminal.clearScreen()}\n');
 }
 
 class LoggerInterrupted implements Exception {
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 e576d2c..533d9ce 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -377,6 +377,7 @@
           commandHelp.r,
           commandHelp.R,
           commandHelp.h,
+          commandHelp.c,
           commandHelp.q,
           commandHelp.s,
           commandHelp.w,
@@ -390,7 +391,6 @@
           commandHelp.z,
           commandHelp.P,
           commandHelp.a,
-          commandHelp.s,
           'An Observatory debugger and profiler on null is available at: null',
           ''
         ].join('\n')
@@ -423,6 +423,15 @@
     expect(testLogger.errorText, contains('Error'));
   }));
 
+  test('ResidentTunner clears the screen when it should', () => testbed.run(() async {
+    const String message = 'This should be cleared';
+    expect(testLogger.statusText, equals(''));
+    testLogger.printStatus(message);
+    expect(testLogger.statusText, equals(message + '\n'));  // printStatus makes a newline
+    residentRunner.clearScreen();
+    expect(testLogger.statusText, equals(''));
+  }));
+
   test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws post', () => testbed.run(() async {
     when(mockDevice.supportsScreenshot).thenReturn(true);
     when(mockIsolate.flutterDebugAllowBanner(true)).thenThrow(Exception());
diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart
index 7def2fd..0f6389c 100644
--- a/packages/flutter_tools/test/src/testbed.dart
+++ b/packages/flutter_tools/test/src/testbed.dart
@@ -822,6 +822,9 @@
 
   @override
   bool get supportsColor => delegate.supportsColor;
+
+  @override
+  void clear() => delegate.clear();
 }
 
 /// An implementation of the Cache which does not download or require locking.