turbo-editors/turbo-rustpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-rust.git
git clone ssh://git@rickub.com/turbo-editors/turbo-rust.git

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

📦 Turbo Rust 713ea5c · on main · k33g · 8h ago
snippets.md · 62 lines · 6.0 KBmarkdown
Blame HistoryOpen raw

Snippets — explanation

What is this about?

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.

Why the menu is built at the moment it opens

Every other menu in the editor is decided once, in New(). This one cannot be, and there are two independent reasons.

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.

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.

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.

Why the editor grew submenus

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.

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.

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.

Two details of the submenu are worth naming because they were chosen rather than fallen into:

  • 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.
  • 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.

Why insertion re-indents

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.

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.

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.

Two smaller decisions inside that:

  • 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.
  • 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.

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.

Why two files, and why the project wins

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.

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.

Why an unreadable file is loud

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.

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.

How it relates to the rest

 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
# Snippets — explanation

## What is this about?

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.

## Why the menu is built at the moment it opens

Every other menu in the editor is decided once, in `New()`. This one cannot be, and there are two independent reasons.

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.

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.

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.

## Why the editor grew submenus

`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.

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.

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.

Two details of the submenu are worth naming because they were chosen rather than fallen into:

- **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.
- **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.

## Why insertion re-indents

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.

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.

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.

Two smaller decisions inside that:

- **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.
- **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.

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.

## Why two files, and why the project wins

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.

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.

## Why an unreadable file is loud

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.

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.

## How it relates to the rest

- Every key and every rule: [Snippets reference](../reference/snippets.md)
- Setting them up: [How to insert snippets from a menu](../how-to/use-snippets.md)
- The other file in the same directory: [Project settings](project-settings.md)
- The language names `languages` uses: [Languages coloured](../reference/languages.md)