]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/commands.jsm
Import 1.0b7.1 supporting Firefox up to 8.*
[dactyl.git] / common / modules / commands.jsm
index 1036f20cf09b7bf2263bf27bdaa9dfe1441daf90..7d7093e99a41befb806ccd157b0a3dd2b3cb6c86 100644 (file)
@@ -94,7 +94,7 @@ update(CommandOption, {
  * A class representing Ex commands. Instances are created by
  * the {@link Commands} class.
  *
- * @param {string[]} specs The names by which this command can be invoked.
+ * @param {[string]} specs The names by which this command can be invoked.
  *     These are specified in the form "com[mand]" where "com" is a unique
  *     command name prefix.
  * @param {string} description A short one line description of the command.
@@ -129,7 +129,7 @@ var Command = Class("Command", {
         this.action = action;
 
         if (extraInfo)
-            update(this, extraInfo);
+            this.update(extraInfo);
         if (this.options)
             this.options = this.options.map(CommandOption.fromArray, CommandOption);
         for each (let option in this.options)
@@ -142,7 +142,7 @@ var Command = Class("Command", {
 
     get helpTag() ":" + this.name,
 
-    get lastCommand() this._lastCommand || commandline.command,
+    get lastCommand() this._lastCommand || this.modules.commandline.command,
     set lastCommand(val) { this._lastCommand = val; },
 
     /**
@@ -155,16 +155,13 @@ var Command = Class("Command", {
         const { dactyl } = this.modules;
 
         let context = args.context;
-        if (this.deprecated && !set.add(this.complained, context ? context.file : "[Command Line]")) {
-            let loc = contexts.context ? context.file + ":" + context.line + ": " : "";
-            dactyl.echoerr(loc + ":" + this.name + " is deprecated" +
-                           (isString(this.deprecated) ? ": " + this.deprecated : ""));
-        }
+        if (this.deprecated)
+            this.warn(context, "deprecated", _("warn.deprecated", ":" + this.name, this.deprecated));
 
         modifiers = modifiers || {};
 
         if (args.count != null && !this.count)
-            throw FailedAssertion(_("command.noRange"));
+            throw FailedAssertion(_("command.noCount"));
         if (args.bang && !this.bang)
             throw FailedAssertion(_("command.noBang"));
 
@@ -212,23 +209,27 @@ var Command = Class("Command", {
     complained: Class.memoize(function () ({})),
 
     /**
-     * @property {string[]} All of this command's name specs. e.g., "com[mand]"
+     * @property {[string]} All of this command's name specs. e.g., "com[mand]"
      */
     specs: null,
-    /** @property {string[]} All of this command's short names, e.g., "com" */
+    /** @property {[string]} All of this command's short names, e.g., "com" */
     shortNames: null,
     /**
-     * @property {string[]} All of this command's long names, e.g., "command"
+     * @property {[string]} All of this command's long names, e.g., "command"
      */
     longNames: null,
 
     /** @property {string} The command's canonical name. */
     name: null,
-    /** @property {string[]} All of this command's long and short names. */
+    /** @property {[string]} All of this command's long and short names. */
     names: null,
 
     /** @property {string} This command's description, as shown in :listcommands */
     description: Messages.Localized(""),
+
+    /** @property {string|null} If set, the deprecation message for this command. */
+    deprecated: Messages.Localized(null),
+
     /**
      * @property {function (Args)} The function called to execute this command.
      */
@@ -295,7 +296,7 @@ var Command = Class("Command", {
 
                 explicitOpts: Class.memoize(function () ({})),
 
-                has: function AP_has(opt) set.has(this.explicitOpts, opt) || typeof opt === "number" && set.has(this, opt),
+                has: function AP_has(opt) Set.has(this.explicitOpts, opt) || typeof opt === "number" && Set.has(this, opt),
 
                 get literalArg() this.command.literal != null && this[this.command.literal] || "",
 
@@ -309,7 +310,7 @@ var Command = Class("Command", {
 
                         util.assert((this.length == 0 || this.command.argCount !== "0") &&
                                     (this.length <= 1 || !/^[01?]$/.test(this.command.argCount)),
-                                    _("error.trailing"));
+                                    _("error.trailingCharacters"));
                     }
                 }
         });
@@ -356,7 +357,21 @@ var Command = Class("Command", {
      * @property {string} For commands defined via :command, contains the Ex
      *     command line to be executed upon invocation.
      */
-    replacementText: null
+    replacementText: null,
+
+    /**
+     * Warns of a misuse of this command once per warning type per file.
+     *
+     * @param {object} context The calling context.
+     * @param {string} type The type of warning.
+     * @param {string} warning The warning message.
+     */
+    warn: function warn(context, type, message) {
+        let loc = !context ? "" : [context.file, context.line, " "].join(":");
+
+        if (!Set.add(this.complained, type + ":" + (context ? context.file : "[Command Line]")))
+            this.modules.dactyl.warn(loc + message);
+    }
 }, {
     // TODO: do we really need more than longNames as a convenience anyway?
     /**
@@ -395,10 +410,12 @@ var Ex = Module("Ex", {
                 else {
                     let opt = cmd.optionMap["-" + k];
                     let val = opt.type && opt.type.parse(v);
+
                     util.assert(val != null && (typeof val !== "number" || !isNaN(val)),
                                 _("option.noSuch", k));
-                    Class.replaceProperty(args, opt.names[0], val);
-                    args.explicitOpts[opt.names[0]] = val;
+
+                    Class.replaceProperty(res, opt.names[0], val);
+                    res.explicitOpts[opt.names[0]] = val;
                 }
         for (let [i, val] in array.iterItems(args))
             res[i] = String(val);
@@ -447,7 +464,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, {
      * Adds a new command to the builtin hive. Accessible only to core
      * dactyl code. Plugins should use group.commands.add instead.
      *
-     * @param {string[]} names The names by which this command can be
+     * @param {[string]} specs 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.
@@ -455,7 +472,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, {
      * @param {Object} extra An optional extra configuration hash.
      * @optional
      */
-    add: function add(names, description, action, extra, replace) {
+    add: function add(specs, description, action, extra, replace) {
         const { commands, contexts } = this.modules;
 
         extra = extra || {};
@@ -463,7 +480,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, {
             extra.definedAt = contexts.getCaller(Components.stack.caller);
 
         extra.hive = this;
-        extra.parsedSpecs = Command.parseSpecs(names);
+        extra.parsedSpecs = Command.parseSpecs(specs);
 
         let names = array.flatten(extra.parsedSpecs);
         let name = names[0];
@@ -483,7 +500,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, {
         let self = this;
         let closure = function () self._map[name];
 
-        memoize(this._map, name, function () commands.Command(names, description, action, extra));
+        memoize(this._map, name, function () commands.Command(specs, description, action, extra));
         memoize(this._list, this._list.length, closure);
         for (let alias in values(names.slice(1)))
             memoize(this._map, alias, closure);
@@ -624,48 +641,58 @@ var Commands = Module("commands", {
         },
 
         /**
-         * Displays a list of user-defined commands.
+         * Lists all user-defined commands matching *filter* and optionally
+         * *hives*.
+         *
+         * @param {string} filter Limits the list to those commands with a name
+         *     matching this anchored substring.
+         * @param {[Hive]} hives List of hives.
+         * @optional
          */
-        list: function list() {
+        list: function list(filter, hives) {
             const { commandline, completion } = this.modules;
             function completerToString(completer) {
                 if (completer)
                     return [k for ([k, v] in Iterator(config.completers)) if (completer == completion.closure[v])][0] || "custom";
                 return "";
             }
+            // TODO: allow matching of aliases?
+            function cmds(hive) hive._list.filter(function (cmd) cmd.name.indexOf(filter || "") == 0)
+
+            let hives = (hives || this.userHives).map(function (h) [h, cmds(h)]).filter(function ([h, c]) c.length);
+
+            let list = <table>
+                <tr highlight="Title">
+                    <td/>
+                    <td style="padding-right: 1em;"></td>
+                    <td style="padding-right: 1ex;">{_("title.Name")}</td>
+                    <td style="padding-right: 1ex;">{_("title.Args")}</td>
+                    <td style="padding-right: 1ex;">{_("title.Range")}</td>
+                    <td style="padding-right: 1ex;">{_("title.Complete")}</td>
+                    <td style="padding-right: 1ex;">{_("title.Definition")}</td>
+                </tr>
+                <col style="min-width: 6em; padding-right: 1em;"/>
+                {
+                    template.map(hives, function ([hive, cmds]) let (i = 0)
+                        <tr style="height: .5ex;"/> +
+                        template.map(cmds, function (cmd)
+                            <tr>
+                                <td highlight="Title">{!i++ ? hive.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>;
 
-            if (!this.userHives.some(function (h) h._list.length))
+            if (list.*.length() === list.text().length() + 2)
                 dactyl.echomsg(_("command.none"));
             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(this.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.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>);
+                commandline.commandOutput(list);
         }
     }),
 
@@ -848,9 +875,9 @@ var Commands = Module("commands", {
 
             let [count, arg, quote] = Commands.parseArg(str, null, _keepQuotes);
             if (quote == "\\" && !complete)
-                return [, , , "Trailing \\"];
+                return [, , , _("error.trailingCharacters", "\\")];
             if (quote && !complete)
-                return [, , , "E114: Missing quote: " + quote];
+                return [, , , _("error.missingQuote", quote)];
             return [count, arg, quote];
         }
 
@@ -881,7 +908,7 @@ var Commands = Module("commands", {
             let matchOpts = function matchOpts(arg) {
                 // Push possible option matches into completions
                 if (complete && !onlyArgumentsRemaining)
-                    completeOpts = options.filter(function (opt) opt.multiple || !set.has(args, opt.names[0]));
+                    completeOpts = options.filter(function (opt) opt.multiple || !Set.has(args, opt.names[0]));
             };
             let resetCompletions = function resetCompletions() {
                 completeOpts = null;
@@ -946,7 +973,7 @@ var Commands = Module("commands", {
                                     util.assert(!error, error);
 
                                     // if we add the argument to an option after a space, it MUST not be empty
-                                    if (sep != "=" && !quote && arg.length == 0)
+                                    if (sep != "=" && !quote && arg.length == 0 && !complete)
                                         arg = null;
 
                                     count++; // to compensate the "=" character
@@ -1336,7 +1363,7 @@ var Commands = Module("commands", {
             // dynamically get completions as specified with the command's completer function
             context.highlight();
             if (!command) {
-                context.message = "No such command: " + match.cmd;
+                context.message = _("command.noSuch", match.cmd);
                 context.highlight(0, match.cmd.length, "SPELLCHECK");
                 return;
             }
@@ -1354,6 +1381,7 @@ var Commands = Module("commands", {
             }
             catch (e) {
                 util.reportError(e);
+                cmdContext.message = _("error.error", e);
             }
         };
 
@@ -1367,8 +1395,6 @@ var Commands = Module("commands", {
     commands: function initCommands(dactyl, modules, window) {
         const { commands, contexts } = modules;
 
-        // TODO: Vim allows commands to be defined without {rep} if there are {attr}s
-        // specified - useful?
         commands.add(["com[mand]"],
             "List or define commands",
             function (args) {
@@ -1377,33 +1403,26 @@ var Commands = Module("commands", {
                 util.assert(!cmd || cmd.split(",").every(commands.validName.closure.test),
                             _("command.invalidName", cmd));
 
-                if (!args.literalArg)
-                    commands.list();
+                if (args.length <= 1)
+                    commands.list(cmd, args.explicitOpts["-group"] ? [args["-group"]] : null);
                 else {
                     util.assert(args["-group"].modifiable,
                                 _("group.cantChangeBuiltin", _("command.commands")));
 
-                    let completer  = args["-complete"];
+                    let completer = args["-complete"];
                     let completerFunc = null; // default to no completion for user commands
 
                     if (completer) {
                         if (/^custom,/.test(completer)) {
                             completer = completer.substr(7);
 
-                            let context = update({}, contexts.context || {});
+                            if (contexts.context)
+                                var ctxt = update({}, contexts.context || {});
                             completerFunc = function (context) {
-                                try {
-                                    var result = contextswithSavedValues(["context"], function () {
-                                        contexts.context = context;
-                                        return dactyl.userEval(completer);
-                                    });
-                                }
-                                catch (e) {
-                                    dactyl.echo(":" + this.name + " ...");
-                                    dactyl.echoerr(_("command.unknownCompleter", completer));
-                                    dactyl.log(e);
-                                    return undefined;
-                                }
+                                var result = contexts.withSavedValues(["context"], function () {
+                                    contexts.context = ctxt;
+                                    return dactyl.userEval(completer);
+                                });
                                 if (callable(result))
                                     return result.apply(this, Array.slice(arguments));
                                 else
@@ -1473,7 +1492,7 @@ var Commands = Module("commands", {
                     },
                     {
                         names: ["-literal", "-l"],
-                        description: "Process the nth ignoring any quoting or meta characters",
+                        description: "Process the specified argument ignoring any quoting or meta characters",
                         type: CommandOption.INT
                     },
                     {
@@ -1558,7 +1577,7 @@ var Commands = Module("commands", {
                 ]
             })),
             iterateIndex: function (args) let (tags = services["dactyl:"].HELP_TAGS)
-                this.iterate(args).filter(function (cmd) cmd.hive === commands.builtin || set.has(cmd.helpTag)),
+                this.iterate(args).filter(function (cmd) cmd.hive === commands.builtin || Set.has(tags, cmd.helpTag)),
             format: {
                 headings: ["Command", "Group", "Description"],
                 description: function (cmd) template.linkifyHelp(cmd.description + (cmd.replacementText ? ": " + cmd.action : "")),
@@ -1576,9 +1595,10 @@ var Commands = Module("commands", {
                 dactyl.clipboardWrite(res);
 
                 let lines = res.split("\n").length;
-                dactyl.echomsg("Yanked " + lines + " line" + (lines == 1 ? "" : "s"));
+                dactyl.echomsg(_("command.yank.yankedLine" + (lines == 1 ? "" : "s"), lines));
             },
             {
+                argCount: "1",
                 completer: function (context) modules.completion[/^:/.test(context.filter) ? "ex" : "javascript"](context),
                 literal: 0
             });