55

I'm having the problem that ctags in vim/gvim takes me to a forward declaration a lot of times instead of to the actual definition of the function.

Any way to get around that?

Undo
  • 25,519
  • 37
  • 106
  • 129
Robert S. Barnes
  • 39,711
  • 30
  • 131
  • 179

10 Answers10

88

I think that the easiest way is to use "g ctrl-]" instead of just "ctrl-]". If there is only one match, it will take you there. If there are multiple matches, it will list them all, letting you choose the one you want, just like :tselect. The best of both worlds. :)

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Derek
  • 3,087
  • 1
  • 21
  • 23
  • For me, this just gives me two forward declarations (in different files) of functions that share the name. But it doesn't show the implementation of the function. – frankster Feb 18 '10 at 11:59
  • 2
    Wow. That's life changing. – Praxeolitic Aug 18 '13 at 17:25
  • Another way I found is to just use `ctags` on header files instead of on all files. This is especially useful for me when working with a library, where I am mostly just interested in the declarations which sort of serve as a rudimentary documentation. – Victor Zamanian Feb 19 '14 at 16:19
  • @VictorZamanian: that will only work if have header files, as in C. – eddy147 Mar 26 '15 at 09:13
  • @eddy147, indeed -- I thought that's what we were discussing considering the use of "declaration vs. definition". Sorry if I misunderstood! – Victor Zamanian Mar 27 '15 at 10:12
  • I think the original question was dealing with the situation where you have declaration in the C file, like "void foo();" with no code, and the actual code for foo is later on in the C file. Vim will generally take you to the declaration not the code, but the asker wanted to get to the code. With my answer, you can choose which you want to go to. – Derek Apr 02 '15 at 20:40
  • In my current case (for which I could not find the answer here), my tags file points to the declaration not the definition which is somewhere below in the same file. This is because the tag file lists the pattern to look for in the file which is identical for declaration and definition. How do you handle this case? (if the declaration is in a separate header file, we would see two candidates for the tag, but if the declaration and definition is in the same file, we only have one location). – Chan Kim Jan 06 '16 at 05:32
  • 4
    This is not a solution at all. The problem is that ctags indexes only the declaration, and not the definition. Even if you type ":tj " you see only one entry: the wrong one. This is not a Vim problem, but a ctags problem. – Kaz Apr 21 '20 at 00:34
10

You should be able to use tn and tp to jump to the various matching tags.

  • Press ^] to take you to the first match.
  • If that's not the match you want, type :tn to go to the next.
  • If you typed :tn too many times you can type :tp to return to the previous one.
Nathan Fellman
  • 122,701
  • 101
  • 260
  • 319
10

I believe Vim goes to the first tag in the tags file by default. You can select a different one if you prefer: use :tj (similar to :tselect, but auto-jump if there's only one match) or Ctrl-] followed by :tn).

The only way of changing the default is to change the order of the tags file, but I don't believe ctags offers a command-line option to do this.

This isn't as hard as it sounds as you basically need a script that opens the tags file, sorts it by the 'kind' of tag and writes it back out again. The 'kind' in the tag is a single character describing whether it's a function (f), a function prototype (p), a macro, a enumerated name etc etc etc. If you're using Linux, it could, in theory, be as simple as:

#!/bin/sh
ctags -R -f - . | tac > tags

Since tac reverses the order of lines in a file, this will automatically put the definition first. However, it gets a bit more complicated as the header needs to be maintained and Vim prefers the tag file to be sorted, so it's better to go through the file and sort on the first entry (the tag name) in forward order and then the kind in reverse order. Therefore, something more complicated may be better.

I apologise for the shameless plug, but I have written a Vim plugin that (indirectly) does what you need. It is intended for adding lots of extra highlighting groups for things like function names, macros, enums etc. However, one of the other things that this does is re-sort the tag file so that the function implementation comes before the function declaration, thereby achieving what you want (I had the same need as you). If you don't want any of the highlighting functionality, you could probably strip it all out quite easily: it's a fairly simple python program and an even simpler Vim script and is available from my site.

DrAl
  • 70,428
  • 10
  • 106
  • 108
  • The problem is that ctags sometimes classifies only the forward declaration, and not the definition. The tags entry (of whic there is exactly one) searches for the line by pattern, and that pattern matches the forward declaration before the definition. – Kaz Apr 21 '20 at 00:37
4

Late to the party, but for incoming vim tag googlers:

I've found that using cscope in addition to ctags is the way to go, at least for C/C++. It's more intelligent about call trees, and you can set it to fallback to ctags if it fails. Just run "cscope -b" everytime you run ctags -R . and you'll be ready to go. If you use the settings below, you'll be able to use Ctrl-]/Ctrl-T like always, but you can also add nifty new jumps like jumping to a function declaration and showing a jumplist of function callers.

