about summary refs log tree commit diff stats
path: root/src/io/buffer.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/io/buffer.nim')
-rw-r--r--src/io/buffer.nim229
1 files changed, 147 insertions, 82 deletions
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index d1a55222..cfe45b5e 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -1,4 +1,3 @@
-import options
 import terminal
 import uri
 import strutils
@@ -14,6 +13,7 @@ import config/config
 import io/term
 import io/lineedit
 import io/cell
+import layout/engine
 
 type
   Buffer* = ref BufferObj
@@ -35,10 +35,12 @@ type
     document*: Document
     displaycontrols*: bool
     redraw*: bool
+    reshape*: bool
     location*: Uri
     source*: string
     showsource*: bool
     rootbox*: CSSBox
+    prevnodes*: seq[Node]
 
 func newBuffer*(attrs: TermAttributes): Buffer =
   new(result)
@@ -150,17 +152,25 @@ func cellOrigin(buffer: Buffer, x: int, y: int): int =
 func currentCellOrigin(buffer: Buffer): int =
   return buffer.cellOrigin(buffer.acursorx, buffer.acursory)
 
-func currentRune(buffer: Buffer): Rune =
+#TODO counter-intuitive naming?
+func currentCell(buffer: Buffer): FixedCell =
   let row = (buffer.cursory - buffer.fromy) * buffer.width
-  return buffer.display[row + buffer.currentCellOrigin()].runes[0]
-
-func cellWidthOverlap*(buffer: Buffer, x: int, y: int): int =
-  let ox = buffer.cellOrigin(x, y)
-  let row = y * buffer.width
-  return buffer.display[row + ox].runes.width()
+  return buffer.display[row + buffer.currentCellOrigin()]
 
-func currentCellWidth*(buffer: Buffer): int =
-  return buffer.cellWidthOverlap(buffer.cursorx - buffer.fromx, buffer.cursory - buffer.fromy)
+func currentRune(buffer: Buffer): Rune =
+  let cell = buffer.currentCell()
+  if cell.runes.len == 0:
+    return Rune(' ')
+  return cell.runes[0]
+
+func getCursorLink(buffer: Buffer): Element =
+  let nodes = buffer.currentCell().nodes
+  for node in nodes:
+    if node.nodeType == ELEMENT_NODE:
+      let elem = Element(node)
+      if elem.tagType == TAG_A:
+        return elem
+  return nil
 
 func currentLineWidth*(buffer: Buffer): int =
   if buffer.cursory > buffer.lines.len:
@@ -175,17 +185,6 @@ func atPercentOf*(buffer: Buffer): int =
   if buffer.lines.len == 0: return 100
   return (100 * (buffer.cursory + 1)) div buffer.numLines
 
-func cursorOnNode*(buffer: Buffer, node: Node): bool =
-  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] =
-  discard #TODO
-
 func canScroll*(buffer: Buffer): bool =
   return buffer.numLines >= buffer.height
 
@@ -204,6 +203,41 @@ proc clearBuffer*(buffer: Buffer) =
   buffer.fromy = 0
   buffer.hovertext = ""
 
+proc clearDisplay*(buffer: Buffer) =
+  var i = 0
+  while i < buffer.display.len:
+    buffer.display[i].runes.setLen(0)
+    inc i
+
+proc refreshDisplay*(buffer: Buffer) =
+  var y = 0
+  buffer.prevdisplay = buffer.display
+  buffer.clearDisplay()
+
+  for line in buffer.lines[buffer.fromy..
+                           buffer.lastVisibleLine - 1]:
+    var w = 0
+    var i = 0
+    while w < buffer.fromx and i < line.len:
+      w += line[i].rune.width()
+      inc i
+
+    let dls = y * buffer.width
+    var j = 0
+    var n = 0
+    while w < buffer.fromx + buffer.width and i < line.len:
+      w += line[i].rune.width()
+      if line[i].rune.width() == 0 and j != 0:
+        inc n
+      buffer.display[dls + j - n].runes.add(line[i].rune)
+      buffer.display[dls + j - n].formatting = line[i].formatting
+      buffer.display[dls + j - n].nodes.add(line[i].nodes)
+      j += line[i].rune.width()
+      inc i
+
+    inc y
+
+
 proc restoreCursorX(buffer: Buffer) =
   buffer.cursorx = max(min(buffer.currentLineWidth() - 1, buffer.xend), 0)
 
@@ -250,7 +284,7 @@ proc cursorUp*(buffer: Buffer) =
       buffer.redraw = true
 
 proc cursorRight*(buffer: Buffer) =
-  let cellwidth = buffer.currentCellWidth()
+  let cellwidth = max(buffer.currentCell().width(), 1)
   let cellorigin = buffer.fromx + buffer.currentCellOrigin()
   let lw = buffer.currentLineWidth()
   if buffer.cursorx < lw - 1:
