Case Study in Vim Plugin Development

Posted on July 22, 2015 by Richard Goulter
Tags: programming.editors, programming.vim

I don’t know a lot about writing plugins.
My own experience goes as far as trying to write a plugin for Eclipse (which gets very complicated as soon as a plugin isn’t a “monolithic” bundle); as well as writing a quite small plugin for my C worksheet. (The fanciest thing it does is to download a JAR archive dependency if not present in the system.).

So I’m not particularly qualified to say what is or isn’t a good plugin architecture, etc.

I recently came across the problem: “In Vim, use * to find next occurence of current selection in visual mode”.
Vim doesn’t offer this behaviour out of the box.
The fella on IRC was using nelstrom/vim-visual-star-search. The readme for this somewhat helpfully links to thinca/vim-visualstar. – Neither of these repos is outstandingly popular.

Nelstrom remarks “Looks like it uses a lot more code to do pretty much the same thing. Is it better?”.

Nelstrom’s plugin is simple enough to show here:

" From http://got-ravings.blogspot.com/2008/07/vim-pr0n-visual-search-mappings.html

" makes * and # work on visual mode too.
function! s:VSetSearch(cmdtype)
  let temp = @s
  norm! gv"sy
  let @/ = '\V' . substitute(escape(@s, a:cmdtype.'\'), '\n', '\\n', 'g')
  let @s = temp
endfunction

xnoremap * :<C-u>call <SID>VSetSearch('/')<CR>/<C-R>=@/<CR><CR>
xnoremap # :<C-u>call <SID>VSetSearch('?')<CR>?<C-R>=@/<CR><CR>

" recursively vimgrep for word under cursor or selection if you hit leader-star
nmap <leader>* :execute 'noautocmd vimgrep /\V' . substitute(escape(expand("<cword>"), '\'), '\n', '\\n', 'g') . '/ **'<CR>
vmap <leader>* :<C-u>call <SID>VSetSearch()<CR>:execute 'noautocmd vimgrep /' . @/ . '/ **'<CR>

Thinca’s is certainly longer:

function! s:search(type, g)
  let s:count = v:count1 . a:type
  let reg = '"'
  let [save_reg, save_type] = [getreg(reg), getregtype(reg)]
  normal! gv""y
  let text = @"
  call setreg(reg, save_reg, save_type)

  let [pre, post] = ['', '']
  if !a:g
    ....
  endif

  let text = substitute(escape(text, '\' . a:type), "\n", '\\n', 'g')

  let @/ = '\V' . pre . text . post
  call histadd('/', @/)
endfunction

(Code truncated at .... for brevity).

With my current proficiency, I’m sure Nelstrom writes closer to how I’d write:

Even from this snippet it’s clear that Thinca’s is of higher quality. I’d base that one the use of functions to do caretaking of the editor environment. – It’s not clear what the argument g is for (like the g in substitute?), but the ommitted lines seem to take care of some edge case.
– Something to note, I guess, it seems that in both cases the VimL is ‘impure’ in providing read-only info. (Again, I’m not sure if it’s possible to get this without the caretaking.).

Not hatin’ on Nelstrom: while the Vim community has some excellent plugins, I think most Vim users don’t bother touching VimL enough to learn how to do this kindof thing.


Newer post Older post