[vim/vim] the help about "<plug>" mappings could be improved (#6705)

7 views
Skip to first unread message

lacygoill

unread,
Aug 13, 2020, 11:51:25 AM8/13/20
to vim/vim, Subscribed

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.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or unsubscribe.

lacygoill

unread,
Aug 13, 2020, 11:51:36 AM8/13/20
to vim/vim, Subscribed

Closing since if the patch is correct, it will be merged in the next update of the runtime files.

lacygoill

unread,
Aug 13, 2020, 11:51:41 AM8/13/20
to vim/vim, Subscribed

Closed #6705.

Bram Moolenaar

unread,
Aug 13, 2020, 4:12:50 PM8/13/20
to vim/vim, Subscribed

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).

Christian Brabandt

unread,
Aug 13, 2020, 4:21:40 PM8/13/20
to vim/vim, Subscribed

I think parenthesis have been established as kind of best practices in plugins.

lacygoill

unread,
Aug 13, 2020, 4:33:23 PM8/13/20
to vim/vim, Subscribed

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

Christian Brabandt

unread,
4:06 AM (13 hours ago) 4:06 AM
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#6705)

New version of this PR: #20351


Reply to this email directly, view it on GitHub, or unsubscribe.

Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/6705/4561949965@github.com>

Reply all
Reply to author
Forward
0 new messages