Thursday, 24 November 2011

Vim 'put' hook which prompts user for confirmation

Yesterday, whilst hacking on a particularly large Upstart C file in the awesome vim editor, I inadvertently 'put' a huge amount of data, caused by my mis-specifying the marks when I had originally yanked the text. Still unaware of the problem, I then compounded the problem nicely by continuing to make further changes to the file and dug myself an even bigger hole by saving the file and exiting vim.

Attempting to compile the now totally invalid code resulted in a seething mass of mocking gcc warnings until it eventually gave up in disgust. Thankfully, I was taking regular backups with rsnapshot and I had the bzr branch history to fall back on. But it still took some time to perform the 3-way diff and get back to my latest changes.

Later on, we had our weekly IRC meeting in #ubuntu-meeting. I use irssi and as I pasted my weekly status update, since I was pasting more than a single line, irssi gave me a helpful warning and the option to either proceed, or abort the multi-line paste operation. This got me thinking... wouldn't it be great if vim did something similar?

After a bit of hacking... behold!:

" Warn user before they attempt to 'put' (paste)
" more data than intended.
function SafePut()
  " Get text of whatever register user is attempting to put.
  let data = getreg(v:register)

  " Count lines.
  let lines = str2nr(len(split(data, '\n')))

  " My chosen heuristic: attempting to put more lines than you can
  " currently see on-screen should be an unusual operation.
  " Tweak as you see fit.
  let threshold = winheight(0)

  if lines > threshold
    echo "Really put " . lines . " lines (y/[n])? "
    let response = nr2char(getchar())
    if response == 'y'
      " ':put' only allows either an actual register name,
      " or some text to put. We'd really like to be able
      " to specify a variable ('v:register'), but we make do
      " with simply passing the *contents* of the actual data
      " in the last register the user accessed.
      put =data
      redraw | echo lines . " more lines"
    else
      redraw | echo "Aborted put operation"
    endif
  else
    " Below threshold, so perform put.
    put =data
  endif
endfunction

" bind SafePut() to the 'p' (put) command
:noremap p :call SafePut()<CR>


There may well be more efficient ways to do this, but if you place the snippet above into your ~/.vimrc, whenever you attempt to put more lines of code than there are lines in your terminal, vim will prompt you to give you the chance to consider your actions.

To undo this hook without restarting vim, press ESCAPE and type:

:unmap p

Other ideas:

  • Somehow(?) put a similar hook in place for the yank operation

    In other words, deal with the problem "at source" (pun fully intended)
  • Have vim autobuild my code whenever I save the file and before I quit

Now all I need is an extension to avoid the dreaded "paste bounce", generally resulting from too much caffeine!