diff --git a/common/bootstrap.js b/common/bootstrap.js
index 93f4d658..e029387c 100755
--- a/common/bootstrap.js
+++ b/common/bootstrap.js
@@ -207,6 +207,7 @@ function init() {
let pref = "extensions.dactyl.cacheFlushCheck";
let val = addon.version + "-" + hardSuffix;
if (!Services.prefs.prefHasUserValue(pref) || Services.prefs.getCharPref(pref) != val) {
+ var cacheFlush = true;
Services.obs.notifyObservers(null, "startupcache-invalidate", "");
Services.prefs.setCharPref(pref, val);
}
@@ -244,6 +245,7 @@ function init() {
JSMLoader.load(BOOTSTRAP_JSM, global);
JSMLoader.init(suffix);
+ JSMLoader.cacheFlush = cacheFlush;
JSMLoader.load("base.jsm", global);
if (!(BOOTSTRAP_CONTRACT in Cc)) {
diff --git a/common/content/buffer.js b/common/content/buffer.js
index 156eb049..f6c8eb75 100644
--- a/common/content/buffer.js
+++ b/common/content/buffer.js
@@ -1291,6 +1291,10 @@ var Buffer = Module("buffer", {
if (!DOM(elem).isScrollable(horizontal ? "horizontal" : "vertical"))
return false;
+ return this.canScroll(elem, dir, horizontal);
+ },
+
+ canScroll: function canScroll(elem, dir, horizontal) {
let pos = "scrollTop", size = "clientHeight", max = "scrollHeight", layoutSize = "offsetHeight",
overflow = "overflowX", border1 = "borderTopWidth", border2 = "borderBottomWidth";
if (horizontal)
diff --git a/common/content/dactyl.js b/common/content/dactyl.js
index f368786d..474f533b 100644
--- a/common/content/dactyl.js
+++ b/common/content/dactyl.js
@@ -111,6 +111,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
}
},
+ signals: {
+ "io.source": function ioSource(context, file, modTime) {
+ if (context.INFO)
+ help.flush("help/plugins.xml", modTime);
+ }
+ },
+
profileName: deprecated("config.profileName", { get: function profileName() config.profileName }),
/**
@@ -484,8 +491,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
if (fileName && fileName[0] == "[")
fileName = "dactyl://command-line/";
-
- if (!context && fileName && fileName[0] !== "[")
+ else if (!context)
context = ctxt || _userContext;
if (isinstance(context, ["Sandbox"]))
@@ -659,82 +665,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
* @private
* Initialize the help system.
*/
- initHelp: function initHelp(force) {
+ initHelp: function initHelp() {
if ("noscriptOverlay" in window)
noscriptOverlay.safeAllow("dactyl:", true, false);
- if (!force && help.initialized)
- return;
-
- help.initialize(force);
-
- // Process plugin help entries.
- XML.ignoreWhiteSpace = XML.prettyPrinting = false;
-
- let body = XML();
- for (let [, context] in Iterator(plugins.contexts))
- try {
- let info = contexts.getDocs(context);
- if (info instanceof XML) {
- if (info.*.@lang.length()) {
- let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
-
- info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
-
- for each (let elem in info.NS::info)
- for each (let attr in ["@name", "@summary", "@href"])
- if (elem[attr].length())
- info[attr] = elem[attr];
- }
- body +=
{info.@summary}
+
- info;
- }
- }
- catch (e) {
- util.reportError(e);
- }
-
- help.files["plugins"] = function () ['text/xml;charset=UTF-8',
- '\n' +
- '\n' +
- '\n' +
-
- {_("help.title.Using Plugins")}
-
-
- {body}
- .toXMLString()];
-
-
- default xml namespace = NS;
-
- help.overlays["index"] = ['text/xml;charset=UTF-8',
- '\n' +
- {
- template.map(dactyl.indices, function ([name, iter])
- {
- template.map(iter(), util.identity)
- }
, <>{"\n\n"}>)
- }];
-
- help.overlays["gui"] = ['text/xml;charset=UTF-8',
- '\n' +
-
- {
- template.map(config.dialogs, function ([name, val])
- (!val[2] || val[2]())
- ? <>- {name}
- {val[0]}
>
- : undefined,
- <>{"\n"}>)
- }
- ];
-
- help.tags["plugins"] = help.tags["plugins.xml"] = "plugins";
- help.tags["index"] = help.tags["index.xml"] = "index";
-
- help.addTags("plugins", util.httpGet("dactyl://help/plugins").responseXML);
- help.addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
+ help.initialize();
},
stringifyXML: function (xml) {
@@ -1280,6 +1215,73 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
}, {
toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
}, {
+ cache: function () {
+ cache.register("help/plugins.xml", function () {
+ // Process plugin help entries.
+ XML.ignoreWhiteSpace = XML.prettyPrinting = false;
+
+ let body = XML();
+ for (let [, context] in Iterator(plugins.contexts))
+ try {
+ let info = contexts.getDocs(context);
+ if (info instanceof XML) {
+ if (info.*.@lang.length()) {
+ let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
+
+ info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
+
+ for each (let elem in info.NS::info)
+ for each (let attr in ["@name", "@summary", "@href"])
+ if (elem[attr].length())
+ info[attr] = elem[attr];
+ }
+ body += {info.@summary}
+
+ info;
+ }
+ }
+ catch (e) {
+ util.reportError(e);
+ }
+
+ return '\n' +
+ '\n' +
+ '\n' +
+
+ {_("help.title.Using Plugins")}
+
+
+ {body}
+ .toXMLString();
+ });
+
+ cache.register("help/index.xml", function () {
+ default xml namespace = NS;
+
+ return '\n' +
+ {
+ template.map(dactyl.indices, function ([name, iter])
+ {
+ template.map(iter(), util.identity)
+ }
, <>{"\n\n"}>)
+ };
+ });
+
+ cache.register("help/gui.xml", function () {
+ default xml namespace = NS;
+
+ return '\n' +
+
+ {
+ template.map(config.dialogs, function ([name, val])
+ (!val[2] || val[2]())
+ ? <>- {name}
- {val[0]}
>
+ : undefined,
+ <>{"\n"}>)
+ }
+ ;
+ });
+ },
events: function () {
events.listen(window, dactyl, "events", true);
},
diff --git a/common/content/modes.js b/common/content/modes.js
index ae26b090..7443873d 100644
--- a/common/content/modes.js
+++ b/common/content/modes.js
@@ -213,57 +213,18 @@ var Modes = Module("modes", {
}
});
-
- function makeTree() {
- let list = modes.all.filter(function (m) m.name !== m.description);
-
- let tree = {};
-
- for (let mode in values(list))
- tree[mode.name] = {};
-
- for (let mode in values(list))
- for (let base in values(mode.bases))
- tree[base.name][mode.name] = tree[mode.name];
-
- let roots = iter([m.name, tree[m.name]] for (m in values(list)) if (!m.bases.length)).toObject();
-
- default xml namespace = NS;
- function rec(obj) {
- XML.ignoreWhitespace = XML.prettyPrinting = false;
-
- let res = ;
- Object.keys(obj).sort().forEach(function (name) {
- let mode = modes.getMode(name);
- res.* += - {mode.displayName}: {mode.description}{
- rec(obj[name])
- }
;
- });
-
- if (res.*.length())
- return res;
- return <>>;
- }
-
- return rec(roots);
- }
-
- util.timeout(function () {
- // Waits for the add-on to become available, if necessary.
- config.addon;
- config.version;
-
- services["dactyl:"].pages["modes.dtd"] = services["dactyl:"].pages["modes.dtd"]();
- });
-
- services["dactyl:"].pages["modes.dtd"] = function () [null,
- util.makeDTD(iter({ "modes.tree": makeTree() },
- config.dtd))];
},
+
cleanup: function cleanup() {
modes.reset();
},
+ signals: {
+ "io.source": function ioSource(context, file, modTime) {
+ cache.flushEntry("modes.dtd", modTime);
+ }
+ },
+
_getModeMessage: function _getModeMessage() {
// when recording a macro
let macromode = "";
@@ -604,6 +565,45 @@ var Modes = Module("modes", {
}, desc));
}
}, {
+ cache: function initCache() {
+ function makeTree() {
+ let list = modes.all.filter(function (m) m.name !== m.description);
+
+ let tree = {};
+
+ for (let mode in values(list))
+ tree[mode.name] = {};
+
+ for (let mode in values(list))
+ for (let base in values(mode.bases))
+ tree[base.name][mode.name] = tree[mode.name];
+
+ let roots = iter([m.name, tree[m.name]] for (m in values(list)) if (!m.bases.length)).toObject();
+
+ default xml namespace = NS;
+ function rec(obj) {
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
+
+ let res = ;
+ Object.keys(obj).sort().forEach(function (name) {
+ let mode = modes.getMode(name);
+ res.* += - {mode.displayName}: {mode.description}{
+ rec(obj[name])
+ }
;
+ });
+
+ if (res.*.length())
+ return res;
+ return <>>;
+ }
+
+ return rec(roots);
+ }
+
+ cache.register("modes.dtd", function ()
+ util.makeDTD(iter({ "modes.tree": makeTree() },
+ config.dtd)));
+ },
mappings: function initMappings() {
mappings.add([modes.BASE, modes.NORMAL],
["", ""],
diff --git a/common/content/mow.js b/common/content/mow.js
index 90c3001d..9f17fa23 100644
--- a/common/content/mow.js
+++ b/common/content/mow.js
@@ -21,7 +21,7 @@ var MOW = Module("mow", {
if (modes.have(modes.OUTPUT_MULTILINE)) {
this.resize(true);
- if (options["more"] && this.isScrollable(1)) {
+ if (options["more"] && this.canScroll(1)) {
// start the last executed command's output at the top of the screen
let elements = this.document.getElementsByClassName("ex-command-output");
DOM(elements[elements.length - 1]).scrollIntoView(true);
@@ -219,7 +219,7 @@ var MOW = Module("mow", {
onKeyPress: function onKeyPress(eventList) {
const KILL = false, PASS = true;
- if (options["more"] && mow.isScrollable(1))
+ if (options["more"] && mow.canScroll(1))
this.updateMorePrompt(false, true);
else {
modes.pop();
@@ -279,7 +279,7 @@ var MOW = Module("mow", {
if (showHelp)
this.widgets.message = ["MoreMsg", _("mow.moreHelp")];
- else if (force || (options["more"] && Buffer.isScrollable(elem, 1)))
+ else if (force || (options["more"] && Buffer.canScroll(elem, 1)))
this.widgets.message = ["MoreMsg", _("mow.more")];
else
this.widgets.message = ["Question", _("mow.continue")];
@@ -341,36 +341,36 @@ var MOW = Module("mow", {
bind(["j", "", ""], "Scroll down one line",
function ({ count }) { mow.scrollVertical("lines", 1 * (count || 1)); },
- function () mow.isScrollable(1), BEEP);
+ function () mow.canScroll(1), BEEP);
bind(["k", "", ""], "Scroll up one line",
function ({ count }) { mow.scrollVertical("lines", -1 * (count || 1)); },
- function () mow.isScrollable(-1), BEEP);
+ function () mow.canScroll(-1), BEEP);
bind(["", "", ""], "Scroll down one line, exit on last line",
function ({ count }) { mow.scrollVertical("lines", 1 * (count || 1)); },
- function () mow.isScrollable(1), DROP);
+ function () mow.canScroll(1), DROP);
// half page down
bind([""], "Scroll down half a page",
function ({ count }) { mow.scrollVertical("pages", .5 * (count || 1)); },
- function () mow.isScrollable(1), BEEP);
+ function () mow.canScroll(1), BEEP);
bind(["", ""], "Scroll down one page",
function ({ count }) { mow.scrollVertical("pages", 1 * (count || 1)); },
- function () mow.isScrollable(1), BEEP);
+ function () mow.canScroll(1), BEEP);
bind([""], "Scroll down one page",
function ({ count }) { mow.scrollVertical("pages", 1 * (count || 1)); },
- function () mow.isScrollable(1), DROP);
+ function () mow.canScroll(1), DROP);
bind([""], "Scroll up half a page",
function ({ count }) { mow.scrollVertical("pages", -.5 * (count || 1)); },
- function () mow.isScrollable(-1), BEEP);
+ function () mow.canScroll(-1), BEEP);
bind(["", ""], "Scroll up half a page",
function ({ count }) { mow.scrollVertical("pages", -1 * (count || 1)); },
- function () mow.isScrollable(-1), BEEP);
+ function () mow.canScroll(-1), BEEP);
bind(["gg"], "Scroll to the beginning of output",
function () { mow.scrollToPercent(null, 0); });
diff --git a/common/locale/en-US/all.xml b/common/locale/en-US/all.xml
index 59c18308..7f6281f5 100644
--- a/common/locale/en-US/all.xml
+++ b/common/locale/en-US/all.xml
@@ -35,6 +35,7 @@
+
diff --git a/common/locale/en-US/map.xml b/common/locale/en-US/map.xml
index 3b0251d4..f7c7305a 100644
--- a/common/locale/en-US/map.xml
+++ b/common/locale/en-US/map.xml
@@ -1,7 +1,7 @@
-
+
-
+
+//
+// This work is licensed for reuse under an MIT license. Details are
+// given in the LICENSE.txt file included with this file.
+"use strict";
+
+Components.utils.import("resource://dactyl/bootstrap.jsm");
+defineModule("cache", {
+ exports: ["Cache", "cache"],
+ require: ["config", "services", "util"]
+}, this);
+
+var Cache = Module("Cache", XPCOM(Ci.nsIRequestObserver), {
+ init: function init() {
+ this.queue = [];
+ this.cache = {};
+ this.providers = {};
+ this.localProviders = {};
+
+ if (JSMLoader.cacheFlush)
+ this.flush();
+
+ update(services["dactyl:"].providers, {
+ "cache": function (uri, path) {
+ // TODO: Return zip reader stream
+
+ let result = cache.force(path);
+ if (isArray(result))
+ return result;
+
+ try {
+ return [services.mime.getTypeFromURI(uri), result];
+ }
+ catch (e) {
+ return ["text/plain", result];
+ }
+ }
+ });
+ },
+
+ Local: function Local(dactyl, modules, window) ({
+ init: function init() {
+ delete this.instance;
+ this.providers = {};
+ },
+
+ isLocal: true
+ }),
+
+ compression: 9,
+
+ cacheFile: Class.Memoize(function () {
+ let dir = File(services.directory.get("ProfD", Ci.nsIFile))
+ .child("dactyl");
+ if (!dir.exists())
+ dir.create(dir.DIRECTORY_TYPE, octal(777));
+ return dir.child("cache.zip");
+ }),
+
+ get cacheReader() {
+ if (!this._cacheReader && this.cacheFile.exists()
+ && !this.inQueue)
+ try {
+ this._cacheReader = services.ZipReader(this.cacheFile);
+ }
+ catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+ util.reportError(e);
+ this.cacheFile.remove(false);
+ }
+
+ return this._cacheReader;
+ },
+
+ get inQueue() this._cacheWriter && this._cacheWriter.inQueue(),
+
+ getCacheWriter: function () {
+ if (!this._cacheWriter)
+ try {
+ let mode = File.MODE_RDWR;
+ if (!this.cacheFile.exists())
+ mode |= File.MODE_CREATE;
+
+ cache._cacheWriter = services.ZipWriter(this.cacheFile, mode);
+ }
+ catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+ util.reportError(e);
+ this.cacheFile.remove(false);
+
+ mode |= File.MODE_CREATE;
+ cache._cacheWriter = services.ZipWriter(this.cacheFile, mode);
+ }
+ return this._cacheWriter;
+ },
+
+ closeReader: function closeReader() {
+ if (cache._cacheReader) {
+ this.cacheReader.close();
+ delete cache._cacheReader;
+ }
+ },
+
+ closeWriter: function closeWriter() {
+ this.closeReader();
+
+ if (this._cacheWriter) {
+ this._cacheWriter.close();
+ delete cache._cacheWriter;
+
+ // ZipWriter bug.
+ if (this.cacheFile.fileSize <= 22)
+ this.cacheFile.remove(false);
+ }
+ },
+
+ flush: function flush() {
+ cache.cache = {};
+ if (this.cacheFile.eists()) {
+ this.closeReader();
+
+ this.flushJAR(this.cacheFile);
+ this.cacheFile.remove(false);
+ }
+ },
+
+ flushAll: function flushAll(file) {
+ this.flushStartup();
+ this.flush();
+ },
+
+ flushEntry: function flushEntry(name, time) {
+ if (this.cacheReader && this.cacheReader.hasEntry(name)) {
+ if (time && this.cacheReader.getEntry(name).lastModifiedTime / 1000 >= time)
+ return;
+
+ this.queue.push([null, name]);
+ cache.processQueue();
+ }
+
+ delete this.cache[name];
+ },
+
+ flushJAR: function flushJAR(file) {
+ services.observer.notifyObservers(file, "flush-cache-entry", "");
+ },
+
+ flushStartup: function flushStartup() {
+ services.observer.notifyObservers(null, "startupcache-invalidate", "");
+ },
+
+ force: function force(name, localOnly) {
+ if (this.cacheReader && this.cacheReader.hasEntry(name)) {
+ return JSON.parse(File.readStream(
+ this.cacheReader.getInputStream(name)));
+ }
+
+ if (Set.has(this.localProviders, name) && !this.isLocal) {
+ for each (let { cache } in overlay.modules)
+ if (cache.has(name))
+ return cache.force(name, true);
+ }
+
+ if (Set.has(this.providers, name)) {
+ let [func, self] = this.providers[name];
+ this.cache[name] = func.call(self || this, name);
+
+ cache.queue.push([Date.now(), name]);
+ cache.processQueue();
+
+ return this.cache[name];
+ }
+
+ if (this.isLocal && !localOnly)
+ return cache.force(name);
+ },
+
+ get: function get(name) {
+ if (!Set.has(this.cache, name)) {
+ this.cache[name] = this.force(name);
+ util.assert(this.cache[name] !== undefined,
+ "No such cache key", false);
+ }
+
+ return this.cache[name];
+ },
+
+ has: function has(name) Set.has(this.providers, name) || set.has(this.cache, name),
+
+ register: function register(name, callback, self) {
+ if (this.isLocal)
+ Set.add(this.localProviders, name);
+
+ this.providers[name] = [callback, self];
+ },
+
+ processQueue: function processQueue() {
+ this.closeReader();
+ this.closeWriter();
+
+ if (this.queue.length && !this.inQueue) {
+ // removeEntry does not work properly with queues.
+ for each (let [, entry] in this.queue)
+ if (this.getCacheWriter().hasEntry(entry)) {
+ this.getCacheWriter().removeEntry(entry, false);
+ this.closeWriter();
+ }
+
+ this.queue.splice(0).forEach(function ([time, entry]) {
+ if (time && Set.has(this.cache, entry)) {
+ let stream = services.CharsetConv("UTF-8")
+ .convertToInputStream(JSON.stringify(this.cache[entry]));
+
+ this.getCacheWriter().addEntryStream(entry, time * 1000,
+ this.compression, stream,
+ true);
+ }
+ }, this);
+
+ if (this._cacheWriter)
+ this.getCacheWriter().processQueue(this, null);
+ }
+ },
+
+ onStopRequest: function onStopRequest() {
+ this.processQueue();
+ }
+});
+
+endModule();
+
+// catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
+
+// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
diff --git a/common/modules/config.jsm b/common/modules/config.jsm
index c13f910f..20c70987 100644
--- a/common/modules/config.jsm
+++ b/common/modules/config.jsm
@@ -14,6 +14,7 @@ defineModule("config", {
}, this);
this.lazyRequire("addons", ["AddonManager"]);
+this.lazyRequire("cache", ["cache"]);
this.lazyRequire("highlight", ["highlight"]);
this.lazyRequire("messages", ["_"]);
@@ -54,9 +55,11 @@ var ConfigBase = Class("ConfigBase", {
"resource://dactyl-content/")));
this.timeout(function () {
- services["dactyl:"].pages.dtd = function () [null, util.makeDTD(config.dtd)];
+ cache.register("config.dtd", function () util.makeDTD(config.dtd));
});
+ services["dactyl:"].pages["dtd"] = function () [null, cache.get("config.dtd")];
+
update(services["dactyl:"].providers, {
"locale": function (uri, path) LocaleChannel("dactyl-locale", config.locale, path, uri),
"locale-local": function (uri, path) LocaleChannel("dactyl-local-locale", config.locale, path, uri)
@@ -101,6 +104,7 @@ var ConfigBase = Class("ConfigBase", {
global: ["addons",
"base",
"io",
+ "cache",
"commands",
"completion",
"config",
diff --git a/common/modules/help.jsm b/common/modules/help.jsm
index c5e0b8f9..4841564a 100644
--- a/common/modules/help.jsm
+++ b/common/modules/help.jsm
@@ -7,21 +7,79 @@
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("help", {
exports: ["help"],
- require: ["dom", "javascript", "protocol", "services", "util"]
+ require: ["cache", "dom", "javascript", "protocol", "services", "util"]
}, this);
+var HelpBuilder = Class("HelpBuilder", {
+ init: function init() {
+ try {
+ help._data = this;
+
+ this.files = {};
+ this.tags = {};
+ this.overlays = {};
+
+ // Scrape the list of help files from all.xml
+ this.tags["all"] = this.tags["all.xml"] = "all";
+ let files = this.findHelpFile("all").map(function (doc)
+ [f.value for (f in DOM.XPath("//dactyl:include/@href", doc))]);
+
+ // Scrape the tags from the rest of the help files.
+ array.flatten(files).forEach(function (file) {
+ this.tags[file + ".xml"] = file;
+ this.findHelpFile(file).forEach(function (doc) {
+ this.addTags(file, doc);
+ }, this);
+ }, this);
+ }
+ finally {
+ delete help._data;
+ }
+ },
+
+ toJSON: function toJSON() ({
+ files: this.files,
+ overlays: this.overlays,
+ tags: this.tags
+ }),
+
+ // Find the tags in the document.
+ addTags: function addTags(file, doc) {
+ for (let elem in DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
+ for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
+ this.tags[tag] = file;
+ },
+
+ bases: ["dactyl://locale-local/", "dactyl://locale/", "dactyl://cache/help/"],
+
+ // Find help and overlay files with the given name.
+ findHelpFile: function findHelpFile(file) {
+ let result = [];
+ for (let base in values(this.bases)) {
+ let url = [base, file, ".xml"].join("");
+ let res = util.httpGet(url);
+ if (res) {
+ if (res.responseXML.documentElement.localName == "document")
+ this.files[file] = url;
+ if (res.responseXML.documentElement.localName == "overlay")
+ this.overlays[file] = url;
+ result.push(res.responseXML);
+ }
+ }
+ return result;
+ }
+});
+
var Help = Module("Help", {
init: function init() {
this.initialized = false;
- this.files = {};
- this.overlays = {};
- this.tags = {};
function Loop(fn)
function (uri, path) {
if (!help.initialized)
return RedirectChannel(uri.spec, uri, 2,
"Initializing. Please wait...");
+
return fn.apply(this, arguments);
}
@@ -36,8 +94,136 @@ var Help = Module("Help", {
return RedirectChannel("dactyl://help/" + help.tags[tag] + "#" + tag.replace(/#/g, encodeURIComponent), uri);
})
});
+
+ cache.register("help", HelpBuilder);
+
+ cache.register("help/versions.xml", function () {
+ let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
+ { mimeType: "text/plain;charset=UTF-8" })
+ .responseText;
+
+ let re = util.regexp(UTF8( \s* # .*\n)
+
+ | ^ (?P \s*)
+ (?P [-•*+]) \ //
+ (?P .*\n
+ (?: \2\ \ .*\n | \s*\n)* )
+
+ | (?P
+ (?: ^ [^\S\n]*
+ (?:[^-•*+\s] | [-•*+]\S)
+ .*\n
+ )+
+ )
+
+ | (?: ^ [^\S\n]* \n) +
+ ]]>), "gmxy");
+
+ let betas = util.regexp(/\[(b\d)\]/, "gx");
+
+ let beta = array(betas.iterate(NEWS))
+ .map(function (m) m[1]).uniq().slice(-1)[0];
+
+
+ default xml namespace = NS;
+ function rec(text, level, li) {
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
+
+ let res = <>>;
+ let list, space, i = 0;
+
+
+ for (let match in re.iterate(text)) {
+ if (match.comment)
+ continue;
+ else if (match.char) {
+ if (!list)
+ res += list = ;
+ let li = ;
+ li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
+ list.* += li;
+ }
+ else if (match.par) {
+ let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
+ let t = tags;
+ tags = array(betas.iterate(tags)).map(function (m) m[1]);
+
+ let group = !tags.length ? "" :
+ !tags.some(function (t) t == beta) ? "HelpNewsOld" : "HelpNewsNew";
+ if (i === 0 && li) {
+ li.@highlight = group;
+ group = "";
+ }
+
+ list = null;
+ if (level == 0 && /^.*:\n$/.test(match.par)) {
+ let text = par.slice(0, -1);
+ res += {template.linkifyHelp(text, true)}
;
+ }
+ else {
+ let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
+ res += {
+ !tags.length ? "" :
+ {tags.join(" ")}
+ }{
+ a ? {a} : ""
+ }{
+ template.linkifyHelp(b, true)
+ }
;
+ }
+ }
+ i++;
+ }
+ for each (let attr in res..@highlight) {
+ attr.parent().@NS::highlight = attr;
+ delete attr.parent().@highlight;
+ }
+ return res;
+ }
+
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
+ let body = rec(NEWS, 0);
+ for each (let li in body..li) {
+ let list = li..li.(@NS::highlight == "HelpNewsOld");
+ if (list.length() && list.length() == li..li.(@NS::highlight != "").length()) {
+ for each (let li in list)
+ li.@NS::highlight = "";
+ li.@NS::highlight = "HelpNewsOld";
+ }
+ }
+
+
+ return '\n' +
+ '\n' +
+ '\n' +
+
+ {config.appName} Versions
+
+
+ {body}
+ .toXMLString()
+ });
},
+ initialize: function initialize() {
+ help.initialized = true;
+ },
+
+ flush: function flush(entries, time) {
+ cache.flushEntry("help", time);
+
+ for (let entry in values(Array.concat(entries || [])))
+ cache.flushEntry(entry, time);
+ },
+
+ get data() this._data || cache.get("help"),
+
+ get files() this.data.files,
+ get overlays() this.data.overlays,
+ get tags() this.data.tags,
+
Local: function Local(dactyl, modules, window) ({
init: function init() {
dactyl.commands["dactyl.help"] = function (event) {
@@ -81,6 +267,7 @@ var Help = Module("Help", {
*/
help: function (topic, consolidated) {
dactyl.initHelp();
+
if (!topic) {
let helpFile = consolidated ? "all" : modules.options["helpfile"];
@@ -210,176 +397,7 @@ var Help = Module("Help", {
zip.close();
}, [function (context, args) completion.file(context)]),
- }),
-
- // Find the tags in the document.
- addTags: function addTags(file, doc) {
- for (let elem in DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
- for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
- this.tags[tag] = file;
- },
-
- namespaces: ["locale-local", "locale"],
-
- // Find help and overlay files with the given name.
- findHelpFile: function findHelpFile(file) {
- let result = [];
- for (let namespace in values(this.namespaces)) {
- let url = ["dactyl://", namespace, "/", file, ".xml"].join("");
- let res = util.httpGet(url);
- if (res) {
- if (res.responseXML.documentElement.localName == "document")
- this.files[file] = url;
- if (res.responseXML.documentElement.localName == "overlay")
- this.overlays[file] = url;
- result.push(res.responseXML);
- }
- }
- return result;
- },
-
- initialize: function initialize(force) {
- // Waits for the add-on to become available, if necessary.
- config.addon;
- config.version;
-
- if (force || !this.initialized) {
-
- this.files["versions"] = function () {
- let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
- { mimeType: "text/plain;charset=UTF-8" })
- .responseText;
-
- let re = util.regexp(UTF8( \s* # .*\n)
-
- | ^ (?P \s*)
- (?P [-•*+]) \ //
- (?P .*\n
- (?: \2\ \ .*\n | \s*\n)* )
-
- | (?P
- (?: ^ [^\S\n]*
- (?:[^-•*+\s] | [-•*+]\S)
- .*\n
- )+
- )
-
- | (?: ^ [^\S\n]* \n) +
- ]]>), "gmxy");
-
- let betas = util.regexp(/\[(b\d)\]/, "gx");
-
- let beta = array(betas.iterate(NEWS))
- .map(function (m) m[1]).uniq().slice(-1)[0];
-
-
- default xml namespace = NS;
- function rec(text, level, li) {
- XML.ignoreWhitespace = XML.prettyPrinting = false;
-
- let res = <>>;
- let list, space, i = 0;
-
-
- for (let match in re.iterate(text)) {
- if (match.comment)
- continue;
- else if (match.char) {
- if (!list)
- res += list = ;
- let li = ;
- li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
- list.* += li;
- }
- else if (match.par) {
- let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
- let t = tags;
- tags = array(betas.iterate(tags)).map(function (m) m[1]);
-
- let group = !tags.length ? "" :
- !tags.some(function (t) t == beta) ? "HelpNewsOld" : "HelpNewsNew";
- if (i === 0 && li) {
- li.@highlight = group;
- group = "";
- }
-
- list = null;
- if (level == 0 && /^.*:\n$/.test(match.par)) {
- let text = par.slice(0, -1);
- res += {template.linkifyHelp(text, true)}
;
- }
- else {
- let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
- res += {
- !tags.length ? "" :
- {tags.join(" ")}
- }{
- a ? {a} : ""
- }{
- template.linkifyHelp(b, true)
- }
;
- }
- }
- i++;
- }
- for each (let attr in res..@highlight) {
- attr.parent().@NS::highlight = attr;
- delete attr.parent().@highlight;
- }
- return res;
- }
-
- XML.ignoreWhitespace = XML.prettyPrinting = false;
- let body = rec(NEWS, 0);
- for each (let li in body..li) {
- let list = li..li.(@NS::highlight == "HelpNewsOld");
- if (list.length() && list.length() == li..li.(@NS::highlight != "").length()) {
- for each (let li in list)
- li.@NS::highlight = "";
- li.@NS::highlight = "HelpNewsOld";
- }
- }
-
-
- return ["application/xml",
- '\n' +
- '\n' +
- '\n' +
-
- {config.appName} Versions
-
-
- {body}
- .toXMLString()
- ];
- }
-
-
-
- // Scrape the list of help files from all.xml
- // Manually process main and overlay files, since XSLTProcessor and
- // XMLHttpRequest don't allow access to chrome documents.
- this.tags["all"] = this.tags["all.xml"] = "all";
- let files = this.findHelpFile("all").map(function (doc)
- [f.value for (f in DOM.XPath("//dactyl:include/@href", doc))]);
-
- // Scrape the tags from the rest of the help files.
- array.flatten(files).forEach(function (file) {
- this.tags[file + ".xml"] = file;
- this.findHelpFile(file).forEach(function (doc) {
- this.addTags(file, doc);
- }, this);
- }, this);
-
- this.tags["versions"] = this.tags["versions.xml"] = "versions";
-
- this.addTags("versions", util.httpGet("dactyl://help/versions").responseXML);
-
- help.initialized = true;
- }
- },
+ })
}, {
}, {
commands: function init_commands(dactyl, modules, window) {
diff --git a/common/modules/io.jsm b/common/modules/io.jsm
index 32e25843..0587ea40 100644
--- a/common/modules/io.jsm
+++ b/common/modules/io.jsm
@@ -175,7 +175,7 @@ var IO = Module("io", {
util.flushCache();
dactyl.loadScript(uri.spec, context);
- help.initialized = false;
+ dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
}
catch (e) {
if (e.fileName && !(e instanceof FailedAssertion))
@@ -200,6 +200,7 @@ var IO = Module("io", {
group: context.GROUP,
line: 1
});
+ dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
}
Set.add(this._scriptNames, file.path);
diff --git a/common/modules/options.jsm b/common/modules/options.jsm
index f3fefc98..98ad8a12 100644
--- a/common/modules/options.jsm
+++ b/common/modules/options.jsm
@@ -794,7 +794,7 @@ var Options = Module("options", {
opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true);
}, window);
- services["dactyl:"].pages["options.dtd"] = function () [null,
+ modules.cache.register("options.dtd", function ()
util.makeDTD(
iter(([["option", o.name, "default"].join("."),
o.type === "string" ? o.defaultValue.replace(/'/g, "''") :
@@ -804,7 +804,13 @@ var Options = Module("options", {
([["option", o.name, "type"].join("."), o.type] for (o in self)),
- config.dtd))];
+ config.dtd)));
+ },
+
+ signals: {
+ "io.source": function ioSource(context, file, modTime) {
+ cache.flushEntry("options.dtd", modTime);
+ }
},
dactyl: dactyl,
diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm
index e4e23911..14d29fea 100644
--- a/common/modules/overlay.jsm
+++ b/common/modules/overlay.jsm
@@ -371,6 +371,9 @@ var Overlay = Module("Overlay", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReferen
};
},
+ get activeModules() this.activeWindow && this.activeWindow.dactyl.modules,
+
+ get modules() this.windows.map(function (w) w.dactyl.modules),
/**
* The most recently active dactyl window.
diff --git a/common/modules/services.jsm b/common/modules/services.jsm
index 0364f492..30e7482a 100644
--- a/common/modules/services.jsm
+++ b/common/modules/services.jsm
@@ -106,8 +106,8 @@ var Services = Module("Services", {
this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest", "open");
this.addClass("XPathEvaluator", "@mozilla.org/dom/xpath-evaluator;1", "nsIDOMXPathEvaluator");
this.addClass("XMLDocument", "@mozilla.org/xml/xml-document;1", ["nsIDOMXMLDocument", "nsIDOMNodeSelector"]);
- this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open");
- this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", "nsIZipWriter", "open");
+ this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open", false);
+ this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", "nsIZipWriter", "open", false);
},
reinit: function () {},