# Rust tools — explanation ## What is this about? A **Rust** menu whose commands come from a TOML file, each run in a terminal window, and the open files re-read afterwards. This page is about why each of those three is the way it is. ## Why the output has three places to go, and a popup by default The first version put every command in a terminal window, and it was the wrong default for four of the five. A terminal is the right answer when the program is *interactive or long*: `cargo run` on something that reads standard input has to be answerable, and a build that turns out to take a minute has to be interruptible with `Ctrl-C`. Neither is true of `cargo clippy --all-targets`, which prints four lines and ends. Giving that a whole window — one you then have to close, on a desktop where windows overlap and are numbered — is more ceremony than the result deserves. A popup is the right answer for a command you run, read and dismiss. It is modal, which is a real cost and is named in the [how-to](../how-to/run-cargo-commands.md): a `go build` you did not expect to be slow holds the editor until it finishes or you press Escape. That cost was accepted on purpose, because the alternative — a dialog appearing unbidden three seconds later — swallows whatever was being typed at the moment it arrives. So the popup **opens immediately and fills in**. You see progress, nothing surprises you, and Escape both closes it and stops the command, which is the only way to interrupt something whose output is not in a terminal. An editing window is the right answer for output you are going to work through: a long `cargo test -- --nocapture`, a coverage report. It is an ordinary buffer, so `Ctrl-F` searches it and `Save as` keeps it. It is filled once the command has ended rather than as it goes, because a buffer growing under the cursor while you search it is the opposite of what that mode is for. None of those three is right for everything, which is why `output` is in the file rather than in the code. `Run` is the worked example: it is the one command in the starter file that says `terminal`, and the comment beside it says why. ## Why a terminal window is still there The editor already had one — a real pseudo-terminal with a VT emulator, built for the `F8` windows — so `output = "terminal"` costs one field on its options and buys colours, paging, `Ctrl-C`, keyboard input and scrollback for nothing, because they are the same mechanisms every other terminal uses. The window stays after the command exits, which is the point: the output is what you asked for, and a window that vanished with it would be useless. That needed a fix of its own. A terminal view consumed every key it was given and wrote it to the shell; once the shell had gone the write failed silently and the key was consumed anyway, so `Ctrl-W` could never close a finished window and the mouse was the only way out. A finished view now takes only the scrolling keys and lets the rest through to the editor. ## Why the exit code is always in the title `cargo build` succeeding prints nothing at all. A popup with an empty body and a neutral title is indistinguishable from one whose command has not started, and the reader is left guessing at the one thing they wanted to know. So the title carries the verdict — `— ok` or `— exit 1` — and an empty body says `(no output)` once the command has ended. While it is still running the body stays blank, because "(no output)" is a verdict and a running command has not reached one. ## Why the commands are in a file Five commands hardwired into the editor would have answered the request. They would also have been wrong within a week. `cargo clippy` is the default linter because rustup ships it and it is what a Rust project actually runs — but plenty want `-D warnings` on the end, or `cargo check` instead when clippy is too slow. `cargo run` assumes a single binary target. A project with a `Makefile` wants `make check`. A workspace wants `--workspace` on everything. None of that is knowable from here, and all of it is one line in a file. So the five are **defaults, not code**: they are the contents of the starter file that **Rust ▸ Create tools file** writes, and changing one is editing a file rather than rebuilding an editor. The file is read every time the menu opens, for the same reason the Snippets menu is: an edit should take effect at once, and the file is often open in the window behind the menu. Commands go to `sh -c` — `cmd.exe /S /C` on Windows — rather than being split into an argv here. The file is the user's own, so pipes, globs and `&&` are features rather than hazards, and one entry can be `cargo fmt && cargo clippy --all-targets && cargo test`. Splitting an argv would mean inventing quoting rules for a string somebody wrote by hand. ## Why there is no user-level tools file Snippets are read from two files — yours and the project's — because your snippets are your habits and should follow you between projects. Tools are not like that. They belong to a project's own toolchain: a global tools file would offer `cargo build` in a Go repository and `go test ./...` in a Rust one. The file is per-project, and that is the whole of the rule. ## Why a tool may name its own menu A menu called **Rust** holding `docker compose up` is a lie about what the menu is. The first tools file anybody writes outgrows Rust, because a project's commands are not all about the language it is written in: containers, databases, deploys, a `Makefile` target somebody added in 2019. Two shapes were considered. A **fixed second menu** called Tools — everything Rust in Rust, everything else in Tools — is one key in the format and no naming problem at all, but it only moves the lie: a Tools menu holding `docker compose up`, `psql`, and a deploy script is just as undifferentiated, and the moment there are ten entries nobody can find one. And a **second file**, `menus.toml`, keeps the tools file simple at the cost of two files that have to agree about which tools exist. So the menu is a **free-form name on the tool**, in the one file: `menu = "Docker"`. A name nothing else uses creates the menu; leaving the key out means Rust. There is no list of allowed names, because a list would be a list of somebody else's projects. Rust itself stays fixed on the bar rather than becoming just another name from the file. **Rust ▸ Create tools file** has to be reachable in a project that has no tools file at all — which is exactly the project that needs it — and a menu that only exists once the file exists cannot offer to write the file. ## Why the hot key is not the file's to choose The author of a tools file cannot know which letters are free. They can see `File`, `Edit`, `Search`, `Run`, `Options`, `Window`, `Snippets`, `Rust` and `Help` on the bar, but only by counting the underlines, and a project shared between people would then depend on nobody adding a menu that collides. Collisions here are **silent**, which is what makes them worth designing against. The bar answers the first menu whose hot key matches; a second menu claiming the same letter is not an error and draws normally — it simply never opens. That trap has already been sprung once in this editor: `Snippets` and `Search` both wanted `S`, `Snippets` was the unreachable one, and every test passed. The fix then was to move Snippets to `N` by hand. Letting a file name menus makes that a permanent hazard rather than a one-off mistake, so the assignment is done by the editor: the first letter of the name nothing else claims. Tildes written into the name are honoured **when the letter is free**, and quietly overridden when it is not. Refusing the file instead was the alternative, and it is worse: the clash depends on which menus exist, so a tools file that worked would break the day an editor release added a menu. Between a menu on a letter you did not ask for and a menu you cannot open, the first is the smaller loss. When every letter of a name is taken, the menu gets no hot key at all. `F10`, the arrow keys and the mouse still reach it, and the alternative — reaching for a letter that is not in the name — would put an underline under nothing. ## Why the bar is rebuilt from a stat `Menu.OnOpen` refills a menu's items just before it drops down, which is how the Rust and Snippets menus follow their files without a restart. It cannot help here: the *set* of menus is part of the bar, not part of any one menu, and adding `menu = "Docker"` to the file should put Docker on the bar. Reading and parsing the file on every turn of the event loop would do it, and would also be work done for nothing on every keystroke of a file nobody has edited. So the bar carries the size and modification time of the tools file it was built from, and one `stat` per turn decides whether to rebuild. Editing the file in the window in front of you, saving it, and watching the bar change is the case this is for. ## Why open files are re-read, and only some of them `Format` is the first item in the menu and it rewrites files on disk — including the one you are looking at. Without anything further, the editor would sit on a stale copy, and the next `F2` would write your unformatted version back over gofmt's work. That is not a rough edge; it is the feature quietly undoing itself. So when a command finishes, the editor re-reads every open file. The interesting part is which ones it refuses to touch. **A file with unsaved changes is left alone**, and the status bar says how many were skipped. Reloading it would throw away work the user has not saved, which no amount of convenience justifies. And the conflict is genuine: the formatter and the unsaved edit disagree about what the file should say, and the editor is not in a position to decide. Naming it and stopping is the honest outcome — the user can save and re-run, or keep editing and format later. Two smaller decisions inside that: - **The cursor stays where it was**, clamped into whatever the file now holds. A formatter moves lines about; putting the cursor back at the top would lose the reader's place for no reason. - **The undo history is discarded.** Undoing back past a reload would restore text the file no longer has, which is worse than not being able to undo at all. ## Why the reload happens on the event loop The command's exit is noticed on the goroutine reading the terminal, which may not touch a buffer or the desktop. So it sets a flag, and the reload runs at the top of the next turn of the event loop. This is the fourth thing in the library built that way — the language-server announcement, the terminal redraws, the autosave deadline, and now this. The rule they share is worth stating once more: **the wake-up may be lost, so the state must not be.** `PostEvent` drops what does not fit in its queue, so anything that depends on a message arriving is a bug waiting for a busy moment. A flag the loop checks for itself cannot go missing. ## Why a command can ask for a value, and why it asks in double braces `go mod init` needs a module path. `cargo new` needs a crate name. `go test -run` needs a pattern. None of those can live in the tools file, because the answer is different every time — and a tool that cannot ask is a tool that has to be edited before each use, which is not a tool. So a `{{label}}` in a command is a value the editor asks for first, in a box titled after the tool. **Single braces were the obvious spelling and are wrong.** `awk '{print $1}'` and `find . -exec rm {} +` are ordinary things to put in a tools file, and reading the first as a placeholder turns a working command into a box asking for "print $1". Double braces collide with almost nothing, and the one construct they do collide with — a nested block in awk — is rare enough to be written down rather than designed around. **The value is quoted by default**, because the alternative fails silently. A path with a space in it, substituted raw, becomes two arguments and the command reports something about a file that does not exist. Quoting makes that case work and makes the other case — "put these three flags on the end" — impossible, so `...` inside the braces asks for the value verbatim. Two behaviours, both documented, rather than one that is wrong half the time. **Nothing is remembered on disk.** The box starts from what was typed last time, for the session. Writing it into the project's own directory was considered and rejected: that directory holds what the project decided, and a filter somebody typed while chasing one test is not that. It would also be the first thing in there that changes without anybody editing it. **A file that cannot be parsed is refused when it is read**, not when the tool is chosen. An unclosed `{{` reaching the shell is a command failing with braces in it, which names neither the tool nor the file; refusing at load names both. That is the same rule an unknown `output` value already follows. **The dialog is refused when it will not fit.** A tool asking for more values than the terminal has rows would give a box whose OK button is below the bottom of the screen — answerable only by Escape, which cancels. Saying "this asks for twelve values and nine fit" is worse than nothing only if you would rather find out by trying. ## How it relates to the rest - Every key of the file and every rule: [Rust tools reference](../reference/rust-tools.md) - Using it: [How to run cargo commands from the editor](../how-to/run-cargo-commands.md) - The windows `output = "terminal"` uses, and why they are real terminals: [Terminal windows](terminal-windows.md) - The other menu built from a file: [Snippets](snippets.md)