| 📦 Turbo Rust 713ea5c k33g 11h ago | 1 | # Snippets — explanation |
| 2 | |
| 3 | ## What is this about? |
| 4 | |
| 5 | A **Snippets** menu whose contents come from a TOML file, and a chosen snippet dropped into the file you are editing. This page is about the three decisions that shape it: why the menu is rebuilt every time it opens, why the editor grew real submenus for it, and why insertion re-indents. |
| 6 | |
| 7 | ## Why the menu is built at the moment it opens |
| 8 | |
| 9 | Every other menu in the editor is decided once, in `New()`. This one cannot be, and there are two independent reasons. |
| 10 | |
| 11 | The first is the file. Snippets live in TOML, and the whole point of that is that you edit it — often in this editor, in the window the **Create snippets file** item just opened for you. A menu built at start-up would show the state of the file when the editor launched, and you would have to restart to see a snippet you had just written. That is the kind of friction that stops people using a feature at all. |
| 12 | |
| 13 | The second is the front window. The menu is filtered by what you are editing, so it changes when you press `F6`. There is no start-up moment at which the answer exists. |
| 14 | |
| 15 | So `ui.Menu` grew an `OnOpen` field: a function the bar calls immediately before dropping a menu down, letting its owner refill `Items` first. It is the same upward-communication mechanism as everything else in this codebase — a function field, not an interface — and it runs at exactly the moment the contents are about to be seen and no more often. |
| 16 | |
| 17 | ## Why the editor grew submenus |
| 18 | |
| 19 | `ui.MenuItem` had no nesting, and adding it was the largest single piece of this work: a second panel to place and draw, arrow keys that mean "further in" and "back out", the pointer opening a branch on hover and closing it on leaving, and a cascade that puts both panels away at once. |
| 20 | |
| 21 | The alternative was one flat panel with the groups as greyed-out captions between separators. It works, needs nothing new, and falls over on the case the feature is for: a project with thirty snippets gives a menu taller than the terminal. Grouping that only labels rather than folds does not solve the problem it appears to solve. |
| 22 | |
| 23 | It is deliberately **one level deep**. The format is groups containing snippets — exactly one level — and a general depth would mean replacing the bar's two indices with a path, in the widget every dialog and every menu test already depends on. That is speculative work on the most load-bearing part of the interface. |
| 24 | |
| 25 | Two details of the submenu are worth naming because they were chosen rather than fallen into: |
| 26 | |
| 27 | - **Right and left are asymmetric with Escape.** Right opens a branch, or moves to the next menu when the item has none, so it always means "further in" wherever you are. Left steps *out* of a submenu to its parent, while Escape puts the whole menu away — because cancel should mean cancel from anywhere. |
| 28 | - **The panel flips left, and is also capped to the screen.** A submenu that would run off the right edge is drawn on the other side of its parent instead. Flipping alone is not enough: a panel wider than the terminal cannot be made to fit by moving it, so the width is capped too and long labels are clipped by the painter. A frame with no right-hand edge looks broken in a way a truncated label does not. |
| 29 | |
| 30 | ## Why insertion re-indents |
| 31 | |
| 32 | A snippet is text, and the obvious implementation is to insert it. That is right for a one-liner and wrong for everything else, which is most of what people keep in snippets. |
| 33 | |
| 34 | Dropped in verbatim, a multi-line body restarts at column zero. Inserted inside a function, inside a loop, inside a `switch` — which is where you insert an `if err != nil` — the result is text that no formatter, no compiler and no reader is happy with, and the first thing you do is re-indent it by hand. A feature whose output needs fixing every time is not saving anyone anything. |
| 35 | |
| 36 | So the lines after the first get the leading whitespace of the line the cursor was on. That copies whatever the file already uses — tabs or spaces, however many — rather than imposing a choice, which matters in a project with a mixed history. |
| 37 | |
| 38 | Two smaller decisions inside that: |
| 39 | |
| 40 | - **A blank line in the body stays blank.** Padding it to the indent would put trailing whitespace in, which every formatter then strips — noise in the diff of the very next save. |
| 41 | - **It is one undo step.** A snippet is one action to the person who chose it, so `Ctrl-Z` should take all of it back. This falls out of doing the whole insertion in a single `ReplaceRange`, which is the rule the buffer already enforces for every other edit. |
| 42 | |
| 43 | Placeholders and tab stops — `${1:name}` and moving between them — were considered and left out. They are a second feature with their own state to keep across edits, and the thing being asked for was reusable text. |
| 44 | |
| 45 | ## Why two files, and why the project wins |
| 46 | |
| 47 | Your own snippets belong to you and should follow you between projects; a project's belong to the project and should arrive with a checkout. Neither is the whole answer, so both are read. |
| 48 | |
| 49 | Where a name clashes in the same group, the project's replaces yours. It is the more specific of the two statements, and it is the one a team agreed on — the same reason a `-theme` flag beats a project's setting while a project's setting beats the built-in default. |
| 50 | |
| 51 | ## Why an unreadable file is loud |
| 52 | |
| 53 | A typo in TOML could drop every snippet silently and leave a menu with nothing but **Create snippets file** — which looks exactly like a project that has no snippets, and sends you to create a file you already have. |
| 54 | |
| 55 | So the menu shows a greyed-out `Cannot read snippets` where the groups would be. It cannot be chosen, it is where you were looking, and the create item is still below it so there is a way forward either way. |
| 56 | |
| 57 | ## How it relates to the rest |
| 58 | |
| 59 | - Every key and every rule: [Snippets reference](../reference/snippets.md) |
| 60 | - Setting them up: [How to insert snippets from a menu](../how-to/use-snippets.md) |
| 61 | - The other file in the same directory: [Project settings](project-settings.md) |
| 62 | - The language names `languages` uses: [Languages coloured](../reference/languages.md) |