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/promise
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]
InterfaceOpaque = ref object
stream: Stream
len: int
BufferInterface* = ref object
map: PromiseMap
packetid: int
opaque: InterfaceOpaque
stream*: Stream
proc getFromOpaque[T](opaque: pointer, res: var T) =
let opaque = cast[InterfaceOpaque](opaque)
if opaque.len != 0:
opaque.stream.sread(res)
proc newBufferInterface*(stream: Stream): BufferInterface =
let opaque = InterfaceOpaque(stream: stream)
result = BufferInterface(
map: newPromiseMap(cast[pointer](opaque)),
packetid: 1, # ids below 1 are invalid
opaque: opaque,
stream: stream
)
proc resolve*(iface: BufferInterface, packetid, len: int) =
iface.opaque.len = len
iface.map.resolve(packetid)
proc hasPromises*(iface: BufferInterface): bool =
return not iface.map.empty()
# 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
var addfun: NimNode
if retval.kind == nnkEmpty:
addfun = quote do:
`thisval`.map.addEmptyPromise(`thisval`.packetid)
retval2 = ident("EmptyPromise")
else:
addfun = quote do:
addPromise[`retval`](`thisval`.map, `thisval`.packetid, getFromOpaque[`retval`])
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:
let promise = `addfun`
inc `thisval`.packetid
return promise)
var pragmas: NimNode
if retval.kind == nnkEmpty:
pragmas = newNimNode(nnkPragma).add(ident("discardable"))
else:
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 "