| 🛟 Updated. 28d5985 k33g 20h ago | 1 | # How to write the starter files |
| 2 | |
| 3 | This guide shows how to write the three templates your editor offers to create in a project. It assumes you have an editor and a `profile.Templates` to fill in. |
| 4 | |
| 5 | Every editor built on turbo-core offers three menu items that write a file into the project's own directory: **Options ▸ Create project settings**, **Snippets ▸ Create snippets file**, and **Create tools file** at the bottom of the toolchain menu. Each has a partner that opens the file instead, and exactly one of the pair is ever available — you can create the file the project has not got, and open the one it has. What those files say is yours; that they are written safely is the library's. |
| 6 | |
| 7 | ## Steps |
| 8 | |
| 9 | ### 1. Write them as text, not as structs |
| 10 | |
| 11 | They are meant to be read and edited by a person. The comments in them say what each key is for, which is the whole reason the editor offers to create one at all rather than only to read one. Encoding them from a struct would be shorter and would produce a file with no comments in it. |
| 12 | |
| 13 | ### 2. Fill in the blanks the library provides |
| 14 | |
| 15 | Each template takes a fixed set of arguments, in a fixed order: |
| 16 | |
| 17 | | Template | Verb | Order | |
| 18 | | --- | --- | --- | |
| 19 | | `Settings` | `%q`, `%q` | the theme name, then the autosave delay | |
| 20 | | `Snippets` | `%s`, `%s` | the ungrouped group's name, then the user's own snippets path | |
| 21 | | `Tools` | — | none | |
| 22 | |
| 23 | Getting this wrong shows up as `%!s(MISSING)` in somebody's project, so it is worth a test: |
| 24 | |
| 25 | ```go |
| 26 | func TestTheSnippetsTemplateTakesExactlyTwoBlanks(t *testing.T) { |
| 27 | if got := strings.Count(snippetsTemplate, "%s"); got != 2 { |
| 28 | t.Errorf("the snippets template has %d %%s, want 2", got) |
| 29 | } |
| 30 | } |
| 31 | ``` |
| 32 | |
| 33 | ### 3. Name your own editor, not the one you copied from |
| 34 | |
| 35 | The likeliest mistake in a second editor is a leftover `turbo-go` in a file written into somebody's Rust project. It is invisible to every other test, so test for it directly: |
| 36 | |
| 37 | ```go |
| 38 | if strings.Contains(template, "turbo-go") { |
| 39 | t.Errorf("the %s template still says turbo-go", name) |
| 40 | } |
| 41 | ``` |
| 42 | |
| 43 | ### 4. Test what is in them, here |
| 44 | |
| 45 | That `Create` writes the profile's template at all is turbo-core's test. What the template *says* — that Build runs `cargo build`, that Run is the one tool in a terminal, that every tool names its output — is your editor's test, because it is about your language. |
| 46 | |
| 47 | ## Keep them in files, not in constants |
| 48 | |
| 49 | `profile.Templates` takes three strings, and a Go constant is the obvious way to supply one. Every editor keeps them in files beside the code instead, embedded at compile time: |
| 50 | |
| 51 | ```go |
| 52 | //go:embed settings.toml.tmpl |
| 53 | var settingsTemplate string |
| 54 | ``` |
| 55 | |
| 56 | Two reasons. A starter file is prose with a shape — comments, blank lines, alignment — and it reads and edits far better as a file than inside a raw string literal that cannot contain a backtick. And a file can be opened in the editor you are building, in the language it is a template for. |
| 57 | |
| 58 | **The `.tmpl` suffix is not decoration.** Each file is passed through `fmt.Sprintf` before it is written, and the settings template holds `theme = %q`, which is not valid TOML. Naming it `settings.toml` would be a claim it cannot meet: a TOML linter would reject it, and the editor would colour it as TOML and draw it as broken. |
| 59 | |
| 60 | Moving them out of the source costs one guard, and it is worth writing: the verbs are no longer next to the contract that documents them, so nothing notices one added or removed. Count them, and fill each template to check no `%!` marker comes out — Go writes `%!q(MISSING)` into the output rather than failing, so a wrong count produces a file that is written, opened, and wrong. |
| 61 | |
| 62 | ## Variants |
| 63 | |
| 64 | ### Your language indents with tabs |
| 65 | |
| 66 | Write `\t` as the two characters backslash-t inside the TOML string. TOML turns the escape into a tab when it reads the file, and a real tab in the template would look like whatever a reader's editor does with tabs. Then test that the tab survived: |
| 67 | |
| 68 | ```go |
| 69 | if !strings.Contains(snippet.Body, "\treturn err") { |
| 70 | t.Errorf("the body is %q; the tab did not survive", snippet.Body) |
| 71 | } |
| 72 | ``` |
| 73 | |
| 74 | ### Your language indents with spaces |
| 75 | |
| 76 | Nothing to escape. Test that no tab crept in instead — a tab in a Rust snippet lands in somebody's file and is reformatted out on the next `cargo fmt`, which is a diff nobody asked for. |
| 77 | |
| 78 | ### The user has no configuration directory |
| 79 | |
| 80 | `snippets.UserPath` returns `""`, and the comment in the template would read "Your own snippets go in:" followed by nothing. The library fills in "(no configuration directory on this system)" for you; leave room for a sentence rather than a path. |
| 81 | |
| 82 | ## See also |
| 83 | |
| 84 | - The templates field by field: [profile reference](../reference/profile.md) |
| 85 | - Why the files are created only on request: [what belongs here](../explanation/what-belongs-here.md) |