1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tradefed.log; 18 19 import jline.ConsoleReader; 20 21 import java.io.IOException; 22 import java.io.OutputStream; 23 24 /** 25 * An OutputStream that can be used to make {@code System.out.print()} play nice with the user's 26 * {@link ConsoleReader} buffer. 27 * <p /> 28 * In trivial performance tests, this class did not have a measurable performance impact. 29 */ 30 public class ConsoleReaderOutputStream extends OutputStream { 31 /** 32 * ANSI "clear line" (Esc + "[2K") followed by carriage return 33 * See: http://ascii-table.com/ansi-escape-sequences-vt-100.php 34 */ 35 private static final String ANSI_CR = "\u001b[2K\r"; 36 private static final String CR = "\r"; 37 private final ConsoleReader mConsoleReader; 38 39 /** 40 * We disable the prompt-shuffling behavior while synchronous, user-initiated tasks are running. 41 * Otherwise, we try to shuffle the prompt when none is displayed, and we end up clearing lines 42 * that shouldn't be cleared. 43 * 44 * @see #setSyncMode() 45 * @see #setAsyncMode() 46 */ 47 private boolean mInAsyncMode = false; 48 ConsoleReaderOutputStream(ConsoleReader reader)49 public ConsoleReaderOutputStream(ConsoleReader reader) { 50 if (reader == null) throw new NullPointerException(); 51 mConsoleReader = reader; 52 } 53 54 /** 55 * Set synchronous mode. This occurs after the user has taken some action, such that the most 56 * recent line on the screen is guaranteed to _not_ be the command prompt. In this case, we 57 * disable the prompt-shuffling behavior (which requires that the most recent line on the screen 58 * be the prompt) 59 */ setSyncMode()60 public void setSyncMode() { 61 mInAsyncMode = false; 62 } 63 64 /** 65 * Set asynchronous mode. This occurs immediately after we display the command prompt and begin 66 * waiting for user input. In this mode, the most recent line on the screen is guaranteed to be 67 * the command prompt. In particular, asynchronous tasks may attempt to print to the screen, 68 * and we will shuffle the prompt when they do so. 69 */ setAsyncMode()70 public void setAsyncMode() { 71 mInAsyncMode = true; 72 } 73 74 /** 75 * Get the ConsoleReader instance that we're using internally 76 */ getConsoleReader()77 public ConsoleReader getConsoleReader() { 78 return mConsoleReader; 79 } 80 81 /** 82 * {@inheritDoc} 83 */ 84 @Override flush()85 public synchronized void flush() { 86 try { 87 mConsoleReader.flushConsole(); 88 } catch (IOException e) { 89 // ignore 90 } 91 } 92 93 /** 94 * A special implementation to keep the user's command buffer visible when asynchronous tasks 95 * write to stdout. 96 * <p /> 97 * If a full-line write is detected (one that terminates with "\n"), we: 98 * <ol> 99 * <li>Clear the current line (which will contain the prompt and the user's buffer</li> 100 * <li>Print the full line(s), which will drop us on a new line</li> 101 * <li>Redraw the prompt and the user's buffer</li> 102 * </ol> 103 * <p /> 104 * By doing so, we never skip any asynchronously-logged output, but we still keep the prompt and 105 * the user's buffer as the last items on the screen. 106 * <p /> 107 * FIXME: We should probably buffer output and only write full lines to the console. 108 */ 109 @Override write(byte[] b, int off, int len)110 public synchronized void write(byte[] b, int off, int len) throws IOException { 111 final boolean shufflePrompt = mInAsyncMode && (b[off + len - 1] == '\n'); 112 113 if (shufflePrompt) { 114 if (mConsoleReader.getTerminal().isANSISupported()) { 115 // use ANSI escape codes to clear the line and jump to the beginning 116 mConsoleReader.printString(ANSI_CR); 117 } else { 118 // Just jump to the beginning of the line to print the message 119 mConsoleReader.printString(CR); 120 } 121 } 122 123 mConsoleReader.printString(new String(b, off, len)); 124 125 if (shufflePrompt) { 126 mConsoleReader.drawLine(); 127 mConsoleReader.flushConsole(); 128 } 129 } 130 131 // FIXME: it'd be nice if ConsoleReader had a way to write a character rather than just a 132 // FIXME: String. As is, this method makes me cringe. Especially since the first thing 133 // FIXME: ConsoleReader does is convert it back into a char array :o( 134 @Override write(int b)135 public synchronized void write(int b) throws IOException { 136 char[] str = new char[] {(char)(b & 0xff)}; 137 mConsoleReader.printString(new String(str)); 138 } 139 } 140 141