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

             
               
              
 







                    

    
                          





                       


                             

                             
                              


                               
 















                                  

                         
                  


                            
                          
                      

                

                 
              

               

                     

                                       
                       
                          
                       
                          

                  

                         
 
                                                

                                
                                      

                      
                                                                    
                                                                        
                                                      
 
                                                       

           
            
                             

                         
                   
           


                    
 











































































                                                                                                             
 
         



                                                         
 






                                                      
 
                                                                                                   
 


                                        
 








                                                                                                                                   

                                             
 
                                           
                                                                     
                                      
 

                                        
                                                         
 
                                      

                         
                                                      





                                                                     
 
                                                                

                                                                                  
                           

                              
                                                                    

                          
                                       
                                         
 
                                                           



                                  
                                                      
                           
                                                 
                                                                                                                                                    
                         
                   
 
                                             
         

                                         
         

                                             
         

                                         
         









                                          
                        









                                   



                    
                       
                           
 
                                     
                                                                          

                                        
                       
          
                                                                
                                                                                       

                         
 

                                                
                                                      

                                         
                        
                                                          
                                                             
                        
 
                                                            





                                                        
 
                                  
                                          
                      
                           
                                                      
                      
                          
 
                                

                        
                           

                                     
                          
 
                                   



                                                            
                                




                                                     
                                           



                                   
                                                       




                                     

                                       
                    
                 


                        

                                     
                                                
                              

                                                          
 
                                                     


                              
         
 

                                          

                        
               
 
                                                   


                   
 
                                               


                   

               
                           

           
                       
 
                                      

                        
                                   
                                                   


                
 
                                               


                

            
             
           

                                 
 


                                      
 


                                      
 
                                       

                      
                        
       
                         
 

                         
 
                                      

                                                    
                        
                                      
                         
 


                                 
 
                                    
                                                                                     
                         
 
                                    
                                                                         
                         
 
                                  
                                                                                             

                        
                        
 

                                                                   


                                                          

                         
 
                                    

                                                                                                

                         

                         
 

                                                             
                                                     

                         
 
                                

                                                                                                 











                                                                                                 
 
                                  
                                                    
                    




                                     
 
                                

                      
                                                      

















                                                           

                                        

                                                             
                   
                                                           

                                             
                       

                                              
                                                








                                              












                                                                

                                                                    
                                                      
 





                                     

                                      

                                     
                                                                     

             





                                            
             

                                                           
                                              


                                                         
           











                                                     

                         







                                        








                                                                   

                                      

                                               

                




                                         
 









                                                     

                                         
                                                                    
                                           
                                                                                

                                 
                              
 











































                                                              
                                    
                





                                        
 




                                           




                                                             
                                 















































                                                         
                                




                                                       
                                                                                                                                                                                                           







                                   






                                               











                                 
                            










                                                                
                         



                                 
import options
import terminal
import uri
import tables
import strutils
import unicode

import types/color
import types/enums
import utils/twtstr
import html/dom
import css/box
import config/config
import io/term
import io/lineedit

type
  Cell = object of RootObj
    fgcolor*: CellColor
    bgcolor*: CellColor
    italic: bool
    bold: bool
    underline: bool

  BufferCell = object of Cell
    rune*: Rune

  BufferRow = seq[BufferCell]

  DisplayCell = object of Cell
    runes*: seq[Rune]

  DisplayRow = seq[DisplayCell]

  DrawInstruction = object
    case t: DrawInstructionType
    of DRAW_TEXT:
      text: seq[Rune]
    of DRAW_GOTO:
      x: int
      y: int
    of DRAW_FGCOLOR, DRAW_BGCOLOR:
      color: CellColor
    of DRAW_STYLE:
      bold: bool
      italic: bool
      underline: bool
    of DRAW_RESET:
      discard

  Buffer* = ref BufferObj
  BufferObj = object
    title*: string
    lines*: seq[BufferRow]
    display*: DisplayRow
    prevdisplay*: DisplayRow
    statusmsg*: DisplayRow
    hovertext*: string
    width*: int
    height*: int
    cursorx*: int
    cursory*: int
    xend*: int
    fromx*: int
    fromy*: int
    nodes*: seq[Node]
    links*: seq[Node]
    elements*: seq[Element]
    idelements*: Table[string, Element]
    selectedlink*: Node
    attrs*: TermAttributes
    document*: Document
    displaycontrols*: bool
    redraw*: bool
    location*: Uri
    source*: string #TODO
    showsource*: bool

func newBuffer*(attrs: TermAttributes): Buffer =
  new(result)
  result.width = attrs.termWidth
  result.height = attrs.termHeight - 1
  result.attrs = attrs

  result.display = newSeq[DisplayCell](result.width * result.height)
  result.prevdisplay = newSeq[DisplayCell](result.width * result.height)
  result.statusmsg = newSeq[DisplayCell](result.width)

func generateFullOutput*(buffer: Buffer): seq[string] =
  var x = 0
  var y = 0
  var s = ""
  for cell in buffer.display:
    if x >= buffer.width:
      inc y
      result.add(s)
      x = 0
      s = ""
    s &= $cell.runes
    inc x

  result.add(s)

# generate a sequence of instructions to replace the previous frame with the
# current one. ideally we should have some mechanism in place to determine
# where we should use this and where we should just rewrite the frame
func generateSwapOutput*(buffer: Buffer): seq[DrawInstruction] =
  var fgcolor: CellColor
  var bgcolor: CellColor
  var italic = false
  var bold = false
  var underline = false

  let max = buffer.width * buffer.height
  let curr = buffer.display
  let prev = buffer.prevdisplay
  var x = 0
  var y = 0
  var cx = 0
  var cy = 0
  var i = 0
  var text: seq[Rune]
  while i < max:
    if x >= buffer.width:
      x = 0
      cx = 0
      text &= Rune('\n')
      inc y
      inc cy

    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.add(DrawInstruction(t: DRAW_TEXT, text: text))
          text.setLen(0)
        result.add(DrawInstruction(t: DRAW_GOTO, x: x, y: y))
        cx = x
        cy = y

      let cancont =
        (curr[i].fgcolor == fgcolor and curr[i].bgcolor == bgcolor and
         curr[i].italic == italic and curr[i].bold == bold and curr[i].underline == underline)

      if text.len > 0 and not cancont:
        result.add(DrawInstruction(t: DRAW_TEXT, text: text))
        text.setLen(0)

      if curr[i].fgcolor != fgcolor:
        fgcolor = curr[i].fgcolor
        result.add(DrawInstruction(t: DRAW_FGCOLOR, color: fgcolor))

      if curr[i].bgcolor != bgcolor:
        bgcolor = curr[i].bgcolor
        result.add(DrawInstruction(t: DRAW_BGCOLOR, color: bgcolor))

      if curr[i].italic != italic or curr[i].bold != bold or curr[i].underline != underline:
        if italic and not curr[i].italic or bold and not curr[i].bold or underline and not curr[i].underline:
          result.add(DrawInstruction(t: DRAW_RESET))
          if fgcolor != defaultColor:
            result.add(DrawInstruction(t: DRAW_FGCOLOR, color: fgcolor))
          if bgcolor != defaultColor:
            result.add(DrawInstruction(t: DRAW_BGCOLOR, color: bgcolor))
        italic = curr[i].italic
        bold = curr[i].bold
        underline = curr[i].underline
        result.add(DrawInstruction(t: DRAW_STYLE, italic: italic, bold: bold, underline: underline))

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

    inc x
    inc i
  
  if text.len > 0:
    result.add(DrawInstruction(t: DRAW_TEXT, text: text))

func generateStatusMessage*(buffer: Buffer): string =
  for cell in buffer.statusmsg:
    for r in cell.runes:
      if r != Rune(0):
        result &= $r

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

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

func width(line: seq[BufferCell]): int =
  for c in line:
    result += c.rune.width()

func cellWidthOverlap*(buffer: Buffer, x: int, 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 buffer.display[row + ox].runes.width()

func currentCellWidth*(buffer: Buffer): int = buffer.cellWidthOverlap(buffer.cursorx - buffer.fromx, buffer.cursory - buffer.fromy)

func currentLineWidth*(buffer: Buffer): int =
  return buffer.lines[buffer.cursory].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 lastNode*(buffer: Buffer): Node =
  return buffer.nodes[^1]

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] =
  if buffer.selectedlink != nil and buffer.selectedLink.parentNode of HtmlElement:
    return some(HtmlElement(buffer.selectedlink.parentNode))
  for node in buffer.nodes:
    if node.isElemNode():
      if node.getFmtLen() > 0:
        if buffer.cursorOnNode(node): return some(HtmlElement(node))
  return none(HtmlElement)

func canScroll*(buffer: Buffer): bool =
  return buffer.numLines >= buffer.height

func getElementById*(buffer: Buffer, id: string): Element =
  if buffer.idelements.hasKey(id):
    return buffer.idelements[id]
  return nil

proc findSelectedNode*(buffer: Buffer): Option[Node] =
  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:
        return some(node)
  return none(Node)

proc writefmt*(buffer: Buffer, str: string) =
  discard

proc writefmt*(buffer: Buffer, c: char) =
  discard

proc writeraw*(buffer: Buffer, str: string) =
  discard

proc writeraw*(buffer: Buffer, c: char) =
  discard

proc write*(buffer: Buffer, str: string) =
  buffer.writefmt(str)
  buffer.writeraw(str)

proc write*(buffer: Buffer, c: char) =
  buffer.writefmt(c)
  buffer.writeraw(c)

proc clearText*(buffer: Buffer) =
  buffer.lines.setLen(0)

proc clearNodes*(buffer: Buffer) =
  buffer.nodes.setLen(0)
  buffer.links.setLen(0)
  buffer.elements.setLen(0)
  buffer.idelements.clear()

proc clearBuffer*(buffer: Buffer) =
  buffer.clearText()
  buffer.clearNodes()
  buffer.cursorx = 0
  buffer.cursory = 0
  buffer.fromx = 0
  buffer.fromy = 0
  buffer.hovertext = ""
  buffer.selectedlink = nil

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

proc scrollTo*(buffer: Buffer, y: int) =
  if y == buffer.fromy:
    return
  buffer.fromy = min(max(buffer.numLines - buffer.height, 0), y)
  buffer.cursory = min(max(buffer.fromy, buffer.cursory), buffer.fromy + buffer.height)
  buffer.redraw = true
  buffer.restoreCursorX()

proc cursorTo*(buffer: Buffer, x: int, y: int) =
  buffer.redraw = false
  buffer.cursory = min(max(y, 0), buffer.numLines - 1)
  if buffer.fromy > buffer.cursory:
    buffer.fromy = max(buffer.cursory, 0)
    buffer.redraw = true
  elif buffer.fromy + buffer.height - 1 <= buffer.cursory:
    buffer.fromy = max(buffer.cursory - buffer.height + 1, 0)
    buffer.redraw = true

  buffer.cursorx = min(max(x, 0), buffer.currentLineWidth())
  if buffer.fromx < buffer.cursorx - buffer.width:
    buffer.fromx = max(0, buffer.cursorx - buffer.width)
    buffer.redraw = true
  elif buffer.fromx > buffer.cursorx:
    buffer.fromx = buffer.cursorx
    buffer.redraw = true

proc cursorDown*(buffer: Buffer) =
  if buffer.cursory < buffer.numLines - 1:
    inc buffer.cursory
    buffer.restoreCursorX()
    if buffer.cursory - buffer.height >= buffer.fromy:
      inc buffer.fromy
      buffer.redraw = true

proc cursorUp*(buffer: Buffer) =
  if buffer.cursory > 0:
    dec buffer.cursory
    buffer.restoreCursorX()
    if buffer.cursory < buffer.fromy:
      dec buffer.fromy
      buffer.redraw = true

proc cursorRight*(buffer: Buffer) =
  let cellwidth = buffer.currentCellWidth()
  let lw = buffer.currentLineWidth()
  if buffer.cursorx < lw - 1:
    buffer.cursorx = min(lw - 1, buffer.cursorx + cellwidth)
    buffer.xend = buffer.cursorx
    if buffer.cursorx - buffer.width >= buffer.fromx:
      inc buffer.fromx
      buffer.redraw = true

