diff --git a/common/content/events.js b/common/content/events.js index 86c908cf..7d6673de 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -852,7 +852,7 @@ var Events = Module("events", { evt_obj.charCode = keyname.charCodeAt(0); } - else if (set.has(this._pseudoKeys)) { + else if (set.has(this._pseudoKeys, keyname)) { evt_obj.dactylString = "<" + this._key_key[keyname] + ">"; } else if (/mouse$/.test(keyname)) { // mouse events diff --git a/common/content/mappings.js b/common/content/mappings.js index a30aefe2..07a5e08a 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -54,6 +54,8 @@ var Map = Class("Map", { get toStringParams() [this.modes.map(function (m) m.name), this.names.map(String.quote)], + get identifier() [this.modes[0].name, this.hive.prefix + this.names[0]].join("."), + /** @property {number} A unique ID for this mapping. */ id: null, /** @property {number[]} All of the modes for which this mapping applies. */ @@ -61,7 +63,7 @@ var Map = Class("Map", { /** @property {function (number)} The function called to execute this mapping. */ action: null, /** @property {string} This mapping's description, as shown in :listkeys. */ - description: "", + description: Messages.Localized(""), /** @property {boolean} Whether this mapping accepts an argument. */ arg: false, diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 3bb93783..d60f70f2 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -363,11 +363,11 @@ function set(ary) { * @param {string} key The key to add. * @returns boolean */ -set.add = function (set, key) { +set.add = curry(function set_add(set, key) { let res = this.has(set, key); set[key] = true; return res; -} +}); /** * Returns true if the given set contains the given key. * @@ -375,8 +375,8 @@ set.add = function (set, key) { * @param {string} key The key to check. * @returns {boolean} */ -set.has = function (set, key) hasOwnProperty.call(set, key) && - propertyIsEnumerable.call(set, key); +set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) && + propertyIsEnumerable.call(set, key)); /** * Returns a new set containing the members of the first argument which * do not exist in any of the other given arguments. @@ -384,13 +384,13 @@ set.has = function (set, key) hasOwnProperty.call(set, key) && * @param {object} set The set. * @returns {object} */ -set.subtract = function (set) { +set.subtract = function set_subtract(set) { set = update({}, set); for (let i = 1; i < arguments.length; i++) for (let k in keys(arguments[i])) delete set[k]; return set; -} +}; /** * Removes an element from a set and returns true if the element was * previously contained. @@ -399,12 +399,67 @@ set.subtract = function (set) { * @param {string} key The key to remove. * @returns boolean */ -set.remove = function (set, key) { +set.remove = curry(function set_remove(set, key) { let res = set.has(set, key); delete set[key]; return res; +}); + +/** + * Curries a function to the given number of arguments. Each + * call of the resulting function returns a new function. When + * a call does not contain enough arguments to satisfy the + * required number, the resulting function is another curried + * function with previous arguments accumulated. + * + * function foo(a, b, c) [a, b, c].join(" "); + * curry(foo)(1, 2, 3) -> "1 2 3"; + * curry(foo)(4)(5, 6) -> "4 5 6"; + * curry(foo)(7)(8)(9) -> "7 8 9"; + * + * @param {function} fn The function to curry. + * @param {integer} length The number of arguments expected. + * @default fn.length + * @optional + * @param {object} self The 'this' value for the returned function. When + * omitted, the value of 'this' from the first call to the function is + * preserved. + * @optional + */ +function curry(fn, length, self, acc) { + if (length == null) + length = fn.length; + if (length == 0) + return fn; + + // Close over function with 'this' + function close(self, fn) function () fn.apply(self, Array.slice(arguments)); + + if (acc == null) + acc = []; + + return function curried() { + let args = acc.concat(Array.slice(arguments)); + + // The curried result should preserve 'this' + if (arguments.length == 0) + return close(self || this, curried); + + if (args.length >= length) + return fn.apply(self || this, args); + + return curry(fn, length, self || this, args); + }; } +if (curry.bind) + var bind = function bind(func) func.bind.apply(func, Array.slice(arguments, bind.length)); +else + var bind = function bind(func, self) { + let args = Array.slice(arguments, bind.length); + return function bound() func.apply(self, args.concat(Array.slice(arguments))); + }; + /** * Returns true if both arguments are functions and * (targ() instanceof src) would also return true. @@ -538,61 +593,6 @@ function memoize(obj, key, getter) { }); } -/** - * Curries a function to the given number of arguments. Each - * call of the resulting function returns a new function. When - * a call does not contain enough arguments to satisfy the - * required number, the resulting function is another curried - * function with previous arguments accumulated. - * - * function foo(a, b, c) [a, b, c].join(" "); - * curry(foo)(1, 2, 3) -> "1 2 3"; - * curry(foo)(4)(5, 6) -> "4 5 6"; - * curry(foo)(7)(8)(9) -> "7 8 9"; - * - * @param {function} fn The function to curry. - * @param {integer} length The number of arguments expected. - * @default fn.length - * @optional - * @param {object} self The 'this' value for the returned function. When - * omitted, the value of 'this' from the first call to the function is - * preserved. - * @optional - */ -function curry(fn, length, self, acc) { - if (length == null) - length = fn.length; - if (length == 0) - return fn; - - // Close over function with 'this' - function close(self, fn) function () fn.apply(self, Array.slice(arguments)); - - if (acc == null) - acc = []; - - return function curried() { - let args = acc.concat(Array.slice(arguments)); - - // The curried result should preserve 'this' - if (arguments.length == 0) - return close(self || this, curried); - - if (args.length >= length) - return fn.apply(self || this, args); - - return curry(fn, length, self || this, args); - }; -} - -if (curry.bind) - var bind = function bind(func) func.bind.apply(func, Array.slice(arguments, bind.length)); -else - var bind = function bind(func, self) { - let args = Array.slice(arguments, bind.length); - return function bound() func.apply(self, args.concat(Array.slice(arguments))); - }; - let sandbox = Cu.Sandbox(this); sandbox.__proto__ = this; /** diff --git a/common/modules/commands.jsm b/common/modules/commands.jsm index 5e941ccc..6c783966 100644 --- a/common/modules/commands.jsm +++ b/common/modules/commands.jsm @@ -11,8 +11,8 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("commands", { exports: ["ArgType", "Command", "Commands", "CommandOption", "Ex", "commands"], - require: ["contexts", "util"], - use: ["config", "messages", "options", "services", "template"] + require: ["contexts", "messages", "util"], + use: ["config", "options", "services", "template"] }, this); /** @@ -132,10 +132,14 @@ var Command = Class("Command", { update(this, extraInfo); if (this.options) this.options = this.options.map(CommandOption.fromArray, CommandOption); + for each (let option in this.options) + option.localeName = ["command", this.name, option.names[0]]; }, get toStringParams() [this.name, this.hive.name], + get identifier() this.hive.prefix + this.name, + get helpTag() ":" + this.name, get lastCommand() this._lastCommand || commandline.command, @@ -224,7 +228,7 @@ var Command = Class("Command", { names: null, /** @property {string} This command's description, as shown in :listcommands */ - description: "", + description: Messages.Localized(""), /** * @property {function (Args)} The function called to execute this command. */ @@ -1078,7 +1082,10 @@ var Commands = Module("commands", { context.completions = compl; } complete.advance(args.completeStart); - complete.keys = { text: "names", description: "description" }; + complete.keys = { + text: "names", + description: function (opt) messages.get(["command", params.name, opt.names[0], "description"].join("."), opt.description) + }; complete.title = ["Options"]; if (completeOpts) complete.completions = completeOpts; diff --git a/common/modules/contexts.jsm b/common/modules/contexts.jsm index 1d61d412..902d684d 100644 --- a/common/modules/contexts.jsm +++ b/common/modules/contexts.jsm @@ -434,6 +434,8 @@ var Contexts = Module("contexts", { get persist() this.group.persist, set persist(val) this.group.persist = val, + prefix: Class.memoize(function () this.name === "builtin" ? "" : this.name + ":"), + get toStringParams() [this.name] }) }, { diff --git a/common/modules/messages.jsm b/common/modules/messages.jsm index dc0f2297..bd614f0d 100644 --- a/common/modules/messages.jsm +++ b/common/modules/messages.jsm @@ -78,6 +78,48 @@ var Messages = Module("messages", { } }, { + Localized: Class("Localized", Class.Property, { + init: function init(prop, obj) { + let _prop = "localized_" + prop; + if (this.initialized) { + /* + if (config.locale === "en-US") + return { configurable: true, enumerable: true, value: null, writable: true }; + */ + + obj[_prop] = this.default; + return { + get: function get() { + let self = this; + let value = this[_prop]; + + function getter(key, default_) function getter() messages.get([name, key].join("."), default_); + + let name = [this.constructor.className.toLowerCase(), this.identifier || this.name, prop].join("."); + if (!isObject(value)) + value = messages.get(name, value) + else if (isArray(value)) + // Deprecated + iter(value).forEach(function ([k, v]) { + if (isArray(v)) + memoize(v, 1, getter(v[0], v[1])); + else + memoize(value, k, getter(k, v)); + }); + else + iter(value).forEach(function ([k, v]) { + memoize(value, k, function () messages.get([name, k].join("."), v)); + }); + + return Class.replaceProperty(this, prop, value); + }, + set: function set(val) this[_prop] = val + } + } + this.default = prop; + this.initialized = true; + } + }) }, { javascript: function initJavascript(dactyl, modules, window) { modules.JavaScript.setCompleter([this._, this.get, this.format], [ diff --git a/common/modules/options.jsm b/common/modules/options.jsm index 05c8cfdd..41a8323d 100644 --- a/common/modules/options.jsm +++ b/common/modules/options.jsm @@ -11,7 +11,7 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("options", { exports: ["Option", "Options", "ValueError", "options"], - require: ["storage"], + require: ["messages", "storage"], use: ["commands", "completion", "prefs", "services", "styles", "template", "util"] }, this); @@ -65,6 +65,11 @@ var Option = Class("Option", { this._op = Option.ops[this.type]; + // Need to trigger setter + if (extraInfo && "values" in extraInfo) + this.values = extraInfo.values; + delete extraInfo.values; + if (extraInfo) update(this, extraInfo); @@ -92,6 +97,11 @@ var Option = Class("Option", { this.globalValue = this.defaultValue; }, + /** + * @property {string} This option's description, as shown in :listoptions. + */ + description: Messages.Localized(""), + get helpTag() "'" + this.name + "'", initValue: function initValue() { @@ -291,11 +301,6 @@ var Option = Class("Option", { */ scope: 1, // Option.SCOPE_GLOBAL // XXX set to BOTH by default someday? - kstep - /** - * @property {string} This option's description, as shown in :listoptions. - */ - description: "", - cleanupValue: null, /** @@ -311,7 +316,7 @@ var Option = Class("Option", { * @property {[[string, string]]} This option's possible values. * @see CompletionContext */ - values: null, + values: Messages.Localized(null), /** * @property {function(host, values)} A function which should return a list @@ -679,7 +684,7 @@ var Option = Class("Option", { */ validateCompleter: function validateCompleter(values) { if (this.values) - var acceptable = this.values; + var acceptable = this.values.array || this.values; else { let context = CompletionContext(""); acceptable = context.fork("", 0, this, this.completer); @@ -688,7 +693,10 @@ var Option = Class("Option", { } if (this.type === "regexpmap" || this.type === "sitemap") return Array.concat(values).every(function (re) acceptable.some(function (item) item[0] == re.result)); - return Array.concat(values).every(function (value) acceptable.some(function (item) item[0] == value)); + + if (isArray(acceptable)) + acceptable = set(acceptable.map(function ([k]) k)); + return Array.concat(values).every(set.has(acceptable)); } }); diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index 6de76cdf..bd42ff17 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -161,6 +161,7 @@ var Overlay = Module("Overlay", { defineModule.time("load", null, function _load() { ["addons", "base", + "io", "commands", "completion", "config", @@ -168,7 +169,6 @@ var Overlay = Module("Overlay", { "downloads", "finder", "highlight", - "io", "javascript", "messages", "options",