From e2b6f97ed0ebceb3c909415baeba71c72f819c37 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 10 Dec 2020 10:21:38 +0100 Subject: [PATCH] Support for 24-bit colours, fixes #619 --- .../org/jline/style/StyleResolverTest.java | 20 ++- .../jline/utils/AttributedCharSequence.java | 130 +++++++++++++----- .../org/jline/utils/AttributedString.java | 12 +- .../jline/utils/AttributedStringBuilder.java | 8 +- .../java/org/jline/utils/AttributedStyle.java | 90 ++++++++---- .../src/main/java/org/jline/utils/Colors.java | 121 +++++++++++----- .../java/org/jline/utils/StyleResolver.java | 111 +++++++++++---- .../utils/AttributedCharSequenceTest.java | 2 +- 8 files changed, 360 insertions(+), 134 deletions(-) diff --git a/style/src/test/java/org/jline/style/StyleResolverTest.java b/style/src/test/java/org/jline/style/StyleResolverTest.java index 3822e6117..3c0db17c2 100644 --- a/style/src/test/java/org/jline/style/StyleResolverTest.java +++ b/style/src/test/java/org/jline/style/StyleResolverTest.java @@ -124,7 +124,25 @@ public void resolveFgNotRed() { @Test public void resolveFgOlive() { AttributedStyle style = underTest.resolve("fg:~olive"); - assertEquals(DEFAULT.foreground(Colors.rgbColor("olive")), style);; + assertEquals(DEFAULT.foreground(Colors.rgbColor("olive")), style); + } + + @Test + public void resolveFgRgbOrchid() { + AttributedStyle style = underTest.resolve("fg-rgb:~orchid"); + assertEquals(DEFAULT.foreground(0xD7, 0x5F, 0xD7), style); + } + + @Test + public void resolveFgRgbHexa() { + AttributedStyle style = underTest.resolve("fg-rgb:xaf740b"); + assertEquals(DEFAULT.foreground(0xAF, 0x74, 0x0B), style); + } + + @Test + public void resolveFgRgbHexaHash() { + AttributedStyle style = underTest.resolve("fg-rgb:#af740b"); + assertEquals(DEFAULT.foreground(0xAF, 0x74, 0x0B), style); } @Test diff --git a/terminal/src/main/java/org/jline/utils/AttributedCharSequence.java b/terminal/src/main/java/org/jline/utils/AttributedCharSequence.java index 098391613..956a4a4b3 100644 --- a/terminal/src/main/java/org/jline/utils/AttributedCharSequence.java +++ b/terminal/src/main/java/org/jline/utils/AttributedCharSequence.java @@ -20,12 +20,16 @@ import static org.jline.utils.AttributedStyle.FG_COLOR; import static org.jline.utils.AttributedStyle.FG_COLOR_EXP; import static org.jline.utils.AttributedStyle.F_BACKGROUND; +import static org.jline.utils.AttributedStyle.F_BACKGROUND_IND; +import static org.jline.utils.AttributedStyle.F_BACKGROUND_RGB; import static org.jline.utils.AttributedStyle.F_BLINK; import static org.jline.utils.AttributedStyle.F_BOLD; import static org.jline.utils.AttributedStyle.F_CONCEAL; import static org.jline.utils.AttributedStyle.F_CROSSED_OUT; import static org.jline.utils.AttributedStyle.F_FAINT; import static org.jline.utils.AttributedStyle.F_FOREGROUND; +import static org.jline.utils.AttributedStyle.F_FOREGROUND_IND; +import static org.jline.utils.AttributedStyle.F_FOREGROUND_RGB; import static org.jline.utils.AttributedStyle.F_INVERSE; import static org.jline.utils.AttributedStyle.F_ITALIC; import static org.jline.utils.AttributedStyle.F_UNDERLINE; @@ -35,6 +39,14 @@ public abstract class AttributedCharSequence implements CharSequence { + public static final int TRUE_COLORS = 0x1000000; + + public enum ForceMode { + None, + Force256Colors, + ForceTrueColors + } + // cache the value here as we can't afford to get it each time static final boolean DISABLE_ALTERNATE_CHARSET = Boolean.getBoolean(PROP_DISABLE_ALTERNATE_CHARSET); @@ -55,32 +67,44 @@ public String toAnsi(Terminal terminal) { return toString(); } int colors = 256; - boolean force256colors = false; + ForceMode forceMode = ForceMode.None; String alternateIn = null, alternateOut = null; if (terminal != null) { Integer max_colors = terminal.getNumericCapability(Capability.max_colors); if (max_colors != null) { colors = max_colors; } - force256colors = AbstractWindowsTerminal.TYPE_WINDOWS_256_COLOR.equals(terminal.getType()) - || AbstractWindowsTerminal.TYPE_WINDOWS_CONEMU.equals(terminal.getType()); + if (AbstractWindowsTerminal.TYPE_WINDOWS_256_COLOR.equals(terminal.getType()) + || AbstractWindowsTerminal.TYPE_WINDOWS_CONEMU.equals(terminal.getType())) { + forceMode = ForceMode.Force256Colors; + } if (!DISABLE_ALTERNATE_CHARSET) { alternateIn = Curses.tputs(terminal.getStringCapability(Capability.enter_alt_charset_mode)); alternateOut = Curses.tputs(terminal.getStringCapability(Capability.exit_alt_charset_mode)); } } - return toAnsi(colors, force256colors, alternateIn, alternateOut); + return toAnsi(colors, forceMode, alternateIn, alternateOut); } + @Deprecated public String toAnsi(int colors, boolean force256colors) { return toAnsi(colors, force256colors, null, null); } + @Deprecated public String toAnsi(int colors, boolean force256colors, String altIn, String altOut) { + return toAnsi(colors, force256colors ? ForceMode.Force256Colors : ForceMode.None, altIn, altOut); + } + + public String toAnsi(int colors, ForceMode force) { + return toAnsi(colors, force, null, null); + } + + public String toAnsi(int colors, ForceMode force, String altIn, String altOut) { StringBuilder sb = new StringBuilder(); - int style = 0; - int foreground = -1; - int background = -1; + long style = 0; + long foreground = 0; + long background = 0; boolean alt = false; for (int i = 0; i < length(); i++) { char c = charAt(i); @@ -105,14 +129,14 @@ public String toAnsi(int colors, boolean force256colors, String altIn, String al sb.append(alt ? altIn : altOut); } } - int s = styleCodeAt(i) & ~F_HIDDEN; // The hidden flag does not change the ansi styles + long s = styleCodeAt(i) & ~F_HIDDEN; // The hidden flag does not change the ansi styles if (style != s) { - int d = (style ^ s) & MASK; - int fg = (s & F_FOREGROUND) != 0 ? (s & FG_COLOR) >>> FG_COLOR_EXP : -1; - int bg = (s & F_BACKGROUND) != 0 ? (s & BG_COLOR) >>> BG_COLOR_EXP : -1; + long d = (style ^ s) & MASK; + long fg = (s & F_FOREGROUND) != 0 ? s & (FG_COLOR | F_FOREGROUND) : 0; + long bg = (s & F_BACKGROUND) != 0 ? s & (BG_COLOR | F_BACKGROUND) : 0; if (s == 0) { sb.append("\033[0m"); - foreground = background = -1; + foreground = background = 0; } else { sb.append("\033["); boolean first = true; @@ -135,18 +159,38 @@ public String toAnsi(int colors, boolean force256colors, String altIn, String al first = attr(sb, (s & F_CROSSED_OUT) != 0 ? "9" : "29", first); } if (foreground != fg) { - if (fg >= 0) { - int rounded = Colors.roundColor(fg, colors); - if (rounded < 8 && !force256colors) { - first = attr(sb, "3" + Integer.toString(rounded), first); - // small hack to force setting bold again after a foreground color change - d |= (s & F_BOLD); - } else if (rounded < 16 && !force256colors) { - first = attr(sb, "9" + Integer.toString(rounded - 8), first); - // small hack to force setting bold again after a foreground color change - d |= (s & F_BOLD); - } else { - first = attr(sb, "38;5;" + Integer.toString(rounded), first); + if (fg > 0) { + int rounded = -1; + if ((fg & F_FOREGROUND_RGB) != 0) { + int r = (int)(fg >> (FG_COLOR_EXP + 16)) & 0xFF; + int g = (int)(fg >> (FG_COLOR_EXP + 8)) & 0xFF; + int b = (int)(fg >> FG_COLOR_EXP) & 0xFF; + if (colors == TRUE_COLORS) { + first = attr(sb, "38;2;" + r + ";" + g + ";" + b, first); + } else { + rounded = Colors.roundRgbColor(r, g, b, colors); + } + } else if ((fg & F_FOREGROUND_IND) != 0) { + rounded = Colors.roundColor((int)(fg >> FG_COLOR_EXP) & 0xFF, colors); + } + if (rounded >= 0) { + if (colors == TRUE_COLORS && force == ForceMode.ForceTrueColors) { + int col = Colors.rgbColor(rounded); + int r = (col >> 16) & 0xFF; + int g = (col >> 8) & 0xFF; + int b = col & 0xFF; + first = attr(sb, "38;2;" + r + ";" + g + ";" + b, first); + } else if (force == ForceMode.Force256Colors || rounded >= 16) { + first = attr(sb, "38;5;" + rounded, first); + } else if (rounded >= 8) { + first = attr(sb, "9" + (rounded - 8), first); + // small hack to force setting bold again after a foreground color change + d |= (s & F_BOLD); + } else { + first = attr(sb, "3" + rounded, first); + // small hack to force setting bold again after a foreground color change + d |= (s & F_BOLD); + } } } else { first = attr(sb, "39", first); @@ -154,14 +198,34 @@ public String toAnsi(int colors, boolean force256colors, String altIn, String al foreground = fg; } if (background != bg) { - if (bg >= 0) { - int rounded = Colors.roundColor(bg, colors); - if (rounded < 8 && !force256colors) { - first = attr(sb, "4" + Integer.toString(rounded), first); - } else if (rounded < 16 && !force256colors) { - first = attr(sb, "10" + Integer.toString(rounded - 8), first); - } else { - first = attr(sb, "48;5;" + Integer.toString(rounded), first); + if (bg > 0) { + int rounded = -1; + if ((bg & F_BACKGROUND_RGB) != 0) { + int r = (int)(bg >> (BG_COLOR_EXP + 16)) & 0xFF; + int g = (int)(bg >> (BG_COLOR_EXP + 8)) & 0xFF; + int b = (int)(bg >> BG_COLOR_EXP) & 0xFF; + if (colors == TRUE_COLORS) { + first = attr(sb, "48;2;" + r + ";" + g + ";" + b, first); + } else { + rounded = Colors.roundRgbColor(r, g, b, colors); + } + } else if ((bg & F_BACKGROUND_IND) != 0) { + rounded = Colors.roundColor((int)(bg >> BG_COLOR_EXP) & 0xFF, colors); + } + if (rounded >= 0) { + if (colors == TRUE_COLORS && force == ForceMode.ForceTrueColors) { + int col = Colors.rgbColor(rounded); + int r = (col >> 16) & 0xFF; + int g = (col >> 8) & 0xFF; + int b = col & 0xFF; + first = attr(sb, "48;2;" + r + ";" + g + ";" + b, first); + } else if (force == ForceMode.Force256Colors || rounded >= 16) { + first = attr(sb, "48;5;" + rounded, first); + } else if (rounded >= 8) { + first = attr(sb, "10" + (rounded - 8), first); + } else { + first = attr(sb, "4" + rounded, first); + } } } else { first = attr(sb, "49", first); @@ -220,7 +284,7 @@ private static boolean attr(StringBuilder sb, String s, boolean first) { public abstract AttributedStyle styleAt(int index); - int styleCodeAt(int index) { + long styleCodeAt(int index) { return styleAt(index).getStyle(); } diff --git a/terminal/src/main/java/org/jline/utils/AttributedString.java b/terminal/src/main/java/org/jline/utils/AttributedString.java index e837be29d..a4def4574 100644 --- a/terminal/src/main/java/org/jline/utils/AttributedString.java +++ b/terminal/src/main/java/org/jline/utils/AttributedString.java @@ -25,7 +25,7 @@ public class AttributedString extends AttributedCharSequence { final char[] buffer; - final int[] style; + final long[] style; final int start; final int end; public static final AttributedString EMPTY = new AttributedString(""); @@ -78,7 +78,7 @@ public AttributedString(CharSequence str, int start, int end, AttributedStyle s) for (int i = 0; i < l; i++) { buffer[i] = str.charAt(start + i); } - style = new int[l]; + style = new long[l]; if (s != null) { Arrays.fill(style, s.getStyle()); } @@ -87,7 +87,7 @@ public AttributedString(CharSequence str, int start, int end, AttributedStyle s) } } - AttributedString(char[] buffer, int[] style, int start, int end) { + AttributedString(char[] buffer, long[] style, int start, int end) { this.buffer = buffer; this.style = style; this.start = start; @@ -142,7 +142,7 @@ public AttributedStyle styleAt(int index) { } @Override - int styleCodeAt(int index) { + long styleCodeAt(int index) { return style[start + index]; } @@ -155,7 +155,7 @@ public AttributedString styleMatches(Pattern pattern, AttributedStyle style) { Matcher matcher = pattern.matcher(this); boolean result = matcher.find(); if (result) { - int[] newstyle = this.style.clone(); + long[] newstyle = this.style.clone(); do { for (int i = matcher.start(); i < matcher.end(); i++) { newstyle[this.start + i] = (newstyle[this.start + i] & ~style.getMask()) | style.getStyle(); @@ -185,7 +185,7 @@ private boolean arrEq(char[] a1, char[] a2, int s1, int s2, int l) { } return true; } - private boolean arrEq(int[] a1, int[] a2, int s1, int s2, int l) { + private boolean arrEq(long[] a1, long[] a2, int s1, int s2, int l) { for (int i = 0; i < l; i++) { if (a1[s1+i] != a2[s2+i]) { return false; diff --git a/terminal/src/main/java/org/jline/utils/AttributedStringBuilder.java b/terminal/src/main/java/org/jline/utils/AttributedStringBuilder.java index bb3485155..34ac9f53d 100644 --- a/terminal/src/main/java/org/jline/utils/AttributedStringBuilder.java +++ b/terminal/src/main/java/org/jline/utils/AttributedStringBuilder.java @@ -24,7 +24,7 @@ public class AttributedStringBuilder extends AttributedCharSequence implements Appendable { private char[] buffer; - private int[] style; + private long[] style; private int length; private TabStops tabs = new TabStops(0); private int lastLineLength = 0; @@ -44,7 +44,7 @@ public AttributedStringBuilder() { public AttributedStringBuilder(int capacity) { buffer = new char[capacity]; - style = new int[capacity]; + style = new long[capacity]; length = 0; } @@ -64,7 +64,7 @@ public AttributedStyle styleAt(int index) { } @Override - int styleCodeAt(int index) { + long styleCodeAt(int index) { return style[index]; } @@ -158,7 +158,7 @@ public AttributedStringBuilder append(AttributedCharSequence str, int start, int ensureCapacity(length + end - start); for (int i = start; i < end; i++) { char c = str.charAt(i); - int s = str.styleCodeAt(i) & ~current.getMask() | current.getStyle(); + long s = str.styleCodeAt(i) & ~current.getMask() | current.getStyle(); if (tabs.defined() && c == '\t') { insertTab(new AttributedStyle(s, 0)); } else { diff --git a/terminal/src/main/java/org/jline/utils/AttributedStyle.java b/terminal/src/main/java/org/jline/utils/AttributedStyle.java index c63bae8be..a982ca130 100644 --- a/terminal/src/main/java/org/jline/utils/AttributedStyle.java +++ b/terminal/src/main/java/org/jline/utils/AttributedStyle.java @@ -8,6 +8,8 @@ */ package org.jline.utils; +import java.util.Objects; + /** * Text styling. * @@ -26,24 +28,28 @@ public class AttributedStyle { public static final int BRIGHT = 8; - static final int F_BOLD = 0x00000001; - static final int F_FAINT = 0x00000002; - static final int F_ITALIC = 0x00000004; - static final int F_UNDERLINE = 0x00000008; - static final int F_BLINK = 0x00000010; - static final int F_INVERSE = 0x00000020; - static final int F_CONCEAL = 0x00000040; - static final int F_CROSSED_OUT = 0x00000080; - static final int F_FOREGROUND = 0x00000100; - static final int F_BACKGROUND = 0x00000200; - static final int F_HIDDEN = 0x00000400; - - static final int MASK = 0x000007FF; + static final long F_BOLD = 0x00000001; + static final long F_FAINT = 0x00000002; + static final long F_ITALIC = 0x00000004; + static final long F_UNDERLINE = 0x00000008; + static final long F_BLINK = 0x00000010; + static final long F_INVERSE = 0x00000020; + static final long F_CONCEAL = 0x00000040; + static final long F_CROSSED_OUT = 0x00000080; + static final long F_FOREGROUND_IND = 0x00000100; + static final long F_FOREGROUND_RGB = 0x00000200; + static final long F_FOREGROUND = F_FOREGROUND_IND | F_FOREGROUND_RGB; + static final long F_BACKGROUND_IND = 0x00000400; + static final long F_BACKGROUND_RGB = 0x00000800; + static final long F_BACKGROUND = F_BACKGROUND_IND | F_BACKGROUND_RGB; + static final long F_HIDDEN = 0x00001000; + + static final long MASK = 0x00001FFF; static final int FG_COLOR_EXP = 16; - static final int BG_COLOR_EXP = 24; - static final int FG_COLOR = 0xFF << FG_COLOR_EXP; - static final int BG_COLOR = 0xFF << BG_COLOR_EXP; + static final int BG_COLOR_EXP = 40; + static final long FG_COLOR = 0xFFFFFFL << FG_COLOR_EXP; + static final long BG_COLOR = 0xFFFFFFL << BG_COLOR_EXP; public static final AttributedStyle DEFAULT = new AttributedStyle(); public static final AttributedStyle BOLD = DEFAULT.bold(); @@ -53,8 +59,8 @@ public class AttributedStyle { public static final AttributedStyle HIDDEN = DEFAULT.hidden(); public static final AttributedStyle HIDDEN_OFF = DEFAULT.hiddenOff(); - final int style; - final int mask; + final long style; + final long mask; public AttributedStyle() { this(0, 0); @@ -64,7 +70,7 @@ public AttributedStyle(AttributedStyle s) { this(s.style, s.mask); } - public AttributedStyle(int style, int mask) { + public AttributedStyle(long style, long mask) { this.style = style; this.mask = mask & MASK | ((style & F_FOREGROUND) != 0 ? FG_COLOR : 0) | ((style & F_BACKGROUND) != 0 ? BG_COLOR : 0); @@ -135,7 +141,7 @@ public AttributedStyle inverse() { } public AttributedStyle inverseNeg() { - int s = (style & F_INVERSE) != 0 ? style & ~F_INVERSE : style | F_INVERSE; + long s = (style & F_INVERSE) != 0 ? style & ~F_INVERSE : style | F_INVERSE; return new AttributedStyle(s, mask | F_INVERSE); } @@ -172,7 +178,15 @@ public AttributedStyle crossedOutDefault() { } public AttributedStyle foreground(int color) { - return new AttributedStyle(style & ~FG_COLOR | F_FOREGROUND | ((color << FG_COLOR_EXP) & FG_COLOR), mask | F_FOREGROUND); + return new AttributedStyle(style & ~FG_COLOR | F_FOREGROUND_IND | (((long) color << FG_COLOR_EXP) & FG_COLOR), mask | F_FOREGROUND_IND); + } + + public AttributedStyle foreground(int r, int g, int b) { + return foregroundRgb(r << 16 | g << 8 | b); + } + + public AttributedStyle foregroundRgb(int color) { + return new AttributedStyle(style & ~FG_COLOR | F_FOREGROUND_RGB | ((((long) color & 0xFFFFFF) << FG_COLOR_EXP) & FG_COLOR), mask | F_FOREGROUND_RGB); } public AttributedStyle foregroundOff() { @@ -184,7 +198,15 @@ public AttributedStyle foregroundDefault() { } public AttributedStyle background(int color) { - return new AttributedStyle(style & ~BG_COLOR | F_BACKGROUND | ((color << BG_COLOR_EXP) & BG_COLOR), mask | F_BACKGROUND); + return new AttributedStyle(style & ~BG_COLOR | F_BACKGROUND_IND | (((long) color << BG_COLOR_EXP) & BG_COLOR), mask | F_BACKGROUND_IND); + } + + public AttributedStyle background(int r, int g, int b) { + return backgroundRgb(r << 16 | g << 8 | b); + } + + public AttributedStyle backgroundRgb(int color) { + return new AttributedStyle(style & ~FG_COLOR | F_BACKGROUND_RGB | ((((long) color & 0xFFFFFF) << FG_COLOR_EXP) & FG_COLOR), mask | F_BACKGROUND_RGB); } public AttributedStyle backgroundOff() { @@ -214,11 +236,11 @@ public AttributedStyle hiddenDefault() { return new AttributedStyle(style & ~F_HIDDEN, mask & ~F_HIDDEN); } - public int getStyle() { + public long getStyle() { return style; } - public int getMask() { + public long getMask() { return mask; } @@ -234,8 +256,22 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = style; - result = 31 * result + mask; - return result; + return 31 * Long.hashCode(style) + Long.hashCode(mask); + } + + public String toAnsi() { + AttributedStringBuilder sb = new AttributedStringBuilder(); + sb.styled(this, " "); + String s = sb.toAnsi(AttributedCharSequence.TRUE_COLORS, AttributedCharSequence.ForceMode.None); + return s.substring(2, s.indexOf('m')); + } + + @Override + public String toString() { + return "AttributedStyle{" + + "style=" + style + + ", mask=" + mask + + ", ansi=" + toAnsi() + + '}'; } } diff --git a/terminal/src/main/java/org/jline/utils/Colors.java b/terminal/src/main/java/org/jline/utils/Colors.java index 29a7b523a..460568431 100644 --- a/terminal/src/main/java/org/jline/utils/Colors.java +++ b/terminal/src/main/java/org/jline/utils/Colors.java @@ -25,42 +25,92 @@ public class Colors { * Default 256 colors palette */ public static final int[] DEFAULT_COLORS_256 = { + // 16 ansi 0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0, 0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, - 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, - 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, - 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, - 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, - 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, - 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, - 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, - 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, - 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, - 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, - 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, - 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, - 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, - 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, - 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, - 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, - 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, - 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, - 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, - 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, - 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, - 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, - 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, - 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, - 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, - 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, - 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, - + // 6x6x6 color cube + 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, + 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, + 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff, + 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, + 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, + 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, + + 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, + 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, + 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, + 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, + 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff, + 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, + + 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, + 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, + 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, + 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, + 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, + 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, + + 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, + 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, + 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, + 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, + 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, + 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, + + 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, + 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, + 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, + 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, + 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, + 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, + + 0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, + 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, + 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, + 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, + 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff, + 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, + + // 24 grey ramp 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee, }; + /** + * Default 88 colors palette + */ + public static final int[] DEFAULT_COLORS_88 = { + // 16 ansi + 0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0, + 0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, + + // 4x4x4 color cube + 0x000000, 0x00008b, 0x0000cd, 0x0000ff, + 0x008b00, 0x008b8b, 0x008bcd, 0x008bff, + 0x00cd00, 0x00cd8b, 0x00cdcd, 0x00cdff, + 0x00ff00, 0x00ff8b, 0x00ffcd, 0x00ffff, + + 0x8b0000, 0x8b008b, 0x8b00cd, 0x8b00ff, + 0x8b8b00, 0x8b8b8b, 0x8b8bcd, 0x8b8bff, + 0x8bcd00, 0x8bcd8b, 0x8bcdcd, 0x8bcdff, + 0x8bff00, 0x8bff8b, 0x8bffcd, 0x8bffff, + + 0xcd0000, 0xcd008b, 0xcd00cd, 0xcd00ff, + 0xcd8b00, 0xcd8b8b, 0xcd8bcd, 0xcd8bff, + 0xcdcd00, 0xcdcd8b, 0xcdcdcd, 0xcdcdff, + 0xcdff00, 0xcdff8b, 0xcdffcd, 0xcdffff, + + 0xff0000, 0xff008b, 0xff00cd, 0xff00ff, + 0xff8b00, 0xff8b8b, 0xff8bcd, 0xff8bff, + 0xffcd00, 0xffcd8b, 0xffcdcd, 0xffcdff, + 0xffff00, 0xffff8b, 0xffffcd, 0xffffff, + + // 8 grey ramp + 0x2e2e2e, 0x5c5c5c, 0x737373, 0x8b8b8b, 0xa2a2a2, 0xb9b9b9, 0xd0d0d0, 0xe7e7e7, + }; + /** D50 illuminant for CAM color spaces */ public static final double[] D50 = new double[] { 96.422f, 100.0f, 82.521f }; /** D65 illuminant for CAM color spaces */ @@ -74,11 +124,11 @@ public class Colors { public static final double[] darkSurrounding = new double[] { 0.8, 0.525, 0.8 }; /** sRGB encoding environment */ - public static final double[] sRGB_encoding_environment = vc(D50, 64.0, 64/5, dimSurrounding); + public static final double[] sRGB_encoding_environment = vc(D50, 64.0, 64.0/5, dimSurrounding); /** sRGB typical environment */ - public static final double[] sRGB_typical_environment = vc(D50, 200.0, 200/5, averageSurrounding); + public static final double[] sRGB_typical_environment = vc(D50, 200.0, 200.0/5, averageSurrounding); /** Adobe RGB environment */ - public static final double[] AdobeRGB_environment = vc(D65, 160.0, 160/5, averageSurrounding); + public static final double[] AdobeRGB_environment = vc(D65, 160.0, 160.0/5, averageSurrounding); private static int[] COLORS_256 = DEFAULT_COLORS_256; @@ -130,15 +180,16 @@ public static int roundRgbColor(int r, int g, int b, int max) { return roundColor((r << 16) + (g << 8) + b, COLORS_256, max, (String) null); } - private static int roundColor(int color, int[] colors, int max, String dist) { + static int roundColor(int color, int[] colors, int max, String dist) { return roundColor(color, colors, max, getDistance(dist)); } - private interface Distance { + @FunctionalInterface + interface Distance { double compute(int c1, int c2); } - private static int roundColor(int color, int[] colors, int max, Distance distance) { + static int roundColor(int color, int[] colors, int max, Distance distance) { double best_distance = Integer.MAX_VALUE; int best_index = Integer.MAX_VALUE; for (int idx = 0; idx < max; idx++) { @@ -151,7 +202,7 @@ private static int roundColor(int color, int[] colors, int max, Distance distanc return best_index; } - private static Distance getDistance(String dist) { + static Distance getDistance(String dist) { if (dist == null) { dist = System.getProperty(PROP_COLOR_DISTANCE, "cie76"); } diff --git a/terminal/src/main/java/org/jline/utils/StyleResolver.java b/terminal/src/main/java/org/jline/utils/StyleResolver.java index f093a88cb..fdd9c5dba 100644 --- a/terminal/src/main/java/org/jline/utils/StyleResolver.java +++ b/terminal/src/main/java/org/jline/utils/StyleResolver.java @@ -32,6 +32,38 @@ 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 = Colors.DEFAULT_COLORS_256[color]; + } + return color; + } + } + /** * Returns the color identifier for the given name. *

