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@gmail.com>
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 var AutoCommand = Struct("event", "filter", "command");
12 update(AutoCommand.prototype, {
13 eventName: Class.Memoize(function () this.event.toLowerCase()),
15 match: function (event, pattern) {
16 return (!event || this.eventName == event.toLowerCase()) && (!pattern || String(this.filter) === String(pattern));
20 var AutoCmdHive = Class("AutoCmdHive", Contexts.Hive, {
21 init: function init(group) {
22 init.supercall(this, group);
26 __iterator__: function () array.iterValues(this._store),
29 * Adds a new autocommand. *cmd* will be executed when one of the specified
30 * *events* occurs and the URL of the applicable buffer matches *regexp*.
32 * @param {Array} events The array of event names for which this
33 * autocommand should be executed.
34 * @param {string} pattern The URL pattern to match against the buffer URL.
35 * @param {string} cmd The Ex command to run.
37 add: function (events, pattern, cmd) {
38 if (!callable(pattern))
39 pattern = Group.compileFilter(pattern);
41 for (let event in values(events))
42 this._store.push(AutoCommand(event, pattern, cmd));
46 * Returns all autocommands with a matching *event* and *filter*.
48 * @param {string} event The event name filter.
49 * @param {string} filter The URL pattern filter.
50 * @returns {[AutoCommand]}
52 get: function (event, filter) {
53 filter = filter && String(Group.compileFilter(filter));
54 return this._store.filter(cmd => cmd.match(event, filter));
58 * Deletes all autocommands with a matching *event* and *filter*.
60 * @param {string} event The event name filter.
61 * @param {string} filter The URL pattern filter.
63 remove: function (event, filter) {
64 filter = filter && String(Group.compileFilter(filter));
65 this._store = this._store.filter(cmd => !cmd.match(event, filter));
70 * @instance autocommands
72 var AutoCommands = Module("autocommands", {
74 if (!config.haveGecko("26"))
75 delete config.autocommands.DownloadPost; // FIXME
78 get activeHives() contexts.allGroups.autocmd.filter(h => h._store.length),
80 add: deprecated("group.autocmd.add", { get: function add() autocommands.user.bound.add }),
81 get: deprecated("group.autocmd.get", { get: function get() autocommands.user.bound.get }),
82 remove: deprecated("group.autocmd.remove", { get: function remove() autocommands.user.bound.remove }),
85 * Lists all autocommands with a matching *event*, *regexp* and optionally
88 * @param {string} event The event name filter.
89 * @param {string} regexp The URL pattern filter.
90 * @param {[Hive]} hives List of hives.
93 list: function (event, regexp, hives) {
95 let hives = hives || this.activeHives;
99 hive._store.forEach(function (autoCmd) {
100 if (autoCmd.match(event, regexp)) {
101 cmds[autoCmd.event] = cmds[autoCmd.event] || [];
102 cmds[autoCmd.event].push(autoCmd);
110 ["tr", { highlight: "Title" },
111 ["td", { colspan: "3" }, "----- Auto Commands -----"]],
114 ["td", { colspan: "3" },
115 ["span", { highlight: "Title" }, hive.name],
116 " ", hive.filter.toJSONXML(modules)]],
117 ["tr", { style: "height: .5ex;" }],
118 iter(cmds(hive)).map(([event, items]) => [
119 ["tr", { style: "height: .5ex;" }],
120 items.map((item, i) =>
122 ["td", { highlight: "Title", style: "padding-left: 1em; padding-right: 1em;" },
123 i == 0 ? event : ""],
124 ["td", {}, item.filter.toJSONXML ? item.filter.toJSONXML(modules) : String(item.filter)],
125 ["td", {}, String(item.command)]]),
126 ["tr", { style: "height: .5ex;" }]]).toArray(),
127 ["tr", { style: "height: .5ex;" }]
129 commandline.commandOutput(table);
133 * Triggers the execution of all autocommands registered for *event*. A map
134 * of *args* is passed to each autocommand when it is being executed.
136 * @param {string} event The event to fire.
137 * @param {Object} args The args to pass to each autocommand.
139 trigger: function (event, args) {
140 if (options.get("eventignore").has(event))
143 dactyl.echomsg(_("autocmd.executing", event, "*".quote()), 8);
145 let lastPattern = null;
146 var { url, doc } = args;
148 uri = util.createURI(url);
150 var { uri, doc } = buffer;
152 event = event.toLowerCase();
153 for (let hive in values(this.matchingHives(uri, doc))) {
154 let args = hive.makeArgs(doc, null, arguments[1]);
156 for (let autoCmd in values(hive._store))
157 if (autoCmd.eventName === event && autoCmd.filter(uri, doc)) {
158 if (!lastPattern || lastPattern !== String(autoCmd.filter))
159 dactyl.echomsg(_("autocmd.executing", event, autoCmd.filter), 8);
161 lastPattern = String(autoCmd.filter);
162 dactyl.echomsg(_("autocmd.autocommand", autoCmd.command), 9);
164 dactyl.trapErrors(autoCmd.command, autoCmd, args);
170 contexts: function initContexts() {
171 update(AutoCommands.prototype, {
172 hives: contexts.Hives("autocmd", AutoCmdHive),
173 user: contexts.hives.autocmd.user,
174 allHives: contexts.allGroups.autocmd,
175 matchingHives: function matchingHives(uri, doc) contexts.matchingGroups(uri, doc).autocmd
178 commands: function initCommands() {
179 commands.add(["au[tocmd]"],
180 "Execute commands automatically on events",
182 let [event, filter, cmd] = args;
186 // NOTE: event can only be a comma separated list for |:au {event} {pat} {cmd}|
187 let validEvents = Object.keys(config.autocommands).map(String.toLowerCase);
188 validEvents.push("*");
190 events = Option.parse.stringlist(event);
191 dactyl.assert(events.every(e => validEvents.indexOf(e.toLowerCase()) >= 0),
192 _("autocmd.noGroup", event));
195 if (args.length > 2) { // add new command, possibly removing all others with the same event/pattern
197 args["-group"].remove(event, filter);
198 cmd = contexts.bindMacro(args, "-ex", params => params);
199 args["-group"].add(events, filter, cmd);
206 // TODO: "*" only appears to work in Vim when there is a {group} specified
207 if (args[0] != "*" || args.length > 1)
208 args["-group"].remove(event, filter); // remove all
211 autocommands.list(event, filter, args.explicitOpts["-group"] ? [args["-group"]] : null); // list all
215 completer: function (context, args) {
216 if (args.length == 1)
217 return completion.autocmdEvent(context);
218 if (args.length == 3)
219 return args["-javascript"] ? completion.javascript(context) : completion.ex(context);
225 contexts.GroupFlag("autocmd"),
227 names: ["-javascript", "-js"],
228 description: "Interpret the action as JavaScript code rather than an Ex command"
236 description: "Apply the autocommands matching the specified URL pattern to the current buffer"
239 description: "Apply the autocommands matching the specified URL pattern to all buffers"
241 ].forEach(function (command) {
242 commands.add([command.name],
244 // TODO: Perhaps this should take -args to pass to the command?
247 if (args.length == 0)
248 return void dactyl.echomsg(_("autocmd.noMatching"));
250 let [event, url] = args;
251 let uri = util.createURI(url) || buffer.uri;
252 let validEvents = Object.keys(config.autocommands);
254 // TODO: add command validators
255 dactyl.assert(event != "*",
256 _("autocmd.cantExecuteAll"));
257 dactyl.assert(validEvents.indexOf(event) >= 0,
258 _("autocmd.noGroup", args));
259 dactyl.assert(autocommands.get(event).some(c => c.filter(uri)),
260 _("autocmd.noMatching"));
262 if (this.name == "doautoall" && dactyl.has("tabs")) {
263 let current = tabs.index();
265 for (let i = 0; i < tabs.count; i++) {
267 // if no url arg is specified use the current buffer's URL
268 autocommands.trigger(event, { url: uri.spec });
271 tabs.select(current);
274 autocommands.trigger(event, { url: uri.spec });
276 argCount: "*", // FIXME: kludged for proper error message should be "1".
277 completer: function (context) completion.autocmdEvent(context),
282 completion: function initCompletion() {
283 completion.autocmdEvent = function autocmdEvent(context) {
284 context.completions = Iterator(config.autocommands);
287 javascript: function initJavascript() {
288 JavaScript.setCompleter(AutoCmdHive.prototype.get, [() => Iterator(config.autocommands)]);
290 options: function initOptions() {
291 options.add(["eventignore", "ei"],
292 "List of autocommand event names which should be ignored",
295 values: iter(update({ all: "All Events" }, config.autocommands)).toArray(),
296 has: Option.has.toggleAll
301 // vim: set fdm=marker sw=4 sts=4 ts=8 et: