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

                


                            
                
                   

    
                      
                        
                  



                           
                      

                

                 
              

               
                          
                       
                       
                 
                  
                   
                  


                       
                   
                    
                   
                       

                            
                             
                       
 
                           
             


                                         
 


                                                                
 
                                                 
           
           
                          
                     
 
                             
                         
                    
                    
           
           
 
                                               
                         

                     
         
 
                
                

                                                                            

                                                                             
                                                 
                          

                               
           

           


                     
                         




                               
           
           

                                   
                                                
                          
         
         




                           























                                                                                    
                                                   













                                         
 
                                                     
                          
           
                               
                                               



                         
 
                                                     
 
                                                                                              
 
                                            





                                                                                     





                                              
                                                 



                                                           


                                             

                                                            
                                                    
                                                          
                                                         
 





                                                                      
 
                                             
                                                   
 













                                              














                                        
                            


                                        
              

                        



                                

                   
                                          
                                                                     
                                      
 
                                       
                                      
                                                         
 


                                                       
                                       







                                                            


                             
                                   

                                                            
 
                                     
             
           





                                                       
                                                

                                

                              
             

                                   


                                                    


                           
               

                                
                                         
             


                                      
                                          
                      
                                                  
                                              
                            
                                            
             


         
                                             






                                        
 








                                                               
                        
                              
 

                                                                              

                      



                                                          
                                                 
                        
                            

                        
                        




                                                                                          
 
                                         



                                                                
       




                                                  
                        

                         











                                                              








                                          

                                                            


                                                 
                                                                
 
                                       
                      

                                     
                                                          
 
                                      

                                     
                        






                                        
 






                                        
 


                                


                                            

                            
 
                                      
                                     
                        
                                
                                          


                                                  
             

                    
 


                                                  
             



                    
 


                        


                            

                              
 
                                      



                                                           
                                         



                                
                                  









                                                       
                                    
                                  
                                          

              
 
                                      



                                                           
                                         



                                
                                  









                                            
                                    








                                        
                                          



                             
                                   

              
 
                                       
                      
 
                                      
                                        
 
                                 
                                 
 
                                    
                                                                                       
 
                                    
                                                                               
 
                                      
                                 

                                        
                                                                                          

                                       
                                                                                  
 
                                  
                                                                                             

                        
                        
 

                                                                   


                                                          

                         
 
                                    

                                                                                                

                         

                         
 
                              




                                                         
                         
 
                                




                                                                                      



                                                        



                                               


                                                               



                                                                                            
 
                                  
                                                    
                    




                                     
 
                                

                      
                                                      







                                                           




                                  

                                                              
                        
 

                                                                     





                                    
                               




                                      
 

                                                                  




                                              
                                 
                                       

               
 






                                               
 
                                  












                                                  
                   
                               

                                                                                  

                           









                                              
 
                                                        




                          




                                                             




                                                              



                                              
 









                                                                                 
                                
                                                               

                                              
       
                               
                                              

                                
                                                 



                              


                                                                                                   
               
                       
 
                                      

                         
                          
 
                                         
                                               
 
                                                                                 
                             
           






                                               
                                       

                                         

                                                                    

                                 


                                        



                                                     
 
                                
                                                                                                                                                                                                  
 
                                              
                                    
 
                                    
                                    
 
                                            

                                  
                                       
              







                                         
                                  
                          




                             



                                             
                                                    
 
                                     
                                  






                               

                           
                         
 

                      
                   


                                    







                               
import options
import streams
import terminal
import unicode

import css/cascade
import css/sheet
import html/dom
import html/tags
import html/parser
import io/cell
import io/loader
import io/term
import layout/box
import render/renderdocument
import render/rendertext
import types/url
import utils/twtstr