proc cursorLeft*(buffer: Buffer) =
  let cellwidth = buffer.currentCellWidth()
  if buffer.fromx > buffer.cursorx:
    buffer.fromx = buffer.cursorx
    buffer.redraw = true
  elif buffer.cursorx > 0:
    buffer.cursorx = max(0, buffer.cursorx - cellwidth)
    if buffer.fromx > buffer.cursorx:
      buffer.fromx = buffer.cursorx
      buffer.redraw = true

  buffer.xend = buffer.cursorx

proc cursorLineBegin*(buffer: Buffer) =
  buffer.cursorx = 0
  buffer.xend = 0
  if buffer.fromx > 0:
    buffer.fromx = 0
    buffer.redraw = true

proc cursorLineEnd*(buffer: Buffer) =
  buffer.cursorx = buffer.currentLineWidth() - 1
  buffer.xend = buffer.cursorx
  buffer.fromx = max(buffer.cursorx - buffer.width + 1, 0)
  buffer.redraw = buffer.fromx > 0

iterator revnodes*(buffer: Buffer): Node {.inline.} =
  var i = buffer.nodes.len - 1
  while i >= 0:
    yield buffer.nodes[i]
    dec i

proc cursorNextWord*(buffer: Buffer) =
  let llen = buffer.currentLineWidth() - 1
  var x = buffer.cursorx
  var y = buffer.cursory
  if llen >= 0:

    while not buffer.lines[y][x].rune.breaksWord():
      if x >= llen:
        break
      inc x

    while buffer.lines[y][x].rune.breaksWord():
      if x >= llen:
        break
      inc x

  if x >= llen:
    if y < buffer.numLines:
      inc y
      x = 0
  buffer.cursorTo(x, y)

proc cursorPrevWord*(buffer: Buffer) =
  var x = buffer.cursorx
  var y = buffer.cursory
  if buffer.currentLineWidth() > 0:
    while not buffer.lines[y][x].rune.breaksWord():
      if x == 0:
        break
      dec x

    while buffer.lines[y][x].rune.breaksWord():
      if x == 0:
        break
      dec x

  if x == 0:
    if y > 0:
      dec y
      x = buffer.lines[y].len - 1
  buffer.cursorTo(x, y)

proc cursorNextLink*(buffer: Buffer) =
  #TODO
  return

proc cursorPrevLink*(buffer: Buffer) =
  #TODO
  return

proc cursorFirstLine*(buffer: Buffer) =
  if buffer.fromy > 0:
    buffer.fromy = 0
    buffer.redraw = true
  else:
    buffer.redraw = false

  buffer.cursory = 0
  buffer.restoreCursorX()

proc cursorLastLine*(buffer: Buffer) =
  if buffer.fromy < buffer.numLines - buffer.height:
    buffer.fromy = buffer.numLines - buffer.height
    buffer.redraw = true
  buffer.cursory = buffer.numLines - 1
  buffer.restoreCursorX()

proc cursorTop*(buffer: Buffer) =
  buffer.cursory = buffer.fromy
  buffer.restoreCursorX()

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

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

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 + 1, 1)
  buffer.fromy = max(0, buffer.fromy - buffer.height)
  buffer.redraw = true
  buffer.restoreCursorX()

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

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

proc pageRight*(buffer: Buffer) =
  buffer.cursorx = min(buffer.fromx, buffer.currentLineWidth())
  buffer.fromx = min(max(buffer.maxScreenWidth() - buffer.width, 0), buffer.fromx + buffer.width)
  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
    if buffer.fromx >= buffer.cursorx:
      buffer.cursorRight()
    buffer.redraw = true

proc scrollLeft*(buffer: Buffer) =
  if buffer.fromx > 0:
    dec buffer.fromx
    if buffer.fromx + buffer.height <= buffer.cursorx:
      buffer.cursorLeft()
    buffer.redraw = true

