Architecture — explanation
What is this about?
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, the library every Turbo editor is built on.
This page is about that split: what is here, what is there, and why the line falls where it does.
What is in this repository
main.go flags, the terminal, and the wiring
internal/golang — no, that is the other editor
internal/rustlang the whole of what makes this Turbo Rust
rustlang.go the profile: name, menu, server, root marker
scan.go the scanner's dispatcher, comments, attributes
literals.go strings, raw strings, characters, lifetimes
words.go numbers, keywords, types, macros
templates.go three //go:embed declarations
*.toml.tmpl the three starter files a project gets, embedded
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.
What main does
Six things, in this order:
- Parses the flags.
- Calls
rustlang.Register(), which teaches the library to colour.rsfiles. - Builds
rustlang.Profile()— the value that says this editor is Turbo Rust. - Reads
.turbo-rust/settings.tomlfrom the working directory, if there is one. - Opens the terminal and hands the screen, the theme name and the profile to
app.New. - Starts rust-analyzer in the crate root, and runs the event loop.
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.
The profile is the seam
profile.Profile{
Name: "Turbo Rust",
Slug: "turbo-rust",
Language: "Rust",
ToolsMenu: "Rus~t~",
RootMarkers: []string{"Cargo.toml"},
Server: profile.Server{Command: "rust-analyzer", …},
Templates: profile.Templates{Settings: …, Snippets: …, Tools: …},
}
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.
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.
Why the scanner is here and not in the library
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.
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.
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.
Why the toolchain menu is Rus~t~ and not ~C~argo
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.
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.
Why the tests drive the real editor
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.
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.
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.
Rejected alternatives
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.
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.
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.
How it relates to the rest
- What each of the library's packages does: turbo-core's package reference
- How the colouring works here: Colouring and completion
- Why the tools menu is data: Rust tools
- The decisions that outlived the refactoring: Design decisions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
|