type
  Buffer* = ref object
    contenttype*: string
    title*: string
    lines*: FlexibleGrid
    display*: FixedGrid
    prevdisplay*: FixedGrid
    statusmsg*: FixedGrid
    hovertext*: string
    width*: int
    height*: int
    cursorx*: int
    cursory*: int
    xend*: int
    fromx*: int
    fromy*: int
    attrs*: TermAttributes
    document*: Document
    viewport*: Viewport
    redraw*: bool
    reshape*: bool
    nostatus*: bool
    location*: Url
    ispipe*: bool
    istream*: Stream
    streamclosed*: bool
    source*: string
    rootbox*: CSSBox
    prevnode*: Node
    sourcepair*: Buffer
    prev*: Buffer
    next* {.cursor.}: Buffer
    userstyle*: CSSStylesheet
    loader*: FileLoader

proc newBuffer*(): Buffer =
  new(result)
  result.attrs = getTermAttributes()
  result.width = result.attrs.width
  result.height = result.attrs.height - 1

  result.display = newFixedGrid(result.width, result.height)
  result.prevdisplay = newFixedGrid(result.width, result.height)
  result.statusmsg = newFixedGrid(result.width)

func generateFullOutput(buffer: Buffer): string =
  var x = 0
  var w = 0
  var format = newFormat()
  result &= HVP(1, 1)

  for cell in buffer.display:
    if x >= buffer.width:
      result &= EL()
      result &= '\n'
      x = 0
      w = 0

    result &= format.processFormat(cell.format)
    result &= $cell.runes

    w += cell.width()
    inc x

  result &= EL()
  result &= '\n'

# generate a sequence of instructions to replace the previous frame with the
# current one. ideally should be used when small changes are made (e.g. hover
# changes underlining)
func generateSwapOutput(buffer: Buffer): string =
  var format = newFormat()
  let curr = buffer.display
  let prev = buffer.prevdisplay
  var i = 0
  var x = 0
  var y = 0
  var line = ""
  var lr = false
  while i < curr.len:
    if x >= buffer.width:
      if lr:
        result &= HVP(y + 1, 1)
        result &= EL()
        result &= line
        lr = false
      x = 0
      inc y
      line = ""
    lr = lr or (curr[i] != prev[i])
    line &= format.processFormat(curr[i].format)
    line &= $curr[i].runes
    inc i
    inc x
  if lr:
    result &= HVP(y + 1, 1)
    result &= EL()
    result &= line
    lr = false
  
  #TODO maybe fix this
  #var x = 0
  #var y = 0
  #var cx = -1
  #var cy = -1
  #var i = 0
  #var text = ""
  #while i < max:
  #  if x >= buffer.width:
  #    x = 0
  #    inc y

  #  if curr[i] != prev[i]:
  #    let currwidth = curr[i].runes.width()
  #    let prevwidth = prev[i].runes.width()
  #    if (curr[i].runes.len > 0 or currwidth < prevwidth) and (x != cx or y != cy):
  #      if text.len > 0:
  #        result &= text
  #        text = ""
  #      result &= HVP(y + 1, x + 1)
  #      cx = x
  #      cy = y

  #    text &= format.processFormat(curr[i].format)

  #    text &= $curr[i].runes
  #    if currwidth < prevwidth:
  #      var j = 0
  #      while j < prevwidth - currwidth:
  #        text &= ' '
  #        inc j
  #    if text.len > 0:
  #      inc cx

  #  inc x
  #  inc i
  #if text.len > 0:
  #  result &= $text

func generateStatusMessage*(buffer: Buffer): string =
  var format = newFormat()
  var w = 0
  for cell in buffer.statusmsg:
    result &= format.processFormat(cell.format)
    result &= $cell.runes
    w += cell.width()
  if w < buffer.width:
    result &= EL()

func numLines(buffer: Buffer): int = buffer.lines.len

func lastVisibleLine(buffer: Buffer): int = min(buffer.fromy + buffer.height, buffer.numLines)

