X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fcommands.jsm;h=7d7093e99a41befb806ccd157b0a3dd2b3cb6c86;hb=70740024f9c028c1fd63e1a1850ab062ff956054;hp=1036f20cf09b7bf2263bf27bdaa9dfe1441daf90;hpb=eeed0be1a8abf7e3c97f43b63c1d595e940fef21;p=dactyl.git diff --git a/common/modules/commands.jsm b/common/modules/commands.jsm index 1036f20..7d7093e 100644 --- a/common/modules/commands.jsm +++ b/common/modules/commands.jsm @@ -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 = + + + + + + + + + + { + template.map(hives, function ([hive, cmds]) let (i = 0) + + + template.map(cmds, function (cmd) + + + + + + + + + ) + + ) + } +
+ {_("title.Name")}{_("title.Args")}{_("title.Range")}{_("title.Complete")}{_("title.Definition")}
{!i++ ? hive.name : ""}{cmd.bang ? "!" : " "}{cmd.name}{cmd.argCount}{cmd.count ? "0c" : ""}{completerToString(cmd.completer)}{cmd.replacementText || "function () { ... }"}
; - if (!this.userHives.some(function (h) h._list.length)) + if (list.*.length() === list.text().length() + 2) dactyl.echomsg(_("command.none")); else - commandline.commandOutput( - - - - - - - - - - - { - template.map(this.userHives, function (hive) let (i = 0) - + - template.map(hive, function (cmd) - template.map(cmd.names, function (name) - - - - - - - - - )) + - ) - } -
- NameArgsRangeCompleteDefinition
{!i++ ? hive.name : ""}{cmd.bang ? "!" : " "}{cmd.name}{cmd.argCount}{cmd.count ? "0c" : ""}{completerToString(cmd.completer)}{cmd.replacementText || "function () { ... }"}
); + 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 });