| 📦 Turbo JS 91999d1 k33g 12h 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 decisions that shape it: why the menu is rebuilt every time it opens, why the editor grew real submenus for it, why insertion re-indents, and why the JavaScript bodies in the starter file are written the way they are. |
| 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 `for…of` loop, inside a callback — which is where you insert a `try / catch` — the result is text that no reader is happy with. JavaScript itself does not mind: unlike Python, its meaning does not depend on indentation, and Prettier would put the file right on the next `npx prettier --write`. But not every project runs Prettier, and a block whose braces sit at one depth and whose body sits at another is unreadable until something does. The first thing you do is re-indent it by hand, and 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 Prettier 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 the JavaScript bodies are indented two spaces |
| 46 | |
| 47 | The starter file the **Snippets** menu writes holds seven JavaScript snippets — `import`, `require`, `async function`, `try / catch`, `class`, `for of`, `test` — and one JSON snippet, `scripts`, and two decisions about their text are worth stating. |
| 48 | |
| 49 | **Two spaces**, because that is what Prettier writes by default, and Prettier is the formatter the starter tools file runs. Turbo Go's snippets use tabs because `gofmt` would rewrite anything else; the same argument runs the other way here — a tab in a snippet would be reformatted away the first time `npx prettier --write .` ran, which is a diff nobody asked for. A project that has configured Prettier differently, or does not use it, edits the file: it is TOML, and that is what it is for. |
| 50 | |
| 51 | **Whichever TOML string needs no escaping.** A one-liner with double quotes in it — `import { name } from "node:module";` — sits in a single-quoted literal string; the multi-line bodies sit in `"""…"""`, which is safe because none of them contains a backslash. The trap to know about when you add one: TOML resolves `\n` and `\"` in a basic string before the editor ever sees them, so a body containing `console.log("caught: \"" + e + "\"")` would arrive with real quotation marks in it and no longer parse. Such a body belongs in a literal string, `'''…'''`, where a backslash is just a backslash. |
| 52 | |
| 53 | ## Why two files, and why the project wins |
| 54 | |
| 55 | 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. |
| 56 | |
| 57 | 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. |
| 58 | |
| 59 | ## Why an unreadable file is loud |
| 60 | |
| 61 | 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. |
| 62 | |
| 63 | 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. |
| 64 | |
| 65 | ## How it relates to the rest |
| 66 | |
| 67 | - Every key and every rule: [Snippets reference](../reference/snippets.md) |
| 68 | - Setting them up: [How to insert snippets from a menu](../how-to/use-snippets.md) |
| 69 | - The other file in the same directory: [Project settings](project-settings.md) |
| 70 | - The language names `languages` uses: [Languages coloured](../reference/languages.md) |