/*
* Copyright 2012-present Facebook, Inc.
*
* 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 com.facebook.buck.util;
import com.google.common.collect.FluentIterable;
import java.io.PrintStream;
import java.util.Collections;
/** Encapsulates the specifics of writing to a particular kind of terminal. */
public final class Ansi {
private static final String RESET = "\u001B[0m";
private static final String BOLD = "\u001B[1m";
private static final String BLACK = "\u001B[30m";
private static final String GREY = "\u001B[0;37m";
private static final String RED = "\u001B[31m";
private static final String YELLOW = "\u001B[33m";
private static final String GREEN = "\u001B[32m";
private static final String CYAN = "\u001B[36m";
private static final String ERROR_SEQUENCE = RED;
private static final String WARNING_SEQUENCE = YELLOW;
private static final String SUCCESS_SEQUENCE = GREEN;
private static final String INFORMATION_SEQUENCE = CYAN;
private static final String SUBTLE_SEQUENCE = GREY;
private static final String BACKGROUND_YELLOW = "\u001B[43m";
private static final String BACKGROUND_GREEN = "\u001B[42m";
private static final String HIGHLIGHTED_ERROR_SEQUENCE = RED;
private static final String HIGHLIGHTED_WARNING_SEQUENCE = BOLD + BACKGROUND_YELLOW + BLACK;
private static final String HIGHLIGHTED_SUCCESS_SEQUENCE = BOLD + BACKGROUND_GREEN + BLACK;
private static final String CURSOR_PREVIOUS_LINE = "\u001B[%dA";
private static final String ERASE_IN_LINE = "\u001B[%dK";
private static final String STOP_WRAPPING = "\u001B[?7l";
private static final String RESUME_WRAPPING = "\u001B[?7h";
private static final int ANSI_PREVIOUS_LINE_STRING_CACHE_MAX_LINES = 22;
private static final String[] ANSI_PREVIOUS_LINE_STRING_CACHE =
new String[ANSI_PREVIOUS_LINE_STRING_CACHE_MAX_LINES];
private static final String ANSI_ERASE_LINE = String.format(ERASE_IN_LINE, 2);
private final boolean isAnsiTerminal;
private final String clearLineString;
private static final Ansi noTtyAnsi = new Ansi(false /* isAnsiTerminal */);
private static final Ansi forceTtyAnsi = new Ansi(true /* isAnsiTerminal */);
/** Construct an Ansi object and conditionally enable fancy escape sequences. */
public Ansi(boolean isAnsiTerminal) {
this.isAnsiTerminal = isAnsiTerminal;
clearLineString = isAnsiTerminal ? ANSI_ERASE_LINE : "";
}
public static Ansi withoutTty() {
return noTtyAnsi;
}
public static Ansi forceTty() {
return forceTtyAnsi;
}
public boolean isAnsiTerminal() {
return isAnsiTerminal;
}
public String asErrorText(String text) {
return wrapWithColor(ERROR_SEQUENCE, text);
}
public String asWarningText(String text) {
return wrapWithColor(WARNING_SEQUENCE, text);
}
public String asSuccessText(String text) {
return wrapWithColor(SUCCESS_SEQUENCE, text);
}
public String asSubtleText(String text) {
return wrapWithColor(SUBTLE_SEQUENCE, text);
}
public String asInformationText(String text) {
return wrapWithColor(INFORMATION_SEQUENCE, text);
}
public String asGreenText(String text) {
return wrapWithColor(GREEN, text);
}
public String asRedText(String text) {
return wrapWithColor(RED, text);
}
public String getHighlightedWarningSequence() {
return isAnsiTerminal ? HIGHLIGHTED_ERROR_SEQUENCE : "";
}
public String getHighlightedResetSequence() {
return isAnsiTerminal ? RESET : "";
}
public String asHighlightedFailureText(String text) {
return wrapWithColor(HIGHLIGHTED_ERROR_SEQUENCE, text);
}
public String asHighlightedWarningText(String text) {
return wrapWithColor(HIGHLIGHTED_WARNING_SEQUENCE, text);
}
public String asHighlightedSuccessText(String text) {
return wrapWithColor(HIGHLIGHTED_SUCCESS_SEQUENCE, text);
}
public Iterable<String> asNoWrap(Iterable<String> textParts) {
if (isAnsiTerminal) {
return FluentIterable.from(Collections.singleton(STOP_WRAPPING))
.append(textParts)
.append(RESUME_WRAPPING);
} else {
return textParts;
}
}
public void printHighlightedSuccessText(PrintStream stream, String text) {
stream.print(asHighlightedSuccessText(text));
}
public void printlnHighlightedFailureText(PrintStream stream, String text) {
stream.println(asHighlightedFailureText(text));
}
public String asHighlightedStatusText(SeverityLevel level, String text) {
switch (level) {
case OK:
return asHighlightedSuccessText(text);
case WARNING:
return asHighlightedWarningText(text);
case ERROR:
return asHighlightedFailureText(text);
default:
String message = String.format("Unexpected SeverityLevel; cannot highlight '%s'!", level);
throw new IllegalArgumentException(message);
}
}
/** Moves the cursor {@code y} lines up. */
public String cursorPreviousLine(int y) {
if (!isAnsiTerminal) {
return "";
}
if (y >= ANSI_PREVIOUS_LINE_STRING_CACHE_MAX_LINES) {
return String.format(CURSOR_PREVIOUS_LINE, y);
}
synchronized (ANSI_PREVIOUS_LINE_STRING_CACHE) {
if (ANSI_PREVIOUS_LINE_STRING_CACHE[y] == null) {
ANSI_PREVIOUS_LINE_STRING_CACHE[y] = String.format(CURSOR_PREVIOUS_LINE, y);
}
return ANSI_PREVIOUS_LINE_STRING_CACHE[y];
}
}
/** Clears the line the cursor is currently on. */
public String clearLine() {
return clearLineString;
}
public static enum SeverityLevel {
OK,
WARNING,
ERROR
}
private String wrapWithColor(String color, String text) {
if (!isAnsiTerminal || text.length() == 0) {
return text;
}
// If there are not tabs at the start return a simple concatenation
if (text.charAt(0) != '\t') {
return color + text + RESET;
}
// Skip tabs, because they don't like being prefixed with color.
int firstNonTab = indexOfFirstNonTab(text);
if (firstNonTab == -1) {
return color + text + RESET;
}
return text.substring(0, firstNonTab) + color + text.substring(firstNonTab) + RESET;
}
/** @return the index of the first character that's not a tab, or -1 if none is found. */
private static int indexOfFirstNonTab(String s) {
final int length = s.length();
for (int i = 1; i < length; i++) {
if (s.charAt(i) != '\t') {
return i;
}
}
return -1;
}
}