From 6add8e83db9e9dbce8532edf7663b366eeb9769f Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 26 Dec 2021 00:02:32 -0800 Subject: [PATCH] Add support for 8-bit indexed colors Fixes #72 --- .../konsole/foundation/text/ColorSupport.kt | 42 +++++----- .../konsole/runtime/internal/ansi/Ansi.kt | 6 +- .../internal/ansi/commands/ColorCommands.kt | 5 +- .../terminal/swing/SgrCodeConverter.kt | 82 ++++++++++++++++--- 4 files changed, 98 insertions(+), 37 deletions(-) diff --git a/konsole/src/main/kotlin/com/varabyte/konsole/foundation/text/ColorSupport.kt b/konsole/src/main/kotlin/com/varabyte/konsole/foundation/text/ColorSupport.kt index f7a6d9a0..78957a3d 100644 --- a/konsole/src/main/kotlin/com/varabyte/konsole/foundation/text/ColorSupport.kt +++ b/konsole/src/main/kotlin/com/varabyte/konsole/foundation/text/ColorSupport.kt @@ -118,11 +118,10 @@ private fun toWhiteCommand(layer: ColorLayer, isBright: Boolean) = when(layer) { ColorLayer.BG -> if (isBright) BG_WHITE_BRIGHT_COMMAND else BG_WHITE_COMMAND } -//// TODO(#72): Add support for lookup colors -//private fun toLookupCommand(layer: ColorLayer, index: Int) = when(layer) { -// ColorLayer.FG -> fgLookupCommand(index) -// ColorLayer.BG -> bgLookupCommand(index) -//} +private fun toLookupCommand(layer: ColorLayer, index: Int) = when(layer) { + ColorLayer.FG -> fgLookupCommand(index) + ColorLayer.BG -> bgLookupCommand(index) +} private fun toTruecolorCommand(layer: ColorLayer, r: Int, g: Int, b: Int) = when(layer) { ColorLayer.FG -> fgTruecolorCommand(r, g, b) @@ -182,10 +181,9 @@ fun RenderScope.color(color: Color, layer: ColorLayer = ColorLayer.FG) { }) } -// TODO(#72): Add support for lookup colors -//fun RenderScope.color(index: Int, layer: ColorLayer = ColorLayer.FG) { -// applyCommand(toLookupCommand(layer, index)) -//} +fun RenderScope.color(index: Int, layer: ColorLayer = ColorLayer.FG) { + applyCommand(toLookupCommand(layer, index)) +} fun RenderScope.rgb(r: Int, g: Int, b: Int, layer: ColorLayer = ColorLayer.FG) { applyCommand(toTruecolorCommand(layer, r, g, b)) @@ -309,17 +307,21 @@ fun RenderScope.white( } } -// TODO(#72): Add support for lookup colors -//fun RenderScope.color( -// index: Int, -// layer: ColorLayer = ColorLayer.FG, -// scopedBlock: RenderScope.() -> Unit -//) { -// scopedState { -// color(index, layer) -// scopedBlock() -// } -//} +/** + * Use an index to lookup an 8-bit color. + * + * See also: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + */ +fun RenderScope.color( + index: Int, + layer: ColorLayer = ColorLayer.FG, + scopedBlock: RenderScope.() -> Unit +) { + scopedState { + color(index, layer) + scopedBlock() + } +} fun RenderScope.rgb( r: Int, diff --git a/konsole/src/main/kotlin/com/varabyte/konsole/runtime/internal/ansi/Ansi.kt b/konsole/src/main/kotlin/com/varabyte/konsole/runtime/internal/ansi/Ansi.kt index d550fde1..2c764e5f 100644 --- a/konsole/src/main/kotlin/com/varabyte/konsole/runtime/internal/ansi/Ansi.kt +++ b/konsole/src/main/kotlin/com/varabyte/konsole/runtime/internal/ansi/Ansi.kt @@ -160,8 +160,7 @@ internal object Ansi { object CLEAR : Code("39${Identifiers.SGR}") - // TODO(#72): Add support for lookup colors - //fun lookup(index: Int) = Code("$FG_NUMERIC;$LOOKUP_SUBCODE;$index${Identifiers.SGR}") + fun lookup(index: Int) = Code("$FG_NUMERIC;$LOOKUP_SUBCODE;$index${Identifiers.SGR}") fun truecolor(r: Int, g: Int, b: Int) = Code("$FG_NUMERIC;$TRUECOLOR_SUBCODE;$r;$g;$b${Identifiers.SGR}") } @@ -186,8 +185,7 @@ internal object Ansi { object CLEAR : Code("49${Identifiers.SGR}") - // TODO(#72): Add support for lookup colors -// fun lookup(index: Int) = Code("$BG_NUMERIC;$LOOKUP_SUBCODE;$index${Identifiers.SGR}") + fun lookup(index: Int) = Code("$BG_NUMERIC;$LOOKUP_SUBCODE;$index${Identifiers.SGR}") fun truecolor(r: Int, g: Int, b: Int) = Code("$BG_NUMERIC;$TRUECOLOR_SUBCODE;$r;$g;$b${Identifiers.SGR}") } } diff --git a/konsole/src/main/kotlin/com/varabyte/konsole/runtime/internal/ansi/commands/ColorCommands.kt b/konsole/src/main/kotlin/com/varabyte/konsole/runtime/internal/ansi/commands/ColorCommands.kt index 6a889d0f..e2dc42bb 100644 --- a/konsole/src/main/kotlin/com/varabyte/konsole/runtime/internal/ansi/commands/ColorCommands.kt +++ b/konsole/src/main/kotlin/com/varabyte/konsole/runtime/internal/ansi/commands/ColorCommands.kt @@ -76,8 +76,7 @@ internal val CLEAR_INVERT_COMMAND = object : AnsiCsiCommand(Colors.CLEAR_INVERT) } } -// internal fun fgLookupCommand(index: Int) = FgColorCommand(Colors.Fg.lookup(index)) +internal fun fgLookupCommand(index: Int) = FgColorCommand(Colors.Fg.lookup(index)) internal fun fgTruecolorCommand(r: Int, g: Int, b: Int) = FgColorCommand(Colors.Fg.truecolor(r, g, b)) -// TODO(#72): Add support for lookup colors -// internal fun bgLookupCommand(index: Int) = BgColorCommand(Colors.Bg.lookup(index)) +internal fun bgLookupCommand(index: Int) = BgColorCommand(Colors.Bg.lookup(index)) internal fun bgTruecolorCommand(r: Int, g: Int, b: Int) = BgColorCommand(Colors.Bg.truecolor(r, g, b)) \ No newline at end of file diff --git a/konsole/src/main/kotlin/com/varabyte/konsole/terminal/swing/SgrCodeConverter.kt b/konsole/src/main/kotlin/com/varabyte/konsole/terminal/swing/SgrCodeConverter.kt index 99b1a23a..8c9b9bcc 100644 --- a/konsole/src/main/kotlin/com/varabyte/konsole/terminal/swing/SgrCodeConverter.kt +++ b/konsole/src/main/kotlin/com/varabyte/konsole/terminal/swing/SgrCodeConverter.kt @@ -9,6 +9,54 @@ import com.varabyte.konsole.foundation.text.Color as AnsiColor private const val Inverted = "inverted" +// Taken from https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit and https://stackoverflow.com/a/27165165 +private val IndexedColors by lazy { + // Range 0 - 15 (legacy colors) + val legacyColors = listOf( + AnsiColor.BLACK.toSwingColor(), + AnsiColor.RED.toSwingColor(), + AnsiColor.GREEN.toSwingColor(), + AnsiColor.YELLOW.toSwingColor(), + AnsiColor.BLUE.toSwingColor(), + AnsiColor.MAGENTA.toSwingColor(), + AnsiColor.CYAN.toSwingColor(), + AnsiColor.WHITE.toSwingColor(), + AnsiColor.BRIGHT_BLACK.toSwingColor(), + AnsiColor.BRIGHT_RED.toSwingColor(), + AnsiColor.BRIGHT_GREEN.toSwingColor(), + AnsiColor.BRIGHT_YELLOW.toSwingColor(), + AnsiColor.BRIGHT_BLUE.toSwingColor(), + AnsiColor.BRIGHT_MAGENTA.toSwingColor(), + AnsiColor.BRIGHT_CYAN.toSwingColor(), + AnsiColor.BRIGHT_WHITE.toSwingColor(), + ).mapIndexed { i, color -> i to color }.toMap() + + // Range 16-231: Representative RGB values + val coreColors = (16..231).associateWith { i -> + val zeroOffset = i - 16 + val rIndex = zeroOffset / 36 + val gIndex = (zeroOffset % 36) / 6 + val bIndex = zeroOffset % 6 + + val r = if (rIndex > 0) 55 + rIndex * 40 else 0 + val g = if (gIndex > 0) 55 + gIndex * 40 else 0 + val b = if (bIndex > 0) 55 + bIndex * 40 else 0 + + println("$i -> $r $g $b") + Color(r, g, b) + } + + // Range 232 - 255: Grayscale + val grayscaleColors = (232 .. 255).associateWith { i -> + val zeroOffset = i - 232 + val gray = zeroOffset * 10 + 8 + + Color(gray, gray, gray) + } + + legacyColors + coreColors + grayscaleColors +} + /** * Convert ANSI SGR codes to instructions that Swing can understand. */ @@ -76,17 +124,31 @@ internal class SgrCodeConverter(val defaultForeground: Color, val defaultBackgro else -> { val optionalCodes = code.parts.optionalCodes ?: return null var attrSetModifier: (MutableAttributeSet.() -> Unit)? = null - if (code.parts.numericCode == Ansi.Csi.Codes.Sgr.Colors.FG_NUMERIC && - optionalCodes[0] == Ansi.Csi.Codes.Sgr.Colors.TRUECOLOR_SUBCODE - ) { - attrSetModifier = - { setInverseAwareForeground(Color(optionalCodes[1], optionalCodes[2], optionalCodes[3])) } + if (code.parts.numericCode == Ansi.Csi.Codes.Sgr.Colors.FG_NUMERIC) { + val color = if (optionalCodes[0] == Ansi.Csi.Codes.Sgr.Colors.TRUECOLOR_SUBCODE) { + Color(optionalCodes[1], optionalCodes[2], optionalCodes[3]) + } else if (optionalCodes[0] == Ansi.Csi.Codes.Sgr.Colors.LOOKUP_SUBCODE) { + IndexedColors[optionalCodes[1]] + } else { + null + } + + if (color != null) { + attrSetModifier = { setInverseAwareForeground(color) } + } } - else if (code.parts.numericCode == Ansi.Csi.Codes.Sgr.Colors.BG_NUMERIC && - optionalCodes[0] == Ansi.Csi.Codes.Sgr.Colors.TRUECOLOR_SUBCODE - ) { - attrSetModifier = - { setInverseAwareBackground(Color(optionalCodes[1], optionalCodes[2], optionalCodes[3])) } + else if (code.parts.numericCode == Ansi.Csi.Codes.Sgr.Colors.BG_NUMERIC) { + val color = if (optionalCodes[0] == Ansi.Csi.Codes.Sgr.Colors.TRUECOLOR_SUBCODE) { + Color(optionalCodes[1], optionalCodes[2], optionalCodes[3]) + } else if (optionalCodes[0] == Ansi.Csi.Codes.Sgr.Colors.LOOKUP_SUBCODE) { + IndexedColors[optionalCodes[1]] + } else { + null + } + + if (color != null) { + attrSetModifier = { setInverseAwareBackground(color) } + } } attrSetModifier