X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Foptions.jsm;fp=common%2Fmodules%2Foptions.jsm;h=a09fef257b3dc149ab1b8a53c1b20b054e2e0984;hb=70740024f9c028c1fd63e1a1850ab062ff956054;hp=1c61a40af6bd7bbd239bd79f7f60c3ae86cd11a0;hpb=718c614c183350706466e22939d0101ca4c87efe;p=dactyl.git
diff --git a/common/modules/options.jsm b/common/modules/options.jsm
index 1c61a40..a09fef2 100644
--- a/common/modules/options.jsm
+++ b/common/modules/options.jsm
@@ -12,7 +12,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("options", {
exports: ["Option", "Options", "ValueError", "options"],
require: ["messages", "storage"],
- use: ["commands", "completion", "prefs", "services", "styles", "template", "util"]
+ use: ["commands", "completion", "config", "prefs", "services", "styles", "template", "util"]
}, this);
/** @scope modules */
@@ -25,7 +25,7 @@ let ValueError = Class("ValueError", ErrorBase);
* A class representing configuration options. Instances are created by the
* {@link Options} class.
*
- * @param {string[]} names The names by which this option is identified.
+ * @param {[string]} names The names by which this option is identified.
* @param {string} description A short one line description of the option.
* @param {string} type The option's value data type (see {@link Option#type}).
* @param {string} defaultValue The default value for this option.
@@ -44,44 +44,21 @@ let ValueError = Class("ValueError", ErrorBase);
* @private
*/
var Option = Class("Option", {
- init: function init(names, description, type, defaultValue, extraInfo) {
+ init: function init(modules, names, description, defaultValue, extraInfo) {
+ this.modules = modules;
this.name = names[0];
this.names = names;
this.realNames = names;
- this.type = type;
this.description = description;
- if (this.type in Option.getKey)
- this.getKey = Option.getKey[this.type];
-
- if (this.type in Option.parse)
- this.parse = Option.parse[this.type];
-
- if (this.type in Option.stringify)
- this.stringify = Option.stringify[this.type];
-
- if (this.type in Option.domains)
- this.domains = Option.domains[this.type];
-
- if (this.type in Option.testValues)
- this.testValues = Option.testValues[this.type];
-
- this._op = Option.ops[this.type];
-
- // Need to trigger setter
- if (extraInfo && "values" in extraInfo && !extraInfo.__lookupGetter__("values")) {
- this.values = extraInfo.values;
- delete extraInfo.values;
- }
-
if (extraInfo)
- update(this, extraInfo);
+ this.update(extraInfo);
- if (set.has(this.modules.config.defaults, this.name))
+ if (Set.has(this.modules.config.defaults, this.name))
defaultValue = this.modules.config.defaults[this.name];
if (defaultValue !== undefined) {
- if (this.type == "string")
+ if (this.type === "string")
defaultValue = Commands.quote(defaultValue);
if (isObject(defaultValue))
@@ -101,6 +78,8 @@ var Option = Class("Option", {
this.globalValue = this.defaultValue;
},
+ magicalProperties: Set(["cleanupValue"]),
+
/**
* @property {string} This option's description, as shown in :listoptions.
*/
@@ -114,6 +93,13 @@ var Option = Class("Option", {
get isDefault() this.stringValue === this.stringDefaultValue,
+ /** @property {value} The value to reset this option to at cleanup time. */
+ get cleanupValue() options.cleanupPrefs.get(this.name),
+ set cleanupValue(value) {
+ if (options.cleanupPrefs.get(this.name) == null)
+ options.cleanupPrefs.set(this.name, value);
+ },
+
/** @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() }); },
@@ -123,14 +109,14 @@ var Option = Class("Option", {
* "charlist" or "stringlist" or else unchanged.
*
* @param {value} value The option value.
- * @returns {value|string[]}
+ * @returns {value|[string]}
*/
parse: function parse(value) Option.dequote(value),
/**
* Returns *values* packed in the appropriate format for the option type.
*
- * @param {value|string[]} values The option value.
+ * @param {value|[string]} values The option value.
* @returns {value}
*/
stringify: function stringify(vals) Commands.quote(vals),
@@ -141,7 +127,7 @@ var Option = Class("Option", {
*
* @param {number} scope The scope to return these values from (see
* {@link Option#scope}).
- * @returns {value|string[]}
+ * @returns {value|[string]}
*/
get: function get(scope) {
if (scope) {
@@ -213,6 +199,7 @@ var Option = Class("Option", {
set stringValue(value) this.value = this.parse(value),
get stringDefaultValue() this.stringify(this.defaultValue),
+ set stringDefaultValue(val) this.defaultValue = this.parse(val),
getKey: function getKey(key) undefined,
@@ -252,7 +239,7 @@ var Option = Class("Option", {
* Sets the option's value using the specified set *operator*.
*
* @param {string} operator The set operator.
- * @param {value|string[]} values The value (or values) to apply.
+ * @param {value|[string]} values The value (or values) to apply.
* @param {number} scope The scope to apply this value to (see
* {@link #scope}).
* @param {boolean} invert Whether this is an invert boolean operation.
@@ -262,7 +249,7 @@ var Option = Class("Option", {
try {
var newValues = this._op(operator, values, scope, invert);
if (newValues == null)
- return "Operator " + operator + " not supported for option type " + this.type;
+ return _("option.operatorNotSupported", operator, this.type);
if (!this.isValidValue(newValues))
return this.invalidArgument(str || this.stringify(values), operator);
@@ -281,7 +268,7 @@ var Option = Class("Option", {
/** @property {string} The option's canonical name. */
name: null,
- /** @property {string[]} All names by which this option is identified. */
+ /** @property {[string]} All names by which this option is identified. */
names: null,
/**
@@ -305,13 +292,14 @@ var Option = Class("Option", {
*/
scope: 1, // Option.SCOPE_GLOBAL // XXX set to BOTH by default someday? - kstep
- cleanupValue: null,
-
/**
* @property {function(CompletionContext, Args)} This option's completer.
* @see CompletionContext
*/
- completer: function completer(context) {
+ completer: function completer(context, extra) {
+ if (/map$/.test(this.type) && extra.value == null)
+ return;
+
if (this.values)
context.completions = this.values;
},
@@ -451,12 +439,14 @@ var Option = Class("Option", {
let [, bang, filter] = /^(!?)(.*)/.exec(pattern);
filter = Option.dequote(filter);
+ let quote = this.keepQuotes ? util.identity : Option.quote;
+
return update(Styles.matchFilter(filter), {
bang: bang,
filter: filter,
result: result !== undefined ? result : !bang,
- toString: function toString() this.bang + Option.quote(this.filter) +
- (typeof this.result === "boolean" ? "" : ":" + Option.quote(this.result)),
+ toString: function toString() this.bang + Option.quote(this.filter, /:/) +
+ (typeof this.result === "boolean" ? "" : ":" + quote(this.result)),
});
},
@@ -466,7 +456,7 @@ var Option = Class("Option", {
regexplist: function regexplist(k, default_) {
for (let re in values(this.value))
- if (re(k))
+ if ((re.test || re).call(re, k))
return re.result;
return arguments.length > 1 ? default_ : null;
},
@@ -495,7 +485,7 @@ var Option = Class("Option", {
parse: {
number: function (value) let (val = Option.dequote(value))
- Option.validIf(Number(val) % 1 == 0, "Integer value required") && parseInt(val),
+ Option.validIf(Number(val) % 1 == 0, _("option.intRequired")) && parseInt(val),
boolean: function boolean(value) Option.dequote(value) == "true" || value == true ? true : false,
@@ -512,7 +502,7 @@ var Option = Class("Option", {
return [];
if (!isArray(value))
value = Option.splitList(value, true);
- return value.map(Option.parseSite);
+ return value.map(Option.parseSite, this);
},
stringmap: function stringmap(value) array.toObject(
@@ -536,7 +526,7 @@ var Option = Class("Option", {
if (v.length > count)
return prev = parse.call(this, filter, val);
else {
- util.assert(prev, "Syntax error", false);
+ util.assert(prev, _("error.syntaxError"), false);
prev.result += "," + v;
}
}, this))
@@ -592,7 +582,7 @@ var Option = Class("Option", {
let value = parseInt(values);
util.assert(Number(values) % 1 == 0,
- "E521: Number required after =: " + this.name + "=" + values);
+ _("command.set.numberRequired", this.name, values));
switch (operator) {
case "+":
@@ -637,18 +627,23 @@ var Option = Class("Option", {
stringlist: function stringlist(operator, values, scope, invert) {
values = Array.concat(values);
+ function uniq(ary) {
+ let seen = {};
+ return ary.filter(function (elem) !Set.add(seen, elem));
+ }
+
switch (operator) {
case "+":
- return array.uniq(Array.concat(this.value, values), true);
+ return uniq(Array.concat(this.value, values), true);
case "^":
// NOTE: Vim doesn't prepend if there's a match in the current value
- return array.uniq(Array.concat(values, this.value), true);
+ return uniq(Array.concat(values, this.value), true);
case "-":
- return this.value.filter(function (item) values.indexOf(item) == -1);
+ return this.value.filter(function (item) !Set.has(this, item), Set(values));
case "=":
if (invert) {
- let keepValues = this.value.filter(function (item) values.indexOf(item) == -1);
- let addValues = values.filter(function (item) this.value.indexOf(item) == -1, this);
+ let keepValues = this.value.filter(function (item) !Set.has(this, item), Set(values));
+ let addValues = values.filter(function (item) !Set.has(this, item), Set(this.value));
return addValues.concat(keepValues);
}
return values;
@@ -688,7 +683,7 @@ var Option = Class("Option", {
* Validates the specified *values* against values generated by the
* option's completer function.
*
- * @param {value|string[]} values The value or array of values to validate.
+ * @param {value|[string]} values The value or array of values to validate.
* @returns {boolean}
*/
validateCompleter: function validateCompleter(values) {
@@ -702,15 +697,54 @@ var Option = Class("Option", {
}
if (isArray(acceptable))
- acceptable = set(acceptable.map(function ([k]) k));
+ 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(values).every(function (re) Set.has(acceptable, re.result));
- return Array.concat(values).every(set.has(acceptable));
- }
+ return Array.concat(values).every(Set.has(acceptable));
+ },
+
+ types: {}
});
+["Boolean",
+ "Charlist",
+ "Number",
+ "RegexpList",
+ "RegexpMap",
+ "SiteList",
+ "SiteMap",
+ "String",
+ "StringList",
+ "StringMap"].forEach(function (name) {
+ let type = name.toLowerCase();
+ let class_ = Class(name + "Option", Option, {
+ type: type,
+
+ _op: Option.ops[type]
+ });
+
+ if (type in Option.getKey)
+ class_.prototype.getKey = Option.getKey[type];
+
+ if (type in Option.parse)
+ class_.prototype.parse = Option.parse[type];
+
+ if (type in Option.stringify)
+ class_.prototype.stringify = Option.stringify[type];
+
+ if (type in Option.domains)
+ class_.prototype.domains = Option.domains[type];
+
+ if (type in Option.testValues)
+ class_.prototype.testValues = Option.testValues[type];
+
+ Option.types[type] = class_;
+ this[class_.className] = class_;
+ EXPORTED_SYMBOLS.push(class_.className);
+}, this);
+
/**
* @instance options
*/
@@ -721,7 +755,6 @@ var Options = Module("options", {
this.needInit = [];
this._options = [];
this._optionMap = {};
- this.Option = Class("Option", Option, { modules: modules });
storage.newMap("options", { store: false });
storage.addObserver("options", function optionObserver(key, event, option) {
@@ -730,6 +763,18 @@ var Options = Module("options", {
if (event == "change" && opt)
opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true);
}, window);
+
+ services["dactyl:"].pages["options.dtd"] = function () [null,
+ 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]
+ for (o in self)),
+
+ ([["option", o.name, "type"].join("."), o.type] for (o in self)),
+
+ config.dtd))];
},
dactyl: dactyl,
@@ -742,7 +787,7 @@ var Options = Module("options", {
* @param {number} scope Only list options in this scope (see
* {@link Option#scope}).
*/
- list: function (filter, scope) {
+ list: function list(filter, scope) {
if (!scope)
scope = Option.SCOPE_BOTH;
@@ -780,13 +825,13 @@ var Options = Module("options", {
cleanup: function cleanup() {
for (let opt in this)
if (opt.cleanupValue != null)
- opt.value = opt.parse(opt.cleanupValue);
+ opt.stringValue = opt.cleanupValue;
},
/**
* Adds a new option.
*
- * @param {string[]} names All names for the option.
+ * @param {[string]} names All names for the option.
* @param {string} description A description of the option.
* @param {string} type The option type (see {@link Option#type}).
* @param {value} defaultValue The option's default value.
@@ -794,7 +839,7 @@ var Options = Module("options", {
* {@link Map#extraInfo}).
* @optional
*/
- add: function (names, description, type, defaultValue, extraInfo) {
+ add: function add(names, description, type, defaultValue, extraInfo) {
const self = this;
if (!extraInfo)
@@ -810,7 +855,7 @@ var Options = Module("options", {
let closure = function () self._optionMap[name];
- memoize(this._optionMap, name, function () self.Option(names, description, type, defaultValue, extraInfo));
+ memoize(this._optionMap, name, function () Option.types[type](modules, names, description, defaultValue, extraInfo));
for (let alias in values(names.slice(1)))
memoize(this._optionMap, alias, closure);
@@ -836,7 +881,7 @@ var Options = Module("options", {
allPrefs: deprecated("prefs.getNames", function allPrefs() prefs.getNames.apply(prefs, arguments)),
getPref: deprecated("prefs.get", function getPref() prefs.get.apply(prefs, arguments)),
invertPref: deprecated("prefs.invert", function invertPref() prefs.invert.apply(prefs, arguments)),
- listPrefs: deprecated("prefs.list", function listPrefs() { commandline.commandOutput(prefs.list.apply(prefs, arguments)); }),
+ listPrefs: deprecated("prefs.list", function listPrefs() { this.modules.commandline.commandOutput(prefs.list.apply(prefs, arguments)); }),
observePref: deprecated("prefs.observe", function observePref() prefs.observe.apply(prefs, arguments)),
popContext: deprecated("prefs.popContext", function popContext() prefs.popContext.apply(prefs, arguments)),
pushContext: deprecated("prefs.pushContext", function pushContext() prefs.pushContext.apply(prefs, arguments)),
@@ -846,6 +891,13 @@ 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.")),
+
+ cleanup: function cleanup(reason) {
+ if (~["disable", "uninstall"].indexOf(reason))
+ this.cleanupPrefs.resetBranch();
+ },
+
/**
* Returns the option with *name* in the specified *scope*.
*
@@ -887,8 +939,11 @@ var Options = Module("options", {
}
if (matches) {
- res.option = this.get(res.name, res.scope);
- if (!res.option && (res.option = this.get(prefix + res.name, res.scope))) {
+ if (res.option = this.get(res.name, res.scope)) {
+ if (prefix === "no" && res.option.type !== "boolean")
+ res.option = null;
+ }
+ else if (res.option = this.get(prefix + res.name, res.scope)) {
res.name = prefix + res.name;
prefix = "";
}
@@ -953,7 +1008,9 @@ var Options = Module("options", {
format: {
description: function (map) (XML.ignoreWhitespace = false, XML.prettyPrinting = false, <>
{options.get("passkeys").has(map.name)
- ? (passed by {template.helpLink("'passkeys'")})
+ ? ({
+ tempate.linkifyHelp(_("option.passkeys.passedBy"))
+ })
: <>>}
{template.linkifyHelp(map.description)}
>)
@@ -968,7 +1025,7 @@ var Options = Module("options", {
format: {
description: function (opt) (XML.ignoreWhitespace = false, XML.prettyPrinting = false, <>
{opt.scope == Option.SCOPE_LOCAL
- ? (buffer local) : ""}
+ ? ({_("option.bufferLocal")}) : ""}
{template.linkifyHelp(opt.description)}
>),
help: function (opt) "'" + opt.name + "'"
@@ -982,12 +1039,12 @@ var Options = Module("options", {
let list = [];
function flushList() {
- let names = set(list.map(function (opt) opt.option ? opt.option.name : ""));
+ let names = Set(list.map(function (opt) opt.option ? opt.option.name : ""));
if (list.length)
if (list.some(function (opt) opt.all))
- options.list(function (opt) !(list[0].onlyNonDefault && opt.isDefault) , list[0].scope);
+ options.list(function (opt) !(list[0].onlyNonDefault && opt.isDefault), list[0].scope);
else
- options.list(function (opt) set.has(names, opt.name), list[0].scope);
+ options.list(function (opt) Set.has(names, opt.name), list[0].scope);
list = [];
}
@@ -1009,7 +1066,7 @@ var Options = Module("options", {
}
if (name == "all" && reset)
- modules.commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ",
+ modules.commandline.input(_("pref.prompt.resetAll", config.host) + " ",
function (resp) {
if (resp == "yes")
for (let pref in values(prefs.getNames()))
@@ -1017,7 +1074,7 @@ var Options = Module("options", {
},
{ promptHighlight: "WarningMsg" });
else if (name == "all")
- commandline.commandOutput(prefs.list(onlyNonDefault, ""));
+ modules.commandline.commandOutput(prefs.list(onlyNonDefault, ""));
else if (reset)
prefs.reset(name);
else if (invertBoolean)
@@ -1044,10 +1101,11 @@ var Options = Module("options", {
}
let opt = modules.options.parseOpt(arg, modifiers);
- util.assert(opt, "Error parsing :set command: " + arg);
+ util.assert(opt, _("command.set.errorParsing", arg));
+ util.assert(!opt.error, _("command.set.errorParsing", opt.error));
let option = opt.option;
- util.assert(option != null || opt.all, "E518: Unknown option: " + opt.name);
+ util.assert(option != null || opt.all, _("command.set.unknownOption", opt.name));
// reset a variable to its default value
if (opt.reset) {
@@ -1099,8 +1157,8 @@ var Options = Module("options", {
context.pushProcessor(0, function (item, text, next) next(item, text.substr(0, 100)));
context.completions = [
- [prefs.get(filter), "Current Value"],
- [prefs.defaults.get(filter), "Default Value"]
+ [prefs.get(filter), _("option.currentValue")],
+ [prefs.defaults.get(filter), _("option.defaultValue")]
].filter(function (k) k[0] != null);
return null;
}
@@ -1115,7 +1173,7 @@ var Options = Module("options", {
if (context.filter.indexOf("=") == -1) {
if (false && prefix)
context.filters.push(function ({ item }) item.type == "boolean" || prefix == "inv" && isArray(item.values));
- return completion.option(context, opt.scope, prefix);
+ return completion.option(context, opt.scope, opt.name == "inv" ? opt.name : prefix);
}
function error(length, message) {
@@ -1125,11 +1183,11 @@ var Options = Module("options", {
let option = opt.option;
if (!option)
- return error(opt.name.length, "No such option: " + opt.name);
+ return error(opt.name.length, _("option.noSuch", opt.name));
context.advance(context.filter.indexOf("="));
if (option.type == "boolean")
- return error(context.filter.length, "Trailing characters");
+ return error(context.filter.length, _("error.trailingCharacters"));
context.advance(1);
if (opt.error)
@@ -1143,8 +1201,8 @@ var Options = Module("options", {
context.title = ["Extra Completions"];
context.pushProcessor(0, function (item, text, next) next(item, text.substr(0, 100)));
context.completions = [
- [option.stringValue, "Current value"],
- [option.stringDefaultValue, "Default value"]
+ [option.stringValue, _("option.currentValue")],
+ [option.stringDefaultValue, _("option.defaultValue")]
].filter(function (f) f[0] !== "");
context.quote = ["", util.identity, ""];
});
@@ -1155,12 +1213,12 @@ var Options = Module("options", {
// Fill in the current values if we're removing
if (opt.operator == "-" && isArray(opt.values)) {
- let have = set([i.text for (i in values(context.allItems.items))]);
+ let have = Set([i.text for (i in values(context.allItems.items))]);
context = context.fork("current-values", 0);
context.anchored = optcontext.anchored;
context.maxItems = optcontext.maxItems;
- context.filters.push(function (i) !set.has(have, i.text));
+ context.filters.push(function (i) !Set.has(have, i.text));
modules.completion.optionValue(context, opt.name, opt.operator, null,
function (context) {
context.generate = function () option.value.map(function (o) [o, ""]);
@@ -1195,7 +1253,7 @@ var Options = Module("options", {
if (str.text().length() == str.*.length())
dactyl.echomsg(_("variable.none"));
else
- dactyl.echo(str, commandline.FORCE_MULTILINE);
+ dactyl.echo(str, modules.commandline.FORCE_MULTILINE);
return;
}
@@ -1206,7 +1264,7 @@ var Options = Module("options", {
util.assert(scope == "g:" || scope == null,
_("command.let.illegalVar", scope + name));
- util.assert(set.has(globalVariables, name) || (expr && !op),
+ util.assert(Set.has(globalVariables, name) || (expr && !op),
_("command.let.undefinedVar", fullName));
if (!expr)
@@ -1304,7 +1362,7 @@ var Options = Module("options", {
function (args) {
for (let [, name] in args) {
name = name.replace(/^g:/, ""); // throw away the scope prefix
- if (!set.has(dactyl._globalVariables, name)) {
+ if (!Set.has(dactyl._globalVariables, name)) {
if (!args.bang)
dactyl.echoerr(_("command.let.noSuch", name));
return;
@@ -1346,7 +1404,7 @@ var Options = Module("options", {
var newValues = opt.parse(context.filter);
}
catch (e) {
- context.message = "Error: " + e;
+ context.message = _("error.error", e);
context.completions = [];
return;
}
@@ -1394,6 +1452,14 @@ var Options = Module("options", {
context.filters.push(function (i) curValues.indexOf(i.text) == -1);
if (op == "-")
context.filters.push(function (i) curValues.indexOf(i.text) > -1);
+
+ memoize(extra, "values", function () {
+ if (op == "+")
+ return curValues.concat(newValues);
+ if (op == "-")
+ return curValues.filter(function (v) newValues.indexOf(val) == -1);
+ return newValues;
+ });
}
let res = completer.call(opt, context, extra);