turbo-editors/turbo-corepublic Fork 0
v1.0.1
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

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

README.md · 77 lines · 6.9 KBmarkdown Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1# theme
2
3Turns TOML colour files into the `tcell` styles the editor draws with.
4
5A theme is a flat map from a **dotted style key**`syntax.keyword`, `menu.selected`, `editor.text` — to a foreground colour, a background colour and a few attributes.
6
7## Where themes come from
8
91. **The user's directory**, passed in as `userDir` — the editor works it out from its profile, which reads `$TURBO_GO_THEME_DIR` (or `$TURBO_RUST_THEME_DIR`) and otherwise falls back to `<slug>/themes` under the platform config directory. Pass `""` to offer the embedded themes alone.
102. **Embedded in the binary**: `turbo-classic`, `turbo-dark`, `borland-light`, `cappuccino`, `catppuccin-frappe`, `catppuccin-latte`, `cobalt`, `darcula`, `intellij-light`, `monochrome-dark`, `monochrome-light`.
113. **A name a theme used to answer to**, listed in `retiredNames`: `monochrome` loads `monochrome-dark`, which is what that theme shipped as before `monochrome-light` joined it.
12
13A user file wins over an embedded theme of the same name, so a shipped theme can be overridden without being replaced. A retired name is resolved *after* the user's directory, so somebody's own `monochrome.toml` still wins there too, and it is not listed by `Available`, so each theme is offered once under the name it has now. The reason the mechanism exists at all is that a theme's name is what somebody wrote in a settings file their whole team shares.
14
15## File format
16
17```toml
18name = "My Theme"
19description = "One line, shown in the theme picker."
20inherits = "turbo-classic" # optional: fill in every key you do not set
21
22[colors]
23default = { fg = "silver", bg = "navy" }
24"syntax.keyword" = { fg = "white", bold = true }
25"syntax.string" = { fg = "#a8d76f" }
26"editor.text" = { fg = "default", bg = "default" } # leave it to the terminal
27```
28
29Colours are ANSI names understood by tcell (`navy`, `aqua`, `silver`, `fuchsia`, …), `#rrggbb` literals, or `default` / `-` / `""` for the terminal's own colour. Attributes: `bold`, `underline`, `italic`, `reverse`, `dim`, `blink` — each a boolean, only ever switched **on**.
30
31An unknown colour name is an **error at load time**, not a silent fallback, so a typo is pointed at instead of quietly repainting half the screen.
32
33## Two kinds of inheritance
34
35**Between files**, with `inherits`: the parent's resolved styles are the starting point, and the child overrides what it names. Chains are capped at 16 hops, so a loop is reported rather than hanging.
36
37**Between keys**, along the dots: `Style("syntax.keyword")` tries `syntax.keyword`, then `syntax`, then `default`. This happens twice — once at parse time, so an entry setting only `fg` inherits its `bg` from its parent key, and once at lookup time, so a theme that never mentions `syntax.keyword` still colours keywords. Shallower keys are resolved first, whatever order they appear in the file.
38
39## Public API
40
41| Function | What it does |
42| --- | --- |
43| `Load(name string) (*Theme, error)` | User directory first, then embedded; `ErrNotFound` if neither has it |
44| `LoadFile(path string) (*Theme, error)` | Reads one file, named after it if it sets no `name` |
45| `Parse(data []byte) (*Theme, error)` | Reads TOML content |
46| `Default() *Theme` | The theme used when the user has not chosen one; never fails |
47| `Available() []string` | Every loadable theme name, sorted, user and embedded merged |
48| — | This package no longer works out where the user's themes live; `profile.Profile.ThemeDir()` does, and the answer is passed in. |
49| `(*Theme) Style(key string) tcell.Style` | The style for a key, with dotted fallback |
50| `(*Theme) Name() / Description() string` | What the theme picker shows |
51| `(*Theme) Keys() []string` | Every key the theme sets, sorted |
52| `(*Theme) Defines(key string) bool` | Whether it sets that exact key, without fallback |
53
54Constants `Key*` in `keys.go` are the complete vocabulary of style keys. A test checks that every embedded theme sets every one of them, so adding a widget without theming it fails the suite.
55
56One consequence of dotted fallback is worth stating, because it surprises people writing a theme: fallback runs along the dots and no further. `terminal.text` falls back to `terminal`, then to `default` — never to `editor.text`. The same holds for `tree.*`, which is why the project tree could not simply borrow `list.selected`: that colour is chosen to stand out against a dialog, and on a window body it can be the very colour underneath it.
57
58## Tests
59
60```sh
61make test
62go test ./theme/
63```
64
65## A shipped theme states its whole palette
66
67`Defines` is satisfied by inheritance, so a theme that omits a key still passes the completeness test — while quietly showing a colour Turbo Classic chose for its navy background. On espresso, on cream or on black that colour can be unreadable, and nothing says so.
68
69`TestEveryEmbeddedThemeSetsEveryKeyItself` closes that. A theme *you* write may still inherit — that is what `inherits` is for, and the documentation recommends it. The rule is narrower: a theme shipped inside the binary is one the project is answerable for, so it states its palette in full.
70
71Three more rules, in `editor` where the colour arithmetic already lives: the cursor is at least 64 from its line and never a plain reversal of it, the current line is at least 16 from the page, and **text meant to be read is at least 64 from its background**. That last one exempts the furniture — desktop, shadow, scrollbar trough, inactive frame, disabled entry, line-number gutter — which sits between 20 and 70 in every theme because it exists to recede.
72
73**Channel distance is the wrong measure for prose.** The rule above compares the strongest channel, which is right for a cursor and wrong for something read word by word: Turbo Classic drew comments in `#808080` on `#000080` — 128 apart, waved straight through, and 4.05:1 to read, below the W3C's floor for body text. `TestEveryThemeWeAuthorKeepsItsCommentsReadable` measures WCAG relative luminance instead and holds `syntax.comment` to **4.5:1**. Comments were the dimmest reading colour in six of the eight shipped themes when it was written.
74
75It covers comments and nothing else, deliberately. `syntax.punctuation` is quieter still in several themes and stays that way — punctuation is recognised by shape, not read. And the two Catppuccin themes are exempt, because their colours are somebody else's published palette copied faithfully, and Catppuccin puts comments at 2.87:1 and 2.83:1. A second test fails if that exemption ever names a theme this package does not ship, so it cannot quietly outlive its reason.
76
77And one rule no measurement finds: **two syntax classes a reader meets side by side must not be drawn identically**. `turbo-classic` once painted `syntax.link` the same lime as `syntax.string`, so a Markdown link and an inline code span were the same thing on screen. Classes deliberately alike — string and char, constant and number — are not grouped together, so the test says nothing about them.