func currentLineWidth(buffer: Buffer): int =
  return buffer.lines[buffer.cursory].width()

func maxfromy(buffer: Buffer): int = max(buffer.numLines - buffer.height, 0)

func maxfromx(buffer: Buffer): int = max(buffer.currentLineWidth() - buffer.width, 0)

func acursorx(buffer: Buffer): int =
  return max(0, buffer.cursorx - buffer.fromx)

func acursory(buffer: Buffer): int =
  return buffer.cursory - buffer.fromy

func cellOrigin(buffer: Buffer, x, y: int): int =
  let row = y * buffer.width
  var ox = x
  while buffer.display[row + ox].runes.len == 0 and ox > 0:
    dec ox
  return ox

func currentCellOrigin(buffer: Buffer): int =
  return buffer.cellOrigin(buffer.acursorx, buffer.acursory)

func currentDisplayCell(buffer: Buffer): FixedCell =
  let row = (buffer.cursory - buffer.fromy) * buffer.width
  return buffer.display[row + buffer.currentCellOrigin()]

func getLink(node: Node): Element =
  if node == nil:
    return nil
  if node.nodeType == ELEMENT_NODE and Element(node).tagType == TAG_A:
    return Element(node)
  return node.findAncestor({TAG_A})

func getCursorLink(buffer: Buffer): Element =
  return buffer.currentDisplayCell().node.getLink()

func currentLine(buffer: Buffer): string =
  return buffer.lines[buffer.cursory].str

func currentCursorBytes(buffer: Buffer): int =
  let line = buffer.currentLine
  var w = 0
  var i = 0
  let cc = buffer.fromx + buffer.cursorx
  while i < line.len and w < cc:
    var r: Rune
    fastRuneAt(line, i, r)
    w += r.width()
  return i

func currentWidth(buffer: Buffer): int =
  let line = buffer.currentLine
  if line.len == 0: return 0
  var w = 0
  var i = 0
  let cc = buffer.fromx + buffer.cursorx
  var r: Rune
  fastRuneAt(line, i, r)
  while i < line.len and w < cc:
    fastRuneAt(line, i, r)
    w += r.width()
  return r.width()

func prevWidth(buffer: Buffer): int =
  let line = buffer.currentLine
  if line.len == 0: return 0
  var w = 0
  var i = 0
  let cc = buffer.fromx + buffer.cursorx
  var pr: Rune
  var r: Rune
  fastRuneAt(line, i, r)
  while i < line.len and w < cc:
    pr = r
    fastRuneAt(line, i, r)
    w += r.width()
  return pr.width()

func maxScreenWidth(buffer: Buffer): int =
  for line in buffer.lines[buffer.fromy..buffer.lastVisibleLine - 1]:
    result = max(line.width(), result)

func atPercentOf(buffer: Buffer): int =
  if buffer.lines.len == 0: return 100
  return (100 * (buffer.cursory + 1)) div buffer.numLines

func hasAnchor*(buffer: Buffer, anchor: string): bool =
  return buffer.document.getElementById(anchor) != nil

func getTitle(buffer: Buffer): string =
  if buffer.document != nil:
    let titles = buffer.document.getElementsByTag(TAG_TITLE)
    if titles.len > 0:
      for text in titles[0].textNodes:
        result &= text.data.strip().clearControls()
    return
  if buffer.ispipe:
    result = "*pipe*"
  else:
    result = $buffer.location

proc clearDisplay(buffer: Buffer) =
  buffer.prevdisplay = buffer.display
  buffer.display = newFixedGrid(buffer.width, buffer.height)

proc refreshDisplay(buffer: Buffer) =
  var r: Rune
  var y = 0
  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.str.len:
      fastRuneAt(line.str, i, r)
      w += r.width()

    let dls = y * buffer.width
    var k = 0
    var cf = line.findFormat(i)
    var nf = line.findNextFormat(i)
    if w > buffer.fromx:
      while k < w - buffer.fromx:
        buffer.display[dls + k].runes.add(Rune(' '))
        inc k

    while i < line.str.len:
      let j = i
      fastRuneAt(line.str, i, r)
      w += r.width()
      if w > buffer.fromx + buffer.width:
        break
      if nf.pos != -1 and nf.pos <= j:
        cf = nf
        nf = line.findNextFormat(j)
      buffer.display[dls + k].runes.add(r)
      if cf.pos != -1:
        buffer.display[dls + k].format = cf.format
        buffer.display[dls + k].node = cf.node
      let tk = k + r.width()
      while k < tk and k < buffer.width - 1:
        inc k

    inc y

proc setCursorXB(buffer: Buffer, byte: int) =
  var r: Rune
  var w = 0
  var b = 0
  while b < byte:
    var r: Rune
    fastRuneAt(buffer.currentLine, b, r)
    w += r.width()

  let x = w
  if x - buffer.fromx >= 0 and x - buffer.width < buffer.fromx:
    buffer.cursorx = x
  else:
    if x > buffer.cursorx:
      buffer.fromx = max(x - buffer.width + 1, 0)
    elif x < buffer.cursorx:
      buffer.fromx = min(x, buffer.maxfromx)
    buffer.cursorx = x
    buffer.redraw = true
  buffer.xend = buffer.cursorx

proc setCursorX(buffer: Buffer, x: int, refresh = true, save = true) =
  if (not refresh) or (buffer.fromx <= x and x < buffer.fromx + buffer.width):
    buffer.cursorx = x
  else:
    if refresh and buffer.fromx > buffer.cursorx:
      buffer.fromx = max(buffer.currentLineWidth() - 1, 0)
      buffer.cursorx = buffer.fromx
    elif x > buffer.cursorx:
      buffer.fromx = max(x - buffer.width + 1, 0)
      buffer.cursorx = x
    elif x < buffer.cursorx:
      buffer.fromx = x
      buffer.cursorx = x
    buffer.redraw = true
  if save:
    buffer.xend = buffer.cursorx

proc restoreCursorX(buffer: Buffer) =
  buffer.setCursorX(max(min(buffer.currentLineWidth() - 1, buffer.xend), 0), false, false)

proc setCursorY(buffer: Buffer, y: int) =
  if buffer.cursory == y:
    return
  if y - buffer.fromy >= 0 and y - buffer.height < buffer.fromy:
    buffer.cursory = y
  else:
    if y > buffer.cursory:
      buffer.fromy = max(y - buffer.height + 1, 0)
    else:
      buffer.fromy = min(y, buffer.maxfromy)
    buffer.cursory = y
    buffer.redraw = true
  buffer.restoreCursorX()

proc setCursorXY*(buffer: Buffer, x, y: int) =
  buffer.setCursorY(max(min(y, buffer.numLines - 1), 0))
  buffer.setCursorX(max(min(buffer.currentLineWidth(), x), 0))

proc setFromXY*(buffer: Buffer, x, y: int) =
  buffer.fromy = max(min(y, buffer.maxfromy), 0)
  buffer.fromx = max(min(x, buffer.maxfromx), 0)

proc setCursorXBY(buffer: Buffer, x, y: int) =
  buffer.setCursorY(y)
  buffer.setCursorXB(x)

proc cursorDown*(buffer: Buffer) =
  if buffer.cursory < buffer.numLines - 1:
    buffer.setCursorY(buffer.cursory + 1)

proc cursorUp*(buffer: Buffer) =
  if buffer.cursory > 0:
    buffer.setCursorY(buffer.cursory - 1)

proc cursorRight*(buffer: Buffer) =
  let cellwidth = buffer.currentWidth()
  if buffer.cursorx + cellwidth < buffer.currentLineWidth():
    buffer.setCursorX(buffer.cursorx + cellwidth)

