1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
10 Components.utils.import("resource://dactyl/bootstrap.jsm");
11 defineModule("config", {
12 exports: ["ConfigBase", "Config", "config"],
13 require: ["dom", "io", "protocol", "services", "util", "template"]
16 this.lazyRequire("addons", ["AddonManager"]);
17 this.lazyRequire("cache", ["cache"]);
18 this.lazyRequire("highlight", ["highlight"]);
19 this.lazyRequire("messages", ["_"]);
20 this.lazyRequire("prefs", ["localPrefs", "prefs"]);
21 this.lazyRequire("storage", ["storage", "File"]);
23 function AboutHandler() {}
24 AboutHandler.prototype = {
25 get classDescription() "About " + config.appName + " Page",
27 classID: Components.ID("81495d80-89ee-4c36-a88d-ea7c4e5ac63f"),
29 get contractID() services.ABOUT + config.name,
31 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
33 newChannel: function (uri) {
34 let channel = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
35 .newChannel("dactyl://content/about.xul", null, null);
36 channel.originalURI = uri;
40 getURIFlags: function (uri) Ci.nsIAboutModule.ALLOW_SCRIPT,
42 var ConfigBase = Class("ConfigBase", {
44 * Called on dactyl startup to allow for any arbitrary application-specific
45 * initialization code. Must call superclass's init function.
47 init: function init() {
50 this.features.push = deprecated("Set.add", function push(feature) Set.add(this, feature));
51 if (this.haveGecko("2b"))
52 Set.add(this.features, "Gecko2");
54 JSMLoader.registerFactory(JSMLoader.Factory(AboutHandler));
55 JSMLoader.registerFactory(JSMLoader.Factory(
56 Protocol("dactyl", "{9c8f2530-51c8-4d41-b356-319e0b155c44}",
57 "resource://dactyl-content/")));
59 this.timeout(function () {
60 cache.register("config.dtd", function () util.makeDTD(config.dtd));
63 services["dactyl:"].pages["dtd"] = function () [null, cache.get("config.dtd")];
65 update(services["dactyl:"].providers, {
66 "locale": function (uri, path) LocaleChannel("dactyl-locale", config.locale, path, uri),
67 "locale-local": function (uri, path) LocaleChannel("dactyl-local-locale", config.locale, path, uri)
71 get prefs() localPrefs,
73 get has() Set.has(this.features),
76 "resource://dactyl-common/config.json",
77 "resource://dactyl-local/config.json"
80 configs: Class.Memoize(function () this.configFiles.map(function (url) JSON.parse(File.readURL(url)))),
82 loadConfig: function loadConfig(documentURL) {
84 for each (let config in this.configs) {
86 config = config.overlays && config.overlays[documentURL] || {};
88 for (let [name, value] in Iterator(config)) {
89 let prop = util.camelCase(name);
91 if (isArray(this[prop]))
92 this[prop] = [].concat(this[prop], value);
93 else if (isObject(this[prop])) {
97 this[prop] = update({}, this[prop],
98 iter([util.camelCase(k), value[k]]
99 for (k in value)).toObject());
151 loadStyles: function loadStyles(force) {
152 highlight.styleableChrome = this.styleableChrome;
154 highlight.loadCSS(this.CSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
155 highlight.loadCSS(this.helpCSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
157 if (!this.haveGecko("2b"))
158 highlight.loadCSS(<![CDATA[
159 !TabNumber font-weight: bold; margin: 0px; padding-right: .8ex;
164 text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
168 let hl = highlight.set("Find", "");
169 hl.onChange = function () {
170 function hex(val) ("#" + util.regexp.iterate(/\d+/g, val)
171 .map(function (num) ("0" + Number(num).toString(16)).slice(-2))
175 let elem = services.appShell.hiddenDOMWindow.document.createElement("div");
176 elem.style.cssText = this.cssText;
178 let keys = iter(Styles.propertyIter(this.cssText)).map(function (p) p.name).toArray();
179 let bg = keys.some(bind("test", /^background/));
180 let fg = keys.indexOf("color") >= 0;
182 let style = DOM(elem).style;
183 prefs[bg ? "safeSet" : "safeReset"]("ui.textHighlightBackground", hex(style.backgroundColor));
184 prefs[fg ? "safeSet" : "safeReset"]("ui.textHighlightForeground", hex(style.color));
188 get addonID() this.name + "@dactyl.googlecode.com",
190 addon: Class.Memoize(function () {
191 return (JSMLoader.bootstrap || {}).addon ||
192 AddonManager.getAddonByID(this.addonID);
195 get styleableChrome() Object.keys(this.overlays),
198 * The current application locale.
200 appLocale: Class.Memoize(function () services.chromeRegistry.getSelectedLocale("global")),
203 * The current dactyl locale.
205 locale: Class.Memoize(function () this.bestLocale(this.locales)),
208 * The current application locale.
210 locales: Class.Memoize(function () {
211 // TODO: Merge with completion.file code.
212 function getDir(str) str.match(/^(?:.*[\/\\])?/)[0];
214 let uri = "resource://dactyl-locale/";
215 let jar = io.isJarURL(uri);
217 let prefix = getDir(jar.JAREntry);
218 var res = iter(s.slice(prefix.length).replace(/\/.*/, "") for (s in io.listJar(jar.JARFile, prefix)))
222 res = array(f.leafName
223 // Fails on FF3: for (f in util.getFile(uri).iterDirectory())
224 for (f in values(util.getFile(uri).readDirectory()))
225 if (f.isDirectory())).array;
228 function exists(pkg) services["resource:"].hasSubstitution("dactyl-locale-" + pkg);
230 return array.uniq([this.appLocale, this.appLocale.replace(/-.*/, "")]
236 * Returns the best locale match to the current locale from a list
237 * of available locales.
239 * @param {[string]} list A list of available locales
242 bestLocale: function (list) {
243 return values([this.appLocale, this.appLocale.replace(/-.*/, ""),
244 "en", "en-US", list[0]])
245 .nth(Set.has(Set(list)), 0);
249 * A list of all known registered chrome and resource packages.
251 get chromePackages() {
254 function process(manifest) {
255 for each (let line in manifest.split(/\n+/)) {
256 let match = /^\s*(content|skin|locale|resource)\s+([^\s#]+)\s/.exec(line);
258 res[match[2]] = true;
261 function processJar(file) {
262 let jar = services.ZipReader(file);
265 if (jar.hasEntry("chrome.manifest"))
266 process(File.readStream(jar.getInputStream("chrome.manifest")));
273 for each (let dir in ["UChrm", "AChrom"]) {
274 dir = File(services.directory.get(dir, Ci.nsIFile));
275 if (dir.exists() && dir.isDirectory())
276 for (let file in dir.iterDirectory())
277 if (/\.manifest$/.test(file.leafName))
278 process(file.read());
280 dir = File(dir.parent);
281 if (dir.exists() && dir.isDirectory())
282 for (let file in dir.iterDirectory())
283 if (/\.jar$/.test(file.leafName))
286 dir = dir.child("extensions");
287 if (dir.exists() && dir.isDirectory())
288 for (let ext in dir.iterDirectory()) {
289 if (/\.xpi$/.test(ext.leafName))
293 ext = File(ext.read().replace(/\n*$/, ""));
294 let mf = ext.child("chrome.manifest");
300 return Object.keys(res).sort();
304 * Returns true if the current Gecko runtime is of the given version
307 * @param {string} min The minimum required version. @optional
308 * @param {string} max The maximum required version. @optional
311 haveGecko: function (min, max) let ({ compare } = services.versionCompare,
312 { platformVersion } = services.runtime)
313 (min == null || compare(platformVersion, min) >= 0) &&
314 (max == null || compare(platformVersion, max) < 0),
316 /** Dactyl's notion of the current operating system platform. */
318 _arch: services.runtime.OS,
320 * @property {string} The normalised name of the OS. This is one of
321 * "Windows", "Mac OS X" or "Unix".
323 get name() this.isWindows ? "Windows" : this.isMacOSX ? "Mac OS X" : "Unix",
324 /** @property {boolean} True if the OS is Windows. */
325 get isWindows() this._arch == "WINNT",
326 /** @property {boolean} True if the OS is Mac OS X. */
327 get isMacOSX() this._arch == "Darwin",
328 /** @property {boolean} True if the OS is some other *nix variant. */
329 get isUnix() !this.isWindows,
330 /** @property {RegExp} A RegExp which matches illegal characters in path components. */
331 get illegalCharacters() this.isWindows ? /[<>:"/\\|?*\x00-\x1f]/g : /[\/\x00]/g,
333 get pathListSep() this.isWindows ? ";" : ":"
337 * @property {string} The pathname of the VCS repository clone's root
338 * directory if the application is running from one via an extension
341 VCSPath: Class.Memoize(function () {
342 if (/pre$/.test(this.addon.version)) {
343 let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg");
344 if (uri instanceof Ci.nsIFileURL &&
347 return uri.file.parent.path;
353 * @property {string} The name of the VCS branch that the application is
354 * running from if using an extension proxy file or was built from if
355 * installed as an XPI.
357 branch: Class.Memoize(function () {
359 return io.system(["hg", "-R", this.VCSPath, "branch"]).output;
360 return (/pre-hg\d+-(\S*)/.exec(this.version) || [])[1];
363 /** @property {string} The name of the current user profile. */
364 profileName: Class.Memoize(function () {
365 // NOTE: services.profile.selectedProfile.name doesn't return
366 // what you might expect. It returns the last _actively_ selected
367 // profile (i.e. via the Profile Manager or -P option) rather than the
368 // current profile. These will differ if the current process was run
369 // without explicitly selecting a profile.
371 let dir = services.directory.get("ProfD", Ci.nsIFile);
372 for (let prof in iter(services.profile.profiles))
373 if (prof.QueryInterface(Ci.nsIToolkitProfile).rootDir.path === dir.path)
378 /** @property {string} The Dactyl version string. */
379 version: Class.Memoize(function () {
381 return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
382 "--template=hg{rev}-{branch}"]).output;
384 return this.addon.version;
387 buildDate: Class.Memoize(function () {
389 return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
390 "--template={date|isodate}"]).output;
391 if ("@DATE@" !== "@" + "DATE@")
392 return _("dactyl.created", "@DATE@");
395 get fileExt() this.name.slice(0, -6),
397 dtd: Class.Memoize(function ()
399 (["dactyl." + k, v] for ([k, v] in iter(config.dtdDactyl))),
400 (["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
404 get name() config.name,
405 get home() "http://dactyl.sourceforge.net/",
406 get apphome() this.home + this.name,
407 code: "http://code.google.com/p/dactyl/",
408 get issues() this.home + "bug/" + this.name,
409 get plugins() "http://dactyl.sf.net/" + this.name + "/plugins",
410 get faq() this.home + this.name + "/faq",
412 "list.mailto": Class.Memoize(function () config.name + "@googlegroups.com"),
413 "list.href": Class.Memoize(function () "http://groups.google.com/group/" + config.name),
415 "hg.latest": Class.Memoize(function () this.code + "source/browse/"), // XXX
416 "irc": "irc://irc.oftc.net/#pentadactyl",
420 "xmlns.dactyl": "http://vimperator.org/namespaces/liberator",
421 "xmlns.html": "http://www.w3.org/1999/xhtml",
422 "xmlns.xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
424 "tag.command-line": <link topic="command-line">command line</link>,
425 "tag.status-line": <link topic="status-line">status line</link>,
426 "mode.command-line": <link topic="command-line-mode">Command Line</link>,
439 helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Dense|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/,
440 styleHelp: function styleHelp() {
441 if (!this.helpStyled) {
442 for (let k in keys(highlight.loaded))
443 if (this.helpStyles.test(k))
444 highlight.loaded[k] = true;
449 Local: function Local(dactyl, modules, { document, window }) ({
450 init: function init() {
451 this.loadConfig(document.documentURI);
453 let append = <e4x xmlns={XUL} xmlns:dactyl={NS}>
454 <menupopup id="viewSidebarMenu"/>
455 <broadcasterset id="mainBroadcasterSet"/>
457 for each (let [id, [name, key, uri]] in Iterator(this.sidebars)) {
458 append.XUL::menupopup[0].* +=
459 <menuitem observes={"pentadactyl-" + id + "Sidebar"} label={name} accesskey={key} xmlns={XUL}/>;
460 append.XUL::broadcasterset[0].* +=
461 <broadcaster id={"pentadactyl-" + id + "Sidebar"}
462 autoCheck="false" type="checkbox" group="sidebar"
463 sidebartitle={name} sidebarurl={uri}
464 oncommand="toggleSidebar(this.id || this.observes);" xmlns={XUL}/>;
467 util.overlayWindow(window, { append: append.elements() });
472 get document() document,
475 get commandContainer() document.documentElement.id
478 browser: Class.Memoize(function () window.gBrowser),
479 tabbrowser: Class.Memoize(function () window.gBrowser),
481 get browserModes() [modules.modes.NORMAL],
484 * @property {string} The ID of the application's main XUL window.
486 mainWindowId: document.documentElement.id,
489 * @property {number} The height (px) that is available to the output
492 get outputHeight() this.browser.mPanelContainer.boxObject.height,
494 tabStrip: Class.Memoize(function () document.getElementById("TabsToolbar") || this.tabbrowser.mTabContainer),
498 * @property {Object} A mapping of names and descriptions
499 * of the autocommands available in this application. Primarily used
500 * for completion results.
505 * @property {Object} A map of :command-complete option values to completer
511 * @property {Object} Application specific defaults for option values. The
512 * property names must be the options' canonical names, and the values
513 * must be strings as entered via :set.
520 * @property {Object} A map of dialogs available via the
521 * :dialog command. Property names map dialog names to an array
522 * with the following elements:
523 * [0] description - A description of the dialog, used in
524 * command completion results for :dialog.
525 * [1] action - The function executed by :dialog.
526 * [2] test - Function which returns true if the dialog is available in
527 * the current window. @optional
532 * @property {set} A list of features available in this
533 * application. Used extensively in feature test macros. Use
534 * dactyl.has(feature) to check for a feature's presence
540 * @property {string} The file extension used for command script files.
541 * This is the name string sans "dactyl".
543 get fileExtension() this.name.slice(0, -6),
548 * @property {string} The name of the application that hosts the
549 * extension. E.g., "Firefox" or "XULRunner".
554 * @property {string} The name of the extension.
560 * @property {[string]} A list of extra scripts in the dactyl or
561 * application namespaces which should be loaded before dactyl
569 * @property {string} The leaf name of any temp files created by
570 * {@link io.createTempFile}.
572 get tempFile() this.name + ".txt",
576 * @property {string} The default highlighting rules.
577 * See {@link Highlights#loadCSS} for details.
579 CSS: Class.Memoize(function () File.readURL("resource://dactyl-skin/global-styles.css")),
581 helpCSS: Class.Memoize(function () File.readURL("resource://dactyl-skin/help-styles.css"))
584 JSMLoader.loadSubScript("resource://dactyl-local-content/config.js", this);
586 config.INIT = update(Object.create(config.INIT), config.INIT, {
587 init: function init(dactyl, modules, window) {
588 init.superapply(this, arguments);
590 let img = window.Image();
591 img.src = this.logo || "resource://dactyl-local-content/logo.png";
592 img.onload = util.wrapCallback(function () {
593 highlight.loadCSS(<>{"!Logo {"}
594 display: inline-block;
595 background: url({img.src});
596 width: {img.width}px;
597 height: {img.height}px;
603 load: function load(dactyl, modules, window) {
604 load.superapply(this, arguments);
606 this.timeout(function () {
607 if (this.branch && this.branch !== "default" &&
608 modules.yes_i_know_i_should_not_report_errors_in_these_branches_thanks.indexOf(this.branch) === -1)
609 dactyl.warn(_("warn.notDefaultBranch", config.appName, this.branch));
616 // catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
618 // vim: set fdm=marker sw=4 sts=4 et ft=javascript: