1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2014 Kris Maglione <maglione.k at Gmail>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
11 defineModule("commands", {
12 exports: ["ArgType", "Command", "Commands", "CommandOption", "Ex", "commands"],
13 require: ["contexts", "messages", "util"]
16 lazyRequire("help", ["help"]);
17 lazyRequire("options", ["Option"]);
18 lazyRequire("template", ["template"]);
21 * A structure representing the options available for a command.
23 * Do NOT create instances of this class yourself, use the helper method
24 * {@see Commands#add} instead
26 * @property {[string]} names An array of option names. The first name
27 * is the canonical option name.
28 * @property {number} type The option's value type. This is one of:
29 * (@link CommandOption.NOARG),
30 * (@link CommandOption.STRING),
31 * (@link CommandOption.STRINGMAP),
32 * (@link CommandOption.BOOL),
33 * (@link CommandOption.INT),
34 * (@link CommandOption.FLOAT),
35 * (@link CommandOption.LIST),
36 * (@link CommandOption.ANY)
37 * @property {function} validator A validator function
38 * @property {function (CompletionContext, object)} completer A list of
39 * completions, or a completion function which will be passed a
40 * {@link CompletionContext} and an object like that returned by
41 * {@link commands.parseArgs} with the following additional keys:
42 * completeOpt - The name of the option currently being completed.
43 * @property {boolean} multiple Whether this option can be specified multiple times
44 * @property {string} description A description of the option
45 * @property {object} default The option's default value
48 var CommandOption = Struct("names", "type", "validator", "completer", "multiple", "description", "default");
49 CommandOption.defaultValue("description", () => "");
50 CommandOption.defaultValue("type", () => CommandOption.NOARG);
51 CommandOption.defaultValue("multiple", () => false);
53 var ArgType = Struct("description", "parse");
54 update(CommandOption, {
56 * @property {object} The option argument is unspecified. Any argument
57 * is accepted and caller is responsible for parsing the return
64 * @property {object} The option doesn't accept an argument.
67 NOARG: ArgType("no arg", arg => !arg || null),
69 * @property {object} The option accepts a boolean argument.
72 BOOL: ArgType("boolean", function parseBoolArg(val) Commands.parseBool(val)),
74 * @property {object} The option accepts a string argument.
77 STRING: ArgType("string", val => val),
79 * @property {object} The option accepts a stringmap argument.
82 STRINGMAP: ArgType("stringmap", (val, quoted) => Option.parse.stringmap(quoted)),
84 * @property {object} The option accepts an integer argument.
87 INT: ArgType("int", function parseIntArg(val) parseInt(val)),
89 * @property {object} The option accepts a float argument.
92 FLOAT: ArgType("float", function parseFloatArg(val) parseFloat(val)),
94 * @property {object} The option accepts a string list argument.
98 LIST: ArgType("list", function parseListArg(arg, quoted) Option.splitList(quoted))
102 * A class representing Ex commands. Instances are created by
103 * the {@link Commands} class.
105 * @param {[string]} specs The names by which this command can be invoked.
106 * These are specified in the form "com[mand]" where "com" is a unique
107 * command name prefix.
108 * @param {string} description A short one line description of the command.
109 * @param {function} action The action invoked by this command when executed.
110 * @param {Object} extraInfo An optional extra configuration hash. The
111 * following properties are supported.
112 * always - see {@link Command#always}
113 * argCount - see {@link Command#argCount}
114 * bang - see {@link Command#bang}
115 * completer - see {@link Command#completer}
116 * count - see {@link Command#count}
117 * domains - see {@link Command#domains}
118 * heredoc - see {@link Command#heredoc}
119 * literal - see {@link Command#literal}
120 * options - see {@link Command#options}
121 * privateData - see {@link Command#privateData}
122 * serialize - see {@link Command#serialize}
123 * subCommand - see {@link Command#subCommand}
127 var Command = Class("Command", {
128 init: function init(specs, description, action, extraInfo) {
129 specs = Array.concat(specs); // XXX
132 this.description = description;
133 this.action = action;
135 if (extraInfo.options)
136 this._options = extraInfo.options;
137 delete extraInfo.options;
140 this.update(extraInfo);
143 get toStringParams() [this.name, this.hive.name],
145 get identifier() this.hive.prefix + this.name,
147 get helpTag() ":" + this.name,
149 get lastCommand() this._lastCommand || this.modules.commandline.command,
150 set lastCommand(val) { this._lastCommand = val; },
153 * Execute this command.
155 * @param {Args} args The Args object passed to {@link #action}.
156 * @param {Object} modifiers Any modifiers to be passed to {@link #action}.
158 execute: function execute(args, modifiers={}) {
159 const { dactyl } = this.modules;
161 let context = args.context;
163 this.warn(context, "deprecated", _("warn.deprecated", ":" + this.name, this.deprecated));
165 if (args.count != null && !this.count)
166 throw FailedAssertion(_("command.noCount"));
167 if (args.bang && !this.bang)
168 throw FailedAssertion(_("command.noBang"));
170 args.doc = this.hive.group.lastDocument;
172 return !dactyl.trapErrors(function exec() {
173 let extra = this.hive.argsExtra(args);
175 for (let k in properties(extra))
177 Object.defineProperty(args, k, Object.getOwnPropertyDescriptor(extra, k));
180 this.always(args, modifiers);
182 if (!context || !context.noExecute)
183 this.action(args, modifiers);
188 * Returns whether this command may be invoked via *name*.
190 * @param {string} name The candidate name.
193 hasName: function hasName(name) Command.hasName(this.parsedSpecs, name),
196 * A helper function to parse an argument string.
198 * @param {string} args The argument string to parse.
199 * @param {CompletionContext} complete A completion context.
200 * Non-null when the arguments are being parsed for completion
202 * @param {Object} extra Extra keys to be spliced into the
203 * returned Args object.
205 * @see Commands#parseArgs
207 parseArgs: function parseArgs(args, complete, extra) this.modules.commands.parseArgs(args, {
213 complained: Class.Memoize(function () RealSet()),
216 * @property {[string]} All of this command's name specs. e.g., "com[mand]"
219 parsedSpecs: Class.Memoize(function () Command.parseSpecs(this.specs)),
221 /** @property {[string]} All of this command's short names, e.g., "com" */
222 shortNames: Class.Memoize(function () array.compact(this.parsedSpecs.map(n => n[1]))),
225 * @property {[string]} All of this command's long names, e.g., "command"
227 longNames: Class.Memoize(function () this.parsedSpecs.map(n => n[0])),
229 /** @property {string} The command's canonical name. */
230 name: Class.Memoize(function () this.longNames[0]),
232 /** @property {[string]} All of this command's long and short names. */
233 names: Class.Memoize(function () this.names = array.flatten(this.parsedSpecs)),
235 /** @property {string} This command's description, as shown in :listcommands */
236 description: Messages.Localized(""),
238 /** @property {string|null} If set, the deprecation message for this command. */
239 deprecated: Messages.Localized(null),
242 * @property {function (Args)} The function called to execute this command.
247 * @property {function (Args)} A function which is called when this
248 * command is encountered, even if we are ignoring commands. Used to
249 * implement control structures.
254 * @property {string} This command's argument count spec.
255 * @see Commands#parseArguments
260 * @property {function (CompletionContext, Args)} This command's completer.
261 * @see CompletionContext
265 /** @property {boolean} Whether this command accepts a here document. */
269 * @property {boolean} Whether this command may be called with a bang,
275 * @property {boolean} Whether this command may be called with a count,
281 * @property {function(args)} A function which should return a list
282 * of domains referenced in the given args. Used in determining
283 * whether to purge the command from history when clearing
286 domains: function (args) [],
289 * @property {boolean} At what index this command's literal arguments
290 * begin. For instance, with a value of 2, all arguments starting with
291 * the third are parsed as a single string, with all quoting characters
292 * passed literally. This is especially useful for commands which take
293 * key mappings or Ex command lines as arguments.
298 * @property {Array} The options this command takes.
299 * @see Commands@parseArguments
301 options: Class.Memoize(function ()
302 this._options.map(function (opt) {
303 let option = CommandOption.fromArray(opt);
304 option.localeName = ["command", this.name, option.names[0]];
309 optionMap: Class.Memoize(function () array(this.options)
310 .map(opt => opt.names.map(name => [name, opt]))
311 .flatten().toObject()),
313 newArgs: function newArgs(base) {
316 res.__proto__ = this.argsPrototype;
320 argsPrototype: Class.Memoize(function argsPrototype() {
321 let res = update([], {
322 __iterator__: function AP__iterator__() array.iterItems(this),
326 explicitOpts: Class.Memoize(function () ({})),
328 has: function AP_has(opt) hasOwnProperty(this.explicitOpts, opt)
329 || typeof opt === "number" && hasOwnProperty(this, opt),
331 get literalArg() this.command.literal != null && this[this.command.literal] || "",
333 // TODO: string: Class.Memoize(function () { ... }),
335 verify: function verify() {
336 if (this.command.argCount) {
337 util.assert((this.length > 0 || !/^[1+]$/.test(this.command.argCount)) &&
338 (this.literal == null || !/[1+]/.test(this.command.argCount) || /\S/.test(this.literalArg || "")),
339 _("error.argumentRequired"));
341 util.assert((this.length == 0 || this.command.argCount !== "0") &&
342 (this.length <= 1 || !/^[01?]$/.test(this.command.argCount)),
343 _("error.trailingCharacters"));
348 this.options.forEach(function (opt) {
349 if (opt.default !== undefined) {
350 let prop = Object.getOwnPropertyDescriptor(opt, "default") ||
351 { configurable: true, enumerable: true, get: function () opt.default };
353 if (prop.get && !prop.set)
354 prop.set = function (val) { Class.replaceProperty(this, opt.names[0], val); };
355 Object.defineProperty(res, opt.names[0], prop);
363 * @property {boolean|function(args)} When true, invocations of this
364 * command may contain private data which should be purged from
365 * saved histories when clearing private data. If a function, it
366 * should return true if an invocation with the given args
367 * contains private data
371 * @property {function} Should return an array of *Object*s suitable to be
372 * passed to {@link Commands#commandToString}, one for each past
373 * invocation which should be restored on subsequent @dactyl startups.
378 * @property {number} If this command takes another ex command as an
379 * argument, the index of that argument. Used in determining whether to
380 * purge the command from history when clearing private data.
384 * @property {boolean} Specifies whether this is a user command. User
385 * commands may be created by plugins, or directly by users, and,
386 * unlike basic commands, may be overwritten. Users and plugin authors
387 * should create only user commands.
391 * @property {string} For commands defined via :command, contains the Ex
392 * command line to be executed upon invocation.
394 replacementText: null,
397 * Warns of a misuse of this command once per warning type per file.
399 * @param {object} context The calling context.
400 * @param {string} type The type of warning.
401 * @param {string} warning The warning message.
403 warn: function warn(context, type, message) {
404 let loc = !context ? "" : [context.file, context.line, " "].join(":");
406 let key = type + ":" + (context ? context.file : "[Command Line]");
408 if (!this.complained.add(key))
409 this.modules.dactyl.warn(loc + message);
412 hasName: function hasName(specs, name)
413 specs.some(([long, short]) =>
414 name.indexOf(short) == 0 && long.indexOf(name) == 0),
416 // TODO: do we really need more than longNames as a convenience anyway?
418 * Converts command name abbreviation specs of the form
419 * 'shortname[optional-tail]' to short and long versions:
420 * ["abc[def]", "ghijkl"] -> [["abcdef", "abc"], ["ghijlk"]]
422 * @param {Array} specs An array of command name specs to parse.
425 parseSpecs: function parseSpecs(specs) {
426 return specs.map(function (spec) {
427 let [, head, tail] = /([^[]+)(?:\[(.*)])?/.exec(spec);
428 return tail ? [head + tail, head] : [head];
434 var Ex = Module("Ex", {
435 Local: function Local(dactyl, modules, window) ({
436 get commands() modules.commands,
437 get context() modules.contexts.context
440 _args: function E_args(cmd, args) {
441 args = Array.slice(args);
443 let res = cmd.newArgs({ context: this.context });
444 if (isObject(args[0]))
445 for (let [k, v] in Iterator(args.shift()))
451 let opt = cmd.optionMap["-" + k];
452 let val = opt.type && opt.type.parse(v);
454 util.assert(val != null && (typeof val !== "number" || !isNaN(val)),
455 _("option.noSuch", k));
457 Class.replaceProperty(res, opt.names[0], val);
458 res.explicitOpts[opt.names[0]] = val;
460 for (let [i, val] in array.iterItems(args))
461 res[i] = String(val);
465 _complete: function E_complete(cmd) let (self = this)
466 function _complete(context, func, obj, args) {
467 args = self._args(cmd, args);
468 args.completeArg = args.length - 1;
469 if (cmd.completer && args.length)
470 return cmd.completer(context, args);
473 _run: function E_run(name) {
475 let cmd = this.commands.get(name);
476 util.assert(cmd, _("command.noSuch"));
478 return update(function exCommand(options) {
479 let args = self._args(cmd, arguments);
481 return cmd.execute(args);
483 dactylCompleter: self._complete(cmd)
487 __noSuchMethod__: function __noSuchMethod__(meth, args) this._run(meth).apply(this, args)
490 var CommandHive = Class("CommandHive", Contexts.Hive, {
491 init: function init(group) {
492 init.supercall(this, group);
500 * Caches this command hive.
503 cache: function cache() {
504 let { cache } = this.modules;
507 let cached = cache.get(this.cacheKey, () => {
509 this.modules.moduleManager.initDependencies("commands");
512 for (let [name, cmd] in Iterator(this._map))
513 if (cmd.sourceModule)
514 map[name] = { sourceModule: cmd.sourceModule, isPlaceholder: true };
517 for (let cmd of this._list)
518 for (let spec of cmd.parsedSpecs)
519 specs.push(spec.concat(cmd.name));
521 return { map: map, specs: specs };
524 let cached = cache.get(this.cacheKey);
526 this._specs = cached.specs;
527 for (let [k, v] in Iterator(cached.map))
532 get cacheKey() "commands/hives/" + this.name + ".json",
534 /** @property {Iterator(Command)} @private */
535 __iterator__: function __iterator__() {
537 this.modules.initDependencies("commands");
539 return array.iterValues(this._list.sort((a, b) => a.name > b.name));
542 /** @property {string} The last executed Ex command line. */
546 * Adds a new command to the builtin hive. Accessible only to core dactyl
547 * code. Plugins should use group.commands.add instead.
549 * @param {[string]} specs The names by which this command can be invoked.
550 * The first name specified is the command's canonical name.
551 * @param {string} description A description of the command.
552 * @param {function} action The action invoked by this command.
553 * @param {Object} extra An optional extra configuration hash.
555 * @param {boolean} replace Replace an existing command of the same name.
558 add: function add(specs, description, action, extra={}, replace=false) {
559 const { commands, contexts } = this.modules;
561 if (!extra.definedAt)
562 extra.definedAt = contexts.getCaller(Components.stack.caller);
563 if (!extra.sourceModule)
564 extra.sourceModule = commands.currentDependency;
567 extra.parsedSpecs = Command.parseSpecs(specs);
569 let names = array.flatten(extra.parsedSpecs);
572 if (this.name != "builtin") {
573 util.assert(!names.some(name => name in commands.builtin._map),
574 _("command.cantReplace", name));
576 util.assert(replace || names.every(name => !(name in this._map)),
577 _("command.wontReplace", name));
580 for (let name in values(names)) {
581 ex.__defineGetter__(name, function () this._run(name));
582 if (name in this._map && !this._map[name].isPlaceholder)
586 let closure = () => this._map[name];
588 memoize(this._map, name, () => commands.Command(specs, description, action, extra));
590 memoize(this._list, this._list.length, closure);
591 for (let alias in values(names.slice(1)))
592 memoize(this._map, alias, closure);
597 _add: function _add(names, description, action, extra={}, replace=false) {
598 const { contexts } = this.modules;
599 extra.definedAt = contexts.getCaller(Components.stack.caller.caller);
600 return this.add.apply(this, arguments);
604 * Clear all commands.
607 clear: function clear() {
608 util.assert(this.group.modifiable, _("command.cantDelete"));
614 * Returns the command with matching *name*.
616 * @param {string} name The name of the command to return. This can be
617 * any of the command's names.
618 * @param {boolean} full If true, only return a command if one of
619 * its names matches *name* exactly.
622 get: function get(name, full) {
623 let cmd = this._map[name]
624 || !full && this._list.find(cmd => cmd.hasName(name))
628 // Hrm. This is wrong. -Kris
629 let name = this._specs.find(spec => Command.hasName(spec, name));
630 return name && this.get(name);
633 if (cmd && cmd.isPlaceholder) {
634 this.modules.moduleManager.initDependencies("commands", [cmd.sourceModule]);
635 cmd = this._map[name];
641 * Remove the user-defined command with matching *name*.
643 * @param {string} name The name of the command to remove. This can be
644 * any of the command's names.
646 remove: function remove(name) {
647 util.assert(this.group.modifiable, _("command.cantDelete"));
649 let cmd = this.get(name);
650 this._list = this._list.filter(c => c !== cmd);
651 for (let name in values(cmd.names))
652 delete this._map[name];
659 var Commands = Module("commands", {
663 Local: function Local(dactyl, modules, window) let ({ Group, contexts } = modules) ({
664 init: function init() {
665 this.Command = Class("Command", Command, { modules: modules });
667 hives: contexts.Hives("commands", Class("CommandHive", CommandHive, { modules: modules })),
668 user: contexts.hives.commands.user,
669 builtin: contexts.hives.commands.builtin
673 reallyInit: function reallyInit() {
675 this.builtin.cache();
677 this.modules.moduleManager.initDependencies("commands");
680 get context() contexts.context,
682 get readHeredoc() modules.io.readHeredoc,
684 get allHives() contexts.allGroups.commands,
686 get userHives() this.allHives.filter(h => h !== this.builtin),
689 * Executes an Ex command script.
691 * @param {string} string A string containing the commands to execute.
692 * @param {object} tokens An optional object containing tokens to be
693 * interpolated into the command string.
694 * @param {object} args Optional arguments object to be passed to
696 * @param {object} context An object containing information about
697 * the file that is being or has been sourced to obtain the
700 execute: function execute(string, tokens, silent, args, context) {
701 contexts.withContext(context || this.context || { file: "[Command Line]", line: 1 },
703 modules.io.withSavedValues(["readHeredoc"], function () {
704 this.readHeredoc = function readHeredoc(end) {
706 contexts.context.line++;
707 while (++i < lines.length) {
708 if (lines[i] === end)
709 return res.join("\n");
712 util.assert(false, _("command.eof", end));
715 args = update({}, args || {});
717 if (tokens && !callable(string))
718 string = util.compileMacro(string, true);
719 if (callable(string))
720 string = string(tokens || {});
722 let lines = string.split(/\r\n|[\r\n]/);
723 let startLine = context.line;
725 for (var i = 0; i < lines.length && !context.finished; i++) {
726 // Deal with editors from Silly OSs.
727 let line = lines[i].replace(/\r$/, "");
729 context.line = startLine + i;
731 // Process escaped new lines
732 while (i < lines.length && /^\s*\\/.test(lines[i + 1]))
733 line += "\n" + lines[++i].replace(/^\s*\\/, "");
736 dactyl.execute(line, args);
740 e.message = context.file + ":" + context.line + ": " + e.message;
741 dactyl.reportError(e, true);
750 * Lists all user-defined commands matching *filter* and optionally
753 * @param {string} filter Limits the list to those commands with a name
754 * matching this anchored substring.
755 * @param {[Hive]} hives List of hives.
758 list: function list(filter, hives) {
759 const { commandline, completion } = this.modules;
760 function completerToString(completer) {
762 return [k for ([k, v] in Iterator(config.completers)) if (completer == completion.bound[v])][0] || "custom";
765 // TODO: allow matching of aliases?
766 function cmds(hive) hive._list.filter(cmd => cmd.name.startsWith(filter || ""))
768 let hives = (hives || this.userHives).map(h => [h, cmds(h)])
769 .filter(([h, c]) => c.length);
771 let list = ["table", {},
772 ["tr", { highlight: "Title" },
774 ["td", { style: "padding-right: 1em;" }],
775 ["td", { style: "padding-right: 1ex;" }, _("title.Name")],
776 ["td", { style: "padding-right: 1ex;" }, _("title.Args")],
777 ["td", { style: "padding-right: 1ex;" }, _("title.Range")],
778 ["td", { style: "padding-right: 1ex;" }, _("title.Complete")],
779 ["td", { style: "padding-right: 1ex;" }, _("title.Definition")]],
780 ["col", { style: "min-width: 6em; padding-right: 1em;" }],
781 hives.map(([hive, cmds]) => let (i = 0) [
782 ["tr", { style: "height: .5ex;" }],
785 ["td", { highlight: "Title" }, !i++ ? hive.name : ""],
786 ["td", {}, cmd.bang ? "!" : " "],
787 ["td", {}, cmd.name],
788 ["td", {}, cmd.argCount],
789 ["td", {}, cmd.count ? "0c" : ""],
790 ["td", {}, completerToString(cmd.completer)],
791 ["td", {}, cmd.replacementText || "function () { ... }"]]),
792 ["tr", { style: "height: .5ex;" }]])];
795 // if (list.*.length() === list.text().length() + 2)
796 // dactyl.echomsg(_("command.none"));
798 commandline.commandOutput(list);
803 * @property Indicates that no count was specified for this
804 * command invocation.
809 * @property {number} Indicates that the full buffer range (1,$) was
810 * specified for this command invocation.
813 // FIXME: this isn't a count at all
814 COUNT_ALL: -2, // :%...
816 /** @property {Iterator(Command)} @private */
817 iterator: function iterator() iter.apply(null, this.hives.array)
818 .sort((a, b) => (a.serialGroup - b.serialGroup ||
822 /** @property {string} The last executed Ex command line. */
825 add: function add() {
826 let group = this.builtin;
827 if (!util.isDactyl(Components.stack.caller)) {
828 deprecated.warn(add, "commands.add", "group.commands.add");
832 return group._add.apply(group, arguments);
834 addUserCommand: deprecated("group.commands.add", { get: function addUserCommand() this.user.bound._add }),
835 getUserCommands: deprecated("iter(group.commands)", function getUserCommands() iter(this.user).toArray()),
836 removeUserCommand: deprecated("group.commands.remove", { get: function removeUserCommand() this.user.bound.remove }),
839 * Returns the specified command invocation object serialized to
840 * an executable Ex command string.
842 * @param {Object} args The command invocation object.
845 commandToString: function commandToString(args) {
846 let res = [args.command + (args.bang ? "!" : "")];
849 if (args.ignoreDefaults)
850 defaults = array(this.options).map(opt => [opt.names[0], opt.default])
853 for (let [opt, val] in Iterator(args.options || {})) {
854 if (val === undefined)
856 if (val != null && defaults[opt] === val)
859 let chr = /^-.$/.test(opt) ? " " : "=";
861 opt += chr + Option.stringify.stringlist(val);
862 else if (val != null)
863 opt += chr + Commands.quote(val);
867 for (let [, arg] in Iterator(args.arguments || []))
868 res.push(Commands.quote(arg));
870 let str = args.literalArg;
872 res.push(!/\n/.test(str) ? str :
873 this.serializeHereDoc ? "<<EOF\n" + String.replace(str, /\n$/, "") + "\nEOF"
874 : String.replace(str, /\n/g, "\n" + res[0].replace(/./g, " ").replace(/.$/, "\\")));
875 return res.join(" ");
879 * Returns the command with matching *name*.
881 * @param {string} name The name of the command to return. This can be
882 * any of the command's names.
885 get: function get(name, full) iter(this.hives).map(([i, hive]) => hive.get(name, full))
886 .find(util.identity),
889 * Returns true if a command invocation contains a URL referring to the
892 * @param {string} command
893 * @param {string} host
896 hasDomain: function hasDomain(command, host) {
898 for (let [cmd, args] in this.subCommands(command))
899 if (Array.concat(cmd.domains(args)).some(domain => util.isSubdomain(domain, host)))
909 * Returns true if a command invocation contains private data which should
910 * be cleared when purging private data.
912 * @param {string} command
915 hasPrivateData: function hasPrivateData(command) {
916 for (let [cmd, args] in this.subCommands(command))
918 return !callable(cmd.privateData) ? cmd.privateData
919 : cmd.privateData(args);
923 // TODO: should it handle comments?
924 // : it might be nice to be able to specify that certain quoting
925 // should be disabled E.g. backslash without having to resort to
926 // using literal etc.
927 // : error messages should be configurable or else we can ditch
928 // Vim compatibility but it actually gives useful messages
929 // sometimes rather than just "Invalid arg"
930 // : I'm not sure documenting the returned object here, and
931 // elsewhere, as type Args rather than simply Object makes sense,
932 // especially since it is further augmented for use in
933 // Command#action etc.
935 * Parses *str* for options and plain arguments.
937 * The returned *Args* object is an augmented array of arguments.
938 * Any key/value pairs of *extra* will be available and the
939 * following additional properties:
940 * -opt - the value of the option -opt if specified
941 * string - the original argument string *str*
942 * literalArg - any trailing literal argument
945 * '-quoted strings - only ' and \ itself are escaped
946 * "-quoted strings - also ", \n and \t are translated
947 * non-quoted strings - everything is taken literally apart from "\
950 * @param {string} str The Ex command-line string to parse. E.g.
951 * "-x=foo -opt=bar arg1 arg2"
952 * @param {[CommandOption]} options The options accepted. These are specified
953 * as an array of {@link CommandOption} structures.
954 * @param {string} argCount The number of arguments accepted.
956 * "1": exactly one argument
957 * "+": one or more arguments
958 * "*": zero or more arguments (default if unspecified)
959 * "?": zero or one arguments
960 * @param {boolean} allowUnknownOptions Whether unspecified options
961 * should cause an error.
962 * @param {number} literal The index at which any literal arg begins.
963 * See {@link Command#literal}.
964 * @param {CompletionContext} complete The relevant completion context
965 * when the args are being parsed for completion.
966 * @param {Object} extra Extra keys to be spliced into the returned
970 parseArgs: function parseArgs(str, params={}) {
973 function getNextArg(str, _keepQuotes=keepQuotes) {
974 if (str.substr(0, 2) === "<<" && hereDoc) {
975 let arg = /^<<(\S*)/.exec(str)[1];
976 let count = arg.length + 2;
978 return [count, "", ""];
979 return [count, self.readHeredoc(arg), ""];
982 let [count, arg, quote] = Commands.parseArg(str, null, _keepQuotes);
983 if (quote == "\\" && !complete)
984 return [, , , _("error.trailingCharacters", "\\")];
985 if (quote && !complete)
986 return [, , , _("error.missingQuote", quote)];
987 return [count, arg, quote];
992 var { allowUnknownOptions, argCount, complete, extra, hereDoc, literal, options, keepQuotes } = params;
1000 var args = params.newArgs ? params.newArgs() : [];
1001 args.string = str; // for access to the unparsed string
1004 for (let [k, v] in Iterator(extra || []))
1007 // FIXME: best way to specify these requirements?
1008 var onlyArgumentsRemaining = allowUnknownOptions || options.length == 0; // after a -- has been found
1014 let matchOpts = function matchOpts(arg) {
1015 // Push possible option matches into completions
1016 if (complete && !onlyArgumentsRemaining)
1017 completeOpts = options.filter(opt => (opt.multiple || !hasOwnProperty(args, opt.names[0])));
1019 let resetCompletions = function resetCompletions() {
1020 completeOpts = null;
1021 args.completeArg = null;
1022 args.completeOpt = null;
1023 args.completeFilter = null;
1024 args.completeStart = i;
1025 args.quote = Commands.complQuote[""];
1030 args.completeArg = 0;
1033 let fail = function fail(error) {
1035 complete.message = error;
1037 util.assert(false, error);
1041 while (i < str.length || complete) {
1045 i += re.exec(str)[0].length;
1047 if (str[i] == "|") {
1048 args.string = str.slice(0, i);
1049 args.trailing = str.slice(i + 1);
1052 if (i == str.length && !complete)
1058 var sub = str.substr(i);
1059 if ((!onlyArgumentsRemaining) && /^--(\s|$)/.test(sub)) {
1060 onlyArgumentsRemaining = true;
1066 if (!onlyArgumentsRemaining) {
1067 for (let [, opt] in Iterator(options)) {
1068 for (let [, optname] in Iterator(opt.names)) {
1069 if (sub.startsWith(optname)) {
1071 let invalid = false;
1072 let arg, quote, quoted;
1074 let sep = sub[optname.length];
1075 let argString = sub.substr(optname.length + 1);
1076 if (sep == "=" || /\s/.test(sep) && opt.type != CommandOption.NOARG) {
1077 [count, quoted, quote, error] = getNextArg(argString, true);
1078 arg = Option.dequote(quoted);
1079 util.assert(!error, error);
1081 // if we add the argument to an option after a space, it MUST not be empty
1082 if (sep != "=" && !quote && arg.length == 0 && !complete)
1085 count++; // to compensate the "=" character
1087 else if (!/\s/.test(sep) && sep != undefined) // this isn't really an option as it has trailing characters, parse it as an argument
1091 if (!complete && quote)
1092 fail(_("command.invalidOptArg", optname, argString));
1095 if (complete && !/[\s=]/.test(sep))
1098 if (complete && count > 0) {
1099 args.completeStart += optname.length + 1;
1100 args.completeOpt = opt;
1101 args.completeFilter = arg;
1102 args.quote = Commands.complQuote[quote] || Commands.complQuote[""];
1104 if (!complete || arg != null) {
1107 arg = opt.type.parse(arg, quoted);
1109 if (complete && isArray(arg)) {
1110 args.completeFilter = arg[arg.length - 1] || "";
1111 args.completeStart += orig.length - args.completeFilter.length;
1114 if (arg == null || (typeof arg == "number" && isNaN(arg))) {
1115 if (!complete || orig != "" || args.completeStart != str.length)
1116 fail(_("command.invalidOptTypeArg", opt.type.description, optname, quoted));
1118 complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
1122 // we have a validator function
1123 if (typeof opt.validator == "function") {
1124 if (opt.validator(arg, quoted) == false && (arg || !complete)) {
1125 fail(_("command.invalidOptArg", optname, quoted));
1126 if (complete) // Always true.
1127 complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
1132 if (arg != null || opt.type == CommandOption.NOARG) {
1133 // option allowed multiple times
1135 args[opt.names[0]] = (args[opt.names[0]] || []).concat(arg);
1137 Class.replaceProperty(args, opt.names[0], opt.type == CommandOption.NOARG || arg);
1139 args.explicitOpts[opt.names[0]] = args[opt.names[0]];
1142 i += optname.length + count;
1143 if (i == str.length)
1147 // if it is invalid, just fall through and try the next argument
1156 if (argCount == "0" || args.length > 0 && (/[1?]/.test(argCount)))
1157 complete.highlight(i, sub.length, "SPELLCHECK");
1159 if (args.length === literal) {
1161 args.completeArg = args.length;
1163 let re = /(?:\s*(?=\n)|\s*)([^]*)/gy;
1164 re.lastIndex = argStart || 0;
1165 sub = re.exec(str)[1];
1168 if (sub.substr(0, 2) === "<<" && hereDoc)
1169 let ([count, arg] = getNextArg(sub)) {
1170 sub = arg + sub.substr(count);
1178 // if not an option, treat this token as an argument
1179 let [count, arg, quote, error] = getNextArg(sub);
1180 util.assert(!error, error);
1183 args.quote = Commands.complQuote[quote] || Commands.complQuote[""];
1184 args.completeFilter = arg || "";
1186 else if (count == -1)
1187 fail(_("command.parsing", arg));
1188 else if (!onlyArgumentsRemaining && sub[0] === "-")
1189 fail(_("command.invalidOpt", arg));
1194 args.completeArg = args.length - 1;
1197 if (count <= 0 || i == str.length)
1201 if (complete && args.trailing == null) {
1202 if (args.completeOpt) {
1203 let opt = args.completeOpt;
1204 let context = complete.fork(opt.names[0], args.completeStart);
1205 let arg = args.explicitOpts[opt.names[0]];
1206 context.filter = args.completeFilter;
1209 context.filters.push(item => arg.indexOf(item.text) === -1);
1211 if (typeof opt.completer == "function")
1212 var compl = opt.completer(context, args);
1214 compl = opt.completer || [];
1216 context.title = [opt.names[0]];
1217 context.quote = args.quote;
1219 context.completions = compl;
1221 complete.advance(args.completeStart);
1224 description: function (opt) messages.get(["command", params.name, "options", opt.names[0], "description"].join("."), opt.description)
1226 complete.title = ["Options"];
1228 complete.completions = completeOpts;
1236 catch (e if complete && e instanceof FailedAssertion) {
1237 complete.message = e;
1242 nameRegexp: util.regexp(literal(/*
1249 forbid: util.regexp(String.replace(literal(/*
1250 U0000-U002c // U002d -
1252 U003a-U0040 // U0041-U005a a-z
1253 U005b-U0060 // U0061-U007a A-Z
1255 U02b0-U02ff // Spacing Modifier Letters
1256 U0300-U036f // Combining Diacritical Marks
1257 U1dc0-U1dff // Combining Diacritical Marks Supplement
1258 U2000-U206f // General Punctuation
1259 U20a0-U20cf // Currency Symbols
1260 U20d0-U20ff // Combining Diacritical Marks for Symbols
1261 U2400-U243f // Control Pictures
1262 U2440-U245f // Optical Character Recognition
1263 U2500-U257f // Box Drawing
1264 U2580-U259f // Block Elements
1265 U2700-U27bf // Dingbats
1266 Ufe20-Ufe2f // Combining Half Marks
1267 Ufe30-Ufe4f // CJK Compatibility Forms
1268 Ufe50-Ufe6f // Small Form Variants
1269 Ufe70-Ufeff // Arabic Presentation Forms-B
1270 Uff00-Uffef // Halfwidth and Fullwidth Forms
1271 Ufff0-Uffff // Specials
1272 */), /U/g, "\\u"), "x")
1275 validName: Class.Memoize(function validName() util.regexp("^" + this.nameRegexp.source + "$")),
1277 commandRegexp: Class.Memoize(function commandRegexp() util.regexp(literal(/*
1280 (?P<prespace> [:\s]*)
1281 (?P<count> (?:\d+ | %)? )
1283 (?: (?P<group> <name>) : )?
1284 (?P<cmd> (?:-? [()] | <name> | !)? ))
1293 name: this.nameRegexp
1297 * Parses a complete Ex command.
1299 * The parsed string is returned as an Array like
1300 * [count, command, bang, args]:
1301 * count - any count specified
1302 * command - the Ex command name
1303 * bang - whether the special "bang" version was called
1304 * args - the commands full argument string
1305 * E.g. ":2foo! bar" -> [2, "foo", true, "bar"]
1307 * @param {string} str The Ex command line string.
1310 // FIXME: why does this return an Array rather than Object?
1311 parseCommand: function parseCommand(str) {
1313 str.replace(/\s*".*$/, "");
1315 let matches = this.commandRegexp.exec(str);
1319 let { spec, count, group, cmd, bang, space, args } = matches;
1321 [cmd, bang] = [bang, cmd];
1323 if (!cmd || args && args[0] != "|" && !(space || cmd == "!"))
1328 count = count == "%" ? this.COUNT_ALL : parseInt(count, 10);
1330 count = this.COUNT_NONE;
1332 return [count, cmd, !!bang, args || "", spec.length, group];
1335 parseCommands: function parseCommands(str, complete) {
1336 const { contexts } = this.modules;
1338 let [count, cmd, bang, args, len, group] = commands.parseCommand(str);
1340 var command = this.get(cmd || "");
1341 else if (group = contexts.getGroup(group, "commands"))
1342 command = group.get(cmd || "");
1344 if (command == null) {
1345 yield [null, { commandString: str }];
1350 var context = complete.fork(command.name).fork("opts", len);;
1352 if (!complete || /(\w|^)[!\s]/.test(str))
1353 args = command.parseArgs(args, context, { count: count, bang: bang });
1355 args = this.parseArgs(args, { extra: { count: count, bang: bang } });
1356 args.context = this.context;
1357 args.commandName = cmd;
1358 args.commandString = str.substr(0, len) + args.string;
1359 str = args.trailing;
1360 yield [command, args];
1367 subCommands: function subCommands(command) {
1368 let commands = [command];
1369 while (command = commands.shift())
1371 for (let [command, args] in this.parseCommands(command)) {
1373 yield [command, args];
1374 if (command.subCommand && args[command.subCommand])
1375 commands.push(args[command.subCommand]);
1383 get complQuote() Commands.complQuote,
1386 get quoteArg() Commands.quoteArg // XXX: better somewhere else?
1389 // returns [count, parsed_argument]
1390 parseArg: function parseArg(str, sep, keepQuotes) {
1393 let len = str.length;
1395 function fixEscapes(str) str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4}|(.))/g,
1396 (m, n1) => n1 || m);
1401 sep = sep != null ? sep : /\s/;
1402 let re1 = RegExp("^" + (sep.source === "" ? "(?!)" : sep.source));
1403 let re2 = RegExp(/^()((?:[^\\S"']|\\.)+)((?:\\$)?)/.source.replace("S", sep.source));
1405 while (str.length && !re1.test(str)) {
1407 if ((res = re2.exec(str)))
1408 arg += keepQuotes ? res[0] : res[2].replace(/\\(.)/g, "$1");
1409 else if ((res = /^(")((?:[^\\"]|\\.)*)("?)/.exec(str)))
1410 arg += keepQuotes ? res[0] : JSON.parse(fixEscapes(res[0]) + (res[3] ? "" : '"'));
1411 else if ((res = /^(')((?:[^']|'')*)('?)/.exec(str)))
1412 arg += keepQuotes ? res[0] : res[2].replace("''", "'", "g");
1420 str = str.substr(res[0].length);
1423 return [len - str.length, arg, quote];
1426 quote: function quote(str) Commands.quoteArg[
1427 /[\b\f\n\r\t]/.test(str) ? '"' :
1428 /[\s"'\\]|^$|^-/.test(str) ? "'"
1431 completion: function initCompletion(dactyl, modules, window) {
1432 const { completion, contexts } = modules;
1434 completion.command = function command(context, group) {
1435 context.title = ["Command"];
1436 context.keys = { text: "longNames", description: "description" };
1438 context.generate = () => group._list;
1440 context.generate = () => modules.commands.hives.map(h => h._list).flatten();
1443 // provides completions for ex commands, including their arguments
1444 completion.ex = function ex(context) {
1445 const { commands } = modules;
1447 // if there is no space between the command name and the cursor
1448 // then get completions of the command name
1449 for (var [command, args] in commands.parseCommands(context.filter, context))
1451 context.advance(args.commandString.length + 1);
1453 args = { commandString: context.filter };
1455 let match = commands.commandRegexp.exec(args.commandString);
1460 context.advance(match.group.length + 1);
1462 context.advance(match.prespace.length + match.count.length);
1463 if (!(match.bang || match.space)) {
1464 context.fork("", 0, this, "command", match.group && contexts.getGroup(match.group, "commands"));
1468 // dynamically get completions as specified with the command's completer function
1469 context.highlight();
1471 context.message = _("command.noSuch", match.cmd);
1472 context.highlight(0, match.cmd.length, "SPELLCHECK");
1476 let cmdContext = context.fork(command.name + "/args", match.fullCmd.length + match.bang.length + match.space.length);
1478 if (!cmdContext.waitingForTab) {
1479 if (!args.completeOpt && command.completer && args.completeStart != null) {
1480 cmdContext.advance(args.completeStart);
1481 cmdContext.quote = args.quote;
1482 cmdContext.filter = args.completeFilter;
1483 command.completer.call(command, cmdContext, args);
1488 util.reportError(e);
1489 cmdContext.message = _("error.error", e);
1493 completion.exMacro = function exMacro(context, args, cmd) {
1494 if (!cmd.action.macro)
1496 let { macro } = cmd.action;
1498 let start = "«%-d-]'", end = "'[-d-%»";
1500 let n = /^\d+$/.test(cmd.argCount) ? parseInt(cmd.argCount) : 12;
1501 for (let i = args.completeArg; i < n; i++)
1502 args[i] = start + i + end;
1505 args: { __proto__: args, toString: function () this.join(" ") },
1506 bang: args.bang ? "!" : "",
1510 if (!macro.valid(params))
1513 let str = macro(params);
1514 let idx = str.indexOf(start);
1515 if (!~idx || !/^(')?(\d+)'/.test(str.substr(idx + start.length))
1516 || RegExp.$2 != args.completeArg)
1519 let quote = RegExp.$2;
1520 context.quote = null;
1521 context.offset -= idx;
1522 context.filter = str.substr(0, idx) + (quote ? Option.quote : util.identity)(context.filter);
1524 context.fork("ex", 0, completion, "ex");
1527 completion.userCommand = function userCommand(context, group) {
1528 context.title = ["User Command", "Definition"];
1529 context.keys = { text: "name", description: "replacementText" };
1530 context.completions = group || modules.commands.user;
1534 commands: function initCommands(dactyl, modules, window) {
1535 const { commands, contexts } = modules;
1537 commands.add(["(", "-("], "",
1538 function (args) { dactyl.echoerr(_("dactyl.cheerUp")); },
1540 commands.add([")", "-)"], "",
1541 function (args) { dactyl.echoerr(_("dactyl.somberDown")); },
1544 commands.add(["com[mand]"],
1545 "List or define commands",
1549 util.assert(!cmd || cmd.split(",").every(commands.validName.bound.test),
1550 _("command.invalidName", cmd));
1552 if (args.length <= 1)
1553 commands.list(cmd, args.explicitOpts["-group"] ? [args["-group"]] : null);
1555 util.assert(args["-group"].modifiable,
1556 _("group.cantChangeBuiltin", _("command.commands")));
1558 let completer = args["-complete"];
1559 let completerFunc = function (context, args) modules.completion.exMacro(context, args, this);
1562 if (/^custom,/.test(completer)) {
1563 completer = completer.substr(7);
1565 if (contexts.context)
1566 var ctxt = update({}, contexts.context || {});
1567 completerFunc = function (context) {
1568 var result = contexts.withSavedValues(["context"], function () {
1569 contexts.context = ctxt;
1570 return dactyl.userEval(completer);
1572 if (callable(result))
1573 return result.apply(this, arguments);
1575 return context.completions = result;
1579 completerFunc = context => modules.completion.bound[config.completers[completer]](context);
1582 let added = args["-group"].add(cmd.split(","),
1583 args["-description"],
1584 contexts.bindMacro(args, "-ex",
1585 function makeParams(args, modifiers) ({
1588 toString: function () this.string
1590 bang: this.bang && args.bang ? "!" : "",
1591 count: this.count && args.count
1594 argCount: args["-nargs"],
1595 bang: args["-bang"],
1596 count: args["-count"],
1597 completer: completerFunc,
1598 literal: args["-literal"],
1599 persist: !args["-nopersist"],
1600 replacementText: args.literalArg,
1601 context: contexts.context && update({}, contexts.context)
1605 dactyl.echoerr(_("command.exists"));
1609 completer: function (context, args) {
1610 const { completion } = modules;
1611 if (args.completeArg == 0)
1612 completion.userCommand(context, args["-group"]);
1614 args["-javascript"] ? completion.javascript(context) : completion.ex(context);
1618 { names: ["-bang", "-b"], description: "Command may be followed by a !" },
1619 { names: ["-count", "-c"], description: "Command may be preceded by a count" },
1621 // TODO: "E180: invalid complete value: " + arg
1622 names: ["-complete", "-C"],
1623 description: "The argument completion function",
1624 completer: function (context) [[k, ""] for ([k, v] in Iterator(config.completers))],
1625 type: CommandOption.STRING,
1626 validator: function (arg) arg in config.completers || /^custom,/.test(arg),
1629 names: ["-description", "-desc", "-d"],
1630 description: "A user-visible description of the command",
1631 default: "User-defined command",
1632 type: CommandOption.STRING
1634 contexts.GroupFlag("commands"),
1636 names: ["-javascript", "-js", "-j"],
1637 description: "Execute the definition as JavaScript rather than Ex commands"
1640 names: ["-literal", "-l"],
1641 description: "Process the specified argument ignoring any quoting or meta characters",
1642 type: CommandOption.INT
1645 names: ["-nargs", "-a"],
1646 description: "The allowed number of arguments",
1647 completer: [["0", "No arguments are allowed (default)"],
1648 ["1", "One argument is allowed"],
1649 ["*", "Zero or more arguments are allowed"],
1650 ["?", "Zero or one argument is allowed"],
1651 ["+", "One or more arguments are allowed"]],
1653 type: CommandOption.STRING,
1654 validator: bind("test", /^[01*?+]$/)
1657 names: ["-nopersist", "-n"],
1658 description: "Do not save this command to an auto-generated RC file"
1663 serialize: function () array(commands.userHives)
1664 .filter(h => h.persist)
1669 options: iter([v, typeof cmd[k] == "boolean" ? null : cmd[k]]
1670 // FIXME: this map is expressed multiple times
1671 for ([k, v] in Iterator({
1675 description: "-description"
1677 if (cmd[k])).toObject(),
1678 arguments: [cmd.name],
1679 literalArg: cmd.action,
1680 ignoreDefaults: true
1682 for (cmd in hive) if (cmd.persist)
1687 commands.add(["delc[ommand]"],
1688 "Delete the specified user-defined command",
1690 util.assert(args.bang ^ !!args[0], _("error.argumentOrBang"));
1694 args["-group"].clear();
1695 else if (args["-group"].get(name))
1696 args["-group"].remove(name);
1698 dactyl.echoerr(_("command.noSuchUser", name));
1702 completer: function (context, args) modules.completion.userCommand(context, args["-group"]),
1703 options: [contexts.GroupFlag("commands")]
1706 commands.add(["comp[letions]"],
1707 "List the completion results for a given command substring",
1708 function (args) { modules.completion.listCompleter("ex", args[0]); },
1711 completer: function (context, args) modules.completion.ex(context),
1715 dactyl.addUsageCommand({
1716 name: ["listc[ommands]", "lc"],
1717 description: "List all Ex commands along with their short descriptions",
1719 iterate: function (args) commands.iterator().map(function (cmd) ({
1722 cmd.hive == commands.builtin ? "" : ["span", { highlight: "Object", style: "padding-right: 1em;" },
1726 iterateIndex: function (args) let (tags = help.tags)
1727 this.iterate(args).filter(cmd => (cmd.hive === commands.builtin || hasOwnProperty(tags, cmd.helpTag))),
1729 headings: ["Command", "Group", "Description"],
1730 description: function (cmd) template.linkifyHelp(cmd.description + (cmd.replacementText ? ": " + cmd.action : "")),
1731 help: function (cmd) ":" + cmd.name
1735 commands.add(["y[ank]"],
1736 "Yank the output of the given command to the clipboard",
1738 let cmd = /^:/.test(args[0]) ? args[0] : ":echo " + args[0];
1740 let res = modules.commandline.withOutputToString(commands.execute, commands, cmd);
1742 dactyl.clipboardWrite(res);
1744 let lines = res.split("\n").length;
1745 dactyl.echomsg(_("command.yank.yankedLine" + (lines == 1 ? "" : "s"), lines));
1749 completer: function (context) modules.completion[/^:/.test(context.filter) ? "ex" : "javascript"](context),
1753 javascript: function initJavascript(dactyl, modules, window) {
1754 const { JavaScript, commands } = modules;
1756 JavaScript.setCompleter([CommandHive.prototype.get, CommandHive.prototype.remove],
1757 [function () [[c.names, c.description] for (c in this)]]);
1758 JavaScript.setCompleter([Commands.prototype.get],
1759 [function () [[c.names, c.description] for (c in this.iterator())]]);
1761 mappings: function initMappings(dactyl, modules, window) {
1762 const { commands, mappings, modes } = modules;
1764 mappings.add([modes.COMMAND],
1765 ["@:"], "Repeat the last Ex command",
1766 function ({ count }) {
1767 if (commands.repeat) {
1768 for (let i in util.interruptibleRange(0, Math.max(count, 1), 100))
1769 dactyl.execute(commands.repeat);
1772 dactyl.echoerr(_("command.noPrevious"));
1778 let quote = function quote(q, list, map=Commands.quoteMap) {
1779 let re = RegExp("[" + list + "]", "g");
1780 function quote(str) (q + String.replace(str, re, $0 => ($0 in map ? map[$0] : ("\\" + $0)))
1786 Commands.quoteMap = {
1791 Commands.quoteArg = {
1792 '"': quote('"', '\n\t"\\\\'),
1793 "'": quote("'", "'", { "'": "''" }),
1794 "": quote("", "|\\\\\\s'\"")
1796 Commands.complQuote = {
1797 '"': ['"', quote("", Commands.quoteArg['"'].list), '"'],
1798 "'": ["'", quote("", Commands.quoteArg["'"].list), "'"],
1799 "": ["", Commands.quoteArg[""], ""]
1802 Commands.parseBool = function (arg) {
1803 if (/^(true|1|on)$/i.test(arg))
1805 if (/^(false|0|off)$/i.test(arg))
1812 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
1814 // vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: