diff options
author | bptato <nincsnevem662@gmail.com> | 2022-11-20 17:36:21 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2022-11-20 17:36:21 +0100 |
commit | 7d6c75e4c737e51f997f2ac02001fad1a5627ca2 (patch) | |
tree | f315fb6e576e1b68e254be26d30d61473c91b3c8 /src/display | |
parent | 06fbb9bc967e11151fdd1311437248cf9bc25706 (diff) | |
download | chawan-7d6c75e4c737e51f997f2ac02001fad1a5627ca2.tar.gz |
Terminal refactorings
Diffstat (limited to 'src/display')
-rw-r--r-- | src/display/client.nim | 45 | ||||
-rw-r--r-- | src/display/pager.nim | 131 |
2 files changed, 83 insertions, 93 deletions
diff --git a/src/display/client.nim b/src/display/client.nim index 54fca3c1..e94a4d4f 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -8,6 +8,8 @@ import terminal when defined(posix): import posix +import std/exitprocs + import bindings/quickjs import buffer/container import css/sheet @@ -19,15 +21,15 @@ import io/lineedit import io/loader import io/request import io/term +import io/window import js/javascript import types/cookie import types/url -import utils/twtstr type Client* = ref ClientObj ClientObj* = object - attrs: TermAttributes + attrs: WindowAttributes feednext: bool s: string errormessage: string @@ -107,11 +109,7 @@ proc command(client: Client, src: string) = client.console.container.cursorLastLine() proc quit(client: Client, code = 0) {.jsfunc.} = - if stdout.isatty(): - print(HVP(getTermAttributes(stdout).height, 1)) - print('\n') - print(EL()) - stdout.showCursor() + client.pager.quit() when defined(posix): assert kill(client.loader.process, cint(SIGTERM)) == 0 quit(code) @@ -150,11 +148,6 @@ proc input(client: Client) = client.s = "" else: client.feedNext = false - if client.pager.container != nil: - if client.pager.lineedit.isNone: - client.pager.refreshStatusMsg() - client.pager.displayStatus() - client.pager.displayCursor() proc setTimeout[T: JSObject|string](client: Client, handler: T, timeout = 0): int {.jsfunc.} = let id = client.timeoutid @@ -211,8 +204,12 @@ proc clearInterval(client: Client, id: int) {.jsfunc.} = client.interval_fdis.del(interval.fdi) client.intervals.del(id) +let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint + proc inputLoop(client: Client) = - client.pager.selector.registerHandle(int(client.console.tty.getFileHandle()), {Read}, nil) + let selector = client.pager.selector + selector.registerHandle(int(client.console.tty.getFileHandle()), {Read}, nil) + let sigwinch = selector.registerSignal(int(SIGWINCH), nil) while true: let events = client.pager.selector.select(-1) for event in events: @@ -226,6 +223,9 @@ proc inputLoop(client: Client) = let timeout = client.timeouts[id] timeout.handler() client.clearTimeout(id) + elif event.fd == sigwinch: + client.attrs = getWindowAttributes(client.console.tty) + client.pager.windowChange(client.attrs) else: let container = client.pager.fdmap[FileHandle(event.fd)] if not client.pager.handleEvent(container): @@ -236,11 +236,7 @@ proc inputLoop(client: Client) = if client.pager.scommand != "": client.command(client.pager.scommand) client.pager.scommand = "" - if client.pager.lineedit.isNone and client.pager.redraw or client.pager.container.redraw: - client.pager.refreshDisplay(client.pager.container) - client.pager.draw() - client.pager.redraw = false - client.pager.container.redraw = false + client.pager.draw() #TODO this is dumb proc readFile(client: Client, path: string): string {.jsfunc.} = @@ -254,6 +250,10 @@ proc writeFile(client: Client, path: string, content: string) {.jsfunc.} = writeFile(path, content) proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], dump: bool) = + let pid = getpid() + addExitProc((proc() = + if pid == getpid(): + client.quit())) if client.config.startup != "": let s = readFile(client.config.startup) client.console.err = newFileStream(stderr) @@ -272,15 +272,14 @@ proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], du client.pager.loadURL(page, ctype = ctype) if stdout.isatty() and not dump: - when defined(posix): - enableRawMode(client.console.tty.getFileHandle()) client.inputLoop() else: for msg in client.pager.status: eprint msg + let ostream = newFileStream(stdout) for container in client.pager.containers: - container.render() - container.drawBuffer() + container.render(true) + client.pager.drawBuffer(container, ostream) stdout.close() client.quit() @@ -328,7 +327,7 @@ proc sleep(client: Client, millis: int) {.jsfunc.} = proc newClient*(config: Config): Client = new(result) result.config = config - result.attrs = getTermAttributes(stdout) + result.attrs = getWindowAttributes(stdout) result.loader = newFileLoader() var tty: File if stdin.isatty(): diff --git a/src/display/pager.nim b/src/display/pager.nim index d4a2dc99..456e6474 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -3,7 +3,6 @@ import os import selectors import streams import tables -import terminal import unicode import buffer/buffer @@ -13,6 +12,7 @@ import config/config import io/lineedit import io/request import io/term +import io/window import js/javascript import js/regex import types/url @@ -24,7 +24,7 @@ type SEARCH_B, ISEARCH_F, ISEARCH_B Pager* = ref object - attrs: TermAttributes + attrs: WindowAttributes commandMode*: bool container*: Container lineedit*: Option[LineEdit] @@ -42,9 +42,8 @@ type fdmap*: Table[FileHandle, Container] icpos: CursorPosition display: FixedGrid - bheight*: int - bwidth*: int redraw*: bool + term*: Terminal iterator containers*(pager: Pager): Container = if pager.container != nil: @@ -135,15 +134,21 @@ proc isearchBackward(pager: Pager) {.jsfunc.} = pager.container.pushCursorPos() pager.setLineEdit(readLine("?", pager.attrs.width, config = pager.config, tty = pager.tty), ISEARCH_B) -proc newPager*(config: Config, attrs: TermAttributes, tty: File): Pager = +func attrs*(pager: Pager): WindowAttributes = pager.term.attrs +func bwidth(pager: Pager): int = pager.attrs.width +func bheight(pager: Pager): int = pager.attrs.height - 1 + +proc newPager*(config: Config, attrs: WindowAttributes, tty: File): Pager = new(result) result.config = config result.attrs = attrs result.tty = tty result.selector = newSelector[Container]() - result.bwidth = attrs.width - 1 # writing to the last column is a bad idea it seems - result.bheight = attrs.height - 1 result.display = newFixedGrid(result.bwidth, result.bheight) + result.term = newTerminal(tty, stdout) + +proc quit*(pager: Pager, code = 0) = + pager.term.quit() proc clearDisplay(pager: Pager) = pager.display = newFixedGrid(pager.bwidth, pager.bheight) @@ -177,7 +182,7 @@ proc refreshDisplay*(pager: Pager, container = pager.container) = let pw = w fastRuneAt(line.str, i, r) w += r.width() - if w > container.fromx + pager.bwidth: + if w > container.fromx + pager.display.width: break # die on exceeding the width limit if nf.pos != -1 and nf.pos <= pw: cf = nf @@ -186,13 +191,13 @@ proc refreshDisplay*(pager: Pager, container = pager.container) = if cf.pos != -1: pager.display[dls + k].format = cf.format let tk = k + r.width() - while k < tk and k < pager.bwidth - 1: + while k < tk and k < pager.display.width - 1: inc k # Finally, override cell formatting for highlighted cells. - let hls = container.findHighlights(by) + let hls = container.findHighlights(container.fromy + by) let aw = container.width - (startw - container.fromx) # actual width for hl in hls: - let area = hl.colorArea(by, startw .. startw + aw) + let area = hl.colorArea(container.fromy + by, startw .. startw + aw) for i in area: pager.display[dls + i - startw].format = hlformat inc by @@ -201,7 +206,7 @@ func generateStatusMessage*(pager: Pager): string = var format = newFormat() var w = 0 for cell in pager.statusmsg: - result &= format.processFormat(cell.format) + result &= pager.term.processFormat(format, cell.format) result &= cell.str w += cell.width() if w < pager.bwidth: @@ -239,55 +244,55 @@ func generateStatusOutput(pager: Pager): string = else: return pager.generateStatusMessage() -func generateFullOutput(pager: Pager): string = - var x = 0 - var w = 0 - var format = newFormat() - result &= HVP(1, 1) - for cell in pager.display: - if x >= pager.bwidth: - result &= EL() - result &= "\r\n" - x = 0 - w = 0 - result &= format.processFormat(cell.format) - result &= cell.str - w += cell.width() - inc x - result &= EL() - result &= "\r\n" - -proc displayCursor*(pager: Pager) = - if pager.container == nil: return - print(HVP(pager.container.acursory + 1, pager.container.acursorx + 1)) - stdout.flushFile() - proc displayStatus*(pager: Pager) = if pager.lineedit.isNone: pager.statusMode() print(pager.generateStatusOutput()) stdout.flushFile() -proc displayPage*(pager: Pager) = - stdout.hideCursor() - print(SGR()) - print(pager.generateFullOutput()) - pager.displayStatus() - pager.displayCursor() - stdout.showCursor() - if pager.lineedit.isSome: - pager.statusMode() - pager.lineedit.get.writePrompt() - pager.lineedit.get.fullRedraw() - stdout.flushFile() +proc drawBuffer*(pager: Pager, container: Container, ostream: Stream) = + var format = newFormat() + for line in container.readLines: + if line.formats.len == 0: + ostream.write(line.str & "\n") + else: + var x = 0 + var i = 0 + var s = "" + for f in line.formats: + var outstr = "" + #assert f.pos < line.str.width(), "fpos " & $f.pos & "\nstr" & line.str & "\n" + while x < f.pos: + var r: Rune + fastRuneAt(line.str, i, r) + outstr &= r + x += r.width() + s &= outstr + s &= pager.term.processFormat(format, f.format) + s &= line.str.substr(i) & pager.term.processFormat(format, newFormat()) & "\n" + ostream.write(s) + ostream.flush() proc redraw(pager: Pager) {.jsfunc.} = pager.redraw = true proc draw*(pager: Pager) = - pager.refreshDisplay() + pager.term.hideCursor() + if pager.redraw or pager.container != nil and pager.container.redraw: + pager.refreshDisplay() + pager.term.outputGrid(pager.display) pager.refreshStatusMsg() - pager.displayPage() + pager.displayStatus() + if pager.lineedit.isSome: + pager.statusMode() + pager.lineedit.get.writePrompt() + pager.lineedit.get.fullRedraw() + elif pager.container != nil: + pager.term.setCursor(pager.container.acursorx, pager.container.acursory) + pager.term.showCursor() + pager.term.flush() + pager.redraw = false + pager.container.redraw = false proc registerContainer*(pager: Pager, container: Container) = pager.fdmap[container.ifd] = container @@ -398,6 +403,11 @@ proc toggleSource*(pager: Pager) {.jsfunc.} = pager.container.sourcepair = container pager.container.children.add(container) +proc windowChange*(pager: Pager, attrs: WindowAttributes) = + pager.attrs = attrs + for container in pager.containers: + container.windowChange(attrs) + # Load request in a new buffer. proc gotoURL*(pager: Pager, request: Request, prevurl = none(URL), ctype = none(string), replace: Container = nil) = if prevurl.isnone or not prevurl.get.equals(request.url, true) or @@ -490,8 +500,7 @@ proc updateReadLineISearch(pager: Pager, linemode: LineMode) = of EDIT: let x = $lineedit.news if x != "": pager.iregex = compileSearchRegex(x) - pager.container.clearSearchHighlights() - pager.container.popCursorPos() + pager.container.popCursorPos(true) if pager.iregex.isSome: if linemode == ISEARCH_F: pager.container.cursorNextMatch(pager.iregex.get, true) @@ -499,9 +508,7 @@ proc updateReadLineISearch(pager: Pager, linemode: LineMode) = pager.container.cursorPrevMatch(pager.iregex.get, true) pager.container.hlon = true pager.container.pushCursorPos() - pager.displayPage() - pager.statusMode() - pager.lineedit.get.fullRedraw() + pager.draw() of FINISH: if pager.iregex.isSome: pager.regex = pager.iregex @@ -582,8 +589,6 @@ proc handleEvent*(pager: Pager, container: Container): bool = pager.gotoURL(newRequest(container.retry.pop()), ctype = container.contenttype) else: pager.setStatusMessage("Couldn't load " & $container.source.location & " (error code " & $container.code & ")") - pager.displayStatus() - pager.displayCursor() if pager.container == nil: return false of SUCCESS: @@ -606,28 +611,14 @@ proc handleEvent*(pager: Pager, container: Container): bool = of REDIRECT: let redirect = container.redirect.get pager.setStatusMessage("Redirecting to " & $redirect) - pager.displayStatus() - pager.displayCursor() 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.displayStatus() - pager.displayCursor() of UPDATE: if container == pager.container: pager.redraw = true - of JUMP: - if container == pager.container: - pager.refreshStatusMsg() - pager.displayStatus() - pager.displayCursor() - of STATUS: - if container == pager.container: - pager.refreshStatusMsg() - pager.displayStatus() - pager.displayCursor() of READ_LINE: if container == pager.container: pager.setLineEdit(readLine(event.prompt, pager.bwidth, current = event.value, hide = event.password, config = pager.config, tty = pager.tty), BUFFER) |