From c1738c9504af4b8c46f12f10925c088e1ac97e8f Mon Sep 17 00:00:00 2001 From: Daylin Morgan Date: Mon, 23 Sep 2024 14:23:30 -0500 Subject: [PATCH] start work to support truecolor and hexcodes --- src/hwylterm/bbansi.nim | 7 ++-- src/hwylterm/bbansi/colors.nim | 30 ++++++++++++++++ src/hwylterm/bbansi/styles.nim | 66 ++++++++++++++++++++-------------- src/hwylterm/bbansi/utils.nim | 47 +++++++++++++++++++----- src/hwylterm/utils.nim | 22 ------------ tests/tbbansi.nim | 31 ++++++++++------ 6 files changed, 134 insertions(+), 69 deletions(-) create mode 100644 src/hwylterm/bbansi/colors.nim delete mode 100644 src/hwylterm/utils.nim diff --git a/src/hwylterm/bbansi.nim b/src/hwylterm/bbansi.nim index 8b70861..1d113ea 100644 --- a/src/hwylterm/bbansi.nim +++ b/src/hwylterm/bbansi.nim @@ -4,11 +4,11 @@ use BB style markup to add color to strings using VT100 escape codes ]## -import std/[os, sequtils, strutils] +import std/[os, sequtils, strformat, strutils] import ./bbansi/[styles, utils] export utils -export bbReset +# export bbReset type BbSpan* = object @@ -124,6 +124,9 @@ proc `&`*(x: BbString, y: string): BbString = result.plain &= y result.spans[^1].slice[1] = result.plain.len - 1 +template bbfmt*(pattern: static string): BbString = + bb(fmt(pattern)) + proc `&`*(x: string, y: BbString): BbString = result.raw = x & y.raw result.plain = x & y.plain diff --git a/src/hwylterm/bbansi/colors.nim b/src/hwylterm/bbansi/colors.nim new file mode 100644 index 0000000..8399dec --- /dev/null +++ b/src/hwylterm/bbansi/colors.nim @@ -0,0 +1,30 @@ +import std/[parseutils, strutils] + +type + ColorRgb* = object + red, green, blue: int + ColorHex* = object + code: string + ColorXterm* = enum + # 0-7 + Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, + # 8-15 + BrightBlack, BrightRed, BrightGreen, BrightYellow, + BrightBlue, BrightMagenta, BrightCyan, BrightWhite + +func rgb*(r, g, b: int): ColorRgb = + ColorRgb(red: r, green: g, blue: b) + +func hexToRgb*(s: string): ColorRgb = + let code = s.replace("#", "") + assert code.len == 6 + discard parseHex(code[0..1], result.red) + discard parseHex(code[2..3], result.green) + discard parseHex(code[4..5], result.blue) + +func `$`*(c: ColorRgb): string = + result.add $c.red + result.add ";" + result.add $c.green + result.add ";" + result.add $c.blue diff --git a/src/hwylterm/bbansi/styles.nim b/src/hwylterm/bbansi/styles.nim index 0625f3c..d17076d 100644 --- a/src/hwylterm/bbansi/styles.nim +++ b/src/hwylterm/bbansi/styles.nim @@ -2,30 +2,44 @@ import std/tables export tables -const - bbReset* = "\e[0m" - bbStyles* = { - "reset": "0", - "bold": "1", - "b": "1", - "faint": "2", - "italic": "3", - "i": "3", - "underline": "4", - "u": "4", - "blink": "5", - "reverse": "7", - "conceal": "8", - "strike": "9", - }.toTable +type + BbStyleAbbr* = enum + B, I, U - bbColors* = { - "black": "0", - "red": "1", - "green": "2", - "yellow": "3", - "blue": "4", - "magenta": "5", - "cyan": "6", - "white": "7", - }.toTable + BbStyle* = enum + Reset, + Bold, Faint, Italic, Underline, Blink, + Reverse = 7, Conceal, Strike + +func toStyle*(a: BbStyleAbbr): BbStyle = + case a: + of B: Bold + of I: Italic + of U: Underline + +const bbReset* = "\e[0m" +# bbStyles* = { +# "reset": "0", +# "bold": "1", +# "b": "1", +# "faint": "2", +# "italic": "3", +# "i": "3", +# "underline": "4", +# "u": "4", +# "blink": "5", +# "reverse": "7", +# "conceal": "8", +# "strike": "9", +# }.toTable +# +# bbColors* = { +# "black": "0", +# "red": "1", +# "green": "2", +# "yellow": "3", +# "blue": "4", +# "magenta": "5", +# "cyan": "6", +# "white": "7", +# }.toTable diff --git a/src/hwylterm/bbansi/utils.nim b/src/hwylterm/bbansi/utils.nim index 9a9430d..8022b73 100644 --- a/src/hwylterm/bbansi/utils.nim +++ b/src/hwylterm/bbansi/utils.nim @@ -1,5 +1,6 @@ -import std/[os, strutils, terminal] -import ./styles +import std/[ + enumutils, os, strutils, terminal, sequtils] +import ./[styles, colors] type BbMode* = enum @@ -20,6 +21,22 @@ proc checkColorSupport(): BbMode = let bbMode* = checkColorSupport() +func normStyle(style: string): string = style.replace("_","").capitalizeAscii() +func toCode(style: BbStyle): string = $ord(style) +func toCode(abbr: BbStyleAbbr): string = abbr.toStyle().toCode() +func toCode(color: ColorXterm): string = "38;5;" & $ord(color) +func toBgCode(color: ColorXterm): string = "48;5;" & $ord(color) +func toCode(c: ColorRgb): string = "38;2;" & $c +func toBgCode(c: ColorRgb): string = "48:2;" & $c + +const ColorXTermNames = ColorXterm.items().toSeq().mapIt(($it).toLowerAscii().capitalizeAscii()) +const BbStyleNames = BbStyle.items().toSeq().mapIt(($it).toLowerAscii().capitalizeAscii()) + +func isHex(s: string): bool = + (s.startswith "#") and (s.len == 7) + +# TODO: write seperate parseStyle procedure + proc toAnsiCode*(s: string): string = if bbMode == Off: return var @@ -33,12 +50,26 @@ proc toAnsiCode*(s: string): string = else: styles = s.splitWhitespace() for style in styles: - if style in bbStyles: - codes.add bbStyles[style] - elif style in bbColors and bbMode == On: - codes.add "3" & bbColors[style] - if bgStyle in bbColors and bbMode == On: - codes.add "4" & bbColors[bgStyle] + let normalizedStyle = style.normStyle + if normalizedStyle in ["B", "I", "U"]: + codes.add parseEnum[BbStyleAbbr](normalizedStyle).toCode() + elif normalizedStyle in BbStyleNames: + codes.add parseEnum[BbStyle](normalizedStyle).toCode() + elif normalizedStyle in ColorXtermNames and bbMode == On: + codes.add parseEnum[ColorXterm](normalizedStyle).toCode() + elif normalizedStyle.isHex(): + codes.add normalizedStyle.hexToRgb.toCode() + else: + when defined(debugBB): echo "unknown style: " & normalizedStyle + + if bbMode == On and bgStyle != "": + let normalizedBgStyle = bgStyle.normStyle + if normalizedBgStyle in ColorXtermNames: + codes.add parseEnum[ColorXTerm](normalizedBgStyle).toBgCode() + elif normalizedBgStyle.isHex(): + codes.add normalizedBgStyle.hexToRgb().toBgCode() + else: + when defined(debugBB): echo "unknown bg style: " & normalizedBgStyle if codes.len > 0: result.add "\e[" diff --git a/src/hwylterm/utils.nim b/src/hwylterm/utils.nim deleted file mode 100644 index 8318ec4..0000000 --- a/src/hwylterm/utils.nim +++ /dev/null @@ -1,22 +0,0 @@ -import std/[os, terminal] - -type - BbMode* = enum - On, NoColor, Off - -proc checkColorSupport(): BbMode = - when defined(bbansiOff): - return Off - when defined(bbansiNoColor): - return NoColor - else: - if os.getEnv("HWYLTERM_FORCE_COLOR") != "": - return On - if os.getEnv("NO_COLOR") != "": - return NoColor - if not isatty(stdout): - return Off - -let bbMode* = checkColorSupport() - - diff --git a/tests/tbbansi.nim b/tests/tbbansi.nim index aeefaba..460ad8c 100644 --- a/tests/tbbansi.nim +++ b/tests/tbbansi.nim @@ -8,16 +8,16 @@ template bbCheck(input: string, output: string): untyped = suite "basic": test "simple": bbCheck "[red][/red]", "" - bbCheck "[red]red text", "\e[31mred text\e[0m" - bbCheck "[red]Red Text", "\e[31mRed Text\e[0m" - bbCheck "[yellow]Yellow Text", "\e[33mYellow Text\e[0m" - bbCheck "[bold red]Bold Red Text", "\e[1;31mBold Red Text\e[0m" - bbCheck "[red]5[/]", "\e[31m5\e[0m" - bbCheck "[bold][red]5","\e[1;31m5\e[0m" + bbCheck "[red]red text", "\e[38;5;1mred text\e[0m" + bbCheck "[red]Red Text", "\e[38;5;1mRed Text\e[0m" + bbCheck "[yellow]Yellow Text", "\e[38;5;3mYellow Text\e[0m" + bbCheck "[bold red]Bold Red Text", "\e[1;38;5;1mBold Red Text\e[0m" + bbCheck "[red]5[/]", "\e[38;5;1m5\e[0m" + bbCheck "[bold][red]5","\e[1;38;5;1m5\e[0m" test "closing": bbCheck "[bold]Bold[red] Bold Red[/red] Bold Only", - "\e[1mBold\e[0m\e[1;31m Bold Red\e[0m\e[1m Bold Only\e[0m" + "\e[1mBold\e[0m\e[1;38;5;1m Bold Red\e[0m\e[1m Bold Only\e[0m" test "abbreviated": bbCheck "[b]Bold[/] Not Bold", "\e[1mBold\e[0m Not Bold" @@ -30,10 +30,10 @@ suite "basic": bbCheck "[[red] ignored pattern", "[red] ignored pattern" test "newlines": - bbCheck "[red]Red Text[/]\nNext Line", "\e[31mRed Text\e[0m\nNext Line" + bbCheck "[red]Red Text[/]\nNext Line", "\e[38;5;1mRed Text\e[0m\nNext Line" test "on color": - bbCheck "[red on yellow]Red on Yellow", "\e[31;43mRed on Yellow\e[0m" + bbCheck "[red on yellow]Red on Yellow", "\e[38;5;1;48;5;3mRed on Yellow\e[0m" test "concat-ops": check "[red]RED[/]".bb & " plain string" == "[red]RED[/] plain string".bb @@ -43,8 +43,9 @@ suite "basic": check "a plain string" & "[blue] a blue string".bb == "a plain string[blue] a blue string".bb - test "case": - bbCheck "[red]no case sensitivity[/RED]", "\e[31mno case sensitivity\e[0m" + test "style insensitive": + bbCheck "[red]no case sensitivity[/RED]", "\e[38;5;1mno case sensitivity\e[0m" + bbCheck "[bright_red]should be BrightRed[/]", "\e[38;5;9mshould be BrightRed\e[0m" test "style full": check "[red]Red[/red]".bb == bb("Red", "red") @@ -53,3 +54,11 @@ suite "basic": test "escape": check bbEscape("[info] brackets") == "[[info] brackets" bbCheck bbEscape("[info] brackets"), "[info] brackets" + + test "fmt": + let x = 5 + check $bbfmt"[red]{x}" == "\e[38;5;1m5\e[0m" + + test "hex": + bbCheck "[#FF0000]red", "\e[38;2;255;0;0mred\e[0m" +