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()