import options import os import streams import terminal import unicode import bindings/curl import css/sheet import config/config import io/buffer import io/cell import io/lineedit import io/loader import io/term import js/javascript import js/regex import types/url import utils/twtstr type Client* = ref ClientObj ClientObj = object buffer: Buffer feednext: bool s: string iserror: bool errormessage: string userstyle: CSSStylesheet loader: FileLoader jsrt: JSRuntime jsctx: JSContext regex: Option[Regex] revsearch: bool needsauth: bool redirecturl: Option[Url] ActionError = object of IOError LoadError = object of ActionError InterruptError = object of LoadError proc statusMode(client: Client) = print(HVP(client.buffer.height + 1, 1)) print(EL()) proc js_console_log(ctx: JSContext, this: JSValue, argc: int, argv: ptr JSValue): JSValue {.cdecl.} = let opaque = ctx.getOpaque() for i in 0.. # * https:// # So we attempt to visit each of them, in the order described above. proc loadUrl(client: Client, url: string, ctype = "") = let firstparse = parseUrl(url) if firstparse.issome: client.gotoUrl(url, none(ClickAction), none(Url), true, ctype) else: let cdir = parseUrl("file://" & getCurrentDir() & DirSep) try: # attempt to load local file client.gotoUrl(url, none(ClickAction), cdir, true, ctype) except LoadError: try: # attempt to load local file (this time percent encoded) client.gotoUrl(percentEncode(url, LocalPathPercentEncodeSet), none(ClickAction), cdir, true, ctype) except LoadError: # attempt to load remote page client.gotoUrl("http://" & url, none(ClickAction), none(Url), true, ctype) # Reload the page in a new buffer, then kill the previous buffer. proc reloadPage(client: Client) = let buf = client.buffer client.gotoUrl(client.buffer.location, none(ClickAction), none(Url), true, client.buffer.contenttype) discardBuffer(buf) # Open a URL prompt and visit the specified URL. proc changeLocation(client: Client) = let buffer = client.buffer var url = buffer.location.serialize(true) client.statusMode() 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 command(client: Client) = var iput: string client.statusMode() let status = readLine("COMMAND: ", iput, client.buffer.width) if status and iput.len > 0: let ret = client.jsctx.eval(iput, "", JS_EVAL_TYPE_GLOBAL) let opaque = client.jsctx.getOpaque() if ret.isException(): let ex = client.jsctx.getException() let str = ex.toString() if str.issome: opaque.err &= str.get & '\n' var stack = ex.getProperty("stack") if not stack.isUndefined(): let str = stack.toString() if str.issome: opaque.err &= str.get & '\n' free(stack) free(ex) else: let str = ret.toString() if str.issome: opaque.err &= str.get & '\n' free(ret) 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 = newStringStream(opaque.err) client.buffer.contenttype = "text/plain" client.setupBuffer() proc searchNext(client: Client) = if client.regex.issome: if not client.revsearch: discard client.buffer.cursorNextMatch(client.regex.get) else: discard client.buffer.cursorPrevMatch(client.regex.get) proc searchPrev(client: Client) = if client.regex.issome: if not client.revsearch: discard client.buffer.cursorPrevMatch(client.regex.get) else: discard client.buffer.cursorNextMatch(client.regex.get) proc search(client: Client) = client.statusMode() var iput: string let status = readLine("/", iput, client.buffer.width) if status: if iput.len != 0: client.regex = compileSearchRegex(iput) client.revsearch = false client.searchNext() proc searchBack(client: Client) = client.statusMode() var iput: string let status = readLine("?", iput, client.buffer.width) if status: if iput.len != 0: client.regex = compileSearchRegex(iput) client.revsearch = true client.searchNext() proc isearch(client: Client) = client.statusMode() var iput: string let cpos = client.buffer.cpos var mark: Mark var my: int template del_mark() = if mark != nil: client.buffer.removeMark(my, mark) let status = readLine("/", iput, client.buffer.width, {}, (proc(state: var LineState): bool = del_mark let regex = compileSearchRegex($state.news) client.buffer.cpos = cpos if regex.issome: let match = client.buffer.cursorNextMatch(regex.get) if match.success: mark = client.buffer.addMark(match.x, match.y, match.str.width()) my = match.y client.buffer.redraw = true client.buffer.refreshBuffer(true) print(HVP(client.buffer.height + 1, 2)) print(SGR()) else: del_mark return true false )) del_mark client.buffer.redraw = true client.buffer.refreshBuffer(true) if status: client.regex = compileSearchRegex(iput) else: client.buffer.cpos = cpos proc isearchBack(client: Client) = client.statusMode() var iput: string let cpos = client.buffer.cpos var mark: Mark var my: int template del_mark() = if mark != nil: client.buffer.removeMark(my, mark) let status = readLine("?", iput, client.buffer.width, {}, (proc(state: var LineState): bool = del_mark let regex = compileSearchRegex($state.news) client.buffer.cpos = cpos if regex.issome: let match = client.buffer.cursorPrevMatch(regex.get) if match.success: mark = client.buffer.addMark(match.x, match.y, match.str.width()) my = match.y client.buffer.redraw = true client.buffer.refreshBuffer(true) print(HVP(client.buffer.height + 1, 2)) print(SGR()) else: del_mark return true false )) del_mark client.buffer.redraw = true if status: client.regex = compileSearchRegex(iput) else: client.buffer.cpos = cpos proc quit(client: Client) = eraseScreen() print(HVP(0, 0)) curl_global_cleanup() 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() of ACTION_COMMAND: client.command() of ACTION_SEARCH: client.search() of ACTION_SEARCH_BACK: client.searchBack() of ACTION_ISEARCH: client.isearch() of ACTION_ISEARCH_BACK: client.isearchBack() of ACTION_SEARCH_NEXT: client.searchNext() of ACTION_SEARCH_PREV: client.searchPrev() else: discard proc checkAuth(client: Client) = client.statusMode() var username = "" let ustatus = readLine("Username: ", username, client.buffer.width) if not ustatus: client.needsauth = false return client.statusMode() var password = "" let pstatus = readLine("Password: ", password, client.buffer.width) if not pstatus: client.needsauth = false return var url = client.buffer.location url.username = username url.password = password var buf = client.buffer client.gotoUrl(url, prevurl = some(client.buffer.location)) discardBuffer(buf) proc followRedirect(client: Client) = if client.redirecturl.issome: var buf = client.buffer let redirecturl = client.redirecturl.get client.redirecturl = none(Url) client.gotoUrl(redirecturl, prevurl = some(client.buffer.location)) discardBuffer(buf) 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() while client.redirecturl.issome: client.followRedirect() client.buffer.refreshBuffer() if client.needsauth: # Unauthorized client.checkAuth() 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) = if curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK: eprint "Failed to initialize libcurl." quit(1) 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 quit(1) if stdout.isatty and not dump: when defined(posix): enableRawMode() 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()