about summary refs log tree commit diff stats
path: root/src/buffer/container.nim
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-09-14 01:08:10 +0200
committerbptato <nincsnevem662@gmail.com>2023-09-14 01:08:10 +0200
commitdb0798acccbedcef4b16737f6be0cf7388cc0528 (patch)
tree208616de68fe9729e09e4084557f27f5b7204387 /src/buffer/container.nim
parentc2004e4aba182473e79100759a2d58e1bd7d184c (diff)
downloadchawan-db0798acccbedcef4b16737f6be0cf7388cc0528.tar.gz
move some modules to local/
makes a bit more sense than the previous arrangement
Diffstat (limited to 'src/buffer/container.nim')
-rw-r--r--src/buffer/container.nim1024
1 files changed, 0 insertions, 1024 deletions
diff --git a/src/buffer/container.nim b/src/buffer/container.nim
deleted file mode 100644
index 53be817a..00000000
--- a/src/buffer/container.nim
+++ /dev/null
@@ -1,1024 +0,0 @@
-import deques
-import options
-import streams
-import unicode
-
-when defined(posix):
-  import posix
-
-import buffer/buffer
-import buffer/cell
-import buffer/select
-import config/config
-import io/promise
-import io/request
-import io/window
-import ips/forkserver
-import ips/serialize
-import js/javascript
-import js/regex
-import types/buffersource
-import types/color
-import types/cookie
-import types/url
-import utils/mimeguess
-import utils/twtstr
-
-import chakasu/charset
-
-type
-  CursorPosition* = object
-    cursorx*: int
-    cursory*: int
-    xend*: int
-    fromx*: int
-    fromy*: int
-    setx: int
-    setxrefresh: bool
-
-  ContainerEventType* = enum
-    NO_EVENT, FAIL, SUCCESS, NEEDS_AUTH, REDIRECT, ANCHOR, NO_ANCHOR, UPDATE,
-    READ_LINE, READ_AREA, OPEN, INVALID_COMMAND, STATUS, ALERT, LOADED, TITLE,
-    CHECK_MAILCAP, QUIT
-
-  ContainerEvent* = object
-    case t*: ContainerEventType
-    of READ_LINE:
-      prompt*: string
-      value*: string
-      password*: bool
-    of READ_AREA:
-      tvalue*: string
-    of OPEN, REDIRECT:
-      request*: Request
-    of ANCHOR, NO_ANCHOR:
-      anchor*: string
-    of ALERT:
-      msg*: string
-    of UPDATE:
-      force*: bool
-    else: discard
-
-  Highlight* = ref object
-    x*, y*: int
-    endy*, endx*: int
-    rect*: bool
-    clear*: bool
-
-  Container* = ref object
-    parent* {.jsget.}: Container
-    children* {.jsget.}: seq[Container]
-    config*: BufferConfig
-    iface*: BufferInterface
-    width* {.jsget.}: int
-    height* {.jsget.}: int
-    title*: string # used in status msg
-    hovertext: array[HoverType, string]
-    lastpeek: HoverType
-    source*: BufferSource
-    pos: CursorPosition
-    bpos: seq[CursorPosition]
-    highlights: seq[Highlight]
-    process* {.jsget.}: Pid
-    loadinfo*: string
-    lines: SimpleFlexibleGrid
-    lineshift: int
-    numLines*: int
-    replace*: Container
-    code*: int
-    retry*: seq[URL]
-    hlon*: bool # highlight on?
-    sourcepair*: Container # pointer to buffer with a source view (may be nil)
-    redraw*: bool
-    needslines*: bool
-    canceled: bool
-    events*: Deque[ContainerEvent]
-    startpos: Option[CursorPosition]
-    hasstart: bool
-    redirectdepth*: int
-    select*: Select
-
-jsDestructor(Container)
-
-proc newBuffer*(forkserver: ForkServer, mainproc: Pid, config: BufferConfig,
-    source: BufferSource, title = "", redirectdepth = 0): Container =
-  let attrs = getWindowAttributes(stdout)
-  let ostream = forkserver.ostream
-  let istream = forkserver.istream
-  ostream.swrite(FORK_BUFFER)
-  ostream.swrite(source)
-  ostream.swrite(config)
-  ostream.swrite(attrs)
-  ostream.swrite(mainproc)
-  ostream.flush()
-  var process: Pid
-  istream.sread(process)
-  return Container(
-    source: source,
-    width: attrs.width,
-    height: attrs.height - 1,
-    title: title,
-    config: config,
-    redirectdepth: redirectdepth,
-    process: process,
-    pos: CursorPosition(
-      setx: -1
-    )
-  )
-
-func charset*(container: Container): Charset =
-  return container.source.charset
-
-func contentType*(container: Container): Option[string] {.jsfget.} =
-  return container.source.contenttype
-
-func location*(container: Container): URL {.jsfget.} =
-  return container.source.location
-
-func lineLoaded(container: Container, y: int): bool =
-  return y - container.lineshift in 0..container.lines.high
-
-func getLine(container: Container, y: int): SimpleFlexibleLine =
-  if container.lineLoaded(y):
-    return container.lines[y - container.lineshift]
-
-iterator ilines*(container: Container, slice: Slice[int]): SimpleFlexibleLine {.inline.} =
-  for y in slice:
-    yield container.getLine(y)
-
-func cursorx*(container: Container): int {.inline.} = container.pos.cursorx
-func cursory*(container: Container): int {.inline.} = container.pos.cursory
-func fromx*(container: Container): int {.inline.} = container.pos.fromx
-func fromy*(container: Container): int {.inline.} = container.pos.fromy
-func xend(container: Container): int {.inline.} = container.pos.xend
-func lastVisibleLine(container: Container): int = min(container.fromy + container.height, container.numLines) - 1
-
-func currentLine(container: Container): string =
-  return container.getLine(container.cursory).str
-
-func cursorBytes(container: Container, y: int, cc = container.cursorx): int =
-  let line = container.getLine(y).str
-  var w = 0
-  var i = 0
-  while i < line.len and w < cc:
-    var r: Rune
-    fastRuneAt(line, i, r)
-    w += r.twidth(w)
-  return i
-
-func currentCursorBytes(container: Container, cc = container.cursorx): int =
-  return container.cursorBytes(container.cursory, cc)
-
-# Returns the X position of the first cell occupied by the character the cursor
-# currently points to.
-func cursorFirstX(container: Container): int =
-  if container.numLines == 0: return 0
-  let line = container.currentLine
-  var w = 0
-  var i = 0
-  var r: Rune
-  let cc = container.cursorx
-  while i < line.len:
-    fastRuneAt(line, i, r)
-    let tw = r.twidth(w)
-    if w + tw > cc:
-      return w
-    w += tw
-
-# Returns the X position of the last cell occupied by the character the cursor
-# currently points to.
-func cursorLastX(container: Container): int =
-  if container.numLines == 0: return 0
-  let line = container.currentLine
-  var w = 0
-  var i = 0
-  var r: Rune
-  let cc = container.cursorx
-  while i < line.len and w <= cc:
-    fastRuneAt(line, i, r)
-    w += r.twidth(w)
-  return max(w - 1, 0)
-
-# Last cell for tab, first cell for everything else (e.g. double width.)
-# This is needed because moving the cursor to the 2nd cell of a double
-# width character clears it on some terminals.
-func cursorDispX(container: Container): int =
-  if container.numLines == 0: return 0
-  let line = container.currentLine
-  if line.len == 0: return 0
-  var w = 0
-  var pw = 0
-  var i = 0
-  var r: Rune
-  let cc = container.cursorx
-  while i < line.len and w <= cc:
-    fastRuneAt(line, i, r)
-    pw = w
-    w += r.twidth(w)
-  if r == Rune('\t'):
-    return max(w - 1, 0)
-  else:
-    return pw
-
-func acursorx*(container: Container): int =
-  max(0, container.cursorDispX() - container.fromx)
-
-func acursory*(container: Container): int =
-  container.cursory - container.fromy
-
-func maxScreenWidth(container: Container): int =
-  for line in container.ilines(container.fromy..container.lastVisibleLine):
-    result = max(line.str.width(), result)
-
-func getTitle*(container: Container): string {.jsfunc.} =
-  if container.title != "":
-    return container.title
-  return container.source.location.serialize(excludepassword = true)
-
-func currentLineWidth(container: Container): int =
-  if container.numLines == 0: return 0
-  return container.currentLine.width()
-
-func maxfromy(container: Container): int = max(container.numLines - container.height, 0)
-
-func maxfromx(container: Container): int = max(container.maxScreenWidth() - container.width, 0)
-
-func atPercentOf*(container: Container): int =
-  if container.numLines == 0: return 100
-  return (100 * (container.cursory + 1)) div container.numLines
-
-func lineWindow(container: Container): Slice[int] =
-  if container.numLines == 0: # not loaded
-    return 0..container.height * 5
-  let n = (container.height * 5) div 2
-  var x = container.fromy - n + container.height div 2
-  var y = container.fromy + n + container.height div 2
-  if y >= container.numLines:
-    x -= y - container.numLines
-    y = container.numLines
-  if x < 0:
-    y += -x
-    x = 0
-  return x .. y
-
-func contains*(hl: Highlight, x, y: int): bool =
-  if hl.rect:
-    let rx = hl.x .. hl.endx
-    let ry = hl.y .. hl.endy
-    return x in rx and y in ry
-  else:
-    return (y > hl.y or y == hl.y and x >= hl.x) and
-      (y < hl.endy or y == hl.endy and x <= hl.endx)
-
-func contains*(hl: Highlight, y: int): bool =
-  return y in hl.y .. hl.endy
-
-func colorArea*(hl: Highlight, y: int, limitx: Slice[int]): Slice[int] =
-  if hl.rect:
-    if y in hl.y .. hl.endy:
-      return max(hl.x, limitx.a) .. min(hl.endx, limitx.b)
-  else:
-    if y in hl.y + 1 .. hl.endy - 1:
-      return limitx
-    if y == hl.y and y == hl.endy:
-      return max(hl.x, limitx.a) .. min(hl.endx, limitx.b)
-    if y == hl.y:
-      return max(hl.x, limitx.a) .. limitx.b
-    if y == hl.endy:
-      return limitx.a .. min(hl.endx, limitx.b)
-
-func findHighlights*(container: Container, y: int): seq[Highlight] =
-  for hl in container.highlights:
-    if y in hl:
-      result.add(hl)
-
-func getHoverText*(container: Container): string =
-  for t in HoverType:
-    if container.hovertext[t] != "":
-      return container.hovertext[t]
-
-func isHoverURL*(container: Container, url: URL): bool =
-  let hoverurl = parseURL(container.hovertext[HOVER_LINK])
-  return hoverurl.isSome and url.host == hoverurl.get.host
-
-proc triggerEvent(container: Container, event: ContainerEvent) =
-  container.events.addLast(event)
-
-proc triggerEvent(container: Container, t: ContainerEventType) =
-  container.triggerEvent(ContainerEvent(t: t))
-
-proc updateCursor(container: Container)
-
-proc setNumLines(container: Container, lines: int, finish = false) =
-  if container.numLines != lines:
-    container.numLines = lines
-    if container.startpos.isSome and finish:
-      container.pos = container.startpos.get
-      container.startpos = none(CursorPosition)
-    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]]) =
-    container.lines.setLen(w.len)
-    container.lineshift = w.a
-    for y in 0 ..< min(res.lines.len, w.len):
-      container.lines[y] = res.lines[y]
-      container.lines[y].str.mnormalize()
-    container.updateCursor()
-    if res.numLines != container.numLines:
-      container.setNumLines(res.numLines, true)
-    let cw = container.fromy ..< container.fromy + container.height
-    if w.a in cw or w.b in cw or cw.a in w or cw.b in w:
-      container.triggerEvent(UPDATE))
-
-proc redraw(container: Container) {.jsfunc.} =
-  container.triggerEvent(ContainerEvent(t: UPDATE, force: true))
-
-proc sendCursorPosition*(container: Container) =
-  container.iface.updateHover(container.cursorx, container.cursory)
-      .then(proc(res: UpdateHoverResult) =
-    if res.link.isSome:
-      container.hovertext[HOVER_LINK] = res.link.get
-    if res.title.isSome:
-      container.hovertext[HOVER_TITLE] = res.title.get
-    if res.link.isSome or res.title.isSome:
-      container.triggerEvent(STATUS)
-    if res.repaint:
-      container.needslines = true)
-
-proc setFromY(container: Container, y: int) {.jsfunc.} =
-  if container.pos.fromy != y:
-    container.pos.fromy = max(min(y, container.maxfromy), 0)
-    container.needslines = true
-    container.triggerEvent(UPDATE)
-
-proc setFromX(container: Container, x: int, refresh = true) {.jsfunc.} =
-  if container.pos.fromx != x:
-    container.pos.fromx = max(min(x, container.maxfromx), 0)
-    if container.pos.fromx > container.cursorx:
-      container.pos.cursorx = min(container.pos.fromx, container.currentLineWidth())
-      if refresh:
-        container.sendCursorPosition()
-    container.triggerEvent(UPDATE)
-
-proc setFromXY(container: Container, x, y: int) {.jsfunc.} =
-  container.setFromY(y)
-  container.setFromX(x)
-
-proc setCursorX(container: Container, x: int, refresh = true, save = true) {.jsfunc.} =
-  if not container.lineLoaded(container.cursory):
-    container.pos.setx = x
-    container.pos.setxrefresh = refresh
-    return
-  container.pos.setx = -1
-  let cw = container.currentLineWidth()
-  let x2 = x
-  let x = max(min(x, cw - 1), 0)
-  if not refresh or container.fromx <= x and x < container.fromx + container.width:
-    container.pos.cursorx = x
-  elif refresh and container.fromx > x:
-    if x2 < container.cursorx:
-      container.setFromX(x, false)
-    container.pos.cursorx = container.fromx
-  elif x > container.cursorx:
-    container.setFromX(max(x - container.width + 1, container.fromx), false)
-    container.pos.cursorx = x
-  elif x < container.cursorx:
-    container.setFromX(x, false)
-    container.pos.cursorx = x
-  if refresh:
-    container.sendCursorPosition()
-  if save:
-    container.pos.xend = container.cursorx
-
-proc restoreCursorX(container: Container) {.jsfunc.} =
-  let x = clamp(container.currentLineWidth() - 1, 0, container.xend)
-  container.setCursorX(x, false, false)
-
-proc setCursorY(container: Container, y: int, refresh = true) {.jsfunc.} =
-  let y = max(min(y, container.numLines - 1), 0)
-  if container.cursory == y: return
-  if y - container.fromy >= 0 and y - container.height < container.fromy:
-    container.pos.cursory = y
-  else:
-    if y > container.cursory:
-      container.setFromY(y - container.height + 1)
-    else:
-      container.setFromY(y)
-    container.pos.cursory = y
-  container.restoreCursorX()
-  if refresh:
-    container.sendCursorPosition()
-
-proc centerLine(container: Container) {.jsfunc.} =
-  container.setFromY(container.cursory - container.height div 2)
-
-proc centerColumn(container: Container) {.jsfunc.} =
-  container.setFromX(container.cursorx - container.width div 2)
-
-proc setCursorXY(container: Container, x, y: int, refresh = true) {.jsfunc.} =
-  let fy = container.fromy
-  container.setCursorY(y, refresh)
-  container.setCursorX(x, refresh)
-  if fy != container.fromy:
-    container.centerLine()
-
-proc cursorDown(container: Container, n = 1) {.jsfunc.} =
-  if container.select.open:
-    container.select.cursorDown()
-  else:
-    container.setCursorY(container.cursory + n)
-
-proc cursorUp(container: Container, n = 1) {.jsfunc.} =
-  if container.select.open:
-    container.select.cursorUp()
-  else:
-    container.setCursorY(container.cursory - n)
-
-proc cursorLeft(container: Container, n = 1) {.jsfunc.} =
-  if container.select.open:
-    container.select.cursorLeft()
-  else:
-    container.setCursorX(container.cursorFirstX() - n)
-
-proc cursorRight(container: Container, n = 1) {.jsfunc.} =
-  if container.select.open:
-    container.select.cursorRight()
-  else:
-    container.setCursorX(container.cursorLastX() + n)
-
-proc cursorLineBegin(container: Container) {.jsfunc.} =
-  container.setCursorX(0)
-
-proc cursorLineTextStart(container: Container) {.jsfunc.} =
-  if container.numLines == 0: return
-  var x = 0
-  for r in container.currentLine.runes:
-    if not r.isWhitespace():
-      break
-    x += r.twidth(x)
-  container.setCursorX(x)
-
-proc cursorLineEnd(container: Container) {.jsfunc.} =
-  container.setCursorX(container.currentLineWidth() - 1)
-
-proc cursorNextWord(container: Container) {.jsfunc.} =
-  if container.numLines == 0: return
-  var r: Rune
-  var b = container.currentCursorBytes()
-  var x = container.cursorx
-  while b < container.currentLine.len:
-    let pb = b
-    fastRuneAt(container.currentLine, b, r)
-    if r.breaksWord():
-      b = pb
-      break
-    x += r.twidth(x)
-
-  while b < container.currentLine.len:
-    let pb = b
-    fastRuneAt(container.currentLine, b, r)
-    if not r.breaksWord():
-      b = pb
-      break
-    x += r.twidth(x)
-
-  if b < container.currentLine.len:
-    container.setCursorX(x)
-  else:
-    if container.cursory < container.numLines - 1:
-      container.cursorDown()
-      container.cursorLineBegin()
-    else:
-      container.cursorLineEnd()
-
-proc cursorPrevWord(container: Container) {.jsfunc.} =
-  if container.numLines == 0: return
-  var b = container.currentCursorBytes()
-  var x = container.cursorx
-  if container.currentLine.len > 0:
-    b = min(b, container.currentLine.len - 1)
-    while b >= 0:
-      let (r, o) = lastRune(container.currentLine, b)
-      if r.breaksWord():
-        break
-      b -= o
-      x -= r.twidth(x)
-
-    while b >= 0:
-      let (r, o) = lastRune(container.currentLine, b)
-      if not r.breaksWord():
-        break
-      b -= o
-      x -= r.twidth(x)
-  else:
-    b = -1
-
-  if b >= 0:
-    container.setCursorX(x)
-  else:
-    if container.cursory > 0:
-      container.cursorUp()
-      container.cursorLineEnd()
-    else:
-      container.cursorLineBegin()
-
-proc pageDown(container: Container) {.jsfunc.} =
-  container.setFromY(container.fromy + container.height)
-  container.setCursorY(container.cursory + container.height)
-  container.restoreCursorX()
-
-proc pageUp(container: Container) {.jsfunc.} =
-  container.setFromY(container.fromy - container.height)
-  container.setCursorY(container.cursory - container.height)
-  container.restoreCursorX()
-
-proc pageLeft(container: Container) {.jsfunc.} =
-  container.setFromX(container.fromx - container.width)
-
-proc pageRight(container: Container) {.jsfunc.} =
-  container.setFromX(container.fromx + container.width)
-
-proc halfPageUp(container: Container) {.jsfunc.} =
-  container.setFromY(container.fromy - container.height div 2 + 1)
-  container.setCursorY(container.cursory - container.height div 2 + 1)
-  container.restoreCursorX()
-
-proc halfPageDown(container: Container) {.jsfunc.} =
-  container.setFromY(container.fromy + container.height div 2 - 1)
-  container.setCursorY(container.cursory + container.height div 2 - 1)
-  container.restoreCursorX()
-
-proc cursorFirstLine(container: Container) {.jsfunc.} =
-  if container.select.open:
-    container.select.cursorFirstLine()
-  else:
-    container.setCursorY(0)
-
-proc cursorLastLine*(container: Container) {.jsfunc.} =
-  if container.select.open:
-    container.select.cursorLastLine()
-  else:
-    container.setCursorY(container.numLines - 1)
-
-proc cursorTop(container: Container) {.jsfunc.} =
-  container.setCursorY(container.fromy)
-
-proc cursorMiddle(container: Container) {.jsfunc.} =
-  container.setCursorY(container.fromy + (container.height - 2) div 2)
-
-proc cursorBottom(container: Container) {.jsfunc.} =
-  container.setCursorY(container.fromy + container.height - 1)
-
-proc cursorLeftEdge(container: Container) {.jsfunc.} =
-  container.setCursorX(container.fromx)
-
-proc cursorMiddleColumn(container: Container) {.jsfunc.} =
-  container.setCursorX(container.fromx + (container.width - 2) div 2)
-
-proc cursorRightEdge(container: Container) {.jsfunc.} =
-  container.setCursorX(container.fromx + container.width - 1)
-
-proc scrollDown(container: Container) {.jsfunc.} =
-  if container.fromy + container.height < container.numLines:
-    container.setFromY(container.fromy + 1)
-    if container.fromy > container.cursory:
-      container.cursorDown()
-  else:
-    container.cursorDown()
-
-proc scrollUp(container: Container) {.jsfunc.} =
-  if container.fromy > 0:
-    container.setFromY(container.fromy - 1)
-    if container.fromy + container.height <= container.cursory:
-      container.cursorUp()
-  else:
-    container.cursorUp()
-
-proc scrollRight(container: Container) {.jsfunc.} =
-  if container.fromx + container.width < container.maxScreenWidth():
-    container.setFromX(container.fromx + 1)
-
-proc scrollLeft(container: Container) {.jsfunc.} =
-  if container.fromx > 0:
-    container.setFromX(container.fromx - 1)
-
-proc alert(container: Container, msg: string) =
-  container.triggerEvent(ContainerEvent(t: ALERT, msg: msg))
-
-proc lineInfo(container: Container) {.jsfunc.} =
-  container.alert("line " & $(container.cursory + 1) & "/" &
-    $container.numLines & " (" & $container.atPercentOf() & "%) col " &
-    $(container.cursorx + 1) & "/" & $container.currentLineWidth &
-    " (byte " & $container.currentCursorBytes & ")")
-
-proc updateCursor(container: Container) =
-  if container.pos.setx > -1:
-    container.setCursorX(container.pos.setx, container.pos.setxrefresh)
-  if container.fromy > container.maxfromy:
-    container.setFromY(container.maxfromy)
-  if container.cursory >= container.numLines:
-    container.setCursorY(container.lastVisibleLine)
-    container.alert("Last line is #" & $container.numLines)
-
-proc gotoLine*[T: string|int](container: Container, s: T) =
-  when s is string:
-    if s == "":
-      redraw(container)
-    elif s[0] == '^':
-      container.cursorFirstLine()
-    elif s[0] == '$':
-      container.cursorLastLine()
-    else:
-      let i = parseUInt32(s)
-      if i.isSome and i.get > 0:
-        container.setCursorY(int(i.get - 1))
-      else:
-        container.alert("First line is #1") # :)
-  else:
-    container.setCursorY(s - 1)
-
-proc pushCursorPos*(container: Container) =
-  if container.select.open:
-    container.select.pushCursorPos()
-  else:
-    container.bpos.add(container.pos)
-
-proc popCursorPos*(container: Container, nojump = false) =
-  if container.select.open:
-    container.select.popCursorPos(nojump)
-  else:
-    container.pos = container.bpos.pop()
-    if not nojump:
-      container.updateCursor()
-      container.sendCursorPosition()
-      container.needslines = true
-
-proc copyCursorPos*(container, c2: Container) =
-  container.startpos = some(c2.pos)
-  container.hasstart = true
-
-proc cursorNextLink*(container: Container) {.jsfunc.} =
-  container.iface
-    .findNextLink(container.cursorx, container.cursory)
-    .then(proc(res: tuple[x, y: int]) =
-      if res.x > -1 and res.y != -1:
-        container.setCursorXY(res.x, res.y))
-
-proc cursorPrevLink*(container: Container) {.jsfunc.} =
-  container.iface
-    .findPrevLink(container.cursorx, container.cursory)
-    .then(proc(res: tuple[x, y: int]) =
-      if res.x > -1 and res.y != -1:
-        container.setCursorXY(res.x, res.y))
-
-proc clearSearchHighlights*(container: Container) =
-  for i in countdown(container.highlights.high, 0):
-    if container.highlights[i].clear:
-      container.highlights.del(i)
-
-proc onMatch(container: Container, res: BufferMatch, refresh: bool) =
-  if res.success:
-    container.setCursorXY(res.x, res.y, refresh)
-    if container.hlon:
-      container.clearSearchHighlights()
-      let ex = res.x + res.str.twidth(res.x) - 1
-      let hl = Highlight(x: res.x, y: res.y, endx: ex, endy: res.y, clear: true)
-      container.highlights.add(hl)
-      container.triggerEvent(UPDATE)
-      container.hlon = false
-  elif container.hlon:
-    container.clearSearchHighlights()
-    container.triggerEvent(UPDATE)
-    container.needslines = true
-    container.hlon = false
-
-proc cursorNextMatch*(container: Container, regex: Regex, wrap, refresh: bool):
-    EmptyPromise {.discardable.} =
-  if container.select.open:
-    container.select.cursorNextMatch(regex, wrap)
-    return newResolvedPromise()
-  else:
-    return container.iface
-      .findNextMatch(regex, container.cursorx, container.cursory, wrap)
-      .then(proc(res: BufferMatch) =
-        container.onMatch(res, refresh))
-
-proc cursorPrevMatch*(container: Container, regex: Regex, wrap, refresh: bool):
-    EmptyPromise {.discardable.} =
-  if container.select.open:
-    container.select.cursorPrevMatch(regex, wrap)
-    return newResolvedPromise()
-  else:
-    return container.iface
-      .findPrevMatch(regex, container.cursorx, container.cursory, wrap)
-      .then(proc(res: BufferMatch) =
-        container.onMatch(res, refresh))
-
-proc setLoadInfo(container: Container, msg: string) =
-  container.loadinfo = msg
-  container.triggerEvent(STATUS)
-
-#TODO TODO TODO this should be called with a timeout.
-proc onload*(container: Container, res: LoadResult) =
-  if container.canceled:
-    container.setLoadInfo("")
-    #TODO we wouldn't need the then part if we had incremental rendering of
-    # HTML.
-    container.iface.cancel().then(proc(lines: int) =
-      container.setNumLines(lines)
-      container.needslines = true)
-  else:
-    if res.bytes == -1 or res.atend:
-      container.setLoadInfo("")
-    elif not res.atend:
-      container.setLoadInfo(convert_size(res.bytes) & " loaded")
-    if res.lines > container.numLines:
-      container.setNumLines(res.lines)
-      container.triggerEvent(STATUS)
-      container.needslines = true
-    if not res.atend:
-      discard container.iface.load().then(proc(res: LoadResult) =
-        container.onload(res))
-    else:
-      container.iface.getTitle().then(proc(title: string): auto =
-        if title != "":
-          container.title = title
-          container.triggerEvent(TITLE)
-        return container.iface.render()
-      ).then(proc(lines: int): auto =
-        container.setNumLines(lines, true)
-        container.needslines = true
-        container.triggerEvent(LOADED)
-        if not container.hasstart and container.source.location.anchor != "":
-          return container.iface.gotoAnchor()
-      ).then(proc(res: tuple[x, y: int]) =
-        if res.x != -1 and res.y != -1:
-          container.setCursorXY(res.x, res.y))
-
-proc load(container: Container) =
-  container.setLoadInfo("Connecting to " & container.location.host & "...")
-  container.iface.connect().then(proc(res: ConnectResult) =
-    let info = container.loadinfo
-    if not res.invalid:
-      container.code = res.code
-      if res.code == 0:
-        container.triggerEvent(SUCCESS)
-        # accept cookies
-        if res.cookies.len > 0 and container.config.cookiejar != nil:
-          container.config.cookiejar.add(res.cookies)
-        if res.referrerpolicy.isSome and container.config.referer_from:
-          container.config.referrerpolicy = res.referrerpolicy.get
-        container.setLoadInfo("Connected to " & $container.source.location & ". Downloading...")
-        if res.needsAuth:
-          container.triggerEvent(NEEDS_AUTH)
-        if res.redirect != nil:
-          container.triggerEvent(ContainerEvent(t: REDIRECT, request: res.redirect))
-        container.source.charset = res.charset
-        if res.contentType == "application/octet-stream":
-          let contentType = guessContentType(container.location.pathname,
-            "application/octet-stream", container.config.mimeTypes)
-          if contentType != "application/octet-stream":
-            container.iface.setContentType(contentType)
-          container.source.contenttype = some(contentType)
-        elif res.contentType != "":
-          container.source.contenttype = some(res.contentType)
-        container.triggerEvent(CHECK_MAILCAP)
-      else:
-        container.setLoadInfo("")
-        container.triggerEvent(FAIL)
-    else:
-      container.setLoadInfo(info)
-  )
-
-proc startload*(container: Container) =
-  container.iface.load()
-    .then(proc(res: tuple[atend: bool, lines, bytes: int]) =
-      container.onload(res))
-
-proc connect2*(container: Container): EmptyPromise =
-  return container.iface.connect2()
-
-proc redirectToFd*(container: Container, fdin: FileHandle, wait: bool):
-    EmptyPromise =
-  return container.iface.redirectToFd(fdin, wait)
-
-proc readFromFd*(container: Container, fdout: FileHandle, ishtml: bool):
-    EmptyPromise =
-  return container.iface.readFromFd(fdout, ishtml)
-
-proc quit*(container: Container) =
-  container.triggerEvent(QUIT)
-
-proc cancel*(container: Container) {.jsfunc.} =
-  if container.select.open:
-    container.select.cancel()
-  else:
-    container.canceled = true
-    container.alert("Canceled loading")
-
-proc findAnchor*(container: Container, anchor: string) =
-  container.iface.findAnchor(anchor).then(proc(found: bool) =
-    if found:
-      container.triggerEvent(ContainerEvent(t: ANCHOR, anchor: anchor))
-    else:
-      container.triggerEvent(NO_ANCHOR))
-
-proc readCanceled*(container: Container) =
-  container.iface.readCanceled().then(proc(repaint: bool) =
-    if repaint:
-      container.needslines = true)
-
-proc readSuccess*(container: Container, s: string) =
-  container.iface.readSuccess(s).then(proc(res: ReadSuccessResult) =
-    if res.repaint:
-      container.needslines = true
-    if res.open.isSome:
-      container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get)))
-
-proc reshape(container: Container): EmptyPromise {.discardable, jsfunc.} =
-  return container.iface.render().then(proc(lines: int): auto =
-    container.setNumLines(lines)
-    return container.requestLines())
-
-proc pipeBuffer*(container, pipeTo: Container) =
-  container.iface.getSource().then(proc() =
-    pipeTo.load() #TODO do not load if pipeTo is killed first?
-  )
-
-proc onclick(container: Container, res: ClickResult)
-
-proc displaySelect(container: Container, selectResult: SelectResult) =
-  let submitSelect = proc(selected: seq[int]) =
-    container.iface.select(selected).then(proc(res: ClickResult) =
-      container.onclick(res))
-  container.select.initSelect(selectResult, container.acursorx,
-    container.acursory, container.height, submitSelect)
-  container.triggerEvent(UPDATE)
-
-proc onclick(container: Container, res: ClickResult) =
-  if res.repaint:
-    container.needslines = true
-  if res.open.isSome:
-    container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get))
-  if res.select.isSome:
-    container.displaySelect(res.select.get)
-  if res.readline.isSome:
-    let rl = res.readline.get
-    let event = if rl.area:
-      ContainerEvent(
-        t: READ_AREA,
-        tvalue: rl.value
-      )
-    else:
-      ContainerEvent(
-        t: READ_LINE,
-        prompt: rl.prompt,
-        value: rl.value,
-        password: rl.hide
-      )
-    container.triggerEvent(event)
-
-proc click(container: Container) {.jsfunc.} =
-  if container.select.open:
-    container.select.click()
-  else:
-    container.iface.click(container.cursorx, container.cursory)
-      .then(proc(res: ClickResult) = container.onclick(res))
-
-proc windowChange*(container: Container, attrs: WindowAttributes) =
-  if attrs.width != container.width or attrs.height - 1 != container.height:
-    container.width = attrs.width
-    container.height = attrs.height - 1
-    container.iface.windowChange(attrs).then(proc(): auto =
-      container.needslines = true
-      return container.iface.render()
-    ).then(proc(lines: int) =
-      if lines != container.numLines:
-        container.setNumLines(lines, true)
-      container.needslines = true)
-
-proc peek(container: Container) {.jsfunc.} =
-  container.alert($container.source.location)
-
-proc clearHover*(container: Container) =
-  container.lastpeek = low(HoverType)
-
-proc peekCursor(container: Container) {.jsfunc.} =
-  var p = container.lastpeek
-  while true:
-    if container.hovertext[p] != "":
-      container.alert($p & ": " & container.hovertext[p])
-      break
-    if p < high(HoverType):
-      inc p
-    else:
-      p = low(HoverType)
-    if p == container.lastpeek: break
-  if container.lastpeek < high(HoverType):
-    inc container.lastpeek
-  else:
-    container.lastpeek = low(HoverType)
-
-proc handleCommand(container: Container) =
-  var packetid, len: int
-  container.iface.stream.sread(len)
-  container.iface.stream.sread(packetid)
-  container.iface.resolve(packetid, len - slen(packetid))
-
-proc setStream*(container: Container, stream: Stream) =
-  container.iface = newBufferInterface(stream)
-  if container.source.t == LOAD_PIPE:
-    container.iface.passFd(container.source.fd).then(proc() =
-      discard close(container.source.fd))
-    stream.flush()
-  container.load()
-
-proc onreadline(container: Container, w: Slice[int], handle: (proc(line: SimpleFlexibleLine)), res: GetLinesResult) =
-  for line in res.lines:
-    handle(line)
-  if res.numLines > w.b + 1:
-    var w = w
-    w.a += 24
-    w.b += 24
-    container.iface.getLines(w).then(proc(res: GetLinesResult) =
-      container.onreadline(w, handle, res))
-  else:
-    container.setNumLines(res.numLines, true)
-
-# Synchronously read all lines in the buffer.
-proc readLines*(container: Container, handle: (proc(line: SimpleFlexibleLine))) =
-  if container.code == 0:
-    # load succeded
-    let w = 0 .. 23
-    container.iface.getLines(w).then(proc(res: GetLinesResult) =
-      container.onreadline(w, handle, res))
-    while container.iface.hasPromises:
-      # fulfill all promises
-      container.handleCommand()
-
-proc drawLines*(container: Container, display: var FixedGrid,
-    hlcolor: CellColor) =
-  var r: Rune
-  var by = 0
-  let endy = min(container.fromy + display.height, container.numLines)
-  for line in container.ilines(container.fromy ..< endy):
-    var w = 0 # width of the row so far
-    var i = 0 # byte in line.str
-    # Skip cells till fromx.
-    while w < container.fromx and i < line.str.len:
-      fastRuneAt(line.str, i, r)
-      w += r.twidth(w)
-    let dls = by * display.width # starting position of row in display
-    # Fill in the gap in case we skipped more cells than fromx mandates (i.e.
-    # we encountered a double-width character.)
-    var k = 0
-    if w > container.fromx:
-      while k < w - container.fromx:
-        display[dls + k].str &= ' '
-        inc k
-    var cf = line.findFormat(w)
-    var nf = line.findNextFormat(w)
-    let startw = w # save this for later
-    # Now fill in the visible part of the row.
-    while i < line.str.len:
-      let pw = w
-      fastRuneAt(line.str, i, r)
-      let rw = r.twidth(w)
-      w += rw
-      if w > container.fromx + display.width:
-        break # die on exceeding the width limit
-      if nf.pos != -1 and nf.pos <= pw:
-        cf = nf
-        nf = line.findNextFormat(pw)
-      if cf.pos != -1:
-        display[dls + k].format = cf.format
-      if r == Rune('\t'):
-        # Needs to be replaced with spaces, otherwise bgcolor isn't displayed.
-        let tk = k + rw
-        while k < tk:
-          display[dls + k].str &= ' '
-          inc k
-      else:
-        display[dls + k].str &= r
-        k += rw
-    # Finally, override cell formatting for highlighted cells.
-    let hls = container.findHighlights(container.fromy + by)
-    let aw = container.width - (startw - container.fromx) # actual width
-    for hl in hls:
-      let area = hl.colorArea(container.fromy + by, startw .. startw + aw)
-      for i in area:
-        var hlformat = display[dls + i - startw].format
-        hlformat.bgcolor = hlcolor
-        display[dls + i - startw].format = hlformat
-    inc by
-
-proc handleEvent*(container: Container) =
-  container.handleCommand()
-  if container.needslines:
-    container.requestLines()
-    container.needslines = false
-
-proc addContainerModule*(ctx: JSContext) =
-  ctx.registerType(Container, name = "Buffer")