turbo-editors/turbo-jspublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-js.git
git clone ssh://git@rickub.com/turbo-editors/turbo-js.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

snippets.md · 70 lines · 7.7 KBmarkdown Blame HistoryRaw
📦 Turbo JS 91999d1 k33g 12h ago1# Snippets — explanation
2
3## What is this about?
4
5A **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
9Every other menu in the editor is decided once, in `New()`. This one cannot be, and there are two independent reasons.
10
11The 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
13The 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
15So `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
21The 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
23It 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
25Two 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
32A 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
34Dropped 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
36So 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
38Two 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
43Placeholders 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
47The 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
55Your 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
57Where 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
61A 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
63So 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)