How do I remap key combinations with a free variable?

For example, let's say I want to be able to insert a single letter without leaving normal mode. I would want something like

nnoremap <leader>i[variable] i[variable]<Esc>

such that pressing <leader>iy inserts y, pressing <leader>iu inserts u, etc.

(this is a stupid example because it doesn't reduce the number of keystrokes)

EDIT: For a more practical example, consider a command that surrounds the current word with the pressed character, i.e.

nnoremap <leader>s[variable] viws[variable][variable]<Esc>P

such that <leader>s" turns word into "word".

At the moment I have such a command (and an equivalent for visual mode surroundings) for ", ' and *.

(I also have extended commands that surround selections with two different characters, i.e. (), <> [] {} <> upon pressing only the first of these. For these I can also imagine having the same command and simply looking up the closing surrounding letter as a function of the input opening character)

  • 1
    Can we get a more practical example? Theres different ways to accomplish different results; for example, a mapping could prompt for a character and make use of the result, or it could be dynamically generated at startup with an execute statement. Depends on your end goal here.
    – D. Ben Knoble
    Aug 15 '18 at 16:06
  • One possibility is make it so that the [variable] is the argument to the last command in your mapping... Such as nnoremap <leader>i i_<Esc>r, where the last r (to replace a single character) is pending and will consume the next character...
    – filbranden
    Aug 16 '18 at 4:48
  • @D.BenKnoble See the edit
    – Bananach
    Aug 16 '18 at 8:12

If you're using Vim, you can use the getchar() function to get a single keystroke and take its numeric value, and then the nr2char() function to convert that into a character string again.

For your example above, inserting a single character, this does the trick:

nnoremap <leader>i :execute "normal i" . nr2char(getchar()) . "\<lt>esc>"<cr>

UPDATE: To handle the specific case you mentioned (surrounding word with characters, such as quote), this would work:

function! s:Surround()
  let chr = nr2char(getchar())
  return "viws" . chr . chr . "\<esc>P"

nnoremap <leader>s<esc> <nop>
nnoremap <expr> <leader>s <SID>Surround()

Using a function and calling nnoremap with <expr> to execute a function and produce the expansion for the map.

Inside a function, you can store the result of getchar() in a variable and use that multiple times.

The other mapping <leader>s<esc> takes care of the trick to prevent moving the cursor. Since the mapping uses the same prefix, Vim will wait for another key to decide which mapping to use, so it will only call Surround() when it already knows which key to use. The mapping using <esc> will be a <nop>, which is also a somewhat convenient way to cancel the mapping.

Perhaps Surround() could use more error checking, possibly looking at whether chr is actually a printable character, to avoid weird effects in case nr2char() can't make a printable character of the pressed key combination.

  • One small annoyance is that getchar() will move the cursor to the command line, but take a look at this Vim trick for a possible workaround.
    – filbranden
    Aug 16 '18 at 5:22
  • That looks quite complicated for such a simple goal. I neither understand how I would adapt that to the more practical example that I updated the question with nor what to do with the vim trick. I am using vim, but I'm quite new to it
    – Bananach
    Aug 16 '18 at 8:16
  • @Bananach Edited the answer to cover that specific case. Seems to work quite well :-)
    – filbranden
    Aug 16 '18 at 8:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.