about summary refs log tree commit diff stats
path: root/src/display/client.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/display/client.nim')
-rw-r--r--src/display/client.nim643
1 files changed, 0 insertions, 643 deletions
diff --git a/src/display/client.nim b/src/display/client.nim
deleted file mode 100644
index 3236732d..00000000
--- a/src/display/client.nim
+++ /dev/null
@@ -1,643 +0,0 @@
-import cstrutils
-import nativesockets
-import net
-import options
-import os
-import selectors
-import streams
-import strutils
-import tables
-import terminal
-
-when defined(posix):
-  import posix
-
-import std/exitprocs
-
-import bindings/quickjs
-import buffer/container
-import config/config
-import css/sheet
-import display/pager
-import display/term
-import html/chadombuilder
-import html/dom
-import html/event
-import io/headers
-import io/lineedit
-import io/loader
-import io/posixstream
-import io/promise
-import io/request
-import io/window
-import ips/forkserver
-import ips/socketstream
-import js/base64
-import js/domexception
-import js/error
-import js/fromjs
-import js/intl
-import js/javascript
-import js/module
-import js/timeout
-import js/tojs
-import types/blob
-import types/cookie
-import types/url
-import utils/opt
-import utils/twtstr
-import xhr/formdata
-import xhr/xmlhttprequest
-
-import chakasu/charset
-
-type
-  Client* = ref object
-    alive: bool
-    attrs: WindowAttributes
-    config {.jsget.}: Config
-    console {.jsget.}: Console
-    errormessage: string
-    fd: int
-    fdmap: Table[int, Container]
-    feednext: bool
-    forkserver: ForkServer
-    notnum: bool # has a non-numeric character been input already?
-    jsctx: JSContext
-    jsrt: JSRuntime
-    line {.jsget.}: LineEdit
-    loader: FileLoader
-    mainproc: Pid
-    pager {.jsget.}: Pager
-    precnum: int32 # current number prefix (when vi-numeric-prefix is true)
-    s: string # current input buffer
-    selector: Selector[Container]
-    store {.jsget, jsset.}: Document
-    timeouts: TimeoutState[Container]
-    userstyle: CSSStylesheet
-
-  Console = ref object
-    err: Stream
-    pager: Pager
-    container: Container
-    prev: Container
-    ibuf: string
-    tty: File
-
-jsDestructor(Client)
-jsDestructor(Console)
-
-proc readChar(console: Console): char =
-  if console.ibuf == "":
-    try:
-      return console.tty.readChar()
-    except EOFError:
-      quit(1)
-  result = console.ibuf[0]
-  console.ibuf = console.ibuf.substr(1)
-
-proc finalize(client: Client) {.jsfin.} =
-  if client.jsctx != nil:
-    free(client.jsctx)
-  if client.jsrt != nil:
-    free(client.jsrt)
-
-proc doRequest(client: Client, req: Request): Response {.jsfunc.} =
-  return client.loader.doRequest(req)
-
-proc fetch[T: Request|string](client: Client, req: T,
-    init = none(RequestInit)): JSResult[FetchPromise] {.jsfunc.} =
-  let req = ?newRequest(client.jsctx, req, init)
-  return ok(client.loader.fetch(req))
-
-proc interruptHandler(rt: JSRuntime, opaque: pointer): int {.cdecl.} =
-  let client = cast[Client](opaque)
-  if client.console == nil or client.console.tty == nil: return
-  try:
-    let c = client.console.tty.readChar()
-    if c == char(3): #C-c
-      client.console.ibuf = ""
-      return 1
-    else:
-      client.console.ibuf &= c
-  except IOError:
-    discard
-  return 0
-
-proc runJSJobs(client: Client) =
-  client.jsrt.runJSJobs(client.console.err)
-
-proc evalJS(client: Client, src, filename: string, module = false): JSValue =
-  client.pager.term.unblockStdin()
-  let flags = if module:
-    JS_EVAL_TYPE_MODULE
-  else:
-    JS_EVAL_TYPE_GLOBAL
-  result = client.jsctx.eval(src, filename, flags)
-  client.runJSJobs()
-  client.pager.term.restoreStdin()
-
-proc evalJSFree(client: Client, src, filename: string) =
-  JS_FreeValue(client.jsctx, client.evalJS(src, filename))
-
-proc command0(client: Client, src: string, filename = "<command>",
-    silence = false, module = false) =
-  let ret = client.evalJS(src, filename, module = module)
-  if JS_IsException(ret):
-    client.jsctx.writeException(client.console.err)
-  else:
-    if not silence:
-      let str = fromJS[string](client.jsctx, ret)
-      if str.isSome:
-        client.console.err.write(str.get & '\n')
-        client.console.err.flush()
-  JS_FreeValue(client.jsctx, ret)
-
-proc command(client: Client, src: string) =
-  client.command0(src)
-  client.console.container.requestLines().then(proc() =
-    client.console.container.cursorLastLine())
-
-proc suspend(client: Client) {.jsfunc.} =
-  client.pager.term.quit()
-  discard kill(client.mainproc, cint(SIGSTOP))
-  client.pager.term.restart()
-
-proc quit(client: Client, code = 0) {.jsfunc.} =
-  if client.alive:
-    client.alive = false
-    client.pager.quit()
-    let ctx = client.jsctx
-    var global = JS_GetGlobalObject(ctx)
-    JS_FreeValue(ctx, global)
-    if client.jsctx != nil:
-      free(client.jsctx)
-    #TODO
-    #if client.jsrt != nil:
-    #  free(client.jsrt)
-  quit(code)
-
-proc feedNext(client: Client) {.jsfunc.} =
-  client.feednext = true
-
-proc alert(client: Client, msg: string) {.jsfunc.} =
-  client.pager.alert(msg)
-
-proc handlePagerEvents(client: Client) =
-  let container = client.pager.container
-  if container != nil:
-    client.pager.handleEvents(container)
-
-proc evalAction(client: Client, action: string, arg0: int32) =
-  let ret = client.evalJS(action, "<command>")
-  let ctx = client.jsctx
-  if JS_IsFunction(ctx, ret):
-    if arg0 != 0:
-      var arg0 = toJS(ctx, arg0)
-      JS_FreeValue(ctx, JS_Call(ctx, ret, JS_UNDEFINED, 1, addr arg0))
-      JS_FreeValue(ctx, arg0)
-    else: # no precnum
-      JS_FreeValue(ctx, JS_Call(ctx, ret, JS_UNDEFINED, 0, nil))
-  JS_FreeValue(ctx, ret)
-
-# The maximum number we are willing to accept.
-# This should be fine for 32-bit signed ints (which precnum currently is).
-# We can always increase it further (e.g. by switching to uint32, uint64...) if
-# it proves to be too low.
-const MaxPrecNum = 100000000
-
-proc handleCommandInput(client: Client, c: char) =
-  if client.config.input.vi_numeric_prefix and not client.notnum:
-    if client.precnum != 0 and c == '0' or c in '1' .. '9':
-      if client.precnum < MaxPrecNum: # better ignore than eval...
-        client.precnum *= 10
-        client.precnum += cast[int32](decValue(c))
-      return
-    else:
-      client.notnum = true
-  client.s &= c
-  let action = getNormalAction(client.config, client.s)
-  client.evalAction(action, client.precnum)
-  if not client.feedNext:
-    client.precnum = 0
-    client.notnum = false
-    client.handlePagerEvents()
-    client.pager.refreshStatusMsg()
-
-proc input(client: Client) =
-  client.pager.term.restoreStdin()
-  while true:
-    let c = client.console.readChar()
-    if client.pager.askpromise != nil:
-      if c == 'y':
-        client.pager.fulfillAsk(true)
-        client.runJSJobs()
-      elif c == 'n':
-        client.pager.fulfillAsk(false)
-        client.runJSJobs()
-    elif client.pager.lineedit.isSome:
-      client.s &= c
-      let edit = client.pager.lineedit.get
-      client.line = edit
-      if edit.escNext:
-        edit.escNext = false
-        if edit.write(client.s, client.pager.term.cs):
-          client.s = ""
-      else:
-        let action = getLinedAction(client.config, client.s)
-        if action == "":
-          if edit.write(client.s, client.pager.term.cs):
-            client.s = ""
-          else:
-            client.feedNext = true
-        elif not client.feednext:
-          client.evalAction(action, 0)
-        if client.pager.lineedit.isNone:
-          client.line = nil
-        if not client.feedNext:
-          client.pager.updateReadLine()
-    else:
-      client.handleCommandInput(c)
-    if not client.feednext:
-      client.s = ""
-      break
-    else:
-      client.feednext = false
-  client.s = ""
-
-proc setTimeout[T: JSValue|string](client: Client, handler: T,
-    timeout = 0i32): int32 {.jsfunc.} =
-  return client.timeouts.setTimeout(handler, timeout)
-
-proc setInterval[T: JSValue|string](client: Client, handler: T,
-    interval = 0i32): int32 {.jsfunc.} =
-  return client.timeouts.setInterval(handler, interval)
-
-proc clearTimeout(client: Client, id: int32) {.jsfunc.} =
-  client.timeouts.clearTimeout(id)
-
-proc clearInterval(client: Client, id: int32) {.jsfunc.} =
-  client.timeouts.clearInterval(id)
-
-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 show(console: Console) {.jsfunc.} =
-  if console.pager.container != console.container:
-    console.prev = console.pager.container
-    console.pager.setContainer(console.container)
-    console.container.requestLines()
-
-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 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()
-  for pid, container in client.pager.procmap:
-    let stream = connectSocketStream(pid, buffered = false, blocking = true)
-    container.setStream(stream)
-    let fd = stream.source.getFd()
-    client.fdmap[int(fd)] = container
-    client.selector.registerHandle(fd, {Read}, nil)
-    client.pager.handleEvents(container)
-  client.pager.procmap.clear()
-
-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():
-    client.input()
-    client.handlePagerEvents()
-  elif fd == client.forkserver.estream.fd:
-    var nl = false
-    const prefix = "STDERR: "
-    var s = prefix
-    while true:
-      try:
-        let c = client.forkserver.estream.readChar()
-        if nl and s.len > prefix.len:
-          client.console.err.write(s)
-          s = prefix
-          nl = false
-        s &= c
-        nl = c == '\n'
-      except IOError:
-        break
-    if s.len > prefix.len:
-      client.console.err.write(s)
-    client.console.err.flush()
-  elif fd in client.loader.connecting:
-    client.loader.onConnected(fd)
-    client.runJSJobs()
-  elif fd in client.loader.ongoing:
-    client.loader.onRead(fd)
-  elif fd in client.loader.unregistered:
-    discard # ignore
-  else:
-    let container = client.fdmap[fd]
-    client.pager.handleEvent(container)
-
-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():
-    #TODO do something here...
-    stderr.write("Error in tty\n")
-    quit(1)
-  elif fd == client.forkserver.estream.fd:
-    #TODO do something here...
-    stderr.write("Fork server crashed :(\n")
-    quit(1)
-  elif fd in client.loader.connecting:
-    #TODO handle error?
-    discard
-  elif fd in client.loader.ongoing:
-    client.loader.onError(fd)
-  elif fd in client.loader.unregistered:
-    discard # already unregistered...
-  else:
-    if fd in client.fdmap:
-      let container = client.fdmap[fd]
-      if container != client.console.container:
-        client.console.log("Error in buffer", $container.location)
-      else:
-        client.console.container = nil
-      client.selector.unregister(fd)
-      client.fdmap.del(fd)
-    if client.console.container != nil:
-      client.console.show()
-    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}, nil)
-  let sigwinch = selector.registerSignal(int(SIGWINCH), nil)
-  while true:
-    let events = client.selector.select(-1)
-    for event in events:
-      if Read in event.events:
-        client.handleRead(event.fd)
-      if Error in event.events:
-        client.handleError(event.fd)
-      if Signal in event.events: 
-        assert event.fd == sigwinch
-        client.attrs = getWindowAttributes(client.console.tty)
-        client.pager.windowChange(client.attrs)
-      if selectors.Event.Timer in event.events:
-        assert client.timeouts.runTimeoutFd(event.fd)
-        client.runJSJobs()
-        client.console.container.requestLines().then(proc() =
-          client.console.container.cursorLastLine())
-    client.loader.unregistered.setLen(0)
-    client.acceptBuffers()
-    if client.pager.scommand != "":
-      client.command(client.pager.scommand)
-      client.pager.scommand = ""
-      client.handlePagerEvents()
-    if client.pager.container == nil:
-      # No buffer to display.
-      quit(1)
-    client.pager.showAlerts()
-    client.pager.draw()
-
-func hasSelectFds(client: Client): bool =
-  return not client.timeouts.empty or
-    client.pager.numload > 0 or
-    client.loader.connecting.len > 0 or
-    client.loader.ongoing.len > 0 or
-    client.pager.procmap.len > 0
-
-proc headlessLoop(client: Client) =
-  while client.hasSelectFds():
-    let events = client.selector.select(-1)
-    for event in events:
-      if Read in event.events:
-        client.handleRead(event.fd)
-      if Error in event.events:
-        client.handleError(event.fd)
-      if selectors.Event.Timer in event.events:
-        assert client.timeouts.runTimeoutFd(event.fd)
-    client.runJSJobs()
-    client.loader.unregistered.setLen(0)
-    client.acceptBuffers()
-
-proc clientLoadJSModule(ctx: JSContext, module_name: cstring,
-    opaque: pointer): JSModuleDef {.cdecl.} =
-  let global = JS_GetGlobalObject(ctx)
-  JS_FreeValue(ctx, global)
-  var x: Option[URL]
-  if module_name.startsWith("/") or module_name.startsWith("./") or
-      module_name.startsWith("../"):
-    let cur = getCurrentDir()
-    x = parseURL($module_name, parseURL("file://" & cur & "/"))
-  else:
-    x = parseURL($module_name)
-  if x.isNone or x.get.scheme != "file":
-    JS_ThrowTypeError(ctx, "Invalid URL: %s", module_name)
-    return nil
-  try:
-    let f = readFile($x.get.path)
-    return finishLoadModule(ctx, f, module_name)
-  except IOError:
-    JS_ThrowTypeError(ctx, "Failed to open file %s", module_name)
-    return nil
-
-proc readBlob(client: Client, path: string): Option[WebFile] {.jsfunc.} =
-  try:
-    return some(newWebFile(path))
-  except IOError:
-    discard
-
-#TODO this is dumb
-proc readFile(client: Client, path: string): string {.jsfunc.} =
-  try:
-    return readFile(path)
-  except IOError:
-    discard
-
-#TODO ditto
-proc writeFile(client: Client, path: string, content: string) {.jsfunc.} =
-  writeFile(path, content)
-
-proc newConsole(pager: Pager, tty: File): Console =
-  new(result)
-  if tty != nil:
-    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")
-    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)
-  else:
-    result.err = newFileStream(stderr)
-
-proc dumpBuffers(client: Client) =
-  client.headlessLoop()
-  let ostream = newFileStream(stdout)
-  for container in client.pager.containers:
-    try:
-      client.pager.drawBuffer(container, ostream)
-      client.pager.handleEvents(container)
-    except IOError:
-      client.console.log("Error in buffer", $container.location)
-      # check for errors
-      client.handleRead(client.forkserver.estream.fd)
-      quit(1)
-  stdout.close()
-
-proc launchClient*(client: Client, pages: seq[string],
-    contentType: Option[string], cs: Charset, dump: bool) =
-  var tty: File
-  var dump = dump
-  if not dump:
-    if stdin.isatty():
-      tty = stdin
-    if stdout.isatty():
-      if tty == nil:
-        dump = not open(tty, "/dev/tty", fmRead)
-    else:
-      dump = true
-  let selector = newSelector[Container]()
-  let efd = int(client.forkserver.estream.fd)
-  selector.registerHandle(efd, {Read}, nil)
-  client.loader.registerFun = proc(fd: int) =
-    selector.registerHandle(fd, {Read}, nil)
-  client.loader.unregisterFun = proc(fd: int) =
-    selector.unregister(fd)
-  client.selector = selector
-  client.pager.launchPager(tty)
-  client.console = newConsole(client.pager, tty)
-  #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,
-    client.console.err, proc(src, file: string) = client.evalJSFree(src, file))
-  client.alive = true
-  addExitProc((proc() = client.quit()))
-  if client.config.start.startup_script != "":
-    let s = if fileExists(client.config.start.startup_script):
-      readFile(client.config.start.startup_script)
-    else:
-      client.config.start.startup_script
-    let ismodule = client.config.start.startup_script.endsWith(".mjs")
-    client.command0(s, client.config.start.startup_script, silence = true,
-      module = ismodule)
-  client.userstyle = client.config.css.stylesheet.parseStylesheet()
-
-  if not stdin.isatty():
-    client.pager.readPipe(contentType, cs, stdin.getFileHandle())
-
-  for page in pages:
-    client.pager.loadURL(page, ctype = contentType, cs = cs)
-  client.pager.showAlerts()
-  client.acceptBuffers()
-  if not dump:
-    client.inputLoop()
-  else:
-    client.dumpBuffers()
-  if client.config.start.headless:
-    client.headlessLoop()
-  client.quit()
-
-proc nimGCStats(client: Client): string {.jsfunc.} =
-  return GC_getStatistics()
-
-proc jsGCStats(client: Client): string {.jsfunc.} =
-  return client.jsrt.getMemoryUsage()
-
-proc nimCollect(client: Client) {.jsfunc.} =
-  GC_fullCollect()
-
-proc jsCollect(client: Client) {.jsfunc.} =
-  JS_RunGC(client.jsrt)
-
-proc sleep(client: Client, millis: int) {.jsfunc.} =
-  sleep millis
-
-proc atob(client: Client, data: string): DOMResult[string] {.jsfunc.} =
-  return atob(data)
-
-proc btoa(client: Client, data: string): DOMResult[string] {.jsfunc.} =
-  return btoa(data)
-
-proc addJSModules(client: Client, ctx: JSContext) =
-  ctx.addDOMExceptionModule()
-  ctx.addCookieModule()
-  ctx.addURLModule()
-  ctx.addEventModule()
-  ctx.addDOMModule()
-  ctx.addHTMLModule()
-  ctx.addIntlModule()
-  ctx.addBlobModule()
-  ctx.addFormDataModule()
-  ctx.addXMLHttpRequestModule()
-  ctx.addHeadersModule()
-  ctx.addRequestModule()
-  ctx.addResponseModule()
-  ctx.addLineEditModule()
-  ctx.addConfigModule()
-  ctx.addPagerModule()
-  ctx.addContainerModule()
-
-func getClient(client: Client): Client {.jsfget: "client".} =
-  return client
-
-proc newClient*(config: Config, forkserver: ForkServer, mainproc: Pid): Client =
-  setControlCHook(proc() {.noconv.} = quit(1))
-  let jsrt = newJSRuntime()
-  JS_SetModuleLoaderFunc(jsrt, normalizeModuleName, clientLoadJSModule, nil)
-  let jsctx = jsrt.newJSContext()
-  let attrs = getWindowAttributes(stdout)
-  let client = Client(
-    config: config,
-    forkserver: forkserver,
-    mainproc: mainproc,
-    attrs: attrs,
-    loader: forkserver.newFileLoader(
-      defaultHeaders = config.getDefaultHeaders(),
-      proxy = config.getProxy(),
-      acceptProxy = true
-    ),
-    jsrt: jsrt,
-    jsctx: jsctx,
-    pager: newPager(config, attrs, forkserver, mainproc, jsctx)
-  )
-  jsrt.setInterruptHandler(interruptHandler, cast[pointer](client))
-  var global = JS_GetGlobalObject(jsctx)
-  jsctx.registerType(Client, asglobal = true)
-  setGlobal(jsctx, global, client)
-  JS_FreeValue(jsctx, global)
-  jsctx.registerType(Console)
-  client.addJSModules(jsctx)
-  return client