# turbo-rust — project summary *A snapshot of the present. No history here — that is `history.md`.* ## What this is A Turbo C-style editor for Rust, written in Go: a full-screen terminal IDE with a menu bar, movable overlapping windows, modal dialogs, mouse support, Rust syntax colouring, loadable TOML themes, completion from `rust-analyzer`, terminal windows running a real shell, per-project settings, a project tree, snippets, and the cargo toolchain a menu away. It is a thin editor on top of [turbo-core](https://rickub.com/turbo-editors/turbo-core), the library every Turbo editor shares. What is in this repository is `main.go` and `internal/rustlang` — about seven hundred lines, six hundred of which are the scanner. Module path `rickub.com/turbo-editors/turbo-rust`. Go 1.26.5. Remote: `ssh://git@rickub.com/turbo-editors/turbo-rust.git`. Created 2026-09-01, the same day turbo-core was extracted, and the reason it was extracted. ## Architecture Two packages here; everything else is the library. ``` main → {turbo-core/app, turbo-core/profile, turbo-core/settings, turbo-core/theme, turbo-core/version, internal/rustlang, tcell} internal/rustlang → {turbo-core/profile, turbo-core/syntax} ``` | File | What it holds | | --- | --- | | `main.go` | Flags, the terminal, and the wiring: register Rust, build the profile, read the project's settings, hand them to `app.New`, start rust-analyzer in the crate root, run the loop | | `internal/rustlang/rustlang.go` | The profile: name, slug, `Rus~t~` menu, `Cargo.toml` root marker, rust-analyzer with the two directories it is looked for in | | `internal/rustlang/scan.go` | The scanner's dispatcher, comments and attributes | | `internal/rustlang/literals.go` | Strings, raw strings, byte literals, characters, lifetimes | | `internal/rustlang/words.go` | Numbers, keywords, types, macros | | `internal/rustlang/templates.go` | The three starter files a project gets | `docs/diagrams/packages.drawio` is generated from `go list` and verified against it edge for edge. ## Decisions in force - **The toolchain menu is `Rus~t~`, on Alt-T, not `~C~argo`.** `R` is Run's and `S` is Search's, so the hot key lands on the last letter of the word, which reads as an afterthought — and `C` was free. Naming it Cargo was still rejected: the menu holds whatever the project put in its tools file, and the first tools file anybody writes outgrows the language's own toolchain. A menu called Cargo holding `docker compose up` is a lie about what the menu is. - **Rust is scanned by hand, in six hundred lines.** Go has a lexer in its standard library and Turbo Go uses it; `rustc` is not a Go library and rust-analyzer's parser is a Rust crate, so the choice was a hand-written scanner or shelling out on every keystroke. The scanner is written against turbo-core's `LineScanner`, in the same style as the five the library ships. - **Three constructs cross a line break and are carried exactly.** A block comment carries a **depth**, not a flag, because Rust nests them and a flag ends `/* a /* b */ c */` at the first `*/`. A raw string carries its **hash count**, because it ends at a quote followed by exactly that many. An ordinary string can carry too, because Rust allows a real newline inside `"…"`. - **An attribute is not carried.** `#[…]` that runs past its line is coloured to the end and dropped, because an unclosed one is nearly always half-typed, and carrying it would paint the rest of the file. - **A lifetime is told from a character literal by looking for the closing quote** where a character would have to put it — one rune along, or further for an escape. `'a` is a lifetime, `'a'` a character, `'static` a lifetime, `'\u{1F600}'` a character. Getting it wrong strings the rest of the line, so it has tests of its own. A lifetime is coloured as a **type**, because it is a generic parameter declared and used where one is. - **A leading capital means a type**, leaning on Rust's naming convention: type, trait and enum variant are all `UpperCamelCase` and nothing else is. It is visibly a heuristic in one place — a `SCREAMING_SNAKE_CASE` constant is coloured as a type — and the reference says so rather than leaving somebody to find out. A second rule for it was rejected: it would mis-colour a type whose name is an acronym, and trading one wrong answer for another is not progress. - **A macro takes its `!`**, and `a != b` is told from it by the `=`. **A number takes its suffix**: `42u8` is one literal, and colouring the `u8` as a type would split a thing that is not two things. **`::` and `:` are punctuation** and **`..`/`..=` are operators**, both against the rune classes turbo-core's helpers would otherwise give them. - **`None`, `Some`, `Ok` and `Err` are coloured as constants** although they are Option's and Result's rather than the language's. A reader meets them before any other variant and reads them as they read `true`. - **The Code menu is turbo-core's, and so are its eight questions.** Describe symbol and Go to definition moved into it from Run and Search; their keys did not change. This repository documents the menu and owns none of it — as with everything else the two editors share, a change to it is a `/methodical-dev` cycle in turbo-core. - **The settings file a project creates turns autosave on.** A project that has gone to the trouble of having one has said what it wants, and the file is the visible, editable place to say otherwise. `settings.Default()` — what applies with no settings file at all — stays **off**: the editor must not write to disk in a directory somebody merely started it in. Two different statements, set in two different places on purpose. - **A workspace, not a `replace`, is how to build against an unreleased turbo-core.** `go work init . ../turbo-core` changes no tracked file, so there is nothing to forget before committing; `go.work` is gitignored in all three repositories. The commented-out `replace` at the bottom of `go.mod` still works and is documented as the older way, with its hazard named. - **The build runs the binary it just built and checks it names the right version.** `scripts/check-version.sh` is called by `make build`, by `scripts/install.sh` before the install, and by `03-build-releases.sh`. A linker stamp is a string and a wrong one is not an error — `-X` naming a symbol that does not exist links happily and stamps nothing — so nothing but running the binary catches it. The comparison is an **equality**: `0.2.0` is a substring of `10.2.0`. - **rust-analyzer is looked for in `CARGO_HOME/bin` and `RUSTUP_HOME/bin` after PATH**, and the installer *runs* it rather than stat-ing it. rustup installs a shim called `rust-analyzer` whether or not the component is there, and the shim fails only when run — so "the file is there" is not the question worth asking. - **Snippets indent with four spaces, not tabs**, because that is what rustfmt does; a tab in a Rust snippet lands in somebody's file and disappears on the next `cargo fmt`. There is a test for it, and another that no template still says `turbo-go`. - **The tests drive the real editor.** `internal/rustlang/editor_test.go` builds a whole Turbo Rust on a simulated terminal through turbo-core's public API. The library's suite proves the library works; these prove *this editor is assembled correctly* — that Register was called, that the profile reached the menu bar, that a `.rs` file comes out coloured and a `.go` file does not. Everything else about the editor's behaviour — the event loop, the menus, the dialogs, the terminal emulator, the theme rules — is turbo-core's, and its `.memory/summary.md` is where those decisions are recorded. - **A tool's command can ask for values.** A `{{label}}` in it opens a box before the command runs; the value is shell-quoted unless the label ends in `...`. The feature is turbo-core's — see its summary — and what belongs to this editor is the starter file's comments, which teach the syntax without adding a sixth tool. - **Agent windows are turbo-core's, and what belongs here is the starter file.** `acp.toml.tmpl` is the fourth embedded template, and `profile.Templates.Agents` is the whole of Turbo Rust's contribution to the feature. The example agent is `docker agent serve acp .turbo-rust/agent.yaml`; the only other thing about this editor in it is the sentence saying a ```rust fence is coloured by the scanner this editor colours its own files with. Every other editor got agent windows the same way — one file, one line. The reasoning, and why it could not have been built here, is in `docs/*/explanation/agent-windows.md`. - **The starter agents file teaches the window's keyboard as well as the format.** `Enter`, `Alt-Enter`, `Tab`, `Esc`, `Ctrl-W` and the copying keys are all in its comments, because a file the editor hands you is the one document a user is guaranteed to see. ## Build, test, run ```bash make install # build + install onto PATH (scripts/install.sh) make build # → bin/turbo-rust make test # the whole suite; the single documented command make check # fmt + vet + test — what a commit should pass make run FILE=src/main.rs go test -short ./... # skips the test that starts a real rust-analyzer ``` Quality gate, separate from the tests: ```bash python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace . ``` ## State as of 2026-09-01 - **Complete and green.** Whole suite passing; quality gate PASS at 0 errors, 0 warnings, 0 smells, complexity 98. - **Verified against a real rust-analyzer**: the editor writes a crate, opens a file, starts the server, types text that exists only in the buffer, and gets `String::len` back. Text already on disk would prove nothing. - **Verified in a real pty**: the menu bar reads `File Edit Search Run Options Window Snippets Rust Help`; a nested block comment, a raw string with quotes inside it, `println!` with its `!`, and `3u8` with its suffix each come out as one correctly-coloured run on the wire. - **`docs/` is 33 pages × EN + FR.** `reference/languages.md` documents the scanner's boundaries, and `internal/rustlang/reference_test.go` holds the code to every row of its Rust table. - **Released as v0.1.0** at `2d5dbec`, which is exactly HEAD — its first release ever, with a release page on Codeberg. - **Depends on `turbo-core v0.2.0`**, with no active `replace`. On `main` that is v0.1.0 and builds from the module proxy. On `feature/more-syntaxes` the `require` names **v0.2.0, which is not published yet**: that branch does not build until turbo-core is tagged and released, and `go.sum` has no entry for it. The old replace block is still there, commented out, as the documented way to develop across the three repositories. ## Not yet established - **Agent windows have never been opened in this editor.** The feature is turbo-core's and was driven end to end from turbo-go against a real `docker agent` and a real llama.cpp; what is here is the starter file, covered by tests that create it, load it back and check it names this editor's own language. Nobody has run `turbo-rust`, pressed `Alt-A` and talked to an agent from it. - **Never used by a person for a working session.** Everything is verified by tests and by scripted pty runs; nobody has spent an hour editing Rust in it. - **No release has been cut.** The numbered scripts `01`…`04` came from turbo-go and are adapted, but `02` and `04` have never been run here — they publish, and publishing is not this session's to do. - **The scanner has met one file of Rust in anger.** It is covered by 30-odd tests and a broad sweep, but it has not been pointed at a large real crate. - **Windows and macOS are untested**, inherited from turbo-core. - **`macro_rules!` bodies are coloured as ordinary Rust**, which is usually right and sometimes not. Documented, not fixed. ## State as of 2026-09-19 — moved to Rickub, released by a workflow - **Module path `rickub.com/turbo-editors/turbo-rust`**, depending on `rickub.com/turbo-editors/turbo-core v1.0.0` — the first turbo-core version published under that path (`v0.9.0` on the proxy still declares the Codeberg path and cannot be required as `rickub.com/…`). Every import, the Makefile's `VERSION_PKG`, `scripts/install.sh`, the README and the docs say `rickub.com`. `GOWORK=off make check` green. The repository on this side is a fresh `git init` with `origin` at `ssh://git@rickub.com/turbo-editors/turbo-rust.git` and **no commit yet**; `01-release.tag.sh` makes the first one. - **Releases are one script and one workflow**, modelled on turbo-core's and identical to turbo-go's. `01-release.tag.sh` runs `make check` under `TURBO_RUST_RELEASING=1`, refuses a tag taken locally or on origin (bump, never move), refuses a `replace` in `go.mod`, commits, pushes the current branch, then tags and pushes the tag. That push starts `.github/workflows/release.yml`: `go test` with `TURBO_RUST_RELEASING=1`, `./02-build-releases.sh "${GITHUB_REF_NAME}"`, release notes from the tag message, a run artifact, then `softprops/action-gh-release@v2` attaching `turbo-rust-*`, `SHA256SUMS` and `README.md` with the job's own `GITHUB_TOKEN` — the only credential Rickub's release API accepts. **`02-release.publish.sh` and `04-release.upload-binaries.sh` are gone**; the build script is now `02-build-releases.sh`, takes the tag as `$1` (CI has no `release.env`), validates it, refuses a `replace`, and starts from an empty `release/${TAG}/`. `release.env` holds only `TAG` and `ABOUT`; `turbo-rust.token.env` is read by nothing. - **`release_test.go`** runs `01` for real against a throwaway bare remote (publishes, then refuses the same tag), runs `02` alone to see it refuse `v0.o.0`, and asserts the workflow's trigger, `contents: write`, `./02-build-releases.sh`, `fail_on_unmatched_files`, docs linked at the tag, no `secrets.`, and `TURBO_RUST_RELEASING`. The copy of the module for the clone leaves out `.git`, `bin`, `release`, `kits`, `demo`, `demos`, `*.env` and `go.work*`; children run with `GOWORK=off`.