diff options
author | bptato <nincsnevem662@gmail.com> | 2022-12-13 02:03:47 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2022-12-13 02:03:47 +0100 |
commit | 89750ef4ad621fe7fdce533d1265f3d970098a19 (patch) | |
tree | 0aa80ceb07ce0ad49e5c3ba369f96bb67c1b6dd5 | |
parent | d9e430c8147c8c2d81b4ca5405786269b2cfc94d (diff) | |
download | chawan-89750ef4ad621fe7fdce533d1265f3d970098a19.tar.gz |
Update config and config docs
-rw-r--r-- | bonus/config.toml | 15 | ||||
-rw-r--r-- | doc/config.md | 168 | ||||
-rw-r--r-- | doc/layout_engine.txt | 67 | ||||
-rw-r--r-- | res/config.toml | 10 | ||||
-rw-r--r-- | src/buffer/container.nim | 7 | ||||
-rw-r--r-- | src/config/config.nim | 19 | ||||
-rw-r--r-- | src/config/toml.nim | 57 | ||||
-rw-r--r-- | src/display/pager.nim | 15 |
8 files changed, 238 insertions, 120 deletions
diff --git a/bonus/config.toml b/bonus/config.toml deleted file mode 100644 index 39e6dc52..00000000 --- a/bonus/config.toml +++ /dev/null @@ -1,15 +0,0 @@ -[[omnirule]] -match = '^ddg:' -substitute-url = '(x) => "https://lite.duckduckgo.com/lite/?kp=-1&kd=-1&q=" + x.substring(4)' - -[[omnirule]] -match = '^wk:' -substitute-url = '(x) => "https://en.wikipedia.org/wiki/Special:Search?search=" + x.substring(4)' - -[[siteconf]] -url = '/^https?:\/\/(www\.)?npr\.org/' -rewrite-url = '(x) => x.pathname = x.pathname.replace(/(.*)\/.*/, "$1").replace(/.*\//, "")' - -[[siteconf]] -url = "^https://news.ycombinator.com.*" -cookie = true diff --git a/doc/config.md b/doc/config.md index ebb10ec9..e7f10f82 100644 --- a/doc/config.md +++ b/doc/config.md @@ -1,17 +1,75 @@ # Configuration -Chawan supports custom keybindings and user stylesheets, defined in a toml -configuration file. +Chawan supports custom keybindings and user stylesheets. The configuration +format is similar to the toml format, with the following exceptions: + +* Table arrays can be cleared like this: +``` +omnirule = [] + +[[omnirule]] # this is accepted +``` +This allows users to disable default array rules. + +* Inline tables may span across multiple lines. Rationale: the toml specified + behavior is counter-intuitive. Chawan will look for a config file in the ~/.config/chawan/ directory called `config.toml`. See the default configuration in the res/ folder for the default configuration. -A list of configurable options follows. +**Table of contents** + +* [start](Start) +* [external](External) +* [display](Display) +* [omnirule](Omnirule) +* [siteconf](Siteconf) +* [stylesheets](Stylesheets) +* [keybindings](Keybindings) + * [pager-actions](Pager actions) + * [line-editing-actions](Line-editing actions) + +## Start + +Start-up options are to be placed in the section `[start]`. + +Following is a list of start-up options: + +<table> + +<tr> +<th>**Name**</th> +<th>**Value**</th> +<th>**Function**</th> +</tr> + +<tr> +<td>visual-home</td> +<td>url</td> +<td>Page opened when cha is called with the -V option (and no other pages are +passed as arguments.)</td> +</tr> + +<tr> +<td>run-script</td> +<td>JavaScript code</td> +<td>Script cha runs on start. Pages will not be loaded until this function +exits. (setTimeout & friends do not block loading, though.)</td> +</tr> + +<tr> +<td>headless</td> +<td>boolean</td> +<td>Whether cha should always start in headless mode. Enabled when cha is +called with -r.</td> +</tr> + +</table> ## External -External options must be placed in a section called `[external]`. +External options are to be placed in the section `[external]`. Following is a list of external options: @@ -40,7 +98,7 @@ the line number.</td> ## Display -Display options must be placed in a section called `[display]`. +Display options are to be placed in the section `[display]`. Following is a list of display options: @@ -119,10 +177,75 @@ black letters on black background, etc).</td> </table> +## Omnirule + +The omni-bar (by default opened with C-l) can be used to perform searches using +omni-rules. These are to be placed in the table array `[[omnirule]]`. + +Examples: +``` +# Search using DuckDuckGo Lite. (Bound to C-k by default.) +[[omnirule]] +match = '^ddg:' +substitute-url = '(x) => "https://lite.duckduckgo.com/lite/?kp=-1&kd=-1&q=" + x.substring(4)' + +# Search using wikipedia, Firefox-style. +[[omnirule]] +match = '^@wikipedia' +substitute-url = '(x) => "https://en.wikipedia.org/wiki/Special:Search?search=" + x.replace(/@wikipedia/, "")' +``` + +Omnirule options: + +<table> + +<tr> +<th>**Name**</th> +<th>**Value**</th> +<th>**Function**</th> +</tr> + +<tr> +<td>match</td> +<td>regex</td> +<td>Regular expression used to match the input string. Note that websites +passed as arguments are matched as well.</td> +</tr> + +<tr> +<td>substitute</td> +<td>JavaScript function</td> +<td>A JavaScript function cha will pass the input string to. If a new string is +returned, it will be parsed instead of the old one.</td> +</tr> + +</table> + ## Siteconf -Configuration options can be specified for individual sites. Each entry must -be in the table array `[[siteconf]]`. +Configuration options can be specified for individual sites. Entries are to be +placed in the table array `[[siteconf]]`. + +Examples: +``` +# Enable cookies on the orange website, to make log-in work. +[[siteconf]] +url = "^https://news.ycombinator.com/.*" +cookie = true + +# Redirect npr.org to text.npr.org. +[[siteconf]] +host = '^(www\.)?npr\.org$' +rewrite-url = ''' +(x) => { + x.host = "text.npr.org"; + x.pathname = x.pathname.replace(/(.*)\/.*/, "$1").replace(/.*\//, ""); + /* No need to return; URL objects are passed by reference. */ +} +''' +``` + +Siteconf options: <table> @@ -135,13 +258,30 @@ be in the table array `[[siteconf]]`. <tr> <td>url</td> <td>regex</td> -<td>Regular expression used to match the URL.</td> +<td>Regular expression used to match the URL. Either this or the `host` option +must be specified.</td> </tr> <tr> -<td>substitute_url</td> -<td>JS function</td> -<td>A JavaScript function returning the substituted url.</td> +<td>host</td> +<td>regex</td> +<td>Regular expression used to match the host part of the URL (i.e. domain +name/ip address.) Either this or the `url` option must be specified.</td> +</tr> + +<tr> +<td>rewrite-url</td> +<td>JavaScript function</td> +<td>A JavaScript function cha will pass the URL to. If a new URL is returned, +it will replace the old one.</td> +</tr> + +<tr> +<td>cookie</td> +<td>boolean</td> +<td>Whether loading cookies should be allowed for this domain. Note: for now, +third-party cookies are always blocked. A third-party cookie setting may be +added in the future.</td> </tr> </table> @@ -162,7 +302,7 @@ There are two ways to import user stylesheets: ## Keybindings -Keybindings must be placed in these sections: +Keybindings are to be placed in these sections: * for pager interaction: `[page]` * for line editing: `[line]` @@ -256,8 +396,8 @@ is typed in. A list of built-in pager functions can be found below. </table> Some entries have an optional `bounds` parameter. If passed, this must be a -JavaScript function with one parameter (the current unicode character), and -must return true if the passed character should count as a word boundary. +JavaScript function that expects one parameter (the current unicode character), +and returns true if the passed character should count as a word boundary. ```Examples: # Control+A moves the cursor to the beginning of the line. diff --git a/doc/layout_engine.txt b/doc/layout_engine.txt deleted file mode 100644 index 0dfc13cf..00000000 --- a/doc/layout_engine.txt +++ /dev/null @@ -1,67 +0,0 @@ -CHAWAN LAYOUT ENGINE DESIGN DOCUMENT - -Laying out boxes turns out to be pretty damn expensive. This is the outline of -a simple (YMMV) yet effective layout engine that can take advantage of previous -layout passes. It doesn't include CSS cascading, which is, by the way, pretty -damn expensive as well but is done by a different module. - -Most algorithms required by this engine are recursive, which is a bit of a -problem because nim has no guarantees of your program not running out of stack -randomly. - -In most cases this shouldn't be a problem but avoiding DOS attacks will be a -bit difficult. Currently we don't support CSS that can be much deeper than the -DOM anyway (does that even exist?) so it shouldn't be that much of a problem if -we can limit the DOM depth. (I've read Safari had like a 250 depth limit at -some point, which sounds reasonable.) - -BUILDER GENERATION: --> *Box (BlockBox etc.) -> BlockBoxBuilder, InlineBoxBuilder, later also - Table/Ruby builders ---> this means that it's not a direct representation of the DOM. and it follows - that we can't preserve builders (?), so they have to be VERY cheap to - create. (maybe we can, but I kind of doubt it's worth the trouble. might - have to investigate the performance implications here.) ---> Builder attributes: children; css; direction; generated ---> generated should have 1:1 correspondence to builders. this helps figure - out what to keep at a tree rebuild: those with matching builders are kept, - those without are re-generated. ----> the re-generator algorithm sounds complicated... TODO figure something out - here - -BUILDING PROCESS: --> *Context -> BlockContext, InlineContext ---> Context attributes: children; direction (for non-flow-roots, flow root - comes later); sizes: content box; padding box; border box; margin box ---> meaning exact positions are NOT calculated until rendering, but sizes are. - this allows us to avoid rebuilding sibling contexts when a block context - changes. ---> BlockContext children: children (BlockContext) OR flow root (InlineContext) ---> BlockContext can either contain block children only or be a flow root. - those trying to be both are broken up in builder generation. ---> flow root is whenever the outer display is block and the inner flow. in - practice it should be the same as flow-root. TODO: investigate non-flow - inline display. ---> InlineContext is built from InlineBoxBuilder (TODO decide if these are - contexts or boxes... context should be better I think...) - it contains a sequence of line boxes, laid out top to bottom (ideally they - could be left-to-right, etc...) - line boxes contain inline atoms, which need to have exact positions, I - think. an inline atom is either an inline block or a word. inline atoms - are separated by inline spaces which are basically spaces you can click. ---> apropos clicking, EVERY inline word has a reference to the html element it - belongs to. ---> if ANY inline context is rebuilt, EVERY inline context in the same flow - root must be rebuilt as well. keeping track of line boxes that haven't - changed sounds like a pain in the ass with minimal benefits so we don't. - -RENDERING PROCESS --> calculating positions must be done here. ---> so arrangeInlines/arrangeBlocks must be done here ----> and we need to somehow manage margins as well. ---> since we should now have box sizes we can easily draw backgrounds as well. --> the old layout engine renders the entire document, which kind of makes - rendering take way too long. so one thing we could try is to render the - pages the user is looking at, the one before that and the one after that. - not sure if this will actually help though, since it would mean we have to - re-render the entire page every time the user scrolls up/down... diff --git a/res/config.toml b/res/config.toml index af4a0dc9..af502275 100644 --- a/res/config.toml +++ b/res/config.toml @@ -18,13 +18,9 @@ double-width-ambiguous = false minimum-contrast = 100 force-clear = false -[[siteconf]] -url = '^ddg:' -substitute_url = '(x) => "https://lite.duckduckgo.com/lite/?kp=-1&kd=-1&q=" + x.substring(4)' - -[[siteconf]] -url = '^wk:' -substitute_url = '(x) => "https://en.wikipedia.org/wiki/Special:Search?search=" + x.substring(4)' +[[omnirule]] +match = '^ddg:' +substitute-url = '(x) => "https://lite.duckduckgo.com/lite/?kp=-1&kd=-1&q=" + x.substring(4)' [page] q = 'quit()' diff --git a/src/buffer/container.nim b/src/buffer/container.nim index a148e0e1..a08f4cb0 100644 --- a/src/buffer/container.nim +++ b/src/buffer/container.nim @@ -97,8 +97,11 @@ type events*: Deque[ContainerEvent] startpos: Option[CursorPosition] hasstart: bool + redirectdepth*: int -proc newBuffer*(dispatcher: Dispatcher, config: Config, source: BufferSource, cookiejar: CookieJar, title = ""): Container = +proc newBuffer*(dispatcher: Dispatcher, config: Config, source: BufferSource, + cookiejar: CookieJar, title = "", + redirectdepth = 0): Container = let attrs = getWindowAttributes(stdout) let config = config.getBufferConfig(source.location, cookiejar) let ostream = dispatcher.forkserver.ostream @@ -112,7 +115,7 @@ proc newBuffer*(dispatcher: Dispatcher, config: Config, source: BufferSource, co result = Container( source: source, attrs: attrs, width: attrs.width, height: attrs.height - 1, contenttype: source.contenttype, - title: title, config: config + title: title, config: config, redirectdepth: redirectdepth ) istream.sread(result.process) result.pos.setx = -1 diff --git a/src/config/config.nim b/src/config/config.nim index 73072763..16e1c652 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -315,14 +315,17 @@ proc parseConfig(config: Config, dir: string, t: TomlValue) = assert conf.url.isSome != conf.host.isSome config.siteconf.add(conf) of "omnirule": - for v in v: - var rule = StaticOmniRule() - for k, v in v: - case k - of "match": rule.match = v.s - of "substitute-url": rule.subst = v.s - if rule.match != "": - config.omnirules.add(rule) + if v.vt == VALUE_ARRAY and v.a.len == 0: + config.omnirules.setLen(0) + else: + for v in v: + var rule = StaticOmniRule() + for k, v in v: + case k + of "match": rule.match = v.s + of "substitute": rule.subst = v.s + if rule.match != "": + config.omnirules.add(rule) proc parseConfig(config: Config, dir: string, stream: Stream) = config.parseConfig(dir, parseToml(stream)) diff --git a/src/config/toml.nim b/src/config/toml.nim index 2c4550b5..89277d24 100644 --- a/src/config/toml.nim +++ b/src/config/toml.nim @@ -323,7 +323,10 @@ proc consumeNoState(state: var TomlParser): bool = node.map[table.key[i]] = TomlValue(vt: VALUE_TABLE, t: t2) node = t2 if table.key[^1] in node.map: - let last = node.map[table.key[^1]] + var last = node.map[table.key[^1]] + if last.vt == VALUE_ARRAY and last.a.len == 0: + last = TomlValue(vt: VALUE_TABLE_ARRAY) + node.map[table.key[^1]] = last if last.vt != VALUE_TABLE_ARRAY: let key = table.key.join('.') state.valueError(fmt"re-definition of node {key} as table array (was {last.vt})") @@ -396,11 +399,59 @@ proc consumeArray(state: var TomlParser): TomlValue = of ',': if val == nil: state.syntaxError("comma without element") - result.a.add(val) + if val.vt == VALUE_TABLE: + # inline table array + result = TomlValue(vt: VALUE_TABLE_ARRAY) + result.ta.add(val.t) + else: + result.a.add(val) else: + if val != nil: + state.syntaxError("missing comma") state.reconsume() val = state.consumeValue() +proc consumeInlineTable(state: var TomlParser): TomlValue = + result = TomlValue(vt: VALUE_TABLE, t: TomlTable()) + var key: seq[string] + var haskey: bool + var val: TomlValue + while state.has(): + let c = state.consume() + case c + of ' ', '\t', '\n': discard + of '}': + if val != nil: + result.a.add(val) + break + of ',': + if key.len == 0: + state.syntaxError("missing key") + if val == nil: + state.syntaxError("comma without element") + var table = result.t + for i in 0 ..< key.high: + let k = key[i] + if k in table.map: + state.syntaxError(fmt"invalid re-definition of key {k}") + else: + let node = TomlTable() + table.map[k] = TomlValue(vt: VALUE_TABLE, t: node) + table = node + let k = key[^1] + if k in table.map: + state.syntaxError(fmt"invalid re-definition of key {k}") + table.map[k] = val + else: + if val != nil: + state.syntaxError("missing comma") + if not haskey: + key = state.consumeKey() + haskey = true + else: + state.reconsume() + val = state.consumeValue() + proc consumeValue(state: var TomlParser): TomlValue = while state.has(): let c = state.consume() @@ -417,6 +468,8 @@ proc consumeValue(state: var TomlParser): TomlValue = #TODO date-time of '[': return state.consumeArray() + of '{': + return state.consumeInlineTable() elif c.isBare(): let s = state.consumeBare(c) case s diff --git a/src/display/pager.nim b/src/display/pager.nim index 06b28346..66280edc 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -483,7 +483,9 @@ proc applySiteconf(pager: Pager, request: Request) = pager.cookiejars[request.url.host] = newCookieJar(request.url) # Load request in a new buffer. -proc gotoURL*(pager: Pager, request: Request, prevurl = none(URL), ctype = none(string), replace: Container = nil) = +proc gotoURL*(pager: Pager, request: Request, prevurl = none(URL), + ctype = none(string), replace: Container = nil, + redirectdepth = 0) = pager.applySiteconf(request) if prevurl.isnone or not prevurl.get.equals(request.url, true) or request.url.hash == "" or request.httpmethod != HTTP_GET: @@ -500,7 +502,7 @@ proc gotoURL*(pager: Pager, request: Request, prevurl = none(URL), ctype = none( location: request.url ) let cookiejar = pager.cookiejars.getOrDefault(request.url.host) - let container = pager.dispatcher.newBuffer(pager.config, source, cookiejar) + let container = pager.dispatcher.newBuffer(pager.config, source, cookiejar, redirectdepth = redirectdepth) if replace != nil: container.replace = replace container.copyCursorPos(container.replace) @@ -711,9 +713,12 @@ proc handleEvent0(pager: Pager, container: Container, event: ContainerEvent): bo if pager.container == container: pager.authorize() of REDIRECT: - let redirect = event.location - pager.alert("Redirecting to " & $redirect) - pager.gotoURL(newRequest(redirect), some(pager.container.source.location), replace = pager.container) + if container.redirectdepth < 10: + let redirect = event.location + pager.alert("Redirecting to " & $redirect) + pager.gotoURL(newRequest(redirect), some(container.source.location), replace = container, redirectdepth = container.redirectdepth + 1) + else: + pager.alert("Error: maximum redirection depth reached") of ANCHOR: var url2 = newURL(container.source.location) url2.hash(event.anchor) |