about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-10-13 17:41:02 +0200
committerbptato <nincsnevem662@gmail.com>2023-10-13 17:42:08 +0200
commit1cfe1655e1ef6f7ae69b9c87731d571e1ef3ca8c (patch)
treef62e05768e4a4205710cb9727b40b8d0cb04f632 /src
parent9ff482dd8d5b1b252e77712e9418b5b253f4bbf8 (diff)
downloadchawan-1cfe1655e1ef6f7ae69b9c87731d571e1ef3ca8c.tar.gz
Refactor Console
* merge dom.console & client.Console
* move client-specific stuff out of Console (into callbacks when necessary)
Diffstat (limited to 'src')
-rw-r--r--src/html/dom.nim43
-rw-r--r--src/html/env.nim6
-rw-r--r--src/js/console.nim58
-rw-r--r--src/local/client.nim166
-rw-r--r--src/local/container.nim9
-rw-r--r--src/local/pager.nim2
6 files changed, 168 insertions, 116 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 008a4e8a..3931ae69 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -17,6 +17,7 @@ import img/bitmap
 import img/painter
 import img/path
 import img/png
+import js/console
 import js/domexception
 import js/error
 import js/fromjs
@@ -91,7 +92,7 @@ type
 
   Window* = ref object of EventTarget
     attrs*: WindowAttributes
-    console* {.jsget.}: console
+    console* {.jsget.}: Console
     navigator* {.jsget.}: Navigator
     settings*: EnvironmentSettings
     loader*: Option[FileLoader]
@@ -110,13 +111,6 @@ type
 
   MimeTypeArray* = object
 
-  # "For historical reasons, console is lowercased."
-  # Also, for a more practical reason: so the javascript macros don't confuse
-  # this and the Client console.
-  # TODO: merge those two
-  console* = ref object
-    err*: Stream
-
   NamedNodeMap = ref object
     element: Element
     attrlist: seq[Attr]
@@ -372,7 +366,6 @@ jsDestructor(PluginArray)
 jsDestructor(MimeTypeArray)
 jsDestructor(Window)
 
-jsDestructor(console)
 jsDestructor(Element)
 jsDestructor(HTMLElement)
 jsDestructor(HTMLInputElement)
@@ -2698,34 +2691,6 @@ proc fetchClassicScript(element: HTMLScriptElement, url: URL,
       let script = createClassicScript(source, url, options, false)
       element.markAsReady(ScriptResult(t: RESULT_SCRIPT, script: script))
 
-proc log*(console: console, ss: varargs[string]) {.jsfunc.} =
-  var s = ""
-  for i in 0..<ss.len:
-    s &= ss[i]
-    console.err.write(ss[i])
-    if i != ss.high:
-      s &= ' '
-      console.err.write(' ')
-  console.err.write('\n')
-  console.err.flush()
-
-proc clear*(console: console) {.jsfunc.} =
-  # Do nothing. By design, we do not allow buffers to clear the console.
-  discard
-
-# For now, these are the same as log().
-proc debug*(console: console, ss: varargs[string]) {.jsfunc.} =
-  console.log(ss)
-
-proc error*(console: console, ss: varargs[string]) {.jsfunc.} =
-  console.log(ss)
-
-proc info*(console: console, ss: varargs[string]) {.jsfunc.} =
-  console.log(ss)
-
-proc warn*(console: console, ss: varargs[string]) {.jsfunc.} =
-  console.log(ss)
-
 proc execute*(element: HTMLScriptElement) =
   let document = element.document
   if document != element.preparationTimeDocument:
@@ -3037,10 +3002,6 @@ proc jsReflectSet(ctx: JSContext, this, val: JSValue, magic: cint): JSValue {.cd
       element.attrulgz(entry.attrname, x.get)
   return JS_DupValue(ctx, val)
 
-proc addconsoleModule*(ctx: JSContext) =
-  #TODO console should not have a prototype
-  ctx.registerType(console, nointerface = true)
-
 func getReflectFunctions(tags: set[TagType]): seq[TabGetSet] =
   for tag in tags:
     if tag in TagReflectMap:
diff --git a/src/html/env.nim b/src/html/env.nim
index 49f6bba3..24e1482d 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -7,6 +7,7 @@ import html/dom
 import html/event
 import io/promise
 import js/base64
+import js/console
 import js/domexception
 import js/error
 import js/intl
@@ -130,7 +131,7 @@ proc addScripting*(window: Window, selector: Selector[int]) =
   ctx.setGlobal(global, window)
   JS_FreeValue(ctx, global)
   ctx.addDOMExceptionModule()
-  ctx.addconsoleModule()
+  ctx.addConsoleModule()
   ctx.addNavigatorModule()
   ctx.addEventModule()
   ctx.addDOMModule()
@@ -150,9 +151,10 @@ proc runJSJobs*(window: Window) =
 proc newWindow*(scripting: bool, selector: Selector[int],
     attrs: WindowAttributes, navigate: proc(url: URL) = nil,
     loader = none(FileLoader)): Window =
+  let err = newFileStream(stderr)
   let window = Window(
     attrs: attrs,
-    console: console(err: newFileStream(stderr)),
+    console: newConsole(err),
     navigator: Navigator(),
     loader: loader,
     settings: EnvironmentSettings(
diff --git a/src/js/console.nim b/src/js/console.nim
new file mode 100644
index 00000000..c4c65975
--- /dev/null
+++ b/src/js/console.nim
@@ -0,0 +1,58 @@
+import std/streams
+
+import js/javascript
+
+type Console* = ref object
+  err*: Stream
+  clearFun: proc()
+  showFun: proc()
+  hideFun: proc()
+
+jsDestructor(Console)
+
+proc newConsole*(err: Stream, clearFun: proc() = nil, showFun: proc() = nil,
+    hideFun: proc() = nil): Console =
+  return Console(
+    err: err,
+    clearFun: clearFun,
+    showFun: showFun,
+    hideFun: hideFun
+  )
+
+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 clear(console: Console) {.jsfunc.} =
+  if console.clearFun != nil:
+    console.clearFun()
+
+# For now, these are the same as log().
+proc debug(console: Console, ss: varargs[string]) {.jsfunc.} =
+  console.log(ss)
+
+proc error(console: Console, ss: varargs[string]) {.jsfunc.} =
+  console.log(ss)
+
+proc info(console: Console, ss: varargs[string]) {.jsfunc.} =
+  console.log(ss)
+
+proc warn(console: Console, ss: varargs[string]) {.jsfunc.} =
+  console.log(ss)
+
+proc show(console: Console, ss: varargs[string]) {.jsfunc.} =
+  if console.showFun != nil:
+    console.showFun()
+
+proc hide(console: Console, ss: varargs[string]) {.jsfunc.} =
+  if console.hideFun != nil:
+    console.hideFun()
+
+proc addConsoleModule*(ctx: JSContext) =
+  #TODO console should not have a prototype
+  # "For historical reasons, console is lowercased."
+  ctx.registerType(Console, nointerface = true, name = "console")
diff --git a/src/local/client.nim b/src/local/client.nim
index 937b4a8b..788ea32f 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -26,6 +26,7 @@ import io/posixstream
 import io/promise
 import io/socketstream
 import js/base64
+import js/console
 import js/domexception
 import js/error
 import js/fromjs
@@ -42,8 +43,8 @@ import local/pager
 import server/forkserver
 import types/blob
 import types/cookie
-import types/url
 import types/opt
+import types/url
 import utils/twtstr
 import xhr/formdata
 import xhr/xmlhttprequest
@@ -54,10 +55,11 @@ type
   Client* = ref object
     alive: bool
     config {.jsget.}: Config
-    console {.jsget.}: Console
+    consoleWrapper: ConsoleWrapper
     fdmap: Table[int, Container]
     feednext: bool
     forkserver: ForkServer
+    ibuf: string
     jsctx: JSContext
     jsrt: JSRuntime
     loader: FileLoader
@@ -66,25 +68,25 @@ type
     selector: Selector[int]
     timeouts: TimeoutState
 
-  Console = ref object
-    err: Stream
-    pager: Pager
+  ConsoleWrapper = object
+    console: Console
     container: Container
     prev: Container
-    ibuf: string
-    tty: File
 
 jsDestructor(Client)
-jsDestructor(Console)
 
-proc readChar(console: Console): char =
-  if console.ibuf == "":
+func console(client: Client): Console {.jsfget.} =
+  return client.consoleWrapper.console
+
+proc readChar(client: Client): char =
+  if client.ibuf == "":
     try:
-      return console.tty.readChar()
+      return client.pager.tty.readChar()
     except EOFError:
       quit(1)
-  result = console.ibuf[0]
-  console.ibuf = console.ibuf.substr(1)
+  else:
+    result = client.ibuf[0]
+    client.ibuf.delete(0..0)
 
 proc finalize(client: Client) {.jsfin.} =
   if client.jsctx != nil:
@@ -102,14 +104,14 @@ proc fetch[T: Request|string](client: Client, req: T,
 
 proc interruptHandler(rt: JSRuntime, opaque: pointer): cint {.cdecl.} =
   let client = cast[Client](opaque)
-  if client.console == nil or client.console.tty == nil: return
+  if client.console == nil or client.pager.tty == nil: return
   try:
-    let c = client.console.tty.readChar()
+    let c = client.pager.tty.readChar()
     if c == char(3): #C-c
-      client.console.ibuf = ""
+      client.ibuf = ""
       return 1
     else:
-      client.console.ibuf &= c
+      client.ibuf &= c
   except IOError:
     discard
   return 0
@@ -139,14 +141,14 @@ proc command0(client: Client, src: string, filename = "<command>",
     if not silence:
       let str = fromJS[string](client.jsctx, ret)
       if str.isSome:
-        client.console.err.write(str.get & '\n')
-        client.console.err.flush()
+        client.console.log(str.get)
   JS_FreeValue(client.jsctx, ret)
 
 proc command(client: Client, src: string) =
   client.command0(src)
-  client.console.container.requestLines().then(proc() =
-    client.console.container.cursorLastLine())
+  let container = client.consoleWrapper.container
+  container.requestLines().then(proc() =
+    container.cursorLastLine())
 
 proc suspend(client: Client) {.jsfunc.} =
   client.pager.term.quit()
@@ -223,7 +225,7 @@ proc handleCommandInput(client: Client, c: char) =
 proc input(client: Client) =
   client.pager.term.restoreStdin()
   while true:
-    let c = client.console.readChar()
+    let c = client.readChar()
     if client.pager.askpromise != nil:
       if c == 'y':
         client.pager.fulfillAsk(true)
@@ -280,26 +282,19 @@ proc clearInterval(client: Client, id: int32) {.jsfunc.} =
 
 let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint
 
-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 showConsole(client: Client) {.jsfunc.} =
+  let container = client.consoleWrapper.container
+  if client.pager.container != container:
+    client.consoleWrapper.prev = client.pager.container
+    client.pager.setContainer(container)
+    container.requestLines()
 
-proc show(console: Console) {.jsfunc.} =
-  if console.pager.container != console.container:
-    console.prev = console.pager.container
-    console.pager.setContainer(console.container)
-    console.container.requestLines()
+proc hideConsole(client: Client) {.jsfunc.} =
+  if client.pager.container == client.consoleWrapper.container:
+    client.pager.setContainer(client.consoleWrapper.prev)
 
-proc hide(console: Console) {.jsfunc.} =
-  if console.pager.container == console.container:
-    console.pager.setContainer(console.prev)
-
-proc buffer(console: Console): Container {.jsfget.} =
-  return console.container
+proc consoleBuffer(client: Client): Container {.jsfget.} =
+  return client.consoleWrapper.container
 
 proc acceptBuffers(client: Client) =
   while client.pager.unreg.len > 0:
@@ -324,7 +319,7 @@ proc c_setvbuf(f: File, buf: pointer, mode: cint, size: csize_t): cint {.
   importc: "setvbuf", header: "<stdio.h>", tags: [].}
 
 proc handleRead(client: Client, fd: int) =
-  if client.console.tty != nil and fd == client.console.tty.getFileHandle():
+  if client.pager.tty != nil and fd == client.pager.tty.getFileHandle():
     client.input()
     client.handlePagerEvents()
   elif fd == client.forkserver.estream.fd:
@@ -360,7 +355,7 @@ proc flushConsole*(client: Client) {.jsfunc.} =
   client.handleRead(client.forkserver.estream.fd)
 
 proc handleError(client: Client, fd: int) =
-  if client.console.tty != nil and fd == client.console.tty.getFileHandle():
+  if client.pager.tty != nil and fd == client.pager.tty.getFileHandle():
     #TODO do something here...
     stderr.write("Error in tty\n")
     quit(1)
@@ -378,21 +373,21 @@ proc handleError(client: Client, fd: int) =
   else:
     if fd in client.fdmap:
       let container = client.fdmap[fd]
-      if container != client.console.container:
+      if container != client.consoleWrapper.container:
         client.console.log("Error in buffer", $container.location)
       else:
-        client.console.container = nil
+        client.consoleWrapper.container = nil
       client.selector.unregister(fd)
       client.fdmap.del(fd)
-    if client.console.container != nil:
-      client.console.show()
+    if client.consoleWrapper.container != nil:
+      client.showConsole()
     else:
       doAssert false
 
 proc inputLoop(client: Client) =
   let selector = client.selector
-  discard c_setvbuf(client.console.tty, nil, IONBF, 0)
-  selector.registerHandle(int(client.console.tty.getFileHandle()), {Read}, 0)
+  discard c_setvbuf(client.pager.tty, nil, IONBF, 0)
+  selector.registerHandle(int(client.pager.tty.getFileHandle()), {Read}, 0)
   let sigwinch = selector.registerSignal(int(SIGWINCH), 0)
   while true:
     let events = client.selector.select(-1)
@@ -403,14 +398,14 @@ proc inputLoop(client: Client) =
         client.handleError(event.fd)
       if Signal in event.events: 
         assert event.fd == sigwinch
-        let attrs = getWindowAttributes(client.console.tty)
+        let attrs = getWindowAttributes(client.pager.tty)
         client.pager.windowChange(attrs)
       if selectors.Event.Timer in event.events:
         let r = client.timeouts.runTimeoutFd(event.fd)
         assert r
         client.runJSJobs()
-        client.console.container.requestLines().then(proc() =
-          client.console.container.cursorLastLine())
+        client.pager.container.requestLines().then(proc() =
+          client.pager.container.cursorLastLine())
     client.loader.unregistered.setLen(0)
     client.acceptBuffers()
     if client.pager.scommand != "":
@@ -483,27 +478,51 @@ proc readFile(client: Client, path: string): string {.jsfunc.} =
 proc writeFile(client: Client, path: string, content: string) {.jsfunc.} =
   writeFile(path, content)
 
-proc newConsole(pager: Pager, tty: File): Console =
-  new(result)
-  if tty != nil:
+const ConsoleTitle = "Browser Console"
+
+proc addConsole(pager: Pager, interactive: bool, clearFun, showFun, hideFun:
+    proc()): ConsoleWrapper =
+  if interactive:
     var pipefd: array[0..1, cint]
     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"), CHARSET_UNKNOWN,
-      pipefd[0], option(url.get(nil)), "Browser console",
-      canreinterpret = false)
-    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.err.flush()
-    result.pager = pager
-    result.tty = tty
-    pager.registerContainer(result.container)
+    let url = newURL("javascript:console.show()").get
+    let container = pager.readPipe0(some("text/plain"), CHARSET_UNKNOWN,
+      pipefd[0], some(url), ConsoleTitle, canreinterpret = false)
+    let err = newPosixStream(pipefd[1])
+    err.writeLine("Type (M-c) console.hide() to return to buffer mode.")
+    err.flush()
+    pager.registerContainer(container)
+    let console = newConsole(
+      err,
+      clearFun = clearFun,
+      showFun = showFun,
+      hideFun = hideFun
+    )
+    return ConsoleWrapper(
+      console: console,
+      container: container
+    )
   else:
-    result.err = newFileStream(stderr)
+    let err = newFileStream(stderr)
+    return ConsoleWrapper(
+      console: newConsole(err)
+    )
+
+proc clearConsole(client: Client) =
+  var pipefd: array[0..1, cint]
+  if pipe(pipefd) == -1:
+    raise newException(Defect, "Failed to open console pipe.")
+  let url = newURL("javascript:console.show()").get
+  let pager = client.pager
+  let replacement = pager.readPipe0(some("text/plain"), CHARSET_UNKNOWN,
+    pipefd[0], some(url), ConsoleTitle, canreinterpret = false)
+  replacement.replace = client.consoleWrapper.container
+  pager.registerContainer(replacement)
+  client.consoleWrapper.container = replacement
+  let console = client.consoleWrapper.console
+  console.err.close()
+  console.err = newPosixStream(pipefd[1])
 
 proc dumpBuffers(client: Client) =
   client.headlessLoop()
@@ -540,7 +559,14 @@ proc launchClient*(client: Client, pages: seq[string],
     selector.unregister(fd)
   client.selector = selector
   client.pager.launchPager(tty)
-  client.console = newConsole(client.pager, tty)
+  let clearFun = proc() =
+    client.clearConsole()
+  let showFun = proc() =
+    client.showConsole()
+  let hideFun = proc() =
+    client.hideConsole()
+  client.consoleWrapper = addConsole(client.pager, interactive = tty != nil,
+    clearFun, showFun, hideFun)
   #TODO passing console.err here makes it impossible to change it later. maybe
   # better associate it with jsctx
   client.timeouts = newTimeoutState(client.selector, client.jsctx,
@@ -597,6 +623,7 @@ func line(client: Client): LineEdit {.jsfget.} =
 
 proc addJSModules(client: Client, ctx: JSContext) =
   ctx.addDOMExceptionModule()
+  ctx.addConsoleModule()
   ctx.addCookieModule()
   ctx.addURLModule()
   ctx.addEventModule()
@@ -643,6 +670,5 @@ proc newClient*(config: Config, forkserver: ForkServer, mainproc: Pid): Client =
   jsctx.registerType(Client, asglobal = true)
   setGlobal(jsctx, global, client)
   JS_FreeValue(jsctx, global)
-  jsctx.registerType(Console)
   client.addJSModules(jsctx)
   return client
diff --git a/src/local/container.nim b/src/local/container.nim
index 1d13ef68..a04b8b88 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -367,8 +367,13 @@ proc setNumLines(container: Container, lines: int, finish = false) =
     container.updateCursor()
     container.triggerEvent(STATUS)
 
-proc requestLines*(container: Container, w = container.lineWindow): auto {.discardable.} =
-  return container.iface.getLines(w).then(proc(res: tuple[numLines: int, lines: seq[SimpleFlexibleLine]]) =
+proc requestLines*(container: Container, w = container.lineWindow): EmptyPromise
+    {.discardable.} =
+  if container.iface == nil:
+    let res = EmptyPromise()
+    res.resolve()
+    return res
+  return container.iface.getLines(w).then(proc(res: GetLinesResult) =
     container.lines.setLen(w.len)
     container.lineshift = w.a
     for y in 0 ..< min(res.lines.len, w.len):
diff --git a/src/local/pager.nim b/src/local/pager.nim
index 1333e32f..3630f08f 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -81,7 +81,7 @@ type
     siteconf: seq[SiteConfig]
     statusgrid*: FixedGrid
     term*: Terminal
-    tty: File
+    tty*: File #TODO this is already referenced in term, remove.
     unreg*: seq[(Pid, SocketStream)]
     urimethodmap: URIMethodMap
     username: string