X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Foptions.jsm;fp=common%2Fmodules%2Foptions.jsm;h=512e304f789b800506c8fe4be6fa6e13f6d6f223;hb=9044153cb63835e39b9de8ec4ade237c03e3888a;hp=a09fef257b3dc149ab1b8a53c1b20b054e2e0984;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git
diff --git a/common/modules/options.jsm b/common/modules/options.jsm
index a09fef2..512e304 100644
--- a/common/modules/options.jsm
+++ b/common/modules/options.jsm
@@ -4,17 +4,18 @@
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
-"use strict";
+/* use strict */
try {
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("options", {
exports: ["Option", "Options", "ValueError", "options"],
- require: ["messages", "storage"],
- use: ["commands", "completion", "config", "prefs", "services", "styles", "template", "util"]
+ require: ["contexts", "messages", "storage"]
}, this);
+this.lazyRequire("config", ["config"]);
+
/** @scope modules */
let ValueError = Class("ValueError", ErrorBase);
@@ -47,32 +48,13 @@ var Option = Class("Option", {
init: function init(modules, names, description, defaultValue, extraInfo) {
this.modules = modules;
this.name = names[0];
- this.names = names;
this.realNames = names;
this.description = description;
if (extraInfo)
this.update(extraInfo);
- if (Set.has(this.modules.config.defaults, this.name))
- defaultValue = this.modules.config.defaults[this.name];
-
- if (defaultValue !== undefined) {
- if (this.type === "string")
- defaultValue = Commands.quote(defaultValue);
-
- if (isObject(defaultValue))
- defaultValue = iter(defaultValue).map(function (val) val.map(Option.quote).join(":")).join(",");
-
- if (isArray(defaultValue))
- defaultValue = defaultValue.map(Option.quote).join(",");
-
- this.defaultValue = this.parse(defaultValue);
- }
-
- // add no{option} variant of boolean {option} to this.names
- if (this.type == "boolean")
- this.names = array([name, "no" + name] for (name in values(names))).flatten().array;
+ this._defaultValue = defaultValue;
if (this.globalValue == undefined && !this.initialValue)
this.globalValue = this.defaultValue;
@@ -101,8 +83,15 @@ var Option = Class("Option", {
},
/** @property {value} The option's global value. @see #scope */
- get globalValue() { try { return options.store.get(this.name, {}).value; } catch (e) { util.reportError(e); throw e; } },
- set globalValue(val) { options.store.set(this.name, { value: val, time: Date.now() }); },
+ get globalValue() {
+ let val = options.store.get(this.name, {}).value;
+ if (val != null)
+ return val;
+ return this.globalValue = this.defaultValue;
+ },
+ set globalValue(val) {
+ options.store.set(this.name, { value: val, time: Date.now() });
+ },
/**
* Returns *value* as an array of parsed values if the option type is
@@ -268,8 +257,9 @@ var Option = Class("Option", {
/** @property {string} The option's canonical name. */
name: null,
+
/** @property {[string]} All names by which this option is identified. */
- names: null,
+ names: Class.Memoize(function () this.realNames),
/**
* @property {string} The option's data type. One of:
@@ -330,7 +320,32 @@ var Option = Class("Option", {
* unless the option is explicitly set either interactively or in an RC
* file or plugin.
*/
- defaultValue: null,
+ defaultValue: Class.Memoize(function () {
+ let defaultValue = this._defaultValue;
+ delete this._defaultValue;
+
+ if (Set.has(this.modules.config.optionDefaults, this.name))
+ defaultValue = this.modules.config.optionDefaults[this.name];
+
+ if (defaultValue == null && this.getter)
+ defaultValue = this.getter();
+
+ if (defaultValue == undefined)
+ return null;
+
+ if (this.type === "string")
+ defaultValue = Commands.quote(defaultValue);
+
+ if (isArray(defaultValue))
+ defaultValue = defaultValue.map(Option.quote).join(",");
+ else if (isObject(defaultValue))
+ defaultValue = iter(defaultValue).map(function (val) val.map(Option.quote).join(":")).join(",");
+
+ if (isArray(defaultValue))
+ defaultValue = defaultValue.map(Option.quote).join(",");
+
+ return this.parse(defaultValue);
+ }),
/**
* @property {function} The function called when the option value is read.
@@ -425,6 +440,7 @@ var Option = Class("Option", {
let re = util.regexp(Option.dequote(val), flags);
re.bang = bang;
re.result = result !== undefined ? result : !bang;
+ re.key = re.bang + Option.quote(util.regexp.getSource(re), /^!|:/);
re.toString = function () Option.unparseRegexp(this, keepQuotes);
return re;
},
@@ -597,6 +613,23 @@ var Option = Class("Option", {
return null;
},
+ string: function string(operator, values, scope, invert) {
+ if (invert)
+ return values[(values.indexOf(this.value) + 1) % values.length];
+
+ switch (operator) {
+ case "+":
+ return this.value + values;
+ case "-":
+ return this.value.replace(values, "");
+ case "^":
+ return values + this.value;
+ case "=":
+ return values;
+ }
+ return null;
+ },
+
stringmap: function stringmap(operator, values, scope, invert) {
let res = update({}, this.value);
@@ -654,23 +687,7 @@ var Option = Class("Option", {
get regexplist() this.stringlist,
get regexpmap() this.stringlist,
get sitelist() this.stringlist,
- get sitemap() this.stringlist,
-
- string: function string(operator, values, scope, invert) {
- if (invert)
- return values[(values.indexOf(this.value) + 1) % values.length];
- switch (operator) {
- case "+":
- return this.value + values;
- case "-":
- return this.value.replace(values, "");
- case "^":
- return values + this.value;
- case "=":
- return values;
- }
- return null;
- }
+ get sitemap() this.stringlist
},
validIf: function validIf(test, error) {
@@ -686,23 +703,31 @@ var Option = Class("Option", {
* @param {value|[string]} values The value or array of values to validate.
* @returns {boolean}
*/
- validateCompleter: function validateCompleter(values) {
- if (this.values)
- var acceptable = this.values.array || this.values;
- else {
+ validateCompleter: function validateCompleter(vals) {
+ function completions(extra) {
let context = CompletionContext("");
- acceptable = context.fork("", 0, this, this.completer);
- if (!acceptable)
- acceptable = context.allItems.items.map(function (item) [item.text]);
+ return context.fork("", 0, this, this.completer, extra) ||
+ context.allItems.items.map(function (item) [item.text]);
+ };
+
+ if (isObject(vals) && !isArray(vals)) {
+ let k = values(completions.call(this, { values: {} })).toObject();
+ let v = values(completions.call(this, { value: "" })).toObject();
+ return Object.keys(vals).every(Set.has(k)) && values(vals).every(Set.has(v));
}
+ if (this.values)
+ var acceptable = this.values.array || this.values;
+ else
+ acceptable = completions.call(this);
+
if (isArray(acceptable))
acceptable = Set(acceptable.map(function ([k]) k));
if (this.type === "regexpmap" || this.type === "sitemap")
- return Array.concat(values).every(function (re) Set.has(acceptable, re.result));
+ return Array.concat(vals).every(function (re) Set.has(acceptable, re.result));
- return Array.concat(values).every(Set.has(acceptable));
+ return Array.concat(vals).every(Set.has(acceptable));
},
types: {}
@@ -745,6 +770,23 @@ var Option = Class("Option", {
EXPORTED_SYMBOLS.push(class_.className);
}, this);
+update(BooleanOption.prototype, {
+ names: Class.Memoize(function ()
+ array.flatten([[name, "no" + name] for (name in values(this.realNames))]))
+});
+
+var OptionHive = Class("OptionHive", Contexts.Hive, {
+ init: function init(group) {
+ init.supercall(this, group);
+ this.values = {};
+ this.has = Set.has(this.values);
+ },
+
+ add: function add(names, description, type, defaultValue, extraInfo) {
+ return this.modules.options.add(names, description, type, defaultValue, extraInfo);
+ }
+});
+
/**
* @instance options
*/
@@ -752,6 +794,12 @@ var Options = Module("options", {
Local: function Local(dactyl, modules, window) let ({ contexts } = modules) ({
init: function init() {
const self = this;
+
+ update(this, {
+ hives: contexts.Hives("options", Class("OptionHive", OptionHive, { modules: modules })),
+ user: contexts.hives.options.user
+ });
+
this.needInit = [];
this._options = [];
this._optionMap = {};
@@ -764,17 +812,23 @@ 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, "''") :
- o.value === true ? "on" :
- o.value === false ? "off" : o.stringDefaultValue]
+ o.defaultValue === true ? "on" :
+ o.defaultValue === false ? "off" : o.stringDefaultValue]
for (o in self)),
([["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,
@@ -811,8 +865,10 @@ var Options = Module("options", {
option.pre = "no";
option.default = (opt.defaultValue ? "" : "no") + opt.name;
}
- else if (isArray(opt.value))
- option.value = <>={template.map(opt.value, function (v) template.highlight(String(v)), <>, >)}>;
+ else if (isArray(opt.value) && opt.type != "charlist")
+ option.value = <>={template.map(opt.value,
+ function (v) template.highlight(String(v)),
+ <>, >)}>;
else
option.value = <>={template.highlight(opt.stringValue)}>;
yield option;
@@ -842,6 +898,12 @@ var Options = Module("options", {
add: function add(names, description, type, defaultValue, extraInfo) {
const self = this;
+ if (!util.isDactyl(Components.stack.caller))
+ deprecated.warn(add, "options.add", "group.options.add");
+
+ util.assert(type in Option.types, _("option.noSuchType", type),
+ false);
+
if (!extraInfo)
extraInfo = {};
@@ -891,7 +953,7 @@ var Options = Module("options", {
setPref: deprecated("prefs.set", function setPref() prefs.set.apply(prefs, arguments)),
withContext: deprecated("prefs.withContext", function withContext() prefs.withContext.apply(prefs, arguments)),
- cleanupPrefs: Class.memoize(function () localPrefs.Branch("cleanup.option.")),
+ cleanupPrefs: Class.Memoize(function () config.prefs.Branch("cleanup.option.")),
cleanup: function cleanup(reason) {
if (~["disable", "uninstall"].indexOf(reason))
@@ -969,7 +1031,8 @@ var Options = Module("options", {
res.optionValue = res.option.get(res.scope);
try {
- res.values = res.option.parse(res.value);
+ if (!res.invert || res.option.type != "number") // Hack.
+ res.values = res.option.parse(res.value);
}
catch (e) {
res.error = e;
@@ -1060,11 +1123,12 @@ var Options = Module("options", {
}
else {
var [matches, name, postfix, valueGiven, operator, value] =
- arg.match(/^\s*?([^=]+?)([?&!])?\s*(([-+^]?)=(.*))?\s*$/);
+ arg.match(/^\s*?((?:[^=\\']|\\.|'[^']*')+?)([?&!])?\s*(([-+^]?)=(.*))?\s*$/);
reset = (postfix == "&");
invertBoolean = (postfix == "!");
}
+ name = Option.dequote(name);
if (name == "all" && reset)
modules.commandline.input(_("pref.prompt.resetAll", config.host) + " ",
function (resp) {
@@ -1442,6 +1506,25 @@ var Options = Module("options", {
context.advance(Option._splitAt);
context.filter = Option.dequote(context.filter);
+ function val(obj) {
+ if (isArray(opt.defaultValue)) {
+ let val = array.nth(obj, function (re) re.key == extra.key, 0);
+ return val && val.result;
+ }
+ if (Set.has(opt.defaultValue, extra.key))
+ return obj[extra.key];
+ }
+
+ if (extra.key && extra.value != null) {
+ context.fork("default", 0, this, function (context) {
+ context.completions = [
+ [val(opt.value), _("option.currentValue")],
+ [val(opt.defaultValue), _("option.defaultValue")]
+ ].filter(function (f) f[0] !== "" && f[0] != null);
+ });
+ context = context.fork("stuff", 0);
+ }
+
context.title = ["Option Value"];
context.quote = Commands.complQuote[Option._quote] || Commands.complQuote[""];
// Not Vim compatible, but is a significant enough improvement
@@ -1469,7 +1552,7 @@ var Options = Module("options", {
},
javascript: function initJavascript(dactyl, modules, window) {
const { options, JavaScript } = modules;
- JavaScript.setCompleter(options.get, [function () ([o.name, o.description] for (o in options))]);
+ JavaScript.setCompleter(Options.prototype.get, [function () ([o.name, o.description] for (o in options))]);
},
sanitizer: function initSanitizer(dactyl, modules, window) {
const { sanitizer } = modules;