@@ -291,10 +325,24 @@ proc cursorLineEnd*(buffer: Buffer) =
   buffer.fromx = max(buffer.cursorx - buffer.width + 1, 0)
   buffer.redraw = buffer.fromx > 0
 
+#TODO this is sloooooow
+proc cursorRightOverflow(buffer: Buffer) =
+  buffer.cursorRight()
+  if buffer.cursorx >= buffer.currentLineWidth() - 1 and buffer.cursory < buffer.numLines - 1:
+    buffer.cursorDown()
+    buffer.cursorLineBegin()
+  buffer.refreshDisplay()
+
+proc cursorLeftOverflow(buffer: Buffer) =
+  buffer.cursorLeft()
+  if buffer.cursorx <= 0 and buffer.cursory > 0:
+    buffer.cursorUp()
+    buffer.cursorLineEnd()
+  buffer.refreshDisplay()
+
 proc cursorNextWord*(buffer: Buffer) =
   let llen = buffer.currentLineWidth() - 1
   if llen >= 0:
-
     while not buffer.currentRune().breaksWord():
       if buffer.cursorx >= llen:
         break
@@ -329,7 +377,32 @@ proc cursorPrevWord*(buffer: Buffer) =
 
 proc cursorNextLink*(buffer: Buffer) =
   #TODO
-  return
+  #let ocx = buffer.cursorx
+  #let ocy = buffer.cursory
+  #let ofx = buffer.fromx
+  #let ofy = buffer.fromy
+  #let elem = buffer.getCursorLink()
+  #if elem != nil:
+  #  while buffer.getCursorLink() == elem:
+  #    buffer.cursorRightOverflow()
+  #    if buffer.cursorx >= buffer.currentLineWidth() - 1 and
+  #        buffer.cursory >= buffer.numLines - 1:
+  #      buffer.cursorx = ocx
+  #      buffer.cursory = ocy
+  #      buffer.fromx = ofx
+  #      buffer.fromy = ofy
+  #      break
+
+  #while buffer.getCursorLink() == nil:
+  #  buffer.cursorRightOverflow()
+  #  if buffer.cursorx >= buffer.currentLineWidth() - 1 and
+  #      buffer.cursory >= buffer.numLines - 1:
+  #    buffer.cursorx = ocx
+  #    buffer.cursory = ocy
+  #    buffer.fromx = ofx
+  #    buffer.fromy = ofy
+  #    break
+  discard
 
 proc cursorPrevLink*(buffer: Buffer) =
   #TODO
@@ -494,6 +567,7 @@ func cellFromLine(line: CSSRowBox, i: int): FlexibleCell =
     result.formatting.overline = true
   if line.textdecoration == TEXT_DECORATION_LINE_THROUGH:
     result.formatting.strike = true
+  result.nodes = line.nodes
 
 proc setRowBox(buffer: Buffer, line: CSSRowBox) =
   let x = line.x
@@ -530,10 +604,6 @@ proc setRowBox(buffer: Buffer, line: CSSRowBox) =
   if i < oline.len:
     buffer.lines[y].add(oline[i..high(oline)])
 
-proc reshape*(buffer: Buffer) =
-  buffer.display = newFixedGrid(buffer.width, buffer.height)
-  buffer.statusmsg = newFixedGrid(buffer.width)
-
 proc updateCursor(buffer: Buffer) =
   if buffer.fromy > buffer.lastVisibleLine - 1:
     buffer.fromy = 0
@@ -545,38 +615,29 @@ proc updateCursor(buffer: Buffer) =
   if buffer.lines.len == 0:
     buffer.cursory = 0
 
-proc clearDisplay*(buffer: Buffer) =
-  var i = 0
-  while i < buffer.display.len:
-    buffer.display[i].runes.setLen(0)
-    inc i
-
-proc refreshDisplay*(buffer: Buffer) =
-  var y = 0
-  buffer.prevdisplay = buffer.display
-  buffer.clearDisplay()
-
-  for line in buffer.lines[buffer.fromy..
-                           buffer.lastVisibleLine - 1]:
-    var w = 0
-    var i = 0
-    while w < buffer.fromx and i < line.len:
-      w += line[i].rune.width()
-      inc i
-
-    let dls = y * buffer.width
-    var j = 0
-    var n = 0
-    while w < buffer.fromx + buffer.width and i < line.len:
-      w += line[i].rune.width()
-      if line[i].rune.width() == 0 and j != 0:
-        inc n
-      buffer.display[dls + j - n].runes.add(line[i].rune)
-      buffer.display[dls + j - n].formatting = line[i].formatting
-      j += line[i].rune.width()
-      inc i
-
-    inc y
+#TODO this works, but needs rethinking:
+#* reshape is called every time the cursor moves onto or off a line box, which
+#  practically means we're re-interpreting all style-sheets AND re-applying all
+#  rules way too often
+#* reshape also calls redraw so the entire window gets re-painted too which
+#  looks pretty bad
+#* and finally it re-arranges all CSS boxes too, which is a rather
+#  resource-intensive operation
+#overall the second point is the easiest to solve, then the first and finally
+#the last (there's only so much you can do in a flow layout, especially with
+#the current layout engine)
+proc updateHover(buffer: Buffer) =
+  let nodes = buffer.currentCell().nodes
+  if nodes != buffer.prevnodes:
+    for node in nodes:
+      if not node.hover:
+        node.hover = true
+        buffer.reshape = true
+    for node in buffer.prevnodes:
+      if node.hover and not (node in nodes):
+        node.hover = false
+        buffer.reshape = true
+  buffer.prevnodes = nodes
 
 proc renderPlainText*(buffer: Buffer, text: string) =
   buffer.clearText()
@@ -610,7 +671,8 @@ proc renderPlainText*(buffer: Buffer, text: string) =
 
 proc renderDocument*(buffer: Buffer) =
   buffer.clearText()
-  #TODO
+  buffer.document.applyStylesheets()
+  buffer.rootbox = buffer.document.alignBoxes(buffer.width, buffer.height)
   if buffer.rootbox == nil:
     return
   var stack: seq[CSSBox]
@@ -633,6 +695,15 @@ proc renderDocument*(buffer: Buffer) =
       dec i
   buffer.updateCursor()
 
+proc reshapeBuffer*(buffer: Buffer) =
+  buffer.display = newFixedGrid(buffer.width, buffer.height)
+  #TODO
+  #buffer.statusmsg = newFixedGrid(buffer.width)
+  if buffer.showsource:
+    buffer.renderPlainText(buffer.source)
+  else:
+    buffer.renderDocument()
+
 proc cursorBufferPos(buffer: Buffer) =
   let x = max(buffer.cursorx - buffer.fromx, 0)
   let y = buffer.cursory - buffer.fromy
@@ -677,8 +748,8 @@ proc displayBuffer(buffer: Buffer) =
 
 proc displayStatusMessage(buffer: Buffer) =
   termGoto(0, buffer.height)
-  eraseLine()
   print(buffer.generateStatusMessage())
+  print(EL())
 
 proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
   var s = ""
@@ -697,8 +768,6 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
     let c = getch()
     s &= c
     let action = getNormalAction(s)
-    var redraw = false
-    var reshape = false
     var nostatus = false
     case action
     of ACTION_QUIT:
@@ -743,38 +812,35 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
         buffer.setLocation(parseUri(url))
         return true
     of ACTION_LINE_INFO:
-      buffer.setStatusMessage("line " & $(buffer.cursory + 1) & "/" & $buffer.numLines & " col " & $(buffer.cursorx + 1) & "/" & $buffer.currentLineWidth() & " cell width: " & $buffer.currentCellWidth())
+      buffer.setStatusMessage("line " & $(buffer.cursory + 1) & "/" & $buffer.numLines & " col " & $(buffer.cursorx + 1) & "/" & $buffer.currentLineWidth() & " cell width: " & $buffer.currentCell().width())
       nostatus = true
     of ACTION_FEED_NEXT:
       feedNext = true
     of ACTION_RELOAD: return true
     of ACTION_RESHAPE:
-      reshape = true
-      redraw = true
-    of ACTION_REDRAW: redraw = true
+      buffer.reshape = true
+      buffer.redraw = true
+    of ACTION_REDRAW: buffer.redraw = true
     of ACTION_TOGGLE_SOURCE:
       buffer.showsource = not buffer.showsource
-      if buffer.showsource:
-        buffer.renderPlainText(buffer.source)
-      else:
-        buffer.renderDocument()
-      redraw = true
+      buffer.reshape = true
+      buffer.redraw = true
     else: discard
     stdout.hideCursor()
+    buffer.updateHover()
 
     if buffer.refreshTermAttrs():
-      redraw = true
-      reshape = true
+      buffer.redraw = true
+      buffer.reshape = true
 
+    if buffer.reshape:
+      buffer.reshapeBuffer()
+      buffer.reshape = false
+      buffer.redraw = true #?
     if buffer.redraw:
-      redraw = true
-
-    if reshape:
-      buffer.reshape()
-      buffer.displayBuffer()
-    if redraw:
       buffer.refreshDisplay()
       buffer.displayBuffer()
+      buffer.redraw = false
 
     if not nostatus:
       buffer.statusMsgForBuffer()
@@ -787,4 +853,3 @@ proc displayPage*(attrs: TermAttributes, buffer: Buffer): bool =
   buffer.displayBuffer()
   buffer.statusMsgForBuffer()
   return inputLoop(attrs, buffer)
-