X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fcommands.jsm;fp=common%2Fmodules%2Fcommands.jsm;h=7d7093e99a41befb806ccd157b0a3dd2b3cb6c86;hb=70740024f9c028c1fd63e1a1850ab062ff956054;hp=1036f20cf09b7bf2263bf27bdaa9dfe1441daf90;hpb=718c614c183350706466e22939d0101ca4c87efe;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 =
+
+ |
+ |
+ {_("title.Name")} |
+ {_("title.Args")} |
+ {_("title.Range")} |
+ {_("title.Complete")} |
+ {_("title.Definition")} |
+
+
+ {
+ template.map(hives, function ([hive, cmds]) let (i = 0)
+
+
+ template.map(cmds, function (cmd)
+
+ {!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(
-
-
- |
- |
- Name |
- Args |
- Range |
- Complete |
- Definition |
-
-
- {
- template.map(this.userHives, function (hive) let (i = 0)
-
+
- template.map(hive, function (cmd)
- template.map(cmd.names, function (name)
-
- {!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
});