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;