RTags: Echte C++-Codenavigation

RTags: Echte C++-Codenavigation

🌐 Read in English
📅 2026-10-07 ✍ Andreas Wittmann đŸ‘ïž ... linux rtags cpp cli

In meinem Ripgrep-Artikel habe ich geschrieben, dass rg nicht zuverlĂ€ssig zwischen der Definition einer Funktion, ihrer Deklaration und einem Aufruf davon unterscheiden kann — Regex ist eine Textsuche, kein C++-Parser. RTags ist das Werkzeug, zu dem ich greife, sobald genau diese Unterscheidung wichtig wird: Es indiziert C/C++-Quellcode mit libclang, demselben Parser, den Clang selbst zum Kompilieren verwendet. Die Antworten kommen also aus einem tatsĂ€chlichen VerstĂ€ndnis der Sprache, nicht aus einem Mustervergleich.

Was RTags eigentlich ist

RTags ist ein Client/Server-Paar. rdm ist ein Daemon, der das Projekt mit libclang parst und das Ergebnis — einen Index aus jedem Symbol, jeder Definition, Deklaration und Referenz — im Speicher und auf der Festplatte hĂ€lt. rc ist ein kleiner Kommandozeilen-Client, der ĂŒber einen lokalen Socket mit rdm spricht und Fragen stellt: Wo ist das definiert, wer ruft das auf, wofĂŒr steht dieses Symbol.

Weil rdm das echte Clang-Frontend verwendet, muss es genau wissen, wie jede Datei kompiliert wird — dieselben Include-Pfade, Defines und Standardversion, die der tatsĂ€chliche Build verwendet. Genau das liefert eine Compilation Database (compile_commands.json), und das ist der eine Einrichtungsschritt, den RTags wirklich braucht.

Eine Compilation Database erzeugen

Verwendet das Projekt CMake, ist das ein einziger Schalter:

cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build

CMake schreibt build/compile_commands.json, eine Liste jeder Übersetzungseinheit mit dem exakten Compiler-Aufruf, mit dem sie gebaut wurde.

FĂŒr ein reines Makefile-Projekt fĂ€ngt bear die tatsĂ€chlichen Compiler-Aufrufe wĂ€hrend eines echten Builds ab und erzeugt dieselbe Datei:

bear -- make

In beiden FĂ€llen entsteht eine JSON-Datei, auf die RTags verweisen kann — welches Build-System sie erzeugt hat, spielt keine Rolle.

Daemon starten und Projekt laden

rdm &
rc -J /pfad/zum/build

rc -J zeigt RTags auf das Verzeichnis mit compile_commands.json und weist rdm an, das Projekt zu indizieren. Bei einer großen Codebasis dauert das beim ersten Mal eine Weile; danach indiziert rdm nur noch Dateien neu, die sich tatsĂ€chlich geĂ€ndert haben.

Den Indizierungsfortschritt prĂŒfen:

rc --is-indexing

Die wichtigsten Abfragen

Alles Folgende setzt voraus, dass die Cursorposition als datei:zeile:spalte angegeben wird — in der Praxis fĂŒllt eine Editor-Integration das automatisch aus, aber es ist nĂŒtzlich, die Rohform mindestens einmal gesehen zu haben.

Zur Definition des Symbols unter dem Cursor springen:

rc -f src/game.cpp:42:10

Jede Referenz auf ein Symbol finden — jede Stelle, an der es verwendet wird, nicht nur, wo es deklariert ist:

rc -r src/game.cpp:42:10

Referenzen anhand des Namens finden, ganz ohne Cursorposition:

rc -R moveItemsOneTile

Alle ĂŒberschriebenen/ĂŒberschreibenden virtuellen Methoden eines Symbols auflisten:

rc -w src/game.cpp:42:10

Jede Datei finden, die einen bestimmten Symbolnamen enthĂ€lt — nĂŒtzlich als erster, breiterer Durchgang:

rc --find-symbols moveItemsOneTile

Eine Datei anhand eines (Teil-)Namens im gesamten Projekt finden:

rc -F game.cpp

Der Unterschied zu rg -l 'moveItemsOneTile' src/ aus dem Ripgrep-Artikel ist genau die Unterscheidung, die Regex nicht treffen kann: rc -r liefert nur tatsĂ€chliche Referenzen, die ĂŒber das Typsystem aufgelöst wurden — keinen Kommentar, der zufĂ€llig den Funktionsnamen erwĂ€hnt, kein String-Literal, keine unabhĂ€ngige Überladung mit demselben Namen in einer anderen Klasse.

