Skip to content

Commit

Permalink
Support for 24-bit colours, fixes #619
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Dec 10, 2020
1 parent c20b133 commit e2b6f97
Show file tree
Hide file tree
Showing 8 changed files with 360 additions and 134 deletions.
20 changes: 19 additions & 1 deletion style/src/test/java/org/jline/style/StyleResolverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
130 changes: 97 additions & 33 deletions terminal/src/main/java/org/jline/utils/AttributedCharSequence.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -135,33 +159,73 @@ 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);
}
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);
Expand Down Expand Up @@ -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();
}

Expand Down
12 changes: 6 additions & 6 deletions terminal/src/main/java/org/jline/utils/AttributedString.java
Original file line number Diff line number Diff line change
Expand Up @@ -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("");
Expand Down Expand Up @@ -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());
}
Expand All @@ -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;
Expand Down Expand Up @@ -142,7 +142,7 @@ public AttributedStyle styleAt(int index) {
}

@Override
int styleCodeAt(int index) {
long styleCodeAt(int index) {
return style[start + index];
}

Expand All @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,7 +44,7 @@ public AttributedStringBuilder() {

public AttributedStringBuilder(int capacity) {
buffer = new char[capacity];
style = new int[capacity];
style = new long[capacity];
length = 0;
}

Expand All @@ -64,7 +64,7 @@ public AttributedStyle styleAt(int index) {
}

@Override
int styleCodeAt(int index) {
long styleCodeAt(int index) {
return style[index];
}

Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit e2b6f97

Please sign in to comment.