about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-11-13 11:17:40 +0100
committerbptato <nincsnevem662@gmail.com>2021-11-13 11:17:40 +0100
commita6099512e42cc95cf6276ab784afd70febdd43b7 (patch)
treedef2061199d9f119a3176858e331ba376c89e2c5
parentc141a3cea17f32f526853cd4085ae4cc80c4fed6 (diff)
downloadchawan-a6099512e42cc95cf6276ab784afd70febdd43b7.tar.gz
Parse ansi escape codes when displaying plain text
-rw-r--r--src/io/buffer.nim27
-rw-r--r--src/io/cell.nim182
-rw-r--r--src/utils/twtstr.nim8
3 files changed, 150 insertions, 67 deletions
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index a0d7839f..d1a55222 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -581,22 +581,31 @@ proc refreshDisplay*(buffer: Buffer) =
 proc renderPlainText*(buffer: Buffer, text: string) =
   buffer.clearText()
   var i = 0
+  var x = 0
   var y = 0
-  var line = ""
+  var r: Rune
+  var format = newFormatting()
   while i < text.len:
     if text[i] == '\n':
-      buffer.setText(0, y, line.toRunes())
+      buffer.addLine()
       inc y
-      line = ""
+      x = 0
+      inc i
     elif text[i] == '\r':
-      discard
+      inc i
     elif text[i] == '\t':
-      line &= ' '.repeat(8)
+      for i in 0..8:
+        buffer.lines[^1].add(FlexibleCell(rune: Rune(' '), formatting: format))
+      inc i
+    elif text[i] == '\e':
+      i = format.parseAnsiCode(text, i)
+    elif text[i].isControlChar():
+      buffer.lines[^1].add(FlexibleCell(rune: Rune('^'), formatting: format))
+      buffer.lines[^1].add(FlexibleCell(rune: Rune(text[i].getControlLetter()), formatting: format))
+      inc i
     else:
-      line &= text[i]
-    inc i
-  if line.len > 0:
-    buffer.setText(0, y, line.toRunes())
+      fastRuneAt(text, i, r)
+      buffer.lines[^1].add(FlexibleCell(rune: r, formatting: format))
   buffer.updateCursor()
 
 proc renderDocument*(buffer: Buffer) =
diff --git a/src/io/cell.nim b/src/io/cell.nim
index 133b88d6..67b7361c 100644
--- a/src/io/cell.nim
+++ b/src/io/cell.nim
@@ -1,4 +1,7 @@
 import unicode
+import strutils
+import sequtils
+import sugar
 
 import types/color
 import utils/twtstr
@@ -28,6 +31,16 @@ type
 
   FixedGrid* = seq[FixedCell]
 
+func newFixedGrid*(w: int, h: int = 1): FixedGrid =
+  return newSeq[FixedCell](w * h)
+
+func width*(line: FlexibleLine): int =
+  for c in line:
+    result += c.rune.width()
+
+func newFormatting*(): Formatting =
+  return Formatting(fgcolor: defaultColor, bgcolor: defaultColor)
+
 proc setText*(grid: var FlexibleGrid, x: int, y: int, text: seq[Rune]) =
   while grid.len <= y:
     grid.add(newSeq[FlexibleCell]())
@@ -40,15 +53,121 @@ proc setText*(grid: var FlexibleGrid, x: int, y: int, text: seq[Rune]) =
     grid[y][i].rune = text[i]
     inc i
 
-func newFixedGrid*(w: int, h: int = 1): FixedGrid =
-  return newSeq[FixedCell](w * h)
-
-func width*(line: FlexibleLine): int =
-  for c in line:
-    result += c.rune.width()
-
-func newFormatting*(): Formatting =
-  return Formatting(fgcolor: defaultColor, bgcolor: defaultColor)
+template inc_check(i: int) =
+  inc i
+  if i >= buf.len:
+    return i
+
+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:
+    inc_check i
+
+  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 9:
+            formatting.strike = true
+          of 22:
+            formatting.bold = false
+          of 23:
+            formatting.italic = 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 = uint8(ip[pi])
+                  inc pi
+                  let g = uint8(ip[pi])
+                  inc pi
+                  let b = uint8(ip[pi])
+                  formatting.fgcolor = CellColor(rgb: true, rgbcolor: (r: r, g: g, b: 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 = uint8(ip[pi])
+                  inc pi
+                  let g = uint8(ip[pi])
+                  inc pi
+                  let b = uint8(ip[pi])
+                  formatting.bgcolor = CellColor(rgb: true, rgbcolor: (r: r, g: g, b: 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
+
+  return i
 
 proc processFormatting*(formatting: var Formatting, cellf: Formatting): string =
     if formatting.bold and not cellf.bold:
@@ -96,48 +215,3 @@ proc processFormatting*(formatting: var Formatting, cellf: Formatting): string =
       result &= SGR(53)
 
     formatting = cellf
-    if formatting.bold and not cellf.bold:
-      result &= SGR(22)
-    if formatting.italic and not cellf.italic:
-      result &= SGR(23)
-    if formatting.underline and not cellf.underline:
-      result &= SGR(24)
-    if formatting.strike and not cellf.strike:
-      result &= SGR(29)
-    if formatting.overline and not cellf.overline:
-      result &= SGR(55)
-
-    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)
-
-    if not formatting.bold and cellf.bold:
-      result &= SGR(1)
-    if not formatting.italic and cellf.italic:
-      result &= SGR(3)
-    if not formatting.underline and cellf.underline:
-      result &= SGR(4)
-    if not formatting.strike and cellf.strike:
-      result &= SGR(9)
-    if not formatting.overline and cellf.overline:
-      result &= SGR(53)
-
-    formatting = cellf
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index db5a1293..27269169 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -190,7 +190,7 @@ func skipBlanks*(buf: string, at: int): int =
   while result < buf.len and buf[result].isWhitespace():
     inc result
 
-template ESC*(s: varargs[string, `$`]): string =
+template CSI*(s: varargs[string, `$`]): string =
   var r = "\e["
   var first = true
   for x in s:
@@ -201,13 +201,13 @@ template ESC*(s: varargs[string, `$`]): string =
   r
 
 template SGR*(s: varargs[string, `$`]): string =
-  ESC(s) & "m"
+  CSI(s) & "m"
 
 template HVP*(s: varargs[string, `$`]): string =
-  ESC(s) & "f"
+  CSI(s) & "f"
 
 template EL*(s: varargs[string, `$`]): string =
-  ESC(s) & "K"
+  CSI(s) & "K"
 
 iterator split*(s: seq[Rune], sep: Rune): seq[Rune] =
   var i = 0