All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.console.output.Ansi Maven / Gradle / Ivy

/*
 * 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");
 * 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.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;

import dorkbox.console.Console;
import dorkbox.console.util.posix.CLibraryPosix;
import dorkbox.console.util.windows.Kernel32;
import dorkbox.util.OS;

/**
 * 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 Hiram Chirino */ @SuppressWarnings({"unused", "WeakerAccess"}) public class Ansi { private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Console.class); private static final PrintStream original_out = System.out; private static final PrintStream original_err = System.err; public static final PrintStream out = createPrintStream(original_out, 1); // STDOUT_FILENO; public static final PrintStream err = createPrintStream(original_err, 2); // STDERR_FILENO static { // make SURE that our console in/out/err are correctly setup BEFORE accessing methods in this class Console.getVersion(); System.setOut(out); System.setErr(err); // don't forget we have to shut down the ansi console as well Thread shutdownThread = new Thread() { @Override public void run() { // called when the JVM is shutting down. restoreSystemStreams(); } }; shutdownThread.setName("Console ANSI stream Shutdown"); Runtime.getRuntime().addShutdownHook(shutdownThread); } private static final String NEW_LINE = System.getProperty("line.separator"); private final StringBuilder builder; private final ArrayList attributeOptions = new ArrayList(8); /** * Restores System.err/out PrintStreams to their ORIGINAL configuration. Useful when using ANSI functionality but do not want to * hook into the system. */ public static void restoreSystemStreams() { System.setOut(original_out); System.setErr(original_err); } /** * Creates a new Ansi object */ public static Ansi ansi() { return new Ansi(); } /** * Creates a new Ansi object from the specified StringBuilder */ public static Ansi ansi(StringBuilder builder) { return new Ansi(builder); } /** * Creates a new Ansi object of the specified length */ public static Ansi ansi(int size) { return new Ansi(size); } /** * Creates a new Ansi object from the specified parent */ public static Ansi ansi(Ansi ansi) { return new Ansi(ansi); } /** * Creates a new Ansi object */ public Ansi() { this(new StringBuilder()); } /** * Creates a new Ansi object from the parent. */ public Ansi(Ansi parent) { this(new StringBuilder(parent.builder)); attributeOptions.addAll(parent.attributeOptions); } /** * Creates a new Ansi object of the specified length */ public Ansi(int size) { this(new StringBuilder(size)); reset(); // always reset a NEW Ansi object (w/ no parent) } /** * Creates a new Ansi object from the specified StringBuilder */ public Ansi(StringBuilder builder) { this.builder = builder; } /** * 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) { 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); } } 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) */ public Ansi cursor(final int x, final int y) { return appendEscapeSequence(AnsiOutputStream.CURSOR_POS, x, y); } /** * Moves the cursor to row 1, column 1 (top left corner). */ public 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 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()); } /** * 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 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 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(); builder.append(value); return this; } /** * Appends a boolean * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final boolean value) { flushAttributes(); builder.append(value); return this; } /** * Appends a char * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final char value) { flushAttributes(); builder.append(value); 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[] valueArray, final int offset, final int length) { flushAttributes(); 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(); builder.append(value); 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(); builder.append(value, start, end); return this; } /** * Appends a CharSequence * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final CharSequence value) { flushAttributes(); builder.append(value); return this; } /** * Appends a double * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final double value) { flushAttributes(); builder.append(value); return this; } /** * Appends a float * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final float value) { flushAttributes(); builder.append(value); return this; } /** * Appends a int * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final int value) { flushAttributes(); builder.append(value); return this; } /** * Appends a long * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final long value) { flushAttributes(); builder.append(value); return this; } /** * Appends a Object * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final Object value) { flushAttributes(); builder.append(value); return this; } /** * Appends a StringBuilder * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final StringBuilder value) { flushAttributes(); builder.append(value); return this; } /** * Appends a StringBuffer * * @param value value to be appended to the ANSI stream * @return this */ public Ansi a(final StringBuffer value) { flushAttributes(); builder.append(value); return this; } /** * Appends a new line * * @return this */ public Ansi newline() { flushAttributes(); builder.append(NEW_LINE); 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(); builder.append(String.format(pattern, args)); return this; } /** * Uses the {@link AnsiRenderer} to generate the ANSI escape sequences for the supplied text. */ public Ansi render(final String text) { a(AnsiRenderer.render(text)); return this; } /** * String formats and renders the supplied arguments. * Uses the {@link AnsiRenderer} to generate the ANSI escape sequences. */ public Ansi render(final String text, final Object... args) { a(String.format(AnsiRenderer.render(text), args)); return this; } @Override public String toString() { flushAttributes(); return builder.toString(); } /////////////////////////////////////////////////////////////////// // Private Helper Methods /////////////////////////////////////////////////////////////////// private static final char FIRST_ESC_CHAR = 27; private static final char SECOND_ESC_CHAR = '['; private Ansi appendEscapeSequence(final char command) { flushAttributes(); builder.append(FIRST_ESC_CHAR); builder.append(SECOND_ESC_CHAR); builder.append(command); return this; } private Ansi appendEscapeSequence(final char command, final int option) { flushAttributes(); builder.append(FIRST_ESC_CHAR); builder.append(SECOND_ESC_CHAR); builder.append(option); builder.append(command); return this; } private Ansi appendEscapeSequence(final char command, final Object... options) { flushAttributes(); return _appendEscapeSequence(command, options); } private void flushAttributes() { if( attributeOptions.isEmpty() ) { return; } if (attributeOptions.size() == 1 && attributeOptions.get(0) == ATTRIBUTE_RESET) { builder.append(FIRST_ESC_CHAR); builder.append(SECOND_ESC_CHAR); builder.append(AnsiOutputStream.TEXT_ATTRIBUTE); } else { _appendEscapeSequence(AnsiOutputStream.TEXT_ATTRIBUTE, attributeOptions.toArray()); } attributeOptions.clear(); } private Ansi _appendEscapeSequence(final char command, final Object... options) { builder.append(FIRST_ESC_CHAR); builder.append(SECOND_ESC_CHAR); int size = options.length; for (int i = 0; i < size; i++) { if (i != 0) { builder.append(';'); } if (options[i] != null) { builder.append(options[i]); } } builder.append(command); return this; } private static boolean isXterm() { String term = System.getenv("TERM"); return "xterm".equalsIgnoreCase(term); } private static PrintStream createPrintStream(final OutputStream stream, final int fileno) { String type = fileno == 1 ? "OUT" : "ERR"; if (!Console.ENABLE_ANSI) { // Use the ANSIOutputStream to strip out the ANSI escape sequences. return getStripPrintStream(stream, type); } // intellij idea console supports ANSI colors... but NOT REALLY! (they are off) if (System.getProperty("idea.launcher.bin.path") != null // "run" || System.getProperty("java.class.path").contains("idea_rt.jar") // "debug" ) { // Use the ANSIOutputStream to strip out the ANSI escape sequences. return getStripPrintStream(stream, type); } if (!isXterm()) { if (OS.isWindows()) { // 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 defaultPrintStream(stream, type); } // On windows we know the console does not interpret ANSI codes.. try { PrintStream printStream = new PrintStream(new WindowsAnsiOutputStream(stream, fileno)); if (logger.isDebugEnabled()) { logger.debug("Created a Windows ANSI PrintStream for {}", type); } return printStream; } 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 (!Console.FORCE_ENABLE_ANSI) { return getStripPrintStream(stream, type); } } 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 (!Console.FORCE_ENABLE_ANSI && CLibraryPosix.isatty(fileno) == 0) { return getStripPrintStream(stream, type); } } 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 defaultPrintStream(stream, type); } private static PrintStream getStripPrintStream(final OutputStream stream, final String type) { if (logger.isDebugEnabled()) { logger.debug("Created a strip-ANSI PrintStream for {}", type); } return new PrintStream(new AnsiOutputStream(stream)); } private static PrintStream defaultPrintStream(final OutputStream stream, final String type) { if (logger.isDebugEnabled()) { logger.debug("Created ANSI PrintStream for {}", type); } return new PrintStream(new FilterOutputStream(stream) { @Override public void close() throws IOException { write(AnsiOutputStream.RESET_CODE); flush(); super.close(); } }); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy