about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.nim63
-rw-r--r--src/html/dom.nim11
-rw-r--r--src/html/parser.nim5
-rw-r--r--src/io/buffer.nim8
-rw-r--r--src/layout/box.nim13
-rw-r--r--src/layout/engine.nim57
-rw-r--r--src/render/renderdocument.nim11
-rw-r--r--src/types/mime.nim6
-rw-r--r--src/utils/twtstr.nim8
9 files changed, 109 insertions, 73 deletions
diff --git a/src/client.nim b/src/client.nim
index 2de2a639..3c95c420 100644
--- a/src/client.nim
+++ b/src/client.nim
@@ -52,7 +52,7 @@ proc getPage(client: Client, url: Url): tuple[s: Stream, contenttype: string] =
     let resp = client.http.get(url.serialize(true))
     let ct = resp.contentType()
     if ct != "":
-      result.contenttype = ct
+      result.contenttype = ct.until(';')
     else:
       result.contenttype = guessContentType(url.path.serialize())
     result.s = resp.bodyStream
@@ -112,6 +112,33 @@ proc readPipe(client: Client) =
     client.buffer.drawBuffer()
 
 var g_client: Client
+proc gotoUrl(client: Client, url: Url, prevurl = none(Url), force = false, newbuf = true) =
+  setControlCHook(proc() {.noconv.} =
+    raise newException(InterruptError, "Interrupted"))
+  if force or prevurl.issome or not prevurl.get.equals(url, true):
+    try:
+      let page = client.getPage(url)
+      if page.s != nil:
+        if newbuf:
+          client.addBuffer()
+          g_client = client
+          setControlCHook(proc() {.noconv.} =
+            if g_client.buffer.prev != nil or g_client.buffer.next != nil:
+              g_client.discardBuffer()
+            interruptError())
+        client.buffer.istream = page.s
+        client.buffer.contenttype = page.contenttype
+        client.buffer.streamclosed = false
+      else:
+        loadError("Couldn't load " & $url)
+    except IOError, OSError:
+      loadError("Couldn't load " & $url)
+  elif client.buffer != nil and prevurl.isnone or not prevurl.get.equals(url):
+    if not client.buffer.hasAnchor(url.anchor):
+      loadError("Couldn't find anchor " & url.anchor)
+  client.buffer.location = url
+  client.setupBuffer()
+
 proc gotoUrl(client: Client, url: string, prevurl = none(Url), force = false, newbuf = true) =
   var oldurl = prevurl
   if oldurl.isnone and client.buffer != nil:
@@ -119,36 +146,7 @@ proc gotoUrl(client: Client, url: string, prevurl = none(Url), force = false, ne
   let newurl = parseUrl(url, oldurl)
   if newurl.isnone:
     loadError("Invalid URL " & url)
-  if newurl.issome:
-    setControlCHook(proc() {.noconv.} =
-      raise newException(InterruptError, "Interrupted"))
-    let url = newurl.get
-    let prevurl = oldurl
-    if force or prevurl.issome or not prevurl.get.equals(url, true):
-      try:
-        let page = client.getPage(url)
-        if page.s != nil:
-          if newbuf:
-            client.addBuffer()
-            g_client = client
-            setControlCHook(proc() {.noconv.} =
-              if g_client.buffer.prev != nil or g_client.buffer.next != nil:
-                g_client.discardBuffer()
-              interruptError())
-          client.buffer.istream = page.s
-          client.buffer.contenttype = page.contenttype
-          client.buffer.streamclosed = false
-        else:
-          loadError("Couldn't load " & $url)
-      except IOError, OSError:
-        loadError("Couldn't load " & $url)
-    elif client.buffer != nil and prevurl.isnone or not prevurl.get.equals(url):
-      if not client.buffer.hasAnchor(url.anchor):
-        loadError("Couldn't find anchor " & url.anchor)
-    client.buffer.setLocation(url)
-    client.setupBuffer()
-  else:
-    loadError("Couldn't parse URL " & url)
+  client.gotoUrl(newurl.get, oldurl, force, newbuf)
 
 proc loadUrl(client: Client, url: string) =
   let firstparse = parseUrl(url)
@@ -163,7 +161,7 @@ proc loadUrl(client: Client, url: string) =
 
 proc reloadPage(client: Client) =
   let pbuffer = client.buffer
-  client.gotoUrl("", none(Url), true, false)
+  client.gotoUrl(pbuffer.location, none(Url), true, false)
   client.buffer.setCursorXY(pbuffer.cursorx, pbuffer.cursory)
   client.buffer.setFromXY(pbuffer.fromx, pbuffer.fromy)
   client.buffer.contenttype = pbuffer.contenttype
@@ -193,6 +191,7 @@ proc toggleSource*(client: Client) =
     client.buffer.sourcepair.sourcepair = client.buffer
     client.buffer.source = client.buffer.prev.source
     client.buffer.streamclosed = true
+    client.buffer.location = client.buffer.sourcepair.location
     let prevtype = client.buffer.prev.contenttype
     if prevtype == "text/html":
       client.buffer.contenttype = "text/plain"
diff --git a/src/html/dom.nim b/src/html/dom.nim
index f6d2878b..0105b72f 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -22,6 +22,7 @@ type
     parentNode*: Node
     parentElement*: Element
     ownerDocument*: Document
+    uid*: int # Unique id
 
   Attr* = ref AttrObj
   AttrObj = object of NodeObj
@@ -266,7 +267,7 @@ func newComment*(): Comment =
   new(result)
   result.nodeType = COMMENT_NODE
 
-func newHtmlElement*(tagType: TagType): HTMLElement =
+func newHtmlElement*(document: Document, tagType: TagType): HTMLElement =
   case tagType
   of TAG_INPUT:
     result = new(HTMLInputElement)
@@ -300,12 +301,14 @@ func newHtmlElement*(tagType: TagType): HTMLElement =
   result.nodeType = ELEMENT_NODE
   result.tagType = tagType
   result.css = rootProperties()
+  result.uid = document.all_elements.len
+  document.all_elements.add(result)
 
 func newDocument*(): Document =
   new(result)
-  result.root = newHtmlElement(TAG_HTML)
-  result.head = newHtmlElement(TAG_HEAD)
-  result.body = newHtmlElement(TAG_BODY)
+  result.root = result.newHtmlElement(TAG_HTML)
+  result.head = result.newHtmlElement(TAG_HEAD)
+  result.body = result.newHtmlElement(TAG_BODY)
   result.nodeType = DOCUMENT_NODE
 
 func newAttr*(parent: Element, key, value: string): Attr =
diff --git a/src/html/parser.nim b/src/html/parser.nim
index 0d2ccd3e..7488534a 100644
--- a/src/html/parser.nim
+++ b/src/html/parser.nim
@@ -23,6 +23,7 @@ type
     elementNode: Element
     textNode: Text
     commentNode: Comment
+    document: Document
 
 func inputSize*(str: string): int =
   if str.len == 0:
@@ -189,7 +190,6 @@ proc insertNode(parent, node: Node) =
 
     let element = (Element(node))
     if element.ownerDocument != nil:
-      node.ownerDocument.all_elements.add(Element(node))
       element.ownerDocument.type_elements[element.tagType].add(element)
       if element.id != "":
         if not (element.id in element.ownerDocument.id_elements):
@@ -351,7 +351,7 @@ proc processDocumentTag(state: var HTMLParseState, tag: DOMParsedTag) =
       return
 
   if tag.open:
-    processDocumentStartElement(state, newHtmlElement(tag.tagid), tag)
+    processDocumentStartElement(state, state.document.newHtmlElement(tag.tagid), tag)
   else:
     processDocumentEndElement(state, tag)
 
@@ -455,6 +455,7 @@ proc parseHtml*(inputStream: Stream): Document =
   insertNode(document.root, document.body)
 
   var state = HTMLParseState()
+  state.document = document
   state.elementNode = document.root
 
   var till_when = false
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index 48bd64d3..f219fe2c 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -34,6 +34,7 @@ type
     fromy*: int
     attrs*: TermAttributes
     document*: Document
+    viewport*: Viewport
     redraw*: bool
     reshape*: bool
     nostatus*: bool
@@ -656,9 +657,6 @@ proc gotoAnchor*(buffer: Buffer) =
         return
       inc i
 
-proc setLocation*(buffer: Buffer, location: Url) =
-  buffer.location = location
-
 proc gotoLocation*(buffer: Buffer, s: string) =
   discard parseUrl(s, buffer.location.some, buffer.location, true)
 
@@ -735,7 +733,9 @@ proc load*(buffer: Buffer) =
 proc render*(buffer: Buffer) =
   case buffer.contenttype
   of "text/html":
-    buffer.lines = renderDocument(buffer.document, buffer.attrs, buffer.userstyle)
+    if buffer.viewport == nil:
+      buffer.viewport = Viewport(term: buffer.attrs)
+    buffer.lines = renderDocument(buffer.document, buffer.attrs, buffer.userstyle, buffer.viewport)
   else: discard
   buffer.updateCursor()
 
diff --git a/src/layout/box.nim b/src/layout/box.nim
index 6502ec80..67cb9be9 100644
--- a/src/layout/box.nim
+++ b/src/layout/box.nim
@@ -5,6 +5,12 @@ import html/dom
 import io/term
 
 type
+  Viewport* = ref object
+    term*: TermAttributes
+    nodes*: seq[Node]
+    root*: BlockBox
+    map*: seq[CSSBox]
+
   CSSBox* = ref object of RootObj
     t*: CSSDisplay
     children*: seq[CSSBox]
@@ -12,10 +18,6 @@ type
     specified*: CSSSpecifiedValues
     node*: Node
 
-  Viewport* = ref object of CSSBox
-    term*: TermAttributes
-    nodes*: seq[Node]
-
   InlineAtom* = ref object of RootObj
     relx*: int
     width*: int
@@ -75,8 +77,11 @@ type
     text*: seq[string]
     ictx*: InlineContext
     newline*: bool
+
   BlockBox* = ref object of CSSBox
     bctx*: BlockContext
+
   InlineBlockBox* = ref object of BlockBox
     ictx*: InlineContext
+
   ListItemBox* = ref object of BlockBox
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 04c2ff26..f45181dc 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -108,11 +108,12 @@ proc processWhitespace(state: var InlineState, c: char) =
     else:
       state.ictx.whitespace = true
 
-proc renderText*(ictx: InlineContext, str: string, maxwidth: int, specified: CSSSpecifiedValues) =
+proc renderText*(ictx: InlineContext, str: string, maxwidth: int, specified: CSSSpecifiedValues, nodes: seq[Node]) =
   var state: InlineState
   state.specified = specified
   state.ictx = ictx
   state.maxwidth = maxwidth
+  state.nodes = nodes
   state.newWord()
 
   #if str.strip().len > 0:
@@ -237,13 +238,16 @@ proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox, parentcss: CSSSpe
   box.ictx.whitespace = false
 
 proc alignInline(bctx: BlockContext, box: InlineBox) =
+  if box.node != nil:
+    bctx.viewport.nodes.add(box.node)
+
   let box = InlineBox(box)
   assert box.ictx != nil
   if box.newline:
     box.ictx.flushLine()
   for text in box.text:
     assert box.children.len == 0
-    box.ictx.renderText(text, bctx.compwidth, box.specified)
+    box.ictx.renderText(text, bctx.compwidth, box.specified, bctx.viewport.nodes)
 
   for child in box.children:
     case child.t
@@ -257,6 +261,8 @@ proc alignInline(bctx: BlockContext, box: InlineBox) =
       bctx.alignInlineBlock(child, box.specified)
     else:
       assert false, "child.t is " & $child.t
+  if box.node != nil:
+    discard bctx.viewport.nodes.pop()
 
 proc alignInlines(bctx: BlockContext, inlines: seq[CSSBox]) =
   let ictx = bctx.newInlineContext()
@@ -311,12 +317,16 @@ proc alignBlocks(bctx: BlockContext, blocks: seq[CSSBox]) =
   flush_group()
 
 proc alignBlock(box: BlockBox) =
+  if box.node != nil:
+    box.bctx.viewport.nodes.add(box.node)
   if box.inlinelayout:
     # Box only contains inline boxes.
     box.bctx.alignInlines(box.children)
   else:
     box.bctx.alignBlocks(box.children)
     box.bctx.arrangeBlocks()
+  if box.node != nil:
+    discard box.bctx.viewport.nodes.pop()
 
 proc getBox(specified: CSSSpecifiedValues): CSSBox =
   case specified{"display"}
@@ -358,10 +368,20 @@ proc getPseudoBox(specified: CSSSpecifiedValues): CSSBox =
     box.children.add(content)
   return box
 
-proc generateBox(elem: Element, box = getBox(elem.css)): CSSBox =
+proc generateBox(elem: Element, viewport: Viewport, first = false): CSSBox =
+  if viewport.map[elem.uid] != nil:
+    return viewport.map[elem.uid]
+
+  let box = if not first:
+    getBox(elem.css)
+  else:
+    getBlockBox(elem.css)
+
   if box == nil:
     return nil
 
+  box.node = elem
+
   var ibox: InlineBox
   template add_ibox() =
     if ibox != nil:
@@ -386,6 +406,7 @@ proc generateBox(elem: Element, box = getBox(elem.css)): CSSBox =
   let before = elem.pseudo[PSEUDO_BEFORE]
   if before != nil:
     let bbox = getPseudoBox(before)
+    bbox.node = elem
     if bbox != nil:
       add_box(bbox)
 
@@ -398,7 +419,7 @@ proc generateBox(elem: Element, box = getBox(elem.css)): CSSBox =
         ibox = box.getTextBox()
         ibox.newline = true
 
-      let cbox = generateBox(elem)
+      let cbox = elem.generateBox(viewport)
       if cbox != nil:
         add_ibox()
         add_box(cbox)
@@ -411,7 +432,7 @@ proc generateBox(elem: Element, box = getBox(elem.css)): CSSBox =
           not text.data.onlyWhitespace():
         if ibox == nil:
           ibox = box.getTextBox()
-        ibox.text.add(Text(child).data)
+        ibox.text.add(text.data)
     else: discard
   add_ibox()
 
@@ -421,19 +442,19 @@ proc generateBox(elem: Element, box = getBox(elem.css)): CSSBox =
     if abox != nil:
       add_box(abox)
 
-  return box
+  viewport.map[elem.uid] = box
 
-proc generateBoxes(document: Document): BlockBox =
-  let box = document.root.generateBox(getBlockBox(document.root.css))
-  assert box != nil
-  assert box.t == DISPLAY_BLOCK
+  return box
 
-  return BlockBox(box)
+proc renderLayout*(viewport: var Viewport, document: Document) =
+  if viewport.root == nil or document.all_elements.len != viewport.map.len:
+    viewport.map = newSeq[CSSBox](document.all_elements.len)
+    viewport.root = BlockBox(document.root.generateBox(viewport, true))
+  else:
+    for uid in 0..viewport.map.high:
+      if not document.all_elements[uid].rendered:
+        viewport.map[uid] = nil
+    viewport.root = BlockBox(document.root.generateBox(viewport))
 
-proc renderLayout*(document: Document, term: TermAttributes): BlockBox =
-  #eprint document.root
-  let viewport = Viewport(term: term)
-  let root = document.generateBoxes()
-  root.bctx = viewport.newBlockContext()
-  alignBlock(root)
-  return root
+  viewport.root.bctx = viewport.newBlockContext()
+  alignBlock(viewport.root)
diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim
index 4b74bde1..9d5e0dcd 100644
--- a/src/render/renderdocument.nim
+++ b/src/render/renderdocument.nim
@@ -152,13 +152,10 @@ proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockContext, x, y: int) =
 
 const css = staticRead"res/ua.css"
 let uastyle = css.parseStylesheet()
