Styles.java
/*
* Copyright (c) 2002-2021, the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package org.jline.builtins;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.jline.utils.StyleResolver;
import static org.jline.builtins.SyntaxHighlighter.REGEX_TOKEN_NAME;
public class Styles {
public static final String NANORC_THEME = "NANORC_THEME";
protected static final List<String> ANSI_STYLES = Arrays.asList(
"blink",
"bold",
"conceal",
"crossed-out",
"crossedout",
"faint",
"hidden",
"inverse",
"inverse-neg",
"inverseneg",
"italic",
"underline");
private static final String DEFAULT_LS_COLORS = "di=1;91:ex=1;92:ln=1;96:fi=";
private static final String DEFAULT_HELP_COLORS = "ti=1;34:co=1:ar=3:op=33";
private static final String DEFAULT_PRNT_COLORS = "th=1;34:rn=1;34:rs=,~grey15:mk=1;34:em=31:vs=32";
private static final String LS_COLORS = "LS_COLORS";
private static final String HELP_COLORS = "HELP_COLORS";
private static final String PRNT_COLORS = "PRNT_COLORS";
private static final String KEY = "([a-z]{2}|\\*\\.[a-zA-Z0-9]+)";
private static final String VALUE = "(([!~#]?[a-zA-Z0-9]+[a-z0-9-;]*)?|" + REGEX_TOKEN_NAME + ")";
private static final String VALUES = VALUE + "(," + VALUE + ")*";
private static final Pattern STYLE_ELEMENT_SEPARATOR = Pattern.compile(":");
private static final Pattern STYLE_ELEMENT_PATTERN = Pattern.compile(KEY + "=" + VALUES);
public static StyleResolver lsStyle() {
return style(LS_COLORS, DEFAULT_LS_COLORS);
}
public static StyleResolver helpStyle() {
return style(HELP_COLORS, DEFAULT_HELP_COLORS);
}
public static StyleResolver prntStyle() {
return style(PRNT_COLORS, DEFAULT_PRNT_COLORS);
}
public static boolean isStylePattern(String style) {
final String[] styleElements = STYLE_ELEMENT_SEPARATOR.split(style);
return Arrays.stream(styleElements)
.allMatch(element -> element.isEmpty()
|| STYLE_ELEMENT_PATTERN.matcher(element).matches());
}
public static StyleResolver style(String name, String defStyle) {
String style = consoleOption(name);
if (style == null) {
style = defStyle;
}
return style(style);
}
private static ConsoleOptionGetter optionGetter() {
try {
return (ConsoleOptionGetter) Class.forName("org.jline.console.SystemRegistry")
.getDeclaredMethod("get")
.invoke(null);
} catch (Exception ignore) {
}
return null;
}
private static <T> T consoleOption(String name, T defVal) {
T out = defVal;
ConsoleOptionGetter cog = optionGetter();
if (cog != null) {
out = cog.consoleOption(name, defVal);
}
return out;
}
private static String consoleOption(String name) {
String out = null;
ConsoleOptionGetter cog = optionGetter();
if (cog != null) {
out = (String) cog.consoleOption(name);
if (out != null && !isStylePattern(out)) {
out = null;
}
}
if (out == null) {
out = System.getenv(name);
if (out != null && !isStylePattern(out)) {
out = null;
}
}
return out;
}
public static StyleResolver style(String style) {
Map<String, String> colors = Arrays.stream(style.split(":"))
.collect(Collectors.toMap(s -> s.substring(0, s.indexOf('=')), s -> s.substring(s.indexOf('=') + 1)));
return new StyleResolver(new StyleCompiler(colors)::getStyle);
}
public static class StyleCompiler {
private static final String ANSI_VALUE = "[0-9]*(;[0-9]+){0,2}";
private static final String COLORS_24BIT = "[#x][0-9a-fA-F]{6}";
private static final List<String> COLORS_8 =
Arrays.asList("white", "black", "red", "blue", "green", "yellow", "magenta", "cyan");
// https://github.com/lhmouse/nano-win/commit/a7aab18dfeef8a0e8073d5fa420677dc8fe548da
private static final Map<String, Integer> COLORS_NANO = new HashMap<>();
static {
COLORS_NANO.put("pink", 204);
COLORS_NANO.put("purple", 163);
COLORS_NANO.put("mauve", 134);
COLORS_NANO.put("lagoon", 38);
COLORS_NANO.put("mint", 48);
COLORS_NANO.put("lime", 148);
COLORS_NANO.put("peach", 215);
COLORS_NANO.put("orange", 208);
COLORS_NANO.put("latte", 137);
}
private final Map<String, String> colors;
private final Map<String, String> tokenColors;
private final boolean nanoStyle;
public StyleCompiler(Map<String, String> colors) {
this(colors, false);
}
public StyleCompiler(Map<String, String> colors, boolean nanoStyle) {
this.colors = colors;
this.nanoStyle = nanoStyle;
this.tokenColors = consoleOption(NANORC_THEME, new HashMap<>());
}
public String getStyle(String reference) {
String rawStyle = colors.get(reference);
if (rawStyle == null) {
return null;
} else if (rawStyle.matches(REGEX_TOKEN_NAME)) {
rawStyle = tokenColors.getOrDefault(rawStyle, "normal");
} else if (!nanoStyle && rawStyle.matches(ANSI_VALUE)) {
return rawStyle;
}
StringBuilder out = new StringBuilder();
boolean first = true;
boolean fg = true;
for (String s : rawStyle.split(",")) {
if (s.trim().isEmpty()) {
fg = false;
continue;
}
if (!first) {
out.append(",");
}
if (ANSI_STYLES.contains(s)) {
out.append(s);
} else if (COLORS_8.contains(s)
|| COLORS_NANO.containsKey(s)
|| s.startsWith("light")
|| s.startsWith("bright")
|| s.startsWith("~")
|| s.startsWith("!")
|| s.matches("\\d+")
|| s.matches(COLORS_24BIT)
|| s.equals("normal")
|| s.equals("default")) {
if (s.matches(COLORS_24BIT)) {
if (fg) {
out.append("fg-rgb:");
} else {
out.append("bg-rgb:");
}
out.append(s);
} else if (s.matches("\\d+") || COLORS_NANO.containsKey(s)) {
if (fg) {
out.append("38;5;");
} else {
out.append("48;5;");
}
out.append(s.matches("\\d+") ? s : COLORS_NANO.get(s).toString());
} else {
if (fg) {
out.append("fg:");
} else {
out.append("bg:");
}
if (COLORS_8.contains(s) || s.startsWith("~") || s.startsWith("!") || s.startsWith("bright-")) {
out.append(s);
} else if (s.startsWith("light")) {
out.append("!").append(s.substring(5));
} else if (s.startsWith("bright")) {
out.append("!").append(s.substring(6));
} else {
out.append("default");
}
}
fg = false;
}
first = false;
}
return out.toString();
}
}
}