diff --git a/jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java b/jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java
index 0b7ae030..5f720d0a 100644
--- a/jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java
+++ b/jansi/src/main/java/org/fusesource/jansi/AnsiConsole.java
@@ -15,8 +15,6 @@
*/
package org.fusesource.jansi;
-import org.fusesource.jansi.internal.Kernel32;
-
import static org.fusesource.jansi.internal.CLibrary.STDERR_FILENO;
import static org.fusesource.jansi.internal.CLibrary.STDOUT_FILENO;
import static org.fusesource.jansi.internal.CLibrary.isatty;
@@ -25,8 +23,6 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
import java.util.Locale;
/**
@@ -34,6 +30,7 @@
*
* @author Hiram Chirino
* @since 1.0
+ * @see #wrapPrintStream(PrintStream, int)
*/
public class AnsiConsole {
@@ -58,24 +55,10 @@ public class AnsiConsole {
static final JansiOutputType JANSI_STDOUT_TYPE;
static final JansiOutputType JANSI_STDERR_TYPE;
static {
- String charset = Charset.defaultCharset().name();
- if (IS_WINDOWS && !IS_CYGWIN && !IS_MINGW) {
- int codepage = Kernel32.GetConsoleOutputCP();
- //http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
- if (Charset.isSupported("ms" + codepage)) {
- charset = "ms" + codepage;
- } else if (Charset.isSupported("cp" + codepage)) {
- charset = "cp" + codepage;
- }
- }
- try {
- out = new PrintStream(wrapOutputStream(system_out), false, charset);
- JANSI_STDOUT_TYPE = jansiOutputType;
- err = new PrintStream(wrapErrorOutputStream(system_err), false, charset);
- JANSI_STDERR_TYPE = jansiOutputType;
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
+ out = wrapSystemOut(system_out);
+ JANSI_STDOUT_TYPE = jansiOutputType;
+ err = wrapSystemErr(system_err);
+ JANSI_STDERR_TYPE = jansiOutputType;
}
private static int installed;
@@ -83,6 +66,7 @@ public class AnsiConsole {
private AnsiConsole() {
}
+ @Deprecated
public static OutputStream wrapOutputStream(final OutputStream stream) {
try {
return wrapOutputStream(stream, STDOUT_FILENO);
@@ -91,6 +75,15 @@ public static OutputStream wrapOutputStream(final OutputStream stream) {
}
}
+ public static PrintStream wrapSystemOut(final PrintStream ps) {
+ try {
+ return wrapPrintStream(ps, STDOUT_FILENO);
+ } catch (Throwable ignore) {
+ return wrapPrintStream(ps, 1);
+ }
+ }
+
+ @Deprecated
public static OutputStream wrapErrorOutputStream(final OutputStream stream) {
try {
return wrapOutputStream(stream, STDERR_FILENO);
@@ -99,6 +92,15 @@ public static OutputStream wrapErrorOutputStream(final OutputStream stream) {
}
}
+ public static PrintStream wrapSystemErr(final PrintStream ps) {
+ try {
+ return wrapPrintStream(ps, STDERR_FILENO);
+ } catch (Throwable ignore) {
+ return wrapPrintStream(ps, 2);
+ }
+ }
+
+ @Deprecated
public static OutputStream wrapOutputStream(final OutputStream stream, int fileno) {
// If the jansi.passthrough property is set, then don't interpret
@@ -161,6 +163,83 @@ public void close() throws IOException {
};
}
+ /**
+ * Wrap PrintStream applying rules in following order:
+ * if jansi.passthrough
is true
, don't wrap but just passthrough (console is
+ * expected to natively support ANSI escape codes),
+ * if jansi.strip
is true
, just strip ANSI escape codes inconditionally,
+ * if OS is Windows and terminal is not Cygwin or Mingw, wrap as WindowsAnsiPrintStream to process ANSI escape codes,
+ * if file descriptor is a terminal (see isatty(int)
) or jansi.force
is true
,
+ * just passthrough,
+ * else strip ANSI escape codes (not a terminal).
+ *
+ *
+ * @param ps original PrintStream to wrap
+ * @param fileno file descriptor
+ * @return wrapped PrintStream depending on OS and system properties
+ */
+ public static PrintStream wrapPrintStream(final PrintStream ps, int fileno) {
+
+ // If the jansi.passthrough property is set, then don't interpret
+ // any of the ansi sequences.
+ if (Boolean.getBoolean("jansi.passthrough")) {
+ jansiOutputType = JansiOutputType.PASSTHROUGH;
+ return ps;
+ }
+
+ // If the jansi.strip property is set, then we just strip the
+ // the ansi escapes.
+ if (Boolean.getBoolean("jansi.strip")) {
+ jansiOutputType = JansiOutputType.STRIP_ANSI;
+ return new AnsiPrintStream(ps);
+ }
+
+ if (IS_WINDOWS && !IS_CYGWIN && !IS_MINGW) {
+
+ // On windows we know the console does not interpret ANSI codes..
+ try {
+ jansiOutputType = JansiOutputType.WINDOWS;
+ return new WindowsAnsiPrintStream(ps);
+ } catch (Throwable ignore) {
+ // this happens when JNA is not in the path.. or
+ // this happens when the stdout is being redirected to a file.
+ }
+
+ // Use the AnsiPrintStream to strip out the ANSI escape sequences.
+ jansiOutputType = JansiOutputType.STRIP_ANSI;
+ return new AnsiPrintStream(ps);
+ }
+
+ // We must be on some Unix variant, including Cygwin or MSYS(2) on Windows...
+ 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..
+ if (!forceColored && isatty(fileno) == 0) {
+ jansiOutputType = JansiOutputType.STRIP_ANSI;
+ return new AnsiPrintStream(ps);
+ }
+ } catch (Throwable ignore) {
+ // These errors happen if the JNI lib is not available for your platform.
+ // But since we are on ANSI friendly platform, assume the user is on the console.
+ }
+
+ // 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.
+ jansiOutputType = JansiOutputType.RESET_ANSI_AT_CLOSE;
+ return new FilterPrintStream(ps) {
+ @Override
+ public void close() {
+ ps.print(AnsiPrintStream.RESET_CODE);
+ ps.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
@@ -168,6 +247,7 @@ public void close() throws IOException {
* sequences.
*
* @return a PrintStream which is ANSI aware.
+ * @see #wrapPrintStream(PrintStream, int)
*/
public static PrintStream out() {
return out;
@@ -180,6 +260,7 @@ public static PrintStream out() {
* sequences.
*
* @return a PrintStream which is ANSI aware.
+ * @see #wrapPrintStream(PrintStream, int)
*/
public static PrintStream err() {
return err;
diff --git a/jansi/src/main/java/org/fusesource/jansi/AnsiPrintStream.java b/jansi/src/main/java/org/fusesource/jansi/AnsiPrintStream.java
new file mode 100644
index 00000000..0d1b7f45
--- /dev/null
+++ b/jansi/src/main/java/org/fusesource/jansi/AnsiPrintStream.java
@@ -0,0 +1,757 @@
+/*
+ * Copyright (C) 2009-2017 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 org.fusesource.jansi;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * A ANSI print stream extracts ANSI escape codes written to
+ * a print stream and calls corresponding process*
methods.
+ *
+ * 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: process*
methods
+ * are empty. Subclasses should actually perform the ANSI escape behaviors
+ * by implementing active code in process*
methods.
+ *
+ * @author Hiram Chirino
+ * @author Joris Kuipers
+ * @since 1.7
+ */
+public class AnsiPrintStream extends FilterPrintStream {
+
+ public static final String RESET_CODE = "\033[0m";
+
+ public AnsiPrintStream(PrintStream ps) {
+ super(ps);
+ }
+
+ private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100;
+ private final 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;
+ private static final int LOOKING_FOR_NEXT_ARG = 2;
+ private static final int LOOKING_FOR_STR_ARG_END = 3;
+ private static final int LOOKING_FOR_INT_ARG_END = 4;
+ private static final int LOOKING_FOR_OSC_COMMAND = 5;
+ private static final int LOOKING_FOR_OSC_COMMAND_END = 6;
+ private static final int LOOKING_FOR_OSC_PARAM = 7;
+ private static final int LOOKING_FOR_ST = 8;
+
+ int state = LOOKING_FOR_FIRST_ESC_CHAR;
+
+ private static final int FIRST_ESC_CHAR = 27;
+ private static final int SECOND_ESC_CHAR = '[';
+ private static final int SECOND_OSC_CHAR = ']';
+ private static final int BEL = 7;
+ private static final int SECOND_ST_CHAR = '\\';
+
+ @Override
+ protected boolean filter(int data) {
+ switch (state) {
+ case LOOKING_FOR_FIRST_ESC_CHAR:
+ if (data == FIRST_ESC_CHAR) {
+ buffer[pos++] = (byte) data;
+ state = LOOKING_FOR_SECOND_ESC_CHAR;
+ return false;
+ }
+ return true;
+
+ case LOOKING_FOR_SECOND_ESC_CHAR:
+ buffer[pos++] = (byte) data;
+ if (data == SECOND_ESC_CHAR) {
+ state = LOOKING_FOR_NEXT_ARG;
+ } else if (data == SECOND_OSC_CHAR) {
+ state = LOOKING_FOR_OSC_COMMAND;
+ } else {
+ reset(false);
+ }
+ break;
+
+ case LOOKING_FOR_NEXT_ARG:
+ buffer[pos++] = (byte) data;
+ if ('"' == data) {
+ startOfValue = pos - 1;
+ state = LOOKING_FOR_STR_ARG_END;
+ } else if ('0' <= data && data <= '9') {
+ startOfValue = pos - 1;
+ state = LOOKING_FOR_INT_ARG_END;
+ } else if (';' == data) {
+ options.add(null);
+ } else if ('?' == data) {
+ options.add('?');
+ } else if ('=' == data) {
+ options.add('=');
+ } else {
+ reset(processEscapeCommand(options, data));
+ }
+ break;
+ default:
+ break;
+
+ case LOOKING_FOR_INT_ARG_END:
+ buffer[pos++] = (byte) data;
+ if (!('0' <= data && data <= '9')) {
+ String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
+ Integer value = new Integer(strValue);
+ options.add(value);
+ if (data == ';') {
+ state = LOOKING_FOR_NEXT_ARG;
+ } else {
+ reset(processEscapeCommand(options, data));
+ }
+ }
+ break;
+
+ case LOOKING_FOR_STR_ARG_END:
+ buffer[pos++] = (byte) data;
+ if ('"' != data) {
+ String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
+ options.add(value);
+ if (data == ';') {
+ state = LOOKING_FOR_NEXT_ARG;
+ } else {
+ reset(processEscapeCommand(options, data));
+ }
+ }
+ break;
+
+ case LOOKING_FOR_OSC_COMMAND:
+ buffer[pos++] = (byte) data;
+ if ('0' <= data && data <= '9') {
+ startOfValue = pos - 1;
+ state = LOOKING_FOR_OSC_COMMAND_END;
+ } else {
+ reset(false);
+ }
+ break;
+
+ case LOOKING_FOR_OSC_COMMAND_END:
+ buffer[pos++] = (byte) data;
+ if (';' == data) {
+ String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
+ Integer value = new Integer(strValue);
+ options.add(value);
+ startOfValue = pos;
+ state = LOOKING_FOR_OSC_PARAM;
+ } else if ('0' <= data && data <= '9') {
+ // already pushed digit to buffer, just keep looking
+ } else {
+ // oops, did not expect this
+ reset(false);
+ }
+ break;
+
+ case LOOKING_FOR_OSC_PARAM:
+ buffer[pos++] = (byte) data;
+ if (BEL == data) {
+ String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
+ options.add(value);
+ reset(processOperatingSystemCommand(options));
+ } else if (FIRST_ESC_CHAR == data) {
+ state = LOOKING_FOR_ST;
+ } else {
+ // just keep looking while adding text
+ }
+ break;
+
+ case LOOKING_FOR_ST:
+ buffer[pos++] = (byte) data;
+ if (SECOND_ST_CHAR == data) {
+ String value = new String(buffer, startOfValue, (pos - 2) - startOfValue, Charset.defaultCharset());
+ options.add(value);
+ reset(processOperatingSystemCommand(options));
+ } else {
+ state = LOOKING_FOR_OSC_PARAM;
+ }
+ break;
+ }
+
+ // Is it just too long?
+ if (pos >= buffer.length) {
+ reset(false);
+ }
+ return false;
+ }
+
+ /**
+ * Resets all state to continue with regular parsing
+ * @param skipBuffer if current buffer should be skipped or written to out
+ */
+ private void reset(boolean skipBuffer) {
+ if (!skipBuffer) {
+ ps.write(buffer, 0, pos);
+ }
+ pos = 0;
+ startOfValue = 0;
+ options.clear();
+ state = LOOKING_FOR_FIRST_ESC_CHAR;
+ }
+
+ /**
+ * Helper for processEscapeCommand() to iterate over integer options
+ * @param optionsIterator the underlying iterator
+ * @throws IOException if no more non-null values left
+ */
+ private int getNextOptionInt(Iterator optionsIterator) throws IOException {
+ for (;;) {
+ if (!optionsIterator.hasNext())
+ throw new IllegalArgumentException();
+ Object arg = optionsIterator.next();
+ if (arg != null)
+ return (Integer) arg;
+ }
+ }
+
+ /**
+ *
+ * @param options
+ * @param command
+ * @return true if the escape command was processed.
+ */
+ private boolean processEscapeCommand(ArrayList options, int command) {
+ try {
+ switch (command) {
+ case 'A':
+ processCursorUp(optionInt(options, 0, 1));
+ return true;
+ case 'B':
+ processCursorDown(optionInt(options, 0, 1));
+ return true;
+ case 'C':
+ processCursorRight(optionInt(options, 0, 1));
+ return true;
+ case 'D':
+ processCursorLeft(optionInt(options, 0, 1));
+ return true;
+ case 'E':
+ processCursorDownLine(optionInt(options, 0, 1));
+ return true;
+ case 'F':
+ processCursorUpLine(optionInt(options, 0, 1));
+ return true;
+ case 'G':
+ processCursorToColumn(optionInt(options, 0));
+ return true;
+ case 'H':
+ case 'f':
+ processCursorTo(optionInt(options, 0, 1), optionInt(options, 1, 1));
+ return true;
+ case 'J':
+ processEraseScreen(optionInt(options, 0, 0));
+ return true;
+ case 'K':
+ processEraseLine(optionInt(options, 0, 0));
+ return true;
+ case 'L':
+ processInsertLine(optionInt(options, 0, 1));
+ return true;
+ case 'M':
+ processDeleteLine(optionInt(options, 0, 1));
+ return true;
+ case 'S':
+ processScrollUp(optionInt(options, 0, 1));
+ return true;
+ case 'T':
+ processScrollDown(optionInt(options, 0, 1));
+ return true;
+ case 'm':
+ // Validate all options are ints...
+ for (Object next : options) {
+ if (next != null && next.getClass() != Integer.class) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ int count = 0;
+ Iterator optionsIterator = options.iterator();
+ while (optionsIterator.hasNext()) {
+ Object next = optionsIterator.next();
+ if (next != null) {
+ count++;
+ int value = (Integer) next;
+ if (30 <= value && value <= 37) {
+ processSetForegroundColor(value - 30);
+ } else if (40 <= value && value <= 47) {
+ processSetBackgroundColor(value - 40);
+ } else if (90 <= value && value <= 97) {
+ processSetForegroundColor(value - 90, true);
+ } else if (100 <= value && value <= 107) {
+ processSetBackgroundColor(value - 100, true);
+ } else if (value == 38 || value == 48) {
+ // extended color like `esc[38;5;m` or `esc[38;2;;;m`
+ int arg2or5 = getNextOptionInt(optionsIterator);
+ if (arg2or5 == 2) {
+ // 24 bit color style like `esc[38;2;;;m`
+ int r = getNextOptionInt(optionsIterator);
+ int g = getNextOptionInt(optionsIterator);
+ int b = getNextOptionInt(optionsIterator);
+ if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
+ if (value == 38)
+ processSetForegroundColorExt(r, g, b);
+ else
+ processSetBackgroundColorExt(r, g, b);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+ else if (arg2or5 == 5) {
+ // 256 color style like `esc[38;5;m`
+ int paletteIndex = getNextOptionInt(optionsIterator);
+ if (paletteIndex >= 0 && paletteIndex <= 255) {
+ if (value == 38)
+ processSetForegroundColorExt(paletteIndex);
+ else
+ processSetBackgroundColorExt(paletteIndex);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+ else {
+ throw new IllegalArgumentException();
+ }
+ } else {
+ switch (value) {
+ case 39:
+ processDefaultTextColor();
+ break;
+ case 49:
+ processDefaultBackgroundColor();
+ break;
+ case 0:
+ processAttributeRest();
+ break;
+ default:
+ processSetAttribute(value);
+ }
+ }
+ }
+ }
+ if (count == 0) {
+ processAttributeRest();
+ }
+ return true;
+ case 's':
+ processSaveCursorPosition();
+ return true;
+ case 'u':
+ processRestoreCursorPosition();
+ return true;
+
+ default:
+ if ('a' <= command && 'z' <= command) {
+ processUnknownExtension(options, command);
+ return true;
+ }
+ if ('A' <= command && 'Z' <= command) {
+ processUnknownExtension(options, command);
+ return true;
+ }
+ return false;
+ }
+ } catch (IllegalArgumentException ignore) {
+ } catch (IOException ioe) {
+ setError();
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @param options
+ * @return true if the operating system command was processed.
+ */
+ private boolean processOperatingSystemCommand(ArrayList options) {
+ int command = optionInt(options, 0);
+ String label = (String) options.get(1);
+ // for command > 2 label could be composed (i.e. contain ';'), but we'll leave
+ // it to processUnknownOperatingSystemCommand implementations to handle that
+ try {
+ switch (command) {
+ case 0:
+ processChangeIconNameAndWindowTitle(label);
+ return true;
+ case 1:
+ processChangeIconName(label);
+ return true;
+ case 2:
+ processChangeWindowTitle(label);
+ return true;
+
+ default:
+ // not exactly unknown, but not supported through dedicated process methods:
+ processUnknownOperatingSystemCommand(command, label);
+ return true;
+ }
+ } catch (IllegalArgumentException ignore) {
+ }
+ return false;
+ }
+
+ /**
+ * Process CSI u
ANSI code, corresponding to RCP – Restore Cursor Position
+ * @throws IOException
+ */
+ protected void processRestoreCursorPosition() throws IOException {
+ }
+
+ /**
+ * Process CSI s
ANSI code, corresponding to SCP – Save Cursor Position
+ * @throws IOException
+ */
+ protected void processSaveCursorPosition() throws IOException {
+ }
+
+ /**
+ * Process CSI s
ANSI code, corresponding to IL – Insert Line
+ * @throws IOException
+ */
+ protected void processInsertLine(int optionInt) throws IOException {
+ }
+
+ /**
+ * Process CSI s
ANSI code, corresponding to DL – Delete Line
+ * @throws IOException
+ */
+ protected void processDeleteLine(int optionInt) throws IOException {
+ }
+
+ /**
+ * Process CSI n T
ANSI code, corresponding to SD – Scroll Down
+ * @throws IOException
+ */
+ protected void processScrollDown(int optionInt) throws IOException {
+ }
+
+ /**
+ * Process CSI n U
ANSI code, corresponding to SU – Scroll Up
+ * @throws IOException
+ */
+ protected void processScrollUp(int optionInt) throws IOException {
+ }
+
+ protected static final int ERASE_SCREEN_TO_END = 0;
+ protected static final int ERASE_SCREEN_TO_BEGINING = 1;
+ protected static final int ERASE_SCREEN = 2;
+
+ /**
+ * Process CSI n J
ANSI code, corresponding to ED – Erase in Display
+ * @throws IOException
+ */
+ protected void processEraseScreen(int eraseOption) throws IOException {
+ }
+
+ protected static final int ERASE_LINE_TO_END = 0;
+ protected static final int ERASE_LINE_TO_BEGINING = 1;
+ protected static final int ERASE_LINE = 2;
+
+ /**
+ * Process CSI n K
ANSI code, corresponding to ED – Erase in Line
+ * @throws IOException
+ */
+ protected void processEraseLine(int eraseOption) throws IOException {
+ }
+
+ protected static final int ATTRIBUTE_INTENSITY_BOLD = 1; // Intensity: Bold
+ protected static final int ATTRIBUTE_INTENSITY_FAINT = 2; // Intensity; Faint not widely supported
+ protected static final int ATTRIBUTE_ITALIC = 3; // Italic; on not widely supported. Sometimes treated as inverse.
+ protected static final int ATTRIBUTE_UNDERLINE = 4; // Underline; Single
+ protected static final int ATTRIBUTE_BLINK_SLOW = 5; // Blink; Slow less than 150 per minute
+ protected static final int ATTRIBUTE_BLINK_FAST = 6; // Blink; Rapid MS-DOS ANSI.SYS; 150 per minute or more
+ protected static final int ATTRIBUTE_NEGATIVE_ON = 7; // Image; Negative inverse or reverse; swap foreground and background
+ protected static final int ATTRIBUTE_CONCEAL_ON = 8; // Conceal on
+ protected static final int ATTRIBUTE_UNDERLINE_DOUBLE = 21; // Underline; Double not widely supported
+ protected static final int ATTRIBUTE_INTENSITY_NORMAL = 22; // Intensity; Normal not bold and not faint
+ protected static final int ATTRIBUTE_UNDERLINE_OFF = 24; // Underline; None
+ protected static final int ATTRIBUTE_BLINK_OFF = 25; // Blink; off
+ @Deprecated
+ protected static final int ATTRIBUTE_NEGATIVE_Off = 27; // Image; Positive
+ protected static final int ATTRIBUTE_NEGATIVE_OFF = 27; // Image; Positive
+ protected static final int ATTRIBUTE_CONCEAL_OFF = 28; // Reveal conceal off
+
+ /**
+ * process SGR
other than 0
(reset), 30-39
(foreground),
+ * 40-49
(background), 90-97
(foreground high intensity) or
+ * 100-107
(background high intensity)
+ * @param attribute
+ * @throws IOException
+ * @see #processAttributeRest()
+ * @see #processSetForegroundColor(int)
+ * @see #processSetForegroundColor(int, boolean)
+ * @see #processSetForegroundColorExt(int)
+ * @see #processSetForegroundColorExt(int, int, int)
+ * @see #processDefaultTextColor()
+ * @see #processDefaultBackgroundColor()
+ */
+ protected void processSetAttribute(int attribute) throws IOException {
+ }
+
+ protected static final int BLACK = 0;
+ protected static final int RED = 1;
+ protected static final int GREEN = 2;
+ protected static final int YELLOW = 3;
+ protected static final int BLUE = 4;
+ protected static final int MAGENTA = 5;
+ protected static final int CYAN = 6;
+ protected static final int WHITE = 7;
+
+ /**
+ * process SGR 30-37
corresponding to Set text color (foreground)
.
+ * @param color the text color
+ * @throws IOException
+ */
+ protected void processSetForegroundColor(int color) throws IOException {
+ processSetForegroundColor(color, false);
+ }
+
+ /**
+ * process SGR 30-37
or SGR 90-97
corresponding to
+ * Set text color (foreground)
either in normal mode or high intensity.
+ * @param color the text color
+ * @param bright is high intensity?
+ * @throws IOException
+ */
+ protected void processSetForegroundColor(int color, boolean bright) throws IOException {
+ }
+
+ /**
+ * process SGR 38
corresponding to extended set text color (foreground)
+ * with a palette of 255 colors.
+ * @param paletteIndex the text color in the palette
+ * @throws IOException
+ */
+ protected void processSetForegroundColorExt(int paletteIndex) throws IOException {
+ }
+
+ /**
+ * process SGR 38
corresponding to extended set text color (foreground)
+ * with a 24 bits RGB definition of the color.
+ * @param r red
+ * @param g green
+ * @param b blue
+ * @throws IOException
+ */
+ protected void processSetForegroundColorExt(int r, int g, int b) throws IOException {
+ }
+
+ /**
+ * process SGR 40-47
corresponding to Set background color
.
+ * @param color the background color
+ * @throws IOException
+ */
+ protected void processSetBackgroundColor(int color) throws IOException {
+ processSetBackgroundColor(color, false);
+ }
+
+ /**
+ * process SGR 40-47
or SGR 100-107
corresponding to
+ * Set background color
either in normal mode or high intensity.
+ * @param color the background color
+ * @param bright is high intensity?
+ * @throws IOException
+ */
+ protected void processSetBackgroundColor(int color, boolean bright) throws IOException {
+ }
+
+ /**
+ * process SGR 48
corresponding to extended set background color
+ * with a palette of 255 colors.
+ * @param paletteIndex the background color in the palette
+ * @throws IOException
+ */
+ protected void processSetBackgroundColorExt(int paletteIndex) throws IOException {
+ }
+
+ /**
+ * process SGR 48
corresponding to extended set background color
+ * with a 24 bits RGB definition of the color.
+ * @param r red
+ * @param g green
+ * @param b blue
+ * @throws IOException
+ */
+ protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException {
+ }
+
+ /**
+ * process SGR 39
corresponding to Default text color (foreground)
+ * @throws IOException
+ */
+ protected void processDefaultTextColor() throws IOException {
+ }
+
+ /**
+ * process SGR 49
corresponding to Default background color
+ * @throws IOException
+ */
+ protected void processDefaultBackgroundColor() throws IOException {
+ }
+
+ /**
+ * process SGR 0
corresponding to Reset / Normal
+ * @throws IOException
+ */
+ protected void processAttributeRest() throws IOException {
+ }
+
+ /**
+ * process CSI n ; m H
corresponding to CUP – Cursor Position
or
+ * CSI n ; m f
corresponding to HVP – Horizontal and Vertical Position
+ * @param row
+ * @param col
+ * @throws IOException
+ */
+ protected void processCursorTo(int row, int col) throws IOException {
+ }
+
+ /**
+ * process CSI n G
corresponding to CHA – Cursor Horizontal Absolute
+ * @param x the column
+ * @throws IOException
+ */
+ protected void processCursorToColumn(int x) throws IOException {
+ }
+
+ /**
+ * process CSI n F
corresponding to CPL – Cursor Previous Line
+ * @param count line count
+ * @throws IOException
+ */
+ protected void processCursorUpLine(int count) throws IOException {
+ }
+
+ /**
+ * process CSI n E
corresponding to CNL – Cursor Next Line
+ * @param count line count
+ * @throws IOException
+ */
+ protected void processCursorDownLine(int count) throws IOException {
+ // Poor mans impl..
+ for (int i = 0; i < count; i++) {
+ print('\n');
+ }
+ }
+
+ /**
+ * process CSI n D
corresponding to CUB – Cursor Back
+ * @param count
+ * @throws IOException
+ */
+ protected void processCursorLeft(int count) throws IOException {
+ }
+
+ /**
+ * process CSI n C
corresponding to CUF – Cursor Forward
+ * @param count
+ * @throws IOException
+ */
+ protected void processCursorRight(int count) throws IOException {
+ // Poor mans impl..
+ for (int i = 0; i < count; i++) {
+ print(' ');
+ }
+ }
+
+ /**
+ * process CSI n B
corresponding to CUD – Cursor Down
+ * @param count
+ * @throws IOException
+ */
+ protected void processCursorDown(int count) throws IOException {
+ }
+
+ /**
+ * process CSI n A
corresponding to CUU – Cursor Up
+ * @param count
+ * @throws IOException
+ */
+ protected void processCursorUp(int count) throws IOException {
+ }
+
+ protected void processUnknownExtension(ArrayList options, int command) {
+ }
+
+ /**
+ * process OSC 0;text BEL
corresponding to Change Window and Icon label
+ * @param label
+ */
+ protected void processChangeIconNameAndWindowTitle(String label) {
+ processChangeIconName(label);
+ processChangeWindowTitle(label);
+ }
+
+ /**
+ * process OSC 1;text BEL
corresponding to Change Icon label
+ * @param label
+ */
+ protected void processChangeIconName(String label) {
+ }
+
+ /**
+ * process OSC 2;text BEL
corresponding to Change Window title
+ * @param label
+ */
+ protected void processChangeWindowTitle(String label) {
+ }
+
+ /**
+ * Process unknown OSC
command.
+ * @param command
+ * @param param
+ */
+ protected void processUnknownOperatingSystemCommand(int command, String param) {
+ }
+
+ private int optionInt(ArrayList options, int index) {
+ if (options.size() <= index)
+ throw new IllegalArgumentException();
+ Object value = options.get(index);
+ if (value == null)
+ throw new IllegalArgumentException();
+ if (!value.getClass().equals(Integer.class))
+ throw new IllegalArgumentException();
+ return (Integer) value;
+ }
+
+ private int optionInt(ArrayList options, int index, int defaultValue) {
+ if (options.size() > index) {
+ Object value = options.get(index);
+ if (value == null) {
+ return defaultValue;
+ }
+ return (Integer) value;
+ }
+ return defaultValue;
+ }
+
+ @Override
+ public void close() {
+ ps.print(RESET_CODE);
+ flush();
+ super.close();
+ }
+
+}
diff --git a/jansi/src/main/java/org/fusesource/jansi/FilterPrintStream.java b/jansi/src/main/java/org/fusesource/jansi/FilterPrintStream.java
new file mode 100644
index 00000000..2084e9af
--- /dev/null
+++ b/jansi/src/main/java/org/fusesource/jansi/FilterPrintStream.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2009-2017 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 org.fusesource.jansi;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * A PrintStream filtering to another PrintStream, without making any assumption about encoding.
+ *
+ * @author Hervé Boutemy
+ * @since 1.17
+ */
+public class FilterPrintStream extends PrintStream
+{
+ private static final String NEWLINE = System.getProperty("line.separator");
+ protected final PrintStream ps;
+
+ public FilterPrintStream(PrintStream ps)
+ {
+ super( new OutputStream() {
+
+ @Override
+ public void write(int b) throws IOException {
+ throw new RuntimeException("Direct OutputStream use forbidden: must go through delegate PrintStream");
+ }
+
+ });
+ this.ps = ps;
+ }
+
+ /**
+ * Filter the content
+ * @param data
+ * @return true
if the data is not filtered then has to be printed to delegate PrintStream
+ */
+ protected boolean filter(int data)
+ {
+ return true;
+ }
+
+ @Override
+ public void write(int data)
+ {
+ if (filter(data))
+ {
+ ps.write(data);
+ }
+ }
+
+ @Override
+ public void write(byte[] buf, int off, int len)
+ {
+ for (int i = 0; i < len; i++)
+ {
+ write(buf[off + i]);
+ }
+ }
+
+ @Override
+ public boolean checkError()
+ {
+ return super.checkError() || ps.checkError();
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+ ps.close();
+ }
+
+ @Override
+ public void flush()
+ {
+ super.flush();
+ ps.flush();
+ }
+
+ private void write(char buf[]) {
+ for (char c : buf)
+ {
+ if (filter(c))
+ {
+ ps.print(c);
+ }
+ }
+ }
+
+ private void write(String s) {
+ char[] buf = new char[s.length()];
+ s.getChars(0, s.length(), buf, 0);
+ write(buf);
+ }
+
+ private void newLine() {
+ write(NEWLINE);
+ }
+
+ /* Methods that do not terminate lines */
+
+ @Override
+ public void print(boolean b) {
+ write(b ? "true" : "false");
+ }
+
+ @Override
+ public void print(char c) {
+ write(String.valueOf(c));
+ }
+
+ @Override
+ public void print(int i) {
+ write(String.valueOf(i));
+ }
+
+ @Override
+ public void print(long l) {
+ write(String.valueOf(l));
+ }
+
+ @Override
+ public void print(float f) {
+ write(String.valueOf(f));
+ }
+
+ @Override
+ public void print(double d) {
+ write(String.valueOf(d));
+ }
+
+ @Override
+ public void print(char s[]) {
+ write(s);
+ }
+
+ @Override
+ public void print(String s) {
+ if (s == null) {
+ s = "null";
+ }
+ write(s);
+ }
+
+ @Override
+ public void print(Object obj) {
+ write(String.valueOf(obj));
+ }
+
+
+ /* Methods that do terminate lines */
+
+ @Override
+ public void println() {
+ newLine();
+ }
+
+ @Override
+ public void println(boolean x) {
+ synchronized (this) {
+ print(x);
+ newLine();
+ }
+ }
+
+ @Override
+ public void println(char x) {
+ synchronized (this) {
+ print(x);
+ newLine();
+ }
+ }
+
+ @Override
+ public void println(int x) {
+ synchronized (this) {
+ print(x);
+ newLine();
+ }
+ }
+
+ @Override
+ public void println(long x) {
+ synchronized (this) {
+ print(x);
+ newLine();
+ }
+ }
+
+ @Override
+ public void println(float x) {
+ synchronized (this) {
+ print(x);
+ newLine();
+ }
+ }
+
+ @Override
+ public void println(double x) {
+ synchronized (this) {
+ print(x);
+ newLine();
+ }
+ }
+
+ @Override
+ public void println(char x[]) {
+ synchronized (this) {
+ print(x);
+ newLine();
+ }
+ }
+
+ @Override
+ public void println(String x) {
+ synchronized (this) {
+ print(x);
+ newLine();
+ }
+ }
+
+ @Override
+ public void println(Object x) {
+ String s = String.valueOf(x);
+ synchronized (this) {
+ print(s);
+ newLine();
+ }
+ }
+}
diff --git a/jansi/src/main/java/org/fusesource/jansi/WindowsAnsiPrintStream.java b/jansi/src/main/java/org/fusesource/jansi/WindowsAnsiPrintStream.java
new file mode 100644
index 00000000..84c0dff2
--- /dev/null
+++ b/jansi/src/main/java/org/fusesource/jansi/WindowsAnsiPrintStream.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2009-2017 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 org.fusesource.jansi;
+
+import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_BLUE;
+import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_GREEN;
+import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_INTENSITY;
+import static org.fusesource.jansi.internal.Kernel32.BACKGROUND_RED;
+import static org.fusesource.jansi.internal.Kernel32.CHAR_INFO;
+import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_BLUE;
+import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_GREEN;
+import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_INTENSITY;
+import static org.fusesource.jansi.internal.Kernel32.FOREGROUND_RED;
+import static org.fusesource.jansi.internal.Kernel32.FillConsoleOutputAttribute;
+import static org.fusesource.jansi.internal.Kernel32.FillConsoleOutputCharacterW;
+import static org.fusesource.jansi.internal.Kernel32.GetConsoleScreenBufferInfo;
+import static org.fusesource.jansi.internal.Kernel32.GetStdHandle;
+import static org.fusesource.jansi.internal.Kernel32.SMALL_RECT;
+import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE;
+import static org.fusesource.jansi.internal.Kernel32.ScrollConsoleScreenBuffer;
+import static org.fusesource.jansi.internal.Kernel32.SetConsoleCursorPosition;
+import static org.fusesource.jansi.internal.Kernel32.SetConsoleTextAttribute;
+import static org.fusesource.jansi.internal.Kernel32.SetConsoleTitle;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.fusesource.jansi.internal.WindowsSupport;
+import org.fusesource.jansi.internal.Kernel32.CONSOLE_SCREEN_BUFFER_INFO;
+import org.fusesource.jansi.internal.Kernel32.COORD;
+
+/**
+ * A Windows ANSI escape processor, that uses JNA to access native platform
+ * API's to change the console attributes.
+ *
+ * @since 1.7
+ * @author Hiram Chirino
+ * @author Joris Kuipers
+ */
+public final class WindowsAnsiPrintStream extends AnsiPrintStream {
+
+ private static final long console = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ private static final short FOREGROUND_BLACK = 0;
+ private static final short FOREGROUND_YELLOW = (short) (FOREGROUND_RED | FOREGROUND_GREEN);
+ private static final short FOREGROUND_MAGENTA = (short) (FOREGROUND_BLUE | FOREGROUND_RED);
+ private static final short FOREGROUND_CYAN = (short) (FOREGROUND_BLUE | FOREGROUND_GREEN);
+ private static final short FOREGROUND_WHITE = (short) (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
+
+ private static final short BACKGROUND_BLACK = 0;
+ private static final short BACKGROUND_YELLOW = (short) (BACKGROUND_RED | BACKGROUND_GREEN);
+ private static final short BACKGROUND_MAGENTA = (short) (BACKGROUND_BLUE | BACKGROUND_RED);
+ private static final short BACKGROUND_CYAN = (short) (BACKGROUND_BLUE | BACKGROUND_GREEN);
+ private static final short BACKGROUND_WHITE = (short) (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE);
+
+ private static final short[] ANSI_FOREGROUND_COLOR_MAP = {
+ FOREGROUND_BLACK,
+ FOREGROUND_RED,
+ FOREGROUND_GREEN,
+ FOREGROUND_YELLOW,
+ FOREGROUND_BLUE,
+ FOREGROUND_MAGENTA,
+ FOREGROUND_CYAN,
+ FOREGROUND_WHITE,
+ };
+
+ private static final short[] ANSI_BACKGROUND_COLOR_MAP = {
+ BACKGROUND_BLACK,
+ BACKGROUND_RED,
+ BACKGROUND_GREEN,
+ BACKGROUND_YELLOW,
+ BACKGROUND_BLUE,
+ BACKGROUND_MAGENTA,
+ BACKGROUND_CYAN,
+ BACKGROUND_WHITE,
+ };
+
+ private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
+ private final short originalColors;
+
+ private boolean negative;
+ private short savedX = -1;
+ private short savedY = -1;
+
+ public WindowsAnsiPrintStream(PrintStream ps) throws IOException {
+ super(ps);
+ getConsoleInfo();
+ originalColors = info.attributes;
+ }
+
+ private void getConsoleInfo() throws IOException {
+ ps.flush();
+ if (GetConsoleScreenBufferInfo(console, info) == 0) {
+ throw new IOException("Could not get the screen info: " + WindowsSupport.getLastErrorMessage());
+ }
+ if (negative) {
+ info.attributes = invertAttributeColors(info.attributes);
+ }
+ }
+
+ private void applyAttribute() throws IOException {
+ ps.flush();
+ short attributes = info.attributes;
+ if (negative) {
+ attributes = invertAttributeColors(attributes);
+ }
+ if (SetConsoleTextAttribute(console, attributes) == 0) {
+ throw new IOException(WindowsSupport.getLastErrorMessage());
+ }
+ }
+
+ private short invertAttributeColors(short attributes) {
+ // Swap the the Foreground and Background bits.
+ int fg = 0x000F & attributes;
+ fg <<= 4;
+ int bg = 0X00F0 & attributes;
+ bg >>= 4;
+ attributes = (short) ((attributes & 0xFF00) | fg | bg);
+ return attributes;
+ }
+
+ private void applyCursorPosition() throws IOException {
+ if (SetConsoleCursorPosition(console, info.cursorPosition.copy()) == 0) {
+ throw new IOException(WindowsSupport.getLastErrorMessage());
+ }
+ }
+
+ @Override
+ protected void processEraseScreen(int eraseOption) throws IOException {
+ getConsoleInfo();
+ int[] written = new int[1];
+ switch (eraseOption) {
+ case ERASE_SCREEN:
+ COORD topLeft = new COORD();
+ topLeft.x = 0;
+ topLeft.y = info.window.top;
+ int screenLength = info.window.height() * info.size.x;
+ FillConsoleOutputAttribute(console, originalColors, screenLength, topLeft, written);
+ FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft, written);
+ break;
+ case ERASE_SCREEN_TO_BEGINING:
+ COORD topLeft2 = new COORD();
+ topLeft2.x = 0;
+ topLeft2.y = info.window.top;
+ int lengthToCursor = (info.cursorPosition.y - info.window.top) * info.size.x
+ + info.cursorPosition.x;
+ FillConsoleOutputAttribute(console, originalColors, lengthToCursor, topLeft2, written);
+ FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2, written);
+ break;
+ case ERASE_SCREEN_TO_END:
+ int lengthToEnd = (info.window.bottom - info.cursorPosition.y) * info.size.x +
+ (info.size.x - info.cursorPosition.x);
+ FillConsoleOutputAttribute(console, originalColors, lengthToEnd, info.cursorPosition.copy(), written);
+ FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition.copy(), written);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void processEraseLine(int eraseOption) throws IOException {
+ getConsoleInfo();
+ int[] written = new int[1];
+ switch (eraseOption) {
+ case ERASE_LINE:
+ COORD leftColCurrRow = info.cursorPosition.copy();
+ leftColCurrRow.x = 0;
+ FillConsoleOutputAttribute(console, originalColors, info.size.x, leftColCurrRow, written);
+ FillConsoleOutputCharacterW(console, ' ', info.size.x, leftColCurrRow, written);
+ break;
+ case ERASE_LINE_TO_BEGINING:
+ COORD leftColCurrRow2 = info.cursorPosition.copy();
+ leftColCurrRow2.x = 0;
+ FillConsoleOutputAttribute(console, originalColors, info.cursorPosition.x, leftColCurrRow2, written);
+ FillConsoleOutputCharacterW(console, ' ', info.cursorPosition.x, leftColCurrRow2, written);
+ break;
+ case ERASE_LINE_TO_END:
+ int lengthToLastCol = info.size.x - info.cursorPosition.x;
+ FillConsoleOutputAttribute(console, originalColors, lengthToLastCol, info.cursorPosition.copy(), written);
+ FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition.copy(), written);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void processCursorLeft(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition.x = (short) Math.max(0, info.cursorPosition.x - count);
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorRight(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition.x = (short) Math.min(info.window.width(), info.cursorPosition.x + count);
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorDown(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition.y = (short) Math.min(Math.max(0, info.size.y - 1), info.cursorPosition.y + count);
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorUp(int count) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition.y = (short) Math.max(info.window.top, info.cursorPosition.y - count);
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorTo(int row, int col) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition.y = (short) Math.max(info.window.top, Math.min(info.size.y, info.window.top + row - 1));
+ info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), col - 1));
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processCursorToColumn(int x) throws IOException {
+ getConsoleInfo();
+ info.cursorPosition.x = (short) Math.max(0, Math.min(info.window.width(), x - 1));
+ applyCursorPosition();
+ }
+
+ @Override
+ protected void processSetForegroundColor(int color, boolean bright) throws IOException {
+ info.attributes = (short) ((info.attributes & ~0x0007) | ANSI_FOREGROUND_COLOR_MAP[color]);
+ info.attributes = (short) ((info.attributes & ~FOREGROUND_INTENSITY) | (bright ? FOREGROUND_INTENSITY : 0));
+ applyAttribute();
+ }
+
+ @Override
+ protected void processSetBackgroundColor(int color, boolean bright) throws IOException {
+ info.attributes = (short) ((info.attributes & ~0x0070) | ANSI_BACKGROUND_COLOR_MAP[color]);
+ info.attributes = (short) ((info.attributes & ~BACKGROUND_INTENSITY) | (bright ? BACKGROUND_INTENSITY : 0));
+ applyAttribute();
+ }
+
+ @Override
+ protected void processDefaultTextColor() throws IOException {
+ info.attributes = (short) ((info.attributes & ~0x000F) | (originalColors & 0xF));
+ info.attributes = (short) (info.attributes & ~FOREGROUND_INTENSITY);
+ applyAttribute();
+ }
+
+ @Override
+ protected void processDefaultBackgroundColor() throws IOException {
+ info.attributes = (short) ((info.attributes & ~0x00F0) | (originalColors & 0xF0));
+ info.attributes = (short) (info.attributes & ~BACKGROUND_INTENSITY);
+ applyAttribute();
+ }
+
+ @Override
+ protected void processAttributeRest() throws IOException {
+ info.attributes = (short) ((info.attributes & ~0x00FF) | originalColors);
+ this.negative = false;
+ applyAttribute();
+ }
+
+ @Override
+ protected void processSetAttribute(int attribute) throws IOException {
+ switch (attribute) {
+ case ATTRIBUTE_INTENSITY_BOLD:
+ info.attributes = (short) (info.attributes | FOREGROUND_INTENSITY);
+ applyAttribute();
+ break;
+ case ATTRIBUTE_INTENSITY_NORMAL:
+ info.attributes = (short) (info.attributes & ~FOREGROUND_INTENSITY);
+ applyAttribute();
+ break;
+
+ // Yeah, setting the background intensity is not underlining.. but it's best we can do
+ // using the Windows console API
+ case ATTRIBUTE_UNDERLINE:
+ info.attributes = (short) (info.attributes | BACKGROUND_INTENSITY);
+ applyAttribute();
+ break;
+ case ATTRIBUTE_UNDERLINE_OFF:
+ info.attributes = (short) (info.attributes & ~BACKGROUND_INTENSITY);
+ applyAttribute();
+ break;
+
+ case ATTRIBUTE_NEGATIVE_ON:
+ negative = true;
+ applyAttribute();
+ break;
+ case ATTRIBUTE_NEGATIVE_OFF:
+ negative = false;
+ applyAttribute();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void processSaveCursorPosition() throws IOException {
+ getConsoleInfo();
+ savedX = info.cursorPosition.x;
+ savedY = info.cursorPosition.y;
+ }
+
+ @Override
+ protected void processRestoreCursorPosition() throws IOException {
+ // restore only if there was a save operation first
+ if (savedX != -1 && savedY != -1) {
+ ps.flush();
+ info.cursorPosition.x = savedX;
+ info.cursorPosition.y = savedY;
+ applyCursorPosition();
+ }
+ }
+
+ @Override
+ protected void processInsertLine(int optionInt) throws IOException {
+ getConsoleInfo();
+ SMALL_RECT scroll = info.window.copy();
+ scroll.top = info.cursorPosition.y;
+ COORD org = new COORD();
+ org.x = 0;
+ org.y = (short)(info.cursorPosition.y + optionInt);
+ CHAR_INFO info = new CHAR_INFO();
+ info.attributes = originalColors;
+ info.unicodeChar = ' ';
+ if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) {
+ throw new IOException(WindowsSupport.getLastErrorMessage());
+ }
+ }
+
+ @Override
+ protected void processDeleteLine(int optionInt) throws IOException {
+ getConsoleInfo();
+ SMALL_RECT scroll = info.window.copy();
+ scroll.top = info.cursorPosition.y;
+ COORD org = new COORD();
+ org.x = 0;
+ org.y = (short)(info.cursorPosition.y - optionInt);
+ CHAR_INFO info = new CHAR_INFO();
+ info.attributes = originalColors;
+ info.unicodeChar = ' ';
+ if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) {
+ throw new IOException(WindowsSupport.getLastErrorMessage());
+ }
+ }
+
+ @Override
+ protected void processChangeWindowTitle(String label) {
+ SetConsoleTitle(label);
+ }
+}
diff --git a/jansi/src/test/java/org/fusesource/jansi/FilterPrintStreamTest.java b/jansi/src/test/java/org/fusesource/jansi/FilterPrintStreamTest.java
new file mode 100644
index 00000000..25b2283b
--- /dev/null
+++ b/jansi/src/test/java/org/fusesource/jansi/FilterPrintStreamTest.java
@@ -0,0 +1,30 @@
+package org.fusesource.jansi;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import org.junit.Test;
+
+public class FilterPrintStreamTest
+{
+ @Test
+ public void testPrintMethods()
+ throws Exception
+ {
+ PrintStream ps = new FilterPrintStream(System.out);
+ ps.println("String");
+ ps.println('€');
+ ps.flush();
+ }
+
+ @Test
+ public void testWrite()
+ throws Exception
+ {
+ OutputStream os = new FilterPrintStream(System.out);
+ os.write('A');
+ os.write('B');
+ os.write("€".getBytes() );
+ os.flush();
+ }
+}
\ No newline at end of file