proc gotoAnchor*(buffer: Buffer): bool =
  if buffer.location.anchor != "":
    let node =  buffer.getElementById(buffer.location.anchor)
    if node != nil:
      buffer.scrollTo(max(node.y - buffer.height div 2, 0))

proc setLocation*(buffer: Buffer, uri: Uri) =
  buffer.location = uri

proc gotoLocation*(buffer: Buffer, uri: Uri) =
  buffer.location = buffer.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

proc setText*(buffer: Buffer, x: int, y: int, text: seq[Rune]) =
  while buffer.lines.len <= y:
    buffer.lines.add(newSeq[BufferCell]())

  while buffer.lines[y].len < x + text.len:
    buffer.lines[y].add(BufferCell())
  
  var i = 0
  while i < text.len:
    buffer.lines[y][i].rune = text[i]
    inc i

proc reshape*(buffer: Buffer) =
  buffer.display = newSeq[DisplayCell](buffer.width * buffer.height)
  buffer.statusmsg = newSeq[DisplayCell](buffer.width)

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)
      j += line[i].rune.width()
      inc i

    inc y

proc renderPlainText*(buffer: Buffer, text: string) =
  var i = 0
  var y = 0
  var line = ""
  while i < text.len:
    if text[i] == '\n':
      buffer.setText(0, y, line.toRunes())
      inc y
      line = ""
    elif text[i] == '\r':
      discard
    elif text[i] == '\t':
      line &= ' '.repeat(8)
    else:
      line &= text[i]
    inc i
  if line.len > 0:
    buffer.setText(0, y, line.toRunes())

proc renderDocument*(buffer: Buffer) =
  var stack: seq[CSSBox]
  stack.add(buffer.document.root.box)
  while stack.len > 0:
    let box = stack.pop()
    buffer.setText(box.innerEdge.x1, box.innerEdge.y1, box.content)

    for child in box.children:
      stack.add(child)

proc cursorBufferPos(buffer: Buffer) =
  let x = max(buffer.cursorx - buffer.fromx, 0)
  let y = buffer.cursory - buffer.fromy
  termGoto(x, y)

proc clearStatusMessage(buffer: Buffer) =
  var i = 0
  while i < buffer.statusmsg.len:
    buffer.statusmsg[i].runes.setLen(0)
    inc i

proc setStatusMessage*(buffer: Buffer, str: string) =
  buffer.clearStatusMessage()
  let text = str.toRunes()
  var i = 0
  var n = 0
  while i < text.len:
    if text[i].width() == 0:
      inc n
    buffer.statusmsg[i - n].runes.add(text[i])
    inc i

proc statusMsgForBuffer(buffer: Buffer) =
  var msg = ($(buffer.cursory + 1) & "/" & $buffer.numLines & " (" &
            $buffer.atPercentOf() & "%) " &
            "<" & buffer.title & ">").ansiStyle(styleReverse).ansiReset().join()
  if buffer.hovertext.len > 0:
    msg &= " " & buffer.hovertext
  buffer.setStatusMessage(msg)

proc displayBufferSwapOutput(buffer: Buffer) =
  termGoto(0, 0)
  let instructions = buffer.generateSwapOutput()
  for inst in instructions:
    case inst.t
    of DRAW_TEXT:
      print(inst.text)
    of DRAW_GOTO:
      termGoto(inst.x, inst.y)
    of DRAW_FGCOLOR:
      let color = inst.color
      if inst.color.rgb:
        let rgb = color.rgbcolor
        print("\e38;2" & $rgb.r & ";" & $rgb.b & ";" & $rgb.g)
      else:
        print("\e[" & $color.color & "m")
    of DRAW_BGCOLOR:
      let color = inst.color
      if inst.color.rgb:
        let rgb = color.rgbcolor
        print("\e48;2" & $rgb.r & ";" & $rgb.b & ";" & $rgb.g)
      else:
        print("\e[" & $color.color & "m")
    of DRAW_STYLE:
      var os = "\e["
      var p = false
      if inst.italic:
        os &= "3"
        p = true
      if inst.bold:
        if p:
          os &= ";"
        os &= "1"
        p = true
      if inst.underline:
        if p:
          os &= ";"
        os &= "4"
        p = true
      os &= "m"
      print(os)
    of DRAW_RESET:
      print("\e[0m")

proc displayBuffer(buffer: Buffer) =
  termGoto(0, 0)
  let full = buffer.generateFullOutput()
  for line in full:
    print(line)
    #TODO
    print("\e[K")
    print('\n')

proc displayStatusMessage(buffer: Buffer) =
  termGoto(0, buffer.height)
  eraseLine()
  print(buffer.generateStatusMessage())

proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool =
  var s = ""
  var feedNext = false
  while true:
    buffer.redraw = false
    buffer.displayStatusMessage()
    stdout.showCursor()
    buffer.cursorBufferPos()
    if not feedNext:
      s = ""
    else:
      feedNext = false
    let c = getch()
    s &= c
    let action = getNormalAction(s)
    var redraw = false
    var reshape = false
    var nostatus = false
    case action
    of ACTION_QUIT:
      eraseScreen()
      setCursorPos(0, 0)
      return false
    of ACTION_CURSOR_LEFT: buffer.cursorLeft()
    of ACTION_CURSOR_DOWN: buffer.cursorDown()
    of ACTION_CURSOR_UP: buffer.cursorUp()
    of ACTION_CURSOR_RIGHT: buffer.cursorRight()
    of ACTION_CURSOR_LINEBEGIN: buffer.cursorLineBegin()
    of ACTION_CURSOR_LINEEND: buffer.cursorLineEnd()
    of ACTION_CURSOR_NEXT_WORD: buffer.cursorNextWord()
    of ACTION_CURSOR_PREV_WORD: buffer.cursorPrevWord()
    of ACTION_CURSOR_NEXT_LINK: buffer.cursorNextLink()
    of ACTION_CURSOR_PREV_LINK: buffer.cursorPrevLink()
    of ACTION_PAGE_DOWN: buffer.pageDown()
    of ACTION_PAGE_UP: buffer.pageUp()
    of ACTION_PAGE_RIGHT: buffer.pageRight()
    of ACTION_PAGE_LEFT: buffer.pageLeft()
    of ACTION_HALF_PAGE_DOWN: buffer.halfPageDown()
    of ACTION_HALF_PAGE_UP: buffer.halfPageUp()
    of ACTION_CURSOR_FIRST_LINE: buffer.cursorFirstLine()
    of ACTION_CURSOR_LAST_LINE: buffer.cursorLastLine()
    of ACTION_CURSOR_TOP: buffer.cursorTop()
    of ACTION_CURSOR_MIDDLE: buffer.cursorMiddle()
    of ACTION_CURSOR_BOTTOM: buffer.cursorBottom()
    of ACTION_CENTER_LINE: buffer.centerLine()
    of ACTION_SCROLL_DOWN: buffer.scrollDown()
    of ACTION_SCROLL_UP: buffer.scrollUp()
    of ACTION_SCROLL_LEFT: buffer.scrollLeft()
    of ACTION_SCROLL_RIGHT: buffer.scrollRight()
    of ACTION_CLICK:
      discard
    of ACTION_CHANGE_LOCATION:
      var url = $buffer.location

      termGoto(0, buffer.height)
      let status = readLine("URL: ", url, buffer.width)
      if status:
        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())
      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
    of ACTION_TOGGLE_SOURCE:
      buffer.showsource = not buffer.showsource
      if buffer.showsource:
        buffer.renderPlainText(buffer.source)
      else:
        buffer.renderDocument()
      redraw = true
    else: discard
    stdout.hideCursor()

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

    if buffer.redraw:
      redraw = true

    if reshape:
      buffer.reshape()
      buffer.displayBuffer()
    if redraw:
      buffer.refreshDisplay()
      buffer.displayBuffer()

    if not nostatus:
      buffer.statusMsgForBuffer()
    else:
      nostatus = false

proc displayPage*(attrs: TermAttributes, buffer: Buffer): bool =
  discard buffer.gotoAnchor()
  buffer.refreshDisplay()
  buffer.displayBuffer()
  buffer.statusMsgForBuffer()
  return inputLoop(attrs, buffer)