]> git.donarmstrong.com Git - dactyl.git/blob - common/content/autocommands.js
Import 1.0b7.1 supporting Firefox up to 8.*
[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-2011 by 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) === 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 *regexp*.
47      *
48      * @param {string} event The event name filter.
49      * @param {string} pattern The URL pattern filter.
50      * @returns {[AutoCommand]}
51      */
52     get: function (event, pattern) {
53         return this._store.filter(function (autoCmd) autoCmd.match(event, regexp));
54     },
55
56     /**
57      * Deletes all autocommands with a matching *event* and *regexp*.
58      *
59      * @param {string} event The event name filter.
60      * @param {string} regexp The URL pattern filter.
61      */
62     remove: function (event, regexp) {
63         this._store = this._store.filter(function (autoCmd) !autoCmd.match(event, regexp));
64     },
65 });
66
67 /**
68  * @instance autocommands
69  */
70 var AutoCommands = Module("autocommands", {
71     init: function () {
72         update(this, {
73             hives: contexts.Hives("autocmd", AutoCmdHive),
74             user: contexts.hives.autocmd.user,
75             allHives: contexts.allGroups.autocmd,
76             matchingHives: function matchingHives(uri, doc) contexts.matchingGroups(uri, doc).autocmd
77         });
78     },
79
80     get activeHives() contexts.allGroups.autocmd.filter(function (h) h._store.length),
81
82     add: deprecated("group.autocmd.add", { get: function add() autocommands.user.closure.add }),
83     get: deprecated("group.autocmd.get", { get: function get() autocommands.user.closure.get }),
84     remove: deprecated("group.autocmd.remove", { get: function remove() autocommands.user.closure.remove }),
85
86     /**
87      * Lists all autocommands with a matching *event*, *regexp* and optionally
88      * *hives*.
89      *
90      * @param {string} event The event name filter.
91      * @param {string} regexp The URL pattern filter.
92      * @param {[Hive]} hives List of hives.
93      * @optional
94      */
95     list: function (event, regexp, hives) {
96
97         let hives = hives || this.activeHives;
98
99         function cmds(hive) {
100             let cmds = {};
101             hive._store.forEach(function (autoCmd) {
102                 if (autoCmd.match(event, regexp)) {
103                     cmds[autoCmd.event] = cmds[autoCmd.event] || [];
104                     cmds[autoCmd.event].push(autoCmd);
105                 }
106             });
107             return cmds;
108         }
109
110         commandline.commandOutput(
111             <table>
112                 <tr highlight="Title">
113                     <td colspan="3">----- Auto Commands -----</td>
114                 </tr>
115                 {
116                     template.map(hives, function (hive)
117                         <tr highlight="Title">
118                             <td colspan="3">{hive.name}</td>
119                         </tr> +
120                         <tr style="height: .5ex;"/> +
121                         template.map(cmds(hive), function ([event, items])
122                             <tr style="height: .5ex;"/> +
123                             template.map(items, function (item, i)
124                                 <tr>
125                                     <td highlight="Title" style="padding-right: 1em;">{i == 0 ? event : ""}</td>
126                                     <td>{item.filter.toXML ? item.filter.toXML() : item.filter}</td>
127                                     <td>{item.command}</td>
128                                 </tr>) +
129                             <tr style="height: .5ex;"/>) +
130                         <tr style="height: .5ex;"/>)
131                 }
132             </table>);
133     },
134
135     /**
136      * Triggers the execution of all autocommands registered for *event*. A map
137      * of *args* is passed to each autocommand when it is being executed.
138      *
139      * @param {string} event The event to fire.
140      * @param {Object} args The args to pass to each autocommand.
141      */
142     trigger: function (event, args) {
143         if (options.get("eventignore").has(event))
144             return;
145
146         dactyl.echomsg(_("autocmd.executing", event, "*".quote()), 8);
147
148         let lastPattern = null;
149         var { url, doc } = args;
150         if (url)
151             uri = util.createURI(url);
152         else
153             var { uri, doc } = buffer;
154
155         event = event.toLowerCase();
156         for (let hive in values(this.matchingHives(uri, doc))) {
157             let args = update({},
158                               hive.argsExtra(arguments[1]),
159                               arguments[1]);
160
161             for (let autoCmd in values(hive._store))
162                 if (autoCmd.eventName === event && autoCmd.filter(uri, doc)) {
163                     if (!lastPattern || lastPattern !== String(autoCmd.filter))
164                         dactyl.echomsg(_("autocmd.executing", event, autoCmd.filter), 8);
165
166                     lastPattern = String(autoCmd.filter);
167                     dactyl.echomsg(_("autocmd.autocommand", autoCmd.command), 9);
168
169                     dactyl.trapErrors(autoCmd.command, autoCmd, args);
170                 }
171         }
172     }
173 }, {
174 }, {
175     commands: function () {
176         commands.add(["au[tocmd]"],
177             "Execute commands automatically on events",
178             function (args) {
179                 let [event, regexp, cmd] = args;
180                 let events = [];
181
182                 if (event) {
183                     // NOTE: event can only be a comma separated list for |:au {event} {pat} {cmd}|
184                     let validEvents = Object.keys(config.autocommands).map(String.toLowerCase);
185                     validEvents.push("*");
186
187                     events = Option.parse.stringlist(event);
188                     dactyl.assert(events.every(function (event) validEvents.indexOf(event.toLowerCase()) >= 0),
189                                   _("autocmd.noGroup", event));
190                 }
191
192                 if (args.length > 2) { // add new command, possibly removing all others with the same event/pattern
193                     if (args.bang)
194                         args["-group"].remove(event, regexp);
195                     cmd = contexts.bindMacro(args, "-ex", function (params) params);
196                     args["-group"].add(events, regexp, cmd);
197                 }
198                 else {
199                     if (event == "*")
200                         event = null;
201
202                     if (args.bang) {
203                         // TODO: "*" only appears to work in Vim when there is a {group} specified
204                         if (args[0] != "*" || args.length > 1)
205                             args["-group"].remove(event, regexp); // remove all
206                     }
207                     else
208                         autocommands.list(event, regexp, args.explicitOpts["-group"] ? [args["-group"]] : null); // list all
209                 }
210             }, {
211                 bang: true,
212                 completer: function (context, args) {
213                     if (args.length == 1)
214                         return completion.autocmdEvent(context);
215                     if (args.length == 3)
216                         return args["-javascript"] ? completion.javascript(context) : completion.ex(context);
217                 },
218                 hereDoc: true,
219                 keepQuotes: true,
220                 literal: 2,
221                 options: [
222                     contexts.GroupFlag("autocmd"),
223                     {
224                         names: ["-javascript", "-js"],
225                         description: "Interpret the action as JavaScript code rather than an Ex command"
226                     }
227                 ]
228             });
229
230         [
231             {
232                 name: "do[autocmd]",
233                 description: "Apply the autocommands matching the specified URL pattern to the current buffer"
234             }, {
235                 name: "doautoa[ll]",
236                 description: "Apply the autocommands matching the specified URL pattern to all buffers"
237             }
238         ].forEach(function (command) {
239             commands.add([command.name],
240                 command.description,
241                 // TODO: Perhaps this should take -args to pass to the command?
242                 function (args) {
243                     // Vim compatible
244                     if (args.length == 0)
245                         return void dactyl.echomsg(_("autocmd.noMatching"));
246
247                     let [event, url] = args;
248                     let defaultURL = url || buffer.uri.spec;
249                     let validEvents = Object.keys(config.autocommands);
250
251                     // TODO: add command validators
252                     dactyl.assert(event != "*",
253                                   _("autocmd.cantExecuteAll"));
254                     dactyl.assert(validEvents.indexOf(event) >= 0,
255                                   _("autocmd.noGroup", args));
256                     dactyl.assert(autocommands.get(event).some(function (c) c.patterns.some(function (re) re.test(defaultURL) ^ !re.result)),
257                                   _("autocmd.noMatching"));
258
259                     if (this.name == "doautoall" && dactyl.has("tabs")) {
260                         let current = tabs.index();
261
262                         for (let i = 0; i < tabs.count; i++) {
263                             tabs.select(i);
264                             // if no url arg is specified use the current buffer's URL
265                             autocommands.trigger(event, { url: url || buffer.uri.spec });
266                         }
267
268                         tabs.select(current);
269                     }
270                     else
271                         autocommands.trigger(event, { url: defaultURL });
272                 }, {
273                     argCount: "*", // FIXME: kludged for proper error message should be "1".
274                     completer: function (context) completion.autocmdEvent(context),
275                     keepQuotes: true
276                 });
277         });
278     },
279     completion: function () {
280         completion.autocmdEvent = function autocmdEvent(context) {
281             context.completions = Iterator(config.autocommands);
282         };
283     },
284     javascript: function () {
285         JavaScript.setCompleter(autocommands.user.get, [function () Iterator(config.autocommands)]);
286     },
287     options: function () {
288         options.add(["eventignore", "ei"],
289             "List of autocommand event names which should be ignored",
290             "stringlist", "",
291             {
292                 values: iter(update({ all: "All Events" }, config.autocommands)).toArray(),
293                 has: Option.has.toggleAll
294             });
295     }
296 });
297
298 // vim: set fdm=marker sw=4 ts=4 et: