1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 23:07:59 +01:00
Files
pentadactyl-pm/common/modules/config.jsm
2011-10-05 05:00:31 -04:00

619 lines
22 KiB
JavaScript

// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-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";
let global = this;
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("config", {
exports: ["ConfigBase", "Config", "config"],
require: ["dom", "io", "protocol", "services", "util", "template"]
}, this);
this.lazyRequire("addons", ["AddonManager"]);
this.lazyRequire("cache", ["cache"]);
this.lazyRequire("highlight", ["highlight"]);
this.lazyRequire("messages", ["_"]);
this.lazyRequire("prefs", ["localPrefs", "prefs"]);
this.lazyRequire("storage", ["storage", "File"]);
function AboutHandler() {}
AboutHandler.prototype = {
get classDescription() "About " + config.appName + " Page",
classID: Components.ID("81495d80-89ee-4c36-a88d-ea7c4e5ac63f"),
get contractID() services.ABOUT + config.name,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
newChannel: function (uri) {
let channel = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
.newChannel("dactyl://content/about.xul", null, null);
channel.originalURI = uri;
return channel;
},
getURIFlags: function (uri) Ci.nsIAboutModule.ALLOW_SCRIPT,
};
var ConfigBase = Class("ConfigBase", {
/**
* Called on dactyl startup to allow for any arbitrary application-specific
* initialization code. Must call superclass's init function.
*/
init: function init() {
this.loadConfig();
this.features.push = deprecated("Set.add", function push(feature) Set.add(this, feature));
if (this.haveGecko("2b"))
Set.add(this.features, "Gecko2");
JSMLoader.registerFactory(JSMLoader.Factory(AboutHandler));
JSMLoader.registerFactory(JSMLoader.Factory(
Protocol("dactyl", "{9c8f2530-51c8-4d41-b356-319e0b155c44}",
"resource://dactyl-content/")));
this.timeout(function () {
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)
});
},
get prefs() localPrefs,
get has() Set.has(this.features),
configFiles: [
"resource://dactyl-common/config.json",
"resource://dactyl-local/config.json"
],
configs: Class.Memoize(function () this.configFiles.map(function (url) JSON.parse(File.readURL(url)))),
loadConfig: function loadConfig(documentURL) {
for each (let config in this.configs) {
if (documentURL)
config = config.overlays && config.overlays[documentURL] || {};
for (let [name, value] in Iterator(config)) {
let prop = util.camelCase(name);
if (isArray(this[prop]))
this[prop] = [].concat(this[prop], value);
else if (isObject(this[prop])) {
if (isArray(value))
value = Set(value);
this[prop] = update({}, this[prop],
iter([util.camelCase(k), value[k]]
for (k in value)).toObject());
}
else
this[prop] = value;
}
}
},
modules: {
global: ["addons",
"base",
"io",
"buffer",
"cache",
"commands",
"completion",
"config",
"contexts",
"dom",
"downloads",
"finder",
"help",
"highlight",
"javascript",
"main",
"messages",
"options",
"overlay",
"prefs",
"protocol",
"sanitizer",
"services",
"storage",
"styles",
"template",
"util"],
window: ["dactyl",
"modes",
"commandline",
"abbreviations",
"autocommands",
"editor",
"events",
"hints",
"key-processors",
"mappings",
"marks",
"mow",
"statusline"]
},
loadStyles: function loadStyles(force) {
highlight.styleableChrome = this.styleableChrome;
highlight.loadCSS(this.CSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
highlight.loadCSS(this.helpCSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
if (!this.haveGecko("2b"))
highlight.loadCSS(<![CDATA[
!TabNumber font-weight: bold; margin: 0px; padding-right: .8ex;
!TabIconNumber {
font-weight: bold;
color: white;
text-align: center;
text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
}
]]>);
let hl = highlight.set("Find", "");
hl.onChange = function () {
function hex(val) ("#" + util.regexp.iterate(/\d+/g, val)
.map(function (num) ("0" + Number(num).toString(16)).slice(-2))
.join("")
).slice(0, 7);
let elem = services.appShell.hiddenDOMWindow.document.createElement("div");
elem.style.cssText = this.cssText;
let keys = iter(Styles.propertyIter(this.cssText)).map(function (p) p.name).toArray();
let bg = keys.some(bind("test", /^background/));
let fg = keys.indexOf("color") >= 0;
let style = DOM(elem).style;
prefs[bg ? "safeSet" : "safeReset"]("ui.textHighlightBackground", hex(style.backgroundColor));
prefs[fg ? "safeSet" : "safeReset"]("ui.textHighlightForeground", hex(style.color));
};
},
get addonID() this.name + "@dactyl.googlecode.com",
addon: Class.Memoize(function () {
return (JSMLoader.bootstrap || {}).addon ||
AddonManager.getAddonByID(this.addonID);
}),
get styleableChrome() Object.keys(this.overlays),
/**
* The current application locale.
*/
appLocale: Class.Memoize(function () services.chromeRegistry.getSelectedLocale("global")),
/**
* The current dactyl locale.
*/
locale: Class.Memoize(function () this.bestLocale(this.locales)),
/**
* The current application locale.
*/
locales: Class.Memoize(function () {
// TODO: Merge with completion.file code.
function getDir(str) str.match(/^(?:.*[\/\\])?/)[0];
let uri = "resource://dactyl-locale/";
let jar = io.isJarURL(uri);
if (jar) {
let prefix = getDir(jar.JAREntry);
var res = iter(s.slice(prefix.length).replace(/\/.*/, "") for (s in io.listJar(jar.JARFile, prefix)))
.toArray();
}
else {
res = array(f.leafName
// Fails on FF3: for (f in util.getFile(uri).iterDirectory())
for (f in values(util.getFile(uri).readDirectory()))
if (f.isDirectory())).array;
}
function exists(pkg) services["resource:"].hasSubstitution("dactyl-locale-" + pkg);
return array.uniq([this.appLocale, this.appLocale.replace(/-.*/, "")]
.filter(exists)
.concat(res));
}),
/**
* Returns the best locale match to the current locale from a list
* of available locales.
*
* @param {[string]} list A list of available locales
* @returns {string}
*/
bestLocale: function (list) {
return values([this.appLocale, this.appLocale.replace(/-.*/, ""),
"en", "en-US", list[0]])
.nth(Set.has(Set(list)), 0);
},
/**
* A list of all known registered chrome and resource packages.
*/
get chromePackages() {
// Horrible hack.
let res = {};
function process(manifest) {
for each (let line in manifest.split(/\n+/)) {
let match = /^\s*(content|skin|locale|resource)\s+([^\s#]+)\s/.exec(line);
if (match)
res[match[2]] = true;
}
}
function processJar(file) {
let jar = services.ZipReader(file);
if (jar)
try {
if (jar.hasEntry("chrome.manifest"))
process(File.readStream(jar.getInputStream("chrome.manifest")));
}
finally {
jar.close();
}
}
for each (let dir in ["UChrm", "AChrom"]) {
dir = File(services.directory.get(dir, Ci.nsIFile));
if (dir.exists() && dir.isDirectory())
for (let file in dir.iterDirectory())
if (/\.manifest$/.test(file.leafName))
process(file.read());
dir = File(dir.parent);
if (dir.exists() && dir.isDirectory())
for (let file in dir.iterDirectory())
if (/\.jar$/.test(file.leafName))
processJar(file);
dir = dir.child("extensions");
if (dir.exists() && dir.isDirectory())
for (let ext in dir.iterDirectory()) {
if (/\.xpi$/.test(ext.leafName))
processJar(ext);
else {
if (ext.isFile())
ext = File(ext.read().replace(/\n*$/, ""));
let mf = ext.child("chrome.manifest");
if (mf.exists())
process(mf.read());
}
}
}
return Object.keys(res).sort();
},
/**
* Returns true if the current Gecko runtime is of the given version
* or greater.
*
* @param {string} min The minimum required version. @optional
* @param {string} max The maximum required version. @optional
* @returns {boolean}
*/
haveGecko: function (min, max) let ({ compare } = services.versionCompare,
{ platformVersion } = services.runtime)
(min == null || compare(platformVersion, min) >= 0) &&
(max == null || compare(platformVersion, max) < 0),
/** Dactyl's notion of the current operating system platform. */
OS: memoize({
_arch: services.runtime.OS,
/**
* @property {string} The normalised name of the OS. This is one of
* "Windows", "Mac OS X" or "Unix".
*/
get name() this.isWindows ? "Windows" : this.isMacOSX ? "Mac OS X" : "Unix",
/** @property {boolean} True if the OS is Windows. */
get isWindows() this._arch == "WINNT",
/** @property {boolean} True if the OS is Mac OS X. */
get isMacOSX() this._arch == "Darwin",
/** @property {boolean} True if the OS is some other *nix variant. */
get isUnix() !this.isWindows,
/** @property {RegExp} A RegExp which matches illegal characters in path components. */
get illegalCharacters() this.isWindows ? /[<>:"/\\|?*\x00-\x1f]/g : /[\/\x00]/g,
get pathListSep() this.isWindows ? ";" : ":"
}),
/**
* @property {string} The pathname of the VCS repository clone's root
* directory if the application is running from one via an extension
* proxy file.
*/
VCSPath: Class.Memoize(function () {
if (/pre$/.test(this.addon.version)) {
let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg");
if (uri instanceof Ci.nsIFileURL &&
uri.file.exists() &&
io.pathSearch("hg"))
return uri.file.parent.path;
}
return null;
}),
/**
* @property {string} The name of the VCS branch that the application is
* running from if using an extension proxy file or was built from if
* installed as an XPI.
*/
branch: Class.Memoize(function () {
if (this.VCSPath)
return io.system(["hg", "-R", this.VCSPath, "branch"]).output;
return (/pre-hg\d+-(\S*)/.exec(this.version) || [])[1];
}),
/** @property {string} The name of the current user profile. */
profileName: Class.Memoize(function () {
// NOTE: services.profile.selectedProfile.name doesn't return
// what you might expect. It returns the last _actively_ selected
// profile (i.e. via the Profile Manager or -P option) rather than the
// current profile. These will differ if the current process was run
// without explicitly selecting a profile.
let dir = services.directory.get("ProfD", Ci.nsIFile);
for (let prof in iter(services.profile.profiles))
if (prof.QueryInterface(Ci.nsIToolkitProfile).rootDir.path === dir.path)
return prof.name;
return "unknown";
}),
/** @property {string} The Dactyl version string. */
version: Class.Memoize(function () {
if (this.VCSPath)
return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
"--template=hg{rev}-{branch}"]).output;
return this.addon.version;
}),
buildDate: Class.Memoize(function () {
if (this.VCSPath)
return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
"--template={date|isodate}"]).output;
if ("@DATE@" !== "@" + "DATE@")
return _("dactyl.created", "@DATE@");
}),
get fileExt() this.name.slice(0, -6),
dtd: Class.Memoize(function ()
iter(this.dtdExtra,
(["dactyl." + k, v] for ([k, v] in iter(config.dtdDactyl))),
(["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
.toObject()),
dtdDactyl: memoize({
get name() config.name,
get home() "http://dactyl.sourceforge.net/",
get apphome() this.home + this.name,
code: "http://code.google.com/p/dactyl/",
get issues() this.home + "bug/" + this.name,
get plugins() "http://dactyl.sf.net/" + this.name + "/plugins",
get faq() this.home + this.name + "/faq",
"list.mailto": Class.Memoize(function () config.name + "@googlegroups.com"),
"list.href": Class.Memoize(function () "http://groups.google.com/group/" + config.name),
"hg.latest": Class.Memoize(function () this.code + "source/browse/"), // XXX
"irc": "irc://irc.oftc.net/#pentadactyl",
}),
dtdExtra: {
"xmlns.dactyl": "http://vimperator.org/namespaces/liberator",
"xmlns.html": "http://www.w3.org/1999/xhtml",
"xmlns.xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"tag.command-line": <link topic="command-line">command line</link>,
"tag.status-line": <link topic="status-line">status line</link>,
"mode.command-line": <link topic="command-line-mode">Command Line</link>,
},
dtdStrings: [
"appName",
"fileExt",
"host",
"hostbin",
"idName",
"name",
"version"
],
helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Dense|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/,
styleHelp: function styleHelp() {
if (!this.helpStyled) {
for (let k in keys(highlight.loaded))
if (this.helpStyles.test(k))
highlight.loaded[k] = true;
}
this.helpCSS = true;
},
Local: function Local(dactyl, modules, { document, window }) ({
init: function init() {
this.loadConfig(document.documentURI);
let append = <e4x xmlns={XUL} xmlns:dactyl={NS}>
<menupopup id="viewSidebarMenu"/>
<broadcasterset id="mainBroadcasterSet"/>
</e4x>;
for each (let [id, [name, key, uri]] in Iterator(this.sidebars)) {
append.XUL::menupopup[0].* +=
<menuitem observes={"pentadactyl-" + id + "Sidebar"} label={name} accesskey={key} xmlns={XUL}/>;
append.XUL::broadcasterset[0].* +=
<broadcaster id={"pentadactyl-" + id + "Sidebar"}
autoCheck="false" type="checkbox" group="sidebar"
sidebartitle={name} sidebarurl={uri}
oncommand="toggleSidebar(this.id || this.observes);" xmlns={XUL}/>;
}
util.overlayWindow(window, { append: append.elements() });
},
get window() window,
get document() document,
ids: Class.Update({
get commandContainer() document.documentElement.id
}),
browser: Class.Memoize(function () window.gBrowser),
tabbrowser: Class.Memoize(function () window.gBrowser),
get browserModes() [modules.modes.NORMAL],
/**
* @property {string} The ID of the application's main XUL window.
*/
mainWindowId: document.documentElement.id,
/**
* @property {number} The height (px) that is available to the output
* window.
*/
get outputHeight() this.browser.mPanelContainer.boxObject.height,
tabStrip: Class.Memoize(function () document.getElementById("TabsToolbar") || this.tabbrowser.mTabContainer),
}),
/**
* @property {Object} A mapping of names and descriptions
* of the autocommands available in this application. Primarily used
* for completion results.
*/
autocommands: {},
/**
* @property {Object} A map of :command-complete option values to completer
* function names.
*/
completers: {},
/**
* @property {Object} Application specific defaults for option values. The
* property names must be the options' canonical names, and the values
* must be strings as entered via :set.
*/
optionDefaults: {},
cleanups: {},
/**
* @property {Object} A map of dialogs available via the
* :dialog command. Property names map dialog names to an array
* with the following elements:
* [0] description - A description of the dialog, used in
* command completion results for :dialog.
* [1] action - The function executed by :dialog.
* [2] test - Function which returns true if the dialog is available in
* the current window. @optional
*/
dialogs: {},
/**
* @property {set} A list of features available in this
* application. Used extensively in feature test macros. Use
* dactyl.has(feature) to check for a feature's presence
* in this array.
*/
features: {},
/**
* @property {string} The file extension used for command script files.
* This is the name string sans "dactyl".
*/
get fileExtension() this.name.slice(0, -6),
guioptions: {},
/**
* @property {string} The name of the application that hosts the
* extension. E.g., "Firefox" or "XULRunner".
*/
host: null,
/**
* @property {string} The name of the extension.
* Required.
*/
name: null,
/**
* @property {[string]} A list of extra scripts in the dactyl or
* application namespaces which should be loaded before dactyl
* initialization.
*/
scripts: [],
sidebars: {},
/**
* @property {string} The leaf name of any temp files created by
* {@link io.createTempFile}.
*/
get tempFile() this.name + ".tmp",
/**
* @constant
* @property {string} The default highlighting rules.
* See {@link Highlights#loadCSS} for details.
*/
CSS: Class.Memoize(function () File.readURL("resource://dactyl-skin/global-styles.css")),
helpCSS: Class.Memoize(function () File.readURL("resource://dactyl-skin/help-styles.css"))
}, {
});
JSMLoader.loadSubScript("resource://dactyl-local-content/config.js", this);
config.INIT = update(Object.create(config.INIT), config.INIT, {
init: function init(dactyl, modules, window) {
init.superapply(this, arguments);
let img = window.Image();
img.src = this.logo || "resource://dactyl-local-content/logo.png";
img.onload = util.wrapCallback(function () {
highlight.loadCSS(<>{"!Logo {"}
display: inline-block;
background: url({img.src});
width: {img.width}px;
height: {img.height}px;
{"}"}</>);
img = null;
});
},
load: function load(dactyl, modules, window) {
load.superapply(this, arguments);
this.timeout(function () {
if (this.branch && this.branch !== "default" &&
modules.yes_i_know_i_should_not_report_errors_in_these_branches_thanks.indexOf(this.branch) === -1)
dactyl.warn(_("warn.notDefaultBranch", config.appName, this.branch));
}, 1000);
}
});
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: