org.fusesource.jansi.AnsiConsole Maven / Gradle / Ivy
Show all versions of log4j-core Show documentation
/*
* 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.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Locale;
import org.fusesource.jansi.internal.CLibrary;
import org.fusesource.jansi.internal.CLibrary.WinSize;
import org.fusesource.jansi.io.AnsiOutputStream;
import org.fusesource.jansi.io.AnsiProcessor;
import org.fusesource.jansi.io.FastBufferedOutputStream;
import org.fusesource.jansi.io.WindowsAnsiProcessor;
import org.fusesource.jansi.internal.Kernel32.CONSOLE_SCREEN_BUFFER_INFO;
import static org.fusesource.jansi.internal.CLibrary.ioctl;
import static org.fusesource.jansi.internal.CLibrary.isatty;
import static org.fusesource.jansi.internal.Kernel32.GetConsoleMode;
import static org.fusesource.jansi.internal.Kernel32.GetStdHandle;
import static org.fusesource.jansi.internal.Kernel32.STD_ERROR_HANDLE;
import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE;
import static org.fusesource.jansi.internal.Kernel32.SetConsoleMode;
import static org.fusesource.jansi.internal.Kernel32.GetConsoleScreenBufferInfo;
/**
* Provides consistent access to an ANSI aware console PrintStream or an ANSI codes stripping PrintStream
* if not on a terminal (see
* Jansi native
* CLibrary isatty(int)).
* The native library used is named jansi
and is loaded using HawtJNI Runtime
* Library
*
* @author Hiram Chirino
* @since 1.0
* @see #systemInstall()
* @see #out()
* @see #err()
* @see #ansiStream(boolean) for more details on ANSI mode selection
*/
public class AnsiConsole {
/**
* The default mode which Jansi will use, can be either force
, strip
* or default
(the default).
* If this property is set, it will override jansi.passthrough
,
* jansi.strip
and jansi.force
properties.
*/
public static final String JANSI_MODE = "jansi.mode";
/**
* Jansi mode specific to the standard output stream.
*/
public static final String JANSI_OUT_MODE = "jansi.out.mode";
/**
* Jansi mode specific to the standard error stream.
*/
public static final String JANSI_ERR_MODE = "jansi.err.mode";
/**
* Jansi mode value to strip all ansi sequences.
*/
public static final String JANSI_MODE_STRIP = "strip";
/**
* Jansi mode value to force ansi sequences to the stream even if it's not a terminal.
*/
public static final String JANSI_MODE_FORCE = "force";
/**
* Jansi mode value that output sequences if on a terminal, else strip them.
*/
public static final String JANSI_MODE_DEFAULT = "default";
/**
* The default color support that Jansi will use, can be either 16
,
* 256
or truecolor
. If not set, JANSI will try to
* autodetect the number of colors supported by the terminal by checking the
* COLORTERM
and TERM
variables.
*/
public static final String JANSI_COLORS = "jansi.colors";
/**
* Jansi colors specific to the standard output stream.
*/
public static final String JANSI_OUT_COLORS = "jansi.out.colors";
/**
* Jansi colors specific to the standard error stream.
*/
public static final String JANSI_ERR_COLORS = "jansi.err.colors";
/**
* Force the use of 16 colors. When using a 256-indexed color, or an RGB
* color, the color will be rounded to the nearest one from the 16 palette.
*/
public static final String JANSI_COLORS_16 = "16";
/**
* Force the use of 256 colors. When using an RGB color, the color will be
* rounded to the nearest one from the standard 256 palette.
*/
public static final String JANSI_COLORS_256 = "256";
/**
* Force the use of 24-bit colors.
*/
public static final String JANSI_COLORS_TRUECOLOR = "truecolor";
/**
* If the jansi.passthrough
system property is set to true, will not perform any transformation
* and any ansi sequence will be conveyed without any modification.
*
* @deprecated use {@link #JANSI_MODE} instead
*/
@Deprecated
public static final String JANSI_PASSTHROUGH = "jansi.passthrough";
/**
* If the jansi.strip
system property is set to true, and jansi.passthrough
* is not enabled, all ansi sequences will be stripped before characters are written to the output streams.
*
* @deprecated use {@link #JANSI_MODE} instead
*/
@Deprecated
public static final String JANSI_STRIP = "jansi.strip";
/**
* If the jansi.force
system property is set to true, and neither jansi.passthrough
* nor jansi.strip
are set, then ansi sequences will be printed to the output stream.
* This forces the behavior which is by default dependent on the output stream being a real console: if the
* output stream is redirected to a file or through a system pipe, ansi sequences are disabled by default.
*
* @deprecated use {@link #JANSI_MODE} instead
*/
@Deprecated
public static final String JANSI_FORCE = "jansi.force";
/**
* If the jansi.eager
system property is set to true, the system streams will be eagerly
* initialized, else the initialization is delayed until {@link #out()}, {@link #err()} or {@link #systemInstall()}
* is called.
*
* @deprecated this property has been added but only for backward compatibility.
* @since 2.1
*/
@Deprecated()
public static final String JANSI_EAGER = "jansi.eager";
/**
* If the jansi.noreset
system property is set to true, the attributes won't be
* reset when the streams are uninstalled.
*/
public static final String JANSI_NORESET = "jansi.noreset";
/**
* @deprecated this field will be made private in a future release, use {@link #sysOut()} instead
*/
@Deprecated
public static PrintStream system_out = System.out;
/**
* @deprecated this field will be made private in a future release, use {@link #out()} instead
*/
@Deprecated
public static PrintStream out;
/**
* @deprecated this field will be made private in a future release, use {@link #sysErr()} instead
*/
@Deprecated
public static PrintStream system_err = System.err;
/**
* @deprecated this field will be made private in a future release, use {@link #err()} instead
*/
@Deprecated
public static PrintStream err;
/**
* Try to find the width of the console for this process.
* Both output and error streams will be checked to determine the width.
* A value of 0 is returned if the width can not be determined.
* @since 2.2
*/
public static int getTerminalWidth() {
int w = out().getTerminalWidth();
if (w <= 0) {
w = err().getTerminalWidth();
}
return w;
}
static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win");
static final boolean IS_CYGWIN = IS_WINDOWS
&& System.getenv("PWD") != null
&& System.getenv("PWD").startsWith("/");
static final boolean IS_MSYSTEM = IS_WINDOWS
&& System.getenv("MSYSTEM") != null
&& (System.getenv("MSYSTEM").startsWith("MINGW")
|| System.getenv("MSYSTEM").equals("MSYS"));
static final boolean IS_CONEMU = IS_WINDOWS
&& System.getenv("ConEmuPID") != null;
static final int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
static {
if (getBoolean(JANSI_EAGER)) {
initStreams();
}
}
private static boolean initialized;
private static int installed;
private static int virtualProcessing;
private AnsiConsole() {
}
private static AnsiPrintStream ansiStream(boolean stdout) {
FileDescriptor descriptor = stdout ? FileDescriptor.out : FileDescriptor.err;
final OutputStream out = new FastBufferedOutputStream(new FileOutputStream(descriptor));
String enc = System.getProperty(stdout ? "sun.stdout.encoding" : "sun.stderr.encoding");
final boolean isatty;
boolean isAtty;
boolean withException;
final int fd = stdout ? CLibrary.STDOUT_FILENO : CLibrary.STDERR_FILENO;
try {
// If we can detect that stdout is not a tty.. then setup
// to strip the ANSI sequences..
isAtty = isatty(fd) != 0;
withException = false;
} 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.
isAtty = false;
withException = true;
}
isatty = isAtty;
final AnsiOutputStream.WidthSupplier width;
final AnsiProcessor processor;
final AnsiType type;
final AnsiOutputStream.IoRunnable installer;
final AnsiOutputStream.IoRunnable uninstaller;
if (!isatty) {
processor = null;
type = withException ? AnsiType.Unsupported : AnsiType.Redirected;
installer = uninstaller = null;
width = new AnsiOutputStream.ZeroWidthSupplier();
}
else if (IS_WINDOWS) {
final long console = GetStdHandle(stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
final int[] mode = new int[1];
final boolean isConsole = GetConsoleMode(console, mode) != 0;
if (isConsole
&& SetConsoleMode(console, mode[0] | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
SetConsoleMode(console, mode[0]); // set it back for now, but we know it works
processor = null;
type = AnsiType.VirtualTerminal;
installer = new AnsiOutputStream.IoRunnable() {
@Override
public void run() throws IOException {
virtualProcessing++;
SetConsoleMode(console, mode[0] | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
};
uninstaller = new AnsiOutputStream.IoRunnable() {
@Override
public void run() throws IOException {
if (--virtualProcessing == 0) {
SetConsoleMode(console, mode[0]);
}
}
};
}
else if ((IS_CONEMU || IS_CYGWIN || IS_MSYSTEM) && !isConsole) {
// ANSI-enabled ConEmu, Cygwin or MSYS(2) on Windows...
processor = null;
type = AnsiType.Native;
installer = uninstaller = null;
}
else {
// On Windows, when no ANSI-capable terminal is used, we know the console does not natively interpret ANSI
// codes but we can use jansi Kernel32 API for console
AnsiProcessor proc;
AnsiType ttype;
try {
proc = new WindowsAnsiProcessor(out, console);
ttype = AnsiType.Emulation;
} catch (Throwable ignore) {
// this happens when the stdout is being redirected to a file.
// Use the AnsiProcessor to strip out the ANSI escape sequences.
proc = new AnsiProcessor(out);
ttype = AnsiType.Unsupported;
}
processor = proc;
type = ttype;
installer = uninstaller = null;
}
width = new AnsiOutputStream.WidthSupplier() {
@Override
public int getTerminalWidth() {
CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
GetConsoleScreenBufferInfo(console, info);
return info.windowWidth();
}
};
}
// We must be on some Unix variant...
else {
// ANSI-enabled ConEmu, Cygwin or MSYS(2) on Windows...
processor = null;
type = AnsiType.Native;
installer = uninstaller = null;
width = new AnsiOutputStream.WidthSupplier() {
@Override
public int getTerminalWidth() {
WinSize sz = new WinSize();
ioctl(fd, CLibrary.TIOCGWINSZ, sz);
return sz.ws_col;
}
};
}
AnsiMode mode;
// If the jansi.mode property is set, use it
String jansiMode = System.getProperty(stdout ? JANSI_OUT_MODE : JANSI_ERR_MODE, System.getProperty(JANSI_MODE));
if (JANSI_MODE_FORCE.equals(jansiMode)) {
mode = AnsiMode.Force;
} else if (JANSI_MODE_STRIP.equals(jansiMode)) {
mode = AnsiMode.Strip;
} else if (jansiMode != null) {
mode = isatty ? AnsiMode.Default : AnsiMode.Strip;
}
// If the jansi.passthrough property is set, then don't interpret
// any of the ansi sequences.
else if (getBoolean(JANSI_PASSTHROUGH)) {
mode = AnsiMode.Force;
}
// If the jansi.strip property is set, then we just strip the
// the ansi escapes.
else if (getBoolean(JANSI_STRIP)) {
mode = AnsiMode.Strip;
}
// 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)
else if (getBoolean(JANSI_FORCE)) {
mode = AnsiMode.Force;
}
else {
mode = isatty ? AnsiMode.Default : AnsiMode.Strip;
}
AnsiColors colors;
String colorterm, term;
// If the jansi.colors property is set, use it
String jansiColors = System.getProperty(stdout ? JANSI_OUT_COLORS : JANSI_ERR_COLORS, System.getProperty(JANSI_COLORS));
if (JANSI_COLORS_TRUECOLOR.equals(jansiColors)) {
colors = AnsiColors.TrueColor;
} else if (JANSI_COLORS_256.equals(jansiColors)) {
colors = AnsiColors.Colors256;
} else if (jansiColors != null) {
colors = AnsiColors.Colors16;
}
// If the COLORTERM env variable contains "truecolor" or "24bit", assume true color support
// see https://gist.github.com/XVilka/8346728#true-color-detection
else if ((colorterm = System.getenv("COLORTERM")) != null
&& (colorterm.contains("truecolor") || colorterm.contains("24bit"))) {
colors = AnsiColors.TrueColor;
}
// check the if TERM contains -direct
else if ((term = System.getenv("TERM")) != null && term.contains("-direct")) {
colors = AnsiColors.TrueColor;
}
// check the if TERM contains -256color
else if (term != null && term.contains("-256color")) {
colors = AnsiColors.Colors256;
}
// else defaults to 16 colors
else {
colors = AnsiColors.Colors16;
}
// If the jansi.noreset property is not set, reset the attributes
// when the stream is closed
boolean resetAtUninstall = type != AnsiType.Unsupported && !getBoolean(JANSI_NORESET);
Charset cs = Charset.defaultCharset();
if (enc != null) {
try {
cs = Charset.forName(enc);
} catch (UnsupportedCharsetException e) {
}
}
return newPrintStream(new AnsiOutputStream(out, width, mode, processor, type, colors, cs,
installer, uninstaller, resetAtUninstall), cs.name());
}
private static AnsiPrintStream newPrintStream(AnsiOutputStream out, String enc) {
if (enc != null) {
try {
return new AnsiPrintStream(out, true, enc);
} catch (UnsupportedEncodingException e) {
}
}
return new AnsiPrintStream(out, true);
}
static boolean getBoolean(String name) {
boolean result = false;
try {
String val = System.getProperty(name);
result = val.isEmpty() || Boolean.parseBoolean(val);
} catch (IllegalArgumentException e) {
} catch (NullPointerException e) {
}
return result;
}
/**
* 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 AnsiPrintStream out() {
initStreams();
return (AnsiPrintStream) out;
}
/**
* Access to the original System.out stream before ansi streams were installed.
*
* @return the originial System.out print stream
*/
public static PrintStream sysOut() {
return system_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 AnsiPrintStream err() {
initStreams();
return (AnsiPrintStream) err;
}
/**
* Access to the original System.err stream before ansi streams were installed.
*
* @return the originial System.err print stream
*/
public static PrintStream sysErr() {
return system_err;
}
/**
* Install AnsiConsole.out()
to System.out
and
* AnsiConsole.err()
to System.err
.
* @see #systemUninstall()
*/
synchronized static public void systemInstall() {
installed++;
if (installed == 1) {
initStreams();
try {
((AnsiPrintStream) out).install();
((AnsiPrintStream) err).install();
} catch (IOException e) {
throw new IOError(e);
}
System.setOut(out);
System.setErr(err);
}
}
/**
* check if the streams have been installed or not
*/
synchronized public static boolean isInstalled() {
return installed > 0;
}
/**
* undo a previous {@link #systemInstall()}. If {@link #systemInstall()} was called
* multiple times, {@link #systemUninstall()} must be called the same number of times before
* it is actually uninstalled.
*/
synchronized public static void systemUninstall() {
installed--;
if (installed == 0) {
try {
((AnsiPrintStream) out).uninstall();
((AnsiPrintStream) err).uninstall();
} catch (IOException e) {
throw new IOError(e);
}
initialized = false;
System.setOut(system_out);
System.setErr(system_err);
}
}
/**
* Initialize the out/err ansi-enabled streams
*/
synchronized static void initStreams()
{
if ( !initialized )
{
out = ansiStream(true);
err = ansiStream(false);
initialized = true;
}
}
;
}