diff options
Diffstat (limited to 'common/content/contexts.js')
-rw-r--r-- | common/content/contexts.js | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/common/content/contexts.js b/common/content/contexts.js new file mode 100644 index 00000000..2b81fd02 --- /dev/null +++ b/common/content/contexts.js @@ -0,0 +1,318 @@ +// Copyright (c) 2010-2011 by Kris Maglione <maglione.k@gmail.com> +// +// This work is licensed for reuse under an MIT license. Details are +// given in the LICENSE.txt file included with this file. +"use strict"; + +var Group = Class("Group", { + init: function init(name, description, filter, persist) { + const self = this; + this.name = name; + this.description = description; + this.filter = filter || function (uri) true; + this.persist = persist || false; + + this.subGroups = { __proto__: this.subGroups, owner: this }; + this.subGroups.instance = this.subGroups; + }, + + get toStringParams() [this.name], + + get builtin() dactyl.builtinGroups.indexOf(this) >= 0, + + subGroups: {} + +}, { + subGroup: {}, + + subGroups: {}, + + SubGroup: Class("SubGroup", Class.Property, { + init: function init(name, constructor) { + const self = this; + if (this.Group) + return { + enumerable: true, + + get: function () array(contexts.groups[self.name]) + }; + + + this.Group = constructor; + this.name = name; + memoize(Group.prototype.subGroups, name, + function () constructor(this.owner.name, this.owner.description, + this.owner.filter, this.owner.persist)); + + memoize(Group.subGroup, name, + function () Object.create({ _subGroup: name, __proto__: contexts.subGroupProto })); + + memoize(Group.subGroups, name, + function () [g.subGroups[name] for (g in values(this.groups)) if (set.has(g.subGroups, name))]); + } + }) +}); + +var Contexts = Module("contexts", { + init: function () { + this.groupList = []; + this.groupMap = {}; + this.subGroupProto = {}; + this.builtinGroups = [this.addGroup("builtin", "Builtin items"), + this.addGroup("user", "User-defined items", null, true)]; + }, + + context: null, + + groups: Class.memoize(function () ({ + __proto__: Group.subGroups, + groups: this.groupList.filter(function (g) g.filter(buffer.uri)) + })), + + allGroups: Class.memoize(function () ({ + __proto__: Group.subGroups, + groups: this.groupList + })), + + get subGroup() Group.subGroup, + + addGroup: function addGroup(name, description, filter, persist) { + this.removeGroup(name); + + let group = Group(name, description, filter, persist); + this.groupList.unshift(group); + this.groupMap[name] = group; + this.subGroupProto.__defineGetter__(name, function () group.subGroups[this._subGroup]); + return group; + }, + + removeGroup: function removeGroup(name, filter) { + let group = this.getGroup(name); + dactyl.assert(!group || !group.builtin, "Cannot remove builtin group"); + + if (group) + this.groupList.splice(this.groupList.indexOf(group), 1); + + if (this.context && this.context.group === group) + this.context.group = null; + + delete this.groupMap[name]; + delete this.subGroupProto[name]; + delete this.groups; + return group; + }, + + getGroup: function getGroup(name, subGroup) { + let group = array.nth(this.groupList, function (h) h.name == name, 0) || null; + if (group && subGroup) + return group.subGroups[subGroup]; + return group; + }, + + bindMacro: function (args, default_, params) { + let process = util.identity; + + if (callable(params)) + var makeParams = function makeParams(self, args) + iter.toObject([k, process(v)] + for ([k, v] in iter(params.apply(self, args)))); + else if (params) + makeParams = function makeParams(self, args) + iter.toObject([name, process(args[i])] + for ([i, name] in Iterator(params))); + + let rhs = args.literalArg; + let type = ["-builtin", "-ex", "-javascript", "-keys"].reduce(function (a, b) args[b] ? b : a, default_); + switch (type) { + case "-builtin": + let noremap = true; + /* fallthrough */ + case "-keys": + let silent = args["-silent"]; + rhs = events.canonicalKeys(rhs, true); + var action = function action() events.feedkeys(action.macro(makeParams(this, arguments)), + noremap, silent); + action.macro = util.compileMacro(rhs, true); + break; + case "-ex": + action = function action() commands.execute(action.macro, makeParams(this, arguments), + false, null, action.context); + action.macro = util.compileMacro(rhs, true); + action.context = this.context && update({}, this.context); + break; + case "-javascript": + if (callable(params)) + action = dactyl.userEval("(function action() { with (action.makeParams(this, arguments)) {" + args.literalArg + "} })"); + else + action = dactyl.userFunc.apply(dactyl, params.concat(args.literalArg).array); + process = function (param) isObject(param) && param.valueOf ? param.valueOf() : param; + action.makeParams = makeParams; + break; + } + action.toString = function toString() (type === default_ ? "" : type + " ") + rhs; + args = null; + return action; + } +}, { +}, { + commands: function initCommands() { + + commands.add(["gr[oup]", "mapg[roup]"], + "Create or select a group", + function (args) { + dactyl.assert(args.length <= 2, "Trailing characters"); + + if (args.length == 0) + return void completion.listCompleter("group", ""); + + let name = Option.dequote(args[0]); + dactyl.assert(commands.validName.test(name), "Invalid group name"); + + let group = contexts.getGroup(name); + + if (args.length == 2) { + dactyl.assert(!group || args.bang, "Group exists"); + + let filter = function siteFilter(uri) + siteFilter.filters.every(function (f) f(uri) == f.result); + + update(filter, { + toString: function () this.filters.join(","), + filters: Option.splitList(args[1], true).map(function (pattern) { + let [, res, filter] = /^(!?)(.*)/.exec(pattern); + + return update(Styles.matchFilter(Option.dequote(filter)), { + result: !res, + toString: function () pattern + }); + }) + }); + + group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"]); + } + + dactyl.assert(group, "No such group: " + name); + dactyl.assert(group.name != "builtin", "Cannot modify builtin group"); + if (args.context) + args.context.group = group; + }, + { + argCount: "*", + bang: true, + completer: function (context, args) { + if (args.length == 1) + completion.group(context); + else { + Option.splitList(context.filter); + context.advance(Option._splitAt); + + context.compare = CompletionContext.Sort.unsorted; + context.completions = [ + [buffer.uri.host, "Current Host"], + [buffer.uri.spec, "Current Page"] + ]; + } + }, + keepQuotes: true, + options: [ + { + names: ["-description", "-desc", "-d"], + description: "A description of this group", + type: CommandOption.STRING + }, + { + names: ["-nopersist", "-n"], + description: "Do not save this group to an auto-generated RC file" + } + ] + }); + + commands.add(["delg[roup]", "delmapg[roup]"], + "Delete a group", + function (args) { + dactyl.assert(contexts.getGroup(args[0]), "No such group: " + args[0]); + contexts.removeGroup(args[0]); + }, + { + argCount: "1", + completer: function (context, args) { + completion.group(context); + context.filters.push(function ({ item }) !item.builtin); + } + }); + + + commands.add(["fini[sh]"], + "Stop sourcing a script file", + function (args) { + dactyl.assert(args.context, "E168: :finish used outside of a sourced file"); + args.context.finished = true; + }, + { argCount: "0" }); + + + function checkStack(cmd) { + util.assert(contexts.context && contexts.context.stack && + contexts.context.stack[cmd] && contexts.context.stack[cmd].length, + "Invalid use of conditional"); + } + function pop(cmd) { + checkStack(cmd); + return contexts.context.stack[cmd].pop(); + } + function push(cmd, value) { + util.assert(contexts.context, "Invalid use of conditional"); + if (arguments.length < 2) + value = contexts.context.noExecute; + contexts.context.stack = contexts.context.stack || {}; + contexts.context.stack[cmd] = (contexts.context.stack[cmd] || []).concat([value]); + } + + commands.add(["if"], + "Execute commands until the next :elseif, :else, or :endif only if the argument returns true", + function (args) { args.context.noExecute = !dactyl.userEval(args[0]); }, + { + always: function (args) { push("if"); }, + argCount: "1", + literal: 0 + }); + commands.add(["elsei[f]", "elif"], + "Execute commands until the next :elseif, :else, or :endif only if the argument returns true", + function (args) {}, + { + always: function (args) { + checkStack("if"); + args.context.noExecute = args.context.stack.if.slice(-1)[0] || + !args.context.noExecute || !dactyl.userEval(args[0]); + }, + argCount: "1", + literal: 0 + }); + commands.add(["el[se]"], + "Execute commands until the next :endif only if the previous conditionals were not executed", + function (args) {}, + { + always: function (args) { + checkStack("if"); + args.context.noExecute = args.context.stack.if.slice(-1)[0] || + !args.context.noExecute; + }, + argCount: "0" + }); + commands.add(["en[dif]", "fi"], + "End a string of :if/:elseif/:else conditionals", + function (args) {}, + { + always: function (args) { args.context.noExecute = pop("if"); }, + argCount: "0" + }); + }, + completion: function initCompletion() { + completion.group = function group(context, modes) { + context.title = ["Group"]; + context.keys = { text: "name", description: function (h) h.description || h.filter }; + context.completions = contexts.groupList.slice(0, -1); + }; + } +}); + |