org.pkl.thirdparty.jline.utils.StyleResolver Maven / Gradle / Ivy
Show all versions of pkl-tools Show documentation
/*
* Copyright (c) 2002-2018, 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.pkl.thirdparty.jline.utils;
import java.util.Locale;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.Objects.requireNonNull;
import static org.pkl.thirdparty.jline.utils.AttributedStyle.*;
// TODO: document style specification
/**
* Resolves named (or source-referenced) {@link AttributedStyle}.
*
* @since 3.6
*/
public class StyleResolver {
private static final Logger log = Logger.getLogger(StyleResolver.class.getName());
private final Function source;
public StyleResolver(final Function source) {
this.source = requireNonNull(source);
}
/**
* Returns the RGB color for the given name.
*
* Bright color can be specified with: {@code !} or {@code bright-}.
*
* Full xterm256 color can be specified with: {@code ~}.
* RGB colors can be specified with: {@code x} or {@code #} where {@code rgb} is
* a 24 bits hexadecimal color.
*
* @param name the name of the color
* @return color code, or {@code null} if unable to determine.
*/
private static Integer colorRgb(String name) {
name = name.toLowerCase(Locale.US);
// check hexadecimal color
if (name.charAt(0) == 'x' || name.charAt(0) == '#') {
try {
return Integer.parseInt(name.substring(1), 16);
} catch (NumberFormatException e) {
log.warning("Invalid hexadecimal color: " + name);
return null;
}
} else {
// load indexed color
Integer color = color(name);
if (color != null && color != -1) {
color = Colors.DEFAULT_COLORS_256[color];
}
return color;
}
}
/**
* Returns the color identifier for the given name.
*
* Bright color can be specified with: {@code !} or {@code bright-}.
*
* Full xterm256 color can be specified with: {@code ~}.
*
* @param name the name of the color
* @return color code, or {@code null} if unable to determine.
*/
private static Integer color(String name) {
int flags = 0;
if (name.equals("default")) {
return -1;
}
// extract bright flag from color name
else if (name.charAt(0) == '!') {
name = name.substring(1);
flags = BRIGHT;
} else if (name.startsWith("bright-")) {
name = name.substring(7);
flags = BRIGHT;
} else if (name.charAt(0) == '~') {
name = name.substring(1);
try {
return Colors.rgbColor(name);
} catch (IllegalArgumentException e) {
log.warning("Invalid style-color name: " + name);
return null;
}
}
switch (name) {
case "black":
case "k":
return flags + BLACK;
case "red":
case "r":
return flags + RED;
case "green":
case "g":
return flags + GREEN;
case "yellow":
case "y":
return flags + YELLOW;
case "blue":
case "b":
return flags + BLUE;
case "magenta":
case "m":
return flags + MAGENTA;
case "cyan":
case "c":
return flags + CYAN;
case "white":
case "w":
return flags + WHITE;
}
return null;
}
// TODO: could consider a small cache to reduce style calculations?
/**
* Resolve the given style specification.
*
* If for some reason the specification is invalid, then {@link AttributedStyle#DEFAULT} will be used.
*
* @param spec the specification
* @return the style
*/
public AttributedStyle resolve(final String spec) {
requireNonNull(spec);
if (log.isLoggable(Level.FINEST)) {
log.finest("Resolve: " + spec);
}
int i = spec.indexOf(":-");
if (i != -1) {
String[] parts = spec.split(":-");
return resolve(parts[0].trim(), parts[1].trim());
}
return apply(DEFAULT, spec);
}
/**
* Resolve the given style specification.
*
* If this resolves to {@link AttributedStyle#DEFAULT} then given default specification is used if non-null.
*
* @param spec the specification
* @param defaultSpec the default specifiaction
* @return the style
*/
public AttributedStyle resolve(final String spec, final String defaultSpec) {
requireNonNull(spec);
if (log.isLoggable(Level.FINEST)) {
log.finest(String.format("Resolve: %s; default: %s", spec, defaultSpec));
}
AttributedStyle style = apply(DEFAULT, spec);
if (style == DEFAULT && defaultSpec != null) {
style = apply(style, defaultSpec);
}
return style;
}
/**
* Apply style specification.
*
* @param style the style to apply to
* @param spec the specification
* @return the new style
*/
private AttributedStyle apply(AttributedStyle style, final String spec) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Apply: " + spec);
}
for (String item : spec.split(",")) {
item = item.trim();
if (item.isEmpty()) {
continue;
}
if (item.startsWith(".")) {
style = applyReference(style, item);
} else if (item.contains(":")) {
style = applyColor(style, item);
} else if (item.matches("[0-9]+(;[0-9]+)*")) {
style = applyAnsi(style, item);
} else {
style = applyNamed(style, item);
}
}
return style;
}
private AttributedStyle applyAnsi(final AttributedStyle style, final String spec) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Apply-ansi: " + spec);
}
return new AttributedStringBuilder()
.style(style)
.ansiAppend("\033[" + spec + "m")
.style();
}
/**
* Apply source-referenced named style.
*
* @param style the style to apply to
* @param spec the specification
* @return the new style
*/
private AttributedStyle applyReference(final AttributedStyle style, final String spec) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Apply-reference: " + spec);
}
if (spec.length() == 1) {
log.warning("Invalid style-reference; missing discriminator: " + spec);
} else {
String name = spec.substring(1);
String resolvedSpec = source.apply(name);
if (resolvedSpec != null) {
return apply(style, resolvedSpec);
}
// null is normal if source has not be configured with named style
}
return style;
}
/**
* Apply default named styles.
*
* @param style the style to apply to
* @param name the named style
* @return the new style
*/
private AttributedStyle applyNamed(final AttributedStyle style, final String name) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Apply-named: " + name);
}
// TODO: consider short aliases for named styles
switch (name.toLowerCase(Locale.US)) {
case "default":
return DEFAULT;
case "bold":
return style.bold();
case "faint":
return style.faint();
case "italic":
return style.italic();
case "underline":
return style.underline();
case "blink":
return style.blink();
case "inverse":
return style.inverse();
case "inverse-neg":
case "inverseneg":
return style.inverseNeg();
case "conceal":
return style.conceal();
case "crossed-out":
case "crossedout":
return style.crossedOut();
case "hidden":
return style.hidden();
default:
log.warning("Unknown style: " + name);
return style;
}
}
// TODO: consider simplify and always using StyleColor, for now for compat with other bits leaving syntax complexity
/**
* Apply color styles specification.
*
* @param style The style to apply to
* @param spec Color specification: {@code :}
* @return The new style
*/
private AttributedStyle applyColor(final AttributedStyle style, final String spec) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Apply-color: " + spec);
}
// extract color-mode:color-name
String[] parts = spec.split(":", 2);
String colorMode = parts[0].trim();
String colorName = parts[1].trim();
// resolve the color-name
Integer color;
// resolve and apply color-mode
switch (colorMode.toLowerCase(Locale.US)) {
case "foreground":
case "fg":
case "f":
color = color(colorName);
if (color == null) {
log.warning("Invalid color-name: " + colorName);
break;
}
return color >= 0 ? style.foreground(color) : style.foregroundDefault();
case "background":
case "bg":
case "b":
color = color(colorName);
if (color == null) {
log.warning("Invalid color-name: " + colorName);
break;
}
return color >= 0 ? style.background(color) : style.backgroundDefault();
case "foreground-rgb":
case "fg-rgb":
case "f-rgb":
color = colorRgb(colorName);
if (color == null) {
log.warning("Invalid color-name: " + colorName);
break;
}
return color >= 0 ? style.foregroundRgb(color) : style.foregroundDefault();
case "background-rgb":
case "bg-rgb":
case "b-rgb":
color = colorRgb(colorName);
if (color == null) {
log.warning("Invalid color-name: " + colorName);
break;
}
return color >= 0 ? style.backgroundRgb(color) : style.backgroundDefault();
default:
log.warning("Invalid color-mode: " + colorMode);
}
return style;
}
}