| /* |
| * 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; |
| |
| /** |
| * We disable the prompt-shuffling behavior while synchronous, user-initiated tasks are running. |
| * Otherwise, we try to shuffle the prompt when none is displayed, and we end up clearing lines |
| * that shouldn't be cleared. |
| * |
| * @see setSyncMode() |
| * @see setAsyncMode() |
| */ |
| private boolean mInAsyncMode = false; |
| |
| public ConsoleReaderOutputStream(ConsoleReader reader) { |
| if (reader == null) throw new NullPointerException(); |
| mConsoleReader = reader; |
| } |
| |
| /** |
| * Set synchronous mode. This occurs after the user has taken some action, such that the most |
| * recent line on the screen is guaranteed to _not_ be the command prompt. In this case, we |
| * disable the prompt-shuffling behavior (which requires that the most recent line on the screen |
| * be the prompt) |
| */ |
| public void setSyncMode() { |
| mInAsyncMode = false; |
| } |
| |
| /** |
| * Set asynchronous mode. This occurs immediately after we display the command prompt and begin |
| * waiting for user input. In this mode, the most recent line on the screen is guaranteed to be |
| * the command prompt. In particular, asynchronous tasks may attempt to print to the screen, |
| * and we will shuffle the prompt when they do so. |
| */ |
| public void setAsyncMode() { |
| mInAsyncMode = true; |
| } |
| |
| /** |
| * Get the ConsoleReader instance that we're using internally |
| */ |
| public ConsoleReader getConsoleReader() { |
| return mConsoleReader; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized 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. |
| * <p /> |
| * FIXME: We should probably buffer output and only write full lines to the console. |
| */ |
| @Override |
| public synchronized void write(byte[] b, int off, int len) throws IOException { |
| final boolean shufflePrompt = mInAsyncMode && (b[off + len - 1] == '\n'); |
| |
| if (shufflePrompt) { |
| 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 (shufflePrompt) { |
| 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 synchronized void write(int b) throws IOException { |
| char[] str = new char[] {(char)(b & 0xff)}; |
| mConsoleReader.printString(new String(str)); |
| } |
| } |
| |