]> git.donarmstrong.com Git - dactyl.git/blob - common/content/autocommands.js
finalize changelog for 7904
[dactyl.git] / common / content / autocommands.js
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>
4 //
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
7 "use strict";
8
9 /** @scope modules */
10
11 var AutoCommand = Struct("event", "filter", "command");
12 update(AutoCommand.prototype, {
13     eventName: Class.Memoize(function () this.event.toLowerCase()),
14
15     match: function (event, pattern) {
16         return (!event || this.eventName == event.toLowerCase()) && (!pattern || String(this.filter) === String(pattern));
17     }
18 });
19
20 var AutoCmdHive = Class("AutoCmdHive", Contexts.Hive, {
21     init: function init(group) {
22         init.supercall(this, group);
23         this._store = [];
24     },
25
26     __iterator__: function () array.iterValues(this._store),
27
28     /**
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*.
31      *
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.
36      */
37     add: function (events, pattern, cmd) {
38         if (!callable(pattern))
39             pattern = Group.compileFilter(pattern);
40
41         for (let event in values(events))
42             this._store.push(AutoCommand(event, pattern, cmd));
43     },
44
45     /**
46      * Returns all autocommands with a matching *event* and *filter*.
47      *
48      * @param {string} event The event name filter.
49      * @param {string} filter The URL pattern filter.
50      * @returns {[AutoCommand]}
51      */
52     get: function (event, filter) {
53         filter = filter && String(Group.compileFilter(filter));
54         return this._store.filter(cmd => cmd.match(event, filter));
55     },
56
57     /**
58      * Deletes all autocommands with a matching *event* and *filter*.
59      *
60      * @param {string} event The event name filter.
61      * @param {string} filter The URL pattern filter.
62      */
63     remove: function (event, filter) {
64         filter = filter && String(Group.compileFilter(filter));
65         this._store = this._store.filter(cmd => !cmd.match(event, filter));
66     },
67 });
68
69 /**
70  * @instance autocommands
71  */
72 var AutoCommands = Module("autocommands", {
73     init: function () {
74         if (!config.haveGecko("26"))
75             delete config.autocommands.DownloadPost; // FIXME
76     },
77
78     get activeHives() contexts.allGroups.autocmd.filter(h => h._store.length),
79
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 }),
83
84     /**
85      * Lists all autocommands with a matching *event*, *regexp* and optionally
86      * *hives*.
87      *
88      * @param {string} event The event name filter.
89      * @param {string} regexp The URL pattern filter.
90      * @param {[Hive]} hives List of hives.
91      * @optional
92      */
93     list: function (event, regexp, hives) {
94
95         let hives = hives || this.activeHives;
96
97         function cmds(hive) {
98             let cmds = {};
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);
103                 }
104             });
105             return cmds;
106         }
107
108         let table = (
109             ["table", {},
110                 ["tr", { highlight: "Title" },
111                     ["td", { colspan: "3" }, "----- Auto Commands -----"]],
112                 hives.map(hive => [
113                     ["tr", {},
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) =>
121                             ["tr", {},
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;" }]
128                 ])]);
129         commandline.commandOutput(table);
130     },
131
132     /**
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.
135      *
136      * @param {string} event The event to fire.
137      * @param {Object} args The args to pass to each autocommand.
138      */
139     trigger: function (event, args) {
140         if (options.get("eventignore").has(event))
141             return;
142
143         dactyl.echomsg(_("autocmd.executing", event, "*".quote()), 8);
144
145         let lastPattern = null;
146         var { url, doc } = args;
147         if (url)
148             uri = util.createURI(url);
149         else
150             var { uri, doc } = buffer;
151
152         event = event.toLowerCase();
153         for (let hive in values(this.matchingHives(uri, doc))) {
154             let args = hive.makeArgs(doc, null, arguments[1]);
155
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);
160
161                     lastPattern = String(autoCmd.filter);
162                     dactyl.echomsg(_("autocmd.autocommand", autoCmd.command), 9);
163
164                     dactyl.trapErrors(autoCmd.command, autoCmd, args);
165                 }
166         }
167     }
168 }, {
169 }, {
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
176         });
177     },
178     commands: function initCommands() {
179         commands.add(["au[tocmd]"],
180             "Execute commands automatically on events",
181             function (args) {
182                 let [event, filter, cmd] = args;
183                 let events = [];
184
185                 if (event) {
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("*");
189
190                     events = Option.parse.stringlist(event);
191                     dactyl.assert(events.every(e => validEvents.indexOf(e.toLowerCase()) >= 0),
192                                   _("autocmd.noGroup", event));
193                 }
194
195                 if (args.length > 2) { // add new command, possibly removing all others with the same event/pattern
196                     if (args.bang)
197                         args["-group"].remove(event, filter);
198                     cmd = contexts.bindMacro(args, "-ex", params => params);
199                     args["-group"].add(events, filter, cmd);
200                 }
201                 else {
202                     if (event == "*")
203                         event = null;
204
205                     if (args.bang) {
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
209                     }
210                     else
211                         autocommands.list(event, filter, args.explicitOpts["-group"] ? [args["-group"]] : null); // list all
212                 }
213             }, {
214                 bang: true,
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);
220                 },
221                 hereDoc: true,
222                 keepQuotes: true,
223                 literal: 2,
224                 options: [
225                     contexts.GroupFlag("autocmd"),
226                     {
227                         names: ["-javascript", "-js"],
228                         description: "Interpret the action as JavaScript code rather than an Ex command"
229                     }
230                 ]
231             });
232
233         [
234             {
235                 name: "do[autocmd]",
236                 description: "Apply the autocommands matching the specified URL pattern to the current buffer"
237             }, {
238                 name: "doautoa[ll]",
239                 description: "Apply the autocommands matching the specified URL pattern to all buffers"
240             }
241         ].forEach(function (command) {
242             commands.add([command.name],
243                 command.description,
244                 // TODO: Perhaps this should take -args to pass to the command?
245                 function (args) {
246                     // Vim compatible
247                     if (args.length == 0)
248                         return void dactyl.echomsg(_("autocmd.noMatching"));
249
250                     let [event, url] = args;
251                     let uri = util.createURI(url) || buffer.uri;
252                     let validEvents = Object.keys(config.autocommands);
253
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"));
261
262                     if (this.name == "doautoall" && dactyl.has("tabs")) {
263                         let current = tabs.index();
264
265                         for (let i = 0; i < tabs.count; i++) {
266                             tabs.select(i);
267                             // if no url arg is specified use the current buffer's URL
268                             autocommands.trigger(event, { url: uri.spec });
269                         }
270
271                         tabs.select(current);
272                     }
273                     else
274                         autocommands.trigger(event, { url: uri.spec });
275                 }, {
276                     argCount: "*", // FIXME: kludged for proper error message should be "1".
277                     completer: function (context) completion.autocmdEvent(context),
278                     keepQuotes: true
279                 });
280         });
281     },
282     completion: function initCompletion() {
283         completion.autocmdEvent = function autocmdEvent(context) {
284             context.completions = Iterator(config.autocommands);
285         };
286     },
287     javascript: function initJavascript() {
288         JavaScript.setCompleter(AutoCmdHive.prototype.get, [() => Iterator(config.autocommands)]);
289     },
290     options: function initOptions() {
291         options.add(["eventignore", "ei"],
292             "List of autocommand event names which should be ignored",
293             "stringlist", "",
294             {
295                 values: iter(update({ all: "All Events" }, config.autocommands)).toArray(),
296                 has: Option.has.toggleAll
297             });
298     }
299 });
300
301 // vim: set fdm=marker sw=4 sts=4 ts=8 et: