about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-01-31 19:24:27 +0100
committerbptato <nincsnevem662@gmail.com>2021-01-31 19:24:27 +0100
commit5d79801b655aae7801fb27fc3a9422bdd5764882 (patch)
tree45f56d26d814231adc7044e6a5f05239aae3ea10
parent09feae49e82e36b9a9b9f39e933f78cf7abec1ec (diff)
downloadchawan-5d79801b655aae7801fb27fc3a9422bdd5764882.tar.gz
new awful html parser that doesn't even work
-rw-r--r--buffer.nim404
-rw-r--r--config2
-rw-r--r--config.nim5
-rw-r--r--display.nim178
-rw-r--r--enums.nim37
-rw-r--r--htmlelement.nim27
-rw-r--r--main.nim17
-rw-r--r--parser.nim180
-rw-r--r--termattrs.nim7
-rw-r--r--twtio.nim8
-rw-r--r--twtstr.nim10
11 files changed, 545 insertions, 330 deletions
diff --git a/buffer.nim b/buffer.nim
index fcc24b76..06459d11 100644
--- a/buffer.nim
+++ b/buffer.nim
@@ -1,3 +1,5 @@
+#beware, awful code ahead
+
 import options
 import uri
 import tables
@@ -15,20 +17,19 @@ import twtstr
 type
   Buffer* = ref BufferObj
   BufferObj = object
-    text*: string
-    rawtext*: string
-    lines*: seq[int]
-    rawlines*: seq[int]
+    fmttext*: seq[string]
+    rawtext*: seq[string]
     title*: string
     hovertext*: string
-    htmlSource*: XmlNode
+    htmlsource*: XmlNode
     width*: int
     height*: int
-    cursorX*: int
-    cursorY*: int
+    cursorx*: int
+    cursory*: int
+    cursorchar*: int
     xend*: int
-    fromX*: int
-    fromY*: int
+    fromx*: int
+    fromy*: int
     nodes*: seq[HtmlNode]
     links*: seq[HtmlNode]
     clickables*: seq[HtmlNode]
@@ -40,73 +41,50 @@ type
     document*: Document
 
 proc newBuffer*(attrs: TermAttributes): Buffer =
-  return Buffer(lines: @[0],
-                rawlines: @[0],
-                width: attrs.termWidth,
+  return Buffer(width: attrs.termWidth,
                 height: attrs.termHeight,
-                cursorY: 1,
-                document: newDocument())
+                attrs: attrs)
 
 func lastLine*(buffer: Buffer): int =
-  assert(buffer.rawlines.len == buffer.lines.len)
-  return buffer.lines.len - 1
+  assert(buffer.fmttext.len == buffer.rawtext.len)
+  return buffer.fmttext.len - 1
 
 func lastVisibleLine*(buffer: Buffer): int =
-  return min(buffer.fromY + buffer.height - 1, buffer.lastLine())
-
-func currentLine*(buffer: Buffer): int =
-  return buffer.cursorY - 1
+  return min(buffer.fromy + buffer.height - 1, buffer.lastLine())
 
-func textBetween*(buffer: Buffer, s: int, e: int): string =
-  return buffer.text.runeSubstr(s, e - s)
-
-#doesn't include newline
-func lineLength*(buffer: Buffer, line: int): int =
-  assert buffer.lines.len > line
-  let str = buffer.textBetween(buffer.lines[line - 1], buffer.lines[line])
-  let len = mk_wcswidth_cjk(str)
-  if len >= 0:
-    return len
-  else:
-    return 0
-
-func rawLineLength*(buffer: Buffer, line: int): int =
-  assert buffer.rawlines.len > line
-  let str = buffer.textBetween(buffer.rawlines[line - 1], buffer.rawlines[line])
-  let len = mk_wcswidth_cjk(str)
-  if len >= 0:
-    return len
-  else:
-    return 0
+func realCurrentLineLength*(buffer: Buffer): int =
+  return mk_wcswidth_cjk(buffer.rawtext[buffer.cursory])
 
 func currentLineLength*(buffer: Buffer): int =
-  return buffer.lineLength(buffer.cursorY)
-
-func currentRawLineLength*(buffer: Buffer): int =
-  return buffer.rawLineLength(buffer.cursorY)
-
-func cursorAtLineEnd*(buffer: Buffer): bool =
-  return buffer.cursorX == buffer.currentRawLineLength()
+  return buffer.rawtext[buffer.cursory].runeLen()
 
 func atPercentOf*(buffer: Buffer): int =
-  return (100 * buffer.cursorY) div buffer.lastLine()
-
+  return (100 * buffer.cursory) div buffer.lastLine()
+
+func fmtBetween*(buffer: Buffer, sx: int, sy: int, ex: int, ey: int): string =
+  if sy < ey:
+    result &= buffer.rawtext[sy].runeSubstr(sx)
+    var i = sy + 1
+    while i < ey - 1:
+      result &= buffer.rawtext[i]
+      inc i
+    result &= buffer.rawtext[i].runeSubstr(0, ex - sx)
+  else:
+    result &= buffer.rawtext[sy].runeSubstr(sx, ex - sx)
 
 func visibleText*(buffer: Buffer): string = 
-  return buffer.textBetween(buffer.lines[buffer.fromY], buffer.lines[buffer.lastVisibleLine()])
+  return buffer.fmttext[buffer.fromy..buffer.lastVisibleLine()].join("\n")
 
 func lastNode*(buffer: Buffer): HtmlNode =
   return buffer.nodes[^1]
 
-func onNewLine*(buffer: Buffer): bool =
-  return buffer.text.len == 0 or buffer.text[^1] == '\n'
-
-func onSpace*(buffer: Buffer): bool =
-  return buffer.text.len > 0 and buffer.text[^1] == ' '
-
 func cursorOnNode*(buffer: Buffer, node: HtmlNode): bool =
-  return buffer.rawlines[buffer.cursorY - 1] + buffer.cursorX >= node.rawchar and
-         buffer.rawlines[buffer.cursorY - 1] + buffer.cursorX < node.rawend
+  if node.y == node.ey and node.y == buffer.cursory:
+    return buffer.cursorx >= node.x and buffer.cursorx < node.ex
+  else:
+    return (buffer.cursory == node.y and buffer.cursorx >= node.x) or
+           (buffer.cursory > node.y and buffer.cursory < node.ey) or
+           (buffer.cursory == node.ey and buffer.cursorx < node.ex)
 
 func findSelectedElement*(buffer: Buffer): Option[HtmlElement] =
   if buffer.selectedlink != nil and buffer.selectedLink.parentNode of HtmlElement:
@@ -117,9 +95,6 @@ func findSelectedElement*(buffer: Buffer): Option[HtmlElement] =
         if buffer.cursorOnNode(node): return some(HtmlElement(node))
   return none(HtmlElement)
 
-func cursorAt*(buffer: Buffer): int =
-  return buffer.rawlines[buffer.currentLine()] + buffer.cursorX
-
 func canScroll*(buffer: Buffer): bool =
   return buffer.lastLine() > buffer.height
 
@@ -131,7 +106,7 @@ func getElementById*(buffer: Buffer, id: string): HtmlElement =
 proc findSelectedNode*(buffer: Buffer): Option[HtmlNode] =
   for node in buffer.nodes:
     if node.getFmtLen() > 0 and node.displayed():
-      if buffer.cursorY >= node.y and buffer.cursorY <= node.y + node.height and buffer.cursorX >= node.x and buffer.cursorX <= node.x + node.width:
+      if buffer.cursory >= node.y and buffer.cursory <= node.y + node.height and buffer.cursorx >= node.x and buffer.cursorx <= node.x + node.width:
         return some(node)
   return none(HtmlNode)
 
@@ -160,12 +135,12 @@ proc addNode*(buffer: Buffer, htmlNode: HtmlNode) =
       buffer.idelements[elem.id] = elem
 
 proc writefmt*(buffer: Buffer, str: string) =
-  buffer.text &= str
+  buffer.fmttext &= str
   if buffer.printwrite:
     stdout.write(str)
 
 proc writefmt*(buffer: Buffer, c: char) =
-  buffer.text &= c
+  buffer.rawtext &= $c
   if buffer.printwrite:
     stdout.write(c)
 
@@ -173,7 +148,7 @@ proc writeraw*(buffer: Buffer, str: string) =
   buffer.rawtext &= str
 
 proc writeraw*(buffer: Buffer, c: char) =
-  buffer.rawtext &= c
+  buffer.rawtext &= $c
 
 proc write*(buffer: Buffer, str: string) =
   buffer.writefmt(str)
@@ -184,10 +159,8 @@ proc write*(buffer: Buffer, c: char) =
   buffer.writeraw(c)
 
 proc clearText*(buffer: Buffer) =
-  buffer.text = ""
-  buffer.rawtext = ""
-  buffer.lines = @[0]
-  buffer.rawlines = @[0]
+  buffer.fmttext.setLen(0)
+  buffer.rawtext.setLen(0)
 
 proc clearNodes*(buffer: Buffer) =
   buffer.nodes.setLen(0)
@@ -199,229 +172,236 @@ proc clearNodes*(buffer: Buffer) =
 proc clearBuffer*(buffer: Buffer) =
   buffer.clearText()
   buffer.clearNodes()
-  buffer.cursorX = 0
-  buffer.cursorY = 1
-  buffer.fromX = 0
-  buffer.fromY = 0
+  buffer.cursorx = 0
+  buffer.cursory = 0
+  buffer.fromx = 0
+  buffer.fromy = 0
   buffer.hovertext = ""
   buffer.selectedlink = nil
 
 proc scrollTo*(buffer: Buffer, y: int): bool =
-  if y == buffer.fromY:
+  if y == buffer.fromy:
     return false
-  buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), y)
-  buffer.cursorY = min(max(buffer.fromY, buffer.cursorY), buffer.fromY + buffer.height)
+  buffer.fromy = min(max(buffer.lastLine() - buffer.height + 1, 0), y)
+  buffer.cursory = min(max(buffer.fromy, buffer.cursory), buffer.fromy + buffer.height)
   return true
 
 proc cursorTo*(buffer: Buffer, x: int, y: int): bool =
   result = false
-  buffer.cursorY = min(max(y, 1), buffer.lastLine())
-  if buffer.fromY >= buffer.cursorY:
-    buffer.fromY = max(buffer.cursorY - 1, 0)
+  buffer.cursory = min(max(y, 0), buffer.lastLine())
+  if buffer.fromy > buffer.cursory:
+    buffer.fromy = max(buffer.cursory, 0)
     result = true
-  elif buffer.fromY + buffer.height <= buffer.cursorY:
-    buffer.fromY = max(buffer.cursorY - buffer.height + 1, 0)
+  elif buffer.fromy + buffer.height - 1 <= buffer.cursory:
+    buffer.fromy = max(buffer.cursory - buffer.height + 2, 0)
     result = true
-  buffer.cursorX = min(max(x, 0), buffer.currentRawLineLength())
-  buffer.fromX = min(max(buffer.currentRawLineLength() - buffer.width + 1, 0), 0) #TODO
+  buffer.cursorx = min(max(x, 0), buffer.currentLineLength())
+  #buffer.fromX = min(max(buffer.currentLineLength() - buffer.width + 1, 0), 0) #TODO
 
 proc cursorDown*(buffer: Buffer): bool =
-  if buffer.cursorY < buffer.lastLine():
-    buffer.cursorY += 1
-    if buffer.cursorX > buffer.currentRawLineLength():
-      if buffer.xend == 0:
-        buffer.xend = buffer.cursorX
-      buffer.cursorX = buffer.currentRawLineLength()
+  if buffer.cursory < buffer.lastLine():
+    inc buffer.cursory
+    if buffer.cursorx >= buffer.currentLineLength():
+      buffer.cursorx = max(buffer.currentLineLength() - 1, 0)
     elif buffer.xend > 0:
-      buffer.cursorX = min(buffer.currentRawLineLength(), buffer.xend)
-    if buffer.cursorY > buffer.lastVisibleLine():
-      buffer.fromY += 1
+      buffer.cursorx = min(buffer.currentLineLength() - 1, buffer.xend)
+    if buffer.cursory >= buffer.lastVisibleLine() and buffer.lastVisibleLine() != buffer.lastLine():
+      inc buffer.fromy
       return true
   return false
 
 proc cursorUp*(buffer: Buffer): bool =
-  if buffer.cursorY > 1:
-    buffer.cursorY -= 1
-    if buffer.cursorX > buffer.currentRawLineLength():
-      if buffer.xend == 0:
-        buffer.xend = buffer.cursorX
-      buffer.cursorX = buffer.currentRawLineLength()
+  if buffer.cursory > 0:
+    dec buffer.cursory
+    if buffer.cursorx > buffer.currentLineLength():
+      if buffer.cursorx == 0:
+        buffer.xend = buffer.cursorx
+      buffer.cursorx = buffer.currentLineLength()
     elif buffer.xend > 0:
-      buffer.cursorX = min(buffer.currentRawLineLength(), buffer.xend)
-    if buffer.cursorY <= buffer.fromY:
-      buffer.fromY -= 1
+      buffer.cursorx = min(buffer.currentLineLength(), buffer.xend)
+    if buffer.cursory < buffer.fromy:
+      dec buffer.fromy
       return true
   return false
 
 proc cursorRight*(buffer: Buffer): bool =
-  if buffer.cursorX < buffer.currentRawLineLength():
-    buffer.cursorX += mk_wcwidth_cjk(buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX))
+  if buffer.cursorx < buffer.currentLineLength() - 1:
+    inc buffer.cursorx
     buffer.xend = 0
   else:
-    buffer.xend = buffer.cursorX
+    buffer.xend = buffer.cursorx
   return false
 
 proc cursorLeft*(buffer: Buffer): bool =
-  if buffer.cursorX > 0:
-    buffer.cursorX -= mk_wcwidth_cjk(buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX))
-    buffer.cursorX -= 1
+  if buffer.cursorx > 0:
+    dec buffer.cursorx
   buffer.xend = 0
   return false
 
 proc cursorLineBegin*(buffer: Buffer) =
-  buffer.cursorX = 0
+  buffer.cursorx = 0
   buffer.xend = 0
 
 proc cursorLineEnd*(buffer: Buffer) =
-  buffer.cursorX = buffer.currentRawLineLength()
-  buffer.xend = buffer.cursorX
+  buffer.cursorx = buffer.currentLineLength() - 1
+  buffer.xend = buffer.cursorx
 
 iterator revnodes*(buffer: Buffer): HtmlNode {.inline.} =
   var i = buffer.nodes.len - 1
   while i >= 0:
     yield buffer.nodes[i]
-    i -= 1
-
-proc cursorNextNode*(buffer: Buffer): bool =
-  for node in buffer.nodes:
-    if node.displayed():
-      if node.y > buffer.cursorY or (node.y == buffer.cursorY and node.x > buffer.cursorX):
-        return buffer.cursorTo(node.x, node.y)
-  buffer.cursorLineEnd()
-  return false
-
-proc cursorPrevNode*(buffer: Buffer): bool =
-  var prevnode: HtmlNode
-  for node in buffer.nodes:
-    if node.displayed():
-      if node.y >= buffer.cursorY and node.x >= buffer.cursorX and prevnode != nil:
-        return buffer.cursorTo(prevnode.x, prevnode.y)
-      prevnode = node
-  buffer.cursorLineBegin()
-  return false
+    dec i
 
 proc cursorNextWord*(buffer: Buffer): bool =
-  if buffer.cursorAtLineEnd():
-    if buffer.cursorY < buffer.lastLine():
-      let ret = buffer.cursorDown()
-      buffer.cursorLineBegin()
-      return ret
-    else:
-      buffer.cursorLineEnd()
-      return false
-
-  var res = buffer.cursorRight()
-  while buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX) != Rune(' '):
-    if buffer.cursorAtLineEnd():
-      return res
-    res = res or buffer.cursorRight()
+  let llen = buffer.currentLineLength() - 1
+  var r: Rune
+  var x = buffer.cursorx
+  var y = buffer.cursory
+  fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  while r != Rune(' '):
+    if x >= llen:
+      break
+    inc x
+    fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  while r == Rune(' '):
+    if x >= llen:
+      break
+    inc x
+    fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  if x >= llen:
+    if y < buffer.lastLine():
+      inc y
+      x = 0
+  return buffer.cursorTo(x, y)
 
 proc cursorPrevWord*(buffer: Buffer): bool =
-  if buffer.cursorX <= 1:
-    if buffer.cursorY > 1:
-      let ret = buffer.cursorUp()
-      buffer.cursorLineEnd()
-      return ret
-    else:
-      buffer.cursorLineBegin()
-      return false
-
-  discard buffer.cursorLeft()
-  while buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX) != Rune(' '):
-    if buffer.cursorX == 0:
-      return false
-    discard buffer.cursorLeft()
+  var r: Rune
+  var x = buffer.cursorx
+  var y = buffer.cursory
+  fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  while r != Rune(' '):
+    if x == 0:
+      break
+    dec x
+    fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  while r == Rune(' '):
+    if x == 0:
+      break
+    dec x
+    fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  if x == 0:
+    if y < buffer.lastLine():
+      dec y
+      x = buffer.rawtext[y].runeLen() - 1
+  return buffer.cursorTo(x, y)
 
 iterator revclickables*(buffer: Buffer): HtmlNode {.inline.} =
   var i = buffer.clickables.len - 1
   while i >= 0:
     yield buffer.clickables[i]
-    i -= 1
+    dec i
 
 proc cursorNextLink*(buffer: Buffer): bool =
   for node in buffer.clickables:
-    if node.y > buffer.cursorY or (node.y == buffer.cursorY and node.x > buffer.cursorX):
-      return buffer.cursorTo(node.x, node.y)
+    if node.y > buffer.cursory or (node.y == buffer.cursorY and node.x > buffer.cursorx):
+      result = buffer.cursorTo(node.x, node.y)
+      if buffer.cursorx < buffer.currentLineLength():
+        var r: Rune
+        fastRuneAt(buffer.rawtext[buffer.cursory], buffer.cursorx, r, false)
+        if r == Rune(' '):
+          return result or buffer.cursorNextWord()
+      return result
   return false
 
 proc cursorPrevLink*(buffer: Buffer): bool =
   for node in buffer.revclickables:
-    if node.y < buffer.cursorY or (node.y == buffer.cursorY and node.x < buffer.cursorX):
+    if node.y < buffer.cursorY or (node.y == buffer.cursorY and node.x < buffer.cursorx):
       return buffer.cursorTo(node.x, node.y)
   return false
 
 proc cursorFirstLine*(buffer: Buffer): bool =
-  if buffer.fromY > 0:
-    buffer.fromY = 0
+  if buffer.fromy > 0:
+    buffer.fromy = 0
     result = true
   else:
     result = false
 
-  buffer.cursorY = 1
+  buffer.cursorY = 0
   buffer.cursorLineBegin()
 
 proc cursorLastLine*(buffer: Buffer): bool =
-  if buffer.fromY < buffer.lastLine() - buffer.height:
-    buffer.fromY = buffer.lastLine() - (buffer.height - 1)
+  if buffer.fromy < buffer.lastLine() - buffer.height:
+    buffer.fromy = buffer.lastLine() - (buffer.height - 2)
     result = true
   else:
     result = false
-  buffer.cursorY = buffer.lastLine()
+  buffer.cursory = buffer.lastLine()
   buffer.cursorLineBegin()
 
+proc cursorTop*(buffer: Buffer): bool =
+  buffer.cursorY = buffer.fromy
+  return false
+
+proc cursorMiddle*(buffer: Buffer): bool =
+  buffer.cursorY = min(buffer.fromy + (buffer.height - 2) div 2, buffer.lastLine())
+  return false
+
+proc cursorBottom*(buffer: Buffer): bool =
+  buffer.cursorY = min(buffer.fromy + buffer.height - 2, buffer.lastLine())
+  return false
+
+proc centerLine*(buffer: Buffer): bool =
+  let ny = max(min(buffer.cursory - buffer.height div 2, buffer.lastLine() - buffer.height + 2), 0)
+  if ny != buffer.fromy:
+    buffer.fromy = ny
+    return true
+  return false
+
 proc halfPageUp*(buffer: Buffer): bool =
-  buffer.cursorY = max(buffer.cursorY - buffer.height div 2 + 1, 1)
-  if buffer.fromY - 1 > buffer.cursorY or true:
-    buffer.fromY = max(0, buffer.fromY - buffer.height div 2 + 1)
+  buffer.cursory = max(buffer.cursorY - buffer.height div 2 + 1, 0)
+  let nfy = max(0, buffer.fromy - buffer.height div 2 + 1)
+  if nfy != buffer.fromy:
+    buffer.fromy = nfy
     return true
   return false
 
 proc halfPageDown*(buffer: Buffer): bool =
-  buffer.cursorY = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine())
-  buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromY + buffer.height div 2 - 1)
-  return true
+  buffer.cursory = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine())
+  let nfy = min(max(buffer.lastLine() - buffer.height + 2, 0), buffer.fromy + buffer.height div 2 - 1)
+  if nfy != buffer.fromy:
+    buffer.fromy = nfy
+    return true
+  return false
 
 proc pageUp*(buffer: Buffer): bool =
   buffer.cursorY = max(buffer.cursorY - buffer.height + 1, 1)
-  buffer.fromY = max(0, buffer.fromY - buffer.height)
+  buffer.fromy = max(0, buffer.fromy - buffer.height)
   return true
 
 proc pageDown*(buffer: Buffer): bool =
   buffer.cursorY = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine())
-  buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromY + buffer.height div 2)
-  return true
-
-proc cursorTop*(buffer: Buffer): bool =
-  buffer.cursorY = buffer.fromY + 1
-  return false
-
-proc cursorMiddle*(buffer: Buffer): bool =
-  buffer.cursorY = min(buffer.fromY + buffer.height div 2, buffer.lastLine())
-  return false
-
-proc cursorBottom*(buffer: Buffer): bool =
-  buffer.cursorY = min(buffer.fromY + buffer.height - 1, buffer.lastLine())
-  return false
-
-proc centerLine*(buffer: Buffer): bool =
-  if min(buffer.cursorY - buffer.height div 2, buffer.lastLine()) == buffer.fromY:
-    return false
-  buffer.fromY = min(buffer.cursorY - buffer.height div 2, buffer.lastLine())
+  buffer.fromy = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromy + buffer.height div 2)
   return true
 
 proc scrollDown*(buffer: Buffer): bool =
-  if buffer.fromY + buffer.height <= buffer.lastLine():
-    buffer.fromY += 1
-    if buffer.fromY >= buffer.cursorY:
+  if buffer.fromy + buffer.height - 1 <= buffer.lastLine():
+    inc buffer.fromy
+    if buffer.fromy >= buffer.cursory:
       discard buffer.cursorDown()
     return true
   discard buffer.cursorDown()
   return false
 
 proc scrollUp*(buffer: Buffer): bool =
-  if buffer.fromY > 0:
-    buffer.fromY -= 1
-    if buffer.fromY + buffer.height <= buffer.cursorY:
+  if buffer.fromy > 0:
+    dec buffer.fromy
+    if buffer.fromy + buffer.height - 1 <= buffer.cursorY:
       discard buffer.cursorUp()
     return true
   discard buffer.cursorUp()
@@ -437,6 +417,13 @@ proc checkLinkSelection*(buffer: Buffer): bool =
       buffer.selectedlink.fmttext = buffer.selectedlink.getFmtText()
       buffer.selectedlink = nil
       buffer.hovertext = ""
+      var stack: seq[HtmlNode]
+      stack.add(anchor)
+      while stack.len > 0:
+        let elem = stack.pop()
+        elem.fmttext = elem.getFmtText()
+        for child in elem.childNodes:
+          stack.add(child)
   for node in buffer.links:
     if buffer.cursorOnNode(node):
       buffer.selectedlink = node
@@ -444,7 +431,13 @@ proc checkLinkSelection*(buffer: Buffer): bool =
       assert(anchor != nil)
       anchor.selected = true
       buffer.hovertext = HtmlAnchorElement(anchor).href
-      node.fmttext = node.getFmtText()
+      var stack: seq[HtmlNode]
+      stack.add(anchor)
+      while stack.len > 0:
+        let elem = stack.pop()
+        elem.fmttext = elem.getFmtText()
+        for child in elem.childNodes:
+          stack.add(child)
       return true
   return false
 
@@ -460,3 +453,12 @@ proc setLocation*(buffer: Buffer, uri: Uri) =
 
 proc gotoLocation*(buffer: Buffer, uri: Uri) =
   buffer.document.location = buffer.document.location.combine(uri)
+
+proc refreshTermAttrs*(buffer: Buffer): bool =
+  let newAttrs = getTermAttributes()
+  if newAttrs != buffer.attrs:
+    buffer.attrs = newAttrs
+    buffer.width = newAttrs.termWidth
+    buffer.height = newAttrs.termHeight
+    return true
+  return false
diff --git a/config b/config
index 48e81660..5724dbc8 100644
--- a/config
+++ b/config
@@ -11,9 +11,7 @@ nmap \e[C ACTION_CURSOR_RIGHT
 nmap ^ ACTION_CURSOR_LINEBEGIN
 nmap $ ACTION_CURSOR_LINEEND
 nmap b ACTION_CURSOR_PREV_WORD
-nmap B ACTION_CURSOR_PREV_NODE
 nmap w ACTION_CURSOR_NEXT_WORD
-nmap W ACTION_CURSOR_NEXT_NODE
 nmap [ ACTION_CURSOR_PREV_LINK
 nmap ] ACTION_CURSOR_NEXT_LINK
 nmap H ACTION_CURSOR_TOP
diff --git a/config.nim b/config.nim
index 8df30caf..abdd74db 100644
--- a/config.nim
+++ b/config.nim
@@ -13,7 +13,6 @@ type
     ACTION_CURSOR_UP, ACTION_CURSOR_DOWN, ACTION_CURSOR_LEFT, ACTION_CURSOR_RIGHT,
     ACTION_CURSOR_LINEEND, ACTION_CURSOR_LINEBEGIN,
     ACTION_CURSOR_NEXT_WORD, ACTION_CURSOR_PREV_WORD,
-    ACTION_CURSOR_NEXT_NODE, ACTION_CURSOR_PREV_NODE,
     ACTION_CURSOR_NEXT_LINK, ACTION_CURSOR_PREV_LINK,
     ACTION_PAGE_DOWN, ACTION_PAGE_UP,
     ACTION_HALF_PAGE_DOWN, ACTION_HALF_PAGE_UP,
@@ -48,10 +47,10 @@ proc getRealKey(key: string): string =
         realk &= c
       skip = false
     elif c == 'C':
-      control += 1
+      inc control
       currchar = c
     elif c == '-' and control == 1:
-      control += 1
+      inc control
     elif control == 1:
       realk &= 'C' & c
       control = 0
diff --git a/display.nim b/display.nim
index 30081c57..78401598 100644
--- a/display.nim
+++ b/display.nim
@@ -27,8 +27,8 @@ type
     x: int
     y: int
     lastwidth: int
-    atchar: int
-    atrawchar: int
+    fmtline: string
+    rawline: string
     centerqueue: seq[HtmlNode]
     centerlen: int
     blanklines: int
@@ -39,30 +39,35 @@ type
     listval: int
 
 func newRenderState(): RenderState =
-  return RenderState(y: 1)
+  return RenderState()
+
+proc write(state: var RenderState, s: string) =
+  state.fmtline &= s
+  state.rawline &= s
+
+proc write(state: var RenderState, fs: string, rs: string) =
+  state.fmtline &= fs
+  state.rawline &= rs
 
 proc flushLine(buffer: Buffer, state: var RenderState) =
-  if buffer.onNewLine():
-    state.blanklines += 1
-  buffer.write('\n')
+  if state.rawline.len == 0:
+    inc state.blanklines
+  assert(state.rawline.runeLen() < buffer.width, "line too long:\n" & state.rawline)
+  buffer.writefmt(state.fmtline)
+  buffer.writeraw(state.rawline)
   state.x = 0
-  state.y += 1
-  state.atchar += 1
-  state.atrawchar += 1
+  inc state.y
   state.nextspaces = 0
-  buffer.lines.add(state.atchar)
-  buffer.rawlines.add(state.atrawchar)
-  assert(buffer.onNewLine())
+  state.fmtline = ""
+  state.rawline = ""
 
 proc addSpaces(buffer: Buffer, state: var RenderState, n: int) =
   if state.x + n > buffer.width:
     buffer.flushLine(state)
     return
   state.blankspaces += n
-  buffer.write(' '.repeat(n))
+  state.write(' '.repeat(n))
   state.x += n
-  state.atchar += n
-  state.atrawchar += n
 
 proc writeWrappedText(buffer: Buffer, state: var RenderState, node: HtmlNode) =
   state.lastwidth = 0
@@ -77,17 +82,12 @@ proc writeWrappedText(buffer: Buffer, state: var RenderState, node: HtmlNode) =
 
     for r in w.runes:
       if r == Rune(' '):
-        var s: Rune
-        fastRuneAt(rawword, 0, s, false)
-        #if s == Rune(' ') and prevl:
-        #  fmtword = fmtword.runeSubstr(1)
-        #  rawword = rawword.runeSubstr(1)
-        #  state.x -= 1
-        buffer.writefmt(fmtword)
-        buffer.writeraw(rawword)
-        state.atchar += fmtword.runeLen()
-        state.atrawchar += rawword.runeLen()
-        var a = rawword
+        if rawword[0] == ' ' and prevl: #first byte can't fool comparison to ascii
+          fmtword = fmtword.substr(1)
+          rawword = rawword.substr(1)
+          state.x -= 1
+          prevl = false
+        state.write(fmtword, rawword)
         fmtword = ""
         rawword = ""
 
@@ -96,29 +96,26 @@ proc writeWrappedText(buffer: Buffer, state: var RenderState, node: HtmlNode) =
 
       state.x += mk_wcwidth_cjk(r)
 
-      if prevl:
-        state.x += mk_wcswidth_cjk(rawword)
-        prevl = false
-
-      if state.x > buffer.width:
+      if state.x >= buffer.width:
         state.lastwidth = max(state.lastwidth, state.x)
         buffer.flushLine(state)
-        state.x = -1
+        state.x = mk_wcswidth_cjk(rawword)
         prevl = true
       else:
         state.lastwidth = max(state.lastwidth, state.x)
 
-      n += 1
+      inc n
+
+  state.write(fmtword, rawword)
+  if prevl:
+    state.x += mk_wcswidth_cjk(rawword)
+    prevl = false
 
-  buffer.writefmt(fmtword)
-  buffer.writeraw(rawword)
-  state.atchar += fmtword.runeLen()
-  state.atrawchar += rawword.runeLen()
   state.lastwidth = max(state.lastwidth, state.x)
 
 proc preAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) =
   let elem = node.nodeAttr()
-  if not buffer.onNewLine() and node.openblock and state.blanklines == 0:
+  if state.rawline.len > 0 and node.openblock and state.blanklines == 0:
     buffer.flushLine(state)
 
   if node.openblock:
@@ -126,13 +123,13 @@ proc preAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) =
       buffer.flushLine(state)
     state.indent += elem.indent
 
-  if not buffer.onNewLine() and state.blanklines == 0 and node.displayed():
+  if state.rawline.len > 0 and state.blanklines == 0 and node.displayed():
     buffer.addSpaces(state, state.nextspaces)
     state.nextspaces = 0
     if state.blankspaces < max(elem.margin, elem.marginleft):
       buffer.addSpaces(state, max(elem.margin, elem.marginleft) - state.blankspaces)
 
-  if elem.centered and buffer.onNewLine() and node.displayed():
+  if elem.centered and state.rawline.len == 0 and node.displayed():
     buffer.addSpaces(state, max(buffer.width div 2 - state.centerlen div 2, 0))
     state.centerlen = 0
   
@@ -143,15 +140,13 @@ proc preAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) =
     of TAG_UL:
       listchar = "•"
     of TAG_OL:
-      state.listval += 1
+      inc state.listval
       listchar = $state.listval & ")"
     else:
       return
     buffer.addSpaces(state, state.indent)
-    buffer.write(listchar)
+    state.write(listchar)
     state.x += listchar.runeLen()
-    state.atchar += listchar.runeLen()
-    state.atrawchar += listchar.runeLen()
     buffer.addSpaces(state, 1)
 
 proc postAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) =
@@ -161,9 +156,10 @@ proc postAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) =
     state.blanklines = 0
     state.blankspaces = 0
 
-  if not buffer.onNewLine() and state.blanklines == 0:
+  if state.rawline.len > 0 and state.blanklines == 0:
     state.nextspaces += max(elem.margin, elem.marginright)
     if node.closeblock and (node.isTextNode() or elem.numChildNodes == 0):
+      state.write($node.nodeAttr().tagType)
       buffer.flushLine(state)
 
   if node.closeblock:
@@ -176,6 +172,8 @@ proc postAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) =
     buffer.flushLine(state)
 
 proc renderNode(buffer: Buffer, node: HtmlNode, state: var RenderState) =
+  if node.isDocument() or node.parentNode == nil:
+    return
   let elem = node.nodeAttr()
   if elem.tagType == TAG_TITLE:
     if node.isTextNode():
@@ -213,11 +211,9 @@ proc renderNode(buffer: Buffer, node: HtmlNode, state: var RenderState) =
 
   node.x = state.x
   node.y = state.y
-  node.fmtchar = state.atchar
-  node.rawchar = state.atrawchar
   buffer.writeWrappedText(state, node)
-  node.fmtend = state.atchar
-  node.rawend = state.atrawchar
+  node.ex = state.x
+  node.ey = state.y
   node.width = state.lastwidth - node.x - 1
   node.height = state.y - node.y + 1
 
@@ -237,10 +233,8 @@ type
     html*: HtmlNode
 
 proc setLastHtmlLine(buffer: Buffer, state: var RenderState) =
-  if buffer.text.len != buffer.lines[^1]:
-    state.atchar = buffer.text.len
-    state.atrawchar = buffer.rawtext.runeLen()
-  buffer.flushLine(state)
+  if state.rawline.len != 0:
+    buffer.flushLine(state)
 
 proc renderHtml*(buffer: Buffer) =
   var stack: seq[XmlHtmlNode]
@@ -263,12 +257,30 @@ proc renderHtml*(buffer: Buffer) =
         if not last and not child.html.hidden:
           last = true
           if HtmlElement(currElem.html).display == DISPLAY_BLOCK:
+            eprint "elem", HtmlElement(currElem.html).tagType, "close @", child.html.nodeAttr().tagType
             stack[^1].html.closeblock = true
       if last:
+        eprint "elem", HtmlElement(currElem.html).tagType, "open @", stack[^1].html.nodeAttr().tagType
         if HtmlElement(currElem.html).display == DISPLAY_BLOCK:
           stack[^1].html.openblock = true
   buffer.setLastHtmlLine(state)
 
+proc nrenderHtml*(buffer: Buffer) =
+  var stack: seq[HtmlNode]
+  let first = buffer.document
+  stack.add(first)
+
+  var state = newRenderState()
+  while stack.len > 0:
+    let currElem = stack.pop()
+    buffer.addNode(currElem)
+    buffer.renderNode(currElem, state)
+    if currElem.childNodes.len > 0:
+      for item in currElem.childNodes:
+        stack.add(item)
+
+  buffer.setLastHtmlLine(state)
+
 proc drawHtml(buffer: Buffer) =
   var state = newRenderState()
   for node in buffer.nodes:
@@ -276,7 +288,7 @@ proc drawHtml(buffer: Buffer) =
   buffer.setLastHtmlLine(state)
 
 proc statusMsgForBuffer(buffer: Buffer) =
-  var msg = $buffer.cursorY & "/" & $buffer.lastLine() & " (" &
+  var msg = $buffer.cursory & "/" & $buffer.lastLine() & " (" &
             $buffer.atPercentOf() & "%) " &
             "<" & buffer.title & ">"
   if buffer.hovertext.len > 0:
@@ -284,9 +296,9 @@ proc statusMsgForBuffer(buffer: Buffer) =
   statusMsg(msg.maxString(buffer.width), buffer.height)
 
 proc cursorBufferPos(buffer: Buffer) =
-  var x = buffer.cursorX
-  var y = buffer.cursorY - 1 - buffer.fromY
-  termGoto(x, y)
+  var x = buffer.cursorx
+  var y = buffer.cursory - 1 - buffer.fromY
+  termGoto(x, y + 1)
 
 proc displayBuffer(buffer: Buffer) =
   eraseScreen()
@@ -298,7 +310,8 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
   var s = ""
   var feedNext = false
   while true:
-    cursorBufferPos(buffer)
+    stdout.showCursor()
+    buffer.cursorBufferPos()
     if not feedNext:
       s = ""
     else:
@@ -320,9 +333,7 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
     of ACTION_CURSOR_LINEBEGIN: buffer.cursorLineBegin()
     of ACTION_CURSOR_LINEEND: buffer.cursorLineEnd()
     of ACTION_CURSOR_NEXT_WORD: redraw = buffer.cursorNextWord()
-    of ACTION_CURSOR_NEXT_NODE: redraw = buffer.cursorNextNode()
     of ACTION_CURSOR_PREV_WORD: redraw = buffer.cursorPrevWord()
-    of ACTION_CURSOR_PREV_NODE: redraw = buffer.cursorPrevNode()
     of ACTION_CURSOR_NEXT_LINK: redraw = buffer.cursorNextLink()
     of ACTION_CURSOR_PREV_LINK: redraw = buffer.cursorPrevLink()
     of ACTION_PAGE_DOWN: redraw = buffer.pageDown()
@@ -361,7 +372,7 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
         buffer.setLocation(parseUri(url))
         return true
     of ACTION_LINE_INFO:
-      statusMsg("line " & $buffer.cursorY & "/" & $buffer.lastLine() & " col " & $buffer.cursorX & "/" & $buffer.currentRawLineLength(), buffer.width)
+      statusMsg("line " & $buffer.cursory & "/" & $buffer.lastLine() & " col " & $buffer.cursorx & "/" & $buffer.realCurrentLineLength(), buffer.width)
       nostatus = true
     of ACTION_FEED_NEXT:
       feedNext = true
@@ -371,6 +382,37 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
       redraw = true
     of ACTION_REDRAW: redraw = true
     else: discard
+    stdout.hideCursor()
+
+    let prevlink = buffer.selectedlink
+    let sel = buffer.checkLinkSelection()
+    if sel:
+      buffer.clearText()
+      buffer.drawHtml()
+      termGoto(0, buffer.selectedlink.y - buffer.fromy)
+      stdout.eraseLine()
+      for i in buffer.selectedlink.y..buffer.selectedlink.ey:
+        if i < buffer.fromy + buffer.height - 1:
+          let line = buffer.fmttext[i]
+          print(line)
+          print('\n')
+      print("".ansiReset())
+    
+    if prevlink != nil:
+      buffer.clearText()
+      buffer.drawHtml()
+      termGoto(0, prevlink.y - buffer.fromy)
+      for i in prevlink.y..prevlink.ey:
+        if i < buffer.fromy + buffer.height - 1:
+          let line = buffer.fmttext[i]
+          stdout.eraseLine()
+          print(line)
+          print('\n')
+      print("".ansiReset())
+
+    if buffer.refreshTermAttrs():
+      redraw = true
+      reshape = true
 
     if reshape:
       buffer.clearText()
@@ -378,20 +420,6 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
     if redraw:
       buffer.displayBuffer()
 
-    let prevlink = buffer.selectedlink
-    let sel = buffer.checkLinkSelection()
-    if prevlink != nil and prevlink != buffer.selectedlink:
-      termGoto(prevlink.x - buffer.fromX, prevlink.y - buffer.fromY - 1)
-      print(buffer.textBetween(prevlink.fmtchar, prevlink.fmtend).ansiReset())
-    if sel and buffer.selectedlink.y < buffer.fromY + buffer.height:
-      termGoto(buffer.selectedlink.x - buffer.fromX, buffer.selectedlink.y - buffer.fromY - 1)
-      let str = buffer.textBetween(buffer.selectedlink.fmtchar, buffer.selectedlink.fmtend)
-      #var i = str.findChar(Rune('\n'))
-      #while i != -1:
-      #  print("".ansiStyle(styleUnderscore))
-      #  i = str.findChar(Rune('\n'), i + 1)
-      print(str.ansiStyle(styleUnderscore).ansiReset())
-
     if not nostatus:
       buffer.statusMsgForBuffer()
     else:
diff --git a/enums.nim b/enums.nim
index e11059d0..e95ca6ac 100644
--- a/enums.nim
+++ b/enums.nim
@@ -5,7 +5,8 @@ type
 
   DisplayType* =
     enum
-    DISPLAY_INLINE, DISPLAY_BLOCK, DISPLAY_SINGLE, DISPLAY_LIST_ITEM, DISPLAY_NONE
+    DISPLAY_INLINE, DISPLAY_BLOCK, DISPLAY_LIST_ITEM, DISPLAY_TABLE_COLUMN,
+    DISPLAY_INLINE_BLOCK, DISPLAY_NONE
 
   InputType* =
     enum
@@ -32,35 +33,47 @@ type
     TAG_CODE, TAG_DATA, TAG_DFN, TAG_EM, TAG_I, TAG_KBD, TAG_MARK, TAG_Q,
     TAG_RB, TAG_RP, TAG_RT, TAG_RTC, TAG_RUBY, TAG_S, TAG_SAMP, TAG_SMALL,
     TAG_SPAN, TAG_STRONG, TAG_SUB, TAG_SUP, TAG_TIME, TAG_U, TAG_VAR, TAG_WBR,
-    TAG_AREA, TAG_AUDIO, TAG_IMG, TAG_MAP, TAG_TRACK, TAG_VIDEO, TAG_EMBED,
+    TAG_AREA, TAG_AUDIO, TAG_IMG, TAG_MAP, TAG_TRACK, TAG_VIDEO,
     TAG_IFRAME, TAG_OBJECT, TAG_PARAM, TAG_PICTURE, TAG_PORTAL, TAG_SOURCE,
     TAG_CANVAS, TAG_NOSCRIPT, TAG_SCRIPT, TAG_DEL, TAG_INS, TAG_CAPTION,
     TAG_COL, TAG_COLGROUP, TAG_TABLE, TAG_TBODY, TAG_TD, TAG_TFOOT, TAG_TH,
     TAG_THEAD, TAG_TR, TAG_BUTTON, TAG_DATALIST, TAG_FIELDSET, TAG_FORM,
     TAG_INPUT, TAG_LABEL, TAG_LEGEND, TAG_METER, TAG_OPTGROUP, TAG_OPTION,
     TAG_OUTPUT, TAG_PROGRESS, TAG_SELECT, TAG_TEXTAREA, TAG_DETAILS,
-    TAG_DIALOG, TAG_MENU, TAG_SUMMARY, TAG_BLINK, TAG_CENTER, TAG_COMMAND,
-    TAG_CONTENT, TAG_DIR, TAG_FONT, TAG_FRAME, TAG_NOFRAMES, TAG_FRAMESET,
-    TAG_STRIKE, TAG_TT
+    TAG_DIALOG, TAG_MENU, TAG_SUMMARY, TAG_BLINK, TAG_CENTER, TAG_CONTENT,
+    TAG_DIR, TAG_FONT, TAG_FRAME, TAG_NOFRAMES, TAG_FRAMESET, TAG_STRIKE, TAG_TT
 
-const InlineTagTypes* = {
+const DisplayInlineTags* = {
   TAG_A, TAG_ABBR, TAG_B, TAG_BDO, TAG_BR, TAG_BUTTON, TAG_CITE, TAG_CODE,
   TAG_DEL, TAG_DFN, TAG_EM, TAG_FONT, TAG_I, TAG_IMG, TAG_INS, TAG_INPUT,
   TAG_IFRAME, TAG_KBD, TAG_LABEL, TAG_MAP, TAG_OBJECT, TAG_Q, TAG_SAMP,
   TAG_SCRIPT, TAG_SELECT, TAG_SMALL, TAG_SPAN, TAG_STRONG, TAG_SUB, TAG_SUP,
   TAG_TEXTAREA, TAG_TT, TAG_VAR, TAG_FONT, TAG_IFRAME, TAG_U, TAG_S, TAG_STRIKE,
-  TAG_WBR
+  TAG_FRAME, TAG_IMG, TAG_INPUT
 }
 
-const BlockTagTypes* = {
+const DisplayNoneTags* = {
+  TAG_AREA, TAG_BASE, TAG_SOURCE, TAG_TRACK, TAG_LINK, TAG_META, TAG_PARAM, TAG_WBR
+}
+
+const DisplayInlineBlockTags* = {
+  TAG_IMG
+}
+
+const DisplayTableColumnTags* = {
+  TAG_COL
+}
+
+const DisplayBlockTags* = {
   TAG_ADDRESS, TAG_BLOCKQUOTE, TAG_CENTER, TAG_DEL, TAG_DIR, TAG_DIV, TAG_DL,
   TAG_FIELDSET, TAG_FORM, TAG_H1, TAG_H2, TAG_H3, TAG_H4, TAG_H5, TAG_H6,
   TAG_HR, TAG_INS, TAG_MENU, TAG_NOFRAMES, TAG_NOSCRIPT, TAG_OL, TAG_P, TAG_PRE,
-  TAG_TABLE, TAG_UL, TAG_CENTER, TAG_DIR, TAG_MENU, TAG_NOFRAMES
+  TAG_TABLE, TAG_UL, TAG_CENTER, TAG_DIR, TAG_MENU, TAG_NOFRAMES, TAG_BODY,
+  #single
+  TAG_HR
 }
 
 const SingleTagTypes* = {
-  TAG_AREA, TAG_BASE, TAG_BR, TAG_COL, TAG_EMBED, TAG_FRAME, TAG_HR, TAG_IMG,
-  TAG_INPUT, TAG_SOURCE, TAG_TRACK, TAG_LINK, TAG_META, TAG_PARAM, TAG_WBR,
-  TAG_COMMAND
+  TAG_AREA, TAG_BASE, TAG_BR, TAG_COL, TAG_FRAME, TAG_HR, TAG_IMG, TAG_INPUT,
+  TAG_SOURCE, TAG_TRACK, TAG_LINK, TAG_META, TAG_PARAM, TAG_WBR, TAG_LI
 }
diff --git a/htmlelement.nim b/htmlelement.nim
index 3ead2f15..7c0d9169 100644
--- a/htmlelement.nim
+++ b/htmlelement.nim
@@ -3,7 +3,6 @@ import terminal
 import uri
 import unicode
 
-import fusion/htmlparser
 import fusion/htmlparser/xmltree
 
 import twtstr
@@ -28,10 +27,8 @@ type
     fmttext*: seq[string]
     x*: int
     y*: int
-    fmtchar*: int
-    rawchar*: int
-    fmtend*: int
-    rawend*: int
+    ex*: int
+    ey*: int
     width*: int
     height*: int
     openblock*: bool
@@ -45,8 +42,8 @@ type
   HtmlElement* = ref HtmlElementObj
   HtmlElementObj = object of HtmlNodeObj
     id*: string
+    class*: string
     tagType*: TagType
-    name*: string
     centered*: bool
     display*: DisplayType
     innerText*: string
@@ -77,6 +74,7 @@ type
 
   HtmlSelectElement* = ref HtmlSelectElementObj
   HtmlSelectElementObj = object of HtmlElementObj
+    name*: string
     value*: string
 
   HtmlOptionElement* = ref HtmlOptionElementObj
@@ -103,7 +101,7 @@ macro genEnumCase(s: string): untyped =
   branch.add(ret)
   casestmt.add(branch)
 
-func tagType*(s: string): TagType =
+func tagType(s: string): TagType =
   genEnumCase(s)
 
 func nodeAttr*(node: HtmlNode): HtmlElement =
@@ -192,6 +190,9 @@ func ancestor*(htmlNode: HtmlNode, tagType: TagType): HtmlElement =
   while result != nil and result.tagType != tagType:
     result = result.parentElement
 
+func displayWhitespace*(htmlElem: HtmlElement): bool =
+  return htmlElem.display == DISPLAY_INLINE or htmlElem.display == DISPLAY_INLINE_BLOCK
+
 proc getRawText*(htmlNode: HtmlNode): string =
   if htmlNode.isElemNode():
     case HtmlElement(htmlNode).tagType
@@ -202,9 +203,9 @@ proc getRawText*(htmlNode: HtmlNode): string =
       result = htmlNode.rawtext.remove("\n")
       if unicode.strip(result).runeLen() > 0:
         if htmlNode.nodeAttr().display != DISPLAY_INLINE:
-          if htmlNode.previousSibling == nil or htmlNode.previousSibling.nodeAttr().display != DISPLAY_INLINE:
+          if htmlNode.previousSibling == nil or htmlNode.previousSibling.nodeAttr().displayWhitespace():
             result = unicode.strip(result, true, false)
-          if htmlNode.nextSibling == nil or htmlNode.nextSibling.nodeAttr().display != DISPLAY_INLINE:
+          if htmlNode.nextSibling == nil or htmlNode.nextSibling.nodeAttr().displayWhitespace():
             result = unicode.strip(result, false, true)
       else:
         result = ""
@@ -259,12 +260,12 @@ proc getHtmlElement*(xmlElement: XmlNode, parentNode: HtmlNode): HtmlElement =
 
   result.id = xmlElement.attr("id")
 
-  if tagType in InlineTagTypes:
+  if tagType in DisplayInlineTags:
     result.display = DISPLAY_INLINE
-  elif tagType in BlockTagTypes:
+  elif tagType in DisplayBlockTags:
     result.display = DISPLAY_BLOCK
-  elif tagType in SingleTagTypes:
-    result.display = DISPLAY_SINGLE
+  elif tagType in DisplayInlineBlockTags:
+    result.display = DISPLAY_INLINE_BLOCK
   elif tagType ==  TAG_LI:
     result.display = DISPLAY_LIST_ITEM
   else:
diff --git a/main.nim b/main.nim
index db4b0f6d..b6ee8cb2 100644
--- a/main.nim
+++ b/main.nim
@@ -1,6 +1,7 @@
 import httpClient
 import uri
 import os
+import streams
 
 import fusion/htmlparser
 import fusion/htmlparser/xmltree
@@ -10,13 +11,19 @@ import termattrs
 import buffer
 import twtio
 import config
+import twtstr
+import parser
 
+let clientInstance = newHttpClient()
 proc loadRemotePage*(url: string): string =
-  return newHttpClient().getContent(url)
+  return clientInstance.getContent(url)
 
 proc loadLocalPage*(url: string): string =
   return readFile(url)
 
+proc getRemotePage*(url: string): Stream =
+  return clientInstance.get(url).bodyStream
+
 proc loadPageUri(uri: Uri, currentcontent: XmlNode): XmlNode =
   var moduri = uri
   moduri.anchor = ""
@@ -25,6 +32,7 @@ proc loadPageUri(uri: Uri, currentcontent: XmlNode): XmlNode =
   elif uri.scheme == "" or uri.scheme == "file":
     return parseHtml(loadLocalPage($moduri))
   else:
+    #return nparseHtml(getRemotePage($moduri))
     return parseHtml(loadRemotePage($moduri))
 
 var buffers: seq[Buffer]
@@ -55,4 +63,9 @@ proc main*() =
     lastUri = newUri
 
 #waitFor loadPage("https://lite.duckduckgo.com/lite/?q=hello%20world")
-main()
+#eprint mk_wcswidth_cjk("abc•de")
+var buf = newBuffer(getTermAttributes())
+buf.document = nparseHtml(getRemotePage("http://lite.duckduckgo.com"))
+buf.nrenderHtml()
+discard displayPage(getTermAttributes(), buf)
+#main()
diff --git a/parser.nim b/parser.nim
index 591a43e6..d69accf3 100644
--- a/parser.nim
+++ b/parser.nim
@@ -1,17 +1,179 @@
 import parsexml
 import htmlelement
 import streams
+import macros
 
-func parseNextNode(str: string) =
-  return
+import twtio
+import enums
+import strutils
+
+#> no I won't manually write all this down
+#> maybe todo to accept stuff other than tagtype (idk how useful that'd be)
+#still todo, it'd be very useful
+macro genEnumCase(s: string): untyped =
+  let casestmt = nnkCaseStmt.newTree() 
+  casestmt.add(ident("s"))
+  for i in low(TagType) .. high(TagType):
+    let ret = nnkReturnStmt.newTree()
+    ret.add(newLit(TagType(i)))
+    let branch = nnkOfBranch.newTree()
+    let enumname = $TagType(i)
+    let tagname = enumname.substr("TAG_".len, enumname.len - 1).tolower()
+    branch.add(newLit(tagname))
+    branch.add(ret)
+    casestmt.add(branch)
+  let ret = nnkReturnStmt.newTree()
+  ret.add(newLit(TAG_UNKNOWN))
+  let branch = nnkElse.newTree()
+  branch.add(ret)
+  casestmt.add(branch)
+
+func tagType(s: string): TagType =
+  genEnumCase(s)
+
+func newHtmlElement(tagType: TagType): HtmlElement =
+  case tagType
+  of TAG_INPUT: result = new(HtmlInputElement)
+  of TAG_A: result = new(HtmlAnchorElement)
+  of TAG_SELECT: result = new(HtmlSelectElement)
+  of TAG_OPTION: result = new(HtmlOptionElement)
+  else: result = new(HtmlElement)
+  result.tagType = tagType
+  result.nodeType = NODE_ELEMENT
+
+func toInputType*(str: string): InputType =
+  case str
+  of "button": INPUT_BUTTON
+  of "checkbox": INPUT_CHECKBOX
+  of "color": INPUT_COLOR
+  of "date": INPUT_DATE
+  of "datetime_local": INPUT_DATETIME_LOCAL
+  of "email": INPUT_EMAIL
+  of "file": INPUT_FILE
+  of "hidden": INPUT_HIDDEN
+  of "image": INPUT_IMAGE
+  of "month": INPUT_MONTH
+  of "number": INPUT_NUMBER
+  of "password": INPUT_PASSWORD
+  of "radio": INPUT_RADIO
+  of "range": INPUT_RANGE
+  of "reset": INPUT_RESET
+  of "search": INPUT_SEARCH
+  of "submit": INPUT_SUBMIT
+  of "tel": INPUT_TEL
+  of "text": INPUT_TEXT
+  of "time": INPUT_TIME
+  of "url": INPUT_URL
+  of "week": INPUT_WEEK
+  else: INPUT_UNKNOWN
+
+func toInputSize*(str: string): int =
+  if str.len == 0:
+    return 20
+  for c in str:
+    if not c.isDigit:
+      return 20
+  return str.parseInt()
+
+proc applyAttribute(htmlElement: HtmlElement, key: string, value: string) =
+  case key
+  of "id": htmlElement.id = value
+  of "class": htmlElement.class = value
+  of "name":
+    case htmlElement.tagType
+    of TAG_SELECT: HtmlSelectElement(htmlElement).name = value
+    else: discard
+  of "value":
+    case htmlElement.tagType
+    of TAG_INPUT: HtmlInputElement(htmlElement).value = value
+    of TAG_SELECT: HtmlSelectElement(htmlElement).value = value
+    of TAG_OPTION: HtmlOptionElement(htmlElement).value = value
+    else: discard
+  of "href":
+    case htmlElement.tagType
+    of TAG_A: HtmlAnchorElement(htmlElement).href = value
+    else: discard
+  of "type":
+    case htmlElement.tagType
+    of TAG_INPUT: HtmlInputElement(htmlElement).itype = value.toInputType()
+    else: discard
+  of "size":
+    case htmlElement.tagType
+    of TAG_INPUT: HtmlInputElement(htmlElement).size = value.toInputSize()
+    else: discard
+  else: return
 
 var s = ""
-proc parseHtml*(inputStream: Stream) =
+proc nparseHtml*(inputStream: Stream): Document =
   var x: XmlParser
   x.open(inputStream, "")
-  while true:
-    x.next()
-    case x.kind
-    of xmlElementStart: discard
-    of xmlEof: break
-    else: discard
+  var parents: seq[HtmlNode]
+  let document = newDocument()
+  parents.add(document)
+  var closed = true
+  while parents.len > 0 and x.kind != xmlEof:
+    var currParent = parents[^1]
+    while true:
+      var parsedNode: HtmlNode
+      x.next()
+      case x.kind
+      of xmlComment: discard #TODO
+      of xmlElementStart:
+        if not closed and currParent.isElemNode() and HtmlElement(currParent).tagType in SingleTagTypes:
+          parents.setLen(parents.len - 1)
+          currParent = parents[^1]
+          closed = true
+        eprint "<" & x.rawData & ">"
+        parsedNode = newHtmlElement(tagType(x.rawData))
+        currParent.childNodes.add(parsedNode)
+        if currParent.isElemNode():
+          parsedNode.parentElement = HtmlElement(currParent)
+        parsedNode.parentNode = currParent
+        parents.add(parsedNode)
+        closed = false
+        break
+      of xmlElementEnd:
+        eprint "</" & x.rawData & ">"
+        parents.setLen(parents.len - 1)
+        closed = true
+      of xmlElementOpen:
+        if not closed and currParent.isElemNode() and HtmlElement(currParent).tagType in SingleTagTypes:
+          parents.setLen(parents.len - 1)
+          currParent = parents[^1]
+          closed = true
+        parsedNode = newHtmlElement(tagType(x.rawData))
+        s = "<" & x.rawData
+        x.next()
+        while x.kind != xmlElementClose and x.kind != xmlEof:
+          if x.kind == xmlAttribute:
+            HtmlElement(parsedNode).applyAttribute(x.rawData.tolower(), x.rawData2)
+            s &= " "
+            s &= x.rawData
+            s &= "=\""
+            s &= x.rawData2
+            s &= "\""
+          x.next()
+        s &= ">"
+        eprint s
+
+        currParent.childNodes.add(parsedNode)
+        if currParent.isElemNode():
+          parsedNode.parentElement = HtmlElement(currParent)
+        parsedNode.parentNode = currParent
+        parents.add(parsedNode)
+        closed = false
+        break
+      of xmlCharData:
+        let textNode = new(HtmlNode)
+        textNode.nodeType = NODE_TEXT
+        textNode.rawtext = x.rawData
+        currParent.childNodes.add(textNode)
+        textNode.parentNode = currParent
+        if currParent.isElemNode():
+          textNode.parentElement = HtmlElement(currParent)
+        eprint x.rawData, currParent.nodeType
+      of xmlEntity:
+        eprint "entity", x.rawData
+      of xmlEof: break
+      else: discard
+  return document
diff --git a/termattrs.nim b/termattrs.nim
index 21fd3b04..d49800ae 100644
--- a/termattrs.nim
+++ b/termattrs.nim
@@ -6,7 +6,6 @@ type
     termHeight*: int
 
 proc getTermAttributes*(): TermAttributes =
-  var t = TermAttributes()
-  t.termWidth = terminalWidth()
-  t.termHeight = terminalHeight()
-  return t
+  let attrs = TermAttributes(termWidth: terminalWidth(),
+                             termHeight: terminalHeight())
+  return attrs
diff --git a/twtio.nim b/twtio.nim
index 16dfb254..0a19bfbf 100644
--- a/twtio.nim
+++ b/twtio.nim
@@ -102,7 +102,7 @@ proc readLine*(prompt: string, current: var string): bool =
         var rune: Rune
         new.fastRuneAt(cursor, rune, false)
         printesc($rune)
-        cursor += 1
+        inc cursor
     of ACTION_LINED_PREV_WORD:
       while cursor > 0:
         print('\b')
@@ -116,7 +116,7 @@ proc readLine*(prompt: string, current: var string): bool =
         var rune: Rune
         new.fastRuneAt(cursor, rune, false)
         printesc($rune)
-        cursor += 1
+        inc cursor
         if cursor < rl:
           new.fastRuneAt(cursor, rune, false)
           if rune == Rune(' '):
@@ -124,7 +124,7 @@ proc readLine*(prompt: string, current: var string): bool =
     of ACTION_LINED_KILL_WORD:
       var chars = 0
       while cursor > chars:
-        chars += 1
+        inc chars
         var rune: Rune
         new.fastRuneAt(cursor - chars, rune, false)
         if rune == Rune(' '):
@@ -157,6 +157,6 @@ proc readLine*(prompt: string, current: var string): bool =
       rl = new.runeLen()
       printesc(new.runeSubstr(cursor))
       print('\b'.repeat(rl - cursor - 1))
-      cursor += 1
+      inc cursor
     else:
       feedNext = true
diff --git a/twtstr.nim b/twtstr.nim
index 9c03436e..1f6b49d4 100644
--- a/twtstr.nim
+++ b/twtstr.nim
@@ -70,7 +70,7 @@ func findChar*(str: string, c: char, start: int = 0): int =
   while i < str.len:
     if str[i] == c:
       return i
-    i += 1
+    inc i
   return -1
 
 func findChar*(str: string, c: Rune, start: int = 0): int =
@@ -306,10 +306,10 @@ func mk_wcwidth_cjk*(ucs: Rune): int =
 
 
 func mk_wcswidth_cjk*(s: string): int =
-  result = 0
-  for r in s.runes:
-    result += mk_wcwidth_cjk(r)
-  return result
+  #result = 0
+  #for r in s.runes:
+  #  result += mk_wcwidth_cjk(r)
+  #return result
   result = 0
   var i = 0
   while i < len(s):