X-Git-Url: https://git.donarmstrong.com/?p=dactyl.git;a=blobdiff_plain;f=common%2Fmodules%2Foptions.jsm;h=a09fef257b3dc149ab1b8a53c1b20b054e2e0984;hp=1c61a40af6bd7bbd239bd79f7f60c3ae86cd11a0;hb=70740024f9c028c1fd63e1a1850ab062ff956054;hpb=718c614c183350706466e22939d0101ca4c87efe 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);