// Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns // Copyright (c) 2008-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. "use strict"; /** @scope modules */ var AutoCommand = Struct("event", "filter", "command"); update(AutoCommand.prototype, { eventName: Class.Memoize(function () { return this.event.toLowerCase(); }), match: function (event, pattern) { return (!event || this.eventName == event.toLowerCase()) && (!pattern || String(this.filter) === String(pattern)); } }); var AutoCmdHive = Class("AutoCmdHive", Contexts.Hive, { init: function init(group) { init.supercall(this, group); this._store = []; }, "@@iterator": function () this._store[Symbol.iterator](), /** * Adds a new autocommand. *cmd* will be executed when one of the specified * *events* occurs and the URL of the applicable buffer matches *regexp*. * * @param {Array} events The array of event names for which this * autocommand should be executed. * @param {string} pattern The URL pattern to match against the buffer URL. * @param {string} cmd The Ex command to run. */ add: function (events, pattern, cmd) { if (!callable(pattern)) pattern = Group.compileFilter(pattern); for (let event of events) this._store.push(AutoCommand(event, pattern, cmd)); }, /** * Returns all autocommands with a matching *event* and *filter*. * * @param {string} event The event name filter. * @param {string} filter The URL pattern filter. * @returns {[AutoCommand]} */ get: function (event, filter) { filter = filter && String(Group.compileFilter(filter)); return this._store.filter(cmd => cmd.match(event, filter)); }, /** * Deletes all autocommands with a matching *event* and *filter*. * * @param {string} event The event name filter. * @param {string} filter The URL pattern filter. */ remove: function (event, filter) { filter = filter && String(Group.compileFilter(filter)); this._store = this._store.filter(cmd => !cmd.match(event, filter)); } }); /** * @instance autocommands */ var AutoCommands = Module("autocommands", { get activeHives() { return contexts.allGroups.autocmd.filter(h => h._store.length); }, add: deprecated("group.autocmd.add", { get: function add() autocommands.user.bound.add }), get: deprecated("group.autocmd.get", { get: function get() autocommands.user.bound.get }), remove: deprecated("group.autocmd.remove", { get: function remove() autocommands.user.bound.remove }), /** * Lists all autocommands with a matching *event*, *regexp* and optionally * *hives*. * * @param {string} event The event name filter. * @param {string} regexp The URL pattern filter. * @param {[Hive]} hives List of hives. * @optional */ list: function (event, regexp, hives) { hives = hives || this.activeHives; function cmds(hive) { let cmds = {}; hive._store.forEach(function (autoCmd) { if (autoCmd.match(event, regexp)) { cmds[autoCmd.event] = cmds[autoCmd.event] || []; cmds[autoCmd.event].push(autoCmd); } }); return cmds; } let table = ( ["table", {}, ["tr", { highlight: "Title" }, ["td", { colspan: "3" }, "----- Auto Commands -----"]], hives.map(hive => [ ["tr", {}, ["td", { colspan: "3" }, ["span", { highlight: "Title" }, hive.name], " ", hive.filter.toJSONXML(modules)]], ["tr", { style: "height: .5ex;" }], iter(cmds(hive)).map(([event, items]) => [ ["tr", { style: "height: .5ex;" }], items.map((item, i) => ["tr", {}, ["td", { highlight: "Title", style: "padding-left: 1em; padding-right: 1em;" }, i == 0 ? event : ""], ["td", {}, item.filter.toJSONXML ? item.filter.toJSONXML(modules) : String(item.filter)], ["td", {}, String(item.command)]]), ["tr", { style: "height: .5ex;" }]]).toArray(), ["tr", { style: "height: .5ex;" }] ])]); commandline.commandOutput(table); }, /** * Triggers the execution of all autocommands registered for *event*. A map * of *args* is passed to each autocommand when it is being executed. * * @param {string} event The event to fire. * @param {Object} args The args to pass to each autocommand. */ trigger: function (event, args) { if (options.get("eventignore").has(event)) return; dactyl.echomsg(_("autocmd.executing", event, '"*"'), 8); let lastPattern = null; var { url, doc } = args; if (url) uri = util.createURI(url); else var { uri, doc } = buffer; event = event.toLowerCase(); for (let hive of this.matchingHives(uri, doc)) { let args = hive.makeArgs(doc, null, arguments[1]); for (let autoCmd of hive._store) if (autoCmd.eventName === event && autoCmd.filter(uri, doc)) { if (!lastPattern || lastPattern !== String(autoCmd.filter)) dactyl.echomsg(_("autocmd.executing", event, autoCmd.filter), 8); lastPattern = String(autoCmd.filter); dactyl.echomsg(_("autocmd.autocommand", autoCmd.command), 9); dactyl.trapErrors(autoCmd.command, autoCmd, args); } } } }, { }, { contexts: function initContexts() { update(AutoCommands.prototype, { hives: contexts.Hives("autocmd", AutoCmdHive), user: contexts.hives.autocmd.user, allHives: contexts.allGroups.autocmd, matchingHives: function matchingHives(uri, doc) contexts.matchingGroups(uri, doc).autocmd }); }, commands: function initCommands() { commands.add(["au[tocmd]"], "Execute commands automatically on events", function (args) { let [event, filter, cmd] = args; let events = []; if (event) { // NOTE: event can only be a comma separated list for |:au {event} {pat} {cmd}| let validEvents = Object.keys(config.autocommands).map(String.toLowerCase); validEvents.push("*"); events = Option.parse.stringlist(event); dactyl.assert(events.every(e => validEvents.indexOf(e.toLowerCase()) >= 0), _("autocmd.noGroup", event)); } if (args.length > 2) { // add new command, possibly removing all others with the same event/pattern if (args.bang) args["-group"].remove(event, filter); cmd = contexts.bindMacro(args, "-ex", params => params); args["-group"].add(events, filter, cmd); } else { if (event == "*") event = null; if (args.bang) { // TODO: "*" only appears to work in Vim when there is a {group} specified if (args[0] != "*" || args.length > 1) args["-group"].remove(event, filter); // remove all } else autocommands.list(event, filter, args.explicitOpts["-group"] ? [args["-group"]] : null); // list all } }, { bang: true, completer: function (context, args) { if (args.length == 1) completion.autocmdEvent(context); else if (args.length == 3) args["-javascript"] ? completion.javascript(context) : completion.ex(context); }, hereDoc: true, keepQuotes: true, literal: 2, options: [ contexts.GroupFlag("autocmd"), { names: ["-javascript", "-js"], description: "Interpret the action as JavaScript code rather than an Ex command" } ] }); [ { name: "do[autocmd]", description: "Apply the autocommands matching the specified URL pattern to the current buffer" }, { name: "doautoa[ll]", description: "Apply the autocommands matching the specified URL pattern to all buffers" } ].forEach(function (command) { commands.add([command.name], command.description, // TODO: Perhaps this should take -args to pass to the command? function (args) { // Vim compatible if (args.length == 0) return void dactyl.echomsg(_("autocmd.noMatching")); let [event, url] = args; let uri = util.createURI(url) || buffer.uri; let validEvents = Object.keys(config.autocommands); // TODO: add command validators dactyl.assert(event != "*", _("autocmd.cantExecuteAll")); dactyl.assert(validEvents.indexOf(event) >= 0, _("autocmd.noGroup", args)); dactyl.assert(autocommands.get(event).some(c => c.filter(uri)), _("autocmd.noMatching")); if (this.name == "doautoall" && dactyl.has("tabs")) { let current = tabs.index(); for (let i = 0; i < tabs.count; i++) { tabs.select(i); // if no url arg is specified use the current buffer's URL autocommands.trigger(event, { url: uri.spec }); } tabs.select(current); } else autocommands.trigger(event, { url: uri.spec }); }, { argCount: "*", // FIXME: kludged for proper error message should be "1". completer: function (context) { completion.autocmdEvent(context); }, keepQuotes: true }); }); }, completion: function initCompletion() { completion.autocmdEvent = function autocmdEvent(context) { context.completions = iter(config.autocommands); }; }, javascript: function initJavascript() { JavaScript.setCompleter(AutoCmdHive.prototype.get, [() => iter(config.autocommands)]); }, options: function initOptions() { options.add(["eventignore", "ei"], "List of autocommand event names which should be ignored", "stringlist", "", { values: iter(update({ all: "All Events" }, config.autocommands)).toArray(), has: Option.has.toggleAll }); } }); // vim: set fdm=marker sw=4 sts=4 ts=8 et: