Vim Tips Wiki
Advertisement

Proposed tip Please edit this page to improve it, or add your comments below (do not use the discussion page).

Please use new tips to discuss whether this page should be a permanent tip, or whether it should be merged to an existing tip.
created January 30, 2014 · complexity basic · author Q335r49 · version 7.0

A Vim script can wait for user input with getchar(). But the annoying thing about getchar() is that it shifts the cursor to the command line while the user waits. This can be disorienting when trying to write a script that captures and processes all keys in input mode, or when one simply wants the cursor to remain in the same spot in normal mode. This tip provides a workaround.

Basic script

The most basic form is something like this:

imap <F8><Esc> <Nop>
imap <F8> <c-r>=PrintChar()<cr>

function! PrintChar()
  let chr=""
  if getchar(1)
    let chr = getchar()
  endif
  call feedkeys("\<F8>")
  echo chr
  return ''
endfunction

When the user presses F8, the above script simply echos the keypresses until the user presses Escape. Every time PrintChar() is called, it uses feedkeys() to reactivate itself, setting up an input loop.

The first line, imap <F8><Esc> <Nop> serves two functions. First, it allows a way to break the loop. The second is more subtle: it makes the F8 mapping ambiguous, which will cause Vim to pause momentarily to wait for the next keystroke, since it is unsure which mapping the user wants to activate. This is a clever way to slow down the loop while still leaving Vim responsive for user input.

More complex forms

As always, scripts can get complex if you want to handle all the various cases. For example, you might notice that F8 can really be any key. Or, you might wonder how you could incorporate this into your script. Here is what I came up with as a getchar() replacement.

nmap <plug>xyz<Esc> <Nop>
nmap <plug>xyz :call InputLoop()<cr>
function! InputLoop()
  let c = getchar(1) ? getchar() : ""
  if c != 0
    call ProcessChar(c)
  else
    call feedkeys("\<plug>xyz")
  endif
  return ''
endfunction

function! GetChar()
  let g:HandleInput="ProcessChar"
  call InputLoop()
endfunction

function! ProcessChar(c)
  if a:c == char2nr('f')
    echo "You pressed f"
  else
    echo "You didn't press f"
  endif
endfunction

Here, the user calls GetChar() and waits for some input. The user then responds to that input via ProcessChar().

See also

  • vim_use mailing list original tip from Andy Wokula; has a 'typewriter mode' that intercepts all user input

Comments

Advertisement