proc cursorLeft*(buffer: Buffer) =
  buffer.setCursorX(max(buffer.cursorx - buffer.prevWidth(), 0))

proc cursorLineBegin*(buffer: Buffer) =
  buffer.setCursorX(0)

proc cursorLineEnd*(buffer: Buffer) =
  buffer.setCursorX(max(buffer.currentLineWidth() - 1, 0))

proc cursorNextWord*(buffer: Buffer) =
  var r: Rune
  var b = buffer.currentCursorBytes()
  var x = buffer.cursorx
  while b < buffer.currentLine.len:
    let pb = b
    fastRuneAt(buffer.currentLine, b, r)
    if r.breaksWord():
      b = pb
      break
    x += r.width()

  while b < buffer.currentLine.len:
    let pb = b
    fastRuneAt(buffer.currentLine, b, r)
    if not r.breaksWord():
      b = pb
      break
    x += r.width()

  if b < buffer.currentLine.len:
    buffer.setCursorX(x)
  else:
    if buffer.cursory < buffer.numLines - 1:
      buffer.cursorDown()
      buffer.cursorLineBegin()
    else:
      buffer.cursorLineEnd()

proc cursorPrevWord*(buffer: Buffer) =
  var b = buffer.currentCursorBytes()
  var x = buffer.cursorx
  if buffer.currentLine.len > 0:
    b = min(b, buffer.currentLine.len - 1)
    while b >= 0:
      let (r, o) = lastRune(buffer.currentLine, b)
      if r.breaksWord():
        break
      b -= o
      x -= r.width()

    while b >= 0:
      let (r, o) = lastRune(buffer.currentLine, b)
      if not r.breaksWord():
        break
      b -= o
      x -= r.width()
  else:
    b = -1

  if b >= 0:
    buffer.setCursorX(x)
  else:
    if buffer.cursory > 0:
      buffer.cursorUp()
      buffer.cursorLineEnd()
    else:
      buffer.cursorLineBegin()

proc cursorNextLink*(buffer: Buffer) =
  let line = buffer.lines[buffer.cursory]
  var i = line.findFormatN(buffer.currentCursorBytes()) - 1
  var link: Element = nil
  if i >= 0:
    link = line.formats[i].node.getLink()
  inc i

  while i < line.formats.len:
    let format = line.formats[i]
    let fl = format.node.getLink()
    if fl != nil and fl != link:
      buffer.setCursorXB(format.pos)
      return
    inc i

  for y in (buffer.cursory + 1)..(buffer.numLines - 1):
    let line = buffer.lines[y]
    i = 0
    while i < line.formats.len:
      let format = line.formats[i]
      let fl = format.node.getLink()
      if fl != nil and fl != link:
        buffer.setCursorXBY(format.pos, y)
        return
      inc i

proc cursorPrevLink*(buffer: Buffer) =
  let line = buffer.lines[buffer.cursory]
  var i = line.findFormatN(buffer.currentCursorBytes()) - 1
  var link: Element = nil
  if i >= 0:
    link = line.formats[i].node.getLink()
  dec i

  while i >= 0:
    let format = line.formats[i]
    let fl = format.node.getLink()
    if fl != nil and fl != link:
      buffer.setCursorXB(format.pos)
      return
    dec i

  for y in countdown(buffer.cursory - 1, 0):
    let line = buffer.lines[y]
    i = line.formats.len - 1
    while i >= 0:
      let format = line.formats[i]
      let fl = format.node.getLink()
      if fl != nil and fl != link:
        #go to beginning of link
        var ly = y #last y
        var lx = format.pos #last x
        for iy in countdown(ly - 1, 0):
          let line = buffer.lines[iy]
          i = line.formats.len - 1
          while i >= 0:
            let format = line.formats[i]
            let nl = format.node.getLink()
            if nl == fl:
              ly = iy
              lx = format.pos
            dec i
        buffer.setCursorXBY(lx, ly)
        return
      dec i

