Wikia

Vim Tips Wiki

Wait for user input (getchar) without moving cursor

Talk0
1,613pages on
this wiki
Revision as of 14:47, January 31, 2014 by Q335r49 (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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 scriptEdit

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 formsEdit

As always, these scripts can get rather complex if you're trying to account for all of Vim's quirks and nuances. For example, you might notice that F8 can really be any key. Here is what I came up with as a more general purpose key handler.

nmap <silent> <plug>GC<esc> :exe g:GC_ProcEsc<cr>
nmap <silent> <plug>GC :call GetChar()<cr>
fun! GetChar()
	exe getchar(1)? g:GC_ProcChar : feedkeys("\<plug>GC")[17]
endfun

fun! KeyPrinter()
	let g:GC_ProcChar="call KeyPrinterHandler(getchar())"
	let g:GC_ProcEsc="call KeyPrinterHandler(27)"
	call GetChar()
endfun
fun! KeyPrinterHandler(c)
	let k=type(a:c)==0? nr2char(a:c) : a:c
	let c=getchar(0)
	while c isnot 0
		let k.=type(c)==0? nr2char(c) : c
		let c=getchar(0)
	endwhile
	exe get(g:KPDict,k,g:KPDict.default)
endfun

let KPDict={}
let KPDict.a='ec "You pressed a"'
let KPDict.b='ec "You pressed b"'
let KPDict.default='ec "The key you pressed isn''t handled"'
let KPDict["\eOP"]='ec "You pressed F1"'
let KPDict["\ek"]='ec "You alt-k"'

KeyPrinter() will echo what key your pressed based on the dictionary KPDict. Some explanation:

  • The script sets g:GC_ProcChar and g:GC_ProcEsc and then calls GetChar().
  • feedkeys(...)[17] is just a cute way to produce an empty string, since 0[17] is an empty string.
  • In KeyPrinterHandler, there is a loop that gets the full keypress. For example, the F1 key is actually ^[OP, but ^[ is eaten up by our mapping, so we would need to check whether any keypresses have been buffered.
  • Note the while c isnot 0. In vimscript, strings are in fact 'equal to' 0 (ie 'f'==0 is true) so we need the is comparison here, since c can be a number or a string. ('f' isnot 0 is true)

See alsoEdit

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

CommentsEdit

Around Wikia's network

Random Wiki