Move CLI options to a top-level class UiOptions
Remove the now-unused BlazeCommandEventHandler.
PiperOrigin-RevId: 259940495
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
index c0a4aea..3297236 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
@@ -30,9 +30,9 @@
 import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
 import com.google.devtools.build.lib.pkgcache.LoadingOptions;
 import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
-import com.google.devtools.build.lib.runtime.BlazeCommandEventHandler;
 import com.google.devtools.build.lib.runtime.KeepGoingOption;
 import com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption;
+import com.google.devtools.build.lib.runtime.UiOptions;
 import com.google.devtools.build.lib.util.OptionsUtils;
 import com.google.devtools.build.lib.util.io.OutErr;
 import com.google.devtools.common.options.OptionsBase;
@@ -314,7 +314,7 @@
         commandId, commandStartTime);
 
     // All this, just to pass a global boolean from the client to the server. :(
-    if (options.getOptions(BlazeCommandEventHandler.Options.class).runningInEmacs) {
+    if (options.getOptions(UiOptions.class).runningInEmacs) {
       request.setRunningInEmacs();
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
index 513d210..bfe6c7c 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -345,8 +345,7 @@
       }
 
       try (SilentCloseable closeable = Profiler.instance().profile("setup event handler")) {
-        BlazeCommandEventHandler.Options eventHandlerOptions =
-            options.getOptions(BlazeCommandEventHandler.Options.class);
+        UiOptions eventHandlerOptions = options.getOptions(UiOptions.class);
         OutErr colorfulOutErr = outErr;
 
         if (!eventHandlerOptions.useColor()) {
@@ -642,8 +641,7 @@
   }
 
   /** Returns the event handler to use for this Blaze command. */
-  private EventHandler createEventHandler(
-      OutErr outErr, BlazeCommandEventHandler.Options eventOptions) {
+  private EventHandler createEventHandler(OutErr outErr, UiOptions eventOptions) {
     Path workspacePath = runtime.getWorkspace().getDirectories().getWorkspace();
     PathFragment workspacePathFragment = workspacePath == null ? null : workspacePath.asFragment();
     return new ExperimentalEventHandler(
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandEventHandler.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandEventHandler.java
deleted file mode 100644
index 03b1537..0000000
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandEventHandler.java
+++ /dev/null
@@ -1,251 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.google.devtools.build.lib.runtime;
-
-import com.google.devtools.build.lib.runtime.ExperimentalStateTracker.ProgressMode;
-import com.google.devtools.common.options.EnumConverter;
-import com.google.devtools.common.options.Option;
-import com.google.devtools.common.options.OptionDocumentationCategory;
-import com.google.devtools.common.options.OptionEffectTag;
-import com.google.devtools.common.options.OptionMetadataTag;
-import com.google.devtools.common.options.OptionsBase;
-
-/**
- * BlazeCommandEventHandler: an event handler established for the duration of a single Blaze
- * command.
- */
-public class BlazeCommandEventHandler {
-
-  public enum UseColor { YES, NO, AUTO }
-  public enum UseCurses { YES, NO, AUTO }
-
-  public static class UseColorConverter extends EnumConverter<UseColor> {
-    public UseColorConverter() {
-      super(UseColor.class, "--color setting");
-    }
-  }
-
-  public static class UseCursesConverter extends EnumConverter<UseCurses> {
-    public UseCursesConverter() {
-      super(UseCurses.class, "--curses setting");
-    }
-  }
-
-  /** Progress mode converter. */
-  public static class ProgressModeConverter extends EnumConverter<ProgressMode> {
-    public ProgressModeConverter() {
-      super(ProgressMode.class, "--experimental_ui_mode setting");
-    }
-  }
-
-  public static class Options extends OptionsBase {
-
-    @Option(
-      name = "show_progress",
-      defaultValue = "true",
-      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help = "Display progress messages during a build."
-    )
-    public boolean showProgress;
-
-    @Option(
-      name = "show_task_finish",
-      defaultValue = "false",
-      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help = "Display progress messages when tasks complete, not just when they start."
-    )
-    public boolean showTaskFinish;
-
-    @Option(
-      name = "show_progress_rate_limit",
-      defaultValue = "0.2", // A nice middle ground; snappy but not too spammy in logs.
-      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help = "Minimum number of seconds between progress messages in the output."
-    )
-    public double showProgressRateLimit;
-
-    @Option(
-        name = "color",
-        defaultValue = "auto",
-        converter = UseColorConverter.class,
-        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-        effectTags = {OptionEffectTag.UNKNOWN},
-        help = "Use terminal controls to colorize output.")
-    public UseColor useColorEnum;
-
-    @Option(
-        name = "curses",
-        defaultValue = "auto",
-        converter = UseCursesConverter.class,
-        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-        effectTags = {OptionEffectTag.UNKNOWN},
-        help = "Use terminal cursor controls to minimize scrolling output.")
-    public UseCurses useCursesEnum;
-
-    @Option(
-      name = "terminal_columns",
-      defaultValue = "80",
-      metadataTags = {OptionMetadataTag.HIDDEN},
-      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help = "A system-generated parameter which specifies the terminal width in columns."
-    )
-    public int terminalColumns;
-
-    @Option(
-        name = "isatty",
-        // TODO(b/137881511): Old name should be removed after 2020-01-01, or whenever is
-        // reasonable.
-        oldName = "is_stderr_atty",
-        defaultValue = "false",
-        metadataTags = {OptionMetadataTag.HIDDEN},
-        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
-        effectTags = {OptionEffectTag.UNKNOWN},
-        help =
-            "A system-generated parameter which is used to notify the "
-                + "server whether this client is running in a terminal. "
-                + "If this is set to false, then '--color=auto' will be treated as '--color=no'. "
-                + "If this is set to true, then '--color=auto' will be treated as '--color=yes'.")
-    public boolean isATty;
-
-    // This lives here (as opposed to the more logical BuildRequest.Options)
-    // because the client passes it to the server *always*.  We don't want the
-    // client to have to figure out when it should or shouldn't to send it.
-    @Option(
-      name = "emacs",
-      defaultValue = "false",
-      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help =
-          "A system-generated parameter which is true iff EMACS=t or INSIDE_EMACS is set "
-              + "in the environment of the client.  This option controls certain display "
-              + "features."
-    )
-    public boolean runningInEmacs;
-
-    @Option(
-      name = "show_timestamps",
-      defaultValue = "false",
-      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help = "Include timestamps in messages"
-    )
-    public boolean showTimestamp;
-
-    @Option(
-        name = "progress_in_terminal_title",
-        defaultValue = "false",
-        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-        effectTags = {OptionEffectTag.UNKNOWN},
-        help =
-            "Show the command progress in the terminal title. "
-                + "Useful to see what bazel is doing when having multiple terminal tabs.")
-    public boolean progressInTermTitle;
-
-    @Option(
-      name = "experimental_external_repositories",
-      defaultValue = "false",
-      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help = "Use external repositories for improved stability and speed when available."
-    )
-    public boolean externalRepositories;
-
-    @Option(
-      name = "force_experimental_external_repositories",
-      defaultValue = "false",
-      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help = "Forces --experimental_external_repositories."
-    )
-    public boolean forceExternalRepositories;
-
-    @Option(
-        name = "attempt_to_print_relative_paths",
-        oldName = "experimental_ui_attempt_to_print_relative_paths",
-        defaultValue = "false",
-        documentationCategory = OptionDocumentationCategory.LOGGING,
-        effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
-        help =
-            "When printing the location part of messages, attempt to use a path relative to the "
-                + "workspace directory or one of the directories specified by --package_path.")
-    public boolean attemptToPrintRelativePaths;
-
-    @Option(
-        name = "experimental_ui_debug_all_events",
-        defaultValue = "false",
-        metadataTags = {OptionMetadataTag.HIDDEN},
-        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
-        effectTags = {OptionEffectTag.UNKNOWN},
-        help = "Report all events known to the Bazel UI.")
-    public boolean experimentalUiDebugAllEvents;
-
-    @Option(
-        name = "experimental_ui_mode",
-        defaultValue = "oldest_actions",
-        converter = ProgressModeConverter.class,
-        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-        effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
-        help =
-            "Determines what kind of data is shown in the detailed progress bar. By default, it is "
-                + "set to show the oldest actions and their running time. The underlying data "
-                + "source is usually sampled in a mode-dependend way to fit within the number of "
-                + "lines given by --ui_actions_shown.")
-    public ProgressMode uiProgressMode;
-
-    @Option(
-        name = "ui_actions_shown",
-        oldName = "experimental_ui_actions_shown",
-        defaultValue = "8",
-        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-        effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
-        help =
-            "Number of concurrent actions shown in the detailed progress bar; each "
-                + "action is shown on a separate line. The progress bar always shows "
-                + "at least one one, all numbers less than 1 are mapped to 1. "
-                + "This option has no effect if --noui is set.")
-    public int uiSamplesShown;
-
-    @Option(
-        name = "experimental_ui_limit_console_output",
-        defaultValue = "0",
-        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
-        effectTags = {OptionEffectTag.UNKNOWN},
-        help =
-            "Number of bytes to which the UI will limit its output (non-positive "
-                + "values indicate unlimited). Once the limit is approaching, the UI "
-                + "will try hard to limit in a meaningful way, but will ultimately just drop all "
-                + "output.")
-    public int experimentalUiLimitConsoleOutput;
-
-    @Option(
-        name = "experimental_ui_deduplicate",
-        defaultValue = "false",
-        documentationCategory = OptionDocumentationCategory.LOGGING,
-        effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
-        help = "Make the UI deduplicate messages to have a cleaner scroll-back log.")
-    public boolean experimentalUiDeduplicate;
-
-    public boolean useColor() {
-      return useColorEnum == UseColor.YES || (useColorEnum == UseColor.AUTO && isATty);
-    }
-
-    public boolean useCursorControl() {
-      return useCursesEnum == UseCurses.YES || (useCursesEnum == UseCurses.AUTO && isATty);
-    }
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
index 41469c44..eb5630b 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
@@ -42,7 +42,7 @@
   /** The set of option-classes that are common to all Blaze commands. */
   private static final ImmutableList<Class<? extends OptionsBase>> COMMON_COMMAND_OPTIONS =
       ImmutableList.of(
-          BlazeCommandEventHandler.Options.class,
+          UiOptions.class,
           CommonCommandOptions.class,
           ClientOptions.class,
           // Skylark options aren't applicable to all commands, but making them a common option
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalEventHandler.java b/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalEventHandler.java
index f00b379..f5e121c 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalEventHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalEventHandler.java
@@ -222,10 +222,7 @@
   }
 
   public ExperimentalEventHandler(
-      OutErr outErr,
-      BlazeCommandEventHandler.Options options,
-      Clock clock,
-      @Nullable PathFragment workspacePathFragment) {
+      OutErr outErr, UiOptions options, Clock clock, @Nullable PathFragment workspacePathFragment) {
     this.terminalWidth = (options.terminalColumns > 0 ? options.terminalColumns : 80);
     this.outputLimit = options.experimentalUiLimitConsoleOutput;
     this.counter = new AtomicLong(outputLimit);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/UiOptions.java b/src/main/java/com/google/devtools/build/lib/runtime/UiOptions.java
new file mode 100644
index 0000000..62b6dc6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/UiOptions.java
@@ -0,0 +1,234 @@
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.runtime;
+
+import com.google.devtools.build.lib.runtime.ExperimentalStateTracker.ProgressMode;
+import com.google.devtools.common.options.EnumConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionMetadataTag;
+import com.google.devtools.common.options.OptionsBase;
+
+/** Command-line UI options. */
+public class UiOptions extends OptionsBase {
+
+  /** Enum to select whether color output is enabled or not. */
+  public enum UseColor {
+    YES,
+    NO,
+    AUTO
+  }
+
+  /** Enum to select whether curses output is enabled or not. */
+  public enum UseCurses {
+    YES,
+    NO,
+    AUTO
+  }
+
+  /** Converter for {@link UseColor}. */
+  public static class UseColorConverter extends EnumConverter<UseColor> {
+    public UseColorConverter() {
+      super(UseColor.class, "--color setting");
+    }
+  }
+
+  /** Converter for {@link UseCurses}. */
+  public static class UseCursesConverter extends EnumConverter<UseCurses> {
+    public UseCursesConverter() {
+      super(UseCurses.class, "--curses setting");
+    }
+  }
+
+  /** Progress mode converter. */
+  public static class ProgressModeConverter extends EnumConverter<ProgressMode> {
+    public ProgressModeConverter() {
+      super(ProgressMode.class, "--experimental_ui_mode setting");
+    }
+  }
+
+  @Option(
+      name = "show_progress",
+      defaultValue = "true",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Display progress messages during a build.")
+  public boolean showProgress;
+
+  @Option(
+      name = "show_task_finish",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Display progress messages when tasks complete, not just when they start.")
+  public boolean showTaskFinish;
+
+  @Option(
+      name = "show_progress_rate_limit",
+      defaultValue = "0.2", // A nice middle ground; snappy but not too spammy in logs.
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Minimum number of seconds between progress messages in the output.")
+  public double showProgressRateLimit;
+
+  @Option(
+      name = "color",
+      defaultValue = "auto",
+      converter = UseColorConverter.class,
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Use terminal controls to colorize output.")
+  public UseColor useColorEnum;
+
+  @Option(
+      name = "curses",
+      defaultValue = "auto",
+      converter = UseCursesConverter.class,
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Use terminal cursor controls to minimize scrolling output.")
+  public UseCurses useCursesEnum;
+
+  @Option(
+      name = "terminal_columns",
+      defaultValue = "80",
+      metadataTags = {OptionMetadataTag.HIDDEN},
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "A system-generated parameter which specifies the terminal width in columns.")
+  public int terminalColumns;
+
+  @Option(
+      name = "isatty",
+      // TODO(b/137881511): Old name should be removed after 2020-01-01, or whenever is
+      // reasonable.
+      oldName = "is_stderr_atty",
+      defaultValue = "false",
+      metadataTags = {OptionMetadataTag.HIDDEN},
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help =
+          "A system-generated parameter which is used to notify the "
+              + "server whether this client is running in a terminal. "
+              + "If this is set to false, then '--color=auto' will be treated as '--color=no'. "
+              + "If this is set to true, then '--color=auto' will be treated as '--color=yes'.")
+  public boolean isATty;
+
+  // This lives here (as opposed to the more logical BuildRequest.Options)
+  // because the client passes it to the server *always*.  We don't want the
+  // client to have to figure out when it should or shouldn't to send it.
+  @Option(
+      name = "emacs",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help =
+          "A system-generated parameter which is true iff EMACS=t or INSIDE_EMACS is set "
+              + "in the environment of the client.  This option controls certain display "
+              + "features.")
+  public boolean runningInEmacs;
+
+  @Option(
+      name = "show_timestamps",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Include timestamps in messages")
+  public boolean showTimestamp;
+
+  @Option(
+      name = "progress_in_terminal_title",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help =
+          "Show the command progress in the terminal title. "
+              + "Useful to see what bazel is doing when having multiple terminal tabs.")
+  public boolean progressInTermTitle;
+
+  @Option(
+      name = "attempt_to_print_relative_paths",
+      oldName = "experimental_ui_attempt_to_print_relative_paths",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.LOGGING,
+      effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+      help =
+          "When printing the location part of messages, attempt to use a path relative to the "
+              + "workspace directory or one of the directories specified by --package_path.")
+  public boolean attemptToPrintRelativePaths;
+
+  @Option(
+      name = "experimental_ui_debug_all_events",
+      defaultValue = "false",
+      metadataTags = {OptionMetadataTag.HIDDEN},
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Report all events known to the Bazel UI.")
+  public boolean experimentalUiDebugAllEvents;
+
+  @Option(
+      name = "experimental_ui_mode",
+      defaultValue = "oldest_actions",
+      converter = ProgressModeConverter.class,
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+      help =
+          "Determines what kind of data is shown in the detailed progress bar. By default, it is "
+              + "set to show the oldest actions and their running time. The underlying data "
+              + "source is usually sampled in a mode-dependend way to fit within the number of "
+              + "lines given by --ui_actions_shown.")
+  public ProgressMode uiProgressMode;
+
+  @Option(
+      name = "ui_actions_shown",
+      oldName = "experimental_ui_actions_shown",
+      defaultValue = "8",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+      help =
+          "Number of concurrent actions shown in the detailed progress bar; each "
+              + "action is shown on a separate line. The progress bar always shows "
+              + "at least one one, all numbers less than 1 are mapped to 1. "
+              + "This option has no effect if --noui is set.")
+  public int uiSamplesShown;
+
+  @Option(
+      name = "experimental_ui_limit_console_output",
+      defaultValue = "0",
+      documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help =
+          "Number of bytes to which the UI will limit its output (non-positive "
+              + "values indicate unlimited). Once the limit is approaching, the UI "
+              + "will try hard to limit in a meaningful way, but will ultimately just drop all "
+              + "output.")
+  public int experimentalUiLimitConsoleOutput;
+
+  @Option(
+      name = "experimental_ui_deduplicate",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.LOGGING,
+      effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+      help = "Make the UI deduplicate messages to have a cleaner scroll-back log.")
+  public boolean experimentalUiDeduplicate;
+
+  public boolean useColor() {
+    return useColorEnum == UseColor.YES || (useColorEnum == UseColor.AUTO && isATty);
+  }
+
+  public boolean useCursorControl() {
+    return useCursesEnum == UseCurses.YES || (useCursesEnum == UseCurses.AUTO && isATty);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
index 92e0951..e1d29b3 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
@@ -31,7 +31,6 @@
 import com.google.devtools.build.lib.exec.TestStrategy.TestOutputFormat;
 import com.google.devtools.build.lib.runtime.AggregatingTestListener;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
-import com.google.devtools.build.lib.runtime.BlazeCommandEventHandler;
 import com.google.devtools.build.lib.runtime.BlazeCommandResult;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
@@ -41,6 +40,7 @@
 import com.google.devtools.build.lib.runtime.TestResultAnalyzer;
 import com.google.devtools.build.lib.runtime.TestResultNotifier;
 import com.google.devtools.build.lib.runtime.TestSummaryPrinter.TestLogPathFormatter;
+import com.google.devtools.build.lib.runtime.UiOptions;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.util.io.AnsiTerminalPrinter;
 import com.google.devtools.build.lib.vfs.Path;
@@ -100,8 +100,10 @@
         options.getOptions(ExecutionOptions.class),
         env.getEventBus());
 
-    printer = new AnsiTerminalPrinter(env.getReporter().getOutErr().getOutputStream(),
-        options.getOptions(BlazeCommandEventHandler.Options.class).useColor());
+    printer =
+        new AnsiTerminalPrinter(
+            env.getReporter().getOutErr().getOutputStream(),
+            options.getOptions(UiOptions.class).useColor());
 
     // Initialize test handler.
     AggregatingTestListener testListener =
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
index 8762d59..a0c48d6 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
@@ -40,7 +40,6 @@
 import com.google.devtools.build.lib.profiler.Profiler;
 import com.google.devtools.build.lib.profiler.SilentCloseable;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
-import com.google.devtools.build.lib.runtime.BlazeCommandEventHandler;
 import com.google.devtools.build.lib.runtime.BlazeCommandResult;
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
@@ -52,6 +51,7 @@
 import com.google.devtools.build.lib.runtime.GotOptionsEvent;
 import com.google.devtools.build.lib.runtime.KeepGoingOption;
 import com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption;
+import com.google.devtools.build.lib.runtime.UiOptions;
 import com.google.devtools.build.lib.runtime.commands.BuildCommand;
 import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
 import com.google.devtools.build.lib.sandbox.SandboxOptions;
@@ -150,7 +150,7 @@
                 LoadingPhaseThreadsOption.class,
                 PackageCacheOptions.class,
                 StarlarkSemanticsOptions.class,
-                BlazeCommandEventHandler.Options.class,
+                UiOptions.class,
                 SandboxOptions.class));
 
     for (BlazeModule module : runtime.getBlazeModules()) {
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/AbstractCommandTest.java b/src/test/java/com/google/devtools/build/lib/runtime/AbstractCommandTest.java
index 9fb518d..e84ec4b 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/AbstractCommandTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/AbstractCommandTest.java
@@ -132,7 +132,7 @@
   private Collection<Class<?>> optionClassesWithDefault(Class<?>... optionClasses) {
     List<Class<?>> result = new ArrayList<>();
     Collections.addAll(result, optionClasses);
-    result.add(BlazeCommandEventHandler.Options.class);
+    result.add(UiOptions.class);
     result.add(CommonCommandOptions.class);
     result.add(ClientOptions.class);
     result.add(StarlarkSemanticsOptions.class);
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/StarlarkOptionsParsingTest.java b/src/test/java/com/google/devtools/build/lib/skylark/StarlarkOptionsParsingTest.java
index ad5c0cf..cce15bc 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/StarlarkOptionsParsingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/StarlarkOptionsParsingTest.java
@@ -24,7 +24,6 @@
 import com.google.devtools.build.lib.pkgcache.LoadingOptions;
 import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
-import com.google.devtools.build.lib.runtime.BlazeCommandEventHandler.Options;
 import com.google.devtools.build.lib.runtime.BlazeCommandResult;
 import com.google.devtools.build.lib.runtime.ClientOptions;
 import com.google.devtools.build.lib.runtime.Command;
@@ -32,6 +31,7 @@
 import com.google.devtools.build.lib.runtime.CommonCommandOptions;
 import com.google.devtools.build.lib.runtime.KeepGoingOption;
 import com.google.devtools.build.lib.runtime.StarlarkOptionsParser;
+import com.google.devtools.build.lib.runtime.UiOptions;
 import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -59,7 +59,7 @@
           KeepGoingOption.class,
           LoadingOptions.class,
           ClientOptions.class,
-          Options.class,
+          UiOptions.class,
           CommonCommandOptions.class);
 
   @Before
@@ -84,7 +84,7 @@
         KeepGoingOption.class,
         LoadingOptions.class,
         ClientOptions.class,
-        Options.class,
+        UiOptions.class,
       },
       allowResidue = true,
       shortDescription =