diff options
-rw-r--r-- | common/content/autocommands.js | 2 | ||||
-rw-r--r-- | common/content/commands.js | 395 | ||||
-rw-r--r-- | common/content/contexts.js | 43 | ||||
-rw-r--r-- | common/content/mappings.js | 5 | ||||
-rw-r--r-- | common/modules/io.jsm | 2 | ||||
-rw-r--r-- | common/modules/styles.jsm | 2 |
6 files changed, 249 insertions, 200 deletions
diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 0f01779a..81e38fac 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -76,7 +76,7 @@ var AutoCommands = Module("autocommands", { this.user = contexts.hives.autocmd.user; }, - hives: Group.Hive("autocmd", AutoCmdHive), + hives: Group.Hives("autocmd", AutoCmdHive), get activeHives() contexts.activeGroups("autocmd").map(function (h) h.autocmd).filter(function (h) h._store.length), diff --git a/common/content/commands.js b/common/content/commands.js index 644a14a4..981ea8cd 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -127,6 +127,8 @@ var Command = Class("Command", { this.options = this.options.map(CommandOption.fromArray, CommandOption); }, + get toStringParams() [this.name, this.hive.group.name], + get helpTag() ":" + this.name, get lastCommand() this._lastCommand || commandline.command, @@ -170,12 +172,8 @@ var Command = Class("Command", { * @param {string} name The candidate name. * @returns {boolean} */ - hasName: function (name) { - return this.specs.some(function (spec) { - let [, head, tail] = /([^[]+)(?:\[(.*)])?/.exec(spec); - return name.indexOf(head) == 0 && (head + (tail || "")).indexOf(name) == 0; - }); - }, + hasName: function (name) this.parsedSpecs.some( + function ([long, short]) name.indexOf(short) == 0 && long.indexOf(name) == 0), /** * A helper function to parse an argument string. @@ -399,109 +397,145 @@ var ex = { __noSuchMethod__: function (meth, args) this._run(meth).apply(this, args) }; -/** - * @instance commands - */ -var Commands = Module("commands", { - init: function () { - this._exCommands = []; - this._exMap = {}; +var CommandHive = Class("CommandHive", { + init: function init(group) { + this.group = group; + this._map = {}; + this._list = []; }, - /** - * @property Indicates that no count was specified for this - * command invocation. - * @final - */ - COUNT_NONE: null, - /** - * @property {number} Indicates that the full buffer range (1,$) was - * specified for this command invocation. - * @final - */ - // FIXME: this isn't a count at all - COUNT_ALL: -2, // :%... + get toStringParams() [this.group.name], + + get builtin() this.group.builtin, /** @property {Iterator(Command)} @private */ - __iterator__: function () { - let sorted = this._exCommands.sort(function (a, b) a.name > b.name); - return array.iterValues(sorted); - }, - iterator: function () { - let sorted = this._exCommands.sort(function (a, b) a.serialGroup - b.serialGroup || a.name > b.name); - return array.iterValues(sorted); - }, + __iterator__: function () array.iterValues(this._list.sort(function (a, b) a.name > b.name)), /** @property {string} The last executed Ex command line. */ repeat: null, - _addCommand: function (args, replace) { - if (!args[3]) - args[3] = {}; - args[3].definedAt = Commands.getCaller(Components.stack.caller.caller); + /** + * Adds a new command. + * + * @param {string[]} names The names by which this command can be + * invoked. The first name specified is the command's canonical + * name. + * @param {string} description A description of the command. + * @param {function} action The action invoked by this command. + * @param {Object} extra An optional extra configuration hash. + * @optional + */ + add: function (names, description, action, extra, replace) { + extra = extra || {}; + if (!extra.definedAt) + extra.definedAt = Commands.getCaller(Components.stack.caller); + + extra.hive = this; + extra.parsedSpecs = Command.parseSpecs(names); - let names = array.flatten(Command.parseSpecs(args[0])); - args.parsedSpecs = names; + let names = array.flatten(extra.parsedSpecs); + let name = names[0]; - dactyl.assert(!names.some(function (name) name in this._exMap && !this._exMap[name].user, this), - "E182: Can't replace non-user command: " + args[0][0]); + dactyl.assert(!names.some(function (name) name in commands.builtin._map), + "E182: Can't replace non-user command: " + name); - if (!replace || !args[3].user) - dactyl.assert(!names.some(function (name) name in this._exMap, this), - "Not replacing command " + args[0]); + dactyl.assert(replace || names.every(function (name) !(name in this._map), this), + "Not replacing command " + name); for (let name in values(names)) { ex.__defineGetter__(name, function () this._run(name)); - if (name in this._exMap) - commands.removeUserCommand(name); + if (name in this._map) + this.remove(name); } - let name = names[0]; - let closure = function () commands._exMap[name]; - - memoize(this._exMap, name, function () Command.apply(null, args)); - memoize(this._exCommands, this._exCommands.length, closure); + let self = this; + let closure = function () self._map[name]; + memoize(this._map, name, function () Command(names, description, action, extra)); + memoize(this._list, this._list.length, closure); for (let alias in values(names.slice(1))) - memoize(this._exMap, alias, closure); + memoize(this._map, alias, closure); return name; }, + _add: function (names, description, action, extra, replace) { + extra = extra || {}; + extra.definedAt = Commands.getCaller(Components.stack.caller.caller); + return this.add.apply(this, arguments); + }, /** - * Adds a new default command. + * Returns the command with matching *name*. * - * @param {string[]} names The names by which this command can be - * invoked. The first name specified is the command's canonical - * name. - * @param {string} description A description of the command. - * @param {function} action The action invoked by this command. - * @param {Object} extra An optional extra configuration hash. - * @optional + * @param {string} name The name of the command to return. This can be + * any of the command's names. + * @param {boolean} full If true, only return a command if one of + * its names matches *name* exactly. + * @returns {Command} */ - add: function (names, description, action, extra) { - return this._addCommand([names, description, action, extra], false); - }, + get: function (name, full) this._map[name] + || !full && array.nth(this._list, function (cmd) cmd.hasName(name), 0) + || null, /** - * Adds a new user-defined command. + * Remove the user-defined command with matching *name*. * - * @param {string[]} names The names by which this command can be - * invoked. The first name specified is the command's canonical - * name. - * @param {string} description A description of the command. - * @param {function} action The action invoked by this command. - * @param {Object} extra An optional extra configuration hash. - * @param {boolean} replace Overwrite an existing command with the same - * canonical name. + * @param {string} name The name of the command to remove. This can be + * any of the command's names. */ - addUserCommand: function (names, description, action, extra, replace) { - extra = extra || {}; - extra.user = true; + remove: function (name) { + dactyl.assert(this.group !== contexts.default, + "Cannot delete non-user commands"); + + let cmd = this.get(name); + this._list = this._list.filter(function (c) c !== cmd); + for (let name in values(cmd.names)) + delete this._map[name]; + } +}); - return this._addCommand([names, description, action, extra], replace); +/** + * @instance commands + */ +var Commands = Module("commands", { + init: function () { + this.user = contexts.hives.commands.user; + this.builtin = contexts.hives.commands.builtin; }, + hives: Group.Hives("commands", CommandHive), + + get allHives() contexts.allGroups.commands, + + get userHives() this.allHives.filter(function (h) h !== this.builtin, this), + + /** + * @property Indicates that no count was specified for this + * command invocation. + * @final + */ + COUNT_NONE: null, + /** + * @property {number} Indicates that the full buffer range (1,$) was + * specified for this command invocation. + * @final + */ + // FIXME: this isn't a count at all + COUNT_ALL: -2, // :%... + + /** @property {Iterator(Command)} @private */ + iterator: function () iter.apply(null, this.hives) + .sort(function (a, b) a.serialGroup - b.serialGroup || a.name > b.name) + .iterValues(), + + /** @property {string} The last executed Ex command line. */ + repeat: null, + + add: function () this.builtin._add.apply(this.builtin, arguments), + addUserCommand: deprecated("commands.user.add", { get: function addUserCommand() this.user.closure._add }), + getUserCommands: deprecated("iter(commands.user)", function getUserCommands() iter(commands.user).toArray()), + removeUserCommand: deprecated("commands.user.remove", { get: function removeUserCommand() this.user.closure.remove }), + /** * Returns the specified command invocation object serialized to * an executable Ex command string. @@ -536,6 +570,60 @@ var Commands = Module("commands", { }, /** + * Returns the command with matching *name*. + * + * @param {string} name The name of the command to return. This can be + * any of the command's names. + * @returns {Command} + */ + get: function (name, full) iter(this.hives).map(function ([i, hive]) hive.get(name, full)) + .nth(util.identity, 0), + + /** + * Displays a list of user-defined commands. + */ + list: function list() { + function completerToString(completer) { + if (completer) + return [k for ([k, v] in Iterator(config.completers)) if (completer == completion.closure[v])][0] || "custom"; + return ""; + } + + if (!commands.userHives.some(function (h) h._list.length)) + dactyl.echomsg("No user-defined commands found"); + else + commandline.commandOutput( + <table> + <tr highlight="Title"> + <td/> + <td style="padding-right: 1em;"></td> + <td style="padding-right: 1ex;">Name</td> + <td style="padding-right: 1ex;">Args</td> + <td style="padding-right: 1ex;">Range</td> + <td style="padding-right: 1ex;">Complete</td> + <td style="padding-right: 1ex;">Definition</td> + </tr> + <col style="min-width: 6em; padding-right: 1em;"/> + { + template.map(commands.userHives, function (hive) let (i = 0) + <tr style="height: .5ex;"/> + + template.map(hive, function (cmd) + template.map(cmd.names, function (name) + <tr> + <td highlight="Title">{!i++ ? hive.group.name : ""}</td> + <td>{cmd.bang ? "!" : " "}</td> + <td>{cmd.name}</td> + <td>{cmd.argCount}</td> + <td>{cmd.count ? "0c" : ""}</td> + <td>{completerToString(cmd.completer)}</td> + <td>{cmd.replacementText || "function () { ... }"}</td> + </tr>)) + + <tr style="height: .5ex;"/>) + } + </table>); + }, + + /** * Executes an Ex command script. * * @param {string} string A string containing the commands to execute. @@ -604,35 +692,6 @@ var Commands = Module("commands", { }, /** - * Returns the command with matching *name*. - * - * @param {string} name The name of the command to return. This can be - * any of the command's names. - * @returns {Command} - */ - get: function (name, full) - this._exMap[name] || !full && array.nth(this._exCommands, function (cmd) cmd.hasName(name), 0) || null, - - /** - * Returns the user-defined command with matching *name*. - * - * @param {string} name The name of the command to return. This can be - * any of the command's names. - * @returns {Command} - */ - getUserCommand: function (name) - array.nth(this._exCommands, function (cmd) cmd.user && cmd.hasName(name), 0) || null, - - /** - * Returns all user-defined commands. - * - * @returns {Command[]} - */ - getUserCommands: function () { - return this._exCommands.filter(function (cmd) cmd.user); - }, - - /** * Returns true if a command invocation contains a URL referring to the * domain *host*. * @@ -1121,21 +1180,8 @@ var Commands = Module("commands", { get complQuote() Commands.complQuote, /** @property */ - get quoteArg() Commands.quoteArg, // XXX: better somewhere else? + get quoteArg() Commands.quoteArg // XXX: better somewhere else? - /** - * Remove the user-defined command with matching *name*. - * - * @param {string} name The name of the command to remove. This can be - * any of the command's names. - */ - removeUserCommand: function (name) { - let cmd = this.get(name); - dactyl.assert(cmd.user, "E184: No such user-defined command: " + name); - this._exCommands = this._exCommands.filter(function (c) c !== cmd); - for (let name in values(cmd.names)) - delete this._exMap[name]; - } }, { /** * Returns a frame object describing the currently executing @@ -1199,7 +1245,7 @@ var Commands = Module("commands", { completion.command = function command(context) { context.title = ["Command"]; context.keys = { text: "longNames", description: "description" }; - context.completions = [k for (k in commands)]; + context.generate = function () commands.hives.map(function (h) h._list).flatten(); }; // provides completions for ex commands, including their arguments @@ -1254,8 +1300,6 @@ var Commands = Module("commands", { }, commands: function () { - let completerMap = config.completers; - // TODO: Vim allows commands to be defined without {rep} if there are {attr}s // specified - useful? commands.add(["com[mand]"], @@ -1266,7 +1310,9 @@ var Commands = Module("commands", { dactyl.assert(!cmd || cmd.split(",").every(commands.validName.closure.test), "E182: Invalid command name"); - if (args.literalArg) { + if (!args.literalArg) + commands.list(); + else { let completer = args["-complete"]; let completerFunc = null; // default to no completion for user commands @@ -1295,10 +1341,10 @@ var Commands = Module("commands", { }; } else - completerFunc = function (context) completion.closure[completerMap[completer]](context); + completerFunc = function (context) completion.closure[config.completers[completer]](context); } - let added = commands.addUserCommand(cmd.split(","), + let added = args["-group"].add(cmd.split(","), args["-description"], contexts.bindMacro(args, "-ex", function makeParams(args, modifiers) ({ @@ -1323,29 +1369,6 @@ var Commands = Module("commands", { if (!added) dactyl.echoerr("E174: Command already exists: add ! to replace it"); } - else { - let completerToString = function completerToString(completer) { - if (completer) - return [k for ([k, v] in Iterator(completerMap)) if (completer == completion.closure[v])][0] || "custom"; - return ""; - } - - // TODO: perhaps we shouldn't allow options in a list call but just ignore them for now - let cmds = commands._exCommands.filter(function (c) c.user && (!cmd || c.name.match("^" + cmd))); - - if (cmds.length > 0) - commandline.commandOutput( - template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"], - ([cmd.bang ? "!" : " ", - cmd.name, - cmd.argCount, - cmd.count ? "0c" : "", - completerToString(cmd.completer), - cmd.replacementText || "function () { ... }"] - for ([, cmd] in Iterator(cmds))))); - else - dactyl.echomsg("No user-defined commands found"); - } }, { bang: true, completer: function (context, args) { @@ -1362,22 +1385,27 @@ var Commands = Module("commands", { // TODO: "E180: invalid complete value: " + arg names: ["-complete", "-C"], description: "The argument completion function", - completer: function (context) [[k, ""] for ([k, v] in Iterator(completerMap))], + completer: function (context) [[k, ""] for ([k, v] in Iterator(config.completers))], type: CommandOption.STRING, - validator: function (arg) arg in completerMap || /^custom,/.test(arg), - }, { + validator: function (arg) arg in config.completers || /^custom,/.test(arg), + }, + { names: ["-description", "-desc", "-d"], description: "A user-visible description of the command", default: "User-defined command", type: CommandOption.STRING - }, { + }, + contexts.GroupFlag("commands"), + { names: ["-javascript", "-js", "-j"], description: "Execute the definition as JavaScript rather than Ex commands" - }, { + }, + { names: ["-literal", "-l"], description: "Process the nth ignoring any quoting or meta characters", type: CommandOption.INT - }, { + }, + { names: ["-nargs", "-a"], description: "The allowed number of arguments", completer: [["0", "No arguments are allowed (default)"], @@ -1388,31 +1416,36 @@ var Commands = Module("commands", { default: "0", type: CommandOption.STRING, validator: function (arg) /^[01*?+]$/.test(arg) - }, { + }, + { names: ["-nopersist", "-n"], description: "Do not save this command to an auto-generated RC file" } ], literal: 1, - serialize: function () [ { - command: this.name, - bang: true, - options: iter([v, typeof cmd[k] == "boolean" ? null : cmd[k]] - // FIXME: this map is expressed multiple times - for ([k, v] in Iterator({ - argCount: "-nargs", - bang: "-bang", - count: "-count", - description: "-description" - })) - if (cmd[k])).toObject(), - arguments: [cmd.name], - literalArg: cmd.action, - ignoreDefaults: true - } - for ([k, cmd] in Iterator(commands._exCommands)) - if (cmd.user && cmd.persist) - ] + + serialize: function () array(commands.userHives) + .filter(function (h) h.group.persist) + .map(function (hive) [ + { + command: this.name, + bang: true, + options: iter([v, typeof cmd[k] == "boolean" ? null : cmd[k]] + // FIXME: this map is expressed multiple times + for ([k, v] in Iterator({ + argCount: "-nargs", + bang: "-bang", + count: "-count", + description: "-description" + })) + if (cmd[k])).toObject(), + arguments: [cmd.name], + literalArg: cmd.action, + ignoreDefaults: true + } + for (cmd in hive) if (cmd.persist) + ], this) + .flatten().array }); commands.add(["comc[lear]"], @@ -1449,8 +1482,14 @@ var Commands = Module("commands", { name: ["listc[ommands]", "lc"], description: "List all Ex commands along with their short descriptions", index: "ex-cmd", - iterate: function (args) commands, + iterate: function (args) commands.iterator().map(function (cmd) ({ + __proto__: cmd, + columns: [ + cmd.hive == commands.builtin ? "" : <span highlight="Object" style="padding-right: 1em;">{cmd.hive.group.name}</span> + ] + })), format: { + headings: ["Command", "Group", "Description"], description: function (cmd) template.linkifyHelp(cmd.description + (cmd.replacementText ? ": " + cmd.action : "")), help: function (cmd) ":" + cmd.name } @@ -1471,8 +1510,8 @@ var Commands = Module("commands", { }); }, javascript: function () { - JavaScript.setCompleter([commands.get, commands.removeUserCommand], - [function () ([c.name, c.description] for (c in commands))]); + JavaScript.setCompleter([commands.user.get, commands.user.remove], + [function () ([c.name, c.description] for (c in this))]); }, mappings: function () { mappings.add(config.browserModes, diff --git a/common/content/contexts.js b/common/content/contexts.js index 2fc81bba..e3f2a0d4 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -58,7 +58,7 @@ var Group = Class("Group", { hiveMap: {}, - Hive: Class("Hive", Class.Property, { + Hives: Class("Hives", Class.Property, { init: function init(name, constructor) { const self = this; if (this.Group) @@ -116,13 +116,18 @@ var Contexts = Module("contexts", { get hives() Group.hiveMap, - addGroup: function addGroup(name, description, filter, persist) { - this.removeGroup(name); - - let group = Group(name, description, filter, persist); - this.groupList.unshift(group); - this.groupMap[name] = group; - this.hiveProto.__defineGetter__(name, function () group[this._hive]); + addGroup: function addGroup(name, description, filter, persist, replace) { + if (replace) + this.removeGroup(name); + + let group = this.groupMap[name]; + if (!group) { + group = Group(name, description, filter, persist); + this.groupList.unshift(group); + this.groupMap[name] = group; + this.hiveProto.__defineGetter__(name, function () group[this._hive]); + delete this.groups; + } return group; }, @@ -248,7 +253,8 @@ var Contexts = Module("contexts", { delete plugins[this.NAME]; if (plugins[this.PATH] === this) delete plugins[this.PATH]; - contexts.removeGroup(this.GROUP); + if (!this.GROUP.builtin) + contexts.removeGroup(this.GROUP); }) }); Class.replaceProperty(plugins, file.path, self); @@ -264,12 +270,15 @@ var Contexts = Module("contexts", { let path = isRuntime ? file.getRelativeDescriptor(isRuntime) : file.path; - self.GROUP = group || - contexts.addGroup((isRuntime ? "" : "script-") + - commands.nameRegexp.iterate(path.replace(/\..*/, "")) - .join("-"), - "Script group for " + file.path, - null, false); + if (!group) + group = contexts.addGroup((isRuntime ? "" : "script-") + + commands.nameRegexp.iterate(path.replace(/\..*/, "")) + .join("-"), + "Script group for " + file.path, + null, false); + + Class.replaceProperty(self, "GROUP", group); + Class.replaceProperty(self, "group", group); return plugins.contexts[file.path] = self; }, @@ -297,8 +306,8 @@ var Contexts = Module("contexts", { dactyl.assert(group || name, "No current group"); let filter = Group.compileFilter(args["-locations"]); - if (!group) - group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"]); + if (!group || args.bang) + group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"], args.bang); else if (!group.builtin) { if (args.has("-locations")) group.filter = filter; diff --git a/common/content/mappings.js b/common/content/mappings.js index c1171158..3f05549a 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -307,7 +307,7 @@ var Mappings = Module("mappings", { repeat: Modes.boundProperty(), - hives: Group.Hive("mappings", MapHive), + hives: Group.Hives("mappings", MapHive), get allHives() contexts.allGroups.mappings, @@ -668,6 +668,7 @@ var Mappings = Module("mappings", { iterate: function (args) { let mainMode = this.getMode(args); let seen = {}; + // Bloody hell. --Kris for (let mode in values([mainMode].concat(mainMode.bases))) for (let hive in mappings.hives.iterValues()) for (let map in array.iterValues(hive.getStack(mode))) @@ -677,7 +678,7 @@ var Mappings = Module("mappings", { name: name, columns: [ mode == mainMode ? "" : <span highlight="Object" style="padding-right: 1em;">{mode.name}</span>, - hive.name == "builtin" ? "" : <span highlight="Object" style="padding-right: 1em;">{hive.name}</span> + hive == mappings.builtin ? "" : <span highlight="Object" style="padding-right: 1em;">{hive.group.name}</span> ], __proto__: map }; diff --git a/common/modules/io.jsm b/common/modules/io.jsm index f83987a7..f4abbfdf 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -612,7 +612,7 @@ var IO = Module("io", { "E189: " + file.path.quote() + " exists (add ! to override)"); // TODO: Use a set/specifiable list here: - let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator()) if (cmd.serialize)]; + let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator(true)) if (cmd.serialize)]; lines = array.flatten(lines); lines.unshift('"' + config.version + "\n"); diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index 3c240ef6..a7259259 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -600,7 +600,7 @@ var Styles = Module("Styles", { }); }, contexts: function (dactyl, modules, window) { - modules.Group.Hive("styles", function (group) styles.addHive(group.name)); + modules.Group.Hives("styles", function (group) styles.addHive(group.name)); }, completion: function (dactyl, modules, window) { const names = Array.slice(util.computedStyle(window.document.createElement("div"))); |