| 📦 Turbo Rust 713ea5c k33g 12h ago | 1 | # turbo-rust — project summary |
| 2 | |
| 3 | *A snapshot of the present. No history here — that is `history.md`.* |
| 4 | |
| 5 | ## What this is |
| 6 | |
| 7 | 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. |
| 8 | |
| 9 | 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. |
| 10 | |
| 11 | Module path `rickub.com/turbo-editors/turbo-rust`. Go 1.26.5. Remote: `ssh://git@rickub.com/turbo-editors/turbo-rust.git`. |
| 12 | |
| 13 | Created 2026-09-01, the same day turbo-core was extracted, and the reason it was extracted. |
| 14 | |
| 15 | ## Architecture |
| 16 | |
| 17 | Two packages here; everything else is the library. |
| 18 | |
| 19 | ``` |
| 20 | main → {turbo-core/app, turbo-core/profile, turbo-core/settings, turbo-core/theme, |
| 21 | turbo-core/version, internal/rustlang, tcell} |
| 22 | internal/rustlang → {turbo-core/profile, turbo-core/syntax} |
| 23 | ``` |
| 24 | |
| 25 | | File | What it holds | |
| 26 | | --- | --- | |
| 27 | | `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 | |
| 28 | | `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 | |
| 29 | | `internal/rustlang/scan.go` | The scanner's dispatcher, comments and attributes | |
| 30 | | `internal/rustlang/literals.go` | Strings, raw strings, byte literals, characters, lifetimes | |
| 31 | | `internal/rustlang/words.go` | Numbers, keywords, types, macros | |
| 32 | | `internal/rustlang/templates.go` | The three starter files a project gets | |
| 33 | |
| 34 | `docs/diagrams/packages.drawio` is generated from `go list` and verified against it edge for edge. |
| 35 | |
| 36 | ## Decisions in force |
| 37 | |
| 38 | - **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. |
| 39 | - **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. |
| 40 | - **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 `"…"`. |
| 41 | - **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. |
| 42 | - **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. |
| 43 | - **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. |
| 44 | - **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. |
| 45 | - **`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`. |
| 46 | - **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. |
| 47 | - **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. |
| 48 | - **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. |
| 49 | - **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`. |
| 50 | - **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. |
| 51 | - **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`. |
| 52 | - **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. |
| 53 | |
| 54 | 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. |
| 55 | |
| 56 | - **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. |
| 57 | - **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`. |
| 58 | - **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. |
| 59 | |
| 60 | ## Build, test, run |
| 61 | |
| 62 | ```bash |
| 63 | make install # build + install onto PATH (scripts/install.sh) |
| 64 | make build # → bin/turbo-rust |
| 65 | make test # the whole suite; the single documented command |
| 66 | make check # fmt + vet + test — what a commit should pass |
| 67 | make run FILE=src/main.rs |
| 68 | go test -short ./... # skips the test that starts a real rust-analyzer |
| 69 | ``` |
| 70 | |
| 71 | Quality gate, separate from the tests: |
| 72 | |
| 73 | ```bash |
| 74 | python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace . |
| 75 | ``` |
| 76 | |
| 77 | ## State as of 2026-09-01 |
| 78 | |
| 79 | - **Complete and green.** Whole suite passing; quality gate PASS at 0 errors, 0 warnings, 0 smells, complexity 98. |
| 80 | - **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. |
| 81 | - **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. |
| 82 | - **`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. |
| 83 | - **Released as v0.1.0** at `2d5dbec`, which is exactly HEAD — its first release ever, with a release page on Codeberg. |
| 84 | - **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. |
| 85 | |
| 86 | ## Not yet established |
| 87 | |
| 88 | - **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. |
| 89 | |
| 90 | |
| 91 | - **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. |
| 92 | - **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. |
| 93 | - **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. |
| 94 | - **Windows and macOS are untested**, inherited from turbo-core. |
| 95 | - **`macro_rules!` bodies are coloured as ordinary Rust**, which is usually right and sometimes not. Documented, not fixed. |
| 96 | |
| 97 | ## State as of 2026-09-19 — moved to Rickub, released by a workflow |
| 98 | |
| 99 | - **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. |
| 100 | - **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. |
| 101 | - **`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`. |