]> git.gir.st - VimFx.git/blob - documentation/api.md
Improve the public API consumption
[VimFx.git] / documentation / api.md
1 <!--
2 This is part of the VimFx documentation.
3 Copyright Simon Lydell 2015.
4 See the file README.md for copying conditions.
5 -->
6
7 # Public API
8
9 VimFx has a public API. It is intended to be used by:
10
11 - Users who prefer to configure things using text files.
12 - Users who would like to add custom commands.
13 - Users who would like to set [special options].
14 - Users who would like to make site-specific customizations.
15 - Extension authors who would like to extend VimFx.
16
17 VimFx users who use the public API should write a so-called [config file].
18
19
20 ## Getting the API
21
22 ```js
23 let {classes: Cc, interfaces: Ci, utils: Cu} = Components
24 Cu.import('resource://gre/modules/Services.jsm')
25 let apiPref = 'extensions.VimFx.api_url'
26 let apiUrl = Services.prefs.getComplexValue(apiPref, Ci.nsISupportsString).data
27 Cu.import(apiUrl, {}).getAPI(vimfx => {
28
29 // Do things with the `vimfx` object here.
30
31 })
32 ```
33
34 You might also want to take a look at the [config file bootstrap.js
35 example][bootstrap.js].
36
37 Note that the callback passed to `getAPI` is called once every time VimFx starts
38 up, not once per Firefox session! This means that if you update VimFx (or
39 disable and then enable it), the callback is re-run with the new version.
40
41
42 ## API
43
44 The following sub-sections assume that you store VimFx’s public API in a
45 variable called `vimfx`.
46
47 ### `vimfx.get(pref)` and `vimfx.set(pref, value)`
48
49 Gets or sets the value of the VimFx pref `pref`.
50
51 You can see all prefs in [defaults.coffee], or by opening [about:config] and
52 filtering by `extensions.vimfx`. Note that you can also access the [special
53 options], which may not be accessed in [about:config], using `vimfx.get()` and
54 `vimfx.set()`—in fact, this is the _only_ way of accessing those options.
55
56 #### `vimfx.get(pref)`
57
58 Gets the value of the VimFx pref `pref`.
59
60 ```js
61 // Get the value of the Hint chars option:
62 vimfx.get('hint_chars')
63 // Get all keyboard shortcuts (as a string) for the `f` command:
64 vimfx.get('modes.normal.follow')
65 ```
66
67 #### `vimfx.set(pref, value)`
68
69 Sets the value of the VimFx pref `pref` to `value`.
70
71 ```js
72 // Set the value of the Hint chars option:
73 vimfx.set('hint_chars', 'abcdefghijklmnopqrstuvwxyz')
74 // Add yet a keyboard shortcut for the `f` command:
75 vimfx.set('modes.normal.follow', vimfx.get('modes.normal.follow') + ' e')
76 ```
77
78 Note: If you produce conflicting keyboard shortcuts, the order of your code does
79 not matter. The command that comes first in VimFx’s settings page in the Add-ons
80 Manager (and in the help dialog) gets the shortcut; the other one(s) do(es) not.
81 See the notes about order in [mode object], [category object] and [command
82 object] for more information about order.
83
84 ```js
85 // Even though we set the shortcut for focusing the search bar last, the command
86 // for focusing the location bar “wins”, because it comes first in VimFx’s
87 // settings page in the Add-ons Manager.
88 vimfx.set('modes.normal.focus_location_bar', 'ö')
89 vimfx.set('modes.normal.focus_search_bar', 'ö')
90
91 // Swapping their orders also swaps the “winner”.
92 let {commands} = vimfx.modes.normal
93 ;[commands.focus_location_bar.order, commands.focus_search_bar.order] =
94 [commands.focus_search_bar.order, commands.focus_location_bar.order]
95 ```
96
97 ### `vimfx.addCommand(options, fn)`
98
99 Creates a new command.
100
101 **Note:** This should only be used by config file users, not by extension
102 authors who wish to extend VimFx. They should add commands manually to
103 [`vimfx.modes`] instead.
104
105 `options`:
106
107 - name: `String`. The name used when accessing the command via
108 `vimfx.modes[options.mode].commands[options.name]`. It is also used for the
109 pref used to store the shortcuts for the command:
110 `` `custom.mode.${options.mode}.${options.name}` ``.
111 - description: `String`. Shown in the help dialog and VimFx’s settings page in
112 the Add-ons Manager.
113 - mode: `String`. Defaults to `'normal'`. The mode to add the command to. The
114 value has to be one of the keys of [`vimfx.modes`].
115 - category: `String`. Defaults to `'misc'` for Normal mode and `''`
116 (uncategorized) otherwise. The category to add the command to. The
117 value has to be one of the keys of [`vimfx.get('categories')`][categories].
118 - order: `Number`. Defaults to putting the command at the end of the category.
119 The first of the default commands has the order `100` and then they increase
120 by `100` per command. This allows to put new commands between two already
121 existing ones.
122
123 `fn` is called when the command is activated. See the [onInput] documentation
124 below for more information.
125
126 Note that you have to give the new command a shortcut in VimFx’s settings page
127 in the Add-ons Manager or set one using `vimfx.set()` to able to use the new
128 command.
129
130 ```js
131 vimfx.addCommand({
132 name: 'hello',
133 description: 'Log Hello World',
134 }, => {
135 console.log('Hello World!')
136 })
137 // Optional:
138 vimfx.set('custom.mode.normal.hello', 'gö')
139 ```
140
141 ### `vimfx.addOptionOverrides(...rules)` and `vimfx.addKeyOverrides(...rules)`
142
143 These methods take any number of arguments. Each argument is a rule. The rules
144 are added in order. The methods may be run multiple times.
145
146 A rule is an `Array` of length 2:
147
148 1. The first item is a function that returns `true` if the rule should be
149 applied and `false` if not. This is called the matching function.
150 2. The second item is the value that should be used if the rule is applied. This
151 is called the override.
152
153 The rules are tried in the same order they were added. When a matching rule is
154 found it is applied. No more rules will be applied.
155
156 #### `vimfx.addOptionOverrides(...rules)`
157
158 The rules are matched any time the value of a VimFx pref is needed.
159
160 The matching function receives a [location object].
161
162 The override is an object whose keys are VimFx pref names and whose values
163 override the pref in question. The values should be formatted as in an [options
164 object].
165
166 ```js
167 vimfx.addOptionOverrides(
168 [ ({hostname, pathname, hash}) =>
169 `${hostname}${pathname}${hash}` === 'google.com/',
170 {prevent_autofocus: false}
171 ]
172 )
173 ```
174
175 #### `vimfx.addKeyOverrides(...rules)`
176
177 The rules are matched any time you press a key that is not part of the tail of a
178 multi-key shortcut.
179
180 The matching function receives a [location object] as well as the current
181 mode name (one of the keys of [`vimfx.modes`]).
182
183 The override is an array of keys which should not activate VimFx commands but be
184 sent to the page.
185
186 This allows to disable commands on specific sites. To _add_ commands on specific
187 sites, add them globally and then disable them on all _other_ sites.
188
189 ```js
190 vimfx.addKeyOverrides(
191 [ location => location.hostname === 'facebook.com',
192 ['j', 'k']
193 ]
194 )
195 ```
196
197 ### `vimfx.on(eventName, listener)`
198
199 Runs `listener(data)` when `eventName` is fired.
200
201 #### The `locationChange` event
202
203 Occurs when opening a new tab or navigating to a new URL causing a full page
204 load. The data passed to listeners is an object with the following properties:
205
206 - vim: The current [vim object].
207 - location: A [location object].
208
209 This can be used to enter a different mode by default on some pages (which can
210 be used to replace the blacklist option).
211
212 ```js
213 vimfx.on('load', ({vim, location}) => {
214 if (location.hostname === 'example.com') {
215 vim.enterMode('ignore')
216 }
217 })
218 ```
219
220 #### The `modeChange` event
221
222 Occurs whenever the current mode in any tab changes. The initial entering of the
223 default mode in new tabs also counts as a mode change. The data passed to
224 listeners is the current [vim object].
225
226 ```js
227 vimfx.on('modeChange', vim => {
228 let mode = vimfx.modes[vim.mode].name()
229 vim.notify(`Entering mode: ${mode}`)
230 })
231 ```
232
233 #### The `TabSelect` event
234
235 Occurs whenever any tab in any window is selected. This is also fired when
236 Firefox starts for the currently selected tab. The data passed to listeners is
237 the `event` object passed to the standard Firefox [TabSelect] event.
238
239 ### `vimfx.refresh()`
240
241 If you make changes to [`vimfx.modes`] directly you need to call
242 `vimfx.refresh()` for your changes to take effect.
243
244 ### `vimfx.modes`
245
246 An object whose keys are mode names and whose values are [mode object]s.
247
248 This is a very low-level part of the API. It allows to:
249
250 - Access all commands and run them. This is the only thing that a config file
251 user needs it for.
252
253 ```js
254 let {commands} = vimfx.modes.normal
255 // Inside a custom command:
256 commands.tab_new.run(args)
257 ```
258
259 - Adding new commands. This is intended to be used by extension authors who wish
260 to extend VimFx, not config file users. They should use the
261 `vimfx.addCommand()` helper instead.
262
263 ```js
264 vimfx.modes.normal.commands.new_command = {
265 pref: 'extensions.my_extension.mode.normal.new_command',
266 category: 'misc',
267 order: 10000,
268 description: () => translate('mode.normal.new_command'),
269 run: args => console.log('New command! args:', args)
270 }
271 ```
272
273 - Adding new modes. This is intended to be used by extension authors who wish to
274 extend VimFx, not config file users.
275
276 ```js
277 vimfx.modes.new_mode = {
278 name: () => translate('mode.new_mode'),
279 order: 10000,
280 commands: {},
281 onEnter(args) {},
282 onLeave(args) {},
283 onInput(args, match) {
284 if (match.type === 'full') {
285 match.command.run(args)
286 }
287 return (match.type !== 'none')
288 },
289 }
290 ```
291
292 When you’re done modifying `vimfx.modes` directly, you need to call
293 `vimfx.refresh()`. (That’s taken care of automatically in the
294 `vimfx.addCommand()` helper.)
295
296 Have a look at [modes.coffee] and [commands.coffee] for more information.
297
298 ### `vimfx.get('categories')`
299
300 An object whose keys are category names and whose values are [category object]s.
301
302 ```js
303 let categories = vimfx.get('categories')
304
305 // Add a new category.
306 categories.custom = {
307 name: () => 'Custom commands',
308 order: 10000,
309 }
310
311 // Swap the order of the Location and Tabs categories.
312 ;[commands.focus_location_bar.order, categories.tabs.order] =
313 [categories.tabs.order, commands.focus_location_bar.order]
314 ```
315
316 ### Mode object
317
318 A mode is an object with the follwing properties:
319
320 - name(): `Function`. Returns a human readable name of the mode used in the help
321 dialog and VimFx’s settings page in the Add-ons Manager.
322 - order: `Number`. The first of the default modes has the order `100` and then
323 they increase by `100` per mode. This allows to put new modes between two
324 already existing ones.
325 - commands: `Object`. The keys are command names and the values are [command
326 object]s.
327 - onEnter(data, ...args): `Function`. Called when the mode is entered.
328 - onLeave(data): `Function`. Called when the mode is left.
329 - onInput(data, match): `Function`. Called when a key is pressed.
330
331 #### onEnter, onLeave and onInput
332
333 These methods are called with an object (called `data` above) with the following
334 properties:
335
336 - vim: The current [vim object].
337 - storage: An object unique to the current [vim object] and to the current mode.
338 Allows to share things between commands of the same mode by getting and
339 setting keys on it.
340
341 ##### onEnter
342
343 This method is called with an object as mentioned above, and after that there
344 may be any number of arguments (`args` in `vim.enterMode(modeName, ...args)`)
345 that the mode is free to do whatever it wants with.
346
347 ##### onInput
348
349 The object passed to this method (see above) also has the following properties:
350
351 - isFrameEvent: `Boolean`. `true` if the event occured in web page content,
352 `false` otherwise (if the event occured in the browser UI).
353 - count: `Number`. The count for the command. `undefined` if no count. (This is
354 simply a copy of `match.count`. `match` is defined below.)
355
356 The above object should be passed to commands when running them. The mode is
357 free to do whatever it wants with the return value (if any) of the commands it
358 runs.
359
360 It also receives a [match object] as the second argument.
361
362 `onInput` should return `true` if the current keypress should not be passed on
363 to the browser and web pages, and `false` otherwise.
364
365 ### Category object
366
367 A category is an object with the follwing properties:
368
369 - name(): `Function`. Returns a human readable name of the category used in the
370 help dialog and VimFx’s settings page in the Add-ons Manager. Config file
371 users adding custom categories could simply return a string; extension authors
372 are encouraged to look up the name from a locale file.
373 - order: `Number`. The first of the default categories is the “uncategorized”
374 category. It has the order `100` and then they increase by `100` per category.
375 This allows to put new categories between two already existing ones.
376
377 ### Command object
378
379 A command is an object with the following properties:
380
381 - pref: `String`. The pref used to store the shortcuts for the command.
382 - run(args): `Function`. Called when the command is activated.
383 - description(): `Function`. Returns a description of the command (as a string),
384 shown in the help dialog and VimFx’s settings page in the Add-ons Manager.
385 - category: `String`. The category to add the command to. The value has to be
386 one of the keys of [`vimfx.get('categories')`][categories].
387 - order: `Number`. The first of the default commands has the order `100` and
388 then they increase by `100` per command. This allows to put new commands
389 between two already existing ones.
390
391 ### Match object
392
393 A `match` object has the following properties:
394
395 - type: `String`. It has one of the following values:
396
397 - `'full'`: The current keypress, together with previous keypresses, fully
398 matches a command shortcut.
399 - `'partial'`: The current keypress, together with previous keypresses,
400 partially matches a command shortcut.
401 - `'count'`: The current keypress is not part of a command shortcut, but is a
402 digit and contributes to the count of a future matched command.
403 - `'none'`: The current keypress is not part of a command shortcut and does
404 not contribute to a count.
405
406 - focus: `String` or `null`. The type of currently focused _element_ plus
407 current pressed _key_ combo. You might not want to run commands and suppress
408 the event if this value is anything other than null. It has one of the
409 following values, depending on what kind of _element_ is focused and which
410 _key_ was pressed:
411
412 - `'editable'`: element: a text input or a `contenteditable` element.
413 key: any pressed key.
414 - `'activatable'`: element: an “activatable” element (link or button).
415 key: see the [`activatable_element_keys`] option.
416 - `'adjustable'`: element: an “adjustable” element (form control or video
417 player). key: see the [`adjustable_element_keys`] option.
418 - `'other'`: element: some other kind of element that can receive keystrokes,
419 for example an element in fullscreen mode. key: any pressed key.
420
421 If none of the above criteria is met, the value is `null`, which means that
422 the currently focused element does not appear to respond to keystrokes in any
423 special way.
424
425 - command: `null` unless `type` is `'full'`. Then it is the matched command (a
426 [command object]).
427
428 The matched command should usually be run at this point. It is suitable to
429 pass on the object passed to [onInput] to the command. Some modes might choose
430 to add extra properties to the object first. (That is favored over passing
431 several arguments, since it makes it easier for the command to in turn pass
432 the same data it got on to another command, if needed.)
433
434 Usually the return value of the command isn’t used, but that’s up to the mode.
435
436 - count: `Number`. The count for the command. `undefined` if no count.
437
438 - specialKeys: `Object`. The keys may be any of the following:
439
440 - `<force>`
441 - `<late>`
442
443 If a key exists, its value is always `true`. The keys that exist indicate the
444 [special keys] for the sequence used for the matched command (if any).
445
446 - keyStr: `String`. The current keypress represented as a string.
447
448 - unmodifiedKey: `String`. `keyStr` without modifiers.
449
450 - toplevel: `Boolean`. Whether or not the match was a toplevel match in the
451 shortcut key tree. This is `true` unless the match is part of the tail of a
452 multi-key shortcut.
453
454 ### Vim object
455
456 There is one `vim` object per tab.
457
458 A `vim` object has the following properties:
459
460 - window: [`Window`]. The current Firefox window object. Most commands
461 interacting with Firefox’s UI use this.
462
463 - browser: [`Browser`]. The `browser` that this vim object handles.
464
465 - options: `Object`. Provides access to all of VimFx’s options. It is an
466 [options object].
467
468 - mode: `String`. The current mode name.
469
470 - enterMode(modeName, ...args): `Function`. Enter mode `modeName`, passing
471 `...args` to the mode. It is up to every mode to do whatever it wants to with
472 `...args`.
473
474 - isFrameEvent(event): `Function`. Returns `true` if `event` occurred in web
475 page content, and `false` otherwise (if it occurred in Firefox’s UI).
476
477 - notify(title, options = {}): `Function`. Display a notification with the title
478 `title` (a `String`). If you need more text than a title, use `options.body`.
479 See [`Notification`] for more information.
480
481 - markPageInteraction(): `Function`. Marks that the user has interacted with the
482 page. After that [autofocus prevention] is not done anymore. Commands
483 interacting with web page content might want to do this.
484
485 **Warning:** There are also properties starting with an underscore on `vim`
486 objects. They are private, and not supposed to be used outside of VimFx’s own
487 source code. They may change at any time.
488
489 ### Options object
490
491 An `options` object provides access to all of VimFx’s options. It is an object
492 whose keys are VimFx pref names.
493
494 Note that the values are not just simply `vimfx.get(pref)` for the `pref` in
495 question; they are _parsed_ (`parse(vimfx.get(pref))`):
496
497 - Space-separated prefs are parsed into arrays of strings.
498
499 - `black_list` and `{prev,next}_patterns` are parsed into arrays of regular
500 expressions.
501
502 (See [parse-prefs.coffee] for all details.)
503
504 Any [option overrides] are automatically taken into account when getting an
505 option value.
506
507 The [special options] are also available on this object.
508
509
510 ### Location object
511
512 A location object is very similar to [`window.location`] in web pages.
513 Technically, it is a [`URL`] instance. You can experient with the current
514 location object by opening the [web console] and entering `location`.
515
516
517 ## Stability
518
519 The public API is currently **experimental** and therefore **unstable.** Things
520 might break with new VimFx versions.
521
522 As soon as VimFx 1.0.0 is released backwards compatibility will be a priority
523 and won’t be broken until VimFx 2.0.0.
524
525 [option overrides]: #vimfxaddOptionOverridesrules
526 [categories]: #vimfxgetcategories
527 [`vimfx.modes`]: #vimfxmodes
528 [onInput]: #oninput
529 [mode object]: #mode-object
530 [category object]: #category-object
531 [command object]: #command-object
532 [match object]: #match-object
533 [vim object]: #vim-object
534 [options object]: #options-object
535 [location object]: #location-object
536
537 [blacklisted]: options.md#blacklist
538 [special options]: options.md#special-options
539 [config file]: config-file.md
540 [bootstrap.js]: config-file.md#bootstrapjs
541 [autofocus prevention]: options.md#prevent-autofocus
542 [`activatable_element_keys`]: options.md#activatable_element_keys
543 [`adjustable_element_keys`]: options.md#adjustable_element_keys
544
545 [special keys]: shortcuts.md#special-keys
546
547 [defaults.coffee]: ../extension/lib/defaults.coffee
548 [parse-prefs.coffee]: ../extension/lib/parse-prefs.coffee
549 [modes.coffee]: ../extension/lib/modes.coffee
550 [commands.coffee]: ../extension/lib/commands.coffee
551 [vim.coffee]: ../extension/lib/vim.coffee
552
553 [`Window`]: https://developer.mozilla.org/en-US/docs/Web/API/Window
554 [`Browser`]: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/browser
555 [`Notification`]: https://developer.mozilla.org/en-US/docs/Web/API/Notification
556 [`window.location`]: https://developer.mozilla.org/en-US/docs/Web/API/Location
557 [`URL`]: https://developer.mozilla.org/en-US/docs/Web/API/URL
558 [TabSelect]: https://developer.mozilla.org/en-US/docs/Web/Events/TabSelect
559 [web console]: https://developer.mozilla.org/en-US/docs/Tools/Web_Console
560 [about:config]: http://kb.mozillazine.org/About:config
Imprint / Impressum