diff options
-rw-r--r-- | src/buffer/buffer.nim | 358 | ||||
-rw-r--r-- | src/buffer/container.nim | 285 | ||||
-rw-r--r-- | src/display/client.nim | 6 | ||||
-rw-r--r-- | src/display/pager.nim | 24 | ||||
-rw-r--r-- | src/ips/serialize.nim | 21 | ||||
-rw-r--r-- | src/utils/eprint.nim | 9 |
6 files changed, 420 insertions, 283 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index 9c016c0f..458b5b17 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -42,12 +42,10 @@ type BufferCommand* = enum LOAD, RENDER, WINDOW_CHANGE, FIND_ANCHOR, READ_SUCCESS, READ_CANCELED, CLICK, FIND_NEXT_LINK, FIND_PREV_LINK, FIND_NEXT_MATCH, FIND_PREV_MATCH, - GET_SOURCE, GET_LINES, MOVE_CURSOR, PASS_FD + GET_SOURCE, GET_LINES, UPDATE_HOVER, PASS_FD, CONNECT, GOTO_ANCHOR ContainerCommand* = enum - SET_LINES, SET_NEEDS_AUTH, SET_CONTENT_TYPE, SET_REDIRECT, SET_TITLE, - SET_HOVER, SET_LOAD_INFO, SET_NUM_LINES, READ_LINE, LOAD_DONE, - ANCHOR_FOUND, ANCHOR_FAIL, JUMP, OPEN, BUFFER_READY, SOURCE_READY, RESHAPE + BUFFER_READY, RESHAPE, FULFILL_PROMISE BufferMatch* = object success*: bool @@ -84,6 +82,60 @@ type config: BufferConfig userstyle: CSSStylesheet + # async, but worse + EmptyPromise = ref object of RootObj + cb: (proc()) + next: EmptyPromise + stream: Stream + + Promise*[T] = ref object of EmptyPromise + res: T + + BufferInterface* = ref object + stream: Stream + packetid: int + promises: Table[int, EmptyPromise] + +proc newBufferInterface*(ostream: Stream): BufferInterface = + result = BufferInterface( + stream: ostream + ) + +proc fulfill*(iface: BufferInterface, packetid, len: int) = + var promise: EmptyPromise + if iface.promises.pop(packetid, promise): + if promise.stream != nil and promise.cb == nil and len != 0: + var abc = alloc(len) + var x = 0 + while x < len: + x += promise.stream.readData(abc, len) + dealloc(abc) + while promise != nil: + if promise.cb != nil: + promise.cb() + promise = promise.next + +proc then*(promise: EmptyPromise, cb: (proc())): EmptyPromise {.discardable.} = + promise.cb = cb + promise.next = EmptyPromise() + return promise.next + +proc then*[T](promise: Promise[T], cb: (proc(x: T))): EmptyPromise {.discardable.} = + return promise.then(proc() = + if promise.stream != nil: + promise.stream.sread(promise.res) + cb(promise.res)) + +proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U] {.discardable.} = + let next = Promise[U]() + promise.then(proc(x: T) = + let p2 = cb(x) + if p2 != nil: + p2.then(proc(y: U) = + next.res = y + next.cb())) + return next + macro writeCommand(buffer: Buffer, cmd: ContainerCommand, args: varargs[typed]) = let cmdblock = newStmtList() var idlist: seq[NimNode] @@ -96,14 +148,15 @@ macro writeCommand(buffer: Buffer, cmd: ContainerCommand, args: varargs[typed]) inc i let lens = ident("lens") cmdblock.add(quote do: + if `cmd` != BUFFER_READY: return var `lens` = slen(`cmd`)) for id in idlist: cmdblock.add(quote do: `lens` += slen(`id`)) - cmdblock.add(quote do: - `buffer`.postream.swrite(`lens`)) + cmdblock.add(quote do: `buffer`.postream.swrite(`lens`)) cmdblock.add(quote do: `buffer`.postream.swrite(`cmd`)) for id in idlist: - cmdblock.add(quote do: `buffer`.postream.swrite(`id`)) + cmdblock.add(quote do: + `buffer`.postream.swrite(`id`)) cmdblock.add(quote do: `buffer`.postream.flush()) return newBlockStmt(cmdblock) @@ -117,28 +170,45 @@ proc buildInterfaceProc(fun: NimNode): tuple[fun, name: NimNode] = if x[^1] == '=': x = "SET_" & x[0..^2] let nup = ident(x) # add this to enums - let this2 = newIdentDefs(ident("stream"), ident("Stream")) + let this2 = newIdentDefs(ident("iface"), ident("BufferInterface")) let thisval = this2[0] body.add(quote do: - `thisval`.swrite(BufferCommand.`nup`)) + `thisval`.stream.swrite(BufferCommand.`nup`) + `thisval`.stream.swrite(`thisval`.packetid)) var params2: seq[NimNode] - params2.add(retval) + var retval2: NimNode + if retval.kind == nnkEmpty: + retval2 = ident("EmptyPromise") + else: + retval2 = newNimNode(nnkBracketExpr).add( + ident("Promise"), + retval) + params2.add(retval2) params2.add(this2) for i in 2 ..< params.len: let param = params[i] for i in 0 ..< param.len - 2: let id2 = newIdentDefs(ident(param[i].strVal), param[^2]) params2.add(id2) - for c in params2[2..^1]: - let s = c[0] # sym e.g. url + for i in 2 ..< params2.len: + let s = params2[i][0] # sym e.g. url body.add(quote do: - `thisval`.swrite(`s`)) + `thisval`.stream.swrite(`s`)) + body.add(quote do: + `thisval`.stream.flush()) body.add(quote do: - `thisval`.flush()) - if retval.kind != nnkEmpty: + `thisval`.promises[`thisval`.packetid] = `retval2`(stream: `thisval`.stream) + inc `thisval`.packetid) + var pragmas: NimNode + if retval.kind == nnkEmpty: + body.add(quote do: + return `thisval`.promises[`thisval`.packetid - 1]) + pragmas = newNimNode(nnkPragma).add(ident("discardable")) + else: body.add(quote do: - `thisval`.sread(result)) - return (newProc(name, params2, body), nup) + return `retval2`(`thisval`.promises[`thisval`.packetid - 1])) + pragmas = newEmptyNode() + return (newProc(name, params2, body, pragmas = pragmas), nup) type ProxyFunction = object @@ -150,17 +220,31 @@ type # Name -> ProxyFunction var ProxyFunctions {.compileTime.}: ProxyMap -macro proxy(fun: typed) = +macro proxy0(fun: untyped) = + fun[0] = ident(fun[0].strVal & "_internal") + return fun + +macro proxy1(fun: typed) = let iproc = buildInterfaceProc(fun) var pfun: ProxyFunction pfun.iname = ident(fun[0].strVal & "_internal") pfun.ename = iproc[1] - for x in fun[3]: pfun.params.add(x) + pfun.params.add(fun[3][0]) + var params2: seq[NimNode] + params2.add(fun[3][0]) + for i in 1 ..< fun[3].len: + let param = fun[3][i] + pfun.params.add(param) + for i in 0 ..< param.len - 2: + let id2 = newIdentDefs(ident(param[i].strVal), param[^2]) + params2.add(id2) ProxyFunctions[fun[0].strVal] = pfun - let ifun = newProc(pfun.iname, pfun.params, fun[6]) - result = newStmtList() - result.add(iproc[0]) - result.add(ifun) + return iproc[0] + +macro proxy(fun: typed) = + quote do: + proxy0(`fun`) + proxy1(`fun`) func getLink(node: StyledNode): HTMLAnchorElement = if node == nil: @@ -199,7 +283,7 @@ func cursorBytes(buffer: Buffer, y: int, cc: int): int = w += r.width() return i -func findPrevLink0(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] = +proc findPrevLink*(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] {.proxy.} = let line = buffer.lines[cursory] var i = line.findFormatN(cursorx) - 1 var link: Element = nil @@ -256,7 +340,7 @@ func findPrevLink0(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] = dec i return (-1, -1) -func findNextLink0(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] = +proc findNextLink*(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] {.proxy.} = let line = buffer.lines[cursory] var i = line.findFormatN(cursorx) - 1 var link: Element = nil @@ -282,7 +366,7 @@ func findNextLink0(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] = inc i return (-1, -1) -proc findPrevMatch0(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: bool): BufferMatch = +proc findPrevMatch*(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: bool): BufferMatch {.proxy.} = template return_if_match = if res.success and res.captures.len > 0: let cap = res.captures[^1] @@ -309,7 +393,7 @@ proc findPrevMatch0(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: b return_if_match dec y -proc findNextMatch0(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: bool): BufferMatch = +proc findNextMatch*(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: bool): BufferMatch {.proxy.} = template return_if_match = if res.success and res.captures.len > 0: let cap = res.captures[0] @@ -336,25 +420,7 @@ proc findNextMatch0(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: b return_if_match inc y -proc findPrevLink*(buffer: Buffer, cursorx, cursory: int) {.proxy.} = - let pl = buffer.findPrevLink0(cursorx, cursory) - buffer.writeCommand(JUMP, pl.x, pl.y, 0) - -proc findNextLink*(buffer: Buffer, cursorx, cursory: int) {.proxy.} = - let pl = buffer.findNextLink0(cursorx, cursory) - buffer.writeCommand(JUMP, pl.x, pl.y, 0) - -proc findPrevMatch*(buffer: Buffer, cursorx, cursory: int, regex: Regex, wrap: bool) {.proxy.} = - let match = buffer.findPrevMatch0(regex, cursorx, cursory, wrap) - if match.success: - buffer.writeCommand(JUMP, match.x, match.y, match.x + match.str.width() - 1) - -proc findNextMatch*(buffer: Buffer, cursorx, cursory: int, regex: Regex, wrap: bool) {.proxy.} = - let match = buffer.findNextMatch0(regex, cursorx, cursory, wrap) - if match.success: - buffer.writeCommand(JUMP, match.x, match.y, match.x + match.str.width() - 1) - -proc gotoAnchor(buffer: Buffer) = +proc gotoAnchor*(buffer: Buffer): tuple[x, y: int] {.proxy.} = if buffer.document == nil: return let anchor = buffer.document.getElementById(buffer.location.anchor) if anchor == nil: return @@ -363,17 +429,30 @@ proc gotoAnchor(buffer: Buffer) = for i in 0 ..< line.formats.len: let format = line.formats[i] if format.node != nil and anchor in format.node.node: - buffer.writeCommand(JUMP, format.pos, y, 0) - return + return (format.pos, y) + +proc do_reshape(buffer: Buffer) = + case buffer.contenttype + of "text/html": + if buffer.viewport == nil: + buffer.viewport = Viewport(window: buffer.attrs) + let ret = renderDocument(buffer.document, buffer.attrs, buffer.userstyle, buffer.viewport, buffer.prevstyled) + buffer.lines = ret[0] + buffer.prevstyled = ret[1] + else: + buffer.lines = renderPlainText(buffer.source) proc windowChange*(buffer: Buffer, attrs: WindowAttributes) {.proxy.} = buffer.attrs = attrs buffer.viewport = Viewport(window: buffer.attrs) buffer.width = buffer.attrs.width buffer.height = buffer.attrs.height - 1 - buffer.reshape = true -proc updateHover(buffer: Buffer, cursorx, cursory: int) = +type UpdateHoverResult* = object + hover*: Option[string] + repaint*: bool + +proc updateHover*(buffer: Buffer, cursorx, cursory: int): UpdateHoverResult {.proxy.} = var thisnode: StyledNode let i = buffer.lines[cursory].findFormatN(cursorx) - 1 if i >= 0: @@ -387,12 +466,13 @@ proc updateHover(buffer: Buffer, cursorx, cursory: int) = if not elem.hover: elem.hover = true buffer.reshape = true + result.repaint = true let link = thisnode.getLink() if link != nil: - buffer.writeCommand(SET_HOVER, link.href) + result.hover = some(link.href) else: - buffer.writeCommand(SET_HOVER, "") + result.hover = some("") for styledNode in prevnode.branch: if styledNode.t == STYLED_ELEMENT and styledNode.node != nil: @@ -400,12 +480,10 @@ proc updateHover(buffer: Buffer, cursorx, cursory: int) = if elem.hover: elem.hover = false buffer.reshape = true + result.repaint = true buffer.prevnode = thisnode -proc moveCursor*(buffer: Buffer, cursorx, cursory: int) {.proxy.} = - buffer.updateHover(cursorx, cursory) - proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement) = let url = parseUrl(elem.href, document.location.some) if url.isSome: @@ -437,7 +515,8 @@ proc loadResources(buffer: Buffer, document: Document) = proc c_setvbuf(f: File, buf: pointer, mode: cint, size: csize_t): cint {. importc: "setvbuf", header: "<stdio.h>", tags: [].} -func getFd(buffer: Buffer): FileHandle = +func getFd(buffer: Buffer): int = + if buffer.streamclosed: return -1 let source = buffer.bsource case source.t of CLONE, LOAD_REQUEST: @@ -446,8 +525,12 @@ func getFd(buffer: Buffer): FileHandle = of LOAD_PIPE: return buffer.bsource.fd -proc setupSource(buffer: Buffer): int = - if buffer.loaded: return -2 +type ConnectResult* = tuple[code: int, needsAuth: bool, redirect: Option[URL], contentType: string] + +proc setupSource(buffer: Buffer): ConnectResult = + if buffer.loaded: + result.code = -2 + return let source = buffer.bsource let setct = source.contenttype.isNone if not setct: @@ -456,13 +539,16 @@ proc setupSource(buffer: Buffer): int = case source.t of CLONE: buffer.istream = connectSocketStream(source.clonepid) - if buffer.istream == nil: return -2 + if buffer.istream == nil: + result.code = -2 + return if setct: buffer.contenttype = "text/plain" of LOAD_PIPE: var f: File if not open(f, source.fd, fmRead): - return 1 + result.code = 1 + return discard c_setvbuf(f, nil, IONBF, 0) buffer.istream = newFileStream(f) if setct: @@ -471,20 +557,19 @@ proc setupSource(buffer: Buffer): int = let request = source.request let response = buffer.loader.doRequest(request) if response.body == nil: - return response.res + result.code = response.res + return if setct: buffer.contenttype = response.contenttype buffer.istream = response.body - if response.status == 401: # Unauthorized - buffer.writeCommand(SET_NEEDS_AUTH) - if response.redirect.isSome: - buffer.writeCommand(SET_REDIRECT, response.redirect.get) + result.needsAuth = response.status == 401 # Unauthorized + result.redirect = response.redirect if setct: - buffer.writeCommand(SET_CONTENT_TYPE, buffer.contenttype) + result.contentType = buffer.contenttype buffer.selector.registerHandle(cast[int](buffer.getFd()), {Read}, 1) buffer.loaded = true -proc load0(buffer: Buffer) = +proc load0(buffer: Buffer): auto = if buffer.contenttype == "text/html": if not buffer.streamclosed: buffer.source = buffer.istream.readAll() @@ -494,40 +579,27 @@ proc load0(buffer: Buffer) = buffer.streamclosed = true else: buffer.document = parseHTML5(newStringStream(buffer.source)) - buffer.writeCommand(SET_TITLE, buffer.document.title) buffer.document.location = buffer.location buffer.loadResources(buffer.document) + return (true, buffer.document.title) + return (false, "") -proc load*(buffer: Buffer) {.proxy.} = - buffer.writeCommand(SET_LOAD_INFO, CONNECT) +proc connect*(buffer: Buffer): ConnectResult {.proxy.} = let code = buffer.setupSource() - if code != -2: - buffer.writeCommand(SET_LOAD_INFO, DOWNLOAD) - buffer.load0() - buffer.writeCommand(LOAD_DONE, code) + return code -proc do_reshape(buffer: Buffer) = - case buffer.contenttype - of "text/html": - if buffer.viewport == nil: - buffer.viewport = Viewport(window: buffer.attrs) - let ret = renderDocument(buffer.document, buffer.attrs, buffer.userstyle, buffer.viewport, buffer.prevstyled) - buffer.lines = ret[0] - buffer.prevstyled = ret[1] - else: - buffer.lines = renderPlainText(buffer.source) +proc load*(buffer: Buffer): tuple[success: bool, title: string] {.proxy.} = + return buffer.load0() -proc render*(buffer: Buffer) {.proxy.} = - buffer.writeCommand(SET_LOAD_INFO, LoadInfo.RENDER) +proc render*(buffer: Buffer): int {.proxy.} = buffer.do_reshape() - buffer.writeCommand(SET_LOAD_INFO, DONE) - buffer.writeCommand(SET_NUM_LINES, buffer.lines.len) - buffer.gotoAnchor() + return buffer.lines.len proc load2(buffer: Buffer) = case buffer.contenttype of "text/html": - assert false, "Not implemented yet..." + #assert false, "Not implemented yet..." + discard else: # This is incredibly stupid but it works so whatever. # (We're basically recv'ing single bytes, but nim std/net does buffering @@ -535,10 +607,10 @@ proc load2(buffer: Buffer) = if not buffer.streamclosed: let c = buffer.istream.readChar() buffer.source &= c - buffer.reshape = true + buffer.do_reshape() proc finishLoad(buffer: Buffer) = - if not buffer.streamclosed: + if buffer.contenttype != "text/html" and not buffer.streamclosed: if not buffer.istream.atEnd: let a = buffer.istream.readAll() buffer.sstream.write(a) @@ -726,21 +798,25 @@ template restore_focus = buffer.document.focus = nil buffer.reshape = true -proc readSuccess*(buffer: Buffer, s: string) {.proxy.} = +type ReadSuccessResult* = tuple[open: Option[Request], reshape: bool] + +proc readSuccess*(buffer: Buffer, s: string): ReadSuccessResult {.proxy.} = if buffer.input != nil: let input = buffer.input case input.inputType of INPUT_SEARCH: input.value = s input.invalid = true + result.reshape = true buffer.reshape = true if input.form != nil: let submitaction = submitForm(input.form, input) if submitaction.isSome: - buffer.writeCommand(OPEN, submitaction.get) + result.open = submitaction of INPUT_TEXT, INPUT_PASSWORD: input.value = s input.invalid = true + result.reshape = true buffer.reshape = true of INPUT_FILE: let cdir = parseUrl("file://" & getCurrentDir() & DirSep) @@ -748,11 +824,22 @@ proc readSuccess*(buffer: Buffer, s: string) {.proxy.} = if path.issome: input.file = path input.invalid = true + result.reshape = true buffer.reshape = true else: discard buffer.input = nil -proc click*(buffer: Buffer, cursorx, cursory: int) {.proxy.} = +type ReadLineResult* = object + prompt*: string + value*: string + hide*: bool + +type ClickResult* = object + open*: Option[Request] + readline*: Option[ReadLineResult] + repaint*: bool + +proc click*(buffer: Buffer, cursorx, cursory: int): ClickResult {.proxy.} = let clickable = buffer.getCursorClickable(cursorx, cursory) if clickable != nil: case clickable.tagType @@ -762,7 +849,7 @@ proc click*(buffer: Buffer, cursorx, cursory: int) {.proxy.} = restore_focus let url = parseUrl(HTMLAnchorElement(clickable).href, clickable.document.baseUrl.some) if url.issome: - buffer.writeCommand(OPEN, newRequest(url.get, HTTP_GET)) + result.open = some(newRequest(url.get, HTTP_GET)) of TAG_OPTION: let option = HTMLOptionElement(clickable) let select = option.select @@ -783,19 +870,30 @@ proc click*(buffer: Buffer, cursorx, cursory: int) {.proxy.} = case input.inputType of INPUT_SEARCH: buffer.input = input - buffer.writeCommand(READ_LINE, "SEARCH: ", input.value, false) + result.readline = some(ReadLineResult( + prompt: "SEARCH: ", + value: input.value + )) of INPUT_TEXT, INPUT_PASSWORD: buffer.input = input - buffer.writeCommand(READ_LINE, "TEXT: ", input.value, input.inputType == INPUT_PASSWORD) + result.readline = some(ReadLineResult( + prompt: "TEXT: ", + value: input.value, + hide: input.inputType == INPUT_PASSWORD + )) of INPUT_FILE: var path = if input.file.issome: input.file.get.path.serialize_unicode() else: "" - buffer.writeCommand(READ_LINE, "Filename: ", path, false) + result.readline = some(ReadLineResult( + prompt: "Filename: ", + value: path + )) of INPUT_CHECKBOX: input.checked = not input.checked input.invalid = true + result.repaint = true buffer.reshape = true of INPUT_RADIO: for radio in input.radiogroup: @@ -803,16 +901,16 @@ proc click*(buffer: Buffer, cursorx, cursory: int) {.proxy.} = radio.invalid = true input.checked = true input.invalid = true + result.repaint = true buffer.reshape = true of INPUT_RESET: if input.form != nil: input.form.reset() + result.repaint = true buffer.reshape = true of INPUT_SUBMIT, INPUT_BUTTON: if input.form != nil: - let submitaction = submitForm(input.form, input) - if submitaction.isSome: - buffer.writeCommand(OPEN, submitaction.get) + result.open = submitForm(input.form, input) else: restore_focus else: @@ -821,26 +919,19 @@ proc click*(buffer: Buffer, cursorx, cursory: int) {.proxy.} = proc readCanceled*(buffer: Buffer) {.proxy.} = buffer.input = nil -proc findAnchor*(buffer: Buffer, anchor: string) {.proxy.} = - if buffer.document != nil and buffer.document.getElementById(anchor) != nil: - buffer.writeCommand(ANCHOR_FOUND) - else: - buffer.writeCommand(ANCHOR_FAIL) +proc findAnchor*(buffer: Buffer, anchor: string): bool {.proxy.} = + return buffer.document != nil and buffer.document.getElementById(anchor) != nil -proc getLines*(buffer: Buffer, w: Slice[int]) {.proxy.} = +proc getLines*(buffer: Buffer, w: Slice[int]): seq[SimpleFlexibleLine] {.proxy.} = var w = w if w.b < 0 or w.b > buffer.lines.high: w.b = buffer.lines.high - var lens = sizeof(SET_LINES) + sizeof(buffer.lines.len) + sizeof(w) + #TODO this is horribly inefficient for y in w: - lens += slen(buffer.lines[y]) - buffer.postream.swrite(lens) - buffer.postream.swrite(SET_LINES) - buffer.postream.swrite(buffer.lines.len) - buffer.postream.swrite(w) - for y in w: - buffer.postream.swrite(buffer.lines[y]) - buffer.postream.flush() + var line = SimpleFlexibleLine(str: buffer.lines[y].str) + for f in buffer.lines[y].formats: + line.formats.add(SimpleFormatCell(format: f.format, pos: f.pos)) + result.add(line) proc passFd*(buffer: Buffer) {.proxy.} = let fd = SocketStream(buffer.pistream).recvFileHandle() @@ -848,7 +939,6 @@ proc passFd*(buffer: Buffer) {.proxy.} = proc getSource*(buffer: Buffer) {.proxy.} = let ssock = initServerSocket() - buffer.writeCommand(SOURCE_READY) let stream = ssock.acceptSocketStream() if not buffer.streamclosed: buffer.source = buffer.istream.readAll() @@ -857,7 +947,7 @@ proc getSource*(buffer: Buffer) {.proxy.} = stream.close() ssock.close() -macro bufferDispatcher(funs: static ProxyMap, buffer: Buffer, cmd: BufferCommand) = +macro bufferDispatcher(funs: static ProxyMap, buffer: Buffer, cmd: BufferCommand, packetid: int) = let switch = newNimNode(nnkCaseStmt) switch.add(ident("cmd")) for k, v in funs: @@ -874,7 +964,28 @@ macro bufferDispatcher(funs: static ProxyMap, buffer: Buffer, cmd: BufferCommand var `id`: `typ` `buffer`.pistream.sread(`id`)) call.add(id) - stmts.add(call) + var rval: NimNode + if v.params[0].kind == nnkEmpty: + stmts.add(call) + else: + rval = ident("retval") + stmts.add(quote do: + let `rval` = `call`) + if rval == nil: + stmts.add(quote do: + let len = slen(FULFILL_PROMISE) + slen(`packetid`) + buffer.postream.swrite(len) + buffer.postream.swrite(FULFILL_PROMISE) + buffer.postream.swrite(`packetid`) + buffer.postream.flush()) + else: + stmts.add(quote do: + let len = slen(FULFILL_PROMISE) + slen(`packetid`) + slen(`rval`) + buffer.postream.swrite(len) + buffer.postream.swrite(FULFILL_PROMISE) + buffer.postream.swrite(`packetid`) + buffer.postream.swrite(`rval`) + buffer.postream.flush()) ofbranch.add(stmts) switch.add(ofbranch) return switch @@ -883,7 +994,9 @@ proc readCommand(buffer: Buffer) = let istream = buffer.pistream var cmd: BufferCommand istream.sread(cmd) - bufferDispatcher(ProxyFunctions, buffer, cmd) + var packetid: int + istream.sread(packetid) + bufferDispatcher(ProxyFunctions, buffer, cmd, packetid) proc runBuffer(buffer: Buffer, rfd: int) = block loop: @@ -905,10 +1018,9 @@ proc runBuffer(buffer: Buffer, rfd: int) = buffer.finishLoad() if not buffer.alive: break loop - if buffer.reshape: + if buffer.reshape and buffer.document != nil: #TODO null check shouldn't be needed? buffer.reshape = false buffer.do_reshape() - buffer.writeCommand(RESHAPE) buffer.pistream.close() buffer.postream.close() buffer.loader.quit() diff --git a/src/buffer/container.nim b/src/buffer/container.nim index 2e9d9618..f17267c3 100644 --- a/src/buffer/container.nim +++ b/src/buffer/container.nim @@ -52,6 +52,7 @@ type clear*: bool Container* = ref object + iface: BufferInterface attrs*: WindowAttributes width*: int height*: int @@ -81,6 +82,7 @@ type redraw*: bool cmdvalid: array[ContainerCommand, bool] needslines*: bool + events*: seq[ContainerEvent] proc newBuffer*(dispatcher: Dispatcher, config: Config, source: BufferSource, title = ""): Container = let attrs = getWindowAttributes(stdout) @@ -240,20 +242,35 @@ func findHighlights*(container: Container, y: int): seq[Highlight] = if y in hl: result.add(hl) -proc expect(container: Container, cmd: ContainerCommand) = - container.cmdvalid[cmd] = true +proc triggerEvent(container: Container, event: ContainerEvent) = + container.events.add(event) + +proc triggerEvent(container: Container, t: ContainerEventType) = + container.triggerEvent(ContainerEvent(t: t)) + +proc updateCursor(container: Container) proc requestLines*(container: Container, w = container.lineWindow) = - container.ostream.getLines(w) - container.expect(SET_LINES) + container.iface.getLines(w).then(proc(res: seq[SimpleFlexibleLine]) = + container.lines.setLen(w.len) + container.lineshift = w.a + for y in 0 ..< min(res.len, w.len): + container.lines[y] = res[y] + container.updateCursor() + let cw = container.fromy ..< container.fromy + container.height + if w.a in cw or w.b in cw or cw.a in w or cw.b in w: + container.triggerEvent(UPDATE)) proc redraw*(container: Container) {.jsfunc.} = container.redraw = true proc sendCursorPosition*(container: Container) = - container.ostream.moveCursor(container.cursorx, container.cursory) - container.expect(SET_HOVER) - container.expect(RESHAPE) + container.iface.updateHover(container.cursorx, container.cursory).then(proc(res: UpdateHoverResult) = + if res.hover.isSome: + container.hovertext = res.hover.get + container.triggerEvent(STATUS) + if res.repaint: + container.needslines = true) proc setFromY*(container: Container, y: int) {.jsfunc.} = if container.pos.fromy != y: @@ -504,49 +521,100 @@ proc popCursorPos*(container: Container, nojump = false) = container.needslines = true proc cursorNextLink*(container: Container) {.jsfunc.} = - container.ostream.findNextLink(container.cursorx, container.cursory) - container.expect(JUMP) + container.iface + .findNextLink(container.cursorx, container.cursory) + .then(proc(res: tuple[x, y: int]) = + container.setCursorXY(res.x, res.y)) proc cursorPrevLink*(container: Container) {.jsfunc.} = - container.ostream.findPrevLink(container.cursorx, container.cursory) - container.expect(JUMP) + container.iface + .findPrevLink(container.cursorx, container.cursory) + .then(proc(res: tuple[x, y: int]) = + container.setCursorXY(res.x, res.y)) + +proc clearSearchHighlights*(container: Container) = + for i in countdown(container.highlights.high, 0): + if container.highlights[i].clear: + container.highlights.del(i) proc cursorNextMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} = - container.ostream.findNextMatch(container.cursorx, container.cursory, regex, wrap) - container.expect(JUMP) + container.iface + .findNextMatch(regex, container.cursorx, container.cursory, wrap) + .then(proc(res: BufferMatch) = + if container.hlon: + container.setCursorXY(res.x, res.y) + container.clearSearchHighlights() + let ex = res.x + res.str.width() - 1 + let hl = Highlight(x: res.x, y: res.y, endx: ex, endy: res.y, clear: true) + container.highlights.add(hl) + container.hlon = false) proc cursorPrevMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} = - container.ostream.findPrevMatch(container.cursorx, container.cursory, regex, wrap) - container.expect(JUMP) + container.iface + .findPrevMatch(regex, container.cursorx, container.cursory, wrap) + .then(proc(res: BufferMatch) = + if container.hlon: + container.setCursorXY(res.x, res.y) + container.clearSearchHighlights() + let ex = res.x + res.str.width() - 1 + let hl = Highlight(x: res.x, y: res.y, endx: ex, endy: res.y, clear: true) + container.highlights.add(hl) + container.hlon = false) + +proc setLoadInfo(container: Container, msg: string) = + container.loadinfo = msg + container.triggerEvent(STATUS) proc load*(container: Container) = - container.ostream.load() - container.expect(LOAD_DONE) - container.expect(SET_LOAD_INFO) - container.expect(SET_NEEDS_AUTH) - container.expect(SET_REDIRECT) - container.expect(SET_CONTENT_TYPE) - container.expect(SET_TITLE) - if container.source.location.anchor != "": - container.expect(JUMP) - -proc gotoAnchor*(container: Container, anchor: string) = - container.ostream.findAnchor(anchor) - container.expect(ANCHOR_FOUND) - container.expect(ANCHOR_FAIL) + container.loadinfo = "Connecting to " & $container.source.location + container.iface.connect().then(proc(res: ConnectResult): auto = + container.code = res.code + if res.code != -2: + if res.code == 0: + if res.needsAuth: + container.triggerEvent(NEEDS_AUTH) + if res.redirect.isSome: + container.triggerEvent(REDIRECT) + if res.contentType != "": + container.contenttype = some(res.contentType) + container.setLoadInfo("Downloading " & $container.source.location) + return container.iface.load() + else: + container.triggerEvent(FAIL) + ).then(proc(res: tuple[success: bool, title: string]): auto = + if res.success: + container.title = res.title + container.setLoadInfo("Rendering " & $container.source.location) + return container.iface.render() + ).then(proc(lines: int): auto = + container.numLines = lines + container.setLoadInfo("") + container.needslines = true + return container.iface.gotoAnchor() + ).then(proc(res: tuple[x, y: int]) = + container.setCursorXY(res.x, res.y) + ) + +proc findAnchor*(container: Container, anchor: string) = + container.iface.findAnchor(anchor).then(proc(found: bool) = + if found: + container.triggerEvent(ANCHOR) + else: + container.triggerEvent(NO_ANCHOR)) proc readCanceled*(container: Container) = - container.ostream.readCanceled() + container.iface.readCanceled() proc readSuccess*(container: Container, s: string) = - container.ostream.readSuccess(s) - container.expect(OPEN) - container.expect(RESHAPE) + container.iface.readSuccess(s).then(proc(res: ReadSuccessResult) = + if res.reshape: + container.needslines = true + if res.open.isSome: + container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get))) proc reshape*(container: Container, noreq = false) {.jsfunc.} = - container.ostream.render() - container.expect(SET_NUM_LINES) - container.expect(JUMP) + container.iface.render().then(proc(lines: int) = + container.numLines = lines) if not noreq: container.needslines = true @@ -558,30 +626,36 @@ proc dupeBuffer*(dispatcher: Dispatcher, container: Container, config: Config, l clonepid: container.process, ) container.pipeto = dispatcher.newBuffer(config, source, container.title) - container.ostream.getSource() - container.expect(SOURCE_READY) + container.iface.getSource().then(proc() = + if container.pipeto != nil: + container.pipeto.load() + container.pipeto = nil) return container.pipeto proc click*(container: Container) {.jsfunc.} = - container.ostream.click(container.cursorx, container.cursory) - container.expect(OPEN) - container.expect(READ_LINE) - container.expect(RESHAPE) + container.iface.click(container.cursorx, container.cursory).then(proc(res: ClickResult) = + if res.repaint: + container.needslines = true + if res.open.isSome: + container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get)) + if res.readline.isSome: + let rl = res.readline.get + container.triggerEvent( + ContainerEvent( + t: READ_LINE, + prompt: rl.prompt, + value: rl.value, + password: rl.hide))) proc windowChange*(container: Container, attrs: WindowAttributes) = container.attrs = attrs container.width = attrs.width container.height = attrs.height - 1 - container.ostream.windowChange(attrs) - container.expect(RESHAPE) - -proc clearSearchHighlights*(container: Container) = - for i in countdown(container.highlights.high, 0): - if container.highlights[i].clear: - container.highlights.del(i) + container.iface.windowChange(attrs).then(proc() = + container.needslines = true) proc handleCommand(container: Container, cmd: ContainerCommand, len: int): ContainerEvent = - if not container.cmdvalid[cmd] and cmd != SET_LINES: + if not container.cmdvalid[cmd] and false or cmd notin {FULFILL_PROMISE, BUFFER_READY}: let len = len - sizeof(cmd) #TODO TODO TODO for i in 0 ..< len: @@ -590,106 +664,28 @@ proc handleCommand(container: Container, cmd: ContainerCommand, len: int): Conta return ContainerEvent(t: INVALID_COMMAND) container.cmdvalid[cmd] = false case cmd - of SET_LOAD_INFO: - var li: LoadInfo - container.istream.sread(li) - case li - of CONNECT: - container.loadinfo = "Connecting to " & $container.source.location - container.expect(SET_LOAD_INFO) - of DOWNLOAD: - container.loadinfo = "Downloading " & $container.source.location - container.expect(SET_LOAD_INFO) - of RENDER: - container.loadinfo = "Rendering " & $container.source.location - container.expect(SET_LOAD_INFO) - of DONE: - container.loadinfo = "" - return ContainerEvent(t: STATUS) - of SET_LINES: - var w: Slice[int] - container.istream.sread(container.numLines) - container.istream.sread(w) - container.lines.setLen(w.len) - container.lineshift = w.a - for y in 0 ..< w.len: - container.istream.sread(container.lines[y]) - container.updateCursor() - let cw = container.fromy ..< container.fromy + container.height - if w.a in cw or w.b in cw or cw.a in w or cw.b in w: - return ContainerEvent(t: UPDATE) - of SET_NUM_LINES: - container.istream.sread(container.numLines) - of SET_NEEDS_AUTH: - return ContainerEvent(t: NEEDS_AUTH) - of SET_CONTENT_TYPE: - var ctype: string - container.istream.sread(ctype, 128) - container.contenttype = some(ctype) - of SET_REDIRECT: - var redirect: URL - container.istream.sread(redirect) - if redirect != nil: - container.redirect = some(redirect) - return ContainerEvent(t: REDIRECT) - of SET_TITLE: - container.istream.sread(container.title) - return ContainerEvent(t: STATUS) - of SET_HOVER: - container.istream.sread(container.hovertext) - return ContainerEvent(t: STATUS) - of LOAD_DONE: - container.istream.sread(container.code) - if container.code == -2: return - if container.code != 0: - return ContainerEvent(t: FAIL) - return ContainerEvent(t: SUCCESS) - of ANCHOR_FOUND: - return ContainerEvent(t: ANCHOR) - of ANCHOR_FAIL: - return ContainerEvent(t: FAIL) - of READ_LINE: - var prompt, str: string - var pwd: bool - container.istream.sread(prompt, 1024) - container.istream.sread(str, 1024) - container.istream.sread(pwd) - container.cmdvalid[OPEN] = false - return ContainerEvent(t: READ_LINE, prompt: prompt, value: str, password: pwd) - of JUMP: - var x, y, ex: int - container.istream.sread(x) - container.istream.sread(y) - container.istream.sread(ex) - if x != -1 and y != -1: - if container.hlon: - container.clearSearchHighlights() - let hl = Highlight(x: x, y: y, endx: ex, endy: y, clear: true) - container.highlights.add(hl) - container.hlon = false - container.setCursorXY(x, y) - of OPEN: - var request: Request - container.istream.sread(request) - container.cmdvalid[READ_LINE] = false - return ContainerEvent(t: OPEN, request: request) of BUFFER_READY: if container.source.t == LOAD_PIPE: - container.ostream.passFd() + container.iface.passFd() let s = SocketStream(container.ostream) s.sendFileHandle(container.source.fd) discard close(container.source.fd) container.ostream.flush() container.load() - of SOURCE_READY: - if container.pipeto != nil: - container.pipeto.load() - container.pipeto = nil of RESHAPE: container.needslines = true + of FULFILL_PROMISE: + var packetid: int + container.istream.sread(packetid) + container.iface.fulfill(packetid, len - slen(packetid) - slen(FULFILL_PROMISE)) if container.needslines: - container.expect(SET_LINES) container.requestLines() + container.needslines = false + +proc setStream*(container: Container, stream: Stream) = + container.istream = stream + container.ostream = stream + container.iface = newBufferInterface(stream) # Synchronously read all lines in the buffer. iterator readLines*(container: Container): SimpleFlexibleLine {.inline.} = @@ -698,11 +694,12 @@ iterator readLines*(container: Container): SimpleFlexibleLine {.inline.} = var len: int container.istream.sread(len) container.istream.sread(cmd) - while cmd != SET_LINES: - discard container.handleCommand(cmd, len) - container.istream.sread(len) - container.istream.sread(cmd) - assert cmd == SET_LINES + #TODO TODO TODO + #while cmd != SET_LINES: + # discard container.handleCommand(cmd, len) + # container.istream.sread(len) + # container.istream.sread(cmd) + #assert cmd == SET_LINES var w: Slice[int] container.istream.sread(container.numLines) container.istream.sread(w) diff --git a/src/display/client.nim b/src/display/client.nim index 36761d56..24ea8183 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -243,8 +243,7 @@ proc acceptBuffers(client: Client) = if pid in client.pager.procmap: let container = client.pager.procmap[pid] client.pager.procmap.del(pid) - container.istream = stream - container.ostream = stream + container.setStream(stream) let fd = stream.source.getFd() client.fdmap[int(fd)] = container client.selector.registerHandle(fd, {Read}, nil) @@ -314,7 +313,8 @@ proc writeFile(client: Client, path: string, content: string) {.jsfunc.} = proc newConsole(pager: Pager, tty: File): Console = new(result) - if tty != nil: + result.tty = tty + if tty != nil and false: var pipefd: array[0..1, cint] if pipe(pipefd) == -1: raise newException(Defect, "Failed to open console pipe.") diff --git a/src/display/pager.nim b/src/display/pager.nim index 119b5002..7bd50e6f 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -8,7 +8,6 @@ import unicode when defined(posix): import posix -import buffer/buffer import buffer/cell import buffer/container import config/config @@ -428,7 +427,7 @@ proc gotoURL*(pager: Pager, request: Request, prevurl = none(URL), ctype = none( pager.addContainer(container) else: pager.container.redirect = some(request.url) - pager.container.gotoAnchor(request.url.anchor) + pager.container.findAnchor(request.url.anchor) # When the user has passed a partial URL as an argument, they might've meant # either: @@ -580,12 +579,7 @@ proc click(pager: Pager) {.jsfunc.} = proc authorize*(pager: Pager) = pager.setLineEdit(readLine("Username: ", pager.attrs.width, term = pager.term), USERNAME) -proc handleEvent*(pager: Pager, container: Container): bool = - var event: ContainerEvent - try: - event = container.handleEvent() - except IOError: - return false +proc handleEvent0*(pager: Pager, container: Container, event: ContainerEvent): bool = case event.t of FAIL: pager.deleteContainer(container) @@ -637,5 +631,19 @@ proc handleEvent*(pager: Pager, container: Container): bool = of NO_EVENT: discard return true +proc handleEvent*(pager: Pager, container: Container): bool = + var event: ContainerEvent + try: + event = container.handleEvent() + except IOError: + return false + if not pager.handleEvent0(container, event): + return false + while container.events.len > 0: + let event = container.events.pop() + if not pager.handleEvent0(container, event): + return false + return true + proc addPagerModule*(ctx: JSContext) = ctx.registerType(Pager) diff --git a/src/ips/serialize.nim b/src/ips/serialize.nim index 9de0e374..2647db2f 100644 --- a/src/ips/serialize.nim +++ b/src/ips/serialize.nim @@ -14,7 +14,7 @@ import types/url proc slen*[T](o: T): int = when T is string: - return sizeof(o.len) + o.len + return slen(o.len) + o.len elif T is bool: return sizeof(char) elif T is URL: @@ -83,6 +83,9 @@ proc slen*[T](o: T): int = of LOAD_PIPE: result += slen(o.fd) result += slen(o.location) result += slen(o.contenttype) + elif T is tuple: + for f in o.fields: + result += slen(f) else: result += sizeof(o) @@ -182,6 +185,14 @@ proc swrite*(stream: Stream, source: BufferSource) = proc swrite*(stream: Stream, bconfig: BufferConfig) = stream.swrite(bconfig.userstyle) +proc swrite*(stream: Stream, tup: tuple) = + for f in tup.fields: + stream.swrite(f) + +proc swrite*(stream: Stream, obj: object) = + for f in obj.fields: + stream.swrite(f) + template sread*[T](stream: Stream, o: T) = stream.read(o) @@ -314,3 +325,11 @@ proc sread*(stream: Stream, source: var BufferSource) = proc sread*(stream: Stream, bconfig: var BufferConfig) = stream.sread(bconfig.userstyle) + +proc sread*(stream: Stream, obj: var object) = + for f in obj.fields: + stream.sread(f) + +proc sread*(stream: Stream, tup: var tuple) = + for f in tup.fields: + stream.sread(f) diff --git a/src/utils/eprint.nim b/src/utils/eprint.nim index c5936d2a..6692ccf5 100644 --- a/src/utils/eprint.nim +++ b/src/utils/eprint.nim @@ -2,8 +2,8 @@ template eprint*(s: varargs[string, `$`]) = {.cast(noSideEffect), cast(tags: []), cast(raises: []).}: var a = false + var o = "" when nimVm: - var o = "" for x in s: if not a: a = true @@ -16,6 +16,7 @@ template eprint*(s: varargs[string, `$`]) = {.cast(noSideEffect), cast(tags: []) if not a: a = true else: - stderr.write(' ') - stderr.write(x) - stderr.write('\n') + o &= ' ' + o &= x + o &= '\n' + stderr.write(o) |