about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config/config.nim18
-rw-r--r--src/config/mailcap.nim3
-rw-r--r--src/extern/runproc.nim2
-rw-r--r--src/html/dom.nim18
-rw-r--r--src/layout/renderdocument.nim (renamed from src/render/renderdocument.nim)0
-rw-r--r--src/local/client.nim12
-rw-r--r--src/local/pager.nim121
-rw-r--r--src/render/rendertext.nim121
-rw-r--r--src/server/buffer.nim147
9 files changed, 182 insertions, 260 deletions
diff --git a/src/config/config.nim b/src/config/config.nim
index 82200334..1ae0a727 100644
--- a/src/config/config.nim
+++ b/src/config/config.nim
@@ -357,12 +357,12 @@ proc readUserStylesheet(dir, file: string): string =
 # of several individual configuration files known as mailcap files.
 proc getMailcap*(config: Config): tuple[mailcap: Mailcap, errs: seq[string]] =
   let configDir = getConfigDir() / "chawan" #TODO store this in config?
-  const gopherPath0 = ChaPath("${%CHA_LIBEXEC_DIR}/gopher2html -u \\$MAILCAP_URL")
-  let gopherPath = gopherPath0.unquote().get
-  const geminiPath0 = ChaPath("${%CHA_LIBEXEC_DIR}/gmi2html")
-  let geminiPath = geminiPath0.unquote().get
-  const mdPath0 = ChaPath("${%CHA_LIBEXEC_DIR}/md2html")
-  let mdPath = mdPath0.unquote().get
+  template uq(s: string): string =
+    ChaPath(s).unquote.get
+  let gopherPath = "${%CHA_LIBEXEC_DIR}/gopher2html -u \\$MAILCAP_URL".uq
+  let geminiPath = "${%CHA_LIBEXEC_DIR}/gmi2html".uq
+  let mdPath = "${%CHA_LIBEXEC_DIR}/md2html".uq
+  let ansiPath = "${%CHA_LIBEXEC_DIR}/ansi2html".uq
   var mailcap: Mailcap = @[]
   var errs: seq[string]
   var found = false
@@ -393,6 +393,12 @@ proc getMailcap*(config: Config): tuple[mailcap: Mailcap, errs: seq[string]] =
     cmd: mdPath,
     flags: {HTMLOUTPUT}
   ))
