Wednesday, July 16, 2008

Vim pr0n: Visual search mappings

I've always maintained that, if you're looking to jack off to the vim help pages, a good place to start is at :help * or :help #. That shit is guaranteed to please. Strangely, vim has no baked-in visual mode equivalents to these mappings i.e. there is no mapping to search for other occurrences of the currently selected text. This is kind of gay, but it's also a blessing when you're in mob formation with a pack of bored-as-fuck nerds on irc. Let me tell you a tale ...

A few days ago in #vim on freenode someone asked how you can create the aforementioned visual mode * and # mappings. Id recently stolen some basic versions from godlygeek's vimrc so I paste binned these and posted the link:
vnoremap * y/<c-r>"<cr>
vnoremap # y?<c-r>"<cr>

Nerds immediately attacked, pointing out that they didn't handle regex escaping. For example if you searched for the string "\<foo.*", the mappings would treat the \< and .* as regex magic rather than the literal strings. So we revised the mappings to these:
vnoremap * y/\V<c-r>=escape(@", '\')<cr><cr>
vnoremap # y/\V<c-r>=escape(@", '\')<cr><cr>

Nerds everywhere began furiously jacking off with one hand while updating their vimrcs with the other. Then strull dumped his version into the channel:
vmap * :<C-U>let old_reg=@"<cr>gvy/<C-R><C-R>=substitute(escape(@",'\/.*$^~[]'),"\n$","","")<CR><CR>:let @"=old_reg<cr>

This one doesn't clobber the unnamed register, and also chops off any trailing \n — handy if you hit * from visual-line mode. His regex escaping was inferior though.

Building on this, godlygeek came along with:
exe 'vnoremap * <C-\><C-n>'
\.':let [@", @/] = reverse([@", @/])<CR>'
\.'gvy'
\.':let [@", @/] = reverse([@", @/])<CR>'
\.":let @/='\\V'.escape(@/, '\\')<CR>"
\.":let @/=substitute(@/, '\\n', '\\\\'.'n', 'g')<CR>"
\."/<CR>"
vmap # *?<CR>?<CR>

He was quite proud of his version because the # mapping was expressed in terms of the * mapping, and because the search string was rightfully jammed in the / register. Probably also because the mapping was fucking nigh incomprehensible.

After a couple more revisions it ended up being expressed, somewhat more readable as this:
function! s:VSetSearch()
let temp = @@
norm! gvy
let @/ = '\V' . substitute(escape(@@, '\'), '\n', '\\n', 'g')
let @@ = temp
endfunction

vnoremap * :<C-u>call <SID>VSetSearch()<CR>//<CR>
vnoremap # :<C-u>call <SID>VSetSearch()<CR>??<CR>

This is the version that is sitting in my vimrc at the moment. Try it out. It owns. Visit the official wiki page here.

No comments:

Post a Comment