blob: 5e5fd52ecc0dd6674a4cb37e10dbef53d05013e5 [file] [log] [blame]
/*
* 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));
}
}