From a46cbefb02457e2057fa7a9a51fe224c9027e7e2 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 29 May 2016 11:44:30 +0200 Subject: [PATCH] Code polish, added comments/javadocs. Added copyright/authors --- Console.iml | 38 + src/META-INF/MANIFEST.MF | 2 +- src/dorkbox/console/Console.java | 303 ++++- src/dorkbox/console/Input.java | 471 +++++++- src/dorkbox/console/InputConsole.java | 478 -------- src/dorkbox/console/output/Ansi.java | 1030 ++++++++--------- src/dorkbox/console/output/AnsiCode.java | 42 - src/dorkbox/console/output/AnsiCodeMap.java | 50 + src/dorkbox/console/output/AnsiConsole.java | 154 --- .../console/output/AnsiOutputStream.java | 108 +- .../console/output/AnsiRenderWriter.java | 6 +- src/dorkbox/console/output/AnsiRenderer.java | 51 +- src/dorkbox/console/output/AnsiString.java | 4 +- src/dorkbox/console/output/Attribute.java | 15 + src/dorkbox/console/output/Color.java | 51 +- .../console/output/HtmlAnsiOutputStream.java | 18 +- .../output/WindowsAnsiOutputStream.java | 30 +- .../CharHolder.java} | 17 +- .../windows/CONSOLE_SCREEN_BUFFER_INFO.java | 6 +- src/dorkbox/console/util/windows/COORD.java | 9 +- .../console/util/windows/CharUnion.java | 33 +- .../console/util/windows/ConsoleMode.java | 53 - src/dorkbox/console/util/windows/HANDLE.java | 4 +- .../console/util/windows/INPUT_RECORD.java | 43 +- .../util/windows/KEY_EVENT_RECORD.java | 21 +- .../util/windows/MOUSE_EVENT_RECORD.java | 21 +- .../console/util/windows/SMALL_RECT.java | 27 +- .../dorkbox/console/AnsiConsoleExample.java | 162 ++- .../dorkbox/console/AnsiConsoleExample2.java | 55 - .../com/dorkbox/console/AnsiRendererTest.java | 15 +- .../console/AnsiRendererTestExample.java | 172 --- test/com/dorkbox/console/AnsiTest.java | 72 +- .../console/HtmlAnsiOutputStreamTest.java | 7 +- test/com/dorkbox/console/jansi.ans | 8 - 34 files changed, 1825 insertions(+), 1751 deletions(-) create mode 100644 Console.iml delete mode 100644 src/dorkbox/console/InputConsole.java delete mode 100644 src/dorkbox/console/output/AnsiCode.java create mode 100644 src/dorkbox/console/output/AnsiCodeMap.java delete mode 100644 src/dorkbox/console/output/AnsiConsole.java rename src/dorkbox/console/{TerminalType.java => util/CharHolder.java} (71%) delete mode 100644 src/dorkbox/console/util/windows/ConsoleMode.java delete mode 100644 test/com/dorkbox/console/AnsiConsoleExample2.java delete mode 100644 test/com/dorkbox/console/AnsiRendererTestExample.java delete mode 100644 test/com/dorkbox/console/jansi.ans 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 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 - *

