-// Copyright (c) 2010-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2010-2012 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";
-try {
-
-Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("contexts", {
exports: ["Contexts", "Group", "contexts"],
- use: ["commands", "messages", "options", "services", "storage", "styles", "template", "util"]
-}, this);
+ require: ["services", "util"]
+});
+
+lazyRequire("commands", ["ArgType", "CommandOption", "commands"]);
+lazyRequire("options", ["Option"]);
+lazyRequire("overlay", ["overlay"]);
+lazyRequire("storage", ["File"]);
+lazyRequire("template", ["template"]);
var Const = function Const(val) Class.Property({ enumerable: true, value: val });
var Group = Class("Group", {
init: function init(name, description, filter, persist) {
- const self = this;
-
this.name = name;
this.description = description;
this.filter = filter || this.constructor.defaultFilter;
this.persist = persist || false;
this.hives = [];
+ this.children = [];
},
+ get contexts() this.modules.contexts,
+
+ set lastDocument(val) { this._lastDocument = util.weakReference(val); },
+ get lastDocument() this._lastDocument && this._lastDocument.get(),
+
modifiable: true,
- cleanup: function cleanup() {
+ cleanup: function cleanup(reason) {
for (let hive in values(this.hives))
util.trapErrors("cleanup", hive);
this.hives = [];
for (let hive in keys(this.hiveMap))
delete this[hive];
+
+ if (reason != "shutdown")
+ this.children.splice(0).forEach(this.contexts.closure.removeGroup);
},
- destroy: function destroy() {
+ destroy: function destroy(reason) {
for (let hive in values(this.hives))
util.trapErrors("destroy", hive);
+
+ if (reason != "shutdown")
+ this.children.splice(0).forEach(this.contexts.closure.removeGroup);
},
argsExtra: function argsExtra() ({}),
+ makeArgs: function makeArgs(doc, context, args) {
+ let res = update({ doc: doc, context: context }, args);
+ return update(res, this.argsExtra(res), args);
+ },
+
get toStringParams() [this.name],
get builtin() this.modules.contexts.builtinGroups.indexOf(this) >= 0,
}, {
- compileFilter: function (patterns, default_) {
+ compileFilter: function (patterns, default_ = false) {
if (arguments.length < 2)
default_ = false;
return update(siteFilter, {
toString: function () this.filters.join(","),
- toXML: function (modules) let (uri = modules && modules.buffer.uri)
+ toJSONXML: function (modules) let (uri = modules && modules.buffer.uri)
template.map(this.filters,
- function (f) <span highlight={uri && f(uri) ? "Filter" : ""}>{f}</span>,
- <>,</>),
+ function (f) ["span", { highlight: uri && f(uri) ? "Filter" : "" },
+ "toJSONXML" in f ? f.toJSONXML() : String(f)],
+ ","),
filters: Option.parse.sitelist(patterns)
});
},
- defaultFilter: Class.memoize(function () this.compileFilter(["*"]))
+ defaultFilter: Class.Memoize(function () this.compileFilter(["*"]))
});
var Contexts = Module("contexts", {
+ init: function () {
+ this.pluginModules = {};
+ },
+
+ cleanup: function () {
+ for each (let module in this.pluginModules)
+ util.trapErrors("unload", module);
+
+ this.pluginModules = {};
+ },
+
Local: function Local(dactyl, modules, window) ({
init: function () {
const contexts = this;
completer: function (context) modules.completion.group(context)
});
+
+ memoize(modules, "userContext", function () contexts.Context(modules.io.getRCFile("~", true), contexts.user, [modules, true]));
+ memoize(modules, "_userContext", function () contexts.Context(modules.io.getRCFile("~", true), contexts.user, [modules.userContext]));
},
cleanup: function () {
- for (let hive in values(this.groupList))
- util.trapErrors("cleanup", hive);
+ for each (let hive in this.groupList.slice())
+ util.trapErrors("cleanup", hive, "shutdown");
},
destroy: function () {
- for (let hive in values(this.groupList))
- util.trapErrors("destroy", hive);
+ for each (let hive in values(this.groupList.slice()))
+ util.trapErrors("destroy", hive, "shutdown");
for (let [name, plugin] in iter(this.modules.plugins.contexts))
- if (plugin && "onUnload" in plugin)
+ if (plugin && "onUnload" in plugin && callable(plugin.onUnload))
util.trapErrors("onUnload", plugin);
},
Hives: Class("Hives", Class.Property, {
init: function init(name, constructor) {
const { contexts } = modules;
- const self = this;
if (this.Hive)
return {
enumerable: true,
- get: function () array(contexts.groups[self.name])
+ get: () => array(contexts.groups[this.name])
};
this.Hive = constructor;
{ _hive: { value: name } })));
memoize(contexts.groupsProto, name,
- function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]);
+ function () [group[name] for (group in values(this.groups)) if (Set.has(group, name))]);
},
get toStringParams() [this.name, this.Hive]
function (dir) dir.contains(file, true),
0);
+ let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-")
+ : file.leafName;
+ let id = util.camelCase(name.replace(/\.[^.]*$/, ""));
+
let contextPath = file.path;
- let self = set.has(plugins, contextPath) && plugins.contexts[contextPath];
+ let self = Set.has(plugins, contextPath) && plugins.contexts[contextPath];
+
+ if (!self && isPlugin && false)
+ self = Set.has(plugins, id) && plugins[id];
if (self) {
- if (set.has(self, "onUnload"))
- self.onUnload();
+ if (Set.has(self, "onUnload"))
+ util.trapErrors("onUnload", self);
}
else {
- let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-")
- : file.leafName;
+ let params = Array.slice(args || [userContext]);
+ params[2] = params[2] || File(file).URI.spec;
- self = update(newContext.apply(null, args || [userContext]), {
- NAME: Const(name.replace(/\.[^.]*$/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())),
+ self = args && !isArray(args) ? args : newContext.apply(null, params);
+ update(self, {
+ NAME: Const(id),
PATH: Const(file.path),
CONTEXT: Const(self),
+ set isGlobalModule(val) {
+ // Hack.
+ if (val)
+ throw Contexts;
+ },
+
unload: Const(function unload() {
if (plugins[this.NAME] === this || plugins[this.PATH] === this)
if (this.onUnload)
- this.onUnload();
+ util.trapErrors("onUnload", this);
if (plugins[this.NAME] === this)
delete plugins[this.NAME];
contexts.removeGroup(this.GROUP);
})
});
- Class.replaceProperty(plugins, file.path, self);
+
+ if (group !== this.user)
+ Class.replaceProperty(plugins, file.path, self);
// This belongs elsewhere
- if (isPlugin && args)
+ if (isPlugin)
Object.defineProperty(plugins, self.NAME, {
configurable: true,
enumerable: true,
group = this.addGroup(commands.nameRegexp
.iterate(name.replace(/\.[^.]*$/, ""))
.join("-").replace(/--+/g, "-"),
- "Script group for " + file.path,
+ _("context.scriptGroup", file.path),
null, false);
Class.replaceProperty(self, "GROUP", group);
return this.Context(file, group, [this.modules.userContext, true]);
},
+ Module: function Module(uri, isPlugin) {
+ const { io, plugins } = this.modules;
+
+ let canonical = uri.spec;
+ if (uri.scheme == "resource")
+ canonical = services["resource:"].resolveURI(uri);
+
+ if (uri instanceof Ci.nsIFileURL)
+ var file = File(uri.file);
+
+ let isPlugin = array.nth(io.getRuntimeDirectories("plugins"),
+ function (dir) dir.contains(file, true),
+ 0);
+
+ let name = isPlugin && file && file.getRelativeDescriptor(isPlugin)
+ .replace(File.PATH_SEP, "-");
+ let id = util.camelCase(name.replace(/\.[^.]*$/, ""));
+
+ let self = Set.has(this.pluginModules, canonical) && this.pluginModules[canonical];
+
+ if (!self) {
+ self = Object.create(jsmodules);
+
+ update(self, {
+ NAME: Const(id),
+
+ PATH: Const(file && file.path),
+
+ CONTEXT: Const(self),
+
+ get isGlobalModule() true,
+ set isGlobalModule(val) {
+ util.assert(val, "Loading non-global module as global",
+ false);
+ },
+
+ unload: Const(function unload() {
+ if (contexts.pluginModules[canonical] == this) {
+ if (this.onUnload)
+ util.trapErrors("onUnload", this);
+
+ delete contexts.pluginModules[canonical];
+ }
+
+ for each (let { plugins } in overlay.modules)
+ if (plugins[this.NAME] == this)
+ delete plugins[this.name];
+ })
+ });
+
+ JSMLoader.loadSubScript(uri.spec, self, File.defaultEncoding);
+ this.pluginModules[canonical] = self;
+ }
+
+ // This belongs elsewhere
+ if (isPlugin)
+ Object.defineProperty(plugins, self.NAME, {
+ configurable: true,
+ enumerable: true,
+ get: function () self,
+ set: function (val) {
+ util.dactyl(val).reportError(FailedAssertion(_("plugin.notReplacingContext", self.NAME), 3, false), true);
+ }
+ });
+
+ return self;
+ },
+
context: null,
/**
return {
__proto__: frame,
filename: this.context.file[0] == "[" ? this.context.file
- : services.io.newFileURI(File(this.context.file)).spec,
+ : File(this.context.file).URI.spec,
lineNumber: this.context.line
};
return frame;
},
- groups: Class.memoize(function () this.matchingGroups(this.modules.buffer.uri)),
+ groups: Class.Memoize(function () this.matchingGroups()),
- allGroups: Class.memoize(function () Object.create(this.groupsProto, {
+ allGroups: Class.Memoize(function () Object.create(this.groupsProto, {
groups: { value: this.initializedGroups() }
})),
matchingGroups: function (uri) Object.create(this.groupsProto, {
- groups: { value: this.activeGroups(uri) },
+ groups: { value: this.activeGroups(uri) }
}),
- activeGroups: function (uri, doc) {
+ activeGroups: function (uri) {
+ if (uri instanceof Ci.nsIDOMDocument)
+ var [doc, uri] = [uri, uri.documentURIObject || util.newURI(uri.documentURI)];
+
if (!uri)
- ({ uri, doc }) = this.modules.buffer;
- return this.initializedGroups().filter(function (g) uri && g.filter(uri, doc));
+ var { uri, doc } = this.modules.buffer;
+
+ return this.initializedGroups().filter(function (g) {
+ let res = uri && g.filter(uri, doc);
+ if (doc)
+ g.lastDocument = res && doc;
+ return res;
+ });
},
flush: function flush() {
initializedGroups: function (hive)
let (need = hive ? [hive] : Object.keys(this.hives))
- this.groupList.filter(function (group) need.some(set.has(group))),
+ this.groupList.filter(function (group) need.some(Set.has(group))),
addGroup: function addGroup(name, description, filter, persist, replace) {
let group = this.getGroup(name);
if (replace) {
util.trapErrors("cleanup", group);
+
if (description)
group.description = description;
if (filter)
- group.filter = filter
+ group.filter = filter;
group.persist = persist;
}
return group;
},
- removeGroup: function removeGroup(name, filter) {
+ removeGroup: function removeGroup(name) {
if (isObject(name)) {
if (this.groupList.indexOf(name) === -1)
return;
getGroup: function getGroup(name, hive) {
if (name === "default")
var group = this.context && this.context.context && this.context.context.GROUP;
- else if (set.has(this.groupMap, name))
+ else if (Set.has(this.groupMap, name))
group = this.groupMap[name];
if (group && hive)
return group;
},
+ getDocs: function getDocs(context) {
+ try {
+ if (isinstance(context, ["Sandbox"])) {
+ let info = "INFO" in context && Cu.evalInSandbox("this.INFO instanceof XML ? INFO.toXMLString() : this.INFO", context);
+ return /^</.test(info) ? XML(info) : info;
+ }
+ if (DOM.isJSONXML(context.INFO))
+ return context.INFO;
+ if (typeof context.INFO == "xml" && config.haveGecko(null, "14.*"))
+ return context.INFO;
+ }
+ catch (e) {}
+ return null;
+ },
+
bindMacro: function (args, default_, params) {
const { dactyl, events, modules } = this.modules;
+ function Proxy(obj, key) Class.Property({
+ configurable: true,
+ enumerable: true,
+ get: function Proxy_get() process(obj[key]),
+ set: function Proxy_set(val) obj[key] = val
+ })
+
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))));
+ let (obj = params.apply(self, args))
+ iter.toObject([k, Proxy(obj, k)] for (k in properties(obj)));
else if (params)
makeParams = function makeParams(self, args)
iter.toObject([name, process(args[i])]
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);
+ rhs = DOM.Event.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() modules.commands
.execute(action.macro, makeParams(this, arguments),
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);
+ action = dactyl.userFunc.apply(dactyl, params.concat(args.literalArg));
process = function (param) isObject(param) && param.valueOf ? param.valueOf() : param;
action.params = params;
action.makeParams = makeParams;
break;
}
+
action.toString = function toString() (type === default_ ? "" : type + " ") + rhs;
args = null;
return action;
get modifiable() this.group.modifiable,
get argsExtra() this.group.argsExtra,
+ get makeArgs() this.group.makeArgs,
get builtin() this.group.builtin,
get name() this.group.name,
get persist() this.group.persist,
set persist(val) this.group.persist = val,
- prefix: Class.memoize(function () this.name === "builtin" ? "" : this.name + ":"),
+ prefix: Class.Memoize(function () this.name === "builtin" ? "" : this.name + ":"),
get toStringParams() [this.name]
})
if (args.has("-locations"))
group.filter = filter;
if (args.has("-description"))
- group.description = args["-description"]
+ group.description = args["-description"];
if (args.has("-nopersist"))
- group.persist = !args["-nopersist"]
+ group.persist = !args["-nopersist"];
}
if (!group.builtin && args.has("-args")) {
group.args = args["-args"];
}
- if (args.context)
+ if (args.context) {
args.context.group = group;
+ if (args.context.context) {
+ args.context.context.group = group;
+
+ let parent = args.context.context.GROUP;
+ if (parent && parent != group) {
+ group.parent = parent;
+ if (!~parent.children.indexOf(group))
+ parent.children.push(group);
+ }
+ }
+ }
util.assert(!group.builtin ||
!["-description", "-locations", "-nopersist"]
- .some(set.has(args.explicitOpts)),
+ .some(Set.has(args.explicitOpts)),
_("group.cantModifyBuiltin"));
},
{
{
names: ["-description", "-desc", "-d"],
description: "A description of this group",
- default: ["User-defined group"],
+ default: "User-defined group",
type: CommandOption.STRING
},
{
names: ["-locations", "-locs", "-loc", "-l"],
- description: ["The URLs for which this group should be active"],
+ description: "The URLs for which this group should be active",
default: ["*"],
type: CommandOption.LIST
},
commands.add(["delg[roup]"],
"Delete a group",
function (args) {
- util.assert(contexts.getGroup(args[0]), _("group.noSuch", args[0]));
- contexts.removeGroup(args[0]);
+ util.assert(args.bang ^ !!args[0], _("error.argumentOrBang"));
+
+ if (args.bang)
+ contexts.groupList = contexts.groupList.filter(function (g) g.builtin);
+ else {
+ util.assert(contexts.getGroup(args[0]), _("group.noSuch", args[0]));
+ contexts.removeGroup(args[0]);
+ }
},
{
- argCount: "1",
+ argCount: "?",
+ bang: true,
completer: function (context, args) {
- modules.completion.group(context);
+ if (args.bang)
+ return;
context.filters.push(function ({ item }) !item.builtin);
+ modules.completion.group(context);
}
});
context.keys = {
active: function (group) group.filter(uri),
text: "name",
- description: function (g) <>{g.filter.toXML ? g.filter.toXML(modules) + <> </> : ""}{g.description || ""}</>
+ description: function (g) ["", g.filter.toJSONXML ? g.filter.toJSONXML(modules).concat("\u00a0") : "", g.description || ""]
};
context.completions = (active === undefined ? contexts.groupList : contexts.initializedGroups(active))
.slice(0, -1);
endModule();
-} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
+// catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
-// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: