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

              
 

                    

                  
                
                
                   


                      
                  

                  

                        
                            
                      
 



                                      

                           
                                 
 




                                    
 

                                                   
 
                                









                                           
                                      

                                 

                                      


                                 

                                      


                                    
                               

                                               

                                                
                               
                                 

                                               

                                                

                               
                                             
 
                                  
                            
                                     
               



                      
                                              
                             
                                      
                                                                      







                                              
 
                    
                                                                                                                                  



                                                                  


                                  
                                                                                                                 








                                                                          
                                                                                










                                                                              
                                                                                                                                     





                                            
                                                                 
 
                                                       

                                
                                                                        
       
                                                             
        
                                  
                                                                     
                     





                                                                                                                 
 
                                 
                                                                                                              


                                     
                                           



                                                   
                       


                               

                                  
 


                                    
                                     
                               

                      
                                                 
                                                       
                                                    
                                     
                                                              
                                                          




                                                 

                        




                           









                                        
                               
































                                                         
                                       

                                          



                                                  

               

                                





                                                                         





                                           
                                                                                   
                                                         









                                                   
       


                              
 



                             
import streams
import terminal
import options
import os

import css/sheet
import config/config
import io/buffer
import io/lineedit
import io/loader
import types/url
import utils/twtstr

type
  Client* = ref object
    buffer: Buffer
    feednext: bool
    s: string
    iserror: bool
    errormessage: string
    userstyle: CSSStylesheet
    loader: FileLoader

  ActionError = object of IOError
  LoadError = object of ActionError
  InterruptError = object of LoadError

proc newClient*(): Client =
  new(result)
  result.loader = newFileLoader()

proc loadError(s: string) =
  raise newException(LoadError, s)

proc actionError(s: string) =
  raise newException(ActionError, s)

proc interruptError() =
  raise newException(InterruptError, "Interrupted")

proc addBuffer(client: Client) =
  if client.buffer == nil:
    client.buffer = newBuffer()
  else:
    let oldnext = client.buffer.next
    client.buffer.next = newBuffer()
    if oldnext != nil:
      oldnext.prev = client.buffer.next
    client.buffer.next.prev = client.buffer
    client.buffer.next.next = oldnext
    client.buffer = client.buffer.next
  client.buffer.loader = client.loader

proc prevBuffer(client: Client) =
  if client.buffer.prev != nil:
    client.buffer = client.buffer.prev
    client.buffer.redraw = true

proc nextBuffer(client: Client) =
  if client.buffer.next != nil:
    client.buffer = client.buffer.next
    client.buffer.redraw = true

proc discardBuffer(client: Client) =
  if client.buffer.next != nil:
    if client.buffer.sourcepair != nil:
      client.buffer.sourcepair.sourcepair = nil
    client.buffer.next.prev = client.buffer.prev
    client.buffer = client.buffer.next
    client.buffer.redraw = true
  elif client.buffer.prev != nil:
    if client.buffer.sourcepair != nil:
      client.buffer.sourcepair.sourcepair = nil
    client.buffer.prev.next = client.buffer.next
    client.buffer = client.buffer.prev
    client.buffer.redraw = true
  else:
    actionError("Can't discard last buffer!")

proc setupBuffer(client: Client) =
  let buffer = client.buffer
  buffer.userstyle = client.userstyle
  buffer.load()
  buffer.render()
  buffer.gotoAnchor()
  buffer.redraw = true

proc readPipe(client: Client, ctype: string) =
  client.buffer = newBuffer()
  client.buffer.loader = client.loader
  client.buffer.contenttype = if ctype != "": ctype else: "text/plain"
  client.buffer.ispipe = true
  client.buffer.istream = newFileStream(stdin)
  client.buffer.load()
  #TODO is this portable at all?
  if reopen(stdin, "/dev/tty", fmReadWrite):
    client.setupBuffer()
  else:
    client.buffer.drawBuffer()

var g_client: Client
proc gotoUrl(client: Client, url: Url, click = none(ClickAction), prevurl = none(Url), force = false, newbuf = true, ctype = "") =
  setControlCHook(proc() {.noconv.} =
    raise newException(InterruptError, "Interrupted"))
  if force or prevurl.issome or not prevurl.get.equals(url, true):
    try:
      let page = if click.isnone:
        client.loader.getPage(url)
      else:
        client.loader.getPage(url, click.get.httpmethod, click.get.mimetype, click.get.body, click.get.multipart)
      if page.s != nil:
        if newbuf:
          client.addBuffer()
          g_client = client
          setControlCHook(proc() {.noconv.} =
            if g_client.buffer.prev != nil or g_client.buffer.next != nil:
              g_client.discardBuffer()
            interruptError())
        client.buffer.istream = page.s
        client.buffer.contenttype = if ctype != "": ctype else: page.contenttype
        client.buffer.streamclosed = false
      else:
        loadError("Couldn't load " & $url)
    except IOError, OSError:
      loadError("Couldn't load " & $url)
  elif client.buffer != nil and prevurl.isnone or not prevurl.get.equals(url):
    if not client.buffer.hasAnchor(url.anchor):
      loadError("Couldn't find anchor " & url.anchor)
  client.buffer.location = url
  client.setupBuffer()

