You could try to add this code in your vimrc
:
let g:my_overlooked_commands = [
\ { 'old': 'vsplit', 'new': 'C-w v' },
\ { 'old': 'split', 'new': 'C-w s' },
\ { 'old': 'q!', 'new': 'ZQ' },
\ { 'old': 'x', 'new': 'ZZ' },
\]
fu! s:alternative_reminder(cmd) abort
call timer_start(0, {->
\ execute('echohl WarningMsg | echo "['.a:cmd.'] was equivalent" | echohl NONE', '') })
return ''
endfu
fu! s:remember(list) abort
for cmd in a:list
let old = cmd.old
let sold = string(old)
exe printf('
\ cnorea <expr> %s getcmdtype() ==# ":" && getcmdline() ==# %s
\? %s.<sid>alternative_reminder(%s)
\: %s
\', old, sold, sold, string(cmd.new), sold
\ )
endfor
endfu
call s:remember(g:my_overlooked_commands)
To use it, inside the list g:my_overlooked_commands
, you would need to add the old commands you're currently using, and their new counterparts you would like to use, but frequently forget to.
After executing an old command, a message should be displayed in the command-line to remind you that there's an alternative.
Edit: Concerning these 2 questions:
- How does the %s.alternative_reminder(%s) part work?
printf()
expects a string as its first argument (called {fmt}
by the help, probably as an abbreviation for format
), and one or more expressions. The format (first argument) can contain one or several %
items. There are several types of items, %s
is one of them. The s
stands for s
tring. It means that you want printf()
to replace it with the value of an expression whose type is a string. Here's an example:
:let var = 'string' | echo printf('here is a %s', var)
Going back to the code, the contents of the format is:
cnorea <expr> %s getcmdtype() ==# ":" && getcmdline() ==# %s
\? %s.<sid>alternative_reminder(%s)
\: %s
It contains 5 %s
items. So, printf()
expects 5 more arguments after the format. Here they are given on the next line:
old, sold, sold, string(cmd.new), sold
The values of these expressions will replace the items in the format, respecting the order in which they are passed to printf()
.
So, this command:
exe printf('
\ cnorea <expr> %s getcmdtype() ==# ":" && getcmdline() ==# %s
\? %s.<sid>alternative_reminder(%s)
\: %s
\', old, sold, sold, string(cmd.new), sold
\ )
Should be (untested) equivalent to something like this:
exe 'cnorea <expr> '.old.' getcmdtype() ==# ":" && getcmdline() ==# '.sold
\. '? '.sold.'.<sid>alternative_reminder('.string(cmd.new).')'
\. ': '.sold
You don't need to invoke the printf()
function. But as soon as I get a concatenation of strings and other expressions, I find that using printf()
makes the code a little more readable.
Once printf()
has done its job, :execute
should receive a string looking like this:
cnorea <expr> vsplit getcmdtype() ==# ":" && getcmdline() ==# 'vsplit'
\? 'vsplit'.<sid>alternative_reminder('vsplit')
\: 'vsplit'
This is an abbreviation which expands the word vsplit
into the value of an expression, which uses
the ternary operator ?:
. If the test:
getcmdtype() ==# ":" && getcmdline() ==# 'vsplit'
… is true, then, the value of the expression is:
'vsplit'.<sid>alternative_reminder('vsplit')
… otherwise:
'vsplit'
Note that <sid>alternative_reminder('vsplit')
is an expression whose value is the one returned at the end of the function. By default, a function returns the number 0
. But here the function explicitly returns an empty string. So, the function doesn't add anything to the expansion of vsplit
, because it's replaced with an empty string. However, to get to the returned value, Vim must process the code of the function. The latter contains some logic to trigger a warning message when necessary.
The goal of the test is to prevent the reminder to be triggered if you're not on a regular Ex command-line, or if you're not at the beginning of the command-line.
Basically, vsplit
should always be expanded into vsplit
(unchanged), but the reminder should be triggered when you execute the command at the beginning of a regular Ex command-line
- Is it possible to adapt this to actually prevent the split from occurring? Removing the %s. from the quoted line above almost achieves
this, but it breaks the :vsplit case.
I haven't tested it very long and I don't know how reliable it is, but you could try this code:
let g:my_overlooked_commands = [
\ { 'old': 'vsplit', 'new': 'C-w v' },
\ { 'old': 'split', 'new': 'C-w s' },
\ { 'old': 'q!', 'new': 'ZQ' },
\ { 'old': 'x', 'new': 'ZZ' },
\]
fu! s:alternative_reminder(old, new) abort
if getcmdline()[-1:-1] ==# ' '
call feedkeys("\<bs>".a:old.' ', 'int')
else
call timer_start(0, {->
\ execute('echohl WarningMsg | echo "['.a:new.'] was equivalent" | echohl NONE', '') })
endif
return ''
endfu
fu! s:remember(list) abort
for cmd in a:list
let old = cmd.old
let sold = string(old)
exe printf('
\ cnorea <expr> %s getcmdtype() ==# ":" && getcmdline() ==# %s
\? timer_start(0, {-> execute("call <sid>alternative_reminder(%s, %s)") }) ? "" : ""
\: %s
\', old, sold, sold, string(cmd.new), sold
\ )
endfor
endfu
call s:remember(g:my_overlooked_commands)