vim workflow: go to definition with ctags

I am always curious about other people’s vim workflow, especially when it comes to project management and goto definitions with ctags. I have now used vim quite some time and want to share my personal workflow. This is about how to create custom local vim configuration files per project and how to manage all of your ctag files easily.

TL;DR

So basically what I do is:

  • Go to my project root
  • Create vim project file
  • Create ctag files
  • Start coding

All done automatically with a one-liner:

$ make-vim-project all

1. The whole story

1.1 Install Dependencies

As I am currently using a MacBook due to work, I have to deal with OSX and therefore of course use homebrew to install my stuff. So first I need to get the exuberant version of ctags.

brew install ctags-exuberant

1.2 Vim and ctags

I am trying to separate programming languages into different ctag files. For example, one file for c/c++, one file for shell-scripts, one file for javascript and so on. For this to work, I need to tell vim where to look for the files. The vim section looks like this:

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" CTAGS/CSCOPE
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Default/Generic tag file
set tags=tags,.tags

" Filetype specific tag files (This is used for global IDE tags)
autocmd FileType c              set tags=.tags_cpp,$HOME/.vim/tags/cpp
autocmd FileType cpp            set tags=.tags_cpp,$HOME/.vim/tags/cpp
autocmd FileType css            set tags=.tags_css,$HOME/.vim/tags/css
autocmd FileType java           set tags=.tags_java,$HOME/.vim/tags/java
autocmd FileType javascript     set tags=.tags_js,$HOME/.vim/tags/js
autocmd FileType html           set tags=.tags_html,$HOME/.vim/tags/html
autocmd FileType php            set tags=.tags_php,$HOME/.vim/tags/php
autocmd FileType sh             set tags=.tags_sh,$HOME/.vim/tags/sh

1.3 Vim and project files

Now I need a way to always tell vim where my project root is in order for it to look for the project specific ctag files. For this I am using local_vimrc via NeoBundle. Here is how to get it into vim.

" ---- PROJECT vimrc
NeoBundle 'LucHermitte/lh-vim-lib', {
\   'name': 'lh-vim-lib'
\}
NeoBundle 'LucHermitte/local_vimrc', {
\   'depends': 'lh-vim-lib'
\}

This plugin will check the root directory for a file called _vimrc_local.vim. The only thing I want to place into this file is the cd path, so it know the root of the project directory.:

$ cat /path/to/project/_vimrc_local.vim

:cd /path/to/project

Whenever I open vim from within this project path, it will check if there a ctag files as defined in vim and ctags above.

1.4 Creating project and ctag files

The setup is almost complete and I just need to create the project and ctag files for every project in its root. So first creating the project file:

$ cd /path/to/project && echo ":cd $(pwd)" > _vimrc_local.vim

And then I will add the ctag files. Here is an example for a c/c++ project:

$ ctags -R -f .tags_cpp \
    --file-scope=yes \
    --sort=yes \
    --c++-kinds=+p \
    --fields=+iaS \
    --extra=+q \
    2>/dev/null

This kind of sucks as I don’t want to issue those long commands every time I create a new project or update my ctags. So it needs to be automated or at least simplified.

1.5 Using a bash functions for project and ctag files

On the most simple form I just want to issue a single command which does everything for me. So I wrote a bash function make-vim-project:

$ make-vim-project
Usage: make-vim-project <type>

all     Create ctags for every filetype
web     Create ctags for php, js, css and html
cpp     Create ctags for c/c++
shell   Create ctags for bash/sh

Now I can create a c/c++ project easily by just typing this:

$ make-vim-project cpp

It will automatically create the _vimrc_local.vim as shown above and all c/c++ relevant ctag files. I also use this command once I update my project. So how does the function look and where do I put it?

First, it can be put anywhere in .bash_profile, .bashrc or any other custom bash file that is sourced by the main bash configuration file. Let’s have a look at the function itself:

#------------------------------------------------------
#-------- Vim Project
make-vim-project() {
    local name dir

    name="_vimrc_local.vim"
    dir="$(pwd)"

    read -r -d '' USAGE <<-'EOF'
Usage: make-vim-project <type>

all     Create ctags for every filetype
web     Create ctags for php, js, css and html
cpp     Create ctags for c/c++
shell   Create ctags for bash/sh
EOF

    if [ $# -ne 1 ]; then
        echo "$USAGE"
        return
    fi

    # CTAGS
    echo "Building ctags"
    if [ "$1" == "all"  ]; then
        make-ctags
        make-ctags-css
        make-ctags-js
        make-ctags-html
        make-ctags-php
        make-ctags-sql
        make-ctags-shell
        make-ctags-cpp
    elif [ "$1" == "web" ]; then
        make-ctags-php
        make-ctags-html
        make-ctags-js
        make-ctags-css
        make-ctags-sql
    elif [ "$1" == "cpp" ]; then
        make-ctags-cpp
    elif [ "$1" == "shell" ]; then
        make-ctags-shell
    else
        echo "$USAGE"
        return
    fi

    # Vimrc
    echo "Creating local vimrc"
    echo ":cd ${dir}" >> "${name}"
}

As you can see, the function just prints its usage, calls other make-ctags-* functions and creates the _vimrc_local.vim file. Have a look at the gist for the complete source of all other make-ctags-* functions:

cytopia/create-vim-project cytopia

Just for clarification, here is how one of the ctag functions will look:

make-ctags-cpp() {
    ctags -R -f .tags_cpp \
        --file-scope=yes \
        --sort=yes \
        --c++-kinds=+p \
        --fields=+iaS \
        --extra=+q \
        2>/dev/null
}

1.6 Project root

Let’s have a look what files are inside my project root after using make-vim-project all:

$ ls -la
...
-rw-r--r--  1 cytopia 1286676289 73381097 Oct 17 12:01 .tags
-rw-r--r--  1 cytopia 1286676289 72893221 Oct 17 12:02 .tags_cpp
-rw-r--r--  1 cytopia 1286676289  1776509 Oct 17 12:01 .tags_css
-rw-r--r--  1 cytopia 1286676289   409973 Oct 17 12:01 .tags_html
-rw-r--r--  1 cytopia 1286676289 64329626 Oct 17 12:01 .tags_js
-rw-r--r--  1 cytopia 1286676289  8989441 Oct 17 12:01 .tags_php
-rw-r--r--  1 cytopia 1286676289     6223 Oct 17 12:01 .tags_sh
-rw-r--r--  1 cytopia 1286676289    52748 Oct 17 12:01 .tags_sql
-rw-r--r--  1 cytopia 1286676289       32 Oct 17 12:02 _vimrc_local.vim

2. What next?

This workflow has evolved during over a year of vim experience and as my personal preference. I am still not quite satisfied with some manual work, especially for updating the ctags once you have added code. If any of you have some better workflows and/or can recommend other vim plugins that do the trick more automated, please let me know and share.

_

2 comments on “vim workflow: go to definition with ctags”

  1. shampoosik Reply

    Even if this script is specific to ruby, the general approach could be used for any kind of project. Just maintain a list of all libraries your application is using, find them locally and run ctags on that.

  2. hurrtz Reply

    Dear cytopia,

    thank you very much for this article about ctags. I would like to inform you about the current state of affairs inside the vim community and invite you to issue a follow up post:

    ctags are now, thankfully, a thing of the past since the adoption of LSP (language server protocol) on backend side and their client side implementations via Ale or Coc (or others).

    For further reading:

    https://langserver.org

    https://github.com/neoclide/coc.nvim

    Bottom line: You don’t need to parse your project any more and have a tool create a tags file. Nowadays you instantiate an appropriate language server that understands the language of your project and you get all the features of ctags on the fly.

    Sincerely

    hurrtz

Leave a Reply to hurrtz Cancel reply

Your email address will not be published.