" setup
if has("cscope")
    set csto=0                                                                             
    set cst
    set nocsverb
    " add any database in current directory
    if filereadable("cscope.out")
    cs add cscope.out
    " else add database pointed to by environment
    elseif $CSCOPE_DB != ""
    cs add $CSCOPE_DB
    endif
    set csverb
endif

" jump to a function declaration
nmap <silent> <C-\> :cs find s <C-R>=expand("<cword>")<CR><CR>1<CR><CR>
" show a list of where function is called
nmap <silent> <C-_> :cs find c <C-R>=expand("<cword>")<CR><CR>
Steve Goranson
  • 329
  • 2
  • 8
  • What do you mean by 'Just run "cscope -b" everytime you run ctags -R '? run cscope -b before running ctags -R? or after? – Chan Kim Jan 06 '16 at 05:39
2

This option worked for me better

Put the following line in your .vimrc and now you can use double click of the mouse (on the variable/entry in your file) to jump to the tagged location. If single match is found, it will jump right away. If multiple entries are matches, it will prompt for user input..

:map <2-LeftMouse> g< c-]>
Niall C.
  • 10,878
  • 7
  • 69
  • 61
  • Couldn't edit but there is a extra space typo after second `<`. Also `set mouse=n` or `set mouse=a` for the mouse to work in Vim. – cassepipe Feb 21 '22 at 16:45
1

Add the following to your .vimrc file:

noremap <c-]> 2<c-]>

This line causes vim to automatically jump to the second match (instead of the first one), which is usually the function definition.

Yariv
  • 486
  • 6
  • 12
0
:tselect my_little_function 

and you would get a list of matches. or if you jump to a tag and you are not happy with it, then type

:tselect

And you get a list of alternative for the last active tag.

Johan
  • 20,067
  • 28
  • 92
  • 110
0

There are several ways to make Vim to jump to a tag directly, if there is only one tag match, otherwise present a list of tag matches.

You can use the 'tjump' ex command. For example, the command ':tjump func1' will jump to the definition func1, if it is defined only once. If func1 is defined multiple times, a list of matching tags will be presented.

You can position the cursor over the tag and press g Ctrl-].

You can visually select a text and press g Ctrl-] to jump or list the matching tags.

You can use the 'stjump' ex command. This will open the matching or selected tag from the tag list in a new window.

You can press Ctrl-W g Ctrl-] to do a :stjump.

Help: :tjump, g_Ctrl-], v_g_CTRL-], :stjump, Ctrl-W_g_Ctrl-]
Tarek Saied
  • 6,482
  • 20
  • 67
  • 111
  • This only works if ctags has actually classified both the forward declaration and the definition. It's a clumsy workaround for a problem that is in ctags, not in Vim. – Kaz Apr 21 '20 at 00:35
0

This can happen because the pattern match stored in the tags file finds the declaration before the definition.

When Exuberant Ctags creates an entry for an identifier, it adds the entire containing line as a match.

However, sometimes the forward declaration matches that pattern. This can happen under certain coding styles.

First, here is an example file, foo.c which doesn't have this problem:

static int foo(int x);
static int foo(int x)
{
}

Ctags creates an entry which looks like this:

foo foo.c   /^static int foo(int x)$/;" f   file:   signature:(int x)

The match is anchored with ^ and $, and since the forward declaration ends with ;, it does not match.

However, the following coding convention triggers the issue:

static int foo(
 int x
);

static int foo(
  int x
)
{
}

The tags entry is now:

foo foo.c   /^static int foo($/;"   f   file:   signature:( int x )

This will find the first line of the declaration, which is indistinguishable from the first line of the definition.

The fix is to make these two somehow different, while staying within the coding convention. In this case we are in C, so one thing we can do is drop the static from the definition.

A C file scope declaration that has no storage class specifier declares a name with linkage. The linkage type (internal or external) is inherited from any previous declaration of the name that specified linkage. Thus:

static int foo(
 int x
);

int foo(
  int x
)
{
}

And now the tag looks like:

foo foo.c   /^int foo($/;"  f   signature:( int x )

which matches only the definition; Vim no longer jumps to the first declaration.

If this solution is not workable, then it may be necessary to write a tags filtering tool which scans the tags file and identifies this problem, fixing up the offending tags. For example, for the second file with the ambiguous lines, we can fix up the tag manually like this:

foo     foo.c   /^static int foo($/;/^static int foo($/;"       f       file:   signature:( int x )

I added a second Ex command into the tag-address, to search for the same pattern again. Now it jumps to the correct line. (Note: however, this is broken if the forward declaration is the very first line of the file.)

Finding ambiguous tags which have more than one match in the file and editing them in the above manner (or some other idea) can be automated, turned into a post-processing pass done each time the tags file is generated. The additional scan of the files is expensive, though; this really should be done in Ctags.

Kaz
  • 55,781
  • 9
  • 100
  • 149
-1

You can press 2 then CTRL+], this will go directly to the second match, in java, this is usually the implementation of some interface.

César Alforde
  • 2,028
  • 2
  • 15
  • 17