Search code examples
clinuxctagsexuberant-ctagsuniversal-ctags

How to setup ctags for linux C development?


I am getting into C development under Linux. However I'm struggling with correctly setting up ctags to work with vim. It correctly registers the tags file and offers completion based on it.

I'm not sure what is the proper way to get "necessary" includes. Currently I have some "hand-picked" headers:

AM_CTAGSFLAGS =             \
    --recurse=yes           \
    --tag-relative=yes      \
    --extras=*              \
    --fields=*              \
    --c-kinds=*             \
    --language-force=C      \
    /usr/include/bits       \
    /usr/include/fcntl.h    \
    /usr/include/stdio.h    \
    /usr/include/stdlib.h   \
    /usr/include/string.h   \
    /usr/include/sys        \
    /usr/include/unistd.h

In a first step, I just put same headers as I have #include ... but that missed some stuff (like /usr/include/bits/...). So I have added those.

But I feel like after all these years someone somewhere had to come up with better solution. So, how is this commonly done?


Solution

  • That's an excellent question, graywolf!

    I've been using this approach for years and here's how I use it:

    1. Generate a tags file for a logical component that your system uses. A logical component is typically what you may think of as an import in other languages (i.e. I'm using OpenGL or some sound library).
    2. For a given logical component, you may have to ignore some text in the header file. This is to keep the ctags program from misinterpreting certain constructs and thus misrepresenting those particular tags in the generated file (think preprocessor macros or compiler directives) or not even placing them into the output file at all.
    3. Modify your path Vim variable to point, in order, to the files that you have generated. I choose an innermost-out pattern. The project code itself first, libraries within the project next, followed by separate system libraries, followed lastly by a final tag file that represents "everything else".

    I usually have a single tags file for the ANSI C headers (or C++ if you use that instead) appropriate for a particular standard (typically c99. c++11 if you use C++), which typically is my "everything else" tags file.

    The hardest part is feeding in the ignore list to ctags. Since you are using C, I'll assume you use the C headers from the system (just the compiler-standard ones that should be available with the language). I use the following ignore list for when I generate these (on Ubuntu 14.04):

    __attribute__
    __attribute_deprecated__
    __attribute_format_arg__+
    __attribute_format_strfmon__+
    __attribute_malloc__
    __attribute_noinline__
    __attribute_pure__
    __attribute_used__
    __attribute_warn_unused_result__
    __attribute_alloc_size__+
    __attribute_const__
    __attribute_artificial__
    __wur
    __THROW
    __THROWNL
    __BEGIN_DECLS
    __END_DECLS
    __BEGIN_NAMESPACE_STD
    __END_NAMESPACE_STD
    __USING_NAMESPACE_STD+
    __BEGIN_NAMESPACE_C99
    __END_NAMESPACE_C99
    __USING_NAMESPACE_C99+
    __warndecl+
    __warnattr+
    __errordecl+
    __flexarr=[]
    __fortify_function
    __REDICRECT+
    __REDIRECT_NTH+
    __REDIRECT_NTHNL+
    __ASMNAME+
    __ASMNAME2+
    __nonnull+
    __always_inline
    __extern_inline=extern
    __extern_always_inline=extern
    __extension__
    __restrict
    __restrict_arr

    This is the most important part. If you don't see much output for headers that you've run ctags on, that's probably what's going on. It drove me nuts until I figured this out. Those keep ctags from being fooled.

    As far as the headers input into ctags, you have the right idea. Don't forget the compiler-specific system headers that are usually located somewhere else like /usr/include/x86_64-linux-gnu. Those will help you drill down to your system's constants if necessary.