proc cursorFirstLine*(buffer: Buffer) =
  buffer.setCursorY(0)

proc cursorLastLine*(buffer: Buffer) =
  buffer.setCursorY(buffer.numLines - 1)

proc cursorTop*(buffer: Buffer) =
  buffer.setCursorY(buffer.fromy)

proc cursorMiddle*(buffer: Buffer) =
  buffer.setCursorY(min(buffer.fromy + (buffer.height - 2) div 2, buffer.numLines - 1))

proc cursorBottom*(buffer: Buffer) =
  buffer.setCursorY(min(buffer.fromy + buffer.height - 1, buffer.numLines - 1))

proc cursorLeftEdge*(buffer: Buffer) =
  buffer.setCursorX(buffer.fromx)

proc cursorVertMiddle*(buffer: Buffer) =
  buffer.setCursorX(min(buffer.fromx + (buffer.width - 2) div 2, buffer.currentLineWidth))

proc cursorRightEdge*(buffer: Buffer) =
  buffer.setCursorX(min(buffer.fromx + buffer.width - 1, buffer.currentLineWidth))

proc centerLine*(buffer: Buffer) =
  let ny = max(min(buffer.cursory - buffer.height div 2, buffer.numLines - buffer.height), 0)
  if ny != buffer.fromy:
    buffer.fromy = ny
    buffer.redraw = true

proc halfPageUp*(buffer: Buffer) =
  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
    buffer.redraw = true
  buffer.restoreCursorX()

proc halfPageDown*(buffer: Buffer) =
  buffer.cursory = min(buffer.cursory + buffer.height div 2 - 1, buffer.numLines - 1)
  let nfy = min(max(buffer.numLines - buffer.height, 0), buffer.fromy + buffer.height div 2 - 1)
  if nfy != buffer.fromy:
    buffer.fromy = nfy
    buffer.redraw = true
  buffer.restoreCursorX()

proc pageUp*(buffer: Buffer) =
  buffer.cursory = max(buffer.cursory - buffer.height, 0)
  let nfy = max(0, buffer.fromy - buffer.height)
  if nfy != buffer.fromy:
    buffer.fromy = nfy
    buffer.redraw = true
  buffer.restoreCursorX()

proc pageDown*(buffer: Buffer) =
  buffer.cursory = min(buffer.cursory + buffer.height, buffer.numLines - 1)
  let nfy = min(buffer.fromy + buffer.height, max(buffer.numLines - buffer.height, 0))
  if nfy != buffer.fromy:
    buffer.fromy = nfy
    buffer.redraw = true
  buffer.restoreCursorX()

proc pageLeft*(buffer: Buffer) =
  buffer.cursorx = max(buffer.cursorx - buffer.width, 0)
  let nfx = max(0, buffer.fromx - buffer.width)
  if nfx != buffer.fromx:
    buffer.fromx = nfx
    buffer.redraw = true

proc pageRight*(buffer: Buffer) =
  buffer.cursorx = min(buffer.fromx, buffer.currentLineWidth())
  let nfx = min(max(buffer.maxScreenWidth() - buffer.width, 0), buffer.fromx + buffer.width)
  if nfx != buffer.fromx:
    buffer.fromx = nfx
    buffer.redraw = true

proc scrollDown*(buffer: Buffer) =
  if buffer.fromy + buffer.height < buffer.numLines:
    inc buffer.fromy
    if buffer.fromy > buffer.cursory:
      buffer.cursorDown()
    buffer.redraw = true
  else:
    buffer.cursorDown()

proc scrollUp*(buffer: Buffer) =
  if buffer.fromy > 0:
    dec buffer.fromy
    if buffer.fromy + buffer.height <= buffer.cursory:
      buffer.cursorUp()
    buffer.redraw = true
  else:
    buffer.cursorUp()

