diff options
author | bptato <nincsnevem662@gmail.com> | 2024-07-28 20:50:51 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-07-28 21:06:28 +0200 |
commit | 9653c35fb9a4398942ecb305835a95fbd87c433a (patch) | |
tree | 2db576e71cd89557592715d64ecb4fb4a46f8c66 /src | |
parent | dbf2e0e831ebaf8a0e6f375a8f423f87280e7862 (diff) | |
download | chawan-9653c35fb9a4398942ecb305835a95fbd87c433a.tar.gz |
buffer, pager, config: add meta-refresh + misc fixes
* buffer, pager, config: add meta-refresh value, which makes it possible to follow http-equiv=refresh META tags. * config: clean up redundant format mode parser * timeout: accept varargs for params to pass on to functions * pager: add "options" dict to JS gotoURL * twtstr: remove redundant startsWithNoCase
Diffstat (limited to 'src')
-rw-r--r-- | src/config/config.nim | 79 | ||||
-rw-r--r-- | src/css/cssparser.nim | 6 | ||||
-rw-r--r-- | src/html/catom.nim | 4 | ||||
-rw-r--r-- | src/html/dom.nim | 6 | ||||
-rw-r--r-- | src/html/env.nim | 12 | ||||
-rw-r--r-- | src/js/timeout.nim | 20 | ||||
-rw-r--r-- | src/layout/renderdocument.nim | 2 | ||||
-rw-r--r-- | src/local/client.nim | 1 | ||||
-rw-r--r-- | src/local/container.nim | 18 | ||||
-rw-r--r-- | src/local/pager.nim | 92 | ||||
-rw-r--r-- | src/local/term.nim | 12 | ||||
-rw-r--r-- | src/server/buffer.nim | 54 | ||||
-rw-r--r-- | src/types/cell.nim | 20 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 30 |
14 files changed, 228 insertions, 128 deletions
diff --git a/src/config/config.nim b/src/config/config.nim index 3d4a0217..3c733bc5 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -30,12 +30,20 @@ import utils/twtstr type ColorMode* = enum - cmMonochrome, cmANSI, cmEightBit, cmTrueColor + cmMonochrome = "monochrome" + cmANSI = "ansi" + cmEightBit = "eight-bit" + cmTrueColor = "true-color" - FormatMode* = set[FormatFlags] + MetaRefresh* = enum + mrNever = "never" + mrAlways = "always" + mrAsk = "ask" ImageMode* = enum - imNone = "none", imSixel = "sixel", imKitty = "kitty" + imNone = "none" + imSixel = "sixel" + imKitty = "kitty" ChaPathResolved* = distinct string @@ -65,6 +73,7 @@ type default_headers*: TableRef[string, string] insecure_ssl_no_verify*: Option[bool] autofocus*: Option[bool] + meta_refresh*: Option[MetaRefresh] OmniRule* = ref object match*: Regex @@ -116,8 +125,8 @@ type DisplayConfig = object color_mode* {.jsgetset.}: Option[ColorMode] - format_mode* {.jsgetset.}: Option[FormatMode] - no_format_mode* {.jsgetset.}: FormatMode + format_mode* {.jsgetset.}: Option[set[FormatFlag]] + no_format_mode* {.jsgetset.}: set[FormatFlag] image_mode* {.jsgetset.}: Option[ImageMode] alt_screen* {.jsgetset.}: Option[bool] highlight_color* {.jsgetset.}: ARGBColor @@ -148,6 +157,7 @@ type cookie* {.jsgetset.}: bool referer_from* {.jsgetset.}: bool autofocus* {.jsgetset.}: bool + meta_refresh* {.jsgetset.}: MetaRefresh Config* = ref object jsctx: JSContext @@ -326,11 +336,9 @@ proc parseConfigValue(ctx: var ConfigParser; x: var int32; v: TomlValue; k: string) proc parseConfigValue(ctx: var ConfigParser; x: var int64; v: TomlValue; k: string) -proc parseConfigValue(ctx: var ConfigParser; x: var Option[ColorMode]; - v: TomlValue; k: string) -proc parseConfigValue(ctx: var ConfigParser; x: var Option[FormatMode]; - v: TomlValue; k: string) -proc parseConfigValue(ctx: var ConfigParser; x: var FormatMode; v: TomlValue; +proc parseConfigValue(ctx: var ConfigParser; x: var ColorMode; v: TomlValue; + k: string) +proc parseConfigValue[T](ctx: var ConfigParser; x: var Option[T]; v: TomlValue; k: string) proc parseConfigValue(ctx: var ConfigParser; x: var ARGBColor; v: TomlValue; k: string) @@ -466,48 +474,21 @@ proc parseConfigValue(ctx: var ConfigParser; x: var int64; v: TomlValue; typeCheck(v, tvtInteger, k) x = v.i -proc parseConfigValue(ctx: var ConfigParser; x: var Option[ColorMode]; - v: TomlValue; k: string) = +proc parseConfigValue(ctx: var ConfigParser; x: var ColorMode; v: TomlValue; + k: string) = typeCheck(v, tvtString, k) - case v.s - of "auto": x = none(ColorMode) - of "monochrome": x = some(cmMonochrome) - of "ansi": x = some(cmANSI) - of "8bit", "eight-bit": x = some(cmEightBit) - of "24bit", "true-color": x = some(cmTrueColor) + let y = strictParseEnum[ColorMode](v.s) + if y.isSome: + x = y.get + # backwards compat + elif v.s == "8bit": + x = cmEightBit + elif v.s == "24bit": + x = cmTrueColor else: raise newException(ValueError, "unknown color mode '" & v.s & "' for key " & k) -proc parseConfigValue(ctx: var ConfigParser; x: var Option[FormatMode]; - v: TomlValue; k: string) = - typeCheck(v, {tvtString, tvtArray}, k) - if v.t == tvtString and v.s == "auto": - x = none(FormatMode) - else: - var y: FormatMode - ctx.parseConfigValue(y, v, k) - x = some(y) - -proc parseConfigValue(ctx: var ConfigParser; x: var FormatMode; v: TomlValue; - k: string) = - typeCheck(v, tvtArray, k) - for i in 0 ..< v.a.len: - let kk = k & "[" & $i & "]" - let vv = v.a[i] - typeCheck(vv, tvtString, kk) - case vv.s - of "bold": x.incl(ffBold) - of "italic": x.incl(ffItalic) - of "underline": x.incl(ffUnderline) - of "reverse": x.incl(ffReverse) - of "strike": x.incl(ffStrike) - of "overline": x.incl(ffOverline) - of "blink": x.incl(ffBlink) - else: - raise newException(ValueError, "unknown format mode '" & vv.s & - "' for key " & kk) - proc parseConfigValue(ctx: var ConfigParser; x: var ARGBColor; v: TomlValue; k: string) = typeCheck(v, tvtString, k) @@ -560,14 +541,14 @@ proc parseConfigValue[T](ctx: var ConfigParser; x: var set[T]; v: TomlValue; typeCheck(v, {tvtString, tvtArray}, k) if v.t == tvtString: var xx: T - xx.parseConfigValue(v, k) + ctx.parseConfigValue(xx, v, k) x = {xx} else: x = {} for i in 0 ..< v.a.len: let kk = k & "[" & $i & "]" var xx: T - xx.parseConfigValue(v.a[i], kk) + ctx.parseConfigValue(xx, v.a[i], kk) x.incl(xx) proc parseConfigValue(ctx: var ConfigParser; x: var CSSConfig; v: TomlValue; diff --git a/src/css/cssparser.nim b/src/css/cssparser.nim index 0d71e6d0..1e56d11f 100644 --- a/src/css/cssparser.nim +++ b/src/css/cssparser.nim @@ -908,9 +908,9 @@ proc parseAnB*(state: var CSSParseState): Option[CSSAnB] = let tok2 = get_tok fail_non_signless_integer tok2, none(CSSAnB) return some((-1, -int(tok2.nvalue))) - elif tok.value.startsWithNoCase("n-"): + elif tok.value.startsWithIgnoreCase("n-"): return some((1, -parse_sub_int(tok.value, "n-".len))) - elif tok.value.startsWithNoCase("-n-"): + elif tok.value.startsWithIgnoreCase("-n-"): fail_plus return some((-1, -parse_sub_int(tok.value, "n-".len))) else: @@ -952,7 +952,7 @@ proc parseAnB*(state: var CSSParseState): Option[CSSAnB] = let tok2 = get_tok fail_non_signless_integer tok2, none(CSSAnB) return some((int(tok.nvalue), -int(tok2.nvalue))) - elif tok.unit.startsWithNoCase("n-"): + elif tok.unit.startsWithIgnoreCase("n-"): # <ndashdigit-dimension> return some((int(tok.nvalue), -parse_sub_int(tok.unit, "n-".len))) else: diff --git a/src/html/catom.nim b/src/html/catom.nim index f22d74ef..0ff7b949 100644 --- a/src/html/catom.nim +++ b/src/html/catom.nim @@ -9,6 +9,7 @@ import monoucha/javascript import monoucha/jserror import monoucha/tojs import types/opt +import utils/twtstr # create a static enum compatible with chame/tags @@ -33,6 +34,7 @@ macro makeStaticAtom = satColor = "color" satCols = "cols" satColspan = "colspan" + satContent = "content" satCrossorigin = "crossorigin" satDOMContentLoaded = "DOMContentLoaded" satDefer = "defer" @@ -102,7 +104,7 @@ macro makeStaticAtom = if t == TAG_UNKNOWN: continue let tn = $t - let name = "sat" & tn[0].toUpperAscii() & tn.substr(1) + let name = "sat" & tn[0].toUpperAscii() & tn.substr(1).kebabToCamelCase() seen.incl(tn) decl0.add(newNimNode(nnkEnumFieldDef).add(ident(name), newStrLitNode(tn))) for i, f in StaticAtom0.getType(): diff --git a/src/html/dom.nim b/src/html/dom.nim index 7491e56b..a2153acb 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -2414,6 +2414,12 @@ func findAnchor*(document: Document; id: string): Element = return child return nil +proc findMetaRefresh*(document: Document): Element = + for child in document.elements(TAG_META): + if child.attr(satHttpEquiv) == "refresh": + return child + return nil + func focus*(document: Document): Element = return document.internalFocus diff --git a/src/html/env.nim b/src/html/env.nim index 131d7b4d..187968e3 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -112,13 +112,13 @@ proc fetch(window: Window; input: JSValue; init = none(RequestInit)): # Forward declaration hack windowFetch = fetch -proc setTimeout(window: Window; handler: JSValue; timeout = 0i32): int32 - {.jsfunc.} = - return window.timeouts.setTimeout(ttTimeout, handler, timeout) +proc setTimeout(window: Window; handler: JSValue; timeout = 0i32; + args: varargs[JSValue]): int32 {.jsfunc.} = + return window.timeouts.setTimeout(ttTimeout, handler, timeout, args) -proc setInterval(window: Window; handler: JSValue; interval = 0i32): int32 - {.jsfunc.} = - return window.timeouts.setTimeout(ttInterval, handler, interval) +proc setInterval(window: Window; handler: JSValue; interval = 0i32; + args: varargs[JSValue]): int32 {.jsfunc.} = + return window.timeouts.setTimeout(ttInterval, handler, interval, args) proc clearTimeout(window: Window; id: int32) {.jsfunc.} = window.timeouts.clearTimeout(id) diff --git a/src/js/timeout.nim b/src/js/timeout.nim index 6c4b157f..f8b8ed8a 100644 --- a/src/js/timeout.nim +++ b/src/js/timeout.nim @@ -5,6 +5,7 @@ import io/dynstream import js/console import monoucha/fromjs import monoucha/javascript +import monoucha/jsutils import types/opt type @@ -16,8 +17,9 @@ type t: TimeoutType fd: int val: JSValue + args: seq[JSValue] - TimeoutState* = object + TimeoutState* = ref object timeoutid: int32 timeouts: Table[int32, TimeoutEntry] timeoutFds: Table[int, int32] @@ -43,33 +45,39 @@ proc clearTimeout*(state: var TimeoutState; id: int32) = let entry = state.timeouts[id] state.selector.unregister(entry.fd) JS_FreeValue(state.jsctx, entry.val) + for arg in entry.args: + JS_FreeValue(state.jsctx, arg) state.timeoutFds.del(entry.fd) state.timeouts.del(id) #TODO varargs proc setTimeout*(state: var TimeoutState; t: TimeoutType; handler: JSValue; - timeout = 0i32): int32 = + timeout: int32; args: openArray[JSValue]): int32 = let id = state.timeoutid inc state.timeoutid let fd = state.selector.registerTimer(max(timeout, 1), t == ttTimeout, 0) state.timeoutFds[fd] = id - state.timeouts[id] = TimeoutEntry( + let entry = TimeoutEntry( t: t, fd: fd, val: JS_DupValue(state.jsctx, handler) ) + for arg in args: + entry.args.add(JS_DupValue(state.jsctx, arg)) + state.timeouts[id] = entry return id proc runEntry(state: var TimeoutState; entry: TimeoutEntry; name: string) = if JS_IsFunction(state.jsctx, entry.val): - let ret = JS_Call(state.jsctx, entry.val, JS_UNDEFINED, 0, nil) + let ret = JS_Call(state.jsctx, entry.val, JS_UNDEFINED, + cint(entry.args.len), entry.args.toJSValueArray()) if JS_IsException(ret): state.jsctx.writeException(state.err) JS_FreeValue(state.jsctx, ret) else: let s = fromJS[string](state.jsctx, entry.val) if s.isSome: - state.evalJSFree(s.get, "setInterval handler") + state.evalJSFree(s.get, name) proc runTimeoutFd*(state: var TimeoutState; fd: int): bool = if fd notin state.timeoutFds: @@ -85,5 +93,7 @@ proc clearAll*(state: var TimeoutState) = for entry in state.timeouts.values: state.selector.unregister(entry.fd) JS_FreeValue(state.jsctx, entry.val) + for arg in entry.args: + JS_FreeValue(state.jsctx, arg) state.timeouts.clear() state.timeoutFds.clear() diff --git a/src/layout/renderdocument.nim b/src/layout/renderdocument.nim index 125e53a6..a407cdfe 100644 --- a/src/layout/renderdocument.nim +++ b/src/layout/renderdocument.nim @@ -55,7 +55,7 @@ proc addFormat(line: var FlexibleLine; pos: int; format: Format; func toFormat(computed: CSSComputedValues): Format = if computed == nil: return Format() - var flags: set[FormatFlags] + var flags: set[FormatFlag] = {} if computed{"font-style"} in {FontStyleItalic, FontStyleOblique}: flags.incl(ffItalic) if computed{"font-weight"} > 500: diff --git a/src/local/client.nim b/src/local/client.nim index 1fc5a7f1..eae079d9 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -738,6 +738,7 @@ proc launchClient*(client: Client; pages: seq[string]; # better associate it with jsctx client.timeouts = newTimeoutState(client.selector, client.jsctx, client.console.err, proc(src, file: string) = client.evalJSFree(src, file)) + client.pager.timeouts = client.timeouts addExitProc((proc() = client.cleanup())) if client.config.start.startup_script != "": let s = if fileExists(client.config.start.startup_script): diff --git a/src/local/container.nim b/src/local/container.nim index 805c1b80..62bda3f2 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -49,7 +49,8 @@ type ContainerEventType* = enum cetAnchor, cetNoAnchor, cetReadLine, cetReadArea, cetReadFile, cetOpen, - cetSetLoadInfo, cetStatus, cetAlert, cetLoaded, cetTitle, cetCancel + cetSetLoadInfo, cetStatus, cetAlert, cetLoaded, cetTitle, cetCancel, + cetMetaRefresh ContainerEvent* = object case t*: ContainerEventType @@ -67,6 +68,9 @@ type anchor*: string of cetAlert: msg*: string + of cetMetaRefresh: + refreshIn*: int + refreshURL*: URL else: discard HighlightType = enum @@ -144,6 +148,7 @@ type replaceBackup*: Container # for redirection; when set, we get discarded # if we are referenced by another container, replaceRef is set so that we # can clear ourselves on discard + #TODO this is a mess :( replaceRef*: Container code*: int # note: this is not the status code, but the ConnectErrorCode. errorMessage*: string @@ -1713,6 +1718,15 @@ proc onload(container: Container; res: int) = ) else: container.needslines = true + if container.config.metaRefresh != mrNever: + container.iface.checkRefresh().then(proc(res: CheckRefreshResult) = + if res.n >= 0: + container.triggerEvent(ContainerEvent( + t: cetMetaRefresh, + refreshIn: res.n, + refreshURL: if res.url != nil: res.url else: container.url + )) + ) else: container.needslines = true container.setLoadInfo(convertSize(res) & " loaded") @@ -1744,7 +1758,7 @@ proc applyResponse*(container: Container; response: Response; cookieJar.add(response.extractCookies()) # set referrer policy, if any let referrerPolicy = response.extractReferrerPolicy() - if container.config.referer_from: + if container.config.refererFrom: if referrerPolicy.isSome: container.loaderConfig.referrerPolicy = referrerPolicy.get else: diff --git a/src/local/pager.nim b/src/local/pager.nim index e150507c..50a07ce8 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -5,6 +5,7 @@ import std/os import std/osproc import std/posix import std/selectors +import std/sets import std/tables import std/unicode @@ -21,6 +22,7 @@ import io/socketstream import io/stdio import io/tempfile import io/urlfilter +import js/timeout import layout/renderdocument import loader/connecterror import loader/headers @@ -146,12 +148,14 @@ type numload*: int # number of pages currently being loaded precnum*: int32 # current number prefix (when vi-numeric-prefix is true) procmap*: seq[ProcMapItem] + refreshAllowed: HashSet[string] regex: Opt[Regex] reverseSearch: bool scommand*: string selector*: Selector[int] status: Surface term*: Terminal + timeouts*: TimeoutState unreg*: seq[Container] jsDestructor(Pager) @@ -1119,7 +1123,7 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset; let ctx = pager.jsctx var res = BufferConfig( userstyle: pager.config.css.stylesheet, - referer_from: pager.config.buffer.referer_from, + refererFrom: pager.config.buffer.referer_from, scripting: pager.config.buffer.scripting, charsets: pager.config.encoding.document_charset, images: pager.config.buffer.images, @@ -1127,7 +1131,8 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset; autofocus: pager.config.buffer.autofocus, isdump: pager.config.start.headless, charsetOverride: charsetOverride, - protocol: pager.config.protocol + protocol: pager.config.protocol, + metaRefresh: pager.config.buffer.meta_refresh ) loaderConfig = LoaderClientConfig( defaultHeaders: newHeaders(pager.config.network.default_headers), @@ -1171,7 +1176,7 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset; if sc.scripting.isSome: res.scripting = sc.scripting.get if sc.referer_from.isSome: - res.referer_from = sc.referer_from.get + res.refererFrom = sc.referer_from.get if sc.document_charset.len > 0: res.charsets = sc.document_charset if sc.images.isSome: @@ -1187,6 +1192,8 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset; loaderConfig.insecureSSLNoVerify = sc.insecure_ssl_no_verify.get if sc.autofocus.isSome: res.autofocus = sc.autofocus.get + if sc.meta_refresh.isSome: + res.metaRefresh = sc.meta_refresh.get if res.images: loaderConfig.filter.allowschemes .add(pager.config.external.urimethodmap.imageProtos) @@ -1196,9 +1203,9 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset; proc gotoURL(pager: Pager; request: Request; prevurl = none(URL); contentType = none(string); cs = CHARSET_UNKNOWN; replace: Container = nil; replaceBackup: Container = nil; redirectDepth = 0; - referrer: Container = nil; save = false; url: URL = nil) = + referrer: Container = nil; save = false; url: URL = nil): Container = pager.navDirection = ndNext - if referrer != nil and referrer.config.referer_from: + if referrer != nil and referrer.config.refererFrom: request.referrer = referrer.url let url = if url != nil: url else: request.url var loaderConfig: LoaderClientConfig @@ -1237,8 +1244,10 @@ proc gotoURL(pager: Pager; request: Request; prevurl = none(URL); else: pager.addContainer(container) inc pager.numload + return container else: pager.container.findAnchor(request.url.anchor) + return nil proc omniRewrite(pager: Pager; s: string): string = for rule in pager.config.omnirule: @@ -1271,7 +1280,7 @@ proc loadURL*(pager: Pager; url: string; ctype = none(string); some(pager.container.url) else: none(URL) - pager.gotoURL(newRequest(firstparse.get), prev, ctype, cs) + discard pager.gotoURL(newRequest(firstparse.get), prev, ctype, cs) return var urls: seq[URL] if pager.config.network.prepend_https and @@ -1288,10 +1297,10 @@ proc loadURL*(pager: Pager; url: string; ctype = none(string); if urls.len == 0: pager.alert("Invalid URL " & url) else: - let prevc = pager.container - pager.gotoURL(newRequest(urls.pop()), contentType = ctype, cs = cs) - if pager.container != prevc: - pager.container.retry = urls + let container = pager.gotoURL(newRequest(urls.pop()), contentType = ctype, + cs = cs) + if container != nil: + container.retry = urls proc readPipe0*(pager: Pager; contentType: string; cs: Charset; fd: FileHandle; url: URL; title: string; flags: set[ContainerFlag]): @@ -1402,7 +1411,7 @@ proc updateReadLine*(pager: Pager) = of lmPassword: let url = LineDataAuth(pager.lineData).url url.password = lineedit.news - pager.gotoURL(newRequest(url), some(pager.container.url), + discard pager.gotoURL(newRequest(url), some(pager.container.url), replace = pager.container, referrer = pager.container) pager.lineData = nil of lmCommand: @@ -1472,18 +1481,26 @@ proc load(pager: Pager; s = "") {.jsfunc.} = pager.setLineEdit(lmLocation, s) # Go to specific URL (for JS) -proc jsGotoURL(pager: Pager; v: JSValue): JSResult[void] {.jsfunc: "gotoURL".} = - let req = fromJS[JSRequest](pager.jsctx, v) - if req.isSome: - pager.gotoURL(req.get.request) +type GotoURLDict = object of JSDict + contentType: Option[string] + replace: Container + +proc jsGotoURL(pager: Pager; v: JSValue; t = GotoURLDict()): JSResult[void] + {.jsfunc: "gotoURL".} = + let request = if (let x = fromJS[JSRequest](pager.jsctx, v); x.isSome): + x.get.request + elif (let x = fromJS[URL](pager.jsctx, v); x.isSome): + newRequest(x.get) else: let s = ?fromJS[string](pager.jsctx, v) - pager.gotoURL(newRequest(?newURL(s))) - ok() + newRequest(?newURL(s)) + discard pager.gotoURL(request, contentType = t.contentType, + replace = t.replace) + return ok() # Reload the page in a new buffer, then kill the previous buffer. proc reload(pager: Pager) {.jsfunc.} = - pager.gotoURL(newRequest(pager.container.url), none(URL), + discard pager.gotoURL(newRequest(pager.container.url), none(URL), pager.container.contentType, replace = pager.container) proc setEnvVars(pager: Pager) {.jsfunc.} = @@ -1818,18 +1835,18 @@ proc redirectTo(pager: Pager; container: Container; request: Request) = container.replaceBackup else: container.find(ndAny) - pager.gotoURL(request, some(container.url), replace = container, + let nc = pager.gotoURL(request, some(container.url), replace = container, replaceBackup = replaceBackup, redirectDepth = container.redirectDepth + 1, referrer = container) - pager.container.loadinfo = "Redirecting to " & $request.url - pager.onSetLoadInfo(pager.container) + nc.loadinfo = "Redirecting to " & $request.url + pager.onSetLoadInfo(nc) dec pager.numload proc fail(pager: Pager; container: Container; errorMessage: string) = dec pager.numload pager.deleteContainer(container, container.find(ndAny)) if container.retry.len > 0: - pager.gotoURL(newRequest(container.retry.pop()), + discard pager.gotoURL(newRequest(container.retry.pop()), contentType = container.contentType) else: pager.alert("Can't load " & $container.url & " (" & errorMessage & ")") @@ -1997,6 +2014,16 @@ proc handleConnectingContainerError*(pager: Pager; i: int) = item.stream.sclose() pager.connectingContainers.del(i) +proc metaRefresh(pager: Pager; container: Container; n: int; url: URL) = + let ctx = pager.jsctx + let fun = ctx.newFunction(["url", "replace"], + "pager.gotoURL(url, {replace: replace})") + let args = [ctx.toJS(url), ctx.toJS(container)] + discard pager.timeouts.setTimeout(ttTimeout, fun, int32(n), args) + JS_FreeValue(ctx, fun) + for arg in args: + JS_FreeValue(ctx, arg) + proc handleEvent0(pager: Pager; container: Container; event: ContainerEvent): bool = case event.t @@ -2035,12 +2062,12 @@ proc handleEvent0(pager: Pager; container: Container; event: ContainerEvent): not event.save and not container.isHoverURL(url): pager.ask("Open pop-up? " & $url).then(proc(x: bool) = if x: - pager.gotoURL(event.request, some(container.url), + discard pager.gotoURL(event.request, some(container.url), referrer = pager.container, save = event.save) ) else: let url = if event.url != nil: event.url else: event.request.url - pager.gotoURL(event.request, some(container.url), + discard pager.gotoURL(event.request, some(container.url), referrer = pager.container, save = event.save, url = url) of cetStatus: if pager.container == container: @@ -2068,6 +2095,23 @@ proc handleEvent0(pager: Pager; container: Container; event: ContainerEvent): pager.connectingContainers.del(i) pager.unregisterFd(int(item.stream.fd)) item.stream.sclose() + of cetMetaRefresh: + let url = event.refreshURL + let n = event.refreshIn + case container.config.metaRefresh + of mrNever: assert false + of mrAlways: pager.metaRefresh(container, n, url) + of mrAsk: + let surl = $url + if surl in pager.refreshAllowed: + pager.metaRefresh(container, n, url) + else: + pager.ask("Redirect to " & $url & " (in " & $n & "ms)?") + .then(proc(x: bool) = + if x: + pager.refreshAllowed.incl($url) + pager.metaRefresh(container, n, url) + ) return true proc handleEvents*(pager: Pager; container: Container) = diff --git a/src/local/term.nim b/src/local/term.nim index 7391a17e..758f0fe3 100644 --- a/src/local/term.nim +++ b/src/local/term.nim @@ -84,7 +84,7 @@ type lineDamage: seq[int] attrs*: WindowAttributes colorMode: ColorMode - formatMode: FormatMode + formatMode: set[FormatFlag] imageMode*: ImageMode smcup: bool tc: Termcap @@ -257,7 +257,7 @@ proc resetFormat(term: Terminal): string = return term.cap me return SGR() -proc startFormat(term: Terminal; flag: FormatFlags): string = +proc startFormat(term: Terminal; flag: FormatFlag): string = when termcap_found: if term.isatty(): case flag @@ -269,7 +269,7 @@ proc startFormat(term: Terminal; flag: FormatFlags): string = else: discard return SGR(FormatCodes[flag].s) -proc endFormat(term: Terminal; flag: FormatFlags): string = +proc endFormat(term: Terminal; flag: FormatFlag): string = when termcap_found: if term.isatty(): case flag @@ -389,7 +389,7 @@ template rgbSGR(rgb: RGBColor; bgmod: int): string = SGR(38 + bgmod, 2, rgb.r, rgb.g, rgb.b) proc processFormat*(term: Terminal; format: var Format; cellf: Format): string = - for flag in FormatFlags: + for flag in FormatFlag: if flag in term.formatMode: if flag in format.flags and flag notin cellf.flags: result &= term.endFormat(flag) @@ -587,7 +587,7 @@ proc applyConfig(term: Terminal) = term.colorMode = term.config.display.color_mode.get if term.config.display.format_mode.isSome: term.formatMode = term.config.display.format_mode.get - for fm in FormatFlags: + for fm in FormatFlag: if fm in term.config.display.no_format_mode: term.formatMode.excl(fm) if term.config.display.image_mode.isSome: @@ -1219,7 +1219,7 @@ proc detectTermAttributes(term: Terminal; windowOnly: bool): TermStartResult = term.formatMode.incl(ffBlink) else: term.smcup = true - term.formatMode = {low(FormatFlags)..high(FormatFlags)} + term.formatMode = {FormatFlag.low..FormatFlag.high} type MouseInputType* = enum diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 984ed826..e27e42e0 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -1,4 +1,4 @@ -from std/strutils import split, toUpperAscii, find +from std/strutils import split, toUpperAscii, find, AllChars import std/macros import std/nativesockets @@ -62,7 +62,8 @@ type bcReadCanceled, bcClick, bcFindNextLink, bcFindPrevLink, bcFindNthLink, bcFindRevNthLink, bcFindNextMatch, bcFindPrevMatch, bcGetLines, bcUpdateHover, bcGotoAnchor, bcCancel, bcGetTitle, bcSelect, bcClone, - bcFindPrevParagraph, bcFindNextParagraph, bcMarkURL, bcToggleImages + bcFindPrevParagraph, bcFindNextParagraph, bcMarkURL, bcToggleImages, + bcCheckRefresh BufferState = enum bsLoadingPage, bsLoadingResources, bsLoaded @@ -131,7 +132,7 @@ type BufferConfig* = object userstyle*: string - referer_from*: bool + refererFrom*: bool styling*: bool scripting*: bool images*: bool @@ -140,6 +141,7 @@ type charsetOverride*: Charset protocol*: Table[string, ProtocolConfig] autofocus*: bool + metaRefresh*: MetaRefresh proc getFromOpaque[T](opaque: pointer; res: var T) = let opaque = cast[InterfaceOpaque](opaque) @@ -704,6 +706,52 @@ proc gotoAnchor*(buffer: Buffer): GotoAnchorResult {.proxy.} = ) return GotoAnchorResult(found: false) +type CheckRefreshResult* = object + # n is timeout in millis. -1 => not found + n*: int + # url == nil => self + url*: URL + +proc checkRefresh*(buffer: Buffer): CheckRefreshResult {.proxy.} = + if buffer.document == nil: + return CheckRefreshResult(n: -1) + let element = buffer.document.findMetaRefresh() + if element == nil: + return CheckRefreshResult(n: -1) + let s = element.attr(satContent) + var i = s.skipBlanks(0) + let s0 = s.until(AllChars - AsciiDigit, i) + let x = parseUInt32(s0, allowSign = false) + if s0 != "": + if x.isNone and (i >= s.len or s[i] != '.'): + return CheckRefreshResult(n: -1) + var n = int(x.get(0) * 1000) + i = s.skipBlanks(i + s0.len) + if i < s.len and s[i] == '.': + inc i + let s1 = s.until(AllChars - AsciiDigit, i) + if s1 != "": + n += int(parseUInt32(s1, allowSign = false).get(0)) + i = s.skipBlanks(i + s1.len) + if i >= s.len: # just reload this page + return CheckRefreshResult(n: n) + if s[i] notin {',', ';'}: + return CheckRefreshResult(n: -1) + i = s.skipBlanks(i + 1) + if s.startsWithIgnoreCase("url=", i): + i = s.skipBlanks(i + "url=".len) + var q = false + if i < s.len and s[i] in {'"', '\''}: + q = true + inc i + var s2 = s.substr(i) + if q and s2.len > 0 and s[^1] in {'"', '\''}: + s2.setLen(s2.high) + let url = buffer.document.parseURL(s2) + if url.isNone: + return CheckRefreshResult(n: -1) + return CheckRefreshResult(n: n, url: url.get) + proc do_reshape(buffer: Buffer) = if buffer.document == nil: return # not parsed yet, nothing to render diff --git a/src/types/cell.nim b/src/types/cell.nim index 8820f351..bc2f6924 100644 --- a/src/types/cell.nim +++ b/src/types/cell.nim @@ -2,19 +2,19 @@ import types/color import utils/strwidth type - FormatFlags* = enum - ffBold - ffItalic - ffUnderline - ffReverse - ffStrike - ffOverline - ffBlink + FormatFlag* = enum + ffBold = "bold" + ffItalic = "italic" + ffUnderline = "underline" + ffReverse = "reverse" + ffStrike = "strike" + ffOverline = "overline" + ffBlink = "blink" Format* = object fgcolor*: CellColor bgcolor*: CellColor - flags*: set[FormatFlags] + flags*: set[FormatFlag] SimpleFormatCell* = object format*: Format @@ -50,7 +50,7 @@ iterator items*(grid: FixedGrid): FixedCell {.inline.} = for cell in grid.cells: yield cell -const FormatCodes*: array[FormatFlags, tuple[s, e: uint8]] = [ +const FormatCodes*: array[FormatFlag, tuple[s, e: uint8]] = [ ffBold: (1u8, 22u8), ffItalic: (3u8, 23u8), ffUnderline: (4u8, 24u8), diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index 14e93bda..44c6ea3f 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -45,12 +45,17 @@ func snakeToKebabCase*(s: string): string = c = '-' func kebabToCamelCase*(s: string): string = - result = s + result = "" var flip = false - for c in result.mitems: - if flip: - c = c.toUpperAscii() - flip = c == '-' + for c in s: + if c == '-': + flip = true + else: + if flip: + result &= c.toUpperAscii() + else: + result &= c + flip = false func camelToKebabCase*(s: string): string = result = "" @@ -61,17 +66,6 @@ func camelToKebabCase*(s: string): string = else: result &= c -func startsWithNoCase*(s, prefix: string): bool = - if s.len < prefix.len: - return false - # prefix.len is always lower - var i = 0 - while true: - if i == prefix.len: return true - if s[i].toLowerAscii() != prefix[i].toLowerAscii(): - return false - inc i - func hexValue*(c: char): int = if c in AsciiDigit: return int(c) - int('0') @@ -114,9 +108,9 @@ func toHexLower*(u: uint16): string = func equalsIgnoreCase*(s1, s2: string): bool {.inline.} = return s1.cmpIgnoreCase(s2) == 0 -func startsWithIgnoreCase*(s1, s2: string): bool = +func startsWithIgnoreCase*(s1, s2: string; si = 0): bool = if s1.len < s2.len: return false - for i in 0 ..< s2.len: + for i in si ..< s2.len: if s1[i].toLowerAscii() != s2[i].toLowerAscii(): return false return true |