@@ -44,21 +76,20 @@ public StyleResolver(final Function source) { */ private static Integer color(String name) { int flags = 0; - name = name.toLowerCase(Locale.US); + if (name.equals("default")) { + return -1; + } // extract bright flag from color name - if (name.charAt(0) == '!') { - name = name.substring(1, name.length()); + else if (name.charAt(0) == '!') { + name = name.substring(1); flags = BRIGHT; } else if (name.startsWith("bright-")) { - name = name.substring(7, name.length()); + name = name.substring(7); flags = BRIGHT; } else if (name.charAt(0) == '~') { + name = name.substring(1); 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); @@ -297,25 +328,51 @@ private AttributedStyle applyColor(final AttributedStyle style, final String spe 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); - } + 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; } diff --git a/terminal/src/test/java/org/jline/utils/AttributedCharSequenceTest.java b/terminal/src/test/java/org/jline/utils/AttributedCharSequenceTest.java index 15ad28d72..829ca4e2a 100644 --- a/terminal/src/test/java/org/jline/utils/AttributedCharSequenceTest.java +++ b/terminal/src/test/java/org/jline/utils/AttributedCharSequenceTest.java @@ -24,7 +24,7 @@ public class AttributedCharSequenceTest { @Test public void testUnderline() throws IOException { AttributedString as = AttributedString.fromAnsi("\33[38;5;0m\33[48;5;15mtest\33[0m"); - assertEquals("\33[38;5;0;48;5;15mtest\33[0m", as.toAnsi(256, true)); + assertEquals("\33[38;5;0;48;5;15mtest\33[0m", as.toAnsi(256, AttributedCharSequence.ForceMode.Force256Colors)); } @Test