Nach einer Änderung neu indizieren

RTags indiziert eine Datei automatisch neu, sobald es die Änderung bemerkt (es beobachtet die Dateien, die es kennt), aber nach einer grĂ¶ĂŸeren Änderung — etwa einer Header-Änderung, die viele Übersetzungseinheiten betrifft — kann es helfen, das explizit anzustoßen:

rc -e src/game.hpp

PrĂŒfen, ob eine bestimmte Datei im Index aktuell ist:

rc --dump-file-maps src/game.cpp

Editor-Integration

Am nĂŒtzlichsten ist RTags innerhalb eines Editors, wo datei:zeile:spalte implizit ist — die Cursorposition ist die Abfrage. Das Projekt liefert rtags.el fĂŒr Emacs mit, und rc ist einfach genug, um es auch aus anderen Editoren heraus zu scripten, die eine Shell aufrufen und das Ergebnis parsen können.

In Emacs, mit geladenem rtags.el, sind die alltÀglichen Bindings:

Letzteres ist besonders erwĂ€hnenswert: Ein Umbenennen ĂŒber rg/sed muss raten, welche textuellen Vorkommen die richtigen sind, und trifft bereitwillig ein unabhĂ€ngiges Symbol, das zufĂ€llig denselben Namen trĂ€gt. Ein Umbenennen ĂŒber RTags trifft nur das, was der Index als dasselbe Symbol aufgelöst hat.

Wo RTags an seine Grenzen stĂ¶ĂŸt

RTags braucht ein Projekt, das tatsĂ€chlich kompiliert — oder zumindest eines, bei dem libclang jede Übersetzungseinheit weit genug parsen kann, um Symbole aufzulösen. Eine halbfertige, gerade kaputte Datei degradiert zu Teilergebnissen, aber nur fĂŒr diese eine Datei; der Rest des Index bleibt unberĂŒhrt — trotzdem ein anderer Fehlermodus als bei ripgrep, dem es völlig egal ist, ob der Code ĂŒberhaupt kompiliert.

Außerdem ist die Einrichtung aufwendiger: eine Compilation Database, ein laufender Daemon und ein initialer Indizierungsdurchgang, gegenĂŒber rgs konfigurationsfreier, sofort startender Textsuche. FĂŒr ein schnelles „kommt dieser String irgendwo in diesem Repo vor" greife ich weiterhin eher zu rg — RTags rechtfertigt seinen Aufwand erst, sobald die Frage lautet „was bedeutet dieses Symbol hier eigentlich".

Wie ich die beiden tatsÀchlich kombiniere

Mein Arbeitsablauf aus dem Ripgrep-Artikel setzt sich hier direkt fort. Lizard meldet eine Funktion mit hoher zyklomatischer KomplexitĂ€t; ich finde sie zuerst mit rg, da das keinen Indizierungsschritt braucht und selbst bei einem Projekt, das ich noch nicht angerĂŒhrt habe, sofort da ist:

rg -n -F 'moveItemsOneTile' src/

Sobald ich tatsĂ€chlich in der Funktion bin und ihre Aufrufer, ihre Überschreibungen verstehen oder ein Umbenennen durchs ganze Projekt verfolgen muss, wechsle ich zu RTags in Emacs. rg sagt mir, wo etwas vorkommt; RTags sagt mir, was es ist und wo es tatsĂ€chlich verwendet wird — zwei verschiedene Fragen, die zufĂ€llig beide damit beginnen, diesen Namen im Code zu finden.

Fazit

Regex-basierte Suche und ein sprachbewusster Index lösen unterschiedliche Probleme, die auf der Kommandozeile Ă€hnlich aussehen. rg ist schnell, braucht keine Einrichtung und durchsucht Text — gut fĂŒr „ich weiß noch nicht genau, wonach ich suche". RTags braucht eine Compilation Database und einen laufenden Daemon, antwortet dafĂŒr mit echtem semantischen VerstĂ€ndnis von C/C++ — gut fĂŒr „ich weiß genau, was dieses Symbol ist, jetzt zeig mir jede Stelle, an der es wichtig ist". Zu wissen, welche Frage man gerade eigentlich stellt, ist der grĂ¶ĂŸte Teil der Entscheidung zwischen den beiden.