RTags is a client/server application that indexes C/C++ code and keeps a persistent file-based database of references, declarations, definitions, symbolnames etc. There’s also limited support for ObjC/ObjC++. It allows you to find symbols by name (including nested class and namespace scope). Most importantly we give you proper follow-symbol and find-references support. We also have neat little things like rename-symbol, integration with clang’s “fixits” (http://clang.llvm.org/diagnostics.html). We also integrate with flymake using clang’s vastly superior errors and warnings. Since RTags constantly will reindex “dirty” files you get live updates of compiler errors and warnings. Since we already know how to compile your sources we have a way to quickly bring up the preprocessed output of the current source file in a buffer.
While existing taggers like gnu global, cscope, etags, ctags etc do a decent job for C they often fall a little bit short for C++. With its incredible lexical complexity, parsing C++ is an incredibly hard task and we make no bones about the fact that the only reason we are able to improve on the current tools is because of clang (http://clang.llvm.org/). RTags is named RTags in recognition of Roberto Raggi on whose C++ parser we intended to base this project but he assured us clang was the way to go. The name stuck though.
Build RTags, start rdm, index some files, (rtags-follow-symbol-at-point)
$ cd rtags $ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=1 $ make $ rdm & $ rc -J . $ # wait for rdm to be silent $ emacs src/rdm.cpp (require 'rtags) # Go to /Users/abakken/dev/rtags-master/src/rdm.cpp:30:34 if (Server *server = Server::instance()) # /|\ # | (rtags-follow-symbol-at-point) # Your location is now on the definition of Server::instance()
There are a few prerequisites you need to have in order to build RTags
- libclang, preferrably 3.2 or higher, on Linux you may be able to use your distro package manager to install this. On OS X you can use homebrew or possibly other package managers, though see below for details if you plan to tag projects that use C++11 features (such as RTags itself). Alternatively you can grab the sources from llvm.org and build it yourself.
- A modern compiler. This means GCC >= 4.7 or Clang >= 3.2. RTags makes extensive use of C++11 features such as variadic templates and threading utility classes from the C++ standard library.
- CMake (>= 2.8), the build system used by RTags.
- RTags might very well work with much older emacsen but the oldest version we test with is 24.3.1. There’s no particular effort made to support older versions but patches that make it work are welcome.
If you plan to tag projects using C++11 features on OS X then you’ll need a custom libclang, the default one built through homebrew does not support using LLVM’s libc++. You’ll need to install one from homebrew-versions instead and explicitly tell it to enable libc++. For LLVM 3.6 the following works:
$ brew tap homebrew/versions $ brew install llvm36 --with-libcxx --with-clang --without-assertions --rtti
or you can install clang and llvm from macports
sudo port install clang-3.5
To build RTags, you need to checkout the repository’s submodules, and run CMake.
$ git submodule init $ git submodule update
We recommend building in a separate directory to keep the build files
separate from the source, but you can run cmake
in the source tree
if you prefer.
$ mkdir build $ cd build $ cmake .. $ make $ make install
If you want to configure the build interactively, run ccmake
(CMake
with an ncurses UI) instead of cmake
.
RTags needs three pieces of information about libclang. All of these can be provided to cmake by way of an environment variable or a cmake variable. If not provided we will try to find llvm-config and interrogate it for the information. You can tell rtags which llvm-config to use like this:
LLVM_CONFIG=/path/to/llvm-config cmake .
or
cmake -DLLVM_CONFIG=/path/to/llvm-config .
If you don’t we will look for variations of the llvm-config executable name in your $PATH.
The three things we need are:
- CLANG_CXXFLAGS Usually something like this:
$ llvm-config --cxxflags -I/usr/local/Cellar/llvm36/3.6.0/lib/llvm-3.6/include -DNDEBUG -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -O3 -std=c++11 -fvisibility-inlines-hidden -fno-exceptions -fno-common -Woverloaded-virtual -Wcast-qual
- CLANG_LIBDIR Usually something like this:
$ llvm-config --libdir /usr/local/Cellar/llvm36/3.6.0/lib/llvm-3.6/lib
We need this to locate clang’s system headers and we will assume that they are located in: ${CLANG_LIBDIR}/clang/CLANG_VERSION_STRING/include (/usr/local/Cellar/llvm36/3.6.0/lib/llvm-3.6/lib/clang/3.6.0/include) There should be headers like stdarg.h and limits.h in this directory.
- CLANG_LIBS Usually something like this:
-L/usr/local/Cellar/llvm36/3.6.0/lib/llvm-3.6/lib -lclang
Unless specified we will take try to find these libraries using cmake’s find_library features and/or assuming that they there will be a libclang.(so|dylib) in ${CLANG_LIBDIR}
Like with LLVM_CONFIG these variables can be override as a cmake variable (cmake -DCLANG_LIBDIR=…) or an environment variable (CLANG_LIBDIR=… cmake)
RTags uses C++11 features and requires a relatively new compiler. Gcc version >= 4.8 or clang >= 3.2 works.
We also require the following libraries:
- libclang (Not sure what the minimum version is but >= 3.5 is recommended)
- libcurses (for some reason clang requires this)
rdm runs in the background and monitors all your indexed files for changes and reindexes when a source file or one of its dependencies is modified. Since clang is a fully compliant compiler it needs specific information about how your sources are compiled to be able to properly index them. This is done through telling rdm about the compile line like this:
$ rc -c gcc -I... -fsomeflag -c foobar.c $ rc -J /path/to/a/directory/containing/compile_commands.json
- Normally one achieves this in one of these ways:
- Make the build system output all compilation commands. E.g. if you’re using ninja (http://martine.github.io/ninja/) you can do something like this:
$ ninja -t commands | rc -c -
After this command rdm will index all the sources in your project.
If you’re using cmake you can do this:
cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=1 rc -J .
This will produce a compile_commands.json which, if used with rc -J, will index all your soures.
There are very likely similar things you can do with other build systems that we’re unfamiliar with.
- The other approach to getting your files indexed is to man-in-the-middle your compiler.
This can be done like this:
$ ln -s /path/to/rtags/bin/gcc-rtags-wrapper.sh /somewhere/that/is/in/your/path/before/usr/bin/gcc $ ln -s /path/to/rtags/bin/gcc-rtags-wrapper.sh /somewhere/that/is/in/your/path/before/usr/bin/c++ $ ln -s /path/to/rtags/bin/gcc-rtags-wrapper.sh /somewhere/that/is/in/your/path/before/usr/bin/cc $ ln -s /path/to/rtags/bin/gcc-rtags-wrapper.sh /somewhere/that/is/in/your/path/before/usr/bin/g++
E.g.
$ which -a gcc | xargs file /home/abakken/bin/gcc: symbolic link to `/home/abakken/dev/rtags/bin/gcc-rtags-wrapper.sh' /usr/bin/gcc: symbolic link to `gcc-4.7'
Now every time you compile a file with `which gcc` rc will get its grubby hands all over your command line and make sure RTags knows about it.
RTags will group source files into projects based on some heuristics.
Essentially it will look for certain files/dirs (like configure/CMakeLists.txt/scons.1/.git) etc to try to determine the likely project root for each source file. For generated source files that end up in the build dir we try to find the source root based on similar heuristics around config.status/CMakeCache.txt etc. Usually this works out reasonably well.
RTags’ only gives you information about current project when you ask for things by name. You can explicitly change the current project using:
$ rc -w foobar
We try to do it automatically for you by passing along information about the current buffer when we call rc from elisp so that rdm can update its current project on demand.
RTags keeps a cache of indexed data so you don’t have to reindex everything if you restart it.
The location of this data is by default ~/.rtags but can be overridden
by passing --data-dir /other/dir
to rdm or putting something like
this in your ~/.rdmrc:
$ cat ~/.rdmrc --data-dir=/other/dir
On Mac OS X, you can set rdm
can be run on demand, on your behalf,
by launchd
, and have it exit cleanly after a period of inactivity.
This isn’t quite plug-and-play, but should be worth the small amount
of effort.
- Create a file, e.g., in emacs, with the following contents:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.andersbakken.rtags.agent</string> <key>ProgramArguments</key> <array> <string>sh</string> <string>-c</string> <string>$RDM -v --launchd --inactivity-timeout 300 --log-file ~/Library/Logs/rtags.launchd.log</string> </array> <key>Sockets</key> <dict> <key>Listener</key> <dict> <key>SockPathName</key> <string>$HOME/.rdm</string> </dict> </dict> </dict> </plist>
- Replace
$HOME
with the absolute path to your home folder. Replace$RDM
with the path to your copy ofrdm
, and add any command line parameters you might usually use.(The
SockPathName
entry relates to the name of the domain socket thatrdm
uses. The settings above are for the default value; if your command line options direct it to use some other name, please modify it to suit. Unfortunatelylaunchd
’s configuration files are a bit naff, so you’ll have to repeat yourself.) - Save the result as
~/Library/LaunchAgents/com.andersbakken.rtags.agent.plist
. - Run the following command from the terminal:
launchctl load ~/Library/LaunchAgents/com.andersbakken.rtags.agent.plist
(This will happen automatically next time you log back in.)
- Try using rtags, and you should find
rdm
will spring into life!
rdm
will automatically quit after 5 minutes of inactivity (this is what the--inactivity-timeout 300
command line option is for), so it won’t stick around hogging memory. Butlaunchd
will still be watching its socket for activity, and will relaunch it if necessary.- You can watch
launchd
’s logging by tailing~/Library/Logs/rtags.launchd.log
.
Now that your files are indexed you can start using rtags. Normally you would do this from your editor but the way to extract this information from rdm is to use the command line tool rc.
E.g.
$ rdm & $ ninja -t commands | rc -c $ rc --follow-location Job.cpp:20:10 /home/abakken/dev/rtags/src/Job.h:10:18 List<RegExp> *mPathFiltersRegExp;
A location has the format of file:line:column.
For Emacs we maintain a set of elisp bindings that allows you to control rtags from your editor. There are projects that provide integration for other editors out there.
Vim: https://github.com/lyuts/vim-rtags and https://github.com/shaneharper/vim-rtags
Sublime Text: https://github.com/rampage644/sublime-rtags
rc has a vast number of commands and options and we intend to write a man page at some point. Most users will have limited interest in ever calling them manually and would rather just use the interactive elisp functions.
There are lots of interactive functions to call:
(rtags-find-symbol-at-point)
Follow symbol under cursor. For references this goes to the definition
(or declaration if no definition is known of the symbol. For
declarations it goes to the definition and vice versa. For definitions
of variables/parameters with constructors it goes to the constructor
in question. If you pass a prefix argument, limit to current source
file, if you pass a prefix argument and have narrowed the current
file, limit to the narrowed region. This prefix argument is the same
for: rtags-find-references-at-point
, rtags-find-symbol
,
rtags-find-references
(rtags-find-references-at-point)
Find all references to symbol under cursor. If symbol is itself a reference it will find all references to the referenced symbol
(rtags-find-symbol)
Prompt for name of symbol to go to. Imagine the following code:
namespace N { class C { public: int func(int); }; }; using namespace N; int C::func(int val) { return val * 2; }
int N::C::func(int) will now be accessible by the following names:
- func
- func(int)
- C::func(int)
- C::func
- N::C::func(int)
- N::C::func
(rtags-find-references)
Prompt for name of symbol to find references to. Same as above but find references to symbol rather than declarations and definitions.
(rtags-diagnostics)
Start an async process in a buffer to receive warnings/errors from clang whenever a file gets reindexed. It integrates with flymake to put highlighting on code with warnings and errors
(rtags-enable-standard-keybindings)
Sets up a ton of standard keybindings under C-x r (we try to avoid crashing with the register shortcuts). If you pass a mode to the function it will set it up on that mode, otherwise it will use c-mode-base-map).
(rtags-find-file)
Lets you jump to file by name (partial or full, concept kinda stolen from gtags.el) with completion in the project. This includes all files under what we determine to be the root of the project, not just source files.
(rtags-find-virtuals-at-point)
For virtual functions, show the various reimplementations of the function at point
(rtags-fixit)
Apply clang’s automatic fixits in current file. If you pass a prefix arg use ediff to apply it. See (http://clang.llvm.org/diagnostics.html) for more info.
(rtags-imenu)
Provices an ido-based imenu like interface to a subset of the symbols in the current file. Note that it does not actually use imenu infrastructure.
(rtags-location-stack-back) (rtags-location-stack-forward)
Whenever RTags jumps somewhere it pushes a location onto its stack. Jump back and forward in this stack
(rtags-next-match) (rtags-previous-match)
For functions that return more than one match, jump to the next/previous one.
(rtags-preprocess-file)
Preprocess current file according to known C(XX)Flags and show the result in a buffer. If region is active only display the preprocessed output for that region.
(rtags-print-symbol-info)
Print some info about symbol under cursor
(rtags-symbol-type)
Print the type of the symbol under cursor.
(rtags-print-dependencies)
Open a buffer showing files that depend on current file/files that current file depends on.
(rtags-print-enum-value-at-point)
Print integral value of enum value at point
(rtags-quit-rdm)
Shut down rdm
(rtags-rename-symbol)
Rename symbol under cursor. Make sure all files are saved and fully indexed before using.
(rtags-reparse-file)
Explicitly trigger a reparse of current file. Mostly for debugging. Unless we have bugs it should not be necessary.
(rtags-show-rtags-buffer)
Switch to *RTags*
buffer. This is the buffer where a number of
functions display their alternatives when they have more than one
match.
Variables:
rtags-path
Path to rc/rdm if they’re not in $PATH
.
rtags-jump-to-first-match
Similar to compilation-auto-jump-to-first-error
. Whether to jump to
the first match automatically when there’s more than one.
rtags-find-file-case-insensitive
Whether to match files case-insensitively
rtags-find-file-prefer-exact-match
Whether to exclude partial matches for file names when an exact
match is found. E.g.
/foobar.cpp
/bar.cpp
If rtags-find-file-prefer-exact-match
is t
a query for bar.cpp
would only return /bar.cpp
, otherwise both foobar.cpp
and bar.cpp
would be returned.
- Fall back to other taggers: You can do something like the following to fall back to e.g. gtags if RTags doesn’t have a certain project indexed:
(defun use-rtags (&optional useFileManager) (and (rtags-executable-find "rc") (cond ((not (gtags-get-rootpath)) t) ((and (not (eq major-mode 'c++-mode)) (not (eq major-mode 'c-mode))) (rtags-has-filemanager)) (useFileManager (rtags-has-filemanager)) (t (rtags-is-indexed))))) (defun tags-find-symbol-at-point (&optional prefix) (interactive "P") (if (and (not (rtags-find-symbol-at-point prefix)) rtags-last-request-not-indexed) (gtags-find-tag))) (defun tags-find-references-at-point (&optional prefix) (interactive "P") (if (and (not (rtags-find-references-at-point prefix)) rtags-last-request-not-indexed) (gtags-find-rtag))) (defun tags-find-symbol () (interactive) (call-interactively (if (use-rtags) 'rtags-find-symbol 'gtags-find-symbol))) (defun tags-find-references () (interactive) (call-interactively (if (use-rtags) 'rtags-find-references 'gtags-find-rtag))) (defun tags-find-file () (interactive) (call-interactively (if (use-rtags t) 'rtags-find-file 'gtags-find-file))) (defun tags-imenu () (interactive) (call-interactively (if (use-rtags t) 'rtags-imenu 'idomenu))) (define-key c-mode-base-map (kbd "M-.") (function tags-find-symbol-at-point)) (define-key c-mode-base-map (kbd "M-,") (function tags-find-references-at-point)) (define-key c-mode-base-map (kbd "M-;") (function tags-find-file)) (define-key c-mode-base-map (kbd "C-.") (function tags-find-symbol)) (define-key c-mode-base-map (kbd "C-,") (function tags-find-references)) (define-key c-mode-base-map (kbd "C-<") (function rtags-find-virtuals-at-point)) (define-key c-mode-base-map (kbd "M-i") (function tags-imenu)) (define-key global-map (kbd "M-.") (function tags-find-symbol-at-point)) (define-key global-map (kbd "M-,") (function tags-find-references-at-point)) (define-key global-map (kbd "M-;") (function tags-find-file)) (define-key global-map (kbd "C-.") (function tags-find-symbol)) (define-key global-map (kbd "C-,") (function tags-find-references)) (define-key global-map (kbd "C-<") (function rtags-find-virtuals-at-point)) (define-key global-map (kbd "M-i") (function tags-imenu))
Here are some videos demonstrating how to use RTags with Emacs though some of these may be outdated:
Set up symlinks and run the daemon
“IMenu” / virtuals / filenames
There are several other projects integrating RTags with other editors.
Sublime Text: https://github.com/rampage644/sublime-rtags
Vim: https://github.com/lyuts/vim-rtags https://github.com/shaneharper/vim-rtags https://github.com/mattn/vim-rtags
Note to those maintainers. If you need RTags to behave differently or add features to make these other integrations easier (like produce output in other formats etc), just drop us a note.
RTags is still under development and is not the most stable piece of software you’ll ever find. We’re constantly working to improve on it.