• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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