about summary refs log blame commit diff stats
path: root/src/io/cell.nim
blob: b783d180ee7d0db8285db2dccb551f587ff925ca (plain) (tree)
1
2
3
4
5
6
7
8
9
               

               
            
              
 
               
                  
                   

    
                     
             
               
                  
                


                 
                      

                       
                           
 

                           
               
 

                                  


                        
                                 

                                   





                             








                                                                


                                                                              
 





                                                                                       
 



                                              
                    
 



                                                   
                         
 


                                   


                                                                 
                                                      




                                 



                                                                
             
                            
       
                   
 
                                                                    
                               



                            
 






















                                                                               
                      
                                                         

                                                 
                   
                                                                                                             

                
                                       

                          


                                                                         

                                                                               
 

                                                                                      
 
                                                        
                   
 

                                                
 




                            
                                                                              












                                                









                                                                                     





                                   
                                
                        
                                
                        

                                                                                   















                                                                           
                                
                        
                                
                        

                                                                                   





                        


                                                  




                                

















                                                                            
             









                                                    
          
 




























                                                                 
                                                                                






























                                                           
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