diff --git a/Console.iml b/Console.iml
new file mode 100644
index 0000000..8041b36
--- /dev/null
+++ b/Console.iml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF
index 1af4762..d95f55d 100644
--- a/src/META-INF/MANIFEST.MF
+++ b/src/META-INF/MANIFEST.MF
@@ -1,3 +1,3 @@
Manifest-Version: 1.0
-Main-Class: com.dorkbox.console.AnsiRendererTestExample
+Main-Class: com.dorkbox.console.AnsiConsoleExample
diff --git a/src/dorkbox/console/Console.java b/src/dorkbox/console/Console.java
index 45af5bb..01ef189 100644
--- a/src/dorkbox/console/Console.java
+++ b/src/dorkbox/console/Console.java
@@ -1,36 +1,99 @@
+/*
+ * Copyright 2016 dorkbox, llc
+ *
+ * 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 dorkbox.console;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import dorkbox.console.output.AnsiOutputStream;
+import dorkbox.console.output.WindowsAnsiOutputStream;
+import dorkbox.console.util.posix.CLibraryPosix;
+import dorkbox.console.util.windows.Kernel32;
+import dorkbox.util.Property;
+
/**
+ * Provides a fluent API for generating ANSI escape sequences and providing access to streams that support it.
+ *
+ * See: https://en.wikipedia.org/wiki/ANSI_escape_code
*
+ * @author dorkbox, llc
*/
public
class Console {
+
+
+ /**
+ * If true, allows an ANSI output stream to be created, otherwise a NO-OP stream is created instead
+ */
+ @Property
+ public static boolean ENABLE_ANSI = true;
+
+ /**
+ * If true, then we always force the raw ANSI output stream to be enabled (even if the output stream is not aware of ANSI commands).
+ * This can be used to obtain the raw ANSI escape codes for other color aware programs (ie: less -r)
+ */
+ @Property
+ public static boolean FORCE_ENABLE_ANSI = false;
+
+ /**
+ * Enables or disables character echo to stdout in the console, should call {@link #setEchoEnabled(boolean)} after initialization
+ */
+ @Property
public static volatile boolean ENABLE_ECHO = true;
- public static boolean PASSWORD_ECHO = true;
+ /**
+ * Enables or disables CTRL-C behavior in the console, should call {@link #setInterruptEnabled(boolean)} after initialization
+ */
+ @Property
+ public static volatile boolean ENABLE_INTERRUPT = false;
+
+ @Property
public static char PASSWORD_ECHO_CHAR = '*';
- // how many threads can read from this input at the same time
- public static int NUMBER_OF_READERS = 32;
-
-
+ /**
+ * Enables the backspace key to delete characters in the line buffer and (if ANSI is enabled) from the screen.
+ */
+ @Property
public final static boolean ENABLE_BACKSPACE = true;
-// enableBackspace = Boolean.parseBoolean(System.getProperty(Console.ENABLE_BACKSPACE, "true"));
- public static String ENABLE_BACKSPACEs = "input.enableBackspace";
-
// OS types supported by the input console. Default is AUTO
public static final String AUTO = "auto";
public static final String UNIX = "unix";
- public static final String WIN = "win";
public static final String WINDOWS = "windows";
public static final String NONE = "none"; // this is the same as unsupported
- public static final String OFF = "off"; // this is the same as unsupported
- public static final String FALSE = "false"; // this is the same as unsupported
- public static final String INPUT_CONSOLE_OSTYPE = "AUTO";
+
+ // valid are what's above
+ @Property
+ public static final String INPUT_CONSOLE_TYPE = "AUTO";
+
+
+
+ private static final PrintStream original_out = System.out;
+ private static final PrintStream original_err = System.err;
+
+ // protected by synchronize
+ private static int installed = 0;
+ private static PrintStream out;
+ private static PrintStream err;
+
/**
@@ -41,7 +104,217 @@ class Console {
return "2.9";
}
- public static Input in = new Input();
- public static String out;
- public static String err;
+ /**
+ * Reads single character input from the console.
+ *
+ * @return -1 if no data or problems
+ */
+ public static
+ int read() {
+ return Input.read();
+ }
+
+ /**
+ * Reads a line of characters from the console, defined as everything before the 'ENTER' key is pressed
+ *
+ * @return null if no data
+ */
+ public static
+ String readLine() {
+ return Input.readLine();
+ }
+
+
+ /**
+ * Reads a line of characters from the console as a character array, defined as everything before the 'ENTER' key is pressed
+ *
+ * @return empty char[] if no data
+ */
+ public static
+ char[] readLineChars() {
+ return Input.readLineChars();
+ }
+
+ /**
+ * Reads a line of characters from the console as a character array, defined as everything before the 'ENTER' key is pressed
+ *
+ * @return empty char[] if no data
+ */
+ public static
+ char[] readPassword() {
+ return Input.readLinePassword();
+ }
+
+ /**
+ * Enables or disables CTRL-C behavior in the console
+ */
+ public static
+ void setInterruptEnabled(final boolean enabled) {
+ Console.ENABLE_INTERRUPT = enabled;
+ Input.setInterruptEnabled(enabled);
+ }
+
+ /**
+ * Enables or disables character echo to stdout
+ */
+ public static
+ void setEchoEnabled(final boolean enabled) {
+ Console.ENABLE_ECHO = enabled;
+ Input.setEchoEnabled(enabled);
+ }
+
+ /**
+ * Override System.err and System.out with an ANSI capable {@link java.io.PrintStream}.
+ */
+ public static synchronized
+ void systemInstall() {
+ installed++;
+ if (installed == 1) {
+ out = out();
+ err = err();
+
+ System.setOut(out);
+ System.setErr(err);
+ }
+ }
+
+ /**
+ * un-does a previous {@link #systemInstall()}.
+ *
+ * If {@link #systemInstall()} was called multiple times, then {@link #systemUninstall()} must be called the same number of
+ * times before it is uninstalled.
+ */
+ public static synchronized
+ void systemUninstall() {
+ installed--;
+ if (installed == 0) {
+ if (out != null && out != original_out) {
+ out.close();
+ System.setOut(original_out);
+ }
+
+ if (err != null && err != original_err) {
+ err.close();
+ System.setErr(original_err);
+ }
+ }
+ }
+
+ /**
+ * If we are installed to the system (IE: System.err/out, then reset those streams, otherwise there is nothing to do from a static
+ * perspective (since creating a NEW ANSI stream will automatically reset the output
+ */
+ public static synchronized
+ void reset() {
+ if (installed >= 1) {
+ // TODO: make this reset readLine, etc as well?
+ try {
+ System.out.write(AnsiOutputStream.RESET_CODE);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ /**
+ * If the standard out natively supports ANSI escape codes, then this just returns System.out, otherwise it will provide an ANSI
+ * aware PrintStream which strips out the ANSI escape sequences or which implement the escape sequences.
+ *
+ * @return a PrintStream which is ANSI aware.
+ */
+ public static
+ PrintStream out() {
+ if (out == null) {
+ out = createPrintStream(original_out, 1); // STDOUT_FILENO
+ }
+ return out;
+ }
+
+ /**
+ * If the standard out natively supports ANSI escape codes, then this just returns System.err, otherwise it will provide an ANSI aware
+ * PrintStream which strips out the ANSI escape sequences or which implement the escape sequences.
+ *
+ * @return a PrintStream which is ANSI aware.
+ */
+ public static
+ PrintStream err() {
+ if (err == null) {
+ err = createPrintStream(original_err, 2); // STDERR_FILENO
+ }
+ return err;
+ }
+
+
+
+
+
+
+
+
+
+ private static boolean isXterm() {
+ String term = System.getenv("TERM");
+ return "xterm".equalsIgnoreCase(term);
+ }
+
+ private static
+ PrintStream createPrintStream(final OutputStream stream, int fileno) {
+ if (!ENABLE_ANSI) {
+ // Use the ANSIOutputStream to strip out the ANSI escape sequences.
+ return new PrintStream(new AnsiOutputStream(stream));
+ }
+
+ if (!isXterm()) {
+ String os = System.getProperty("os.name");
+ if (os.startsWith("Windows")) {
+ // check if windows10+ (which natively supports ANSI)
+ if (Kernel32.isWindows10OrGreater()) {
+ // Just wrap it up so that when we get closed, we reset the attributes.
+ return deafultPrintStream(stream);
+ }
+
+ // On windows we know the console does not interpret ANSI codes..
+ try {
+ return new PrintStream(new WindowsAnsiOutputStream(stream, fileno));
+ } catch (Throwable ignore) {
+ // this happens when JNA is not in the path.. or
+ // this happens when the stdout is being redirected to a file.
+ // this happens when the stdout is being redirected to different console.
+ }
+
+ // Use the ANSIOutputStream to strip out the ANSI escape sequences.
+ if (!FORCE_ENABLE_ANSI) {
+ return new PrintStream(new AnsiOutputStream(stream));
+ }
+ } else {
+ // We must be on some unix variant..
+ try {
+ // If we can detect that stdout is not a tty.. then setup to strip the ANSI sequences..
+ if (!FORCE_ENABLE_ANSI && CLibraryPosix.isatty(fileno) == 0) {
+ return new PrintStream(new AnsiOutputStream(stream));
+ }
+ } catch (Throwable ignore) {
+ // These errors happen if the JNI lib is not available for your platform.
+ }
+ }
+ }
+
+ // By default we assume the terminal can handle ANSI codes.
+ // Just wrap it up so that when we get closed, we reset the attributes.
+ return deafultPrintStream(stream);
+ }
+
+ private static
+ PrintStream deafultPrintStream(final OutputStream stream) {
+ return new PrintStream(new FilterOutputStream(stream) {
+ @Override
+ public
+ void close() throws IOException {
+ write(AnsiOutputStream.RESET_CODE);
+ flush();
+ super.close();
+ }
+ });
+ }
}
diff --git a/src/dorkbox/console/Input.java b/src/dorkbox/console/Input.java
index f44706f..7848704 100644
--- a/src/dorkbox/console/Input.java
+++ b/src/dorkbox/console/Input.java
@@ -1,15 +1,472 @@
+/*
+ * Copyright 2010 dorkbox, llc
+ *
+ * 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 dorkbox.console;
-/**
- *
- */
-public
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.slf4j.Logger;
+
+import dorkbox.console.input.PosixTerminal;
+import dorkbox.console.input.Terminal;
+import dorkbox.console.input.UnsupportedTerminal;
+import dorkbox.console.input.WindowsTerminal;
+import dorkbox.console.util.CharHolder;
+import dorkbox.objectPool.ObjectPool;
+import dorkbox.objectPool.PoolableObject;
+import dorkbox.util.OS;
+import dorkbox.util.bytes.ByteBuffer2;
+import dorkbox.util.bytes.ByteBuffer2Poolable;
+
class Input {
- public
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Input.class);
+ private static final char[] emptyLine = new char[0];
+
+
+ private static final List charInputBuffers = new ArrayList();
+ private final static ObjectPool charInputPool = ObjectPool.NonBlocking(new PoolableObject() {
+ @Override
+ public
+ CharHolder create() {
+ return new CharHolder();
+ }
+
+ @Override
+ public
+ void onReturn(final CharHolder object) {
+ // dump the chars in the buffer (safer for passwords, etc)
+ object.character = (char) 0;
+ charInputBuffers.remove(object);
+ }
+
+ @Override
+ public
+ void onTake(final CharHolder object) {
+ charInputBuffers.add(object);
+ }
+ });
+
+
+ private static final List lineInputBuffers = new ArrayList();
+ private final static ObjectPool lineInputPool = ObjectPool.NonBlocking(new ByteBuffer2Poolable() {
+ @Override
+ public
+ void onReturn(final ByteBuffer2 object) {
+ // dump the chars in the buffer (safer for passwords, etc)
+ object.clearSecure();
+ lineInputBuffers.remove(object);
+ }
+
+ @Override
+ public
+ void onTake(final ByteBuffer2 object) {
+ lineInputBuffers.add(object);
+ }
+ });
+
+
+
+ private final static Terminal terminal;
+
+ private static final Object inputLock = new Object();
+ private static final Object inputLockSingle = new Object();
+ private static final Object inputLockLine = new Object();
+
+ static {
+ String type = Console.INPUT_CONSOLE_TYPE.toUpperCase(Locale.ENGLISH);
+
+ Throwable didFallbackE = null;
+ Terminal term;
+ try {
+ if (type.equals(Console.UNIX)) {
+ term = new PosixTerminal();
+ }
+ else if (type.equals(Console.WINDOWS)) {
+ term = new WindowsTerminal();
+ }
+ else if (type.equals(Console.NONE)) {
+ term = new UnsupportedTerminal();
+ }
+ else {
+ // if these cannot be created, because we are in an IDE, an error will be thrown
+ if (OS.isWindows()) {
+ term = new WindowsTerminal();
+ }
+ else {
+ term = new PosixTerminal();
+ }
+ }
+ } catch (Exception e) {
+ didFallbackE = e;
+ term = new UnsupportedTerminal();
+ }
+
+ terminal = term;
+
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Created Terminal: {} ({}w x {}h)", Input.terminal.getClass().getSimpleName(),
+ Input.terminal.getWidth(), Input.terminal.getHeight());
+ }
+
+ if (didFallbackE != null && !didFallbackE.getMessage().equals(Terminal.CONSOLE_ERROR_INIT)) {
+ logger.error("Failed to construct terminal, falling back to unsupported.", didFallbackE);
+ } else if (term instanceof UnsupportedTerminal) {
+ logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available.");
+ }
+
+ // echo and backspace
+ term.setEchoEnabled(Console.ENABLE_ECHO);
+ term.setInterruptEnabled(Console.ENABLE_INTERRUPT);
+
+ Thread consoleThread = new Thread(new Runnable() {
+ @Override
+ public
+ void run() {
+ Input.run();
+ }
+ });
+ consoleThread.setDaemon(true);
+ consoleThread.setName("Console Input Reader");
+
+ consoleThread.start();
+
+ // has to be NOT DAEMON thread, since it must run before the app closes.
+
+ // don't forget we have to shut down the ansi console as well
+ // alternatively, shut everything down when the JVM closes.
+ Thread shutdownThread = new Thread() {
+ @Override
+ public
+ void run() {
+ // called when the JVM is shutting down.
+ release0();
+
+ try {
+ terminal.restore();
+ // this will 'hang' our shutdown, and honestly, who cares? We're shutting down anyways.
+ // inputConsole.reader.close(); // hangs on shutdown
+ } catch (IOException ignored) {
+ ignored.printStackTrace();
+ }
+ }
+ };
+ shutdownThread.setName("Console Input Shutdown");
+ Runtime.getRuntime().addShutdownHook(shutdownThread);
+ }
+
+ static
+ void setInterruptEnabled(final boolean enabled) {
+ terminal.setInterruptEnabled(enabled);
+ }
+
+ static
+ void setEchoEnabled(final boolean enabled) {
+ terminal.setEchoEnabled(enabled);
+ }
+
+ private static InputStream wrappedInputStream = new InputStream() {
+ @Override
+ public
+ int read() throws IOException {
+ return Input.read();
+ }
+
+ @Override
+ public
+ void close() throws IOException {
+ Input.release0();
+ }
+ };
+
+ static
+ InputStream getInputStream() {
+ return wrappedInputStream;
+ }
+
+ private
Input() {
}
- public int read() {
- return InputConsole.read();
+ /**
+ * Reads single character input from the console.
+ *
+ * @return -1 if no data or problems
+ */
+ static
+ int read() {
+ CharHolder holder;
+
+ synchronized (inputLock) {
+ // don't want to register a read() WHILE we are still processing the current input.
+ // also adds it to the global list of char inputs
+ holder = charInputPool.take();
+ }
+
+ synchronized (inputLockSingle) {
+ try {
+ inputLockSingle.wait();
+ } catch (InterruptedException e) {
+ return -1;
+ }
+ }
+
+ char c = holder.character;
+
+ // also clears and removes from the global list of char inputs
+ charInputPool.put(holder);
+
+ return c;
+ }
+
+ /**
+ * @return empty char[] if no data or problems
+ */
+ static
+ char[] readLineChars() {
+ ByteBuffer2 buffer;
+
+ synchronized (inputLock) {
+ // don't want to register a readLine() WHILE we are still processing the current line info.
+ // also adds it to the global list of line inputs
+ buffer = lineInputPool.take();
+ }
+
+ synchronized (inputLockLine) {
+ try {
+ inputLockLine.wait();
+ } catch (InterruptedException e) {
+ return emptyLine;
+ }
+ }
+
+ int len = buffer.position();
+ if (len == 0) {
+ return emptyLine;
+ }
+
+ buffer.rewind();
+ char[] readChars = buffer.readChars(len / 2); // java always stores chars in 2 bytes
+
+ // also clears and removes from the global list of line inputs
+ lineInputPool.put(buffer);
+
+ return readChars;
+ }
+
+ /**
+ * Reads line input from the console
+ *
+ * @return empty char[] if no data
+ */
+ static
+ char[] readLinePassword() {
+ // don't bother in an IDE. it won't work.
+ boolean echoEnabled = Console.ENABLE_ECHO;
+ Console.ENABLE_ECHO = false;
+ char[] readLine0 = readLineChars();
+ Console.ENABLE_ECHO = echoEnabled;
+
+ return readLine0;
+ }
+
+ /**
+ * Reads a single line of characters, defined as everything before the 'ENTER' key is pressed
+ * @return null if no data
+ */
+ static
+ String readLine() {
+ char[] line = Input.readLineChars();
+ if (line == null) {
+ return null;
+ }
+ return new String(line);
+ }
+
+
+
+ /**
+ * releases any thread still waiting.
+ */
+ private static
+ void release0() {
+ synchronized (inputLockSingle) {
+ inputLockSingle.notifyAll();
+ }
+
+ synchronized (inputLockLine) {
+ inputLockLine.notifyAll();
+ }
+ }
+
+ private static
+ void run() {
+ Logger logger2 = logger;
+
+ final boolean ansiEnabled = Console.ENABLE_ANSI;
+// Ansi ansi = Ansi.ansi();
+// PrintStream out = AnsiConsole.out;
+ PrintStream out = System.out;
+
+ int typedChar;
+ char asChar;
+ final char overWriteChar = ' ';
+ boolean enableBackspace = Console.ENABLE_BACKSPACE;
+
+
+ // don't type ; in a bash shell, it quits everything
+ // \n is replaced by \r in unix terminal?
+ while ((typedChar = terminal.read()) != -1) {
+ synchronized (inputLock) {
+ // don't let anyone add a new reader while we are still processing the current actions
+ asChar = (char) typedChar;
+
+ if (logger2.isTraceEnabled()) {
+ logger2.trace("READ: {} ({})", asChar, typedChar);
+ }
+
+ // notify everyone waiting for a character.
+ synchronized (inputLockSingle) {
+ // have to do readChar first (readLine has to deal with \b and \n
+ for (CharHolder holder : charInputBuffers) {
+ holder.character = asChar; // copy by value
+ }
+
+ inputLockSingle.notifyAll();
+ }
+
+ // now to handle readLine stuff
+
+ // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed anyways.
+ if (enableBackspace && asChar == '\b') {
+ int position = 0;
+ char[] overwrite = null;
+
+ // clear ourself + one extra.
+ for (ByteBuffer2 buffer : lineInputBuffers) {
+ // size of the buffer BEFORE our backspace was typed
+ int length = buffer.position();
+ int amtToOverwrite = 4; // 2*2 backspace is always 2 chars (^?) * 2 because it's bytes
+
+ if (length > 1) {
+ char charAt = buffer.readChar(length - 2);
+ amtToOverwrite += getPrintableCharacters(charAt);
+
+ // delete last item in our buffer
+ length -= 2;
+ buffer.setPosition(length);
+
+ // now figure out where the cursor is really at.
+ // this is more memory friendly than buf.toString.length
+ for (int i = 0; i < length; i += 2) {
+ charAt = buffer.readChar(i);
+ position += getPrintableCharacters(charAt);
+ }
+
+ position++;
+ }
+
+ overwrite = new char[amtToOverwrite];
+ for (int i = 0; i < amtToOverwrite; i++) {
+ overwrite[i] = overWriteChar;
+ }
+ }
+
+ if (ansiEnabled && overwrite != null) {
+ // move back however many, over write, then go back again
+// out.print(ansi.cursorToColumn(position));
+ out.print(overwrite);
+// out.print(ansi.cursorToColumn(position));
+ out.flush();
+ }
+ }
+ else if (asChar == '\n') {
+ // ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows, which we changed)
+ synchronized (inputLockLine) {
+ inputLockLine.notifyAll();
+ }
+ }
+ else {
+ // only append if we are not a new line.
+ // our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n')
+ for (ByteBuffer2 buffer : lineInputBuffers) {
+ buffer.writeChar(asChar);
+ }
+ }
+ }
+ }
+ }
+
+ private static final int PLUS_TWO_MAYBE = 128 + 32;
+ private static final int PLUS_ONE = 128 + 127;
+
+ /**
+ * Return the number of characters that will be printed when the specified character is echoed to the screen
+ *
+ * Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie.
+ */
+ private static
+ int getPrintableCharacters(final int ch) {
+ // StringBuilder sbuff = new StringBuilder();
+
+ if (ch >= 32) {
+ if (ch < 127) {
+ // sbuff.append((char) ch);
+ return 1;
+ }
+ else if (ch == 127) {
+ // sbuff.append('^');
+ // sbuff.append('?');
+ return 2;
+ }
+ else {
+ // sbuff.append('M');
+ // sbuff.append('-');
+ int count = 2;
+
+ if (ch >= PLUS_TWO_MAYBE) {
+ if (ch < PLUS_ONE) {
+ // sbuff.append((char) (ch - 128));
+ count++;
+ }
+ else {
+ // sbuff.append('^');
+ // sbuff.append('?');
+ count += 2;
+ }
+ }
+ else {
+ // sbuff.append('^');
+ // sbuff.append((char) (ch - 128 + 64));
+ count += 2;
+ }
+ return count;
+ }
+ }
+ else {
+ // sbuff.append('^');
+ // sbuff.append((char) (ch + 64));
+ return 2;
+ }
+
+ // return sbuff;
}
}
+
diff --git a/src/dorkbox/console/InputConsole.java b/src/dorkbox/console/InputConsole.java
deleted file mode 100644
index 66a7ecb..0000000
--- a/src/dorkbox/console/InputConsole.java
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- * Copyright 2010 dorkbox, llc
- *
- * 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 dorkbox.console;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import org.slf4j.Logger;
-
-import dorkbox.console.input.PosixTerminal;
-import dorkbox.console.input.Terminal;
-import dorkbox.console.input.UnsupportedTerminal;
-import dorkbox.console.input.WindowsTerminal;
-import dorkbox.console.output.Ansi;
-import dorkbox.objectPool.ObjectPool;
-import dorkbox.util.FastThreadLocal;
-import dorkbox.util.OS;
-import dorkbox.util.bytes.ByteBuffer2;
-import dorkbox.util.bytes.ByteBuffer2Poolable;
-
-public
-class InputConsole {
-
- private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class);
- private static final char[] emptyLine = new char[0];
-
-
- private final static ObjectPool pool;
- private final static Terminal terminal;
-
- private static final Object inputLock = new Object();
- private static final Object inputLockSingle = new Object();
- private static final Object inputLockLine = new Object();
-
- private static final FastThreadLocal readBuff = new FastThreadLocal();
- private static final List readBuffers = new CopyOnWriteArrayList();
- private static final FastThreadLocal threadBufferCounter = new FastThreadLocal();
-
- private static final FastThreadLocal readLineBuff = new FastThreadLocal();
- private static final List readLineBuffers = new CopyOnWriteArrayList();
-
-
-
- static {
- pool = ObjectPool.Blocking(new ByteBuffer2Poolable(), Console.NUMBER_OF_READERS);
-
- String type = Console.INPUT_CONSOLE_OSTYPE.toUpperCase(Locale.ENGLISH);
-
- Throwable didFallbackE = null;
- Class extends Terminal> t;
- try {
- if (type.equals(Console.UNIX)) {
- t = PosixTerminal.class;
- }
- else if (type.equals(Console.WIN) || type.equals(Console.WINDOWS)) {
- t = WindowsTerminal.class;
- }
- else if (type.equals(Console.NONE) || type.equals(Console.OFF) || type.equals(Console.FALSE)) {
- t = UnsupportedTerminal.class;
- }
- else {
- // if these cannot be created, because we are in an IDE, an error will be thrown
- if (OS.isWindows()) {
- t = WindowsTerminal.class;
- }
- else {
- t = PosixTerminal.class;
- }
- }
- } catch (Exception e) {
- didFallbackE = e;
- t = UnsupportedTerminal.class;
- }
-
- Terminal term = null;
- try {
- term = t.newInstance();
- } catch (Throwable e) {
- didFallbackE = e;
- t = UnsupportedTerminal.class;
-
- try {
- term = t.newInstance();
- } catch (Exception e1) {
- // UnsupportedTerminal can't do this
- }
- }
- terminal = term;
-
-
- if (logger.isTraceEnabled()) {
- logger.trace("Creating terminal based on type: " + type);
- }
-
- if (logger.isDebugEnabled()) {
- logger.debug("Created Terminal: {} ({}w x {}h)", InputConsole.terminal.getClass().getSimpleName(),
- InputConsole.terminal.getWidth(), InputConsole.terminal.getHeight());
- }
-
- if (didFallbackE != null && !didFallbackE.getMessage().equals(Terminal.CONSOLE_ERROR_INIT)) {
- logger.error("Failed to construct terminal, falling back to unsupported.", didFallbackE);
- } else if (term instanceof UnsupportedTerminal) {
- logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available.");
- }
-
- // echo and backspace
- term.setEchoEnabled(Console.ENABLE_ECHO);
- term.setInterruptEnabled(Console.ENABLE_BACKSPACE);
-
- Thread consoleThread = new Thread(new Runnable() {
- @Override
- public
- void run() {
- InputConsole.run();
- }
- });
- consoleThread.setDaemon(true);
- consoleThread.setName("Console Input Reader");
-
- consoleThread.start();
-
- // has to be NOT DAEMON thread, since it must run before the app closes.
-
- // don't forget we have to shut down the ansi console as well
- // alternatively, shut everything down when the JVM closes.
- Thread shutdownThread = new Thread() {
- @Override
- public
- void run() {
- // called when the JVM is shutting down.
- release0();
-
- try {
- terminal.restore();
- // this will 'hang' our shutdown, and honestly, who cares? We're shutting down anyways.
- // inputConsole.reader.close(); // hangs on shutdown
- } catch (IOException ignored) {
- ignored.printStackTrace();
- }
- }
- };
- shutdownThread.setName("Console Input Shutdown");
- Runtime.getRuntime().addShutdownHook(shutdownThread);
- }
-
- private static InputStream wrappedInputStream = new InputStream() {
- @Override
- public
- int read() throws IOException {
- return InputConsole.read();
- }
-
- @Override
- public
- void close() throws IOException {
- InputConsole.release0();
- }
- };
-
- public static
- InputStream getInputStream() {
- return wrappedInputStream;
- }
-
- private
- InputConsole() {
- }
-
- /**
- * return -1 if no data or bunged-up
- */
- public static
- int read() {
- Integer bufferCounter = threadBufferCounter.get();
- ByteBuffer2 buffer = readBuff.get();
-
- if (buffer == null) {
- bufferCounter = 0;
- threadBufferCounter.set(bufferCounter);
-
- try {
- buffer = pool.takeInterruptibly();
- buffer.clear();
- } catch (InterruptedException e) {
- logger.error("Interrupted while receiving buffer from pool.");
- buffer = pool.newInstance();
- }
-
- readBuff.set(buffer);
- readBuffers.add(buffer);
- }
-
- if (bufferCounter == buffer.position()) {
- synchronized (inputLockSingle) {
- buffer.setPosition(0);
- threadBufferCounter.set(0);
-
- try {
- inputLockSingle.wait();
- } catch (InterruptedException e) {
- return -1;
- }
- }
- }
-
- bufferCounter = threadBufferCounter.get();
- char c = buffer.readChar(bufferCounter);
- bufferCounter += 2;
-
- threadBufferCounter.set(bufferCounter);
- return c;
- }
-
- /**
- * return empty char[] if no data
- */
- public static
- char[] readLinePassword() {
- // don't bother in an IDE. it won't work.
- boolean echoEnabled = Console.ENABLE_ECHO;
- Console.ENABLE_ECHO = false;
- char[] readLine0 = readLineChars();
- Console.ENABLE_ECHO = echoEnabled;
-
- return readLine0;
- }
-
- /**
- * return null if no data
- */
- public static
- String readLine() {
- char[] line = InputConsole.readLineChars();
- if (line == null) {
- return null;
- }
- return new String(line);
- }
-
- /**
- * return empty char[] if no data
- */
- public static
- char[] readLineChars() {
- synchronized (inputLock) {
- // empty here, because we don't want to register a readLine WHILE we are still processing
- // the current line info.
-
- // the threadBufferForRead getting added is the part that is important
- if (readLineBuff.get() == null) {
- ByteBuffer2 buffer;
- try {
- buffer = pool.takeInterruptibly();
- } catch (InterruptedException e) {
- logger.error("Interrupted while receiving buffer from pool.");
- buffer = pool.newInstance();
- }
-
- readLineBuff.set(buffer);
- readLineBuffers.add(buffer);
- }
- else {
- readLineBuff.get().clear();
- }
- }
-
- synchronized (inputLockLine) {
- try {
- inputLockLine.wait();
- } catch (InterruptedException e) {
- return emptyLine;
- }
- }
-
- ByteBuffer2 buffer = readLineBuff.get();
- int len = buffer.position();
- if (len == 0) {
- return emptyLine;
- }
-
- buffer.rewind();
- char[] readChars = buffer.readChars(len / 2); // java always stores chars in 2 bytes
-
- // dump the chars in the buffer (safer for passwords, etc)
- buffer.clearSecure();
-
- readLineBuffers.remove(buffer);
- pool.put(buffer);
- readLineBuff.set(null);
-
- return readChars;
- }
-
- /**
- * releases any thread still waiting.
- */
- private static
- void release0() {
- synchronized (inputLockSingle) {
- inputLockSingle.notifyAll();
- }
-
- synchronized (inputLockLine) {
- inputLockLine.notifyAll();
- }
- }
-
- private static
- void run() {
- Logger logger2 = logger;
-
- final boolean ansiEnabled = Ansi.isEnabled();
- Ansi ansi = Ansi.ansi();
-// PrintStream out = AnsiConsole.out;
- PrintStream out = System.out;
-
- int typedChar;
- char asChar;
- final char overWriteChar = ' ';
- boolean enableBackspace = Console.ENABLE_BACKSPACE;
-
-
- // don't type ; in a bash shell, it quits everything
- // \n is replaced by \r in unix terminal?
- while ((typedChar = terminal.read()) != -1) {
- synchronized (inputLock) {
- // don't let anyone add a new reader while we are still processing the current actions
- asChar = (char) typedChar;
-
- if (logger2.isTraceEnabled()) {
- logger2.trace("READ: {} ({})", asChar, typedChar);
- }
-
- // notify everyone waiting for a character.
- synchronized (inputLockSingle) {
- // have to do readChar first (readLine has to deal with \b and \n
- for (ByteBuffer2 buffer : readBuffers) {
- buffer.writeChar(asChar);
- }
-
- inputLockSingle.notifyAll();
- }
-
- // now to handle readLine stuff
-
- // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed anyways.
- if (enableBackspace && asChar == '\b') {
- int position = 0;
- char[] overwrite = null;
-
- // clear ourself + one extra.
- for (ByteBuffer2 buffer : readLineBuffers) {
- // size of the buffer BEFORE our backspace was typed
- int length = buffer.position();
- int amtToOverwrite = 4; // 2*2 backspace is always 2 chars (^?) * 2 because it's bytes
-
- if (length > 1) {
- char charAt = buffer.readChar(length - 2);
- amtToOverwrite += getPrintableCharacters(charAt);
-
- // delete last item in our buffer
- length -= 2;
- buffer.setPosition(length);
-
- // now figure out where the cursor is really at.
- // this is more memory friendly than buf.toString.length
- for (int i = 0; i < length; i += 2) {
- charAt = buffer.readChar(i);
- position += getPrintableCharacters(charAt);
- }
-
- position++;
- }
-
- overwrite = new char[amtToOverwrite];
- for (int i = 0; i < amtToOverwrite; i++) {
- overwrite[i] = overWriteChar;
- }
- }
-
- if (ansiEnabled && overwrite != null) {
- // move back however many, over write, then go back again
- out.print(ansi.cursorToColumn(position));
- out.print(overwrite);
- out.print(ansi.cursorToColumn(position));
- out.flush();
- }
- }
- else if (asChar == '\n') {
- // ignoring \r, because \n is ALWAYS the last character in a new line sequence. (even for windows)
- synchronized (inputLockLine) {
- inputLockLine.notifyAll();
- }
- }
- else {
- // only append if we are not a new line.
- // our windows console PREVENTS us from returning '\r' (it truncates '\r\n', and returns just '\n')
- for (ByteBuffer2 buffer : readLineBuffers) {
- buffer.writeChar(asChar);
- }
- }
- }
- }
- }
-
- private static final int PLUS_TWO_MAYBE = 128 + 32;
- private static final int PLUS_ONE = 128 + 127;
-
- /**
- * Return the number of characters that will be printed when the specified character is echoed to the screen
- *
- * Adapted from cat by Torbjorn Granlund, as repeated in stty by David MacKenzie.
- */
- private static
- int getPrintableCharacters(final int ch) {
- // StringBuilder sbuff = new StringBuilder();
-
- if (ch >= 32) {
- if (ch < 127) {
- // sbuff.append((char) ch);
- return 1;
- }
- else if (ch == 127) {
- // sbuff.append('^');
- // sbuff.append('?');
- return 2;
- }
- else {
- // sbuff.append('M');
- // sbuff.append('-');
- int count = 2;
-
- if (ch >= PLUS_TWO_MAYBE) {
- if (ch < PLUS_ONE) {
- // sbuff.append((char) (ch - 128));
- count++;
- }
- else {
- // sbuff.append('^');
- // sbuff.append('?');
- count += 2;
- }
- }
- else {
- // sbuff.append('^');
- // sbuff.append((char) (ch - 128 + 64));
- count += 2;
- }
- return count;
- }
- }
- else {
- // sbuff.append('^');
- // sbuff.append((char) (ch + 64));
- return 2;
- }
-
- // return sbuff;
- }
-}
-
diff --git a/src/dorkbox/console/output/Ansi.java b/src/dorkbox/console/output/Ansi.java
index b0d49df..65d76ef 100644
--- a/src/dorkbox/console/output/Ansi.java
+++ b/src/dorkbox/console/output/Ansi.java
@@ -1,20 +1,4 @@
-/**
- * Copyright (C) 2009, Progress Software Corporation and/or its
- * subsidiaries or affiliates. All rights reserved.
- *
- * 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 asValue 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.
- *
- *
+/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,313 +12,93 @@
* 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.
+ *
+ *
+ * Copyright (C) 2009 the original author(s).
+ *
+ * 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 dorkbox.console.output;
+import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_RESET;
+
import java.util.ArrayList;
-import java.util.concurrent.Callable;
+
+import dorkbox.console.Console;
/**
- * Provides a fluent API for generating ANSI escape sequences.
- *
+ * Provides a fluent API for generating ANSI escape sequences and providing access to streams that support it.
+ *
* See: https://en.wikipedia.org/wiki/ANSI_escape_code
*
- * @author Dorkbox, LLC
+ * @author dorkbox, llc
* @author Hiram Chirino
*/
-@SuppressWarnings({"WeakerAccess", "unused"})
-public class Ansi {
+@SuppressWarnings("unused")
+public
+class Ansi {
+ static {
+ // make SURE that our console in/out/err are correctly setup BEFORE accessing methods in this class
+ Console.getVersion();
+ }
private static final String NEW_LINE = System.getProperty("line.separator");
- private static final char FIRST_ESC_CHAR = 27;
- private static final char SECOND_ESC_CHAR = '[';
-
- private static int installed;
+ private final StringBuilder builder;
+ private final ArrayList attributeOptions = new ArrayList(8);
/**
- * Override System.err and System.out with an ANSI capable {@link java.io.PrintStream}.
+ * Creates a new Ansi object
*/
- public static synchronized
- void systemInstall() {
- installed++;
- if (installed == 1) {
- System.setOut(AnsiConsole.out);
- System.setErr(AnsiConsole.err);
- }
+ public static
+ Ansi ansi() {
+ return new Ansi();
}
/**
- * un-does a previous {@link #systemInstall()}.
- *
- * If {@link #systemInstall()} was called multiple times, then {@link #systemUninstall()} must be called the same number of
- * times before it is uninstalled.
- */
- public static synchronized
- void systemUninstall() {
- installed--;
- if (installed == 0) {
- if (AnsiConsole.out != AnsiConsole.system_out) {
- AnsiConsole.out.close();
- }
-
- if (AnsiConsole.err != AnsiConsole.system_err) {
- AnsiConsole.err.close();
- }
-
- System.setOut(AnsiConsole.system_out);
- System.setErr(AnsiConsole.system_err);
- }
- }
-
- public static final String DISABLE = Ansi.class.getName() + ".disable";
-
- private static Callable detector = new Callable() {
- @Override
- public Boolean call() throws Exception {
- return !Boolean.getBoolean(DISABLE);
- }
- };
-
- public static void setDetector(final Callable detector) {
- if (detector == null) {
- throw new IllegalArgumentException();
- }
- Ansi.detector = detector;
- }
-
- public static boolean isDetected() {
- try {
- return detector.call();
- }
- catch (Exception e) {
- return true;
- }
- }
-
- private static final InheritableThreadLocal holder = new InheritableThreadLocal()
- {
- @Override
- protected Boolean initialValue() {
- return isDetected();
- }
- };
-
- public static void setEnabled(final boolean flag) {
- holder.set(flag);
- }
-
- public static boolean isEnabled() {
- return holder.get();
- }
-
- private static class NoAnsi extends Ansi
- {
- public
- NoAnsi() {
- super();
- }
-
- public
- NoAnsi(final StringBuilder builder) {
- super(builder);
- }
-
- public
- NoAnsi(final int size) {
- super(size);
- }
-
- @Override
- public Ansi fg(Color color) {
- return this;
- }
-
- @Override
- public Ansi bg(Color color) {
- return this;
- }
-
- @Override
- public Ansi fgBright(Color color) {
- return this;
- }
-
- @Override
- public Ansi bgBright(Color color) {
- return this;
- }
-
- @Override
- public Ansi fgBrightDefault() { return this; }
-
- @Override
- public Ansi bgBrightDefault() { return this; }
-
- @Override
- public Ansi fgDefault() { return this; }
-
- @Override
- public Ansi bgDefault() { return this; }
-
-
- @Override
- public Ansi a(Attribute attribute) {
- return this;
- }
-
- @Override
- public Ansi cursor(int x, int y) {
- return this;
- }
-
- @Override
- public Ansi cursorToColumn(int x) {
- return this;
- }
-
- @Override
- public Ansi cursorUp(int y) {
- return this;
- }
-
- @Override
- public Ansi cursorRight(int x) {
- return this;
- }
-
- @Override
- public Ansi cursorDown(int y) {
- return this;
- }
-
- @Override
- public Ansi cursorLeft(int x) {
- return this;
- }
-
- @Override
- public Ansi cursorDownLine() {
- return this;
- }
-
- @Override
- public Ansi cursorDownLine(final int n) {
- return this;
- }
-
- @Override
- public Ansi cursorUpLine() {
- return this;
- }
-
- @Override
- public Ansi cursorUpLine(final int n) {
- return this;
- }
-
- @Override
- public Ansi eraseScreen() {
- return this;
- }
-
- @Override
- public Ansi eraseScreen(Erase kind) {
- return this;
- }
-
- @Override
- public Ansi eraseLine() {
- return this;
- }
-
- @Override
- public Ansi eraseLine(Erase kind) {
- return this;
- }
-
- @Override
- public Ansi scrollUp(int rows) {
- return this;
- }
-
- @Override
- public Ansi scrollDown(int rows) {
- return this;
- }
-
- @Override
- public Ansi saveCursorPosition() {
- return this;
- }
-
- @Override
- public Ansi restoreCursorPosition() {
- return this;
- }
-
- @Override
- public Ansi reset() {
- return this;
- }
- }
-
- /**
- * Creates a new Ansi object and resets the output to the default.
- */
- public static Ansi ansi() {
- if (isEnabled()) {
- return new Ansi();
- }
- else {
- return new NoAnsi();
- }
- }
-
- /**
- * Creates a new Ansi object from the specified StringBuilder. This does NOT reset the output back to default.
+ * Creates a new Ansi object from the specified StringBuilder
*/
public static
Ansi ansi(StringBuilder builder) {
- if (isEnabled()) {
- return new Ansi(builder);
- }
- else {
- return new NoAnsi(builder);
- }
+ return new Ansi(builder);
}
/**
- * Creates a new Ansi object of the specified length and reset the output back to default.
+ * Creates a new Ansi object of the specified length
*/
public static
Ansi ansi(int size) {
-
- if (isEnabled()) {
- return new Ansi(size);
- }
- else {
- return new NoAnsi(size);
- }
+ return new Ansi(size);
}
-
-
-
- private final StringBuilder builder;
- private final ArrayList attributeOptions = new ArrayList(5);
+ /**
+ * Creates a new Ansi object from the specified parent
+ */
+ public static
+ Ansi ansi(Ansi ansi) {
+ return new Ansi(ansi);
+ }
/**
- * Creates a new Ansi object and resets the output to the default.
+ * Creates a new Ansi object
*/
public
Ansi() {
this(new StringBuilder());
- reset(); // always reset a NEW Ansi object (w/ no parent)
}
/**
- * Creates a new Ansi object from the parent. This does NOT reset the output back to default.
+ * Creates a new Ansi object from the parent.
*/
public
Ansi(Ansi parent) {
@@ -343,7 +107,7 @@ public class Ansi {
}
/**
- * Creates a new Ansi object of the specified length and reset the output back to default.
+ * Creates a new Ansi object of the specified length
*/
public
Ansi(int size) {
@@ -352,233 +116,191 @@ public class Ansi {
}
/**
- * Creates a new Ansi object from the specified StringBuilder. This does NOT reset the output back to default.
+ * Creates a new Ansi object from the specified StringBuilder
*/
public
Ansi(StringBuilder builder) {
this.builder = builder;
- // don't know if there is a parent or not, so we don't reset()
}
+ /**
+ * Sets the foreground color of the ANSI output. BRIGHT_* colors are a brighter version of that color. DEFAULT is the color from
+ * the beginning before any other color was applied.
+ *
+ * @param color foreground color to set
+ */
public
Ansi fg(Color color) {
- attributeOptions.add(color.fg());
- return this;
- }
+ if (color.isNormal()) {
+ if (color != Color.DEFAULT) {
+ attributeOptions.add(color.fg());
+ }
+ else {
+ attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_FG);
+ }
+ }
+ else {
+ if (color != Color.BRIGHT_DEFAULT) {
+ attributeOptions.add(color.fgBright());
+ }
+ else {
+ attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_FG);
+ attributeOptions.add(AnsiOutputStream.ATTRIBUTE_BOLD);
+ }
+ }
- public
- Ansi fgDefault() {
- attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_FG);
- return this;
- }
-
- public
- Ansi fgBlack() {
- return this.fg(Color.BLACK);
- }
-
- public
- Ansi fgBlue() {
- return this.fg(Color.BLUE);
- }
-
- public
- Ansi fgCyan() {
- return this.fg(Color.CYAN);
- }
-
- public
- Ansi fgGreen() {
- return this.fg(Color.GREEN);
- }
-
- public
- Ansi fgMagenta() {
- return this.fg(Color.MAGENTA);
- }
-
- public
- Ansi fgRed() {
- return this.fg(Color.RED);
- }
-
- public
- Ansi fgYellow() {
- return this.fg(Color.YELLOW);
- }
-
- public
- Ansi fgWhite() {
- return this.fg(Color.WHITE);
- }
-
- public
- Ansi fgBright(Color color) {
- attributeOptions.add(color.fgBright());
- return this;
- }
-
- public
- Ansi fgBrightDefault() {
- attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_FG);
- attributeOptions.add(AnsiOutputStream.ATTRIBUTE_BOLD);
- return this;
- }
-
- public
- Ansi fgBrightBlack() {
- return this.fgBright(Color.BLACK);
- }
-
- public
- Ansi fgBrightBlue() {
- return this.fgBright(Color.BLUE);
- }
-
- public
- Ansi fgBrightCyan() {
- return this.fgBright(Color.CYAN);
- }
-
- public
- Ansi fgBrightGreen() {
- return this.fgBright(Color.GREEN);
- }
-
- public
- Ansi fgBrightMagenta() {
- return this.fgBright(Color.MAGENTA);
- }
-
- public
- Ansi fgBrightRed() {
- return this.fgBright(Color.RED);
- }
-
- public
- Ansi fgBrightYellow() {
- return this.fgBright(Color.YELLOW);
- }
-
- public
- Ansi fgBrightWhite() {
- return this.fgBright(Color.WHITE);
- }
-
-
- public
- Ansi bg(Color color) {
- attributeOptions.add(color.bg());
- return this;
- }
-
- public
- Ansi bgDefault() {
- attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_BG);
- return this;
- }
-
- public
- Ansi bgBlack() {
- return this.bg(Color.BLACK);
- }
-
- public
- Ansi bgBlue() {
- return this.bg(Color.BLUE);
- }
-
- public
- Ansi bgCyan() {
- return this.bg(Color.CYAN);
- }
-
- public
- Ansi bgGreen() {
- return this.bg(Color.GREEN);
- }
-
- public
- Ansi bgMagenta() {
- return this.bg(Color.MAGENTA);
- }
-
- public
- Ansi bgRed() {
- return this.bg(Color.RED);
- }
-
- public
- Ansi bgYellow() {
- return this.bg(Color.YELLOW);
- }
-
- public
- Ansi bgWhite() {
- return this.bg(Color.WHITE);
- }
-
- public
- Ansi bgBright(Color color) {
- attributeOptions.add(color.bgBright());
- return this;
- }
-
- public
- Ansi bgBrightDefault() {
- attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_BG);
- attributeOptions.add(AnsiOutputStream.ATTRIBUTE_BOLD);
- return this;
- }
-
-
- public
- Ansi bgBrightBlack() {
- return this.bgBright(Color.BLACK);
- }
-
- public
- Ansi bgBrightBlue() {
- return this.bgBright(Color.BLUE);
- }
-
- public
- Ansi bgBrightCyan() {
- return this.bgBright(Color.CYAN);
- }
-
- public
- Ansi bgBrightGreen() {
- return this.bgBright(Color.GREEN);
- }
-
- public
- Ansi bgBrightMagenta() {
- return this.bgBright(Color.MAGENTA);
- }
-
- public
- Ansi bgBrightRed() {
- return this.bgBright(Color.RED);
- }
-
- public
- Ansi bgBrightYellow() {
- return this.bgBright(Color.YELLOW);
- }
-
- public
- Ansi bgBrightWhite() {
- return this.bgBright(Color.WHITE);
- }
-
- public
- Ansi a(Attribute attribute) {
- attributeOptions.add(attribute.value());
return this;
}
/**
+ * Sets the background color of the ANSI output. BRIGHT_* colors are a brighter version of that color. DEFAULT is the color from
+ * the beginning before any other color was applied.
+ *
+ * @param color background color to set
+ */
+ public
+ Ansi bg(Color color) {
+ if (color.isNormal()) {
+ if (color != Color.DEFAULT) {
+ attributeOptions.add(color.bg());
+ }
+ else {
+ attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_BG);
+ }
+ }
+ else {
+ if (color != Color.BRIGHT_DEFAULT) {
+ attributeOptions.add(color.bgBright());
+ }
+ else {
+ attributeOptions.add(AnsiOutputStream.ATTRIBUTE_DEFAULT_BG);
+ attributeOptions.add(AnsiOutputStream.ATTRIBUTE_BOLD);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Moves the cursor y (default 1) cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
+ */
+ public
+ Ansi cursorUp(final int y) {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_UP, y);
+ }
+
+ /**
+ * Moves the cursor y (default 1) cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
+ */
+ public
+ Ansi cursorDown(final int y) {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN, y);
+ }
+
+ /**
+ * Moves the cursor x (default 1) cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
+ */
+ public
+ Ansi cursorRight(final int x) {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_FORWARD, x);
+ }
+
+ /**
+ * Moves the cursor x (default 1) cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
+ */
+ public
+ Ansi cursorLeft(final int x) {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_BACK, x);
+ }
+
+
+ /**
+ * Moves the cursor 1 cell cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
+ */
+ public
+ Ansi cursorUp() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_UP);
+ }
+
+ /**
+ * Moves the cursor 1 cell cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
+ */
+ public
+ Ansi cursorDown() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN);
+ }
+
+ /**
+ * Moves the cursor 1 cell cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
+ */
+ public
+ Ansi cursorRight() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_FORWARD);
+ }
+
+ /**
+ * Moves the cursor 1 cell cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
+ */
+ public
+ Ansi cursorLeft() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_BACK);
+ }
+
+ /**
+ * Moves cursor to beginning of the line n (default 1) lines down.
+ */
+ public
+ Ansi cursorDownLine(final int n) {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN_LINE, n);
+ }
+
+ /**
+ * Moves cursor to beginning of the line n (default 1) lines up.
+ */
+ public
+ Ansi cursorUpLine(final int n) {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_UP_LINE, n);
+ }
+
+
+ /**
+ * Moves cursor to beginning of the line 1 line down.
+ */
+ public
+ Ansi cursorDownLine() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN_LINE);
+ }
+
+
+ /**
+ * Moves cursor to beginning of the line 1 lines up.
+ */
+ public
+ Ansi cursorUpLine() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_UP_LINE);
+ }
+
+ /**
+ * Moves the cursor to column n (default 1).
+ * @param n is 1 indexed (the very first value is 1, not 0)
+ */
+ public
+ Ansi cursorToColumn(final int n) {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_TO_COL, n);
+ }
+
+ /**
+ * Moves the cursor to column 1. The very first value is 1, not 0.
+ */
+ public
+ Ansi cursorToColumn() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_TO_COL);
+ }
+
+ /**
+ * Moves the cursor to row x, column y.
+ *
+ * The values are 1-based, the top left corner.
+ *
* @param x is 1 indexed (the very first value is 1, not 0)
* @param y is 1 indexed (the very first value is 1, not 0)
*/
@@ -588,188 +310,279 @@ public class Ansi {
}
/**
- * @param x is 1 indexed (the very first value is 1, not 0)
+ * Moves the cursor to row 1, column 1 (top left corner).
*/
public
- Ansi cursorToColumn(final int x) {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_TO_COL, x);
+ Ansi cursor() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_POS);
}
+ /**
+ * Clears part of the screen, by default clear everything forwards of the cursor
+ *
+ * @param kind
+ * - {@link Erase#FORWARD} (or missing), clear from cursor to end of screen.
+ * - {@link Erase#BACKWARD}, clear from cursor to beginning of the screen.
+ * - {@link Erase#ALL}, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS).
+ */
public
- Ansi cursorUp(final int y) {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_UP, y);
- }
-
- public
- Ansi cursorDown(final int y) {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN, y);
- }
-
- public
- Ansi cursorRight(final int x) {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_RIGHT, x);
- }
-
- public
- Ansi cursorLeft(final int x) {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_LEFT, x);
- }
-
- public
- Ansi cursorDownLine() {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN_LINE);
- }
-
- public
- Ansi cursorDownLine(final int n) {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_DOWN_LINE, n);
- }
-
- public
- Ansi cursorUpLine() {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_UP_LINE);
- }
-
- public
- Ansi cursorUpLine(final int n) {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_UP_LINE, n);
+ Ansi eraseScreen(final Erase kind) {
+ if (kind == null) {
+ return eraseScreen();
+ }
+
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_SCREEN, kind.value());
}
+ /**
+ * Clears everything forwards of the cursor
+ */
public
Ansi eraseScreen() {
return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_SCREEN, Erase.ALL.value());
}
- public
- Ansi eraseScreen(final Erase kind) {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_SCREEN, kind.value());
- }
-
- public
- Ansi eraseLine() {
- return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_LINE);
- }
-
+ /**
+ * Erases part of the line.
+ *
+ * @param kind
+ * - {@link Erase#FORWARD} (or missing), clear from cursor to the end of the line.
+ * - {@link Erase#BACKWARD}, clear from cursor to beginning of the line.
+ * - {@link Erase#ALL}, clear entire line. Cursor position does not change.
+ */
public
Ansi eraseLine(final Erase kind) {
return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_LINE, kind.value());
}
+ /**
+ * Erases from cursor to the end of the line.
+ */
public
- Ansi scrollUp(final int rows) {
- return appendEscapeSequence(AnsiOutputStream.PAGE_SCROLL_UP, rows);
+ Ansi eraseLine() {
+ return appendEscapeSequence(AnsiOutputStream.CURSOR_ERASE_LINE);
}
+
+
+ /**
+ * Scroll whole page up by n (default 1) lines. New lines are added at the bottom.
+ */
public
- Ansi scrollDown(final int rows) {
- return appendEscapeSequence(AnsiOutputStream.PAGE_SCROLL_DOWN, rows);
+ Ansi scrollUp(final int n) {
+ return appendEscapeSequence(AnsiOutputStream.SCROLL_UP, n);
}
+ /**
+ * Scroll whole page up by 1 line. New lines are added at the bottom.
+ */
+ public
+ Ansi scrollUp() {
+ return appendEscapeSequence(AnsiOutputStream.SCROLL_UP);
+ }
+
+ /**
+ * Scroll whole page down by n (default 1) lines. New lines are added at the top.
+ */
+ public
+ Ansi scrollDown(final int n) {
+ return appendEscapeSequence(AnsiOutputStream.SCROLL_DOWN, n);
+ }
+
+ /**
+ * Scroll whole page down by 1 line. New lines are added at the top.
+ */
+ public
+ Ansi scrollDown() {
+ return appendEscapeSequence(AnsiOutputStream.SCROLL_DOWN);
+ }
+
+ /**
+ * Saves the cursor position.
+ */
public
Ansi saveCursorPosition() {
return appendEscapeSequence(AnsiOutputStream.SAVE_CURSOR_POS);
}
+ /**
+ * Restores the cursor position.
+ */
public
Ansi restoreCursorPosition() {
return appendEscapeSequence(AnsiOutputStream.RESTORE_CURSOR_POS);
}
+ /**
+ * Resets all of the attributes on the ANSI stream
+ */
public
Ansi reset() {
return a(Attribute.RESET);
}
+ /**
+ * Bold enabled
+ */
public
Ansi bold() {
return a(Attribute.BOLD);
}
+ /**
+ * Bold disabled
+ */
public
Ansi boldOff() {
return a(Attribute.BOLD_OFF);
}
+ /**
+ * Faint enabled (not widely supported)
+ */
public
Ansi faint() {
return a(Attribute.FAINT);
}
+ /**
+ * Faint disabled (not widely supported)
+ */
public
Ansi faintOff() {
return a(Attribute.FAINT_OFF);
}
+ /**
+ * Italic enabled (not widely supported. Sometimes treated as inverse)
+ */
public
Ansi italic() {
return a(Attribute.ITALIC);
}
+ /**
+ * Italic disabled (not widely supported. Sometimes treated as inverse)
+ */
public
Ansi italicOff() {
return a(Attribute.ITALIC_OFF);
}
+ /**
+ * Underline; Single
+ */
public
Ansi underline() {
return a(Attribute.UNDERLINE);
}
+ /**
+ * Underline; Double
+ */
public
Ansi underlineDouble() {
return a(Attribute.UNDERLINE_DOUBLE);
}
+ /**
+ * Underline disabled
+ */
public
Ansi underlineOff() {
return a(Attribute.UNDERLINE_OFF);
}
+ /**
+ * Blink; Slow less than 150 per minute
+ */
public
Ansi blinkSlow() {
return a(Attribute.BLINK_SLOW);
}
+ /**
+ * Blink; Rapid 150 per minute or more
+ */
public
Ansi blinkFast() {
return a(Attribute.BLINK_FAST);
}
+ /**
+ * Blink disabled
+ */
public
Ansi blinkOff() {
return a(Attribute.BLINK_OFF);
}
+ /**
+ * Negative inverse or reverse; swap foreground and background
+ */
public
Ansi negative() {
return a(Attribute.NEGATIVE);
}
+ /**
+ * Negative disabled (back to normal)
+ */
public
Ansi negativeOff() {
return a(Attribute.NEGATIVE_OFF);
}
+ /**
+ * Conceal on
+ */
public
Ansi conceal() {
return a(Attribute.CONCEAL);
}
+ /**
+ * Conceal off
+ */
public
Ansi concealOff() {
return a(Attribute.CONCEAL_OFF);
}
+ /**
+ * Strikethrough enabled
+ */
public
Ansi strikethrough() {
return a(Attribute.STRIKETHROUGH);
}
+ /**
+ * Strikethrough disabled
+ */
public
Ansi strikethroughOff() {
return a(Attribute.STRIKETHROUGH_OFF);
}
+ /**
+ * Appends an attribute (color/etc)
+ *
+ * @param attribute the Attribute (color/etc) to be appended to the ANSI stream
+ * @return this
+ */
+ public
+ Ansi a(Attribute attribute) {
+ attributeOptions.add(attribute.value());
+ return this;
+ }
+
+ /**
+ * Appends a String
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final String value) {
flushAttributes();
@@ -777,6 +590,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a boolean
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final boolean value) {
flushAttributes();
@@ -784,6 +603,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a char
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final char value) {
flushAttributes();
@@ -791,13 +616,25 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a char array + offset + length
+ *
+ * @param valueArray value to be appended to the ANSI stream
+ * @return this
+ */
public
- Ansi a(final char[] value, final int offset, final int len) {
+ Ansi a(final char[] valueArray, final int offset, final int length) {
flushAttributes();
- builder.append(value, offset, len);
+ builder.append(valueArray, offset, length);
return this;
}
+ /**
+ * Appends a char array
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final char[] value) {
flushAttributes();
@@ -805,6 +642,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a CharSequence + start + end
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final CharSequence value, final int start, final int end) {
flushAttributes();
@@ -812,6 +655,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a CharSequence
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final CharSequence value) {
flushAttributes();
@@ -819,6 +668,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a double
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final double value) {
flushAttributes();
@@ -826,6 +681,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a float
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final float value) {
flushAttributes();
@@ -833,6 +694,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a int
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final int value) {
flushAttributes();
@@ -840,6 +707,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a long
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final long value) {
flushAttributes();
@@ -847,6 +720,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a Object
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final Object value) {
flushAttributes();
@@ -854,6 +733,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a StringBuilder
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final StringBuilder value) {
flushAttributes();
@@ -861,6 +746,12 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a StringBuffer
+ *
+ * @param value value to be appended to the ANSI stream
+ * @return this
+ */
public
Ansi a(final StringBuffer value) {
flushAttributes();
@@ -868,6 +759,11 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a new line
+ *
+ * @return this
+ */
public
Ansi newline() {
flushAttributes();
@@ -875,6 +771,13 @@ public class Ansi {
return this;
}
+ /**
+ * Appends a formatted string
+ *
+ * @param pattern String.format pattern to use
+ * @param args arguments to use in the formatted string
+ * @return this
+ */
public
Ansi format(final String pattern, final Object... args) {
flushAttributes();
@@ -910,15 +813,8 @@ public class Ansi {
///////////////////////////////////////////////////////////////////
// Private Helper Methods
///////////////////////////////////////////////////////////////////
-
- private
- Ansi appendCommandSequence(final char command) {
- flushAttributes();
- builder.append(FIRST_ESC_CHAR);
- builder.append(SECOND_ESC_CHAR);
- builder.append(command);
- return this;
- }
+ private static final char FIRST_ESC_CHAR = 27;
+ private static final char SECOND_ESC_CHAR = '[';
private
Ansi appendEscapeSequence(final char command) {
@@ -950,10 +846,10 @@ public class Ansi {
if( attributeOptions.isEmpty() ) {
return;
}
- if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) {
+ if (attributeOptions.size() == 1 && attributeOptions.get(0) == ATTRIBUTE_RESET) {
builder.append(FIRST_ESC_CHAR);
builder.append(SECOND_ESC_CHAR);
- builder.append(AnsiOutputStream.TEXT_ATTRIBUTE);
+ builder.append(ATTRIBUTE_RESET);
} else {
_appendEscapeSequence(AnsiOutputStream.TEXT_ATTRIBUTE, attributeOptions.toArray());
}
diff --git a/src/dorkbox/console/output/AnsiCode.java b/src/dorkbox/console/output/AnsiCode.java
deleted file mode 100644
index dee8996..0000000
--- a/src/dorkbox/console/output/AnsiCode.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package dorkbox.console.output;
-
-/**
- *
- */
-class AnsiCode {
- Enum anEnum;
- String formalName;
- boolean isColorForBackground;
-
- public
- AnsiCode(final Enum anEnum, final String formalName, final boolean isColorForBackground) {
- this.anEnum = anEnum;
- this.formalName = formalName;
- this.isColorForBackground = isColorForBackground;
- }
-
- public
- boolean isColor() {
- return anEnum instanceof Color;
- }
-
- public
- boolean isBackgroundColor() {
- return isColorForBackground;
- }
-
- public
- Color getColor() {
- return (Color) anEnum;
- }
-
- public
- boolean isAttribute() {
- return anEnum instanceof Attribute;
- }
-
- public
- Attribute getAttribute() {
- return (Attribute) anEnum;
- }
-}
diff --git a/src/dorkbox/console/output/AnsiCodeMap.java b/src/dorkbox/console/output/AnsiCodeMap.java
new file mode 100644
index 0000000..fe59f58
--- /dev/null
+++ b/src/dorkbox/console/output/AnsiCodeMap.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 dorkbox, llc
+ *
+ * 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 dorkbox.console.output;
+
+/**
+ * Used for determining what ANSI attribute to use based on a formal name
+ */
+class AnsiCodeMap {
+ private final Enum anEnum;
+ private final boolean isColorForBackground;
+
+ AnsiCodeMap(final Enum anEnum, final boolean isColorForBackground) {
+ this.anEnum = anEnum;
+ this.isColorForBackground = isColorForBackground;
+ }
+
+ boolean isColor() {
+ return anEnum instanceof Color;
+ }
+
+ boolean isBackgroundColor() {
+ return isColorForBackground;
+ }
+
+ Color getColor() {
+ return (Color) anEnum;
+ }
+
+ boolean isAttribute() {
+ return anEnum instanceof Attribute;
+ }
+
+ Attribute getAttribute() {
+ return (Attribute) anEnum;
+ }
+}
diff --git a/src/dorkbox/console/output/AnsiConsole.java b/src/dorkbox/console/output/AnsiConsole.java
deleted file mode 100644
index bd33f83..0000000
--- a/src/dorkbox/console/output/AnsiConsole.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/**
- * Copyright (C) 2009, Progress Software Corporation and/or its
- * subsidiaries or affiliates. All rights reserved.
- *
- * 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 asValue of the License at
- *
- * 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 dorkbox.console.output;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintStream;
-
-import dorkbox.console.util.posix.CLibraryPosix;
-import dorkbox.console.util.windows.Kernel32;
-
-/**
- * Provides consistent access to an ANSI aware console PrintStream.
- *
- * See: https://en.wikipedia.org/wiki/ANSI_escape_code
- *
- * @author Dorkbox, LLC
- * @author Hiram Chirino
- */
-@SuppressWarnings("SpellCheckingInspection")
-class AnsiConsole {
-
- static final int STDOUT_FILENO = 1;
- static final int STDERR_FILENO = 2;
-
- public static final PrintStream system_out = System.out;
- public static final PrintStream out = new PrintStream(wrapOutputStream(system_out, STDOUT_FILENO));
-
- public static final PrintStream system_err = System.err;
- public static final PrintStream err = new PrintStream(wrapOutputStream(system_err, STDERR_FILENO));
-
- private static boolean isXterm() {
- String term = System.getenv("TERM");
- return term != null && term.equals("xterm");
- }
-
- private static
- OutputStream wrapOutputStream(final OutputStream stream, int fileno) {
-
- // If the jansi.passthrough property is set, then don't interpret
- // any of the ansi sequences.
- if (Boolean.getBoolean("jansi.passthrough")) {
- return stream;
- }
-
- // If the jansi.strip property is set, then we just strip the
- // the ansi escapes.
- if (Boolean.getBoolean("jansi.strip")) {
- return new AnsiOutputStream(stream);
- }
-
- String os = System.getProperty("os.name");
- if (os.startsWith("Windows") && !isXterm()) {
-
- // check if windows10+ (which natively supports ANSI)
- if (Kernel32.isWindows10OrGreater()) {
- // Just wrap it up so that when we get closed, we reset the attributes.
- return new FilterOutputStream(stream) {
- @Override
- public
- void close() throws IOException {
- write(AnsiOutputStream.RESET_CODE);
- flush();
- super.close();
- }
- };
- }
-
- // On windows we know the console does not interpret ANSI codes..
- try {
- return new WindowsAnsiOutputStream(stream, fileno);
- } catch (Throwable ignore) {
- ignore.printStackTrace();
- // this happens when JNA is not in the path.. or
- // this happens when the stdout is being redirected to a file.
- // this happens when the stdout is being redirected to different console.
- }
-
- // Use the ANSIOutputStream to strip out the ANSI escape sequences.
- return new AnsiOutputStream(stream);
- }
-
- // We must be on some unix variant..
- try {
- // If the jansi.force property is set, then we force to output
- // the ansi escapes for piping it into ansi color aware commands (e.g. less -r)
- boolean forceColored = Boolean.getBoolean("jansi.force");
-
- // If we can detect that stdout is not a tty.. then setup to strip the ANSI sequences..
- int rc = CLibraryPosix.isatty(fileno);
- if (!isXterm() && !forceColored && rc == 0) {
- return new AnsiOutputStream(stream);
- }
-
- // These errors happen if the JNI lib is not available for your platform.
- } catch (NoClassDefFoundError ignore) {
- } catch (UnsatisfiedLinkError ignore) {
- }
-
- // By default we assume your Unix tty can handle ANSI codes.
- // Just wrap it up so that when we get closed, we reset the attributes.
- return new FilterOutputStream(stream) {
- @Override
- public
- void close() throws IOException {
- write(AnsiOutputStream.RESET_CODE);
- flush();
- super.close();
- }
- };
- }
-
- /**
- * If the standard out natively supports ANSI escape codes, then this just
- * returns System.out, otherwise it will provide an ANSI aware PrintStream
- * which strips out the ANSI escape sequences or which implement the escape
- * sequences.
- *
- * @return a PrintStream which is ANSI aware.
- */
- public static
- PrintStream out() {
- return out;
- }
-
- /**
- * If the standard out natively supports ANSI escape codes, then this just
- * returns System.err, otherwise it will provide an ANSI aware PrintStream
- * which strips out the ANSI escape sequences or which implement the escape
- * sequences.
- *
- * @return a PrintStream which is ANSI aware.
- */
- public static
- PrintStream err() {
- return err;
- }
-}
diff --git a/src/dorkbox/console/output/AnsiOutputStream.java b/src/dorkbox/console/output/AnsiOutputStream.java
index f83c59b..03b5f0f 100644
--- a/src/dorkbox/console/output/AnsiOutputStream.java
+++ b/src/dorkbox/console/output/AnsiOutputStream.java
@@ -1,4 +1,19 @@
-/**
+/*
+ * Copyright 2016 dorkbox, llc
+ *
+ * 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.
+ *
+ *
* Copyright (C) 2009, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
*
@@ -14,7 +29,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package dorkbox.console.output;
import java.io.FilterOutputStream;
@@ -24,21 +38,19 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
/**
- * A ANSI output stream extracts ANSI escape codes written to
- * an output stream.
+ * A ANSI output stream extracts ANSI escape codes written to an output stream.
*
* For more information about ANSI escape codes, see:
* http://en.wikipedia.org/wiki/ANSI_escape_code
*
- * This class just filters out the escape codes so that they are not
- * sent out to the underlying OutputStream. Subclasses should
+ * This class just filters out the escape codes so that they are not sent out to the underlying OutputStream. Subclasses should
* actually perform the ANSI escape behaviors.
*
+ * @author dorkbox, llc
* @author Hiram Chirino
* @author Joris Kuipers
- * @since 1.0
*/
-@SuppressWarnings("NumericCastThatLosesPrecision")
+@SuppressWarnings({"NumericCastThatLosesPrecision", "WeakerAccess"})
public
class AnsiOutputStream extends FilterOutputStream {
private static final Charset CHARSET = Charset.forName("UTF-8");
@@ -52,23 +64,29 @@ class AnsiOutputStream extends FilterOutputStream {
static final int CYAN = 6;
static final int WHITE = 7;
- static final char CURSOR_UP = 'A';
+ static final char CURSOR_UP = 'A'; // Moves the cursor n (default 1) cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
static final char CURSOR_DOWN = 'B';
- static final char CURSOR_RIGHT = 'C';
- static final char CURSOR_LEFT = 'D';
- static final char CURSOR_DOWN_LINE = 'E';
- static final char CURSOR_UP_LINE = 'F';
- static final char CURSOR_TO_COL = 'G';
- static final char CURSOR_POS = 'H';
- static final char CURSOR_POS_ALT = 'f';
+ static final char CURSOR_FORWARD = 'C';
+ static final char CURSOR_BACK = 'D';
- static final char CURSOR_ERASE_SCREEN = 'J';
- static final char CURSOR_ERASE_LINE = 'K';
- static final char PAGE_SCROLL_UP = 'S';
- static final char PAGE_SCROLL_DOWN = 'T';
- static final char SAVE_CURSOR_POS = 's';
- static final char RESTORE_CURSOR_POS = 'u';
- static final char TEXT_ATTRIBUTE = 'm';
+ static final char CURSOR_DOWN_LINE = 'E'; // Moves cursor to beginning of the line n (default 1) lines down.
+ static final char CURSOR_UP_LINE = 'F'; // Moves cursor to beginning of the line n (default 1) lines up.
+
+ static final char CURSOR_TO_COL = 'G'; // Moves the cursor to column n (default 1).
+
+ static final char CURSOR_POS = 'H'; // Moves the cursor to row n, column m. The values are 1-based, and default to 1 (top left corner) if omitted.
+ static final char CURSOR_POS_ALT = 'f'; // Moves the cursor to row n, column m. Both default to 1 if omitted. Same as CUP
+
+ static final char CURSOR_ERASE_SCREEN = 'J'; // Clears part of the screen. If n is 0 (or missing), clear from cursor to end of screen. If n is 1, clear from cursor to beginning of the screen. If n is 2, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS).
+ static final char CURSOR_ERASE_LINE = 'K'; // Erases part of the line. If n is zero (or missing), clear from cursor to the end of the line. If n is one, clear from cursor to beginning of the line. If n is two, clear entire line. Cursor position does not change.
+
+
+ static final char SCROLL_UP = 'S'; // Scroll whole page up by n (default 1) lines. New lines are added at the bottom. (not ANSI.SYS)
+ static final char SCROLL_DOWN = 'T'; // Scroll whole page down by n (default 1) lines. New lines are added at the top. (not ANSI.SYS)
+
+ static final char SAVE_CURSOR_POS = 's'; // Saves the cursor position.
+ static final char RESTORE_CURSOR_POS = 'u'; // Restores the cursor position.
+ static final char TEXT_ATTRIBUTE = 'm'; // Sets SGR parameters, including text color. After CSI can be zero or more parameters separated with ;. With no parameters, CSI m is treated as CSI 0 m (reset / normal), which is typical of most of the ANSI escape sequences.
static final int ATTRIBUTE_RESET = 0; // Reset / Normal - all attributes off
static final int ATTRIBUTE_BOLD = 1; // Intensity: Bold
@@ -90,27 +108,19 @@ class AnsiOutputStream extends FilterOutputStream {
static final int ATTRIBUTE_CONCEAL_OFF = 28; // Reveal conceal off
static final int ATTRIBUTE_STRIKETHROUGH_OFF = 29; // Not crossed out
+
static final int ATTRIBUTE_DEFAULT_FG = 39; // Default text color (foreground)
static final int ATTRIBUTE_DEFAULT_BG = 49; // Default background color
+
// for Erase Screen/Line
static final int ERASE_TO_END = 0;
static final int ERASE_TO_BEGINNING = 1;
static final int ERASE_ALL = 2;
- static final byte[] RESET_CODE = new Ansi().reset()
- .toString()
- .getBytes(CHARSET);
- AnsiOutputStream(OutputStream os) {
- super(os);
- }
private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100;
- private byte buffer[] = new byte[MAX_ESCAPE_SEQUENCE_LENGTH];
- private int pos = 0;
- private int startOfValue;
- private final ArrayList