import sequtils import streams import strutils import sugar import unicode import html/dom import types/color import utils/twtstr type FormatFlags* = enum FLAG_BOLD FLAG_ITALIC FLAG_UNDERLINE FLAG_REVERSE FLAG_STRIKE FLAG_OVERLINE Formatting* = object fgcolor*: CellColor bgcolor*: CellColor flags: set[FormatFlags] Cell* = object of RootObj formatting*: Formatting node*: Node FormattingCell* = object of Cell pos*: int FlexibleLine* = object str*: string formats*: seq[FormattingCell] FlexibleGrid* = seq[FlexibleLine] FixedCell* = object of Cell runes*: seq[Rune] FixedGrid* = seq[FixedCell] const FormatCodes: array[FormatFlags, tuple[s: int, e: int]] = [ FLAG_BOLD: (1, 22), FLAG_ITALIC: (3, 23), FLAG_UNDERLINE: (4, 24), FLAG_REVERSE: (7, 27), FLAG_STRIKE: (9, 29), FLAG_OVERLINE: (53, 55), ] template flag_template(formatting: Formatting, val: bool, flag: FormatFlags) = if val: formatting.flags.incl(flag) else: formatting.flags.excl(flag) template `italic=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_ITALIC template `bold=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_BOLD template `underline=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_UNDERLINE template `reverse=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_REVERSE template `strike=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_STRIKE template `overline=`*(f: var Formatting, b: bool) = flag_template f, b, FLAG_OVERLINE #TODO ????? func `==`*(a: FixedCell, b: FixedCell): bool = return a.formatting == b.formatting and a.runes == b.runes and a.node == b.node func newFixedGrid*(w: int, h: int = 1): FixedGrid = return newSeq[FixedCell](w * h) func width*(line: FlexibleLine): int = return line.str.width() func width*(cell: FixedCell): int = return cell.runes.width() func newFormatting*(): Formatting = return Formatting(fgcolor: defaultColor, bgcolor: defaultColor) func findFormatN*(line: FlexibleLine, pos: int): int = var i = 0 while i < line.formats.len: if line.formats[i].pos > pos: break inc i return i func findFormat*(line: FlexibleLine, pos: int): FormattingCell = let i = line.findFormatN(pos) - 1 if i != -1: result = line.formats[i] else: result.pos = -1 func findNextFormat*(line: FlexibleLine, pos: int): FormattingCell = let i = line.findFormatN(pos) if i < line.formats.len: result = line.formats[i] else: result.pos = -1 func subformats*(formats: seq[FormattingCell], pos: int): seq[FormattingCell] = var i = 0 while i < formats.len: if formats[i].pos >= pos: if result.len == 0 and i > 0: var f = formats[i - 1] f.pos = 0 result.add(f) var f = formats[i] f.pos -= pos result.add(f) inc i if result.len == 0 and i > 0: var f = formats[i - 1] f.pos = 0 result.add(f) proc setLen*(line: var FlexibleLine, len: int) = for i in 0 ..< line.formats.len: if line.formats[i].pos >= len: line.formats.setLen(i) break line.str.setLen(len) #line.formats = line.formats.filter((x) => x.pos < len) proc add*(a: var FlexibleLine, b: FlexibleLine) = let l = a.str.len a.formats.add(b.formats.map((x) => FormattingCell(formatting: x.formatting, node: x.node, pos: l + x.pos))) a.str &= b.str proc addLine*(grid: var FlexibleGrid) = grid.add(FlexibleLine()) proc addFormat*(line: var FlexibleLine, pos: int, format: Formatting) = line.formats.add(FormattingCell(formatting: format, pos: line.str.len)) proc addFormat*(grid: var FlexibleGrid, y, pos: int, format: Formatting) = grid[y].formats.add(FormattingCell(formatting: format, pos: grid[y].str.len)) proc addFormat*(grid: var FlexibleGrid, y, pos: int, format: Formatting, node: Node) = grid[y].formats.add(FormattingCell(formatting: format, node: node, pos: pos)) proc addCell*(grid: var FlexibleGrid, y: int, r: Rune) = grid[y].str &= $r proc addCell*(grid: var FlexibleGrid, r: Rune) = grid.addCell(grid.len - 1, r) template inc_check(i: int) = inc i if i >= buf.len: return i proc handleAnsiCode(formatting: var Formatting, final: char, params: string) = case final of 'm': if params.len == 0: formatting = newFormatting() else: let sparams = params.split(';') try: let ip = sparams.map((x) => parseInt(x)) var pi = 0 while pi < ip.len: case ip[pi] of 0: formatting = newFormatting() of 1: formatting.bold = true of 3: formatting.italic = true of 4: formatting.underline = true of 7: formatting.reverse = true of 9: formatting.strike = true of 22: formatting.bold = false of 23: formatting.italic = false of 27: formatting.reverse = false of 29: formatting.strike = false of 30..37: formatting.fgcolor = CellColor(rgb: false, color: uint8(ip[pi])) of 38: inc pi if pi < ip.len: if ip[pi] == 2: inc pi if pi + 2 < ip.len: let r = ip[pi] inc pi let g = ip[pi] inc pi let b = ip[pi] formatting.fgcolor = CellColor(rgb: true, rgbcolor: rgb(r, g, b)) else: #TODO inc pi continue else: break of 39: formatting.fgcolor = defaultColor of 40..47: formatting.bgcolor = CellColor(rgb: false, color: uint8(ip[0])) of 48: inc pi if pi < ip.len: if ip[pi] == 2: inc pi if pi + 2 < ip.len: let r = ip[pi] inc pi let g = ip[pi] inc pi let b = ip[pi] formatting.bgcolor = CellColor(rgb: true, rgbcolor: rgb(r, g, b)) else: #TODO inc pi continue else: break of 49: formatting.bgcolor = defaultColor of 53: formatting.overline = true of 55: formatting.overline = false else: discard inc pi except ValueError: discard else: discard proc parseAnsiCode*(formatting: var Formatting, buf: string, fi: int): int = var i = fi if buf[i] != '\e': return i inc_check i if 0x40 <= int(buf[i]) and int(buf[i]) <= 0x5F: if buf[i] != '[': #C1, TODO? return inc_check i let sp = i #parameter bytes while 0x30 <= int(buf[i]) and int(buf[i]) <= 0x3F: inc_check i let params = buf.substr(sp, i - 1) #let si = i #intermediate bytes while 0x20 <= int(buf[i]) and int(buf[i]) <= 0x2F: inc_check i #let interm = buf.substr(si, i) let final = buf[i] #final byte if 0x40 <= int(buf[i]) and int(buf[i]) <= 0x7E: formatting.handleAnsiCode(final, params) return i proc parseAnsiCode*(formatting: var Formatting, stream: Stream) = if stream.atEnd(): return var c = stream.readChar() if 0x40 <= int(c) and int(c) <= 0x5F: if c != '[': #C1, TODO? return if stream.atEnd(): return c = stream.readChar() var params = $c #parameter bytes while 0x30 <= int(c) and int(c) <= 0x3F: params &= c if stream.atEnd(): return c = stream.readChar() #intermediate bytes #var interm = $c while 0x20 <= int(c) and int(c) <= 0x2F: #interm &= c if stream.atEnd(): return c = stream.readChar() #final byte if 0x40 <= int(c) and int(c) <= 0x7E: let final = c formatting.handleAnsiCode(final, params) proc processFormatting*(formatting: var Formatting, cellf: Formatting): string = for flag in FormatFlags: if flag in formatting.flags and flag notin cellf.flags: result &= SGR(FormatCodes[flag].e) if cellf.fgcolor != formatting.fgcolor: var color = cellf.fgcolor if color.rgb: let rgb = color.rgbcolor result &= SGR(38, 2, rgb.r, rgb.g, rgb.b) elif color == defaultColor: result &= SGR() formatting = newFormatting() else: result &= SGR(color.color) if cellf.bgcolor != formatting.bgcolor: var color = cellf.bgcolor if color.rgb: let rgb = color.rgbcolor result &= SGR(48, 2, rgb.r, rgb.g, rgb.b) elif color == defaultColor: result &= SGR() formatting = newFormatting() else: result &= SGR(color.color) for flag in FormatFlags: if flag notin formatting.flags and flag in cellf.flags: result &= SGR(FormatCodes[flag].s) formatting = cellf