Attempt to redraw the active commandline when needed
The goal is to have the prompt and any text remain visible even
when asynchronous processes spit out output.
Bug: 6205785
Change-Id: I2d06a8ce57f744a6bf705745b604eb6d21c5fb13
diff --git a/src/com/android/tradefed/command/Console.java b/src/com/android/tradefed/command/Console.java
index bcfee01..5747ebe 100644
--- a/src/com/android/tradefed/command/Console.java
+++ b/src/com/android/tradefed/command/Console.java
@@ -24,6 +24,7 @@
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceManager;
import com.android.tradefed.device.IDeviceManager;
+import com.android.tradefed.log.ConsoleReaderOutputStream;
import com.android.tradefed.log.LogRegistry;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.QuotationAwareTokenizer;
@@ -34,6 +35,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -164,7 +166,9 @@
*/
private static ConsoleReader getReader() {
try {
- return new ConsoleReader();
+ final ConsoleReader reader = new ConsoleReader();
+ System.setOut(new PrintStream(new ConsoleReaderOutputStream(reader)));
+ return reader;
} catch (IOException e) {
System.err.format("Failed to initialize ConsoleReader: %s\n", e.getMessage());
return null;
@@ -642,7 +646,6 @@
protected void printLine(String output) {
if (mConsoleReader != null) {
try {
- mConsoleReader.printString(output);
mConsoleReader.printNewline();
} catch (IOException e) {
// not guaranteed to work, but worth a try
diff --git a/src/com/android/tradefed/log/ConsoleReaderOutputStream.java b/src/com/android/tradefed/log/ConsoleReaderOutputStream.java
new file mode 100644
index 0000000..4527e1b
--- /dev/null
+++ b/src/com/android/tradefed/log/ConsoleReaderOutputStream.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.android.tradefed.log;
+
+import jline.ConsoleReader;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An OutputStream that can be used to make {@code System.out.print()} play nice with the user's
+ * {@link ConsoleReader} buffer.
+ * <p />
+ * In trivial performance tests, this class did not have a measurable performance impact.
+ */
+public class ConsoleReaderOutputStream extends OutputStream {
+ /**
+ * ANSI "clear line" (Esc + "[2K") followed by carriage return
+ * See: http://ascii-table.com/ansi-escape-sequences-vt-100.php
+ */
+ private static final String ANSI_CR = "\u001b[2K\r";
+ private static final String CR = "\r";
+ private final ConsoleReader mConsoleReader;
+
+ public ConsoleReaderOutputStream(ConsoleReader reader) {
+ if (reader == null) throw new NullPointerException();
+ mConsoleReader = reader;
+ }
+
+ @Override
+ public void flush() {
+ try {
+ mConsoleReader.flushConsole();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ /**
+ * A special implementation to keep the user's command buffer visible when asynchronous tasks
+ * write to stdout.
+ * <p />
+ * If a full-line write is detected (one that terminates with "\n"), we:
+ * <ol>
+ * <li>Clear the current line (which will contain the prompt and the user's buffer</li>
+ * <li>Print the full line(s), which will drop us on a new line</li>
+ * <li>Redraw the prompt and the user's buffer</li>
+ * </ol>
+ * <p />
+ * By doing so, we never skip any asynchronously-logged output, but we still keep the prompt and
+ * the user's buffer as the last items on the screen.
+ */
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ final boolean fullLine = b[off + len - 1] == '\n';
+ if (fullLine) {
+ if (mConsoleReader.getTerminal().isANSISupported()) {
+ // use ANSI escape codes to clear the line and jump to the beginning
+ mConsoleReader.printString(ANSI_CR);
+ } else {
+ // Just jump to the beginning of the line to print the message
+ mConsoleReader.printString(CR);
+ }
+ }
+
+ mConsoleReader.printString(new String(b, off, len));
+
+ if (fullLine) {
+ mConsoleReader.drawLine();
+ mConsoleReader.flushConsole();
+ }
+ }
+
+ // FIXME: it'd be nice if ConsoleReader had a way to write a character rather than just a
+ // FIXME: String. As is, this method makes me cringe. Especially since the first thing
+ // FIXME: ConsoleReader does is convert it back into a char array :o(
+ @Override
+ public void write(int b) throws IOException {
+ char[] str = new char[] {(char)(b & 0xff)};
+ mConsoleReader.printString(new String(str));
+ }
+}
+