diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config/config.nim | 94 | ||||
-rw-r--r-- | src/config/toml.nim | 3 | ||||
-rw-r--r-- | src/local/client.nim | 18 | ||||
-rw-r--r-- | src/main.nim | 12 |
4 files changed, 112 insertions, 15 deletions
diff --git a/src/config/config.nim b/src/config/config.nim index ebf36d49..287c2e54 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -1,8 +1,10 @@ import std/options import std/os import std/streams +import std/strutils import std/tables +import bindings/quickjs import config/chapath import config/mailcap import config/mimetypes @@ -12,6 +14,7 @@ import js/fromjs import js/javascript import js/propertyenumlist import js/regex +import js/tojs import loader/headers import types/cell import types/color @@ -70,6 +73,11 @@ type display_charset* {.jsgetset.}: Option[Charset] document_charset* {.jsgetset.}: seq[Charset] + CommandConfig = object + jsObj*: JSValue + init*: seq[tuple[k, cmd: string]] # initial k/v map + map*: Table[string, JSValue] # qualified name -> function + ExternalConfig = object tmpdir* {.jsgetset.}: ChaPathResolved editor* {.jsgetset.}: string @@ -130,6 +138,7 @@ type #TODO getset siteconf*: seq[SiteConfig] omnirule*: seq[OmniRule] + cmd*: CommandConfig page* {.jsget.}: ActionMap line* {.jsget.}: ActionMap @@ -331,6 +340,8 @@ proc parseConfigValue(ctx: var ConfigParser; x: var Mailcap; v: TomlValue; k: string) proc parseConfigValue(ctx: var ConfigParser; x: var URIMethodMap; v: TomlValue; k: string) +proc parseConfigValue(ctx: var ConfigParser; x: var CommandConfig; v: TomlValue; + k: string) proc typeCheck(v: TomlValue; t: TomlValueType; k: string) = if v.t != t: @@ -626,6 +637,28 @@ proc parseConfigValue(ctx: var ConfigParser; x: var URIMethodMap; v: TomlValue; x.parseURIMethodMap(f.readAll()) x.append(DefaultURIMethodMap) +func isCompatibleIdent(s: string): bool = + if s.len == 0 or s[0] notin AsciiAlpha + {'_', '$'}: + return false + for i in 1 ..< s.len: + if s[i] notin AsciiAlphaNumeric + {'_', '$'}: + return false + return true + +proc parseConfigValue(ctx: var ConfigParser; x: var CommandConfig; v: TomlValue; + k: string) = + typeCheck(v, tvtTable, k) + for kk, vv in v: + let kkk = k & "." & kk + typeCheck(vv, {tvtTable, tvtString}, kkk) + if not kk.isCompatibleIdent(): + raise newException(ValueError, "invalid command name: " & kkk) + if vv.t == tvtTable: + ctx.parseConfigValue(x, vv, kkk) + else: # tvtString + # skip initial "cmd.", we don't need it + x.init.add((kkk.substr("cmd.".len), vv.s)) + type ParseConfigResult* = object success*: bool warnings*: seq[string] @@ -700,15 +733,64 @@ proc getNormalAction*(config: Config; s: string): string = proc getLinedAction*(config: Config; s: string): string = return config.line.getOrDefault(s) -proc readConfig*(pathOverride: Option[string]; jsctx: JSContext): Config = - result = Config(jsctx: jsctx) - discard result.parseConfig("res", newStringStream(defaultConfig)) #TODO TODO TODO +type ReadConfigResult = tuple + config: Config + res: ParseConfigResult + +proc readConfig*(pathOverride: Option[string]; jsctx: JSContext): + ReadConfigResult = + let config = Config(jsctx: jsctx) + var res = config.parseConfig("res", newStringStream(defaultConfig)) + if not res.success: + return (nil, res) if pathOverride.isNone: when defined(debug): - discard result.readConfig(getCurrentDir() / "res", "config.toml") - discard result.readConfig(getConfigDir() / "chawan", "config.toml") + res = config.readConfig(getCurrentDir() / "res", "config.toml") + if not res.success: + return (nil, res) + res = config.readConfig(getConfigDir() / "chawan", "config.toml") else: - discard result.readConfig(getCurrentDir(), pathOverride.get) + res = config.readConfig(getCurrentDir(), pathOverride.get) + if not res.success: + return (nil, res) + return (config, res) + +# called after parseConfig returns +proc initCommands*(config: Config): Err[string] = + let ctx = config.jsctx + let obj = JS_NewObject(ctx) + defer: JS_FreeValue(ctx, obj) + if JS_IsException(obj): + return err(ctx.getExceptionStr()) + for i in countdown(config.cmd.init.high, 0): + let (k, cmd) = config.cmd.init[i] + if k in config.cmd.map: + # already in map; skip + continue + var objIt = obj + let name = k.afterLast('.') + if name.len < k.len: + for ss in k.substr(0, k.high - name.len - 1).split('.'): + var prop = JS_GetPropertyStr(ctx, objIt, cstring(ss)) + if JS_IsUndefined(prop): + prop = JS_NewObject(ctx) + ctx.definePropertyE(objIt, ss, prop) + if JS_IsException(prop): + return err(ctx.getExceptionStr()) + objIt = prop + if cmd == "": + config.cmd.map[k] = JS_UNDEFINED + continue + let fun = ctx.eval(cmd, "<" & k & ">", JS_EVAL_TYPE_GLOBAL) + if JS_IsException(fun): + return err(ctx.getExceptionStr()) + if not JS_IsFunction(ctx, fun): + return err(k & " is not a function") + ctx.definePropertyE(objIt, name, JS_DupValue(ctx, fun)) + config.cmd.map[k] = fun + config.cmd.jsObj = JS_DupValue(ctx, obj) + config.cmd.init = @[] + ok() proc addConfigModule*(ctx: JSContext) = ctx.registerType(ActionMap) diff --git a/src/config/toml.nim b/src/config/toml.nim index 5f471b27..760f335d 100644 --- a/src/config/toml.nim +++ b/src/config/toml.nim @@ -183,6 +183,7 @@ proc consumeString(state: var TomlParser, first: char): multiline = true state.seek(2) if multiline and state.peek(0) == '\n': + inc state.line discard state.consume() var escape = false var ml_trim = false @@ -223,6 +224,8 @@ proc consumeString(state: var TomlParser, first: char): if c notin {'\n', ' ', '\t'}: res &= c ml_trim = false + if c == '\n': + inc state.line else: if c == '\n': inc state.line diff --git a/src/local/client.nim b/src/local/client.nim index 72c85744..c63a18db 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -181,8 +181,13 @@ proc handlePagerEvents(client: Client) = if container != nil: client.pager.handleEvents(container) -proc evalAction(client: Client, action: string, arg0: int32): EmptyPromise = - var ret = client.evalJS(action, "<command>") +proc evalActionJS(client: Client; action: string): JSValue = + client.config.cmd.map.withValue(action, p): + return JS_DupValue(client.jsctx, p[]) + return client.evalJS(action, "<command>") + +proc evalAction(client: Client; action: string; arg0: int32): EmptyPromise = + var ret = client.evalActionJS(action) let ctx = client.jsctx var p = EmptyPromise() p.resolve() @@ -814,16 +819,13 @@ proc newClient*(config: Config; forkserver: ForkServer; jsctx: JSContext; tmpdir: config.external.tmpdir )) pager.setLoader(loader) - let client = Client( - config: config, - jsrt: jsrt, - jsctx: jsctx, - pager: pager - ) + let client = Client(config: config, jsrt: jsrt, jsctx: jsctx, pager: pager) jsrt.setInterruptHandler(interruptHandler, cast[pointer](client)) var global = JS_GetGlobalObject(jsctx) jsctx.registerType(Client, asglobal = true) setGlobal(jsctx, global, client) + jsctx.definePropertyE(global, "cmd", config.cmd.jsObj) + config.cmd.jsObj = JS_NULL JS_FreeValue(jsctx, global) client.addJSModules(jsctx) return client diff --git a/src/main.nim b/src/main.nim index 193a3575..20d9c564 100644 --- a/src/main.nim +++ b/src/main.nim @@ -175,14 +175,24 @@ proc main() = inc ctx.i let jsrt = newJSRuntime() let jsctx = jsrt.newJSContext() - let config = readConfig(ctx.configPath, jsctx) var warnings = newSeq[string]() + let (config, res) = readConfig(ctx.configPath, jsctx) + if not res.success: + stderr.write(res.errorMsg) + quit(1) + warnings.add(res.warnings) for opt in ctx.opts: let res = config.parseConfig(getCurrentDir(), opt, laxnames = true) if not res.success: stderr.write(res.errorMsg) quit(1) + warnings.add(res.warnings) config.css.stylesheet &= ctx.stylesheet + block commands: + let res = config.initCommands() + if res.isErr: + stderr.write("Error parsing commands: " & res.error) + quit(1) set_cjk_ambiguous(config.display.double_width_ambiguous) if pages.len == 0 and stdin.isatty(): if ctx.visual: |