proc scrollRight*(buffer: Buffer) =
  if buffer.fromx + buffer.width < buffer.maxScreenWidth():
    inc buffer.fromx
    buffer.redraw = true

proc scrollLeft*(buffer: Buffer) =
  if buffer.fromx > 0:
    dec buffer.fromx
    if buffer.cursorx < buffer.fromx:
      buffer.setCursorX(max(buffer.currentLineWidth() - 1, 0))
    buffer.redraw = true

proc gotoAnchor*(buffer: Buffer) =
  let anchor = buffer.document.getElementById(buffer.location.anchor)
  if anchor == nil: return
  for y in 0..(buffer.numLines - 1):
    let line = buffer.lines[y]
    var i = 0
    while i < line.formats.len:
      let format = line.formats[i]
      if anchor in format.node:
        buffer.setCursorY(y)
        buffer.centerLine()
        buffer.setCursorXB(format.pos)
        return
      inc i

proc gotoLocation*(buffer: Buffer, s: string) =
  discard parseUrl(s, buffer.location.some, buffer.location, true)

proc refreshTermAttrs*(buffer: Buffer): bool =
  let newAttrs = getTermAttributes()
  if newAttrs != buffer.attrs:
    buffer.attrs = newAttrs
    buffer.width = newAttrs.width
    buffer.height = newAttrs.height - 1
    return true
  return false

proc updateCursor(buffer: Buffer) =
  if buffer.fromy > buffer.lastVisibleLine - 1:
    buffer.fromy = 0
    buffer.cursory = buffer.lastVisibleLine - 1

  if buffer.lines.len == 0:
    buffer.cursory = 0

proc updateHover(buffer: Buffer) =
  let thisnode = buffer.currentDisplayCell().node
  let prevnode = buffer.prevnode

  if thisnode != prevnode:
    for node in thisnode.branch:
      if node.nodeType == ELEMENT_NODE:
        let elem = Element(node)
        if not elem.hover and node notin prevnode:
          elem.hover = true
          buffer.reshape = true
          elem.refreshStyle()

    let link = thisnode.getLink()
    if link != nil:
      if link.tagType == TAG_A:
        let anchor = HTMLAnchorElement(link)
        buffer.hovertext = parseUrl(anchor.href, buffer.location.some).serialize()
    else:
      buffer.hovertext = ""

    for node in prevnode.branch:
      if node.nodeType == ELEMENT_NODE:
        let elem = Element(node)
        if elem.hover and node notin thisnode:
          elem.hover = false
          buffer.reshape = true
          elem.refreshStyle()

  buffer.prevnode = thisnode

proc loadResources(buffer: Buffer, document: Document) =
  var stack: seq[Element]
  stack.add(document.root)
  while stack.len > 0:
    let elem = stack.pop()

    if elem.tagType == TAG_LINK:
      let elem = HTMLLinkElement(elem)
      if elem.rel == "stylesheet":
        let url = parseUrl(elem.href, document.location.some)
        if url.issome:
          if url.get.scheme == buffer.location.scheme:
            let res = buffer.loader.getPage(url.get)
            if res.s != nil and res.contenttype == "text/css":
              let sheet = parseStylesheet(res.s.readAll())
              elem.parentElement.sheets.add(sheet)

    for i in countdown(elem.children.high, 0):
      let child = elem.children[i]
      stack.add(child)

proc load*(buffer: Buffer) =
  case buffer.contenttype
  of "text/html":
    if not buffer.streamclosed:
      #TODO not sure what to do with this.
      #Ideally we could just throw away the source data after parsing but then
      #source view won't work. Well we could still generate it... best would be a
      #config option like a) store source b) generate source
      buffer.source = buffer.istream.readAll()
      buffer.istream.close()
      buffer.streamclosed = true
    buffer.document = parseHtml(newStringStream(buffer.source))
    buffer.document.location = buffer.location
    buffer.loadResources(buffer.document)
  else:
    if not buffer.streamclosed:
      buffer.source = buffer.istream.readAll()
      buffer.istream.close()
      buffer.streamclosed = true
    buffer.lines = renderPlainText(buffer.source)

