19

Many plugins make their public mapping interface accessible through <Plug> maps. Users can then use these maps as hooks for their own mappings, e.g. :nmap <Leader>fu <Plug>fooPluginUnlinkRootDir.

Recently I have come across some plugins which put their map names in brackets, e.g.

This syntax is not documented anywhere in the help files nor do any of the bundled Vim runtime files use it. Nevertheless, these plugins do their job just fine.

What is the motivation for the brackets? Is there any advantage in using them? Should plugin authors be encouraged to follow this practice (as a best practice)?

glts
  • 21,808
  • 12
  • 73
  • 94

3 Answers3

18

Thanks ZyX; your answer already covers the fundamentals, so let me just add why I've adopted the <Plug>(PluginNameAndMore) notation. (I think I saw it first in Kana Natsuno's plugins.)

Two reasons:

  1. When wrapping a mapping with other stuff, it's easier to visually parse the individual mapping targets, like here:

    imap <C-x><C-c> <Plug>(CompleteStart)<Plug>(CamelCaseComplete)<SID>(CamelCaseCompleteModifyUndo)<Plug>(CamelCasePostComplete)<Plug>(CompleteoptLongestSelect)

  2. When defining multiple mappings for a plugin, one must be careful that the LHS of one is not contained in another mapping. Otherwise, there will be a delay when the mapping is triggered, as Vim needs to wait for additional keystrokes before the ambiguity can be resolved. The closing parenthesis prevents any such ambiguity.


BAD                 GOOD
<Plug>MyFunc        <Plug>MyFuncNext, <Plug>(MyFunc)
<Plug>MyFuncReverse <Plug>MyFuncPrev, <Plug>(MyFuncReverse)
Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
  • Right, a plug map name should not be a prefix of another plug map name. That *is* a reason, thank you! – glts Dec 04 '12 at 08:58
  • Never thought about this: I always use variables for configuration and not `hasmapto()` and thus am completely immune to these issues. Wrapping own plugins is always exporting and using their functions directly, wrapping foreign ones is always `let map=s:_r.map.maparg(…)`+`let map.lhs='TemporaryLHS'`+`call s:_r.map.map(map)` and then redefining the map, it is the most universal solution. – ZyX Dec 04 '12 at 21:01
  • @ZyX: For published plugins, the ``-mappings are the recommended and customary way to allow customization of the keys. The only exception is would accept is when your plugin defines 10+ similar mappings; then, something like `g:MyPluginPrefix` might make more sense. – Ingo Karkat Dec 05 '12 at 07:51
  • Using variables is more convenient in most cases. If you want to disable something that uses `hasmapto()` you have to invent a dummy name for it, one per mapping. For mappings defined in frawor it is just `let g:frawormap_GroupName_MappingName=0` and `let g:frawormap_GroupName=0` for the whole group. I can’t imagine a *natural* way to disable all mappings defined in one plugin with `hasmapto()`. Frawor also allows (forces to allow) customization of *both* prefix and subsequent keys, `hasmapto()` lacks this (I had to have 16 lines cycle to change only prefix of NERDCommenter). – ZyX Dec 05 '12 at 15:48
  • As a plugin writer I also have a strong prejudice to *any* mapping lacking `nore` thing even if it is legal. Third minor thing is memory and CPU efficience (though if you are using frawor and not something self-written based on variables you probably do not care *that* much). – ZyX Dec 05 '12 at 15:48
  • Though most of advantages described here come out of frawor (straightforward way to implement variable-based customization will force you to use `let g:MyPlugiVar='DONOTMAP'` which is similar to `imap DONOTMAP MyPlugin` in case of `hasmapto()`; forced prefixes and mapping groups disabling also will force you to write much more code without frawor), there is no way to implement these advantages on top of `hasmapto()` that will look naturally. – ZyX Dec 05 '12 at 15:52
  • And last one (just remembered I saw this somewhere on vim-dev): `imap ,a ISurround` I used for testing does not work at all when I have `set langmap=IY` (it just puts `ISurround`). For me this is enough reason to never use `hasmapto()` in my plugins. And, unlike other ones, it is not a minor advantage, but a big issue. Variable-based solutions have no such problems. Minimal configuration to reproduce: `vim -u NORC -N --cmd 'set rtp=~/.vam/surround' -c 'set langmap=IY' -s <(<<<$'i\C-gs')` (where `~/.vam/surround` is the path where you cloned git://github.com/tpope/vim-surround). – ZyX Dec 05 '12 at 16:20
8

Both of {lhs} and {rhs} in the mappings command are byte sequences which can contain arbitrary data (except for NUL byte) as long as it has natural number of bytes (for {rhs} having zero number of bytes is also allowed). From this point of view (…) practice has no advantages over another one.

Specifically for (textobj-…) there is one minor advantage: you can select the whole {lhs} without <Plug> part with a) motion and have more readable dashes between words. I have no idea though why would one want to do so as the whole {lhs} can be selected with aW (with <Plug> part).

I do not see any reason for the LineJuggler version.

You should better ask authors about this. @IngoKarkat is here, on stackoverflow and will probably read the question soon. I have no idea how to contact Kana Natsuno.

Community
  • 1
  • 1
ZyX
  • 52,536
  • 7
  • 114
  • 135
2

Parens make it clearer (to humans) when there are other keystrokes in a mapping following the <Plug> invocation. For instance I have this mapping:

nmap ]c <Plug>GitGutterNextHunkzv

That makes ]c jump to the next Git hunk and then do zv to open any folds there. But since <Plug> names are arbitrary, the command could have been called GitGutterNextHunkzv. In general there's no way for somebody reading a file using a <Plug> mapping to know if the whole thing is a name or there are other characters following.

If the GitGutter plug-in had used brackets for in the <Plug> name, it would be far clearer to see what's going on:

nmap ]c <Plug>(GitGutterNextHunk)zv
Smylers
  • 1,673
  • 14
  • 18