| 📦 Turbo Rust 713ea5c k33g 11h ago | 1 | # Architecture — explanation |
| 2 | |
| 3 | ## What is this about? |
| 4 | |
| 5 | Turbo Rust is a command, a profile and a scanner. Everything else — the editing widget, the windows, the menus, the dialogs, the themes, the terminal emulator, the file tree, the LSP client — is [turbo-core](https://rickub.com/turbo-editors/turbo-core), the library every Turbo editor is built on. |
| 6 | |
| 7 | This page is about that split: what is here, what is there, and why the line falls where it does. |
| 8 | |
| 9 | ## What is in this repository |
| 10 | |
| 11 | ``` |
| 12 | main.go flags, the terminal, and the wiring |
| 13 | internal/golang — no, that is the other editor |
| 14 | internal/rustlang the whole of what makes this Turbo Rust |
| 15 | rustlang.go the profile: name, menu, server, root marker |
| 16 | scan.go the scanner's dispatcher, comments, attributes |
| 17 | literals.go strings, raw strings, characters, lifetimes |
| 18 | words.go numbers, keywords, types, macros |
| 19 | templates.go three //go:embed declarations |
| 20 | *.toml.tmpl the three starter files a project gets, embedded |
| 21 | ``` |
| 22 | |
| 23 | About seven hundred lines, of which six hundred are the scanner. There is no `internal/app`, no `internal/ui`, no `internal/buffer` — those exist once, in the library, and every editor built on it uses them unchanged. |
| 24 | |
| 25 | ## What `main` does |
| 26 | |
| 27 | Six things, in this order: |
| 28 | |
| 29 | 1. Parses the flags. |
| 30 | 2. Calls `rustlang.Register()`, which teaches the library to colour `.rs` files. |
| 31 | 3. Builds `rustlang.Profile()` — the value that says this editor is Turbo Rust. |
| 32 | 4. Reads `.turbo-rust/settings.toml` from the working directory, if there is one. |
| 33 | 5. Opens the terminal and hands the screen, the theme name and the profile to `app.New`. |
| 34 | 6. Starts rust-analyzer in the crate root, and runs the event loop. |
| 35 | |
| 36 | That is the whole command. Every decision it makes — which theme wins, which files to open, whether to start a language server — is about *this run*, not about Rust. |
| 37 | |
| 38 | ## The profile is the seam |
| 39 | |
| 40 | ```go |
| 41 | profile.Profile{ |
| 42 | Name: "Turbo Rust", |
| 43 | Slug: "turbo-rust", |
| 44 | Language: "Rust", |
| 45 | ToolsMenu: "Rus~t~", |
| 46 | RootMarkers: []string{"Cargo.toml"}, |
| 47 | Server: profile.Server{Command: "rust-analyzer", …}, |
| 48 | Templates: profile.Templates{Settings: …, Snippets: …, Tools: …}, |
| 49 | } |
| 50 | ``` |
| 51 | |
| 52 | Everything that would otherwise be a hardcoded `"turbo-rust"`, `"rust-analyzer"` or `"Cargo.toml"` somewhere in eleven thousand lines is one field here. The library reads them; nothing in the library knows what any of them mean. |
| 53 | |
| 54 | `Slug` carries more than it looks. The binary is `turbo-rust`, the project directory is `.turbo-rust`, the user's own configuration lives in `~/.config/turbo-rust`, and the environment variables that override it are `TURBO_RUST_THEME_DIR` and `TURBO_RUST_SNIPPET_DIR` — all derived from that one word. |
| 55 | |
| 56 | ## Why the scanner is here and not in the library |
| 57 | |
| 58 | turbo-core colours eight languages itself: TOML, YAML, Markdown, JavaScript, HTML, XML, Dockerfiles and shell. Those are the ones every editor meets whatever it is for — a project's configuration is TOML or YAML, its documentation is Markdown, its scripts are shell, its image build a Dockerfile. |
| 59 | |
| 60 | Rust is not one of them, and neither is Go. The language that *defines* an editor is registered by that editor, which is why a `.go` file opens as plain text here and a `.rs` file opens as plain text in Turbo Go. |
| 61 | |
| 62 | That could have gone the other way. Putting both scanners in the library would let either editor colour either language, at no cost in dependencies — a Rust scanner is ordinary Go. It was rejected because it would mean the library grows a language every time somebody builds an editor, and because "what does this editor register?" would stop being the first question about a new one. |
| 63 | |
| 64 | ## Why the toolchain menu is `Rus~t~` and not `~C~argo` |
| 65 | |
| 66 | The hot key had to avoid `R` (Run) and `S` (Search), which left `T` — a hot key on the last letter of a word, which reads as an afterthought. Naming the menu **Cargo** would have taken `C`, which is free. |
| 67 | |
| 68 | It was still rejected. The menu holds whatever the project put in its tools file, and that is not always cargo: the first tools file anybody writes outgrows the language's own toolchain, because a project's commands include containers, databases and a `Makefile` target somebody added in 2019. A menu called Cargo holding `docker compose up` is a lie about what the menu is, in exactly the way the library's own documentation warns about. `Rust` is the language, and the language is what this editor is for. |
| 69 | |
| 70 | ## Why the tests drive the real editor |
| 71 | |
| 72 | `internal/rustlang/editor_test.go` builds a whole Turbo Rust on a simulated terminal — `app.New(screen, "turbo-classic", rustlang.Profile())` — opens a file and checks the colouring, the menu bar and the hot keys. It uses only the library's public API. |
| 73 | |
| 74 | That is deliberate. The library's own suite proves the library works; what these tests prove is that *this editor is assembled correctly* — that `Register` was called, that the profile reached the menu bar, that a `.rs` file comes out coloured. A bug where `main` forgot to register Rust would pass every test in turbo-core. |
| 75 | |
| 76 | The same file drives a **real rust-analyzer** end to end: it writes a crate, opens a file, starts the server, types text that exists only in the buffer, and asks for a completion. Text that is already on disk proves nothing — the server answers from disk for anything it has not been told is open. |
| 77 | |
| 78 | ## Rejected alternatives |
| 79 | |
| 80 | **Forking Turbo Go.** The obvious way to get a second editor, and the reason the library exists instead: two copies of eleven thousand lines drift within a month, and every fix has to be made twice by somebody who remembers there are two. |
| 81 | |
| 82 | **A plugin system.** Turbo Rust is a Go program that imports a library. There is no dynamic loading and no ABI. Adding one would mean freezing the API of every package in turbo-core rather than of the handful a profile touches. |
| 83 | |
| 84 | **A configuration file instead of a profile.** The profile could have been TOML read at start-up, which would make a new editor a file rather than a program. It would also make the scanner inexpressible, and a half-configurable editor — everything but the colouring — is worse than either whole answer. |
| 85 | |
| 86 | ## How it relates to the rest |
| 87 | |
| 88 | - What each of the library's packages does: [turbo-core's package reference](https://rickub.com/turbo-editors/turbo-core/blob/main/docs/en/reference/packages.md) |
| 89 | - How the colouring works here: [Colouring and completion](colouring-and-completion.md) |
| 90 | - Why the tools menu is data: [Rust tools](rust-tools.md) |
| 91 | - The decisions that outlived the refactoring: [Design decisions](design-decisions.md) |