// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k at Gmail>
+// Copyright (c) 2008-2014 Kris Maglione <maglione.k at Gmail>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
try {
-Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("commands", {
exports: ["ArgType", "Command", "Commands", "CommandOption", "Ex", "commands"],
- require: ["contexts", "messages", "util"],
- use: ["config", "options", "services", "template"]
-}, this);
+ require: ["contexts", "messages", "util"]
+});
+
+lazyRequire("help", ["help"]);
+lazyRequire("options", ["Option"]);
+lazyRequire("template", ["template"]);
/**
* A structure representing the options available for a command.
* @property {number} type The option's value type. This is one of:
* (@link CommandOption.NOARG),
* (@link CommandOption.STRING),
+ * (@link CommandOption.STRINGMAP),
* (@link CommandOption.BOOL),
* (@link CommandOption.INT),
* (@link CommandOption.FLOAT),
*/
var CommandOption = Struct("names", "type", "validator", "completer", "multiple", "description", "default");
-CommandOption.defaultValue("description", function () "");
-CommandOption.defaultValue("type", function () CommandOption.NOARG);
-CommandOption.defaultValue("multiple", function () false);
+CommandOption.defaultValue("description", () => "");
+CommandOption.defaultValue("type", () => CommandOption.NOARG);
+CommandOption.defaultValue("multiple", () => false);
var ArgType = Struct("description", "parse");
update(CommandOption, {
* @property {object} The option doesn't accept an argument.
* @final
*/
- NOARG: ArgType("no arg", function (arg) !arg || null),
+ NOARG: ArgType("no arg", arg => !arg || null),
/**
* @property {object} The option accepts a boolean argument.
* @final
* @property {object} The option accepts a string argument.
* @final
*/
- STRING: ArgType("string", function (val) val),
+ STRING: ArgType("string", val => val),
+ /**
+ * @property {object} The option accepts a stringmap argument.
+ * @final
+ */
+ STRINGMAP: ArgType("stringmap", (val, quoted) => Option.parse.stringmap(quoted)),
/**
* @property {object} The option accepts an integer argument.
* @final
* @param {function} action The action invoked by this command when executed.
* @param {Object} extraInfo An optional extra configuration hash. The
* following properties are supported.
+ * always - see {@link Command#always}
* argCount - see {@link Command#argCount}
* bang - see {@link Command#bang}
* completer - see {@link Command#completer}
var Command = Class("Command", {
init: function init(specs, description, action, extraInfo) {
specs = Array.concat(specs); // XXX
- let parsedSpecs = extraInfo.parsedSpecs || Command.parseSpecs(specs);
this.specs = specs;
- this.shortNames = array.compact(parsedSpecs.map(function (n) n[1]));
- this.longNames = parsedSpecs.map(function (n) n[0]);
- this.name = this.longNames[0];
- this.names = array.flatten(parsedSpecs);
this.description = description;
this.action = action;
+ if (extraInfo.options)
+ this._options = extraInfo.options;
+ delete extraInfo.options;
+
if (extraInfo)
this.update(extraInfo);
- if (this.options)
- this.options = this.options.map(CommandOption.fromArray, CommandOption);
- for each (let option in this.options)
- option.localeName = ["command", this.name, option.names[0]];
},
get toStringParams() [this.name, this.hive.name],
* @param {Args} args The Args object passed to {@link #action}.
* @param {Object} modifiers Any modifiers to be passed to {@link #action}.
*/
- execute: function execute(args, modifiers) {
+ execute: function execute(args, modifiers={}) {
const { dactyl } = this.modules;
let context = args.context;
if (this.deprecated)
this.warn(context, "deprecated", _("warn.deprecated", ":" + this.name, this.deprecated));
- modifiers = modifiers || {};
-
if (args.count != null && !this.count)
throw FailedAssertion(_("command.noCount"));
if (args.bang && !this.bang)
throw FailedAssertion(_("command.noBang"));
+ args.doc = this.hive.group.lastDocument;
+
return !dactyl.trapErrors(function exec() {
let extra = this.hive.argsExtra(args);
+
for (let k in properties(extra))
if (!(k in args))
Object.defineProperty(args, k, Object.getOwnPropertyDescriptor(extra, k));
* @param {string} name The candidate name.
* @returns {boolean}
*/
- hasName: function hasName(name) this.parsedSpecs.some(
- function ([long, short]) name.indexOf(short) == 0 && long.indexOf(name) == 0),
+ hasName: function hasName(name) Command.hasName(this.parsedSpecs, name),
/**
* A helper function to parse an argument string.
extra: extra
}),
- complained: Class.memoize(function () ({})),
+ complained: Class.Memoize(function () RealSet()),
/**
* @property {[string]} All of this command's name specs. e.g., "com[mand]"
*/
specs: null,
+ parsedSpecs: Class.Memoize(function () Command.parseSpecs(this.specs)),
+
/** @property {[string]} All of this command's short names, e.g., "com" */
- shortNames: null,
+ shortNames: Class.Memoize(function () array.compact(this.parsedSpecs.map(n => n[1]))),
+
/**
* @property {[string]} All of this command's long names, e.g., "command"
*/
- longNames: null,
+ longNames: Class.Memoize(function () this.parsedSpecs.map(n => n[0])),
/** @property {string} The command's canonical name. */
- name: null,
+ name: Class.Memoize(function () this.longNames[0]),
+
/** @property {[string]} All of this command's long and short names. */
- names: null,
+ names: Class.Memoize(function () this.names = array.flatten(this.parsedSpecs)),
/** @property {string} This command's description, as shown in :listcommands */
description: Messages.Localized(""),
* @property {function (Args)} The function called to execute this command.
*/
action: null,
+
+ /**
+ * @property {function (Args)} A function which is called when this
+ * command is encountered, even if we are ignoring commands. Used to
+ * implement control structures.
+ */
+ always: null,
+
/**
* @property {string} This command's argument count spec.
* @see Commands#parseArguments
*/
argCount: 0,
+
/**
* @property {function (CompletionContext, Args)} This command's completer.
* @see CompletionContext
*/
completer: null,
+
/** @property {boolean} Whether this command accepts a here document. */
hereDoc: false,
+
/**
* @property {boolean} Whether this command may be called with a bang,
* e.g., :com!
*/
bang: false,
+
/**
* @property {boolean} Whether this command may be called with a count,
* e.g., :12bdel
*/
count: false,
+
/**
* @property {function(args)} A function which should return a list
* of domains referenced in the given args. Used in determining
* private data.
*/
domains: function (args) [],
+
/**
* @property {boolean} At what index this command's literal arguments
* begin. For instance, with a value of 2, all arguments starting with
* key mappings or Ex command lines as arguments.
*/
literal: null,
+
/**
* @property {Array} The options this command takes.
* @see Commands@parseArguments
*/
- options: [],
+ options: Class.Memoize(function ()
+ this._options.map(function (opt) {
+ let option = CommandOption.fromArray(opt);
+ option.localeName = ["command", this.name, option.names[0]];
+ return option;
+ }, this)),
+ _options: [],
- optionMap: Class.memoize(function () array(this.options)
- .map(function (opt) opt.names.map(function (name) [name, opt]))
+ optionMap: Class.Memoize(function () array(this.options)
+ .map(opt => opt.names.map(name => [name, opt]))
.flatten().toObject()),
newArgs: function newArgs(base) {
return res;
},
- argsPrototype: Class.memoize(function argsPrototype() {
+ argsPrototype: Class.Memoize(function argsPrototype() {
let res = update([], {
__iterator__: function AP__iterator__() array.iterItems(this),
command: this,
- explicitOpts: Class.memoize(function () ({})),
+ 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) hasOwnProperty(this.explicitOpts, opt)
+ || typeof opt === "number" && hasOwnProperty(this, opt),
get literalArg() this.command.literal != null && this[this.command.literal] || "",
- // TODO: string: Class.memoize(function () { ... }),
+ // TODO: string: Class.Memoize(function () { ... }),
verify: function verify() {
if (this.command.argCount) {
});
this.options.forEach(function (opt) {
- if (opt.default !== undefined)
- Object.defineProperty(res, opt.names[0],
- Object.getOwnPropertyDescriptor(opt, "default") ||
- { configurable: true, enumerable: true, get: function () opt.default });
+ if (opt.default !== undefined) {
+ let prop = Object.getOwnPropertyDescriptor(opt, "default") ||
+ { configurable: true, enumerable: true, get: function () opt.default };
+
+ if (prop.get && !prop.set)
+ prop.set = function (val) { Class.replaceProperty(this, opt.names[0], val); };
+ Object.defineProperty(res, opt.names[0], prop);
+ }
});
return res;
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]")))
+ let key = type + ":" + (context ? context.file : "[Command Line]");
+
+ if (!this.complained.add(key))
this.modules.dactyl.warn(loc + message);
}
}, {
+ hasName: function hasName(specs, name)
+ specs.some(([long, short]) =>
+ name.indexOf(short) == 0 && long.indexOf(name) == 0),
+
// TODO: do we really need more than longNames as a convenience anyway?
/**
* Converts command name abbreviation specs of the form
var CommandHive = Class("CommandHive", Contexts.Hive, {
init: function init(group) {
init.supercall(this, group);
+
this._map = {};
this._list = [];
+ this._specs = [];
},
+ /**
+ * Caches this command hive.
+ */
+
+ cache: function cache() {
+ let { cache } = this.modules;
+ this.cached = true;
+
+ let cached = cache.get(this.cacheKey, () => {
+ this.cached = false;
+ this.modules.moduleManager.initDependencies("commands");
+
+ let map = {};
+ for (let [name, cmd] in Iterator(this._map))
+ if (cmd.sourceModule)
+ map[name] = { sourceModule: cmd.sourceModule, isPlaceholder: true };
+
+ let specs = [];
+ for (let cmd of this._list)
+ for (let spec of cmd.parsedSpecs)
+ specs.push(spec.concat(cmd.name));
+
+ return { map: map, specs: specs };
+ });
+
+ let cached = cache.get(this.cacheKey);
+ if (this.cached) {
+ this._specs = cached.specs;
+ for (let [k, v] in Iterator(cached.map))
+ this._map[k] = v;
+ }
+ },
+
+ get cacheKey() "commands/hives/" + this.name + ".json",
+
/** @property {Iterator(Command)} @private */
- __iterator__: function __iterator__() array.iterValues(this._list.sort(function (a, b) a.name > b.name)),
+ __iterator__: function __iterator__() {
+ if (this.cached)
+ this.modules.initDependencies("commands");
+ this.cached = false;
+ return array.iterValues(this._list.sort((a, b) => a.name > b.name));
+ },
/** @property {string} The last executed Ex command line. */
repeat: null,
/**
- * Adds a new command to the builtin hive. Accessible only to core
- * dactyl code. Plugins should use group.commands.add instead.
+ * Adds a new command to the builtin hive. Accessible only to core dactyl
+ * code. Plugins should use group.commands.add instead.
*
- * @param {[string]} specs The names by which this command can be
- * invoked. The first name specified is the command's canonical
- * name.
+ * @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 {function} action The action invoked by this command.
* @param {Object} extra An optional extra configuration hash.
- * @optional
+ * @optional
+ * @param {boolean} replace Replace an existing command of the same name.
+ * @optional
*/
- add: function add(specs, description, action, extra, replace) {
+ add: function add(specs, description, action, extra={}, replace=false) {
const { commands, contexts } = this.modules;
- extra = extra || {};
if (!extra.definedAt)
extra.definedAt = contexts.getCaller(Components.stack.caller);
+ if (!extra.sourceModule)
+ extra.sourceModule = commands.currentDependency;
extra.hive = this;
extra.parsedSpecs = Command.parseSpecs(specs);
let names = array.flatten(extra.parsedSpecs);
let name = names[0];
- util.assert(!names.some(function (name) name in commands.builtin._map),
- _("command.cantReplace", name));
+ if (this.name != "builtin") {
+ util.assert(!names.some(name => name in commands.builtin._map),
+ _("command.cantReplace", name));
- util.assert(replace || names.every(function (name) !(name in this._map), this),
- _("command.wontReplace", name));
+ util.assert(replace || names.every(name => !(name in this._map)),
+ _("command.wontReplace", name));
+ }
for (let name in values(names)) {
ex.__defineGetter__(name, function () this._run(name));
- if (name in this._map)
+ if (name in this._map && !this._map[name].isPlaceholder)
this.remove(name);
}
- let self = this;
- let closure = function () self._map[name];
+ let closure = () => this._map[name];
- memoize(this._map, name, function () commands.Command(specs, description, action, extra));
- memoize(this._list, this._list.length, closure);
+ memoize(this._map, name, () => commands.Command(specs, description, action, extra));
+ if (!extra.hidden)
+ memoize(this._list, this._list.length, closure);
for (let alias in values(names.slice(1)))
memoize(this._map, alias, closure);
return name;
},
- _add: function _add(names, description, action, extra, replace) {
+ _add: function _add(names, description, action, extra={}, replace=false) {
const { contexts } = this.modules;
-
- extra = extra || {};
extra.definedAt = contexts.getCaller(Components.stack.caller.caller);
return this.add.apply(this, arguments);
},
* its names matches *name* exactly.
* @returns {Command}
*/
- get: function get(name, full) this._map[name]
- || !full && array.nth(this._list, function (cmd) cmd.hasName(name), 0)
- || null,
+ get: function get(name, full) {
+ let cmd = this._map[name]
+ || !full && this._list.find(cmd => cmd.hasName(name))
+ || null;
+
+ if (!cmd && full) {
+ // Hrm. This is wrong. -Kris
+ let name = this._specs.find(spec => Command.hasName(spec, name));
+ return name && this.get(name);
+ }
+
+ if (cmd && cmd.isPlaceholder) {
+ this.modules.moduleManager.initDependencies("commands", [cmd.sourceModule]);
+ cmd = this._map[name];
+ }
+ return cmd;
+ },
/**
* Remove the user-defined command with matching *name*.
util.assert(this.group.modifiable, _("command.cantDelete"));
let cmd = this.get(name);
- this._list = this._list.filter(function (c) c !== cmd);
+ this._list = this._list.filter(c => c !== cmd);
for (let name in values(cmd.names))
delete this._map[name];
}
*/
var Commands = Module("commands", {
lazyInit: true,
+ lazyDepends: true,
Local: function Local(dactyl, modules, window) let ({ Group, contexts } = modules) ({
init: function init() {
});
},
+ reallyInit: function reallyInit() {
+ if (false)
+ this.builtin.cache();
+ else
+ this.modules.moduleManager.initDependencies("commands");
+ },
+
get context() contexts.context,
get readHeredoc() modules.io.readHeredoc,
get allHives() contexts.allGroups.commands,
- get userHives() this.allHives.filter(function (h) h !== this.builtin, this),
+ get userHives() this.allHives.filter(h => h !== this.builtin),
/**
* Executes an Ex command script.
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 [k for ([k, v] in Iterator(config.completers)) if (completer == completion.bound[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 (list.*.length() === list.text().length() + 2)
- dactyl.echomsg(_("command.none"));
- else
- commandline.commandOutput(list);
+ function cmds(hive) hive._list.filter(cmd => cmd.name.startsWith(filter || ""))
+
+ let hives = (hives || this.userHives).map(h => [h, cmds(h)])
+ .filter(([h, c]) => c.length);
+
+ let list = ["table", {},
+ ["tr", { highlight: "Title" },
+ ["td"],
+ ["td", { style: "padding-right: 1em;" }],
+ ["td", { style: "padding-right: 1ex;" }, _("title.Name")],
+ ["td", { style: "padding-right: 1ex;" }, _("title.Args")],
+ ["td", { style: "padding-right: 1ex;" }, _("title.Range")],
+ ["td", { style: "padding-right: 1ex;" }, _("title.Complete")],
+ ["td", { style: "padding-right: 1ex;" }, _("title.Definition")]],
+ ["col", { style: "min-width: 6em; padding-right: 1em;" }],
+ hives.map(([hive, cmds]) => let (i = 0) [
+ ["tr", { style: "height: .5ex;" }],
+ cmds.map(cmd =>
+ ["tr", {},
+ ["td", { highlight: "Title" }, !i++ ? hive.name : ""],
+ ["td", {}, cmd.bang ? "!" : " "],
+ ["td", {}, cmd.name],
+ ["td", {}, cmd.argCount],
+ ["td", {}, cmd.count ? "0c" : ""],
+ ["td", {}, completerToString(cmd.completer)],
+ ["td", {}, cmd.replacementText || "function () { ... }"]]),
+ ["tr", { style: "height: .5ex;" }]])];
+
+ // E4X-FIXME
+ // if (list.*.length() === list.text().length() + 2)
+ // dactyl.echomsg(_("command.none"));
+ // else
+ commandline.commandOutput(list);
}
}),
/** @property {Iterator(Command)} @private */
iterator: function iterator() iter.apply(null, this.hives.array)
- .sort(function (a, b) a.serialGroup - b.serialGroup || a.name > b.name)
+ .sort((a, b) => (a.serialGroup - b.serialGroup ||
+ a.name > b.name))
.iterValues(),
/** @property {string} The last executed Ex command line. */
return group._add.apply(group, arguments);
},
- addUserCommand: deprecated("group.commands.add", { get: function addUserCommand() this.user.closure._add }),
+ addUserCommand: deprecated("group.commands.add", { get: function addUserCommand() this.user.bound._add }),
getUserCommands: deprecated("iter(group.commands)", function getUserCommands() iter(this.user).toArray()),
- removeUserCommand: deprecated("group.commands.remove", { get: function removeUserCommand() this.user.closure.remove }),
+ removeUserCommand: deprecated("group.commands.remove", { get: function removeUserCommand() this.user.bound.remove }),
/**
* Returns the specified command invocation object serialized to
let defaults = {};
if (args.ignoreDefaults)
- defaults = array(this.options).map(function (opt) [opt.names[0], opt.default])
+ defaults = array(this.options).map(opt => [opt.names[0], opt.default])
.toObject();
for (let [opt, val] in Iterator(args.options || {})) {
+ if (val === undefined)
+ continue;
if (val != null && defaults[opt] === val)
continue;
+
let chr = /^-.$/.test(opt) ? " " : "=";
if (isArray(val))
opt += chr + Option.stringify.stringlist(val);
opt += chr + Commands.quote(val);
res.push(opt);
}
+
for (let [, arg] in Iterator(args.arguments || []))
res.push(Commands.quote(arg));
let str = args.literalArg;
if (str)
res.push(!/\n/.test(str) ? str :
- this.hereDoc && false ? "<<EOF\n" + String.replace(str, /\n$/, "") + "\nEOF"
+ this.serializeHereDoc ? "<<EOF\n" + String.replace(str, /\n$/, "") + "\nEOF"
: String.replace(str, /\n/g, "\n" + res[0].replace(/./g, " ").replace(/.$/, "\\")));
return res.join(" ");
},
* any of the command's names.
* @returns {Command}
*/
- get: function get(name, full) iter(this.hives).map(function ([i, hive]) hive.get(name, full))
- .nth(util.identity, 0),
+ get: function get(name, full) iter(this.hives).map(([i, hive]) => hive.get(name, full))
+ .find(util.identity),
/**
* Returns true if a command invocation contains a URL referring to the
hasDomain: function hasDomain(command, host) {
try {
for (let [cmd, args] in this.subCommands(command))
- if (Array.concat(cmd.domains(args)).some(function (domain) util.isSubdomain(domain, host)))
+ if (Array.concat(cmd.domains(args)).some(domain => util.isSubdomain(domain, host)))
return true;
}
catch (e) {
hasPrivateData: function hasPrivateData(command) {
for (let [cmd, args] in this.subCommands(command))
if (cmd.privateData)
- return !callable(cmd.privateData) || cmd.privateData(args);
+ return !callable(cmd.privateData) ? cmd.privateData
+ : cmd.privateData(args);
return false;
},
* Args object.
* @returns {Args}
*/
- parseArgs: function parseArgs(str, params) {
+ parseArgs: function parseArgs(str, params={}) {
const self = this;
- function getNextArg(str, _keepQuotes) {
- if (arguments.length < 2)
- _keepQuotes = keepQuotes;
-
+ function getNextArg(str, _keepQuotes=keepQuotes) {
if (str.substr(0, 2) === "<<" && hereDoc) {
let arg = /^<<(\S*)/.exec(str)[1];
let count = arg.length + 2;
try {
- var { allowUnknownOptions, argCount, complete, extra, hereDoc, literal, options, keepQuotes } = params || {};
+ var { allowUnknownOptions, argCount, complete, extra, hereDoc, literal, options, keepQuotes } = params;
if (!options)
options = [];
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(opt => (opt.multiple || !hasOwnProperty(args, opt.names[0])));
};
let resetCompletions = function resetCompletions() {
completeOpts = null;
if (!onlyArgumentsRemaining) {
for (let [, opt] in Iterator(options)) {
for (let [, optname] in Iterator(opt.names)) {
- if (sub.indexOf(optname) == 0) {
+ if (sub.startsWith(optname)) {
let count = 0;
let invalid = false;
let arg, quote, quoted;
if (arg == null || (typeof arg == "number" && isNaN(arg))) {
if (!complete || orig != "" || args.completeStart != str.length)
- fail(_("command.invalidOptTypeArg", opt.type.description, optname, argString));
+ fail(_("command.invalidOptTypeArg", opt.type.description, optname, quoted));
if (complete)
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
}
// we have a validator function
if (typeof opt.validator == "function") {
if (opt.validator(arg, quoted) == false && (arg || !complete)) {
- fail(_("command.invalidOptArg", optname, argString));
+ fail(_("command.invalidOptArg", optname, quoted));
if (complete) // Always true.
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
}
if (sub.substr(0, 2) === "<<" && hereDoc)
let ([count, arg] = getNextArg(sub)) {
sub = arg + sub.substr(count);
- }
+ };
args.push(sub);
args.quote = null;
context.filter = args.completeFilter;
if (isArray(arg))
- context.filters.push(function (item) arg.indexOf(item.text) === -1);
+ context.filters.push(item => arg.indexOf(item.text) === -1);
if (typeof opt.completer == "function")
var compl = opt.completer(context, args);
}
},
- nameRegexp: util.regexp(<![CDATA[
+ nameRegexp: util.regexp(literal(/*
[^
0-9
<forbid>
]
[^ <forbid> ]*
- ]]>, "gx", {
- forbid: util.regexp(String.replace(<![CDATA[
+ */), "gx", {
+ forbid: util.regexp(String.replace(literal(/*
U0000-U002c // U002d -
U002e-U002f
U003a-U0040 // U0041-U005a a-z
Ufe70-Ufeff // Arabic Presentation Forms-B
Uff00-Uffef // Halfwidth and Fullwidth Forms
Ufff0-Uffff // Specials
- ]]>, /U/g, "\\u"), "x")
+ */), /U/g, "\\u"), "x")
}),
- validName: Class.memoize(function validName() util.regexp("^" + this.nameRegexp.source + "$")),
+ validName: Class.Memoize(function validName() util.regexp("^" + this.nameRegexp.source + "$")),
- commandRegexp: Class.memoize(function commandRegexp() util.regexp(<![CDATA[
+ commandRegexp: Class.Memoize(function commandRegexp() util.regexp(literal(/*
^
(?P<spec>
(?P<prespace> [:\s]*)
(?P<count> (?:\d+ | %)? )
(?P<fullCmd>
- (?: (?P<group> <name>) : )?
- (?P<cmd> (?:<name> | !)? ))
+ (?: (?P<group> <name>) : )?
+ (?P<cmd> (?:-? [()] | <name> | !)? ))
(?P<bang> !?)
(?P<space> \s*)
)
(?:. | \n)*?
)?
$
- ]]>, "x", {
+ */), "x", {
name: this.nameRegexp
})),
return;
}
- if (complete) {
- complete.fork(command.name);
- var context = complete.fork("args", len);
- }
+ if (complete)
+ var context = complete.fork(command.name).fork("opts", len);;
if (!complete || /(\w|^)[!\s]/.test(str))
args = command.parseArgs(args, context, { count: count, bang: bang });
let quote = null;
let len = str.length;
- function fixEscapes(str) str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4}|(.))/g, function (m, n1) n1 || m);
+ function fixEscapes(str) str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4}|(.))/g,
+ (m, n1) => n1 || m);
// Fix me.
if (isString(sep))
context.title = ["Command"];
context.keys = { text: "longNames", description: "description" };
if (group)
- context.generate = function () group._list;
+ context.generate = () => group._list;
else
- context.generate = function () modules.commands.hives.map(function (h) h._list).flatten();
+ context.generate = () => modules.commands.hives.map(h => h._list).flatten();
};
// provides completions for ex commands, including their arguments
return;
}
- let cmdContext = context.fork(command.name, match.fullCmd.length + match.bang.length + match.space.length);
+ let cmdContext = context.fork(command.name + "/args", match.fullCmd.length + match.bang.length + match.space.length);
try {
if (!cmdContext.waitingForTab) {
if (!args.completeOpt && command.completer && args.completeStart != null) {
}
};
+ completion.exMacro = function exMacro(context, args, cmd) {
+ if (!cmd.action.macro)
+ return;
+ let { macro } = cmd.action;
+
+ let start = "«%-d-]'", end = "'[-d-%»";
+
+ let n = /^\d+$/.test(cmd.argCount) ? parseInt(cmd.argCount) : 12;
+ for (let i = args.completeArg; i < n; i++)
+ args[i] = start + i + end;
+
+ let params = {
+ args: { __proto__: args, toString: function () this.join(" ") },
+ bang: args.bang ? "!" : "",
+ count: args.count
+ };
+
+ if (!macro.valid(params))
+ return;
+
+ let str = macro(params);
+ let idx = str.indexOf(start);
+ if (!~idx || !/^(')?(\d+)'/.test(str.substr(idx + start.length))
+ || RegExp.$2 != args.completeArg)
+ return;
+
+ let quote = RegExp.$2;
+ context.quote = null;
+ context.offset -= idx;
+ context.filter = str.substr(0, idx) + (quote ? Option.quote : util.identity)(context.filter);
+
+ context.fork("ex", 0, completion, "ex");
+ };
+
completion.userCommand = function userCommand(context, group) {
context.title = ["User Command", "Definition"];
context.keys = { text: "name", description: "replacementText" };
commands: function initCommands(dactyl, modules, window) {
const { commands, contexts } = modules;
+ commands.add(["(", "-("], "",
+ function (args) { dactyl.echoerr(_("dactyl.cheerUp")); },
+ { hidden: true });
+ commands.add([")", "-)"], "",
+ function (args) { dactyl.echoerr(_("dactyl.somberDown")); },
+ { hidden: true });
+
commands.add(["com[mand]"],
"List or define commands",
function (args) {
let cmd = args[0];
- util.assert(!cmd || cmd.split(",").every(commands.validName.closure.test),
+ util.assert(!cmd || cmd.split(",").every(commands.validName.bound.test),
_("command.invalidName", cmd));
if (args.length <= 1)
_("group.cantChangeBuiltin", _("command.commands")));
let completer = args["-complete"];
- let completerFunc = null; // default to no completion for user commands
+ let completerFunc = function (context, args) modules.completion.exMacro(context, args, this);
if (completer) {
if (/^custom,/.test(completer)) {
return dactyl.userEval(completer);
});
if (callable(result))
- return result.apply(this, Array.slice(arguments));
+ return result.apply(this, arguments);
else
return context.completions = result;
};
}
else
- completerFunc = function (context) modules.completion.closure[config.completers[completer]](context);
+ completerFunc = context => modules.completion.bound[config.completers[completer]](context);
}
let added = args["-group"].add(cmd.split(","),
function makeParams(args, modifiers) ({
args: {
__proto__: args,
- toString: function () this.string,
+ toString: function () this.string
},
bang: this.bang && args.bang ? "!" : "",
count: this.count && args.count
["+", "One or more arguments are allowed"]],
default: "0",
type: CommandOption.STRING,
- validator: function (arg) /^[01*?+]$/.test(arg)
+ validator: bind("test", /^[01*?+]$/)
},
{
names: ["-nopersist", "-n"],
literal: 1,
serialize: function () array(commands.userHives)
- .filter(function (h) h.persist)
- .map(function (hive) [
+ .filter(h => h.persist)
+ .map(hive => [
{
command: this.name,
bang: true,
ignoreDefaults: true
}
for (cmd in hive) if (cmd.persist)
- ], this)
+ ])
.flatten().array
});
iterate: function (args) commands.iterator().map(function (cmd) ({
__proto__: cmd,
columns: [
- cmd.hive == commands.builtin ? "" : <span highlight="Object" style="padding-right: 1em;">{cmd.hive.name}</span>
+ cmd.hive == commands.builtin ? "" : ["span", { highlight: "Object", style: "padding-right: 1em;" },
+ cmd.hive.name]
]
})),
- iterateIndex: function (args) let (tags = services["dactyl:"].HELP_TAGS)
- this.iterate(args).filter(function (cmd) cmd.hive === commands.builtin || Set.has(tags, cmd.helpTag)),
+ iterateIndex: function (args) let (tags = help.tags)
+ this.iterate(args).filter(cmd => (cmd.hive === commands.builtin || hasOwnProperty(tags, cmd.helpTag))),
format: {
headings: ["Command", "Group", "Description"],
description: function (cmd) template.linkifyHelp(cmd.description + (cmd.replacementText ? ": " + cmd.action : "")),
javascript: function initJavascript(dactyl, modules, window) {
const { JavaScript, commands } = modules;
- JavaScript.setCompleter([commands.user.get, commands.user.remove],
+ JavaScript.setCompleter([CommandHive.prototype.get, CommandHive.prototype.remove],
[function () [[c.names, c.description] for (c in this)]]);
- JavaScript.setCompleter([commands.get],
+ JavaScript.setCompleter([Commands.prototype.get],
[function () [[c.names, c.description] for (c in this.iterator())]]);
},
mappings: function initMappings(dactyl, modules, window) {
mappings.add([modes.COMMAND],
["@:"], "Repeat the last Ex command",
- function (args) {
+ function ({ count }) {
if (commands.repeat) {
- for (let i in util.interruptibleRange(0, Math.max(args.count, 1), 100))
+ for (let i in util.interruptibleRange(0, Math.max(count, 1), 100))
dactyl.execute(commands.repeat);
}
else
}
});
-(function () {
-
- Commands.quoteMap = {
- "\n": "\\n",
- "\t": "\\t",
- };
- function quote(q, list, map) {
- map = map || Commands.quoteMap;
- let re = RegExp("[" + list + "]", "g");
- function quote(str) q + String.replace(str, re, function ($0) $0 in map ? map[$0] : ("\\" + $0)) + q;
- quote.list = list;
- return quote;
- };
-
- Commands.quoteArg = {
- '"': quote('"', '\n\t"\\\\'),
- "'": quote("'", "'", { "'": "''" }),
- "": quote("", "|\\\\\\s'\"")
- };
- Commands.complQuote = {
- '"': ['"', quote("", Commands.quoteArg['"'].list), '"'],
- "'": ["'", quote("", Commands.quoteArg["'"].list), "'"],
- "": ["", Commands.quoteArg[""], ""]
- };
-
- Commands.parseBool = function (arg) {
- if (/^(true|1|on)$/i.test(arg))
- return true;
- if (/^(false|0|off)$/i.test(arg))
- return false;
- return NaN;
- };
-})();
+let quote = function quote(q, list, map=Commands.quoteMap) {
+ let re = RegExp("[" + list + "]", "g");
+ function quote(str) (q + String.replace(str, re, $0 => ($0 in map ? map[$0] : ("\\" + $0)))
+ + q);
+ quote.list = list;
+ return quote;
+};
+
+Commands.quoteMap = {
+ "\n": "\\n",
+ "\t": "\\t"
+};
+
+Commands.quoteArg = {
+ '"': quote('"', '\n\t"\\\\'),
+ "'": quote("'", "'", { "'": "''" }),
+ "": quote("", "|\\\\\\s'\"")
+};
+Commands.complQuote = {
+ '"': ['"', quote("", Commands.quoteArg['"'].list), '"'],
+ "'": ["'", quote("", Commands.quoteArg["'"].list), "'"],
+ "": ["", Commands.quoteArg[""], ""]
+};
+
+Commands.parseBool = function (arg) {
+ if (/^(true|1|on)$/i.test(arg))
+ return true;
+ if (/^(false|0|off)$/i.test(arg))
+ return false;
+ return NaN;
+};
endModule();
} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
-// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: