* 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.
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)
get helpTag() ":" + this.name,
- get lastCommand() this._lastCommand || commandline.command,
+ get lastCommand() this._lastCommand || this.modules.commandline.command,
set lastCommand(val) { this._lastCommand = val; },
/**
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"));
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.
*/
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] || "",
util.assert((this.length == 0 || this.command.argCount !== "0") &&
(this.length <= 1 || !/^[01?]$/.test(this.command.argCount)),
- _("error.trailing"));
+ _("error.trailingCharacters"));
}
}
});
* @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?
/**
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);
* 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.
* @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 || {};
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];
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);
},
/**
- * 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);
}
}),
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];
}
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;
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
// 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;
}
}
catch (e) {
util.reportError(e);
+ cmdContext.message = _("error.error", e);
}
};
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) {
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
},
{
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
},
{
]
})),
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 : "")),
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
});