support [color(9)] syntax

This commit is contained in:
Daylin Morgan 2025-02-05 01:46:35 -06:00
parent a648a8329c
commit 8d5c9bf9e4
Signed by: daylin
GPG key ID: 950D13E9719334AD
4 changed files with 62 additions and 43 deletions

0
bbansi.nim Normal file
View file

View file

@ -7,7 +7,7 @@
{.push raises:[].} {.push raises:[].}
import std/[ import std/[
macros, os, sequtils, strformat, strutils, terminal macros, os, sequtils, strformat, strscans, strutils, terminal
] ]
import ./bbansi/[styles, colors] import ./bbansi/[styles, colors]
@ -18,7 +18,9 @@ type
ColorSystem = enum ColorSystem = enum
TrueColor, EightBit, Standard, None TrueColor, EightBit, Standard, None
proc checkColorSupport(): BbMode = proc checkColorSupport(file = stdout): BbMode =
when defined(bbansiOn):
return On
when defined(bbansiOff): when defined(bbansiOff):
return Off return Off
when defined(bbansiNoColor): when defined(bbansiNoColor):
@ -28,7 +30,7 @@ proc checkColorSupport(): BbMode =
return On return On
elif getEnv("NO_COLOR") != "": elif getEnv("NO_COLOR") != "":
return NoColor return NoColor
elif (getEnv("TERM") in ["dumb", "unknown"]) or not isatty(stdout): elif (getEnv("TERM") in ["dumb", "unknown"]) or not isatty(file):
return Off return Off
proc checkColorSystem(): ColorSystem = proc checkColorSystem(): ColorSystem =
@ -44,7 +46,7 @@ proc checkColorSystem(): ColorSystem =
of "16color": Standard of "16color": Standard
else: Standard else: Standard
var bbMode* = checkColorSupport() let gbbMode* = checkColorSupport()
let colorSystem* = checkColorSystem() let colorSystem* = checkColorSystem()
func firstCapital(s: string): string = s.toLowerAscii().capitalizeAscii() func firstCapital(s: string): string = s.toLowerAscii().capitalizeAscii()
@ -69,45 +71,53 @@ macro enumNames(a: typed): untyped =
const ColorXTermNames = enumNames(ColorXterm).mapIt(firstCapital(it)) const ColorXTermNames = enumNames(ColorXterm).mapIt(firstCapital(it))
const BbStyleNames = enumNames(BbStyle).mapIt(firstCapital(it)) const BbStyleNames = enumNames(BbStyle).mapIt(firstCapital(it))
const ColorDigitStrings = (1..255).toSeq().mapIt($it) # const ColorDigitStrings = (1..255).toSeq().mapIt($it)
func get256Color(s: string): int =
template parseUnsafe(body: untyped): untyped = try:
try: body if scanf(s, "Color($i)", result):
if result > 255:
result = 0
except: discard except: discard
proc parseStyle(codes: var seq[string], style: string) = func parseStyle(mode: BbMode, style: string): string =
try:
var style = normalizeStyle(style) var style = normalizeStyle(style)
if style in ["B", "I", "U"]: if style in ["B", "I", "U"]:
parseUnsafe: codes.add parseEnum[BbStyleAbbr](style).toCode() return parseEnum[BbStyleAbbr](style).toCode()
elif style in BbStyleNames: elif style in BbStyleNames:
parseUnsafe: codes.add parseEnum[BbStyle](style).toCode() return parseEnum[BbStyle](style).toCode()
if not (bbMode == On): return if not (mode == On): return
if style in ColorXtermNames: if style in ColorXtermNames:
parseUnsafe: codes.add parseEnum[ColorXterm](style).toCode() return parseEnum[ColorXterm](style).toCode()
elif style.isHex(): elif style.isHex():
codes.add style.hexToRgb.toCode() return style.hexToRgb.toCode()
elif style in ColorDigitStrings: elif "Color(" in style:
parseUnsafe: codes.add parseInt(style).toCode() if (let num = style.get256Color(); num > 0):
return num.toCode()
else: else:
when defined(debugBB): echo "unknown style: " & normalizedStyle when defined(debugBB): debugEcho "unknown style: " & style
except: discard
func parseBgStyle(codes: var seq[string], style: string) = func parseBgStyle(mode: BbMode, style: string): string =
try:
var style = normalizeStyle(style) var style = normalizeStyle(style)
if style in ColorXtermNames: if style in ColorXtermNames:
parseUnsafe: codes.add parseEnum[ColorXTerm](style).toBgCode() return parseEnum[ColorXTerm](style).toBgCode()
elif style.isHex(): elif style.isHex():
codes.add style.hexToRgb().toBgCode() return style.hexToRgb().toBgCode()
elif style in ColorDigitStrings: elif "Color(" in style:
parseUnsafe: codes.add parseInt(style).toBgCode() if (let num = style.get256Color(); num > 0):
return num.toBgCode()
else: else:
when defined(debugBB): echo "unknown bg style: " & style when defined(debugBB): debugEcho "unknown style: " & style
except: discard
proc toAnsiCode*(s: string): string = func toAnsiCode*(mode: BbMode, s: string ): string =
if bbMode == Off: return if mode == Off: return
var var
codes: seq[string] codes: seq[string]
styles: seq[string] styles: seq[string]
@ -118,17 +128,21 @@ proc toAnsiCode*(s: string): string =
bgStyle = fgBgSplit[1].strip().toLowerAscii() bgStyle = fgBgSplit[1].strip().toLowerAscii()
else: else:
styles = s.splitWhitespace() styles = s.splitWhitespace()
for style in styles:
parseStyle codes, style
if bbMode == On and bgStyle != "": for style in styles:
parseBgStyle codes, bgStyle let code = parseStyle(mode, style)
if code != "": codes.add code
if mode == On and bgStyle != "":
let code = parseBgStyle(mode, bgStyle)
if code != "": codes.add code
if codes.len > 0: if codes.len > 0:
result.add "\e[" result.add "\e["
result.add codes.join ";" result.add codes.join ";"
result.add "m" result.add "m"
proc toAnsiCode*(s: string): string {.inline.} = toAnsiCode(gBbMode, s)
func stripAnsi*(s: string): string = func stripAnsi*(s: string): string =
## remove all ansi escape codes from a string ## remove all ansi escape codes from a string
@ -264,6 +278,7 @@ func bb*(s: string): BbString =
result.closeFinalSpan result.closeFinalSpan
proc bb*(s: string, style: string): BbString = proc bb*(s: string, style: string): BbString =
bb("[" & style & "]" & s & "[/" & style & "]") bb("[" & style & "]" & s & "[/" & style & "]")
@ -290,20 +305,23 @@ proc `&`*(x: string, y: BbString): BbString =
func len*(bbs: BbString): int = func len*(bbs: BbString): int =
bbs.plain.len bbs.plain.len
proc `$`*(bbs: BbString): string = func toString(bbs: Bbstring, mode: BbMode): string =
if bbMode == Off: if mode == Off:
return bbs.plain return bbs.plain
for span in bbs.spans: for span in bbs.spans:
var codes = "" var codes = ""
if span.styles.len > 0: if span.styles.len > 0:
codes = span.styles.join(" ").toAnsiCode codes = toAnsiCode(mode, span.styles.join(" "))
result.add codes result.add codes
result.add bbs.plain[span.slice[0] .. span.slice[1]] result.add bbs.plain[span.slice[0] .. span.slice[1]]
if codes != "": if codes != "":
result.add toAnsiCode("reset") result.add toAnsiCode(mode, "reset")
proc `$`*(bbs: BbString): string =
bbs.toString(gBbMode)
func align*(bs: BbString, count: Natural, padding = ' '): Bbstring = func align*(bs: BbString, count: Natural, padding = ' '): Bbstring =
if bs.len < count: if bs.len < count:

View file

@ -1,7 +1,6 @@
{.define: bbansiOn.}
import std/[os, strutils, unittest] import std/[os, strutils, unittest]
import hwylterm/bbansi import hwylterm/bbansi
bbMode = On
template bbCheck(input: string, output: string): untyped = template bbCheck(input: string, output: string): untyped =
check escape($bb(input)) == escape(output) check escape($bb(input)) == escape(output)
@ -17,6 +16,8 @@ suite "basic":
bbCheck "[red]5[/]", "\e[38;5;1m5\e[0m" bbCheck "[red]5[/]", "\e[38;5;1m5\e[0m"
bbCheck "[bold][red]5","\e[1;38;5;1m5\e[0m" bbCheck "[bold][red]5","\e[1;38;5;1m5\e[0m"
check "[bold]bold[/bold]" == "bold".bbMarkup("bold") check "[bold]bold[/bold]" == "bold".bbMarkup("bold")
bbCheck "[color(9)]red[/color(9)][color(2)]blue[/color(2)]", "\x1B[38;5;9mred\x1B[0m\x1B[38;5;2mblue\x1B[0m"
bbCheck "[color(256)]no color![/]", "no color!"
test "compile time": test "compile time":
const s = bb"[red]red text" const s = bb"[red]red text"

View file

@ -33,8 +33,8 @@
- [x] confirmation proc - [x] confirmation proc
- [ ] basic progress bar - [ ] basic progress bar
- [ ] support for 256 and truecolors - [ ] support for 256 and truecolors
- [ ] support for rgb colors - [x] support for rgb colors
- [ ] modify 256 colors w/parser changes to be `"[color(9)]red"` instead of `[9]red` - [x] modify 256 colors w/parser changes to be `"[color(9)]red"` instead of `[9]red`
- [x] improve color detection [ref](https://github.com/Textualize/rich/blob/4101991898ee7a09fe1706daca24af5e1e054862/rich/console.py#L791) - [x] improve color detection [ref](https://github.com/Textualize/rich/blob/4101991898ee7a09fe1706daca24af5e1e054862/rich/console.py#L791)
## testing ## testing