- * 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 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 options = new ArrayList(); private static final int LOOKING_FOR_FIRST_ESC_CHAR = 0; private static final int LOOKING_FOR_SECOND_ESC_CHAR = 1; @@ -131,8 +141,26 @@ class AnsiOutputStream extends FilterOutputStream { private static final int BEL = 7; private static final int SECOND_ST_CHAR = '\\'; - // TODO: implement to get perf boost: public void write(byte[] b, int off, int len) + public static final byte[] RESET_CODE = new StringBuilder(3).append((char)FIRST_ESC_CHAR) + .append((char)SECOND_ESC_CHAR) + .append(AnsiOutputStream.TEXT_ATTRIBUTE) + .toString() + .getBytes(CHARSET); + + public + AnsiOutputStream(OutputStream os) { + super(os); + } + + + private byte buffer[] = new byte[MAX_ESCAPE_SEQUENCE_LENGTH]; + private int pos = 0; + private int startOfValue; + private final ArrayList options = new ArrayList(); + + + @Override public void write(int data) throws IOException { switch (state) { @@ -301,9 +329,6 @@ class AnsiOutputStream extends FilterOutputStream { state = LOOKING_FOR_FIRST_ESC_CHAR; } - - - /** * @return true if the escape command was processed. */ @@ -317,10 +342,10 @@ class AnsiOutputStream extends FilterOutputStream { case CURSOR_DOWN: processCursorDown(optionInt(options, 0, 1)); return true; - case CURSOR_RIGHT: + case CURSOR_FORWARD: processCursorRight(optionInt(options, 0, 1)); return true; - case CURSOR_LEFT: + case CURSOR_BACK: processCursorLeft(optionInt(options, 0, 1)); return true; case CURSOR_DOWN_LINE: @@ -342,10 +367,10 @@ class AnsiOutputStream extends FilterOutputStream { case CURSOR_ERASE_LINE: processEraseLine(optionInt(options, 0, 0)); return true; - case PAGE_SCROLL_UP: + case SCROLL_UP: processScrollUp(optionInt(options, 0, 1)); return true; - case PAGE_SCROLL_DOWN: + case SCROLL_DOWN: processScrollDown(optionInt(options, 0, 1)); return true; case TEXT_ATTRIBUTE: @@ -554,7 +579,6 @@ class AnsiOutputStream extends FilterOutputStream { @Override public void close() throws IOException { - write(RESET_CODE); flush(); super.close(); } diff --git a/src/dorkbox/console/output/AnsiRenderWriter.java b/src/dorkbox/console/output/AnsiRenderWriter.java index a3f05fa..cc49658 100644 --- a/src/dorkbox/console/output/AnsiRenderWriter.java +++ b/src/dorkbox/console/output/AnsiRenderWriter.java @@ -64,13 +64,10 @@ class AnsiRenderWriter extends PrintWriter { } } - // - // Need to prevent partial output from being written while formatting or we will get rendering exceptions - // - @Override public PrintWriter format(final String format, final Object... args) { + flush(); // prevents partial output from being written while formatting or we will get rendering exceptions print(String.format(format, args)); return this; } @@ -78,6 +75,7 @@ class AnsiRenderWriter extends PrintWriter { @Override public PrintWriter format(final Locale l, final String format, final Object... args) { + flush(); // prevents partial output from being written while formatting or we will get rendering exceptions print(String.format(l, format, args)); return this; } diff --git a/src/dorkbox/console/output/AnsiRenderer.java b/src/dorkbox/console/output/AnsiRenderer.java index 634c228..d0d39d1 100644 --- a/src/dorkbox/console/output/AnsiRenderer.java +++ b/src/dorkbox/console/output/AnsiRenderer.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 the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +28,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package dorkbox.console.output; import java.util.HashMap; @@ -39,6 +53,7 @@ import java.util.Map; *
  *   @|bold,red Warning!|@
  * 
+ * For Colors, FG_x and BG_x are supported, as are BRIGHT_x (and consequently, FG_BRIGHT_x) * * @author dorkbox, llc * @author Jason Dillon @@ -55,7 +70,13 @@ class AnsiRenderer { private static final int BEGIN_TOKEN_LEN = 2; private static final int END_TOKEN_LEN = 2; - private static Map codeMap = new HashMap(32); + private static Map codeMap = new HashMap(32); + + static { + // have to make sure that all the different categories are added to our map. + Color red = Color.RED; + Attribute bold = Attribute.BOLD; + } static void reg(Enum anEnum, String codeName) { @@ -64,11 +85,11 @@ class AnsiRenderer { static void reg(Enum anEnum, String codeName, boolean isBackgroundColor) { - codeMap.put(codeName, new AnsiCode(anEnum, codeName, isBackgroundColor)); + codeMap.put(codeName, new AnsiCodeMap(anEnum, isBackgroundColor)); } /** - * Renders {@link AnsiCode} names on the given Ansi. + * Renders {@link AnsiCodeMap} names on the given Ansi. * * @param ansi The Ansi to render upon * @param codeNames The code names to render @@ -82,26 +103,26 @@ class AnsiRenderer { } /** - * Renders a {@link AnsiCode} name on the given Ansi. + * Renders a {@link AnsiCodeMap} name on the given Ansi. * * @param ansi The Ansi to render upon * @param codeName The code name to render */ public static Ansi render(Ansi ansi, String codeName) { - AnsiCode ansiCode = codeMap.get(codeName.toUpperCase(Locale.ENGLISH)); - assert ansiCode != null : "Invalid ANSI code name: '" + codeName + "'"; + AnsiCodeMap ansiCodeMap = codeMap.get(codeName.toUpperCase(Locale.ENGLISH)); + assert ansiCodeMap != null : "Invalid ANSI code name: '" + codeName + "'"; - if (ansiCode.isColor()) { - if (ansiCode.isBackgroundColor()) { - ansi = ansi.bg(ansiCode.getColor()); + if (ansiCodeMap.isColor()) { + if (ansiCodeMap.isBackgroundColor()) { + ansi = ansi.bg(ansiCodeMap.getColor()); } else { - ansi = ansi.fg(ansiCode.getColor()); + ansi = ansi.fg(ansiCodeMap.getColor()); } } - else if (ansiCode.isAttribute()) { - ansi = ansi.a(ansiCode.getAttribute()); + else if (ansiCodeMap.isAttribute()) { + ansi = ansi.a(ansiCodeMap.getAttribute()); } else { assert false : "Undetermined ANSI code name: '" + codeName + "'"; } @@ -110,7 +131,7 @@ class AnsiRenderer { } /** - * Renders text using the {@link AnsiCode} names. + * Renders text using the {@link AnsiCodeMap} names. * * @param text The text to render * @param codeNames The code names to render @@ -167,7 +188,7 @@ class AnsiRenderer { } /** - * Renders {@link AnsiCode} names as an ANSI escape string. + * Renders {@link AnsiCodeMap} names as an ANSI escape string. * * @param codeNames The code names to render * diff --git a/src/dorkbox/console/output/AnsiString.java b/src/dorkbox/console/output/AnsiString.java index 9a6248e..7fd8da4 100644 --- a/src/dorkbox/console/output/AnsiString.java +++ b/src/dorkbox/console/output/AnsiString.java @@ -18,6 +18,7 @@ package dorkbox.console.output; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.Charset; /** * An ANSI string which reports the size of rendered text correctly (ignoring any ANSI escapes). @@ -27,6 +28,7 @@ import java.io.IOException; */ public class AnsiString implements CharSequence { + private static final Charset CHARSET = Charset.forName("UTF-8"); private final CharSequence encoded; private final CharSequence plain; @@ -47,7 +49,7 @@ class AnsiString implements CharSequence { try { out.write(str.toString() - .getBytes()); + .getBytes(CHARSET)); out.flush(); out.close(); } catch (IOException e) { diff --git a/src/dorkbox/console/output/Attribute.java b/src/dorkbox/console/output/Attribute.java index 30f134a..9ef52db 100644 --- a/src/dorkbox/console/output/Attribute.java +++ b/src/dorkbox/console/output/Attribute.java @@ -1,3 +1,18 @@ +/* + * 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; import static dorkbox.console.output.AnsiOutputStream.ATTRIBUTE_BLINK_FAST; diff --git a/src/dorkbox/console/output/Color.java b/src/dorkbox/console/output/Color.java index bbe958c..eebe329 100644 --- a/src/dorkbox/console/output/Color.java +++ b/src/dorkbox/console/output/Color.java @@ -17,21 +17,45 @@ package dorkbox.console.output; public enum Color { - BLACK (AnsiOutputStream.BLACK, "BLACK"), - RED (AnsiOutputStream.RED, "RED"), - GREEN (AnsiOutputStream.GREEN, "GREEN"), - YELLOW (AnsiOutputStream.YELLOW, "YELLOW"), - BLUE (AnsiOutputStream.BLUE, "BLUE"), - MAGENTA(AnsiOutputStream.MAGENTA, "MAGENTA"), - CYAN (AnsiOutputStream.CYAN, "CYAN"), - WHITE (AnsiOutputStream.WHITE, "WHITE"); + BLACK (AnsiOutputStream.BLACK, true, "BLACK"), + RED (AnsiOutputStream.RED, true, "RED"), + GREEN (AnsiOutputStream.GREEN, true, "GREEN"), + YELLOW (AnsiOutputStream.YELLOW, true, "YELLOW"), + BLUE (AnsiOutputStream.BLUE, true, "BLUE"), + MAGENTA(AnsiOutputStream.MAGENTA, true, "MAGENTA"), + CYAN (AnsiOutputStream.CYAN, true, "CYAN"), + WHITE (AnsiOutputStream.WHITE, true, "WHITE"), + + + // Brighter versions of those colors, ie: BRIGHT_BLACK is gray. + BRIGHT_BLACK (AnsiOutputStream.BLACK, false, "BRIGHT_BLACK"), + BRIGHT_RED (AnsiOutputStream.RED, false, "BRIGHT_RED"), + BRIGHT_GREEN (AnsiOutputStream.GREEN, false, "BRIGHT_GREEN"), + BRIGHT_YELLOW (AnsiOutputStream.YELLOW, false, "BRIGHT_YELLOW"), + BRIGHT_BLUE (AnsiOutputStream.BLUE, false, "BRIGHT_BLUE"), + BRIGHT_MAGENTA(AnsiOutputStream.MAGENTA, false, "BRIGHT_MAGENTA"), + BRIGHT_CYAN (AnsiOutputStream.CYAN, false, "BRIGHT_CYAN"), + BRIGHT_WHITE (AnsiOutputStream.WHITE, false, "BRIGHT_WHITE"), + + // SPECIAL use case here. This is intercepted (so the color doesn't matter) + /** + * DEFAULT is the color of console BEFORE any colors/settings are applied + */ + DEFAULT (AnsiOutputStream.WHITE, true, "DEFAULT"), + + /** + * DEFAULT is the color of console BEFORE any colors/settings are applied + */ + BRIGHT_DEFAULT (AnsiOutputStream.WHITE, false, "BRIGHT_DEFAULT"); private final int value; private final String name; + private final boolean isNormal; - Color(int index, String name) { + Color(int index, boolean isNormal, String name) { this.value = index; this.name = name; + this.isNormal = isNormal; // register code names with the ANSI renderer AnsiRenderer.reg(this, name, false); @@ -45,22 +69,23 @@ enum Color { return name; } - public int fg() { return value + 30; } - public int bg() { return value + 40; } - public + /** is this a BRIGHT color or NORMAL color? */ + boolean isNormal() { + return isNormal; + } + int fgBright() { return value + 90; } - public int bgBright() { return value + 100; } diff --git a/src/dorkbox/console/output/HtmlAnsiOutputStream.java b/src/dorkbox/console/output/HtmlAnsiOutputStream.java index 9d6baf3..68b479e 100644 --- a/src/dorkbox/console/output/HtmlAnsiOutputStream.java +++ b/src/dorkbox/console/output/HtmlAnsiOutputStream.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. *

@@ -23,6 +38,7 @@ import java.util.ArrayList; import java.util.List; /** + * @author dorkbox, llc * @author Daniel Doubrovkine */ public diff --git a/src/dorkbox/console/output/WindowsAnsiOutputStream.java b/src/dorkbox/console/output/WindowsAnsiOutputStream.java index 07a0dbe..81aeb65 100644 --- a/src/dorkbox/console/output/WindowsAnsiOutputStream.java +++ b/src/dorkbox/console/output/WindowsAnsiOutputStream.java @@ -1,10 +1,9 @@ /* - * Copyright (C) 2009, Progress Software Corporation and/or its - * subsidiaries or affiliates. All rights reserved. + * 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 asValue of the License at + * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -14,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2016 dorkbox, llc + * + * 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 copy of the License at + * You may obtain a asValue of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -58,7 +59,7 @@ import dorkbox.console.util.windows.SMALL_RECT; * @author Joris Kuipers */ @SuppressWarnings("NumericCastThatLosesPrecision") -final class WindowsAnsiOutputStream extends AnsiOutputStream { +public final class WindowsAnsiOutputStream extends AnsiOutputStream { private static final short ANSI_FOREGROUND_COLOR_MAP[]; private static final short ANSI_BACKGROUND_COLOR_MAP[]; @@ -92,12 +93,13 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { private volatile short savedX = (short) -1; private volatile short savedY = (short) -1; + public WindowsAnsiOutputStream(final OutputStream os, int fileHandle) throws IOException { super(os); - if (fileHandle == AnsiConsole.STDOUT_FILENO) { + if (fileHandle == 1) { // STDOUT_FILENO fileHandle = Kernel32.STD_OUTPUT_HANDLE; - } else if (fileHandle == AnsiConsole.STDERR_FILENO) { + } else if (fileHandle == 2) { // STDERR_FILENO fileHandle = Kernel32.STD_ERROR_HANDLE; } else { throw new IllegalArgumentException("Invalid file handle " + fileHandle); @@ -321,7 +323,7 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { void processCursorUpLine(final int count) throws IOException { getConsoleInfo(); info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y - count); - info.cursorPosition.x = 0; + info.cursorPosition.x = (short) 0; applyCursorPosition(); } @@ -329,7 +331,7 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { void processCursorDownLine(final int count) throws IOException { getConsoleInfo(); info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y + count); - info.cursorPosition.x = 0; + info.cursorPosition.x = (short) 0; applyCursorPosition(); } @@ -383,6 +385,8 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { } /** + * Scrolls the contents of the buffer either UP or DOWN + * * @param rowsToScroll negative to go down, positive to go up. * * Scroll up and new lines are added at the bottom, scroll down and new lines are added at the @@ -405,12 +409,12 @@ final class WindowsAnsiOutputStream extends AnsiOutputStream { // the content that will be scrolled scrollRect.top = (short) (0); - scrollRect.bottom = (short) (Short.MAX_VALUE); - scrollRect.left = 0; + scrollRect.bottom = Short.MAX_VALUE; + scrollRect.left = (short) 0; scrollRect.right = (short) (info.size.x - 1); // The destination for the scroll rectangle is xxx row up/down. - coordDest.x = 0; + coordDest.x = (short) 0; coordDest.y = (short) (-rowsToScroll); // fill the space with whatever color was already there with spaces diff --git a/src/dorkbox/console/TerminalType.java b/src/dorkbox/console/util/CharHolder.java similarity index 71% rename from src/dorkbox/console/TerminalType.java rename to src/dorkbox/console/util/CharHolder.java index f5d2bdb..85bb896 100644 --- a/src/dorkbox/console/TerminalType.java +++ b/src/dorkbox/console/util/CharHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2010 dorkbox, llc + * 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. @@ -13,10 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.console; - -public class TerminalType { - +package dorkbox.console.util; +/** + * Used for single char input + */ +public +class CharHolder { + // default is nothing (0) + public char character = (char) 0; + public + CharHolder() { + } } diff --git a/src/dorkbox/console/util/windows/CONSOLE_SCREEN_BUFFER_INFO.java b/src/dorkbox/console/util/windows/CONSOLE_SCREEN_BUFFER_INFO.java index 714e8b8..d41aba2 100644 --- a/src/dorkbox/console/util/windows/CONSOLE_SCREEN_BUFFER_INFO.java +++ b/src/dorkbox/console/util/windows/CONSOLE_SCREEN_BUFFER_INFO.java @@ -39,7 +39,9 @@ class CONSOLE_SCREEN_BUFFER_INFO extends Structure { } @Override - public String toString() { - return "Size: " + size + " CursorPos: " + cursorPosition + " Attribs: " + attributes + " Window: " + window + " MaxWindowSize: " + maximumWindowSize; + public + String toString() { + return "Size: " + size + " CursorPos: " + cursorPosition + " Attribs: " + attributes + " Window: " + window + " MaxWindowSize: " + + maximumWindowSize; } } diff --git a/src/dorkbox/console/util/windows/COORD.java b/src/dorkbox/console/util/windows/COORD.java index 84da97f..8bf4cb1 100644 --- a/src/dorkbox/console/util/windows/COORD.java +++ b/src/dorkbox/console/util/windows/COORD.java @@ -25,8 +25,6 @@ import com.sun.jna.Structure; */ public class COORD extends Structure { - static public class ByValue extends COORD implements Structure.ByValue { } - public short x; public short y; @@ -45,7 +43,12 @@ class COORD extends Structure { } @Override - public String toString() { + public + String toString() { return x + ":" + y; } + + + static public + class ByValue extends COORD implements Structure.ByValue {} } diff --git a/src/dorkbox/console/util/windows/CharUnion.java b/src/dorkbox/console/util/windows/CharUnion.java index 5cb20d7..65c0daa 100644 --- a/src/dorkbox/console/util/windows/CharUnion.java +++ b/src/dorkbox/console/util/windows/CharUnion.java @@ -1,30 +1,51 @@ +/* + * 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.util.windows; import com.sun.jna.Union; -public class CharUnion extends Union { +public +class CharUnion extends Union { public char unicodeChar; public byte asciiChar; - public CharUnion() { + public + CharUnion() { } - public CharUnion(char c) { + public + CharUnion(char c) { setType(char.class); unicodeChar = c; } - public CharUnion(byte c) { + public + CharUnion(byte c) { setType(byte.class); asciiChar = c; } - public void set(char c) { + public + void set(char c) { setType(char.class); unicodeChar = c; } - public void set(byte c) { + public + void set(byte c) { setType(byte.class); asciiChar = c; } diff --git a/src/dorkbox/console/util/windows/ConsoleMode.java b/src/dorkbox/console/util/windows/ConsoleMode.java deleted file mode 100644 index 092b518..0000000 --- a/src/dorkbox/console/util/windows/ConsoleMode.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2002-2012, the original author or authors. - * - * This software is distributable under the BSD license. See the terms of the - * BSD license in the documentation provided with this software. - * - * http://www.opensource.org/licenses/bsd-license.php - * - * @author Marc Prud'hommeaux - * @author Jason Dillon - */ -package dorkbox.console.util.windows; - -/** - * Console mode - *

- * Constants copied wincon.h. - */ -public enum ConsoleMode { - /** - * CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or - * ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the - * ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and linefeed characters are handled by the system. - */ - ENABLE_PROCESSED_INPUT(1), - - /** - * The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disable, the functions - * return when one or more characters are available. - */ - ENABLE_LINE_INPUT(2), - - /** - * Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are read. This mode can be - * used only if the ENABLE_LINE_INPUT mode is also enabled. - */ - ENABLE_ECHO_INPUT(4), - - /** - * User interactions that change the size of the console screen buffer are reported in the console's input buffer. Information about - * these events can be read from the input buffer by applications using theReadConsoleInput function, but not by those using ReadFile - * orReadConsole. - */ - ENABLE_WINDOW_INPUT(8), - ; - - - public final int code; - - ConsoleMode(final int code) { - this.code = code; - } -} diff --git a/src/dorkbox/console/util/windows/HANDLE.java b/src/dorkbox/console/util/windows/HANDLE.java index 3662689..376bad2 100644 --- a/src/dorkbox/console/util/windows/HANDLE.java +++ b/src/dorkbox/console/util/windows/HANDLE.java @@ -27,7 +27,9 @@ import com.sun.jna.PointerType; */ public class HANDLE extends PointerType { - /** Constant value representing an invalid HANDLE. */ + /** + * Constant value representing an invalid HANDLE. + */ public static final HANDLE INVALID_HANDLE_VALUE = new HANDLE(Pointer.createConstant(Pointer.SIZE == 8 ? -1 : 0xFFFFFFFFL)); private boolean immutable; diff --git a/src/dorkbox/console/util/windows/INPUT_RECORD.java b/src/dorkbox/console/util/windows/INPUT_RECORD.java index 073c3f5..e47639e 100644 --- a/src/dorkbox/console/util/windows/INPUT_RECORD.java +++ b/src/dorkbox/console/util/windows/INPUT_RECORD.java @@ -1,3 +1,18 @@ +/* + * 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.util.windows; import java.util.Arrays; @@ -9,22 +24,16 @@ import com.sun.jna.Union; /** * https://msdn.microsoft.com/en-us/library/ms683499(v=VS.85).aspx */ -public class INPUT_RECORD extends Structure { - static public class ByReference extends INPUT_RECORD implements Structure.ByReference {} - +public +class INPUT_RECORD extends Structure { public static final short KEY_EVENT = 0x0001; public static final short MOUSE_EVENT = 0x0002; - public short EventType; public EventUnion Event; - public static class EventUnion extends Union { - public KEY_EVENT_RECORD KeyEvent; - public MOUSE_EVENT_RECORD MouseEvent; - } - @Override - public void read() { + public + void read() { readField("EventType"); switch (EventType) { case KEY_EVENT: @@ -38,7 +47,19 @@ public class INPUT_RECORD extends Structure { } @Override - protected List getFieldOrder() { + protected + List getFieldOrder() { return Arrays.asList("EventType", "Event"); } + + + static public + class ByReference extends INPUT_RECORD implements Structure.ByReference {} + + + public static + class EventUnion extends Union { + public KEY_EVENT_RECORD KeyEvent; + public MOUSE_EVENT_RECORD MouseEvent; + } } diff --git a/src/dorkbox/console/util/windows/KEY_EVENT_RECORD.java b/src/dorkbox/console/util/windows/KEY_EVENT_RECORD.java index 43e575f..231bce2 100644 --- a/src/dorkbox/console/util/windows/KEY_EVENT_RECORD.java +++ b/src/dorkbox/console/util/windows/KEY_EVENT_RECORD.java @@ -1,3 +1,18 @@ +/* + * 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.util.windows; import java.util.Arrays; @@ -8,7 +23,8 @@ import com.sun.jna.Structure; /** * https://msdn.microsoft.com/en-us/library/ms684166(v=VS.85).aspx */ -public class KEY_EVENT_RECORD extends Structure { +public +class KEY_EVENT_RECORD extends Structure { public boolean keyDown; public short repeatCount; public short virtualKeyCode; @@ -17,7 +33,8 @@ public class KEY_EVENT_RECORD extends Structure { public int controlKeyState; @Override - protected List getFieldOrder() { + protected + List getFieldOrder() { return Arrays.asList("keyDown", "repeatCount", "virtualKeyCode", "virtualScanCode", "uChar", "controlKeyState"); } } diff --git a/src/dorkbox/console/util/windows/MOUSE_EVENT_RECORD.java b/src/dorkbox/console/util/windows/MOUSE_EVENT_RECORD.java index 0e66a7b..35ec7e0 100644 --- a/src/dorkbox/console/util/windows/MOUSE_EVENT_RECORD.java +++ b/src/dorkbox/console/util/windows/MOUSE_EVENT_RECORD.java @@ -1,3 +1,18 @@ +/* + * 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.util.windows; import java.util.Arrays; @@ -5,14 +20,16 @@ import java.util.List; import com.sun.jna.Structure; -public class MOUSE_EVENT_RECORD extends Structure { +public +class MOUSE_EVENT_RECORD extends Structure { public COORD mousePosition; public int buttonState; public int controlKeyState; public int eventFlags; @Override - protected List getFieldOrder() { + protected + List getFieldOrder() { return Arrays.asList("mousePosition", "buttonState", "controlKeyState", "eventFlags"); } } diff --git a/src/dorkbox/console/util/windows/SMALL_RECT.java b/src/dorkbox/console/util/windows/SMALL_RECT.java index 285ebbf..2a6b10a 100644 --- a/src/dorkbox/console/util/windows/SMALL_RECT.java +++ b/src/dorkbox/console/util/windows/SMALL_RECT.java @@ -23,28 +23,37 @@ import com.sun.jna.Structure; /** * https://msdn.microsoft.com/en-us/library/ms686311%28VS.85%29.aspx */ -public class SMALL_RECT extends Structure { - static public class ByReference extends SMALL_RECT implements Structure.ByReference { } - +@SuppressWarnings("NumericCastThatLosesPrecision") +public +class SMALL_RECT extends Structure { public short left; public short top; public short right; public short bottom; - public short width() { - return (short) (this.right-this.left); + public + short width() { + return (short) (this.right - this.left); } - public short height() { - return (short) (this.bottom-this.top); + + public + short height() { + return (short) (this.bottom - this.top); } @Override - protected List getFieldOrder() { + protected + List getFieldOrder() { return Arrays.asList("left", "top", "right", "bottom"); } @Override - public String toString() { + public + String toString() { return "LTRB: " + left + "," + top + "," + right + "," + bottom; } + + + static public + class ByReference extends SMALL_RECT implements Structure.ByReference {} } diff --git a/test/com/dorkbox/console/AnsiConsoleExample.java b/test/com/dorkbox/console/AnsiConsoleExample.java index 5f69781..7dee159 100644 --- a/test/com/dorkbox/console/AnsiConsoleExample.java +++ b/test/com/dorkbox/console/AnsiConsoleExample.java @@ -1,50 +1,140 @@ -/** - * 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. - */ package com.dorkbox.console; -import java.io.FileInputStream; import java.io.IOException; +import dorkbox.console.Console; import dorkbox.console.output.Ansi; +import dorkbox.console.output.Color; +import dorkbox.console.output.Erase; /** - * - * @author Hiram Chirino + * System output for visual confirmation of ANSI codes. + *

+ * Must enable assertions to verify no errors!! (ie: java -ea -jar blah.jar) */ -public class AnsiConsoleExample { +public +class AnsiConsoleExample { + public static + void main(String[] args) throws IOException { - private AnsiConsoleExample() {} + Console.systemInstall(); + Console.ENABLE_ANSI = true; - public static void main(String[] args) throws IOException { - String file = "jansi.ans"; - if( args.length>0 ) - file = args[0]; + System.out.println(Ansi.ansi() + .fg(Color.BLACK).a("black").bg(Color.BLACK).a("black") + .reset() + .fg(Color.BRIGHT_BLACK).a("b-black").bg(Color.BRIGHT_BLACK).a("b-black") + .reset()); - // Allows us to disable ANSI processing. - if( "true".equals(System.getProperty("jansi", "true")) ) { - Ansi.systemInstall(); + System.out.println(Ansi.ansi() + .fg(Color.BLUE).a("blue").bg(Color.BLUE).a("blue") + .reset() + .fg(Color.BRIGHT_BLUE).a("b-blue").bg(Color.BRIGHT_BLUE).a("b-blue") + .reset()); + + System.out.println(Ansi.ansi() + .fg(Color.CYAN).a("cyan").bg(Color.CYAN).a("cyan") + .reset() + .fg(Color.BRIGHT_CYAN).a("b-cyan").bg(Color.BRIGHT_CYAN).a("b-cyan") + .reset()); + + System.out.println(Ansi.ansi() + .fg(Color.GREEN).a("green").bg(Color.GREEN).a("green") + .reset() + .fg(Color.BRIGHT_GREEN).a("b-green").bg(Color.BRIGHT_GREEN).a("b-green") + .reset()); + + System.out.println(Ansi.ansi() + .fg(Color.MAGENTA).a("magenta").bg(Color.MAGENTA).a("magenta") + .reset() + .fg(Color.BRIGHT_MAGENTA).a("b-magenta").bg(Color.BRIGHT_MAGENTA).a("b-magenta") + .reset()); + + System.out.println(Ansi.ansi() + .fg(Color.RED).a("red").bg(Color.RED).a("red") + .reset() + .fg(Color.BRIGHT_RED).a("b-red").bg(Color.BRIGHT_RED).a("b-red") + .reset()); + + System.out.println(Ansi.ansi() + .fg(Color.YELLOW).a("yellow").bg(Color.YELLOW).a("yellow") + .reset() + .fg(Color.BRIGHT_YELLOW).a("b-yellow").bg(Color.BRIGHT_YELLOW).a("b-yellow") + .reset()); + + System.out.println(Ansi.ansi() + .fg(Color.WHITE).a("white").bg(Color.WHITE).a("white") + .reset() + .fg(Color.BRIGHT_WHITE).a("b-white").bg(Color.BRIGHT_WHITE).a("b-white") + .reset()); + + + Console.reset(); // reset the ansi stream. Can ALSO have ansi().reset(), but that would be redundant + + + System.out.println("The following line should be blank except for the first '>'"); + System.out.println(Ansi.ansi() + .a(">THIS SHOULD BE BLANK") + .cursorToColumn(2) + .eraseLine()); + + System.out.println("The following line should be blank"); + System.out.println(Ansi.ansi() + .a(">THIS SHOULD BE BLANK") + .eraseLine(Erase.ALL)); + + System.out.println(Ansi.ansi() + .a(">THIS SHOULD BE BLANK") + .eraseLine(Erase.BACKWARD) + .a("Everything on this line before this should be blank")); + + System.out.println(Ansi.ansi() + .a("Everything on this line after this should be blank") + .saveCursorPosition() + .a(">THIS SHOULD BE BLANK") + .restoreCursorPosition() + .eraseLine()); + + System.out.println("00000000000000000000000000"); + System.out.println("00000000000000000000000000"); + System.out.println("00000000000000000000000000"); + System.out.println("00000000000000000000000000"); + System.out.println("00000000000000000000000000"); + + System.out.println(Ansi.ansi() + .a("Should have two blank spots in the above 0's") + .saveCursorPosition() + .cursorUp(4) + .cursorLeft(30) + .a(" ") + .cursorDownLine() + .cursorRight(5) + .a(" ") + .restoreCursorPosition()); + + System.err.println("ver : " + Console.getVersion()); + + + System.out.println("Now testing the input console. 'q' to quit"); + Console.ENABLE_ECHO = true; + + int read; + while ((read = Console.read()) != 'q') { + if (Character.isDigit(read)) { + int numericValue = Character.getNumericValue(read); + // reverse if pressing 2 + if (numericValue == 2) { + System.out.print(Ansi.ansi().scrollDown(1)); + System.out.flush(); // flush guarantees the terminal moves the way we want + } + else { + System.out.print(Ansi.ansi().scrollUp(numericValue)); + System.out.flush(); // flush guarantees the terminal moves the way we want + } + } +// System.err.println("READ :" + read + " (" + (char) read + ")"); } - FileInputStream f = new FileInputStream(file); - int c; - while( (c=f.read())>=0 ) { - System.out.write(c); - } - f.close(); - } - + Console.systemUninstall(); + } } diff --git a/test/com/dorkbox/console/AnsiConsoleExample2.java b/test/com/dorkbox/console/AnsiConsoleExample2.java deleted file mode 100644 index e00f698..0000000 --- a/test/com/dorkbox/console/AnsiConsoleExample2.java +++ /dev/null @@ -1,55 +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 - * - * 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.dorkbox.console; - -import static dorkbox.console.output.Ansi.ansi; - -import java.io.FileInputStream; -import java.io.IOException; - -import dorkbox.console.output.Ansi; - -/** - * - * @author Hiram Chirino - */ -public class AnsiConsoleExample2 { - - private AnsiConsoleExample2() {} - - public static void main(String[] args) throws IOException { - String file = "jansi.ans"; - if( args.length>0 ) - file = args[0]; - - // Allows us to disable ANSI processing. - if( "true".equals(System.getProperty("jansi", "true")) ) { - Ansi.systemInstall(); - } - - System.out.print(ansi().reset().eraseScreen().cursor(1, 1)); - System.out.print("======================================================================="); - FileInputStream f = new FileInputStream(file); - int c; - while( (c=f.read())>=0 ) { - System.out.write(c); - } - f.close(); - System.out.println("======================================================================="); - } - -} diff --git a/test/com/dorkbox/console/AnsiRendererTest.java b/test/com/dorkbox/console/AnsiRendererTest.java index 68a0042..0c54cf8 100644 --- a/test/com/dorkbox/console/AnsiRendererTest.java +++ b/test/com/dorkbox/console/AnsiRendererTest.java @@ -17,7 +17,7 @@ package com.dorkbox.console; import static dorkbox.console.output.AnsiRenderer.render; -import static java.awt.Font.BOLD; +import static dorkbox.console.output.Attribute.BOLD; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; +import dorkbox.console.Console; import dorkbox.console.output.Ansi; import dorkbox.console.output.AnsiRenderer; import dorkbox.console.output.Color; @@ -43,7 +44,7 @@ public class AnsiRendererTest @Before public void setUp() { - Ansi.setEnabled(true); + Console.ENABLE_ANSI = true; } @Test @@ -66,7 +67,6 @@ public class AnsiRendererTest String str = render("@|bold,red foo|@"); System.out.println(str); assertEquals(Ansi.ansi().a(BOLD).fg(Color.RED).a("foo").reset().toString(), str); - assertEquals(Ansi.ansi().bold().fgRed().a("foo").reset().toString(), str); } @Test @@ -74,7 +74,6 @@ public class AnsiRendererTest String str = render("@|bold,red foo bar baz|@"); System.out.println(str); assertEquals(Ansi.ansi().a(BOLD).fg(Color.RED).a("foo bar baz").reset().toString(), str); - assertEquals(Ansi.ansi().bold().fgRed().a("foo bar baz").reset().toString(), str); } @Test @@ -82,10 +81,10 @@ public class AnsiRendererTest String str = render("@|bold,red foo bar baz|@ ick @|bold,red foo bar baz|@"); System.out.println(str); assertEquals(Ansi.ansi() - .a(BOLD).fg(Color.RED).a("foo bar baz").reset() - .a(" ick ") - .a(BOLD).fg(Color.RED).a("foo bar baz").reset() - .toString(), str); + .a(BOLD).fg(Color.RED).a("foo bar baz").reset() + .a(" ick ") + .a(BOLD).fg(Color.RED).a("foo bar baz").reset() + .toString(), str); } @Test diff --git a/test/com/dorkbox/console/AnsiRendererTestExample.java b/test/com/dorkbox/console/AnsiRendererTestExample.java deleted file mode 100644 index c6c730e..0000000 --- a/test/com/dorkbox/console/AnsiRendererTestExample.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.dorkbox.console; - -import static dorkbox.console.output.Ansi.ansi; - -import java.io.IOException; - -import dorkbox.console.Console; -import dorkbox.console.output.Ansi; -import dorkbox.console.output.Erase; - -/** - * System output for visual confirmation of ANSI codes. - *

- * Must enable assertions to verify no errors!! (ie: java -ea -jar blah.jar) - */ -public -class AnsiRendererTestExample { - public static - void main(String[] args) throws IOException { - Ansi.systemInstall(); - Ansi.setEnabled(true); - - System.out.println(ansi() - .fgBlack().a("black").bgBlack().a("black") - .reset() - .fgBrightBlack().a("b-black").bgBrightBlack().a("b-black") - .reset()); - - System.out.println(ansi() - .fgBlue().a("blue").bgBlue().a("blue") - .reset() - .fgBrightBlue().a("b-blue").bgBrightBlue().a("b-blue") - .reset()); - - System.out.println(ansi() - .fgCyan().a("cyan").bgCyan().a("cyan") - .reset() - .fgBrightCyan().a("b-cyan").bgBrightCyan().a("b-cyan") - .reset()); - - System.out.println(ansi() - .fgGreen().a("green").bgGreen().a("green") - .reset() - .fgBrightGreen().a("b-green").bgBrightGreen().a("b-green") - .reset()); - - System.out.println(ansi() - .fgMagenta().a("magenta").bgMagenta().a("magenta") - .reset() - .fgBrightMagenta().a("b-magenta").bgBrightMagenta().a("b-magenta") - .reset()); - - System.out.println(ansi() - .fgRed().a("red").bgRed().a("red") - .reset() - .fgBrightRed().a("b-red").bgBrightRed().a("b-red") - .reset()); - - System.out.println(ansi() - .fgYellow().a("yellow").bgYellow().a("yellow") - .reset() - .fgBrightYellow().a("b-yellow").bgBrightYellow().a("b-yellow") - .reset()); - - System.out.println(ansi() - .fgWhite().a("white").bgWhite().a("white") - .reset() - .fgBrightWhite().a("b-white").bgBrightWhite().a("b-white") - .reset()); - - - System.out.println(ansi()); // reset the ansi stream. Can ALSO have ansi().reset(), but that would be redundant - - System.out.println("The following line should be blank except for the first '>'"); - System.out.println(ansi() - .a(">THIS SHOULD BE BLANK") - .cursorToColumn(2) - .eraseLine()); - - System.out.println("The following line should be blank"); - System.out.println(ansi() - .a(">THIS SHOULD BE BLANK") - .eraseLine(Erase.ALL)); - - System.out.println(ansi() - .a(">THIS SHOULD BE BLANK") - .eraseLine(Erase.BACKWARD) - .a("Everything before this should be blank")); - - System.out.println(ansi() - .a("Everything after this should be blank") - .saveCursorPosition() - .a(">THIS SHOULD BE BLANK") - .restoreCursorPosition() - .eraseLine(Erase.FORWARD)); - - System.out.println("00000000000000000000000000"); - System.out.println("00000000000000000000000000"); - System.out.println("00000000000000000000000000"); - System.out.println("00000000000000000000000000"); - System.out.println("00000000000000000000000000"); - - System.out.println(ansi() - .a("Should have two blank spots in the above 0's") - .saveCursorPosition() - .cursorUp(4) - .cursorLeft(30) - .a(" ") - .cursorDownLine() - .cursorRight(5) - .a(" ") - .restoreCursorPosition()); - - - // verify the output renderer -// String str = render("@|bold foo|@foo"); -// System.out.println(str); -// assertEquals(ansi().a(BOLD).a("foo").reset().a("foo").toString(), str); -// assertEquals(ansi().bold().a("foo").reset().a("foo").toString(), str); -// -// -// str = render("@|bold,red foo|@"); -// System.out.println(str); -// assertEquals(ansi().a(BOLD).fg(RED).a("foo").reset().toString(), str); -// assertEquals(ansi().bold().fgRed().a("foo").reset().toString(), str); -// -// str = render("@|bold,red foo bar baz|@"); -// System.out.println(str); -// assertEquals(ansi().a(BOLD).fg(RED).a("foo bar baz").reset().toString(), str); -// assertEquals(ansi().bold().fgRed().a("foo bar baz").reset().toString(), str); -// -// -// str = render("@|bold,red foo bar baz|@ ick @|bold,red foo bar baz|@"); -// System.out.println(str); -// String expected = ansi().a(BOLD) -// .fg(RED) -// .a("foo bar baz") -// .reset() -// .a(" ick ") -// .a(BOLD) -// .fg(RED) -// .a("foo bar baz") -// .reset() -// .toString(); -// -// assertEquals(expected, str); -// -// -// str = render("@|bold foo"); // shouldn't work -// System.err.println(str + " <- shouldn't work"); -// -// str = render("@|bold|@"); // shouldn't work -// System.err.println(str + " <- shouldn't work"); -// -// Ansi.systemUninstall(); -// -// str = render("@|bold foo|@foo"); -// System.out.println(str + " <- shouldn't work"); - - - System.err.println("ver : " + Console.getVersion()); - - - System.out.println("Now testing the input console. '?' to quit"); - Console.ENABLE_ECHO = true; - - int read; - while ((read = Console.in.read()) != '?') { -// System.err.println("READ :" + read + " (" + (char) read + ")"); - } - } -} diff --git a/test/com/dorkbox/console/AnsiTest.java b/test/com/dorkbox/console/AnsiTest.java index 35155be..5733b59 100644 --- a/test/com/dorkbox/console/AnsiTest.java +++ b/test/com/dorkbox/console/AnsiTest.java @@ -16,6 +16,10 @@ package com.dorkbox.console; +import static dorkbox.console.output.Ansi.ansi; +import static dorkbox.console.output.AnsiRenderer.render; +import static dorkbox.console.output.Attribute.BOLD; +import static dorkbox.console.output.Color.RED; import static org.junit.Assert.assertEquals; import org.junit.Test; @@ -30,32 +34,54 @@ import dorkbox.console.output.Color; */ public class AnsiTest { - @Test - public void testSetEnabled() throws Exception { - Ansi.setEnabled(false); - new Thread() - { - @Override - public void run() { - assertEquals(false, Ansi.isEnabled()); - } - }.run(); - - Ansi.setEnabled(true); - new Thread() - { - @Override - public void run() { - assertEquals(true, Ansi.isEnabled()); - } - }.run(); - } - @Test public void testClone() throws CloneNotSupportedException { - Ansi ansi = Ansi.ansi().a("Some text").bg(Color.BLACK).fg(Color.WHITE); - Ansi clone = new Ansi(ansi); + Ansi ansi = ansi().a("Some text").bg(Color.BLACK).fg(Color.WHITE); + Ansi clone = ansi(ansi); assertEquals(ansi.a("test").reset().toString(), clone.a("test").reset().toString()); } + + @Test + public void testOutput() throws CloneNotSupportedException { + + // verify the output renderer + String str = render("@|bold foo|@foo"); + assertEquals(ansi().a(BOLD).a("foo").reset().a("foo").toString(), str); + assertEquals(ansi().bold().a("foo").reset().a("foo").toString(), str); + + + str = render("@|bold,red foo|@"); + assertEquals(ansi().a(BOLD).fg(RED).a("foo").reset().toString(), str); + assertEquals(ansi().bold().fg(RED).a("foo").reset().toString(), str); + + str = render("@|bold,red foo bar baz|@"); + assertEquals(ansi().a(BOLD).fg(RED).a("foo bar baz").reset().toString(), str); + assertEquals(ansi().bold().fg(RED).a("foo bar baz").reset().toString(), str); + + + str = render("@|bold,red foo bar baz|@ ick @|bold,red foo bar baz|@"); + String expected = ansi().a(BOLD) + .fg(RED) + .a("foo bar baz") + .reset() + .a(" ick ") + .a(BOLD) + .fg(RED) + .a("foo bar baz") + .reset() + .toString(); + + assertEquals(expected, str); + + + str = render("@|bold foo"); // shouldn't work + System.err.println(str + " <- shouldn't work"); + + str = render("@|bold|@"); // shouldn't work + System.err.println(str + " <- shouldn't work"); + + str = render("@|bold foo|@foo"); + System.out.println(str + " <- shouldn't work"); + } } diff --git a/test/com/dorkbox/console/HtmlAnsiOutputStreamTest.java b/test/com/dorkbox/console/HtmlAnsiOutputStreamTest.java index 01bc573..bece74f 100644 --- a/test/com/dorkbox/console/HtmlAnsiOutputStreamTest.java +++ b/test/com/dorkbox/console/HtmlAnsiOutputStreamTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.Charset; import org.junit.Test; @@ -29,6 +30,7 @@ import dorkbox.console.output.HtmlAnsiOutputStream; * @author Daniel Doubrovkine */ public class HtmlAnsiOutputStreamTest { + private static final Charset charset = Charset.forName("UTF-8"); @Test public void testNoMarkup() throws IOException { @@ -82,8 +84,9 @@ public class HtmlAnsiOutputStreamTest { private String colorize(String text) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); HtmlAnsiOutputStream hos = new HtmlAnsiOutputStream(os); - hos.write(text.getBytes("UTF-8")); + + hos.write(text.getBytes(charset)); hos.close(); - return new String(os.toByteArray(), "UTF-8"); + return new String(os.toByteArray(), charset); } } diff --git a/test/com/dorkbox/console/jansi.ans b/test/com/dorkbox/console/jansi.ans deleted file mode 100644 index d9d3c52..0000000 --- a/test/com/dorkbox/console/jansi.ans +++ /dev/null @@ -1,8 +0,0 @@ -[?7h -ÚÄÄ¿ÚÄÄÄÄÄ¿ ÚÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÂÄÄ¿ -³ÛÛÃÙÛÛÛÛÛÀÂÙÛÛÛÛÛÀÂÙÛÛÛÛÛÛ³Þݳ -ÚÄÄ¿ ³ÛÛ³ÛÛÜÜÜÛÛ³ÛÛÚÄ¿ÛÛ³ÛÛÜÜÜÜ ³Üܳ -³±±ÀÄÙ±Û³±ÛÚÄ¿±Û³±Û³ ³±Û³ ßßßß±Û³±Û³ -À¿²²²²²Ú´²²³ ³²²³²²³ ³²²³ß²²²²²ß³²²³ -ÀÄÄÄÄÄÙÀÄÄÙ ÀÄÄÁÄÄÙ ÀÄÄÁÄÄÄÄÄÄÄÁÄÄÙ -