proc gotoUrl(client: Client, url: string, click = none(ClickAction), prevurl = none(Url), force = false, newbuf = true, ctype = "") =
  var oldurl = prevurl
  if oldurl.isnone and client.buffer != nil:
    oldurl = client.buffer.location.some
  let newurl = parseUrl(url, oldurl)
  if newurl.isnone:
    loadError("Invalid URL " & url)
  client.gotoUrl(newurl.get, click, oldurl, force, newbuf, ctype)

proc loadUrl(client: Client, url: string, ctype = "") =
  let firstparse = parseUrl(url)
  if firstparse.issome:
    client.gotoUrl(url, none(ClickAction), none(Url), true, true, ctype)
  else:
    let cdir = parseUrl("file://" & getCurrentDir() & DirSep)
    try:
      # attempt to load local file
      client.gotoUrl(url, none(ClickAction), cdir, true, true, ctype)
    except LoadError:
      try:
        # attempt to load local file (this time percent encoded)
        client.gotoUrl(percentEncode(url, LocalPathPercentEncodeSet), none(ClickAction), cdir, true, true, ctype)
      except LoadError:
        # attempt to load remote page
        client.gotoUrl("http://" & url, none(ClickAction), none(Url), true, true, ctype)

proc reloadPage(client: Client) =
  client.gotoUrl(client.buffer.location, none(ClickAction), none(Url), true, false, client.buffer.contenttype)

proc changeLocation(client: Client) =
  let buffer = client.buffer
  var url = buffer.location.serialize(true)
  print(HVP(buffer.height + 1, 1))
  print(EL())
  let status = readLine("URL: ", url, buffer.width)
  if status:
    client.loadUrl(url)

proc click(client: Client) =
  let s = client.buffer.click()
  if s.issome and s.get.url != "":
    client.gotoUrl(s.get.url, s)

proc toggleSource*(client: Client) =
  let buffer = client.buffer
  if buffer.sourcepair != nil:
    client.buffer = buffer.sourcepair
    client.buffer.redraw = true
  else:
    client.addBuffer()
    client.buffer.sourcepair = client.buffer.prev
    client.buffer.sourcepair.sourcepair = client.buffer
    client.buffer.source = client.buffer.prev.source
    client.buffer.streamclosed = true
    client.buffer.location = client.buffer.sourcepair.location
    client.buffer.ispipe = client.buffer.sourcepair.ispipe
    let prevtype = client.buffer.prev.contenttype
    if prevtype == "text/html":
      client.buffer.contenttype = "text/plain"
    else:
      client.buffer.contenttype = "text/html"
    client.setupBuffer()

proc quit(client: Client) =
  eraseScreen()
  print(HVP(0, 0))
  quit(0)

proc input(client: Client) =
  let buffer = client.buffer
  if not client.feednext:
    client.s = ""
  else:
    client.feednext = false
  let c = getch()
  client.s &= c
  let action = getNormalAction(client.s)
  case action
  of ACTION_QUIT: client.quit()
  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_CURSOR_LEFT_EDGE: buffer.cursorLeftEdge()
  of ACTION_CURSOR_VERT_MIDDLE: buffer.cursorVertMiddle()
  of ACTION_CURSOR_RIGHT_EDGE: buffer.cursorRightEdge()
  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: client.click()
  of ACTION_CHANGE_LOCATION: client.changeLocation()
  of ACTION_LINE_INFO: buffer.lineInfo()
  of ACTION_FEED_NEXT: client.feednext = true
  of ACTION_RELOAD: client.reloadPage()
  of ACTION_RESHAPE: buffer.reshape = true
  of ACTION_REDRAW: buffer.redraw = true
  of ACTION_TOGGLE_SOURCE: client.toggleSource()
  of ACTION_PREV_BUFFER: client.prevBuffer()
  of ACTION_NEXT_BUFFER: client.nextBuffer()
  of ACTION_DISCARD_BUFFER: client.discardBuffer()
  else: discard

proc inputLoop(client: Client) =
  while true:
    g_client = client
    setControlCHook(proc() {.noconv.} =
      g_client.buffer.setStatusMessage("Interrupted rendering procedure")
      g_client.buffer.redraw = true
      g_client.buffer.reshape = false
      g_client.inputLoop())
    client.buffer.refreshBuffer()
    try:
      client.input()
    except ActionError as e:
      client.buffer.setStatusMessage(e.msg)

proc launchClient*(client: Client, pages: seq[string], ctype: string, dump: bool) =
  client.userstyle = gconfig.stylesheet.parseStylesheet()
  if not stdin.isatty:
    client.readPipe(ctype)
  try:
    for page in pages:
      client.loadUrl(page, ctype)
  except LoadError as e:
    eprint(e.msg & '\n')
    quit(1)

  if stdout.isatty and not dump: client.inputLoop()
  else:
    var buffer = client.buffer
    while buffer.next != nil:
      buffer = buffer.next

    buffer.drawBuffer()
    while buffer.prev != nil:
      buffer = buffer.prev
      buffer.drawBuffer()