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

org.jline.utils.StyleResolver Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2002-2018, the original author or authors.
 *
 * 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.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.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 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; name = name.toLowerCase(Locale.US); // extract bright flag from color name if (name.charAt(0) == '!') { name = name.substring(1, name.length()); flags = BRIGHT; } else if (name.startsWith("bright-")) { name = name.substring(7, name.length()); flags = BRIGHT; } else if (name.charAt(0) == '~') { try { // TODO: if the palette is not the default one, should be // TODO: translate into 24-bits first and let the #toAnsi() call // TODO: round with the current palette ? name = name.substring(1, name.length()); 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, spec.length()); 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 = color(colorName); if (color == null) { log.warning("Invalid color-name: " + colorName); } else { // resolve and apply color-mode switch (colorMode.toLowerCase(Locale.US)) { case "foreground": case "fg": case "f": return style.foreground(color); case "background": case "bg": case "b": return style.background(color); default: log.warning("Invalid color-mode: " + colorMode); } } return style; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy