about summary refs log tree commit diff stats
path: root/src/buffer/cell.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/buffer/cell.nim')
-rw-r--r--src/buffer/cell.nim321
1 files changed, 321 insertions, 0 deletions
diff --git a/src/buffer/cell.nim b/src/buffer/cell.nim
new file mode 100644
index 00000000..84efef46
--- /dev/null
+++ b/src/buffer/cell.nim
@@ -0,0 +1,321 @@
+import sequtils
+import streams
+import strutils
+import sugar
+
+import css/stylednode
+import layout/box
+import types/color
+import utils/twtstr
+
+type
+  FormatFlags* = enum
+    FLAG_BOLD
+    FLAG_ITALIC
+    FLAG_UNDERLINE
+    FLAG_REVERSE
+    FLAG_STRIKE
+    FLAG_OVERLINE
+    FLAG_BLINK
+
+  Format* = object
+    fgcolor*: CellColor
+    bgcolor*: CellColor
+    flags*: set[FormatFlags]
+
+  # A FormatCell *starts* a new terminal formatting context.
+  # If no FormatCell exists before a given cell, the default formatting is used.
+  FormatCell* = object
+    format*: Format
+    pos*: int
+    computed*: ComputedFormat
+    node*: StyledNode
+
+  SimpleFormatCell* = object
+    format*: Format
+    pos*: int
+
+  FlexibleLine* = object
+    str*: string
+    formats*: seq[FormatCell]
+
+  SimpleFlexibleLine* = object
+    str*: string
+    formats*: seq[SimpleFormatCell]
+
+  FlexibleGrid* = seq[FlexibleLine]
+
+  SimpleFlexibleGrid* = seq[SimpleFlexibleLine]
+
+  FixedCell* = object
+    str*: string
+    format*: Format
+
+  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),
+  FLAG_BLINK: (5, 25),
+]
+
+template flag_template(format: Format, val: bool, flag: FormatFlags) =
+  if val: format.flags.incl(flag)
+  else: format.flags.excl(flag)
+
+template `italic=`*(f: var Format, b: bool) = flag_template f, b, FLAG_ITALIC
+template `bold=`*(f: var Format, b: bool) = flag_template f, b, FLAG_BOLD
+template `underline=`*(f: var Format, b: bool) = flag_template f, b, FLAG_UNDERLINE
+template `reverse=`*(f: var Format, b: bool) = flag_template f, b, FLAG_REVERSE
+template `strike=`*(f: var Format, b: bool) = flag_template f, b, FLAG_STRIKE
+template `overline=`*(f: var Format, b: bool) = flag_template f, b, FLAG_OVERLINE
+template `blink=`*(f: var Format, b: bool) = flag_template f, b, FLAG_BLINK
+
+func `==`*(a: FixedCell, b: FixedCell): bool =
+  return a.format == b.format and
+    a.str == b.str
+
+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.str.width()
+
+func newFormat*(): Format =
+  return Format(fgcolor: defaultColor, bgcolor: defaultColor)
+
+# Get the first format cell after pos, if any.
+func findFormatN*(line: FlexibleLine|SimpleFlexibleLine, 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): FormatCell =
+  let i = line.findFormatN(pos) - 1
+  if i != -1:
+    result = line.formats[i]
+  else:
+    result.pos = -1
+
+func findFormat*(line: SimpleFlexibleLine, pos: int): SimpleFormatCell =
+  let i = line.findFormatN(pos) - 1
+  if i != -1:
+    result = line.formats[i]
+  else:
+    result.pos = -1
+
+func findNextFormat*(line: FlexibleLine, pos: int): FormatCell =
+  let i = line.findFormatN(pos)
+  if i < line.formats.len:
+    result = line.formats[i]
+  else:
+    result.pos = -1
+
+func findNextFormat*(line: SimpleFlexibleLine, pos: int): SimpleFormatCell =
+  let i = line.findFormatN(pos)
+  if i < line.formats.len:
+    result = line.formats[i]
+  else:
+    result.pos = -1
+
+proc addLine*(grid: var FlexibleGrid) =
+  grid.add(FlexibleLine())
+
+proc insertFormat*(line: var FlexibleLine, pos, i: int, format: Format, computed: ComputedFormat = nil) =
+  if computed == nil:
+    line.formats.insert(FormatCell(format: format, pos: pos), i)
+  else:
+    line.formats.insert(FormatCell(format: format, computed: computed, node: computed.node, pos: pos), i)
+
+proc addFormat*(line: var FlexibleLine, pos: int, format: Format) =
+  line.formats.add(FormatCell(format: format, pos: pos))
+
+proc addFormat*(line: var FlexibleLine, pos: int, format: Format, computed: ComputedFormat) =
+  #if computed != nil and line.formats.len > 0 and line.formats[^1].computed == computed and line.formats[^1].format.bgcolor != format.bgcolor:
+  #  return
+  if computed == nil:
+    line.formats.add(FormatCell(format: format, pos: pos))
+  else:
+    line.formats.add(FormatCell(format: format, computed: computed, node: computed.node, pos: pos))
+
+template inc_check(i: int) =
+  inc i
+  if i >= buf.len:
+    return i
+
+proc handleAnsiCode(format: var Format, final: char, params: string) =
+  case final
+  of 'm':
+    if params.len == 0:
+      format = newFormat()
+    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:
+            format = newFormat()
+          of 1: format.bold = true
+          of 3: format.italic = true
+          of 4: format.underline = true
+          of 5: format.blink = true
+          of 7: format.reverse = true
+          of 9: format.strike = true
+          of 22: format.bold = false
+          of 23: format.italic = false
+          of 25: format.blink = false
+          of 27: format.reverse = false
+          of 29: format.strike = false
+          of 30..37: format.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]
+                  format.fgcolor = CellColor(rgb: true, rgbcolor: rgb(r, g, b))
+              else:
+                #TODO
+                inc pi
+                continue
+            else:
+              break
+          of 39:
+            format.fgcolor = defaultColor
+          of 40..47:
+            format.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]
+                  format.bgcolor = CellColor(rgb: true, rgbcolor: rgb(r, g, b))
+              else:
+                #TODO
+                inc pi
+                continue
+            else:
+              break
+          of 49: format.bgcolor = defaultColor
+          of 53: format.overline = true
+          of 55: format.overline = false
+          else: discard
+          inc pi
+      except ValueError: discard
+  else: discard
+
+proc parseAnsiCode*(format: var Format, 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:
+    format.handleAnsiCode(final, params)
+
+  return i
+
+proc parseAnsiCode*(format: var Format, 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
+    format.handleAnsiCode(final, params)
+
+proc processFormat*(format: var Format, cellf: Format): string =
+  for flag in FormatFlags:
+    if flag in format.flags and flag notin cellf.flags:
+      result &= SGR(FormatCodes[flag].e)
+
+  if cellf.fgcolor != format.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()
+      format = newFormat()
+    else:
+      result &= SGR(color.color)
+
+  if cellf.bgcolor != format.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()
+      format = newFormat()
+    else:
+      result &= SGR(color.color)
+
+  for flag in FormatFlags:
+    if flag notin format.flags and flag in cellf.flags:
+      result &= SGR(FormatCodes[flag].s)
+
+  format = cellf