about summary refs log tree commit diff stats
path: root/src/buffer
diff options
context:
space:
mode:
Diffstat (limited to 'src/buffer')
-rw-r--r--src/buffer/container.nim1024
-rw-r--r--src/buffer/select.nim306
2 files changed, 0 insertions, 1330 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")
diff --git a/src/buffer/select.nim b/src/buffer/select.nim
deleted file mode 100644
index f7afa4d9..00000000
--- a/src/buffer/select.nim
+++ /dev/null
@@ -1,306 +0,0 @@
-import unicode
-
-import buffer/buffer
-import buffer/cell
-import js/regex
-import utils/twtstr
-
-type
-  SubmitSelect* = proc(selected: seq[int])
-  CloseSelect* = proc()
-
-  Select* = object
-    open*: bool
-    options: seq[string]
-    multiple: bool
-    # old selection
-    oselected*: seq[int]
-    # new selection
-    selected*: seq[int]
-    # cursor distance from y
-    cursor: int
-    # widest option
-    maxw: int
-    # maximum height on screen (yes the naming is dumb)
-    maxh: int
-    # first index to display
-    si: int
-    # location on screen
-    x: int
-    y: int
-    redraw*: bool
-    submitFun: SubmitSelect
-    bpos: seq[int]
-
-proc windowChange*(select: var Select, height: int) =
-  select.maxh = height - 2
-  if select.y + select.options.len >= select.maxh:
-    select.y = height - select.options.len
-    if select.y < 0:
-      select.si = -select.y
-      select.y = 0
-  if select.selected.len > 0:
-    let i = select.selected[0]
-    if select.si > i:
-      select.si = i
-    elif select.si + select.maxh < i:
-      select.si = max(i - select.maxh, 0)
-  select.redraw = true
-
-proc initSelect*(select: var Select, selectResult: SelectResult,
-    x, y, height: int, submitFun: SubmitSelect) =
-  select.open = true
-  select.multiple = selectResult.multiple
-  select.options = selectResult.options
-  select.oselected = selectResult.selected
-  select.selected = selectResult.selected
-  select.submitFun = submitFun
-  for opt in select.options.mitems:
-    opt.mnormalize()
-    select.maxw = max(select.maxw, opt.width())
-  select.x = x
-  select.y = y
-  select.windowChange(height)
-
-# index of option currently under cursor
-func hover(select: Select): int =
-  return select.cursor + select.si
-
-func dispheight(select: Select): int =
-  return select.maxh - select.y
-
-proc `hover=`(select: var Select, i: int) =
-  let i = clamp(i, 0, select.options.high)
-  if i >= select.si + select.dispheight:
-    select.si = i - select.dispheight + 1
-    select.cursor = select.dispheight - 1
-  elif i < select.si:
-    select.si = i
-    select.cursor = 0
-  else:
-    select.cursor = i - select.si
-
-proc cursorDown*(select: var Select) =
-  if select.hover < select.options.high and
-      select.cursor + select.y < select.maxh - 1:
-    inc select.cursor
-    select.redraw = true
-  elif select.si < select.options.len - select.maxh:
-    inc select.si
-    select.redraw = true
-
-proc cursorUp*(select: var Select) =
-  if select.cursor > 0:
-    dec select.cursor
-    select.redraw = true
-  elif select.si > 0:
-    dec select.si
-    select.redraw = true
-  elif select.multiple and select.cursor > -1:
-    select.cursor = -1
-
-proc close(select: var Select) =
-  select = Select()
-
-proc cancel*(select: var Select) =
-  select.submitFun(select.oselected)
-  select.close()
-
-proc submit(select: var Select) =
-  select.submitFun(select.selected)
-  select.close()
-
-proc click*(select: var Select) =
-  if not select.multiple:
-    select.selected = @[select.hover]
-    select.submit()
-  elif select.cursor == -1:
-    select.submit()
-  else:
-    var k = select.selected.len
-    let i = select.hover
-    for j in 0 ..< select.selected.len:
-      if select.selected[j] >= i:
-        k = j
-        break
-    if k < select.selected.len and select.selected[k] == i:
-      select.selected.delete(k)
-    else:
-      select.selected.insert(i, k)
-    select.redraw = true
-
-proc cursorLeft*(select: var Select) =
-  select.submit()
-
-proc cursorRight*(select: var Select) =
-  select.click()
-
-proc getCursorX*(select: var Select): int =
-  if select.cursor == -1:
-    return select.x
-  return select.x + 1
-
-proc getCursorY*(select: var Select): int =
-  return select.y + 1 + select.cursor
-
-proc cursorFirstLine*(select: var Select) =
-  if select.cursor != 0 or select.si != 0:
-    select.cursor = 0
-    select.si = 0
-    select.redraw = true
-
-proc cursorLastLine*(select: var Select) =
-  if select.hover < select.options.len:
-    select.cursor = select.dispheight - 1
-    select.si = max(select.options.len - select.maxh, 0)
-    select.redraw = true
-
-proc cursorNextMatch*(select: var Select, regex: Regex, wrap: bool) =
-  var j = -1
-  for i in select.hover + 1 ..< select.options.len:
-    if regex.exec(select.options[i]).success:
-      j = i
-      break
-  if j != -1:
-    select.hover = j
-    select.redraw = true
-  elif wrap:
-    for i in 0 ..< select.hover:
-      if regex.exec(select.options[i]).success:
-        j = i
-        break
-    if j != -1:
-      select.hover = j
-      select.redraw = true
-
-proc cursorPrevMatch*(select: var Select, regex: Regex, wrap: bool) =
-  var j = -1
-  for i in countdown(select.hover - 1, 0):
-    if regex.exec(select.options[i]).success:
-      j = i
-      break
-  if j != -1:
-    select.hover = j
-    select.redraw = true
-  elif wrap:
-    for i in countdown(select.options.high, select.hover):
-      if regex.exec(select.options[i]).success:
-        j = i
-        break
-    if j != -1:
-      select.hover = j
-      select.redraw = true
-
-proc pushCursorPos*(select: var Select) =
-  select.bpos.add(select.hover)
-
-proc popCursorPos*(select: var Select, nojump = false) =
-  select.hover = select.bpos.pop()
-  if not nojump:
-    select.redraw = true
-
-const HorizontalBar = $Rune(0x2500)
-const VerticalBar = $Rune(0x2502)
-const CornerTopLeft = $Rune(0x250C)
-const CornerTopRight = $Rune(0x2510)
-const CornerBottomLeft = $Rune(0x2514)
-const CornerBottomRight = $Rune(0x2518)
-
-proc drawBorders(display: var FixedGrid, sx, ex, sy, ey: int,
-    upmore, downmore: bool) =
-  for y in sy .. ey:
-    var x = 0
-    while x < sx:
-      if display[y * display.width + x].str == "":
-        display[y * display.width + x].str = " "
-        inc x
-      else:
-        #x = display[y * display.width + x].str.twidth(x)
-        inc x
-  # Draw corners.
-  let tl = if upmore: VerticalBar else: CornerTopLeft
-  let tr = if upmore: VerticalBar else: CornerTopRight
-  let bl = if downmore: VerticalBar else: CornerBottomLeft
-  let br = if downmore: VerticalBar else: CornerBottomRight
-  const fmt = newFormat()
-  display[sy * display.width + sx].str = tl
-  display[sy * display.width + ex].str = tr
-  display[ey * display.width + sx].str = bl
-  display[ey * display.width + ex].str = br
-  display[sy * display.width + sx].format = fmt
-  display[sy * display.width + ex].format = fmt
-  display[ey * display.width + sx].format = fmt
-  display[ey * display.width + ex].format = fmt
-  # Draw top, bottom borders.
-  let ups = if upmore: " " else: HorizontalBar
-  let downs = if downmore: " " else: HorizontalBar
-  for x in sx + 1 .. ex - 1:
-    display[sy * display.width + x].str = ups
-    display[ey * display.width + x].str = downs
-    display[sy * display.width + x].format = fmt
-    display[ey * display.width + x].format = fmt
-  if upmore:
-    display[sy * display.width + sx + (ex - sx) div 2].str = ":"
-  if downmore:
-    display[ey * display.width + sx + (ex - sx) div 2].str = ":"
-  # Draw left, right borders.
-  for y in sy + 1 .. ey - 1:
-    display[y * display.width + sx].str = VerticalBar
-    display[y * display.width + ex].str = VerticalBar
-    display[y * display.width + sx].format = fmt
-    display[y * display.width + ex].format = fmt
-
-proc drawSelect*(select: Select, display: var FixedGrid) =
-  if display.width < 2 or display.height < 2:
-    return # border does not fit...
-  # Max width, height with one row/column on the sides.
-  let mw = display.width - 2
-  let mh = display.height - 2
-  var sy = select.y
-  let si = select.si
-  var ey = min(sy + select.options.len, mh) + 1
-  var sx = select.x
-  if sx + select.maxw >= mw:
-    sx = display.width - select.maxw
-    if sx < 0:
-      # This means the widest option is wider than the available screen.
-      # w3m simply cuts off the part that doesn't fit, and we do that too,
-      # but I feel like this may not be the best solution.
-      sx = 0
-  var ex = min(sx + select.maxw, mw) + 1
-  let upmore = select.si > 0
-  let downmore = select.si + mh < select.options.len
-  drawBorders(display, sx, ex, sy, ey, upmore, downmore)
-  if select.multiple and not upmore:
-    display[sy * display.width + sx].str = "X"
-  # move inside border
-  inc sy
-  inc sx
-  var r: Rune
-  var k = 0
-  var format = newFormat()
-  while k < select.selected.len and select.selected[k] < si:
-    inc k
-  for y in sy ..< ey:
-    let i = y - sy + si
-    var j = 0
-    var x = sx
-    let dls = y * display.width
-    if k < select.selected.len and select.selected[k] == i:
-      format.reverse = true
-      inc k
-    else:
-      format.reverse = false
-    while j < select.options[i].len:
-      fastRuneAt(select.options[i], j, r)
-      let rw = r.twidth(x)
-      let ox = x
-      x += rw
-      if x > ex:
-        break
-      display[dls + ox].str = $r
-      display[dls + ox].format = format
-    while x < ex:
-      display[dls + x].str = " "
-      display[dls + x].format = format
-      inc x