Rust / Developer tools / C navigation
Ripscope, a new engine for a familiar workflow
Keep the editor habits. Rethink the engine. How I built fast C navigation around a familiar interface, current answers and inspectable evidence.
A code navigation tool earns its place in the space between a question and your next edit. Where is this function defined? Who calls it? Where does this value change? The answer should arrive while the question is still in your head.
I built Ripscope to make that loop faster in C projects. It is written in safe Rust, runs on x86-64 Linux and Windows, and works from the terminal or inside Vim, Neovim and Emacs. Its first public release is 0.1.0.
The interesting design problem was how much to change. I wanted a different engine behind a workflow people could already know.
Keep the habits, replace the engine
cscope established a useful vocabulary for exploring C. Ask for definitions, callers, callees, includes or assignments. Get source locations back. Let the editor take you there.
Ripscope speaks that command line and line-oriented query protocol. Existing cscope -Rbqk scripts and Vim's built-in cscope support can use it. It also ships editor integrations that install the matching executable and manage the project index. The first search builds that index in your cache directory. Later searches pick up saved edits automatically.
One more call. One more result.
greet on line 5 of main.c. After adding and saving another call, the same search finds lines 5 and 8. Playback starts only when you choose it. No rebuild command comes between saving and searching.The small loop in the demo is the product in miniature. Find callers of greet, add another call, save, search again. A navigation tool should make that sequence feel ordinary.
Keeping an interface does not mean keeping its implementation. Ripscope uses its own C scanner and a SQLite index. It does not read cscope databases or reproduce cscope's interactive terminal screen. The compatibility target is the search workflow, with deliberate differences documented where the answers or supported options differ.
Read the source you actually have
One useful property of cscope-style navigation is that you can start exploring before you can build the project. That matters when the code is unfamiliar, the toolchain is missing, or you are looking through several platform variants.
Ripscope needs no compiler, preprocessor or compilation database. It reads C as written, including conditional branches that a particular build would leave inactive. It records the names, declarations, calls and contexts it recognises.
That choice shapes what an answer means. An assignment search can find a written increment or indexed store. It does not prove that the statement executes. A caller search follows recognised source constructs. It does not resolve arbitrary macros or pointer aliases.
I made those boundaries part of the reading model. Uncertainty in one construct should not throw away every useful fact in a file. At the same time, an inspectable answer needs an honest account of what the scanner can recognise.
Make saving and searching one loop
A fast search is less useful when remembering to rebuild the index becomes part of every edit.
Ripscope reanalyses changed files instead of rebuilding everything. An editor session watches the sources used by the index and checks for relevant changes before each query. When needed, it refreshes first. Keeping that session open also avoids starting a new process and opening the index for every search.
The source changes. The answer stays whole.
- 01Save a fileThe source watch signals a relevant change.
- 02Refresh what changedReanalyse changed files. Commit one transaction.
- 03Ask the next questionRead a complete committed index revision.
The useful unit of work is a complete revision. An update commits in one SQLite transaction, so a stored query reads a complete index rather than a partly rewritten one. If an update fails, the previous revision remains available.
There is an intentional distinction here. Symbol queries use the committed index. Text searches read the live source files. Ordinary freshness checks use file metadata, with hashes for recent edits. --verify reads every file when you need a thorough check. The storage guide explains the lifecycle, including the Windows case where an edit preserves both size and modification time.
The visible result is simple: save, search, keep reading. Getting there required treating freshness, consistency and startup cost as parts of the same experience.
Check agreement and correctness separately
Building against a familiar tool creates an awkward question. When your answer differs, did you introduce a bug or fix one?
I kept two kinds of evidence. A pinned, checksum-verified cscope executable supplies captured reference behavior. Separate expectations describe what specific written C should mean. A deliberate correction keeps the reference observation beside the intended result.
A small example makes the distinction tangible. These three lines come directly from the named-mutation fixture.
counter++;
counter--;
++counter;
The captured cscope reading records each occurrence without the assignment marker. The intended reading marks all three as assignments, so Ripscope can include these written mutations in an assignment search. This is a bounded correction with a source example you can inspect.
Agreement is one check. Reading is another.
Written C sourceReference behavior
What does the pinned cscope executable return?
Captured outputIntended reading
What construct is actually written in this source?
Independent source expectationsCompare both with Ripscope's answer.
Those are different questions. “Does it agree with cscope?” checks compatibility. “Does it identify the construct written here?” checks the reading. Passing one cannot stand in for passing the other.
This made development more concrete. A proposed scanner change needed a small source example, an expected reading and a reason for changing the answer. Real executable tests then exercised queries, sessions, warnings, exact-byte JSON output and recovery. Separate checks drove the editors in isolated projects.
The public evidence guide keeps unresolved readings visible too. A finite collection of examples can establish specific behavior. It cannot justify a universal C accuracy percentage. That distinction makes a wrong answer easier to investigate, because the claim and the source behind it have a place to live.
Measure the search people wait for
There is no single speed number for a navigation tool. A new process, a first query and a later query inside an open editor session do different work. Index creation is another operation again.
The 0.1.0 benchmark report separates those cases and compares both plain cscope and its -q inverted index. It shows the two repetitions independently, together with the answer comparisons. A timing win needs an equivalent or source-confirmed better answer.
One example gives a feel for the result. On the measured Linux kernel selection, later text searches run about six times as fast as cscope -q on the Intel Core i7-8550U Linux host. That is a result for that workload and session, with startup excluded. The report also retains slower and inconclusive cases, including small-project lookups where saving a fraction of a millisecond is harder than it sounds.
What I find satisfying is the connection between the architecture and the experience. Reusing a session removes repeated setup. Reanalysing changed files keeps updates local. Recording useful answer rows avoids work at query time. Each decision has a job in the save-and-search loop.
Try it in the code you are reading
Ripscope is for exploring written C without first assembling a working build. You can ask all nine cscope searches, navigate from your editor, or use exact-byte JSON results in other tooling.
Start with the installation guide, then search a project you know. Try a definition, a caller and an assignment. Save a change and ask again.
Find a caller, save another, find it again. The engine should keep up with that thought.