Put this in your vimrc:
map <C-]> :TabExpand 1<CR>
command -nargs=1 TabExpand call HandleTabTagExpand( <f-args> )
let s:commentchar = """
function HandleTabTagExpand(tagnumber)
let tagident = expand("<cword>")
redir @a
try
sil exe "tselect ".tagident
catch /^Vim(\a\+):E433:/ " no tag file
echom "No tag file found."
return
catch /^Vim(\a\+):E426:/ " tag not found
echom "Tag not found."
return
endtry
redir END
let tagresults = split(@a, "\n")
let tagmatches = []
let linenum = 0
for line in tagresults
if linenum % 3 != 1
" every third line contains the file names
let linenum = linenum + 1
continue
endif
" figure out where the filename actually starts
" (it's usually column 32, but it might be farther)
" one before 32 is 31, but index is 30 since arrays begin at zero
let filestart = 30
let c = line[filestart]
while (filestart < strlen(line))
let filestart = filestart + 1
if c == " "
break
endif
let c = line[filestart]
endwhile
" store the parsed match in an array
call add(tagmatches, strpart(line, filestart))
let linenum = linenum + 1
endfor
" navigate to the match specified by tagnumber
try
exe "tab drop ".tagmatches[a:tagnumber-1]
catch /^Vim(\a\+):E471:/ " argument required (means no tag found)
echom "Tag not found."
endtry
let done = 0
let matchcount = 0
let f_line = ""
while done < 1 && matchcount < 1000
sil exe "/".tagident
let f_line = getline(".")
let matchcount = matchcount + 1
if match(f_line, "^\s+".s:commentchar) < 0
let done = 1
endif
endwhile
let f_index = stridx( f_line, tagident )
sil exe "normal 0"
sil exe "normal ".f_index."l"
endfunction
What this does is silently execute tselect, redirecting its output to register a
. Tselect does the same thing as tag or tjump but it doesn't actually perform the jump, only the lookup, and returns a list of results. Without using :redir
those results wouldn't be stored anywhere that we can get at them.
Next, it pulls from register a
the tag output returned from tselect and parses it into a line by line array. The output contains other stuff we don't care about. All we need to know is that every second line (mod 3) has a matching file name starting (normally) at column 32, except where the other stuff in front is longer, so we have to account for that by finding the first space beginning at column 31. The character after the first space is where the file will always begin, regardless of what preceeds it.
Finally we parse the file names one by one, and pass one of those filenames to tab drop, which should open a new tab if one isn't already open, or jump over to the already-open tab if there is one.
Now you can just type
:TabExpand n
(where n is an integer) and it will open existing tab if there is one, or a new tab, for the nth matching tag. The -1 in the array address gives us more user-friendly behavior: when we give it a 1, meaning the first match, it results in zero which is the actual first index in the array.
If you want to jump immediately to the first tag match, you can use the default mapping for tag jump (<C-]>
) because we have remapped it to call :TabExpand 1
.
The last part of the code ignores commented lines (based on the specified comment character), and jumps to the exact line and cursor location of the next tag match. You can press n
to cycle through further matches in the same file.
switchbuf=usetab
orswitchbuf=newtab
has no effect on<c-]>
behaviour :(