about summary refs log tree commit diff stats
path: root/src/display
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-11-25 16:38:23 +0100
committerbptato <nincsnevem662@gmail.com>2022-11-25 16:38:23 +0100
commitaaacdc350547dd5a5d637f9a054888792781895a (patch)
tree8a9c0edff45a37a4c31de254b03016e2bfb7fb60 /src/display
parent8607bb0c1e7653c1249c40fa573f94718b4d5727 (diff)
downloadchawan-aaacdc350547dd5a5d637f9a054888792781895a.tar.gz
Improve status messages, fix regressions, etc
Diffstat (limited to 'src/display')
-rw-r--r--src/display/client.nim106
-rw-r--r--src/display/pager.nim135
2 files changed, 119 insertions, 122 deletions
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