proc render*(buffer: Buffer) =
  case buffer.contenttype
  of "text/html":
    if buffer.viewport == nil:
      buffer.viewport = Viewport(term: buffer.attrs)
    buffer.lines = renderDocument(buffer.document, buffer.attrs, buffer.userstyle, buffer.viewport)
  else: discard
  buffer.updateCursor()

proc cursorBufferPos(buffer: Buffer) =
  let x = buffer.acursorx
  let y = buffer.acursory
  print(HVP(y + 1, x + 1))

proc clearStatusMessage(buffer: Buffer) =
  buffer.statusmsg = newFixedGrid(buffer.width)

proc writeStatusMessage(buffer: Buffer, str: string, format: Format = Format()) =
  buffer.clearStatusMessage()
  var i = 0
  for r in str.runes:
    i += r.width()
    if i >= buffer.statusmsg.len:
      buffer.statusmsg[^1].runes.setLen(0)
      buffer.statusmsg[^1].runes.add(Rune('$'))
      break
    buffer.statusmsg[i].runes.add(r)
    buffer.statusmsg[i].format = format

proc statusMsgForBuffer(buffer: Buffer) =
  var msg = $(buffer.cursory + 1) & "/" & $buffer.numLines & " (" &
            $buffer.atPercentOf() & "%) " & "<" & buffer.title & ">"
  if buffer.hovertext.len > 0:
    msg &= " " & buffer.hovertext
  var format: Format
  format.reverse = true
  buffer.writeStatusMessage(msg, format)

proc setStatusMessage*(buffer: Buffer, str: string) =
  buffer.writeStatusMessage(str)
  buffer.nostatus = true

proc lineInfo*(buffer: Buffer) =
    buffer.setStatusMessage("line " & $(buffer.cursory + 1) & "/" & $buffer.numLines & " col " & $(buffer.cursorx + 1) & "/" & $buffer.currentLineWidth() & " x: " & $buffer.currentCursorBytes())

proc displayBufferSwapOutput(buffer: Buffer) =
  print(buffer.generateSwapOutput())

proc displayBuffer(buffer: Buffer) =
  print(buffer.generateFullOutput())

proc displayStatusMessage*(buffer: Buffer) =
  print(HVP(buffer.height + 1, 1))
  print(SGR())
  print(buffer.generateStatusMessage())
  print(SGR())

proc click*(buffer: Buffer): string =
  let link = buffer.getCursorLink()
  if link != nil:
    if link.tagType == TAG_A:
      return HTMLAnchorElement(link).href
  return ""

proc drawBuffer*(buffer: Buffer) =
  var format = newFormat()
  for line in buffer.lines:
    if line.formats.len == 0:
      print(line.str & '\n')
    else:
      var x = 0
      for f in line.formats:
        print(line.str.substr(x, f.pos - 1))
        print(format.processFormat(f.format))
        x = f.pos
      print(line.str.substr(x, line.str.len) & '\n')

proc refreshBuffer*(buffer: Buffer) =
  buffer.title = buffer.getTitle()
  stdout.hideCursor()

  if buffer.refreshTermAttrs():
    buffer.redraw = true
    buffer.reshape = true

  if buffer.redraw:
    buffer.refreshDisplay()
    buffer.displayBuffer()
    buffer.redraw = false

  buffer.updateHover()
  if buffer.reshape:
    buffer.render()
    buffer.reshape = false
    buffer.refreshDisplay()
    buffer.displayBufferSwapOutput()

  if not buffer.nostatus:
    buffer.statusMsgForBuffer()
  else:
    buffer.nostatus = false
  buffer.displayStatusMessage()
  buffer.cursorBufferPos()
  stdout.showCursor()