Monorepos
A monorepo is a single repository that holds many projects, often in several languages, side by side. Rune was built with monorepos in mind: search and navigation stream results as they are found and scale to millions of files, and code intelligence roots a separate language server at each project it discovers rather than trying to load the whole tree at once.
This guide explains how that works across every language, and the few habits that keep a large workspace fast.
How Rune discovers projects
When you open a workspace, Rune does not assume the folder you opened is a single project. Each language extension looks for that language's project markers, the manifest files that mark where a project begins:
| Language | Project markers |
|---|---|
| Go | go.mod, go.sum, go.work |
| Rust | Cargo.toml |
| Python | pyproject.toml, requirements.txt (or .lock, .in), .venv |
Discovery happens in two ways:
- At the workspace root. If the folder you opened carries a marker, its language server starts right away, rooted at the workspace.
- On demand, as you open files. When you open a source file, the extension walks up from that file to the nearest enclosing folder that carries a marker for its language, and starts a language server rooted at that folder. Each project gets its own server the first time you open one of its files, and that server is reused for every other file in the same project.
So in a monorepo with, say, a Go service, a Rust crate, and a Python worker, you end up with one language server per project, each scoped to its own subtree. Opening a Go file in one service never pulls the Rust crate or the Python worker into that server's view.
A source file with no marker in any folder between it and the workspace
root gets no code intelligence, because there is no project to root a
server at. This is deliberate: a stray script deep in the tree does not
silently spin up a language server over a whole subtree. Add the
appropriate manifest (go mod init, cargo init, or a
pyproject.toml) at the project root and reopen the file.
Search and navigation scale with the repo
Code intelligence is scoped per project, but search is not: the fuzzy file finder, content search, and structural search all range over the entire workspace, so you can jump anywhere in the monorepo from a single prompt.
They stay fast on large trees because they stream. Rune walks the
workspace with several workers in parallel and feeds results into the
fuzzy picker as they are found, so the list starts filling immediately
instead of waiting for a full scan to finish. The walker has been tuned
against a tree of several million files; you can keep typing to narrow
the results while the scan is still running, and cancel it at any time
with <ctrl-c>.
The walk skips what you would expect: directories ignored by
.gitignore (such as node_modules, target, .venv, and build
output) are not traversed, so vendored and generated files do not drown
out your own code or slow the scan.
For the commands and keybindings, see the Search guide. Its main entry points in a monorepo are:
searchfile(<meta-p>): fuzzy-find any file by name across every project.searchtext(<meta-\>): fuzzy-search file contents across the workspace.searchast(<alt-shift-f>and friends): find a function, type, or variable definition anywhere in the workspace using the structural (tree-sitter) index.
Very large workspaces
searchtext uses a built-in scanner by default. On an exceptionally
large workspace, you can point it at an external tool like
ripgrep or the silver searcher
instead, either per invocation or through the extension's command
config key:
searchtext rg --color never -n --no-heading --max-columns 500 ""
Keeping a big workspace fast
The single most effective habit: open your workspace at the root of the project you are working in, not at the top of the whole monorepo.
Rune scopes a language server to the project it discovers, but the file and text search still range over everything under the workspace root. If you open the entire monorepo, every search sees every project. If you open just the subproject you are editing (for example, the one Go module or the one Rust crate), search is naturally narrowed to it, and the language server has less surrounding context to consider.
A few more tips:
- Keep
.gitignoreaccurate. Because the walker skips ignored directories, adding generated output, dependency caches, and vendor folders to.gitignoredirectly shrinks what every search has to scan. This is the highest-leverage tuning for a large repo. - Let discovery do its job. You do not need to pre-open or configure each project. Opening one of its files is enough to bring its language server up, scoped to that project.
- Mind root-level markers. If the workspace root itself carries a
marker (a top-level
go.mod,Cargo.toml,pyproject.toml, or.venv), a language server is rooted at the whole workspace and takes the whole tree as its project. That is exactly what you want for a single-project repo, and usually not what you want when the root is just a container for many subprojects. In that case, prefer opening the subproject directly, or keep the shared markers out of the very top level.
What this looks like in practice
Say your repository is laid out like this:
my-monorepo/
services/
api/ go.mod (a Go service)
billing/ go.mod (another Go service)
crates/
engine/ Cargo.toml (a Rust crate)
workers/
ingest/ pyproject.toml (a Python project)
scripts/
deploy.py (a loose script, no project)
Opening my-monorepo as your workspace and then editing files gives
you:
- Opening
services/api/main.gostarts a Go language server rooted atservices/api. Opening a file inservices/billingstarts a second, independent Go server rooted there. - Opening
crates/engine/src/lib.rsstarts rust-analyzer rooted atcrates/engine. - Opening
workers/ingest/app.pyprepares that project's Python environment and starts a language server rooted atworkers/ingest. - Opening
scripts/deploy.pygets no language server, because there is no Python marker between it and the workspace root. If you want code intelligence there, givescripts/apyproject.toml. searchfile,searchtext, andsearchastsee all of it, streaming results from every project into one picker.
If you spend the day in services/api, opening the workspace at
services/api instead of my-monorepo keeps searches scoped to that
service while everything above still works exactly the same way.
Go's pre-modules GOPATH layout has no go.mod marker and is not
supported for code intelligence. Run go mod init <name> at each Go
project's root to convert it. See the Go guide for details.