The help about <Plug> mappings (:h using-<Plug>, and various examples given here and there) can all suffer from an issue where the user has to wait before the mapping they pressed takes effect.
Example:
vim9script
set timeoutlen=3000
nmap <F3> <plug>foo
nno <plug>foo :echo 'foo'<cr>
nno <plug>foobar :echo 'foobar'<cr>
Press <F3> to make Vim echo foo; it will take 3 seconds.
One solution is to wrap the sequence inside a pair of parentheses:
vim9script
set timeoutlen=3000
nmap <F3> <plug>(foo)
nno <plug>(foo) :echo 'foo'<cr>
nno <plug>(foobar) :echo 'foobar'<cr>
This time, <F3> echo'es foo instantaneously.
This patch updates the documentation so that the users avoid this pitfall when they install <plug> mappings:
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 45332e6a4..d1c041326 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -2044,9 +2044,9 @@ for this mapping, but the user might already use it for something else. To
allow the user to define which keys a mapping in a plugin uses, the <Leader>
item can be used: >
- 22 map <unique> <Leader>a <Plug>TypecorrAdd
+ 22 map <unique> <Leader>a <Plug>(TypecorrAdd)
-The "<Plug>TypecorrAdd" thing will do the work, more about that further on.
+The "<Plug>(TypecorrAdd)" thing will do the work, more about that further on.
The user can set the "mapleader" variable to the key sequence that he wants
this mapping to start with. Thus if the user has done: >
@@ -2062,15 +2062,15 @@ already happened to exist. |:map-<unique>|
But what if the user wants to define his own key sequence? We can allow that
with this mechanism: >
- 21 if !hasmapto('<Plug>TypecorrAdd')
- 22 map <unique> <Leader>a <Plug>TypecorrAdd
+ 21 if !hasmapto('<Plug>(TypecorrAdd)')
+ 22 map <unique> <Leader>a <Plug>(TypecorrAdd)
23 endif
-This checks if a mapping to "<Plug>TypecorrAdd" already exists, and only
+This checks if a mapping to "<Plug>(TypecorrAdd)" already exists, and only
defines the mapping from "<Leader>a" if it doesn't. The user then has a
chance of putting this in his vimrc file: >
- map ,c <Plug>TypecorrAdd
+ map ,c <Plug>(TypecorrAdd)
Then the mapped key sequence will be ",c" instead of "_a" or "\a".
@@ -2100,13 +2100,13 @@ function (without the "s:"), which is again another function.
<SID> can be used with mappings. It generates a script ID, which identifies
the current script. In our typing correction plugin we use it like this: >
- 24 noremap <unique> <script> <Plug>TypecorrAdd <SID>Add
+ 24 noremap <unique> <script> <Plug>(TypecorrAdd) <SID>Add
..
28 noremap <SID>Add :call <SID>Add(expand("<cword>"), 1)<CR>
Thus when a user types "\a", this sequence is invoked: >
- \a -> <Plug>TypecorrAdd -> <SID>Add -> :call <SID>Add()
+ \a -> <Plug>(TypecorrAdd) -> <SID>Add -> :call <SID>Add()
If another script was also map <SID>Add, it would get another script ID and
thus define another mapping.
@@ -2149,9 +2149,10 @@ difference between using <SID> and <Plug>:
To make it very unlikely that other plugins use the same sequence of
characters, use this structure: <Plug> scriptname mapname
In our example the scriptname is "Typecorr" and the mapname is "Add".
- This results in "<Plug>TypecorrAdd". Only the first character of
+ This results in "<Plug>(TypecorrAdd)". Only the first character of
scriptname and mapname is uppercase, so that we can see where mapname
- starts.
+ starts. The parentheses prevent Vim from waiting for more keys to be
+ typed if another mapping starts in the same way.
<SID> is the script ID, a unique identifier for a script.
Internally Vim translates <SID> to "<SNR>123_", where "123" can be any
@@ -2226,10 +2227,10 @@ Here is the resulting complete example: >
18 \ synchronization
19 let s:count = 4
20
- 21 if !hasmapto('<Plug>TypecorrAdd')
- 22 map <unique> <Leader>a <Plug>TypecorrAdd
+ 21 if !hasmapto('<Plug>(TypecorrAdd)')
+ 22 map <unique> <Leader>a <Plug>(TypecorrAdd)
23 endif
- 24 noremap <unique> <script> <Plug>TypecorrAdd <SID>Add
+ 24 noremap <unique> <script> <Plug>(TypecorrAdd) <SID>Add
25
26 noremenu <script> Plugin.Add\ Correction <SID>Add
27
@@ -2279,7 +2280,7 @@ Here is a simple example for a plugin help file, called "typecorr.txt": >
6 There are currently only a few corrections. Add your own if you like.
7
8 Mappings:
- 9 <Leader>a or <Plug>TypecorrAdd
+ 9 <Leader>a or <Plug>(TypecorrAdd)
10 Add a correction for the word under the cursor.
11
12 Commands:
@@ -2417,13 +2418,13 @@ To make sure mappings will only work in the current buffer use the >
command. This needs to be combined with the two-step mapping explained above.
An example of how to define functionality in a filetype plugin: >
- if !hasmapto('<Plug>JavaImport')
- map <buffer> <unique> <LocalLeader>i <Plug>JavaImport
+ if !hasmapto('<Plug>(JavaImport)')
+ map <buffer> <unique> <LocalLeader>i <Plug>(JavaImport)
endif
- noremap <buffer> <unique> <Plug>JavaImport oimport ""<Left><Esc>
+ noremap <buffer> <unique> <Plug>(JavaImport) oimport ""<Left><Esc>
|hasmapto()| is used to check if the user has already defined a map to
-<Plug>JavaImport. If not, then the filetype plugin defines the default
+<Plug>(JavaImport). If not, then the filetype plugin defines the default
mapping. This starts with |<LocalLeader>|, which allows the user to select
the key(s) he wants filetype plugin mappings to start with. The default is a
backslash.
@@ -2440,12 +2441,12 @@ plugin for the mail filetype: >
" Add mappings, unless the user didn't want this.
if !exists("no_plugin_maps") && !exists("no_mail_maps")
" Quote text by inserting "> "
- if !hasmapto('<Plug>MailQuote')
- vmap <buffer> <LocalLeader>q <Plug>MailQuote
- nmap <buffer> <LocalLeader>q <Plug>MailQuote
+ if !hasmapto('<Plug>(MailQuote)')
+ vmap <buffer> <LocalLeader>q <Plug>(MailQuote)
+ nmap <buffer> <LocalLeader>q <Plug>(MailQuote)
endif
- vnoremap <buffer> <Plug>MailQuote :s/^/> /<CR>
- nnoremap <buffer> <Plug>MailQuote :.,$s/^/> /<CR>
+ vnoremap <buffer> <Plug>(MailQuote) :s/^/> /<CR>
+ nnoremap <buffer> <Plug>(MailQuote) :.,$s/^/> /<CR>
endif
Two global variables are used:
See here for an example of a user who was faced with this issue.
Note that only the closing parenthesis is necessary. The opening one is just there for symmetry and making the mappings a little more readable. Also, any character would work, as long as it's not commonly used in the sequence which follows <plug>. Usually, such a sequence only uses alphabetical characters (maybe digits, underscores and hyphens too), but not parentheses. It seems that the convention which is followed nowadays by most plugins authors is to use parentheses.
Closing since if the patch is correct, it will be merged in the next update of the runtime files.
What we really need is a terminator for the mapped characters. Instead of using (TypecorrAdd) we can use TypecorrAdd;
Any punctuation will do, a semicolon stands out best (a dot can be a bit confusing).
I think parenthesis have been established as kind of best practices in plugins.
What we really need is a terminator for the mapped characters. Instead of using (TypecorrAdd) we can use TypecorrAdd;
Any punctuation will do, a semicolon stands out best (a dot can be a bit confusing).
You're right, but the parentheses have become a widely adopted convention by now. :vimgrep tells me I have 591 <plug>(...) mappings, all installed from third-party plugins. It might be confusing for some new users to read these <plug> in the help:
<plug>foobar;
While in most third-party plugins, they would read:
<plug>(foobar)
Besides, it's subjective, but I find the parentheses more readable; or maybe it's just the habit of reading the second form, I don't know.
In any case, any terminator will be an improvement for the current examples in the help.
There is one – small – benefit of using a semicolon over parentheses. You save 1 byte, which might be useful in a mapping with a very long lhs; its size must not go beyond 50 bytes, otherwise E474 is raised:
✔
vim -Nu NONE +'nno xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx abc'
✘
vim -Nu NONE +'nno xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx abc'
E474: Invalid argument
Most helpful comment
I think parenthesis have been established as kind of best practices in plugins.