diff options
-rw-r--r-- | src/buffer/buffer.nim | 52 | ||||
-rw-r--r-- | src/buffer/container.nim | 55 | ||||
-rw-r--r-- | src/config/bufferconfig.nim | 1 | ||||
-rw-r--r-- | src/config/config.nim | 2 | ||||
-rw-r--r-- | src/config/toml.nim | 1 | ||||
-rw-r--r-- | src/display/client.nim | 106 | ||||
-rw-r--r-- | src/display/pager.nim | 135 | ||||
-rw-r--r-- | src/io/lineedit.nim | 22 | ||||
-rw-r--r-- | src/io/term.nim | 58 | ||||
-rw-r--r-- | src/ips/forkserver.nim | 1 | ||||
-rw-r--r-- | src/ips/socketstream.nim | 2 |
11 files changed, 243 insertions, 192 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index 0c30ea92..2acf0e6c 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -36,6 +36,9 @@ import types/url import utils/twtstr type + LoadInfo* = enum + CONNECT, DOWNLOAD, RENDER, DONE + BufferCommand* = enum LOAD, RENDER, WINDOW_CHANGE, GOTO_ANCHOR, READ_SUCCESS, READ_CANCELED, CLICK, FIND_NEXT_LINK, FIND_PREV_LINK, FIND_NEXT_MATCH, FIND_PREV_MATCH, @@ -43,8 +46,8 @@ type ContainerCommand* = enum SET_LINES, SET_NEEDS_AUTH, SET_CONTENT_TYPE, SET_REDIRECT, SET_TITLE, - SET_HOVER, READ_LINE, LOAD_DONE, ANCHOR_FOUND, ANCHOR_FAIL, JUMP, OPEN, - BUFFER_READY, SOURCE_READY, RESHAPE + SET_HOVER, SET_LOAD_INFO, SET_NUM_LINES, READ_LINE, LOAD_DONE, + ANCHOR_FOUND, ANCHOR_FAIL, JUMP, OPEN, BUFFER_READY, SOURCE_READY, RESHAPE BufferMatch* = object success*: bool @@ -53,6 +56,7 @@ type str*: string Buffer* = ref object + alive: bool input: HTMLInputElement contenttype: string lines: FlexibleGrid @@ -83,12 +87,14 @@ type macro writeCommand(buffer: Buffer, cmd: ContainerCommand, args: varargs[typed]) = result = newStmtList() let lens = ident("lens") - result.add(quote do: + let calclens = newStmtList() + calclens.add(quote do: var `lens` = slen(`cmd`)) for arg in args: - result.add(quote do: `lens` += slen(`arg`)) - result.add(quote do: + calclens.add(quote do: `lens` += slen(`arg`)) + calclens.add(quote do: `buffer`.postream.swrite(`lens`)) + result.add(newBlockStmt(calclens)) result.add(quote do: `buffer`.postream.swrite(`cmd`)) for arg in args: result.add(quote do: `buffer`.postream.swrite(`arg`)) @@ -272,15 +278,13 @@ proc gotoAnchor(buffer: Buffer) = if buffer.document == nil: return let anchor = buffer.document.getElementById(buffer.location.anchor) if anchor == nil: return - for y in 0..<buffer.lines.len: + for y in 0 ..< buffer.lines.len: let line = buffer.lines[y] - var i = 0 - while i < line.formats.len: + 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) + buffer.writeCommand(JUMP, format.pos, y, 0) return - inc i proc windowChange(buffer: Buffer) = buffer.viewport = Viewport(window: buffer.attrs) @@ -732,8 +736,11 @@ proc readCommand(buffer: Buffer) = let fd = SocketStream(istream).recvFileHandle() buffer.bsource.fd = fd of LOAD: + buffer.writeCommand(SET_LOAD_INFO, CONNECT) let code = buffer.setupSource() - buffer.load() + if code != -2: + buffer.writeCommand(SET_LOAD_INFO, DOWNLOAD) + buffer.load() buffer.writeCommand(LOAD_DONE, code) of GOTO_ANCHOR: var anchor: string @@ -743,7 +750,10 @@ proc readCommand(buffer: Buffer) = else: buffer.writeCommand(ANCHOR_FAIL) of RENDER: + buffer.writeCommand(SET_LOAD_INFO, LoadInfo.RENDER) buffer.render() + buffer.writeCommand(SET_LOAD_INFO, DONE) + buffer.writeCommand(SET_NUM_LINES, buffer.lines.len) buffer.gotoAnchor() of GET_LINES: var w: Slice[int] @@ -826,7 +836,7 @@ proc readCommand(buffer: Buffer) = proc runBuffer(buffer: Buffer, rfd: int) = block loop: - while true: + while buffer.alive: let events = buffer.selector.select(-1) for event in events: if Read in event.events: @@ -842,6 +852,8 @@ proc runBuffer(buffer: Buffer, rfd: int) = break loop elif event.fd == buffer.getFd(): buffer.finishLoad() + if not buffer.alive: + break loop if buffer.reshape: buffer.reshape = false buffer.render() @@ -854,14 +866,16 @@ proc runBuffer(buffer: Buffer, rfd: int) = proc launchBuffer*(config: BufferConfig, source: BufferSource, attrs: WindowAttributes, loader: FileLoader, mainproc: Pid) = - let buffer = new Buffer - buffer.userstyle = parseStylesheet(config.userstyle) - buffer.attrs = attrs + let buffer = Buffer( + alive: true, + userstyle: parseStylesheet(config.userstyle), + attrs: attrs, + config: config, + loader: loader, + bsource: source, + sstream: newStringStream() + ) buffer.windowChange() - buffer.sstream = newStringStream() - buffer.config = config - buffer.loader = loader - buffer.bsource = source buffer.selector = newSelector[int]() let sstream = connectSocketStream(mainproc, false) sstream.swrite(getpid()) diff --git a/src/buffer/container.nim b/src/buffer/container.nim index cd321b48..b1f73629 100644 --- a/src/buffer/container.nim +++ b/src/buffer/container.nim @@ -34,7 +34,7 @@ type ContainerEventType* = enum NO_EVENT, FAIL, SUCCESS, NEEDS_AUTH, REDIRECT, ANCHOR, NO_ANCHOR, UPDATE, - READ_LINE, OPEN, INVALID_COMMAND + READ_LINE, OPEN, INVALID_COMMAND, STATUS ContainerEvent* = object case t*: ContainerEventType @@ -65,10 +65,10 @@ type bpos: seq[CursorPosition] highlights: seq[Highlight] parent*: Container - sourcepair*: Container istream*: Stream ostream*: Stream process*: Pid + loadinfo*: string lines: SimpleFlexibleGrid lineshift: int numLines*: int @@ -76,14 +76,14 @@ type code*: int retry*: seq[URL] redirect*: Option[URL] - ispipe: bool hlon*: bool + sourcepair*: Container pipeto: Container redraw*: bool - sourceready*: bool cmdvalid: array[ContainerCommand, bool] + needslines*: bool -proc newBuffer*(dispatcher: Dispatcher, config: Config, source: BufferSource, ispipe = false, autoload = true): Container = +proc newBuffer*(dispatcher: Dispatcher, config: Config, source: BufferSource, title = ""): Container = let attrs = getWindowAttributes(stdout) let ostream = dispatcher.forkserver.ostream let istream = dispatcher.forkserver.istream @@ -96,7 +96,7 @@ proc newBuffer*(dispatcher: Dispatcher, config: Config, source: BufferSource, is result = Container( source: source, attrs: attrs, width: attrs.width, height: attrs.height - 1, contenttype: source.contenttype, - ispipe: ispipe + title: title ) result.cmdvalid[BUFFER_READY] = true istream.sread(result.process) @@ -179,8 +179,6 @@ func maxScreenWidth(container: Container): int = func getTitle*(container: Container): string = if container.title != "": return container.title - if container.ispipe: - return "*pipe*" return container.source.location.serialize(excludepassword = true) func currentLineWidth(container: Container): int = @@ -268,7 +266,7 @@ proc sendCursorPosition*(container: Container) = proc setFromY*(container: Container, y: int) {.jsfunc.} = if container.pos.fromy != y: container.pos.fromy = max(min(y, container.maxfromy), 0) - container.requestLines() + container.needslines = true container.redraw = true proc setFromX*(container: Container, x: int) {.jsfunc.} = @@ -511,7 +509,7 @@ proc popCursorPos*(container: Container, nojump = false) = container.updateCursor() if not nojump: container.sendCursorPosition() - container.requestLines() + container.needslines = true macro proxy(fun: typed) = let name = fun[0] # sym @@ -558,10 +556,13 @@ proc cursorPrevMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} proc load*(container: Container) = container.writeCommand(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.writeCommand(GOTO_ANCHOR, anchor) @@ -574,9 +575,10 @@ proc readSuccess*(container: Container, s: string) {.proxy.} = discard proc reshape*(container: Container, noreq = false) {.jsfunc.} = container.writeCommand(RENDER) container.expect(RESHAPE) + container.expect(SET_NUM_LINES) container.expect(JUMP) if not noreq: - container.requestLines() + container.needslines = true proc dupeBuffer*(dispatcher: Dispatcher, container: Container, config: Config, location = none(URL), contenttype = none(string)): Container = let source = BufferSource( @@ -585,7 +587,7 @@ proc dupeBuffer*(dispatcher: Dispatcher, container: Container, config: Config, l contenttype: if contenttype.isSome: contenttype else: container.contenttype, clonepid: container.process, ) - container.pipeto = dispatcher.newBuffer(config, source, container.ispipe) + container.pipeto = dispatcher.newBuffer(config, source, container.title) container.writeCommand(GET_SOURCE) container.expect(SOURCE_READY) return container.pipeto @@ -611,12 +613,29 @@ proc clearSearchHighlights*(container: Container) = proc handleCommand(container: Container, cmd: ContainerCommand, len: int): ContainerEvent = if not container.cmdvalid[cmd]: let len = len - sizeof(cmd) - #TODO TODO TODO this is very dumb + #TODO TODO TODO for i in 0 ..< len: discard container.istream.readChar() - return ContainerEvent(t: INVALID_COMMAND) + if cmd != RESHAPE: + 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) @@ -629,6 +648,8 @@ proc handleCommand(container: Container, cmd: ContainerCommand, len: int): Conta 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: @@ -643,8 +664,10 @@ proc handleCommand(container: Container, cmd: ContainerCommand, len: int): Conta 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 @@ -652,10 +675,8 @@ proc handleCommand(container: Container, cmd: ContainerCommand, len: int): Conta return ContainerEvent(t: FAIL) return ContainerEvent(t: SUCCESS) of ANCHOR_FOUND: - container.cmdvalid[ANCHOR_FAIL] = false return ContainerEvent(t: ANCHOR) of ANCHOR_FAIL: - container.cmdvalid[ANCHOR_FOUND] = false return ContainerEvent(t: FAIL) of READ_LINE: var prompt, str: string @@ -696,6 +717,8 @@ proc handleCommand(container: Container, cmd: ContainerCommand, len: int): Conta container.pipeto.load() container.pipeto = nil of RESHAPE: + container.needslines = true + if container.needslines: container.requestLines() # Synchronously read all lines in the buffer. diff --git a/src/config/bufferconfig.nim b/src/config/bufferconfig.nim index 6e8d19e6..78f3b87e 100644 --- a/src/config/bufferconfig.nim +++ b/src/config/bufferconfig.nim @@ -1,5 +1,4 @@ import config/config -import css/sheet type BufferConfig* = object userstyle*: string diff --git a/src/config/config.nim b/src/config/config.nim index c8002289..ff4f0b45 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -102,6 +102,8 @@ proc readUserStylesheet(dir, file: string): string = proc parseConfig(config: Config, dir: string, t: TomlValue) = for k, v in t: case k + of "startup": + config.startup = v.s of "headless": config.headless = v.b of "page": diff --git a/src/config/toml.nim b/src/config/toml.nim index 7f743f09..dd0b4fe1 100644 --- a/src/config/toml.nim +++ b/src/config/toml.nim @@ -334,7 +334,6 @@ proc consumeNumber(state: var TomlParser, c: char): TomlValue = let val = parseFloat64(repr) return TomlValue(vt: VALUE_FLOAT, f: val) - eprint repr let val = parseInt64(repr) return TomlValue(vt: VALUE_INTEGER, i: val) diff --git a/src/display/client.nim b/src/display/client.nim index 05a5a0fc..36761d56 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -36,6 +36,7 @@ import types/url type Client* = ref ClientObj ClientObj* = object + alive: bool attrs: WindowAttributes dispatcher: Dispatcher feednext: bool @@ -120,43 +121,52 @@ proc command(client: Client, src: string) = client.console.container.cursorLastLine() proc quit(client: Client, code = 0) {.jsfunc.} = - client.pager.quit() + if client.alive: + client.alive = false + client.pager.quit() quit(code) proc feedNext(client: Client) {.jsfunc.} = client.feednext = true +proc alert(client: Client, msg: string) {.jsfunc.} = + client.pager.alert(msg) + proc input(client: Client) = restoreStdin(client.console.tty.getFileHandle()) - let c = client.console.readChar() - client.s &= c - if client.pager.lineedit.isSome: - let edit = client.pager.lineedit.get - client.line = edit - if edit.escNext: - edit.escNext = false - if edit.write(client.s): - client.s = "" - else: - let action = getLinedAction(client.config, client.s) - if action == "": + while true: + let c = client.console.readChar() + client.s &= c + if client.pager.lineedit.isSome: + let edit = client.pager.lineedit.get + client.line = edit + if edit.escNext: + edit.escNext = false if edit.write(client.s): client.s = "" - else: - client.feedNext = true - elif not client.feedNext: - client.evalJSFree(action, "<command>") - if client.pager.lineedit.isNone: - client.line = nil + else: + let action = getLinedAction(client.config, client.s) + if action == "": + if edit.write(client.s): + client.s = "" + else: + client.feedNext = true + elif not client.feedNext: + client.evalJSFree(action, "<command>") + if client.pager.lineedit.isNone: + client.line = nil + if not client.feedNext: + client.pager.updateReadLine() + else: + let action = getNormalAction(client.config, client.s) + client.evalJSFree(action, "<command>") if not client.feedNext: - client.pager.updateReadLine() - else: - let action = getNormalAction(client.config, client.s) - client.evalJSFree(action, "<command>") - if not client.feedNext: - client.s = "" - else: - client.feedNext = false + client.pager.refreshStatusMsg() + if not client.feedNext: + client.s = "" + break + else: + client.feedNext = false proc setTimeout[T: JSObject|string](client: Client, handler: T, timeout = 0): int {.jsfunc.} = let id = client.timeoutid @@ -216,6 +226,15 @@ proc clearInterval(client: Client, id: int) {.jsfunc.} = let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint proc acceptBuffers(client: Client) = + while client.pager.unreg.len > 0: + let (pid, stream) = client.pager.unreg.pop() + let fd = stream.source.getFd() + if int(fd) in client.fdmap: + client.selector.unregister(fd) + client.fdmap.del(int(fd)) + else: + client.pager.procmap.del(pid) + stream.close() var i = 0 while i < client.pager.procmap.len: let stream = client.ssock.acceptSocketStream() @@ -234,12 +253,20 @@ proc acceptBuffers(client: Client) = #TODO print an error? stream.close() +proc log(console: Console, ss: varargs[string]) {.jsfunc.} = + for i in 0..<ss.len: + console.err.write(ss[i]) + if i != ss.high: + console.err.write(' ') + console.err.write('\n') + console.err.flush() + proc inputLoop(client: Client) = let selector = client.selector selector.registerHandle(int(client.console.tty.getFileHandle()), {Read}, nil) let sigwinch = selector.registerSignal(int(SIGWINCH), nil) + client.acceptBuffers() while true: - client.acceptBuffers() let events = client.selector.select(-1) for event in events: if Read in event.events: @@ -249,13 +276,11 @@ proc inputLoop(client: Client) = else: let container = client.fdmap[event.fd] if not client.pager.handleEvent(container): - disableRawMode() - for msg in client.pager.status: - eprint msg client.quit(1) if Error in event.events: - eprint "Error", event #TODO handle errors + client.alert("Error in selected fds, check console") + client.console.log($event) if Signal in event.events: if event.fd == sigwinch: client.attrs = getWindowAttributes(client.console.tty) @@ -272,7 +297,9 @@ proc inputLoop(client: Client) = if client.pager.scommand != "": client.command(client.pager.scommand) client.pager.scommand = "" + client.pager.refreshStatusMsg() client.pager.draw() + client.acceptBuffers() #TODO this is dumb proc readFile(client: Client, path: string): string {.jsfunc.} = @@ -292,11 +319,12 @@ proc newConsole(pager: Pager, tty: File): Console = if pipe(pipefd) == -1: raise newException(Defect, "Failed to open console pipe.") let url = newURL("javascript:console.show()") - result.container = pager.readPipe0(some("text/plain"), pipefd[0], option(url)) + result.container = pager.readPipe0(some("text/plain"), pipefd[0], option(url), "Browser console") var f: File if not open(f, pipefd[1], fmWrite): raise newException(Defect, "Failed to open file for console pipe.") result.err = newFileStream(f) + result.err.writeLine("Type (M-c) console.hide() to return to buffer mode.") result.pager = pager result.tty = tty pager.registerContainer(result.container) @@ -307,12 +335,11 @@ proc dumpBuffers(client: Client) = client.acceptBuffers() for container in client.pager.containers: container.load() - for msg in client.pager.status: - eprint msg let ostream = newFileStream(stdout) for container in client.pager.containers: container.reshape(true) client.pager.drawBuffer(container, ostream) + client.pager.dumpAlerts() stdout.close() proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], dump: bool) = @@ -329,6 +356,7 @@ proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], du client.selector = newSelector[Container]() client.pager.launchPager(tty) client.console = newConsole(client.pager, tty) + client.alive = true addExitProc((proc() = client.quit())) if client.config.startup != "": let s = if fileExists(client.config.startup): @@ -357,14 +385,6 @@ proc nimGCStats(client: Client): string {.jsfunc.} = proc jsGCStats(client: Client): string {.jsfunc.} = return client.jsrt.getMemoryUsage() -proc log(console: Console, ss: varargs[string]) {.jsfunc.} = - for i in 0..<ss.len: - console.err.write(ss[i]) - if i != ss.high: - console.err.write(' ') - console.err.write('\n') - console.err.flush() - proc show(console: Console) {.jsfunc.} = if console.pager.container != console.container: console.prev = console.pager.container diff --git a/src/display/pager.nim b/src/display/pager.nim index d04981a0..fdce5cc0 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -1,3 +1,4 @@ +import net import options import os import streams @@ -16,6 +17,7 @@ import io/request import io/term import io/window import ips/forkserver +import ips/socketstream import js/javascript import js/regex import types/buffersource @@ -30,7 +32,7 @@ type SEARCH_B, ISEARCH_F, ISEARCH_B Pager* = ref object - attrs: WindowAttributes + alerts: seq[string] commandMode*: bool container*: Container dispatcher*: Dispatcher @@ -42,10 +44,10 @@ type regex: Option[Regex] iregex: Option[Regex] reverseSearch: bool - status*: seq[string] - statusmsg*: FixedGrid + statusgrid*: FixedGrid tty: File procmap*: Table[Pid, Container] + unreg*: seq[(Pid, SocketStream)] icpos: CursorPosition display: FixedGrid redraw*: bool @@ -111,23 +113,15 @@ proc searchPrev(pager: Pager) {.jsfunc.} = else: pager.container.cursorNextMatch(pager.regex.get, true) -#TODO get rid of this -proc statusMode(pager: Pager) = - pager.term.setCursor(0, pager.attrs.height - 1) - pager.term.resetFormat2() - pager.term.eraseLine() - -#TODO ditto -proc setLineEdit*(pager: Pager, edit: LineEdit, mode: LineMode) = - pager.statusMode() - edit.writeStart() - pager.term.flush() +proc setLineEdit(pager: Pager, edit: LineEdit, mode: LineMode) = pager.lineedit = some(edit) pager.linemode = mode proc clearLineEdit(pager: Pager) = pager.lineedit = none(LineEdit) +func attrs(pager: Pager): WindowAttributes = pager.term.attrs + proc searchForward(pager: Pager) {.jsfunc.} = pager.setLineEdit(readLine("/", pager.attrs.width, config = pager.config, tty = pager.tty), SEARCH_F) @@ -142,16 +136,13 @@ proc isearchBackward(pager: Pager) {.jsfunc.} = pager.container.pushCursorPos() pager.setLineEdit(readLine("?", pager.attrs.width, config = pager.config, tty = pager.tty), ISEARCH_B) -func attrs*(pager: Pager): WindowAttributes = pager.term.attrs - proc newPager*(config: Config, attrs: WindowAttributes, dispatcher: Dispatcher): Pager = let pager = Pager( dispatcher: dispatcher, config: config, - attrs: attrs, display: newFixedGrid(attrs.width, attrs.height - 1), - statusmsg: newFixedGrid(attrs.width), - term: newTerminal(stdout, config) + statusgrid: newFixedGrid(attrs.width), + term: newTerminal(stdout, config, attrs) ) return pager @@ -160,15 +151,20 @@ proc launchPager*(pager: Pager, tty: File) = if tty != nil: pager.term.start(tty) +proc dumpAlerts*(pager: Pager) = + for msg in pager.alerts: + eprint msg + proc quit*(pager: Pager, code = 0) = pager.term.quit() + pager.dumpAlerts() proc clearDisplay(pager: Pager) = pager.display = newFixedGrid(pager.display.width, pager.display.height) proc buffer(pager: Pager): Container {.jsfget, inline.} = pager.container -proc refreshDisplay*(pager: Pager, container = pager.container) = +proc refreshDisplay(pager: Pager, container = pager.container) = var r: Rune var by = 0 pager.clearDisplay() @@ -219,36 +215,29 @@ proc refreshDisplay*(pager: Pager, container = pager.container) = pager.display[dls + i - startw].format = hlformat inc by -func generateStatusMessage*(pager: Pager): string = - var format = newFormat() - var w = 0 - for cell in pager.statusmsg: - result &= pager.term.processFormat(format, cell.format) - result &= cell.str - w += cell.width() - if w < pager.statusmsg.width: - result &= EL() - proc clearStatusMessage(pager: Pager) = - pager.statusmsg = newFixedGrid(pager.statusmsg.width) + pager.statusgrid = newFixedGrid(pager.statusgrid.width) proc writeStatusMessage(pager: Pager, str: string, format: Format = Format()) = pager.clearStatusMessage() var i = 0 for r in str.runes: i += r.width() - if i >= pager.statusmsg.len: - pager.statusmsg[^1].str = "$" + if i >= pager.statusgrid.len: + pager.statusgrid[^1].str = "$" break - pager.statusmsg[i].str &= r - pager.statusmsg[i].format = format + pager.statusgrid[i].str &= r + pager.statusgrid[i].format = format proc refreshStatusMsg*(pager: Pager) = let container = pager.container - if pager.status.len > 0: - pager.writeStatusMessage(pager.status[0]) - pager.status.delete(0) - elif container != nil: + if container == nil: return + if container.loadinfo != "": + pager.writeStatusMessage(container.loadinfo) + elif pager.alerts.len > 0: + pager.writeStatusMessage(pager.alerts[0]) + pager.alerts.delete(0) + else: var msg = $(container.cursory + 1) & "/" & $container.numLines & " (" & $container.atPercentOf() & "%) " & "<" & container.getTitle() & ">" if container.hovertext.len > 0: @@ -257,17 +246,6 @@ proc refreshStatusMsg*(pager: Pager) = format.reverse = true pager.writeStatusMessage(msg, format) -#TODO get rid of this -func generateStatusOutput(pager: Pager): string = - return pager.generateStatusMessage() - -#TODO ditto -proc displayStatus*(pager: Pager) = - if pager.lineedit.isNone: - pager.statusMode() - print(pager.generateStatusOutput()) - stdout.flushFile() - proc drawBuffer*(pager: Pager, container: Container, ostream: Stream) = var format = newFormat() for line in container.readLines: @@ -298,14 +276,15 @@ proc draw*(pager: Pager) = pager.term.hideCursor() if pager.redraw or pager.container != nil and pager.container.redraw: pager.refreshDisplay() - pager.term.outputGrid(pager.display) - pager.refreshStatusMsg() - pager.displayStatus() + pager.term.writeGrid(pager.display) + if pager.lineedit.isSome: + pager.term.writeGrid(pager.lineedit.get.generateOutput(), 0, pager.attrs.height - 1) + else: + pager.term.writeGrid(pager.statusgrid, 0, pager.attrs.height - 1) + pager.term.outputGrid() if pager.lineedit.isSome: - pager.statusMode() - pager.lineedit.get.writePrompt() - pager.lineedit.get.fullRedraw() - elif pager.container != nil: + pager.term.setCursor(pager.lineedit.get.getCursorX(), pager.container.attrs.height - 1) + else: pager.term.setCursor(pager.container.acursorx, pager.container.acursory) pager.term.showCursor() pager.term.flush() @@ -358,12 +337,11 @@ proc nextBuffer*(pager: Pager): bool {.jsfunc.} = return true return false -proc setStatusMessage*(pager: Pager, msg: string) = - pager.status.add(msg) - pager.refreshStatusMsg() +proc alert*(pager: Pager, msg: string) {.jsfunc.} = + pager.alerts.add(msg) proc lineInfo(pager: Pager) {.jsfunc.} = - pager.setStatusMessage(pager.container.lineInfo()) + pager.alert(pager.container.lineInfo()) proc deleteContainer(pager: Pager, container: Container) = if container.parent == nil and @@ -399,14 +377,13 @@ proc deleteContainer(pager: Pager, container: Container) = pager.setContainer(nil) container.parent = nil container.children.setLen(0) - container.istream.close() - container.ostream.close() + pager.unreg.add((container.process, SocketStream(container.istream))) pager.dispatcher.forkserver.removeChild(container.process) proc discardBuffer*(pager: Pager) {.jsfunc.} = if pager.container == nil or pager.container.parent == nil and pager.container.children.len == 0: - pager.setStatusMessage("Cannot discard last buffer!") + pager.alert("Cannot discard last buffer!") else: pager.deleteContainer(pager.container) @@ -424,10 +401,9 @@ proc toggleSource*(pager: Pager) {.jsfunc.} = pager.addContainer(container) proc windowChange*(pager: Pager, attrs: WindowAttributes) = - pager.attrs = attrs - pager.display = newFixedGrid(attrs.width, attrs.height - 1) - pager.statusmsg = newFixedGrid(attrs.width) pager.term.windowChange(attrs) + pager.display = newFixedGrid(attrs.width, attrs.height - 1) + pager.statusgrid = newFixedGrid(attrs.width) for container in pager.containers: container.windowChange(attrs) @@ -483,25 +459,25 @@ proc loadURL*(pager: Pager, url: string, ctype = none(string)) = if localurl.isSome: # attempt to load local file urls.add(localurl.get) if urls.len == 0: - pager.setStatusMessage("Invalid URL " & url) + pager.alert("Invalid URL " & url) else: let prevc = pager.container pager.gotoURL(newRequest(urls.pop()), ctype = ctype) if pager.container != prevc: pager.container.retry = urls -proc readPipe0*(pager: Pager, ctype: Option[string], fd: FileHandle, location: Option[URL]): Container = +proc readPipe0*(pager: Pager, ctype: Option[string], fd: FileHandle, location: Option[URL], title: string): Container = let source = BufferSource( t: LOAD_PIPE, fd: fd, contenttype: some(ctype.get("text/plain")), location: location.get(newURL("file://-")) ) - let container = pager.dispatcher.newBuffer(pager.config, source, ispipe = true) + let container = pager.dispatcher.newBuffer(pager.config, source, title) return container proc readPipe*(pager: Pager, ctype: Option[string], fd: FileHandle) = - let container = pager.readPipe0(ctype, fd, none(URL)) + let container = pager.readPipe0(ctype, fd, none(URL), "*pipe*") pager.addContainer(container) proc command(pager: Pager) {.jsfunc.} = @@ -612,11 +588,12 @@ proc handleEvent*(pager: Pager, container: Container): bool = if container.retry.len > 0: pager.gotoURL(newRequest(container.retry.pop()), ctype = container.contenttype) else: - pager.setStatusMessage("Couldn't load " & $container.source.location & " (error code " & $container.code & ")") + pager.alert("Couldn't load " & $container.source.location & " (error code " & $container.code & ")") if pager.container == nil: return false of SUCCESS: container.reshape() + pager.container.loadinfo = "" if container.replace != nil: container.children.add(container.replace.children) for child in container.children: @@ -634,24 +611,24 @@ proc handleEvent*(pager: Pager, container: Container): bool = pager.authorize() of REDIRECT: let redirect = container.redirect.get - pager.setStatusMessage("Redirecting to " & $redirect) + pager.alert("Redirecting to " & $redirect) pager.gotoURL(newRequest(redirect), some(pager.container.source.location), replace = pager.container) of ANCHOR: pager.addContainer(pager.dupeContainer(container, container.redirect)) of NO_ANCHOR: - pager.setStatusMessage("Couldn't find anchor " & container.redirect.get.anchor) + pager.alert("Couldn't find anchor " & container.redirect.get.anchor) of UPDATE: if container == pager.container: pager.redraw = true of READ_LINE: if container == pager.container: - pager.setLineEdit(readLine(event.prompt, pager.statusmsg.width, current = event.value, hide = event.password, config = pager.config, tty = pager.tty), BUFFER) + pager.setLineEdit(readLine(event.prompt, pager.attrs.width, current = event.value, hide = event.password, config = pager.config, tty = pager.tty), BUFFER) of OPEN: pager.gotoURL(event.request, some(container.source.location)) - of INVALID_COMMAND: - if container == pager.container: - if pager.status.len == 0: - pager.setStatusMessage("Invalid command from buffer") + of INVALID_COMMAND: discard + of STATUS: + if pager.container == container: + pager.refreshStatusMsg() of NO_EVENT: discard return true diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim index 2d7da512..eb812122 100644 --- a/src/io/lineedit.nim +++ b/src/io/lineedit.nim @@ -5,6 +5,7 @@ import sequtils import sugar import bindings/quickjs +import buffer/cell import config/config import js/javascript import utils/twtstr @@ -16,6 +17,7 @@ type LineEdit* = ref object news*: seq[Rune] prompt*: string + promptw: int current: string state*: LineEditState escNext*: bool @@ -81,6 +83,25 @@ proc begin0(state: LineEdit) = proc space(edit: LineEdit, i: int) = print(' '.repeat(i)) +proc generateOutput*(edit: LineEdit): FixedGrid = + result = newFixedGrid(edit.maxlen) + let os = edit.news.substr(edit.shift, edit.shift + edit.displen) + var x = 0 + for r in edit.prompt.runes(): + result[x].str &= $r + x += r.lwidth() + if edit.hide: + for r in os: + result[x].str = "*" + x += r.lwidth() + else: + for r in os: + result[x].str &= $r + x += r.lwidth() + +proc getCursorX*(edit: LineEdit): int = + return edit.promptw + edit.news.lwidth(edit.shift, edit.cursor) + proc redraw(state: LineEdit) = var dispw = state.news.lwidth(state.shift, state.shift + state.displen) if state.shift + state.displen > state.news.len: @@ -282,6 +303,7 @@ proc readLine*(prompt: string, termwidth: int, current = "", tty: File): LineEdit = new(result) result.prompt = prompt + result.promptw = prompt.lwidth() result.current = current result.news = current.toRunes() result.cursor = result.news.len diff --git a/src/io/term.nim b/src/io/term.nim index 518990e3..f892761b 100644 --- a/src/io/term.nim +++ b/src/io/term.nim @@ -9,7 +9,6 @@ import buffer/cell import config/config import io/window import types/color -import utils/twtstr #TODO switch from termcap... @@ -40,6 +39,7 @@ type infile: File outfile: File cleared: bool + canvas: FixedGrid prevgrid: FixedGrid attrs*: WindowAttributes mincontrast: float @@ -273,26 +273,9 @@ proc processFormat*(term: Terminal, format: var Format, cellf: Format): string = proc windowChange*(term: Terminal, attrs: WindowAttributes) = term.attrs = attrs + term.canvas = newFixedGrid(attrs.width, attrs.height) term.cleared = false -proc getCursorPos(term: Terminal): (int, int) = - term.write(CSI("6n")) - term.flush() - var c = term.infile.readChar() - while true: - while c != '\e': - c = term.infile.readChar() - c = term.infile.readChar() - if c == '[': break - var tmp = "" - while (let c = term.infile.readChar(); c != ';'): - tmp &= c - result[1] = parseInt32(tmp) - tmp = "" - while (let c = term.infile.readChar(); c != 'R'): - tmp &= c - result[0] = parseInt32(tmp) - func generateFullOutput(term: Terminal, grid: FixedGrid): string = var format = newFormat() result &= term.cursorGoto(0, 0) @@ -304,7 +287,8 @@ func generateFullOutput(term: Terminal, grid: FixedGrid): string = result &= cell.str result &= SGR() result &= term.clearEnd() - result &= "\r\n" + if y != grid.height - 1: + result &= "\r\n" func generateSwapOutput(term: Terminal, grid: FixedGrid): string = var format = newFormat() @@ -343,14 +327,19 @@ proc hideCursor*(term: Terminal) = proc showCursor*(term: Terminal) = term.outfile.showCursor() -proc outputGrid*(term: Terminal, grid: FixedGrid) = +proc writeGrid*(term: Terminal, grid: FixedGrid, x = 0, y = 0) = + for ly in y ..< y + grid.height: + for lx in x ..< x + grid.width: + term.canvas[ly * term.canvas.width + lx] = grid[(ly - y) * grid.width + (lx - x)] + +proc outputGrid*(term: Terminal) = term.outfile.write(SGR()) if not term.cleared: - term.outfile.write(term.generateFullOutput(grid)) + term.outfile.write(term.generateFullOutput(term.canvas)) term.cleared = true else: - term.outfile.write(term.generateSwapOutput(grid)) - term.prevgrid = grid + term.outfile.write(term.generateSwapOutput(term.canvas)) + term.prevgrid = term.canvas when defined(posix): import posix @@ -359,10 +348,10 @@ when defined(posix): # see https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html var orig_termios: Termios var stdin_fileno: FileHandle - proc disableRawMode*() {.noconv.} = + proc disableRawMode() {.noconv.} = discard tcSetAttr(stdin_fileno, TCSAFLUSH, addr orig_termios) - proc enableRawMode*(fileno: FileHandle) = + proc enableRawMode(fileno: FileHandle) = stdin_fileno = fileno discard tcGetAttr(fileno, addr orig_termios) var raw = orig_termios @@ -385,6 +374,12 @@ when defined(posix): discard fcntl(fileno, F_SETFL, orig_flags) stdin_unblocked = false else: + proc disableRawMode() = + discard + + proc enableRawMode(fileno: FileHandle) = + discard + proc unblockStdin*(): cint = discard @@ -396,8 +391,7 @@ proc isatty*(term: Terminal): bool = proc quit*(term: Terminal) = if term.infile != nil and term.isatty(): - when defined(posix): - disableRawMode() + disableRawMode() if term.smcup: term.write(term.disableAltScreen()) else: @@ -454,15 +448,15 @@ proc start*(term: Terminal, infile: File) = term.infile = infile assert term.outfile.getFileHandle().setInheritable(false) assert term.infile.getFileHandle().setInheritable(false) - when defined(posix): - if term.isatty(): - enableRawMode(infile.getFileHandle()) + if term.isatty(): + enableRawMode(infile.getFileHandle()) term.detectTermAttributes() if term.smcup: term.write(term.enableAltScreen()) -proc newTerminal*(outfile: File, config: Config): Terminal = +proc newTerminal*(outfile: File, config: Config, attrs: WindowAttributes): Terminal = let term = new Terminal term.outfile = outfile term.config = config + term.windowChange(attrs) return term diff --git a/src/ips/forkserver.nim b/src/ips/forkserver.nim index 3e93402d..1c29b120 100644 --- a/src/ips/forkserver.nim +++ b/src/ips/forkserver.nim @@ -33,6 +33,7 @@ proc newFileLoader*(forkserver: ForkServer, defaultHeaders: HeaderList = Default proc removeChild*(forkserver: Forkserver, pid: Pid) = forkserver.ostream.swrite(REMOVE_CHILD) + forkserver.ostream.swrite(pid) forkserver.ostream.flush() proc forkLoader(ctx: var ForkServerContext, defaultHeaders: HeaderList): FileLoader = diff --git a/src/ips/socketstream.nim b/src/ips/socketstream.nim index 3918fe2c..6ce9bd88 100644 --- a/src/ips/socketstream.nim +++ b/src/ips/socketstream.nim @@ -16,7 +16,7 @@ proc sockReadData(s: Stream, buffer: pointer, len: int): int = let s = SocketStream(s) result = s.source.recv(buffer, len) if result < 0: - raise newException(Defect, "Failed to read data (code " & $osLastError() & ")") + raise newException(IOError, "Failed to read data (code " & $osLastError() & ")") elif result < len: s.isend = true |