import macros
import nativesockets
import net
import options
import os
import selectors
import streams
import tables
import unicode
when defined(posix):
import posix
import buffer/cell
import css/cascade
import css/cssparser
import css/mediaquery
import css/sheet
import css/stylednode
import data/charset
import config/config
import html/dom
import html/env
import html/htmlparser
import html/tags
import io/loader
import io/request
import io/posixstream
import io/teestream
import ips/serialize
import ips/serversocket
import ips/socketstream
import js/regex
import io/window
import layout/box
import render/renderdocument
import render/rendertext
import types/buffersource
import types/color
import types/cookie
import types/referer
import types/url
import utils/twtstr
type
LoadInfo* = enum
CONNECT, DOWNLOAD, RENDER, DONE
BufferCommand* = enum
LOAD, RENDER, WINDOW_CHANGE, FIND_ANCHOR, READ_SUCCESS, READ_CANCELED,
CLICK, FIND_NEXT_LINK, FIND_PREV_LINK, FIND_NEXT_MATCH, FIND_PREV_MATCH,
GET_SOURCE, GET_LINES, UPDATE_HOVER, PASS_FD, CONNECT, GOTO_ANCHOR, CANCEL,
GET_TITLE
HoverType* = enum
HOVER_TITLE = "TITLE"
HOVER_LINK = "URL"
BufferMatch* = object
success*: bool
x*: int
y*: int
str*: string
Buffer* = ref object
fd: int
alive: bool
cs: Charset
readbufsize: int
contenttype: string
lines: FlexibleGrid
rendered: bool
source: BufferSource
width: int
height: int
attrs: WindowAttributes
window: Window
document: Document
viewport: Viewport
prevstyled: StyledNode
url: URL
selector: Selector[int]
istream: Stream
sstream: Stream
available: int
pstream: Stream # pipe stream
srenderer: StreamRenderer
connected: bool
loaded: bool # istream is closed
prevnode: StyledNode
loader: FileLoader
config: BufferConfig
userstyle: CSSStylesheet
timeouts: Table[int, (proc())]
tasks: array[BufferCommand, int] #TODO this should have arguments
savetask: bool
hovertext: array[HoverType, string]
# async, but worse
EmptyPromise = ref object of RootObj
cb: (proc())
next: EmptyPromise
stream: Stream
Promise*[T] = ref object of EmptyPromise
res: T
BufferInterface* = ref object
stream*: Stream
packetid: int
promises: Table[int, EmptyPromise]
proc newBufferInterface*(ostream: Stream): BufferInterface =
result = BufferInterface(
stream: ostream,
packetid: 1 # ids below 1 are invalid
)
proc fulfill*(iface: BufferInterface, packetid, len: int) =
var promise: EmptyPromise
if iface.promises.pop(packetid, promise):
if promise.stream != nil and promise.cb == nil and len != 0:
var abc = alloc(len)
var x = 0
while x < len:
x += promise.stream.readData(abc, len)
dealloc(abc)
while promise != nil:
if promise.cb != nil:
promise.cb()
promise = promise.next
proc hasPromises*(iface: BufferInterface): bool =
return iface.promises.len > 0
proc then*(promise: EmptyPromise, cb: (proc())): EmptyPromise {.discardable.} =
if promise == nil: return
promise.cb = cb
promise.next = EmptyPromise()
return promise.next
proc then*[T](promise: Promise[T], cb: (proc(x: T))): EmptyPromise {.discardable.} =
if promise == nil: return
return promise.then(proc() =
if promise.stream != nil:
promise.stream.sread(promise.res)
cb(promise.res))
# Warning: we assume these aren't discarded.
proc then*[T](promise: EmptyPromise, cb: (proc(): Promise[T])): Promise[T] =
if promise == nil: return
let next = Promise[T]()
promise.then(proc() =
let p2 = cb()
if p2 != nil:
p2.then(proc(x: T) =
next.res = x
next.cb()))
return next
proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U] =
if promise == nil: return
let next = Promise[U]()
promise.then(proc(x: T) =
let p2 = cb(x)
if p2 != nil:
p2.then(proc(y: U) =
next.res = y
next.cb()))
return next
# get enum identifier of proxy function
func getFunId(fun: NimNode): string =
let name = fun[0] # sym
result = name.strVal.toScreamingSnakeCase()
if result[^1] == '=':
result = "SET_" & result[0..^2]
proc buildInterfaceProc(fun: NimNode, funid: string): tuple[fun, name: NimNode] =
let name = fun[0] # sym
let params = fun[3] # formalparams
let retval = params[0] # sym
var body = newStmtList()
assert params.len >= 2 # return type, this value
let nup = ident(funid) # add this to enums
let this2 = newIdentDefs(ident("iface"), ident("BufferInterface"))
let thisval = this2[0]
body.add(quote do:
`thisval`.stream.swrite(BufferCommand.`nup`)
`thisval`.stream.swrite(`thisval`.packetid))
var params2: seq[NimNode]
var retval2: NimNode
if retval.kind == nnkEmpty:
retval2 = ident("EmptyPromise")
else:
retval2 = newNimNode(nnkBracketExpr).add(
ident("Promise"),
retval)
params2.add(retval2)
params2.add(this2)
for i in 2 ..< params.len:
let param = params[i]
for i in 0 ..< param.len - 2:
let id2 = newIdentDefs(ident(param[i].strVal), param[^2])
params2.add(id2)
for i in 2 ..< params2.len:
let s = params2[i][0] # sym e.g. url
body.add(quote do:
`thisval`.stream.swrite(`s`))
body.add(quote do:
`thisval`.stream.flush())
body.add(quote do:
`thisval`.promises[`thisval`.packetid] = `retval2`(stream: `thisval`.stream)
inc `thisval`.packetid)
var pragmas: NimNode
if retval.kind == nnkEmpty:
body.add(quote do:
return `thisval`.promises[`thisval`.packetid - 1])
pragmas = newNimNode(nnkPragma).add(ident("discardable"))
else:
body.add(quote do:
return `retval2`(`thisval`.promises[`thisval`.packetid - 1]))
pragmas = newEmptyNode()
return (newProc(name, params2, body, pragmas = pragmas), nup)
type
ProxyFunction = ref object
iname: NimNode # internal name
ename: NimNode # enum name
params: seq[NimNode]
istask: bool
ProxyMap = Table[string, ProxyFunction]
# Name -> ProxyFunction
var ProxyFunctions {.compileTime.}: ProxyMap
proc getProxyFunction(funid: string): ProxyFunction =
if funid notin ProxyFunctions:
ProxyFunctions[funid] = ProxyFunction()
return ProxyFunctions[funid]
macro proxy0(fun: untyped) =
fun[0] = ident(fun[0].strVal & "_internal")
return fun
macro proxy1(fun: typed) =
let funid = getFunId(fun)
let iproc = buildInterfaceProc(fun, funid)
let pfun = getProxyFunction(funid)
pfun.iname = ident(fun[0].strVal & "_internal")
pfun.ename = iproc[1]
pfun.params.add(fun[3][0])
var params2: seq[NimNode]
params2.add(fun[3][0])
for i in 1 ..< fun[3].len:
let param = fun[3][i]
pfun.params.add(param)
for i in 0 ..< param.len - 2:
let id2 = newIdentDefs(ident(param[i].strVal), param[^2])
params2.add(id2)
ProxyFunctions[funid] = pfun
return iproc[0]
macro proxy(fun: typed) =
quote do:
proxy0(`fun`)
proxy1(`fun`)
macro task(fun: typed) =
let funid = getFunId(fun)
let pfun = getProxyFunction(funid)
pfun.istask = true
fun
func getTitleAttr(node: StyledNode): string =
if node == nil:
return ""
if node.t == STYLED_ELEMENT and node.node != nil:
let element = Element(node.node)
if element.attrb("title"):
return element.attr("title")
if node.node != nil:
var node = node.node
for element in node.ancestors:
if element.attrb("title"):
return element.attr("title")
#TODO pseudo-elements
const ClickableElements = {
TAG_A, TAG_INPUT, TAG_OPTION, TAG_BUTTON, TAG_TEXTAREA
}
func getClickable(styledNode: StyledNode): Element =
if styledNode == nil or styledNode.node == nil:
return nil
if styledNode.t == STYLED_ELEMENT:
let element = Element(styledNode.node)
if element.tagType in ClickableElements:
return element
styledNode.node.findAncestor(ClickableElements)
func getClickHover(styledNode: StyledNode): string =
let clickable = styledNode.getClickable()
if clickable != nil:
case clickable.tagType
of TAG_A:
return HTMLAnchorElement(clickable).href
of TAG_INPUT:
return ""
of TAG_OPTION:
return "