I'm sure there's something I can do with ctags or something like that, but I actually like writing vim functions[1]. So my new function is to position the cursor on any subroutine name or method call, hit ",gs" and automatically either jump to that sub or be presented with a list of packages with that sub definition.
function! GotoSub(subname)
let files = []
" find paths to modules with that sub
let paths = split(system("ack --perl -l 'sub\\s+".a:subname."' lib t/lib"), "\n")
if empty(paths)
echomsg("Subroutine '".a:subname."' not found")
else
let file = PickFromList('file', paths)
execute "edit +1 " . file
" jump to where that sub is defined
execute "/sub\\s\\+" . a:subname . "\\>"
endif
endfunction
Of course, you might wonder what that "pick from list" is. I keep needing to either let a user select from a list of choices (usually file names) or just return the one damned choice if there is only one.
function! PickFromList( name, list, ... )
let forcelist = a:0 && a:1 ? 1 : 0
if 1 == len(a:list) && !forcelist
let choice = 0
else
let lines = [ 'Choose a '. a:name . ':' ]
\ + map(range(1, len(a:list)), 'v:val .": ". a:list[v:val - 1]')
let choice = inputlist(lines)
if choice > 0 && choice <= len(a:list)
let choice = choice - 1
else
let choice = choice - 1
endif
end
return a:list[choice]
endfunction
And the actual mapping is simple:
noremap ,gs :call GotoSub(expand(''))
And now I navigate through my code so much faster.
1. Though I spent a fair amount of time this morning writing bash to autogenerate database diffs and the transition was a bit confusing.