-proc renderDocument*(document: Document, attrs: TermAttributes, userstyle: CSSStylesheet): FlexibleGrid =
+proc renderDocument*(document: Document, attrs: TermAttributes, userstyle: CSSStylesheet, layout: var Viewport): FlexibleGrid =
   document.applyStylesheets(uastyle, userstyle)
-  let rootbox = document.renderLayout(attrs)
-  var stack: seq[BlockContext]
-  if rootbox.bctx == nil: #TODO
-    result.addLine()
-    return
-  result.renderBlockContext(rootbox.bctx, 0, 0)
+  layout.renderLayout(document)
+  result.setLen(0)
+  result.renderBlockContext(layout.root.bctx, 0, 0)
   if result.len == 0:
     result.addLine()
diff --git a/src/types/mime.nim b/src/types/mime.nim
index 4dfe87e9..5c48c896 100644
--- a/src/types/mime.nim
+++ b/src/types/mime.nim
@@ -6,6 +6,8 @@ const DefaultGuess = [
   ("xhtml", "application/xhtml+xml"),
   ("xhtm", "application/xhtml+xml"),
   ("xht", "application/xhtml+xml"),
+  ("txt", "text/plain"),
+  ("", "text/plain")
 ].toTable()
 
 proc guessContentType*(path: string): string =
@@ -13,7 +15,7 @@ proc guessContentType*(path: string): string =
   var n = 0
   while i > 0:
     if path[i] == '/':
-      return "text/plain"
+      return DefaultGuess[""]
     if path[i] == '.':
       n = i
       break
@@ -22,4 +24,4 @@ proc guessContentType*(path: string): string =
     let ext = path.substr(n + 1)
     if ext in DefaultGuess:
       return DefaultGuess[ext]
-  return "text/plain"
+  return DefaultGuess[""]
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index cb236b53..3050d50e 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -217,6 +217,14 @@ func skipBlanks*(buf: string, at: int): int =
   while result < buf.len and buf[result].isWhitespace():
     inc result
 
+func until*(s: string, c: char): string =
+  var i = 0
+  while i < s.len:
+    if s[i] == c:
+      break
+    result.add(s[i])
+    inc i
+
 func number_additive*(i: int, range: HSlice[int, int], symbols: openarray[(int, string)]): string =
   if i notin range:
     return $i