+  mailcap.add(MailcapEntry(
+    mt: "text",
+    subt: "x-ansi",
+    cmd: ansiPath,
+    flags: {HTMLOUTPUT}
+  ))
   if not found:
     mailcap.add(MailcapEntry(
       mt: "*",
diff --git a/src/config/mailcap.nim b/src/config/mailcap.nim
index d5d17eae..89d268db 100644
--- a/src/config/mailcap.nim
+++ b/src/config/mailcap.nim
@@ -20,6 +20,7 @@ type
     NEEDSTERMINAL = "needsterminal"
     COPIOUSOUTPUT = "copiousoutput"
     HTMLOUTPUT = "x-htmloutput" # from w3m
+    ANSIOUTPUT = "x-ansioutput" # Chawan extension
 
   MailcapEntry* = object
     mt*: string
@@ -122,6 +123,8 @@ proc parseFieldKey(entry: var MailcapEntry, k: string): NamedField =
     entry.flags.incl(COPIOUSOUTPUT)
   of "x-htmloutput":
     entry.flags.incl(HTMLOUTPUT)
+  of "x-ansioutput":
+    entry.flags.incl(ANSIOUTPUT)
   of "test":
     return NAMED_FIELD_TEST
   of "nametemplate":
diff --git a/src/extern/runproc.nim b/src/extern/runproc.nim
index 7982dbda..9d189dff 100644
--- a/src/extern/runproc.nim
+++ b/src/extern/runproc.nim
@@ -55,4 +55,4 @@ proc runProcessInto*(cmd, ins: string): bool =
 
 proc myExec*(cmd: string) =
   discard execl("/bin/sh", "sh", "-c", cmd, nil)
-  quit(127)
+  exitnow(127)
diff --git a/src/html/dom.nim b/src/html/dom.nim
index f8ddaad5..1104ee90 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -1781,21 +1781,19 @@ proc write(document: Document, text: varargs[string]): Err[DOMException]
     CDB_parseDocumentWriteChunk(document.parser)
   return ok()
 
-func html*(document: Document): HTMLElement =
-  for element in document.elements(TAG_HTML):
+func findFirst*(document: Document, tagType: TagType): HTMLElement =
+  for element in document.elements(tagType):
     return HTMLElement(element)
+  nil
+
+func html*(document: Document): HTMLElement =
+  return document.findFirst(TAG_HTML)
 
 func head*(document: Document): HTMLElement {.jsfget.} =
-  let html = document.html
-  if html != nil:
-    for element in html.elements(TAG_HEAD):
-      return HTMLElement(element)
+  return document.findFirst(TAG_HEAD)
 
 func body*(document: Document): HTMLElement {.jsfget.} =
-  let html = document.html
-  if html != nil:
-    for element in html.elements(TAG_BODY):
-      return HTMLElement(element)
+  return document.findFirst(TAG_BODY)
 
 func select*(option: HTMLOptionElement): HTMLSelectElement =
   for anc in option.ancestors:
diff --git a/src/render/renderdocument.nim b/src/layout/renderdocument.nim
index 7526b111..7526b111 100644
--- a/src/render/renderdocument.nim
+++ b/src/layout/renderdocument.nim
diff --git a/src/local/client.nim b/src/local/client.nim
index e0a453db..71aaac8c 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -527,8 +527,8 @@ proc addConsole(pager: Pager, interactive: bool, clearFun, showFun, hideFun:
     if pipe(pipefd) == -1:
       raise newException(Defect, "Failed to open console pipe.")
     let url = newURL("stream:console").get
-    let container = pager.readPipe0(some("text/plain"), CHARSET_UNKNOWN,
-      pipefd[0], some(url), ConsoleTitle, canreinterpret = false)
+    let container = pager.readPipe0("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()
@@ -555,8 +555,8 @@ proc clearConsole(client: Client) =
     raise newException(Defect, "Failed to open console pipe.")
   let url = newURL("stream:console").get
   let pager = client.pager
-  let replacement = pager.readPipe0(some("text/plain"), CHARSET_UNKNOWN,
-    pipefd[0], some(url), ConsoleTitle, canreinterpret = false)
+  let replacement = pager.readPipe0("text/plain", CHARSET_UNKNOWN, pipefd[0],
+    some(url), ConsoleTitle, canreinterpret = false)
   replacement.replace = client.consoleWrapper.container
   pager.registerContainer(replacement)
   client.consoleWrapper.container = replacement
@@ -621,10 +621,10 @@ proc launchClient*(client: Client, pages: seq[string],
     let ismodule = client.config.start.startup_script.endsWith(".mjs")
     client.command0(s, client.config.start.startup_script, silence = true,
       module = ismodule)
-
   if not stdin.isatty():
+    # stdin may very well receive ANSI text
+    let contentType = contentType.get("text/x-ansi")
     client.pager.readPipe(contentType, cs, stdin.getFileHandle(), "*stdin*")
-
   for page in pages:
     client.pager.loadURL(page, ctype = contentType, cs = cs)
   client.pager.showAlerts()
diff --git a/src/local/pager.nim b/src/local/pager.nim
index c279faac..62746a4e 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -761,7 +761,7 @@ proc loadURL*(pager: Pager, url: string, ctype = none(string),
     if pager.container != prevc:
       pager.container.retry = urls
 
-proc readPipe0*(pager: Pager, ctype: Option[string], cs: Charset,
+proc readPipe0*(pager: Pager, contentType: string, cs: Charset,
     fd: FileHandle, location: Option[URL], title: string,
     canreinterpret: bool): Container =
   var location = location.get(newURL("stream:-").get)
@@ -773,12 +773,12 @@ proc readPipe0*(pager: Pager, ctype: Option[string], cs: Charset,
     title = title,
     canreinterpret = canreinterpret,
     fd = fd,
-    contentType = some(ctype.get("text/plain"))
+    contentType = some(contentType)
   )
 
-proc readPipe*(pager: Pager, ctype: Option[string], cs: Charset, fd: FileHandle,
+proc readPipe*(pager: Pager, contentType: string, cs: Charset, fd: FileHandle,
     title: string) =
-  let container = pager.readPipe0(ctype, cs, fd, none(URL), title, true)
+  let container = pager.readPipe0(contentType, cs, fd, none(URL), title, true)
   inc pager.numload
   pager.addContainer(container)
 
@@ -948,45 +948,85 @@ proc authorize(pager: Pager) =
 
 type CheckMailcapResult = tuple[promise: EmptyPromise, connect: bool]
 
+proc checkMailcap(pager: Pager, container: Container,
+  contentTypeOverride = none(string)): CheckMailcapResult
+
+# Pipe output of an x-ansioutput mailcap command to the text/x-ansi handler.
+proc ansiDecode(pager: Pager, container: Container, fdin: cint,
+    ishtml: var bool, fdout: var cint) =
+  let cs = container.charset
+  let url = container.location
+  let entry = pager.mailcap.getMailcapEntry("text/x-ansi", "", url, cs)
+  var canpipe = true
+  let cmd = unquoteCommand(entry.cmd, "text/x-ansi", "", url, cs, canpipe)
+  if not canpipe:
+    pager.alert("Error: could not pipe to text/x-ansi, decoding as text/plain")
+  else:
+    var pipefdOutAnsi: array[2, cint]
+    if pipe(pipefdOutAnsi) == -1:
+      raise newException(Defect, "Failed to open pipe.")
+    case fork()
+    of -1:
+      pager.alert("Error: failed to fork ANSI decoder process")
+      discard close(pipefdOutAnsi[0])
+      discard close(pipefdOutAnsi[1])
+    of 0: # child process
+      if fdin != -1:
+        discard close(fdin)
+      discard close(pipefdOutAnsi[0])
+      discard dup2(fdout, stdin.getFileHandle())
+      discard close(fdout)
+      discard dup2(pipefdOutAnsi[1], stdout.getFileHandle())
+      discard close(pipefdOutAnsi[1])
+      closeStderr()
+      myExec(cmd)
+      assert false
+    else:
+      discard close(pipefdOutAnsi[1])
+      discard close(fdout)
+      fdout = pipefdOutAnsi[0]
+      ishtml = HTMLOUTPUT in entry.flags
+
 # Pipe input into the mailcap command, then read its output into a buffer.
 # needsterminal is ignored.
 proc runMailcapReadPipe(pager: Pager, container: Container,
     entry: MailcapEntry, cmd: string): CheckMailcapResult =
-  var pipefd_in: array[2, cint]
-  if pipe(pipefd_in) == -1:
-    raise newException(Defect, "Failed to open pipe.")
-  var pipefd_out: array[2, cint]
-  if pipe(pipefd_out) == -1:
+  var pipefdIn: array[2, cint]
+  var pipefdOut: array[2, cint]
+  if pipe(pipefdIn) == -1 or pipe(pipefdOut) == -1:
     raise newException(Defect, "Failed to open pipe.")
   let pid = fork()
   if pid == -1:
+    pager.alert("Failed to fork process!")
     return (nil, false)
-  elif pid == 0:
-    # child process
-    discard close(pipefd_in[1])
-    discard close(pipefd_out[0])
-    stdout.flushFile()
-    discard dup2(pipefd_in[0], stdin.getFileHandle())
-    discard dup2(pipefd_out[1], stdout.getFileHandle())
+  elif pid == 0: # child process
+    discard close(pipefdIn[1])
+    discard close(pipefdOut[0])
+    discard dup2(pipefdIn[0], stdin.getFileHandle())
+    discard dup2(pipefdOut[1], stdout.getFileHandle())
     closeStderr()
-    discard close(pipefd_in[0])
-    discard close(pipefd_out[1])
+    discard close(pipefdIn[0])
+    discard close(pipefdOut[1])
     myExec(cmd)
     assert false
-  # parent
-  discard close(pipefd_in[0])
-  discard close(pipefd_out[1])
-  let fdin = pipefd_in[1]
-  let fdout = pipefd_out[0]
-  let p = container.redirectToFd(fdin, wait = false, cache = true)
-  let p2 = p.then(proc(): auto =
+  else:
+    # parent
+    discard close(pipefdIn[0])
+    discard close(pipefdOut[1])
+    let fdin = pipefdIn[1]
+    var fdout = pipefdOut[0]
+    var ishtml = HTMLOUTPUT in entry.flags
+    if not ishtml and ANSIOUTPUT in entry.flags:
+      # decode ANSI sequence
+      pager.ansiDecode(container, fdin, ishtml, fdout)
+    let p = container.redirectToFd(fdin, wait = false, cache = true)
     discard close(fdin)
-    let ishtml = HTMLOUTPUT in entry.flags
-    return container.readFromFd(fdout, $pid, ishtml)
-  ).then(proc() =
-    discard close(fdout)
-  )
-  return (p2, true)
+    let p2 = p.then(proc(): auto =
+      let p = container.readFromFd(fdout, $pid, ishtml)
+      discard close(fdout)
+      return p
+    )
+    return (p2, true)
 
 # Pipe input into the mailcap command, and discard its output.
 # If needsterminal, leave stderr and stdout open and wait for the process.
@@ -1048,11 +1088,13 @@ proc runMailcapReadFile(pager: Pager, container: Container,
       quit(ret)
     # parent
     discard close(pipefd[1])
-    let fdout = pipefd[0]
-    let ishtml = HTMLOUTPUT in entry.flags
-    return container.readFromFd(fdout, $pid, ishtml).then(proc() =
-      discard close(fdout)
-    )
+    var fdout = pipefd[0]
+    var ishtml = HTMLOUTPUT in entry.flags
+    if not ishtml and ANSIOUTPUT in entry.flags:
+      pager.ansiDecode(container, -1, ishtml, fdout)
+    let p = container.readFromFd(fdout, $pid, ishtml)
+    discard close(fdout)
+    return p
   )
   return (p, true)
 
@@ -1134,12 +1176,13 @@ proc filterBuffer(pager: Pager, container: Container): CheckMailcapResult =
 # pager is suspended until the command exits.
 #TODO add support for edit/compose, better error handling (use Promise[bool]
 # instead of tuple[EmptyPromise, bool])
-proc checkMailcap(pager: Pager, container: Container): CheckMailcapResult =
+proc checkMailcap(pager: Pager, container: Container,
+    contentTypeOverride = none(string)): CheckMailcapResult =
   if container.filter != nil:
     return pager.filterBuffer(container)
   if container.contentType.isNone:
     return (nil, true)
-  let contentType = container.contentType.get
+  let contentType = contentTypeOverride.get(container.contentType.get)
   if contentType == "text/html":
     # We support HTML natively, so it would make little sense to execute
     # mailcap filters for it.
@@ -1164,7 +1207,7 @@ proc checkMailcap(pager: Pager, container: Container): CheckMailcapResult =
     var canpipe = true
     let cmd = unquoteCommand(entry.cmd, contentType, outpath, url, cs, canpipe)
     putEnv("MAILCAP_URL", $url) #TODO delEnv this after command is finished?
-    if {COPIOUSOUTPUT, HTMLOUTPUT} * entry.flags == {}:
+    if {COPIOUSOUTPUT, HTMLOUTPUT, ANSIOUTPUT} * entry.flags == {}:
       # no output.
       if canpipe:
         return pager.runMailcapWritePipe(container, entry[], cmd)
diff --git a/src/render/rendertext.nim b/src/render/rendertext.nim
deleted file mode 100644
index 27992215..00000000
--- a/src/render/rendertext.nim
+++ /dev/null
@@ -1,121 +0,0 @@
-import std/streams
-import std/strutils
-import std/unicode
-
-import types/cell
-import utils/strwidth
-
-type StreamRenderer* = ref object
-  ansiparser: AnsiCodeParser
-  format: Format
-  af: bool
-  stream: Stream
-  newline: bool
-  w: int
-  j: int # byte in line
-
-proc newStreamRenderer*(): StreamRenderer =
-  return StreamRenderer(ansiparser: AnsiCodeParser(state: PARSE_DONE))
-
-proc rewind*(renderer: StreamRenderer) =
-  renderer.format = Format()
-  renderer.ansiparser.state = PARSE_DONE
-
-proc addFormat(grid: var FlexibleGrid, renderer: StreamRenderer) =
-  if renderer.af:
-    renderer.af = false
-    if renderer.j == grid[^1].str.len:
-      grid[^1].addFormat(renderer.w, renderer.format)
-
-proc processBackspace(grid: var FlexibleGrid, renderer: StreamRenderer,
-    r: Rune): bool =
-  let pj = renderer.j
-  var cr: Rune
-  fastRuneAt(grid[^1].str, renderer.j, cr)
-  if r == Rune('_') or cr == Rune('_') or r == cr:
-    let flag = if r == cr: FLAG_BOLD else: FLAG_UNDERLINE
-    if r != cr and cr == Rune('_'):
-      # original is _, we must replace :(
-      # like less, we assume no double _ for double width characters.
-      grid[^1].str.delete(pj..<renderer.j)
-      let s = $r
-      grid[^1].str.insert(s, pj)
-      renderer.j = pj + s.len
-    let n = grid[^1].findFormatN(renderer.w) - 1
-    if n != -1 and grid[^1].formats[n].pos == renderer.w:
-      let flags = grid[^1].formats[n].format.flags
-      if r == cr and r == Rune('_') and flag in flags:
-        # double overstrike of _, this is nonsensical on a teletype but less(1)
-        # treats it as an underline so we do that too
-        grid[^1].formats[n].format.flags.incl(FLAG_UNDERLINE)
-      else:
-        grid[^1].formats[n].format.flags.incl(flag)
-    elif n != -1:
-      var format = grid[^1].formats[n].format
-      format.flags.incl(flag)
-      grid[^1].insertFormat(renderer.w, n + 1, format)
-    else:
-      grid[^1].addFormat(renderer.w, Format(flags: {flag}))
-    renderer.w += r.twidth(renderer.w)
-    if renderer.j == grid[^1].str.len:
-      grid[^1].addFormat(renderer.w, Format())
-    return true
-  let n = grid[^1].findFormatN(renderer.w)
-  grid[^1].formats.setLen(n)
-  grid[^1].str.setLen(renderer.j)
-  return false
-
-proc processAscii(grid: var FlexibleGrid, renderer: StreamRenderer, c: char) =
-  case c
-  of '\b':
-    if renderer.j == 0:
-      grid[^1].str &= c
-      inc renderer.j
-      renderer.w += Rune(c).twidth(renderer.w)
-    else:
-      let (r, len) = lastRune(grid[^1].str, grid[^1].str.high)
-      renderer.j -= len
-      renderer.w -= r.twidth(renderer.w)
-  of '\n':
-    grid.addFormat(renderer)
-    renderer.newline = true
-  of '\r': discard
-  of '\e':
-    renderer.ansiparser.reset()
-  else:
-    grid.addFormat(renderer)
-    grid[^1].str &= c
-    renderer.w += Rune(c).twidth(renderer.w)
-    inc renderer.j
-
-proc renderChunk*(grid: var FlexibleGrid, renderer: StreamRenderer,
-    buf: openArray[char]) =
-  if grid.len == 0:
-    grid.addLine()
-  var i = 0
-  while i < buf.len:
-    if renderer.newline:
-      # avoid newline at end of stream
-      grid.addLine()
-      renderer.newline = false
-      renderer.w = 0
-      renderer.j = 0
-    let pi = i
-    var r: Rune
-    fastRuneAt(buf, i, r)
-    if renderer.j < grid[^1].str.len:
-      if grid.processBackspace(renderer, r):
-        continue
-    if uint32(r) < 0x80:
-      let c = char(r)
-      if renderer.ansiparser.state != PARSE_DONE:
-        if not renderer.ansiparser.parseAnsiCode(renderer.format, c):
-          if renderer.ansiparser.state == PARSE_DONE:
-            renderer.af = true
-          continue
-      grid.processAscii(renderer, c)
-    else:
-      grid.addFormat(renderer)
-      grid[^1].str &= r
-      renderer.w += r.twidth(renderer.w)
-      renderer.j += i - pi
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index 9ed49c96..783da17b 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -36,10 +36,9 @@ import js/javascript
 import js/regex
 import js/timeout
 import js/tojs
+import layout/renderdocument
 import loader/headers
 import loader/loader
-import render/renderdocument
-import render/rendertext
 import types/cell
 import types/color
 import types/cookie
@@ -109,7 +108,6 @@ type
     quirkstyle: CSSStylesheet
     userstyle: CSSStylesheet
     htmlParser: HTML5ParserWrapper
-    srenderer: StreamRenderer
     bgcolor: CellColor
     needsBOMSniff: bool
     decoder: TextDecoder
@@ -610,34 +608,43 @@ proc gotoAnchor*(buffer: Buffer): Opt[tuple[x, y: int]] {.proxy.} =
   return err()
 
 proc do_reshape(buffer: Buffer) =
-  if buffer.ishtml:
-    if buffer.document == nil:
-      return # not parsed yet, nothing to render
-    let uastyle = if buffer.document.mode != QUIRKS:
-      buffer.uastyle
-    else:
-      buffer.quirkstyle
-    if buffer.document.cachedSheetsInvalid:
-      buffer.prevStyled = nil
-    let styledRoot = buffer.document.applyStylesheets(uastyle,
-      buffer.userstyle, buffer.prevStyled)
-    buffer.lines.renderDocument(buffer.bgcolor, styledRoot, buffer.attrs)
-    buffer.prevStyled = styledRoot
+  if buffer.document == nil:
+    return # not parsed yet, nothing to render
+  let uastyle = if buffer.document.mode != QUIRKS:
+    buffer.uastyle
+  else:
+    buffer.quirkstyle
+  if buffer.document.cachedSheetsInvalid:
+    buffer.prevStyled = nil
+  let styledRoot = buffer.document.applyStylesheets(uastyle,
+    buffer.userstyle, buffer.prevStyled)
+  buffer.lines.renderDocument(buffer.bgcolor, styledRoot, buffer.attrs)
+  buffer.prevStyled = styledRoot
 
 proc processData0(buffer: Buffer, data: openArray[char]): bool =
   if buffer.ishtml:
     if buffer.htmlParser.parseBuffer(data) == PRES_STOP:
       buffer.charsetStack = @[buffer.htmlParser.builder.charset]
       return false
-    buffer.document = buffer.htmlParser.builder.document
   else:
-    buffer.lines.renderChunk(buffer.srenderer, data)
+    var plaintext = buffer.document.findFirst(TAG_PLAINTEXT)
+    if plaintext == nil:
+      const s = "<plaintext id='text'>"
+      doAssert buffer.htmlParser.parseBuffer(s) != PRES_STOP
+      plaintext = buffer.document.findFirst(TAG_PLAINTEXT)
+    if data.len > 0:
+      let lastChild = plaintext.lastChild
+      var text = newString(data.len)
+      copyMem(addr text[0], unsafeAddr data[0], data.len)
+      if lastChild != nil and lastChild of Text:
+        Text(lastChild).data &= text
+      else:
+        plaintext.insert(buffer.document.createTextNode(text), nil)
   true
 
 func canSwitch(buffer: Buffer): bool {.inline.} =
-  if buffer.ishtml and buffer.htmlParser.builder.confidence != ccTentative:
-    return false
-  return buffer.charsetStack.len > 0
+  return buffer.htmlParser.builder.confidence == ccTentative and
+    buffer.charsetStack.len > 0
 
 proc initDecoder(buffer: Buffer) =
   if buffer.charset != CHARSET_UTF_8:
@@ -648,11 +655,8 @@ proc initDecoder(buffer: Buffer) =
 proc switchCharset(buffer: Buffer) =
   buffer.charset = buffer.charsetStack.pop()
   buffer.initDecoder()
-  if buffer.ishtml:
-    buffer.htmlParser.restart(buffer.charset)
-  else:
-    buffer.srenderer.rewind()
-    buffer.lines.setLen(0)
+  buffer.htmlParser.restart(buffer.charset)
+  buffer.document = buffer.htmlParser.builder.document
 
 const BufferSize = 16384
 
@@ -812,41 +816,39 @@ proc rewind(buffer: Buffer): bool =
 proc setHTML(buffer: Buffer, ishtml: bool) =
   buffer.ishtml = ishtml
   buffer.initDecoder()
-  if ishtml:
-    let factory = newCAtomFactory()
-    buffer.factory = factory
-    let navigate = if buffer.config.scripting:
-      proc(url: URL) = buffer.navigate(url)
-    else:
-      nil
-    buffer.window = newWindow(
-      buffer.config.scripting,
-      buffer.config.images,
-      buffer.selector,
-      buffer.attrs,
-      factory,
-      navigate,
-      some(buffer.loader)
-    )
-    let confidence = if buffer.config.charsetOverride == CHARSET_UNKNOWN:
-      ccTentative
-    else:
-      ccCertain
-    buffer.htmlParser = newHTML5ParserWrapper(
-      buffer.window,
-      buffer.url,
-      buffer.factory,
-      confidence,
-      buffer.charset
-    )
-    assert buffer.htmlParser.builder.document != nil
-    const css = staticRead"res/ua.css"
-    const quirk = css & staticRead"res/quirk.css"
-    buffer.uastyle = css.parseStylesheet(factory)
-    buffer.quirkstyle = quirk.parseStylesheet(factory)
-    buffer.userstyle = parseStylesheet(buffer.config.userstyle, factory)
+  let factory = newCAtomFactory()
+  buffer.factory = factory
+  let navigate = if buffer.config.scripting:
+    proc(url: URL) = buffer.navigate(url)
+  else:
+    nil
+  buffer.window = newWindow(
+    buffer.config.scripting,
+    buffer.config.images,
+    buffer.selector,
+    buffer.attrs,
+    factory,
+    navigate,
+    some(buffer.loader)
+  )
+  let confidence = if buffer.config.charsetOverride == CHARSET_UNKNOWN:
+    ccTentative
   else:
-    buffer.srenderer = newStreamRenderer()
+    ccCertain
+  buffer.htmlParser = newHTML5ParserWrapper(
+    buffer.window,
+    buffer.url,
+    buffer.factory,
+    confidence,
+    buffer.charset
+  )
+  assert buffer.htmlParser.builder.document != nil
+  const css = staticRead"res/ua.css"
+  const quirk = css & staticRead"res/quirk.css"
+  buffer.uastyle = css.parseStylesheet(factory)
+  buffer.quirkstyle = quirk.parseStylesheet(factory)
+  buffer.userstyle = parseStylesheet(buffer.config.userstyle, factory)
+  buffer.document = buffer.htmlParser.builder.document
 
 proc extractCookies(response: Response): seq[Cookie] =
   result = @[]
@@ -1122,21 +1124,14 @@ proc finishLoad(buffer: Buffer): EmptyPromise =
   if buffer.decoder != nil and buffer.decoder.finish() == tdfrError or
       buffer.validator != nil and buffer.validator[].finish() == tvrError:
     doAssert buffer.processData0("\uFFFD")
-  var p: EmptyPromise
-  if buffer.ishtml:
-    buffer.htmlParser.finish()
-    buffer.document = buffer.htmlParser.builder.document
-    buffer.document.readyState = rsInteractive
-    buffer.dispatchDOMContentLoadedEvent()
-    p = buffer.loadResources()
-  else:
-    p = EmptyPromise()
-    p.resolve()
+  buffer.htmlParser.finish()
+  buffer.document.readyState = rsInteractive
+  buffer.dispatchDOMContentLoadedEvent()
   buffer.selector.unregister(buffer.fd)
   buffer.loader.unregistered.add(buffer.fd)
   buffer.fd = -1
   buffer.istream.close()
-  return p
+  return buffer.loadResources()
 
 type LoadResult* = tuple[
   atend: bool,
@@ -1235,12 +1230,10 @@ proc cancel*(buffer: Buffer): int {.proxy.} =
   buffer.loader.unregistered.add(buffer.fd)
   buffer.fd = -1
   buffer.istream.close()
-  if buffer.ishtml:
-    buffer.htmlParser.finish()
-    buffer.document = buffer.htmlParser.builder.document
-    buffer.document.readyState = rsInteractive
-    buffer.state = bsLoaded
-    buffer.do_reshape()
+  buffer.htmlParser.finish()
+  buffer.document.readyState = rsInteractive
+  buffer.state = bsLoaded
+  buffer.do_reshape()
   return buffer.lines.len
 
 #https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm