turbo-editors/turbo-rustpublic Fork 0
v1.0.0
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 v1.0.0 · k33g · 10h ago
write-a-theme.md · 152 lines · 7.9 KBmarkdown
Blame HistoryOpen raw

How to write your own theme

This guide shows how to add a colour theme of your own. It assumes you know where your configuration directory is and can edit a TOML file.

1. Find where themes go

turbo-rust -list-themes

The last line tells you the directory — ~/.config/turbo-rust/themes on Linux, ~/Library/Application Support/turbo-rust/themes on macOS. Create it:

mkdir -p ~/.config/turbo-rust/themes

2. Start from an existing theme

The quickest start is to inherit from one that already works and override only what you want:

# ~/.config/turbo-rust/themes/mine.toml
name = "Mine"
description = "Turbo Classic, but the comments are readable."
inherits = "turbo-classic"

[colors]
"syntax.comment" = { fg = "#8a8a8a", italic = true }
"syntax.string"  = { fg = "#87d7af" }

Everything you do not set is taken from turbo-classic.

Inherit from a theme whose ground is the same as yours. The colours you leave out were chosen against the background of the theme you inherit from, so a dark theme built on turbo-classic will show, here and there, a colour picked for Borland navy. If you are writing a dark theme, inherit from turbo-dark, cappuccino, catppuccin-frappe, cobalt, darcula or monochrome-dark; if a light one, from borland-light, catppuccin-latte, intellij-light or monochrome-light. That is also why the eleven themes that ship in the binary each state their palette in full rather than inheriting most of it — a test holds them to it, because a shipped theme is one the project is answerable for.

3. Use it

turbo-rust -theme mine main.rs

Or from inside the editor: Options ▸ Theme…, which lists every theme it can find.

4. Iterate

Edit the file, then restart the editor. There is no live reload.

If the theme fails to load, Turbo Rust falls back to the default rather than refusing to start. To see why it failed:

turbo-rust -list-themes

A broken theme is listed with the parse error beside it — an unknown colour name is an error, not a silent fallback, so a typo is pointed at rather than quietly repainting half the screen.

5. Check it stays readable

The project holds every theme it ships to five measured rules, and they are worth applying to your own. make test runs them.

Rule Why
The cursor is at least 64 apart from the line it sits on, in its strongest channel A terminal draws its cursor over the cell; one that blends in cannot be found
The cursor is never a plain reversal of that line A terminal that draws its cursor by inverting the cell would invert it back into invisibility
The current line is at least 16 from the page turbo-dark once used ten, which is no highlight at all
Text you have to read is at least 64 from its background Furniture — the desktop, a shadow, a scrollbar trough, a disabled entry — is exempt: it exists to recede
Comments read at 4.5:1 or better against their background, by WCAG relative luminance A comment is prose, read word by word. turbo-classic drew them in #808080 on its navy: 128 channel values apart, so the rule above waved it through, and 4.05:1 to read, which is below the W3C's floor for body text. Comments were the dimmest colour in six of the eight shipped themes.

The contrast rule is applied to the six themes this project authors. The two Catppuccin themes are exempt, and the exemption is written where it is made: their colours are somebody else's published palette, faithfully copied, and Catppuccin puts comments at 2.87:1 in Frappé and 2.83:1 in Latte. A theme called Catppuccin that is not those exact values is a different theme wearing a borrowed name, so the fix — if anyone wants one — is upstream.

It is applied to comments and to nothing else. syntax.punctuation is quieter still in several themes and stays that way: punctuation is recognised by shape, not read.

A sixth rule catches the mistake 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 — every colour readable, every key set, and the two simply equal. Both monochromes pass this rule with no hue at all, by using bold, italic and underline instead.

Variants

Override a shipped theme rather than adding one. Name your file after it — turbo-classic.toml — and yours wins. The embedded one is not replaced, so deleting your file brings it back.

Start from scratch. Leave inherits out. Set at least default; every key you do not set falls back along the dots to it, so a theme with one line is still a usable theme.

Colour only the syntax. One key does it:

[colors]
syntax = { fg = "silver" }

syntax.keyword, syntax.string and the rest all fall back to it.

Keep the terminal's own colours. Use default as a colour value:

[colors]
"editor.text" = { fg = "default", bg = "default" }

Test it in a checkout without installing it. Point the editor at any directory:

TURBO_RUST_THEME_DIR=./my-themes turbo-rust -theme mine main.rs

The cursor is hard to see. editor.cursor does two things: its background is sent to the terminal as the cursor's own colour, and it also paints the cell underneath as a fallback for terminals that ignore that. Set it to something loud:

[colors]
"editor.cursor" = { fg = "#000000", bg = "#ff8700" }

Two things make a cursor colour a bad one, and the test suite rejects both: a plain reversal of the line — which terminals that draw their cursor by inverting the cell turn back into invisibility — and anything less than 64 channel values away from the line it sits on.

The cursor's line is hard to find. That is editor.currentline, and it is a different key. It has to differ from editor.text by at least 16 channel values to count as a highlight at all.

Markdown and HTML look plain. Five keys belong to the markup languages and have no equivalent in Rust, so a theme written before they existed does not set them:

[colors]
"syntax.heading"   = { fg = "white", bold = true }
"syntax.tag"       = { fg = "aqua" }
"syntax.attribute" = { fg = "yellow" }
"syntax.emphasis"  = { fg = "fuchsia", bold = true }
"syntax.link"      = { fg = "aqua", underline = true }

Give syntax.link a different colour from syntax.string: a link and an inline code span sit side by side in most prose, and sharing a colour makes them one blur. The shipped turbo-classic had exactly that fault until it was looked at on a real terminal.

The project tree looks flat. It has four keys of its own, and none of them falls back to list:

[colors]
"tree.text"      = { fg = "silver", bg = "navy" }
"tree.directory" = { fg = "white", bg = "navy", bold = true }
"tree.selected"  = { fg = "black", bg = "aqua" }
"tree.unfocused" = { fg = "black", bg = "gray" }

Give tree.text the same background as window.body, so the tree looks like part of its window, and make tree.selected clearly different from it — the test suite holds every shipped theme to at least 64 channel values between the two, because a highlight the same colour as the page is no highlight.

Terminal windows look wrong. They have two keys of their own, and neither falls back to editor:

[colors]
"terminal.text"   = { fg = "silver", bg = "black" }
"terminal.cursor" = { fg = "black", bg = "aqua" }

terminal.text is what a shell's output gets when it names no colour of its own — set it to something close to a real terminal rather than to your editor background, or less and htop will look out of place. A program that does name its colours keeps them either way.

See also

  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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# How to write your own theme

This guide shows how to add a colour theme of your own. It assumes you know where your configuration directory is and can edit a TOML file.

## 1. Find where themes go

```bash
turbo-rust -list-themes
```

The last line tells you the directory — `~/.config/turbo-rust/themes` on Linux, `~/Library/Application Support/turbo-rust/themes` on macOS. Create it:

```bash
mkdir -p ~/.config/turbo-rust/themes
```

## 2. Start from an existing theme

The quickest start is to inherit from one that already works and override only what you want:

```toml
# ~/.config/turbo-rust/themes/mine.toml
name = "Mine"
description = "Turbo Classic, but the comments are readable."
inherits = "turbo-classic"

[colors]
"syntax.comment" = { fg = "#8a8a8a", italic = true }
"syntax.string"  = { fg = "#87d7af" }
```

Everything you do not set is taken from `turbo-classic`.

**Inherit from a theme whose ground is the same as yours.** The colours you leave out were chosen against the background of the theme you inherit from, so a dark theme built on `turbo-classic` will show, here and there, a colour picked for Borland navy. If you are writing a dark theme, inherit from `turbo-dark`, `cappuccino`, `catppuccin-frappe`, `cobalt`, `darcula` or `monochrome-dark`; if a light one, from `borland-light`, `catppuccin-latte`, `intellij-light` or `monochrome-light`. That is also why the eleven themes that ship in the binary each state their palette in full rather than inheriting most of it — a test holds them to it, because a shipped theme is one the project is answerable for.

## 3. Use it

```bash
turbo-rust -theme mine main.rs
```

Or from inside the editor: `Options ▸ Theme…`, which lists every theme it can find.

## 4. Iterate

Edit the file, then restart the editor. There is no live reload.

If the theme fails to load, Turbo Rust falls back to the default rather than refusing to start. To see *why* it failed:

```bash
turbo-rust -list-themes
```

A broken theme is listed with the parse error beside it — an unknown colour name is an error, not a silent fallback, so a typo is pointed at rather than quietly repainting half the screen.

## 5. Check it stays readable

The project holds every theme it ships to five measured rules, and they are worth applying to your own. `make test` runs them.

| Rule | Why |
| --- | --- |
| The cursor is at least 64 apart from the line it sits on, in its strongest channel | A terminal draws its cursor over the cell; one that blends in cannot be found |
| The cursor is never a plain reversal of that line | A terminal that draws its cursor by inverting the cell would invert it back into invisibility |
| The current line is at least 16 from the page | `turbo-dark` once used ten, which is no highlight at all |
| Text you have to read is at least 64 from its background | Furniture — the desktop, a shadow, a scrollbar trough, a disabled entry — is exempt: it exists to recede |
| Comments read at 4.5:1 or better against their background, by WCAG relative luminance | A comment is prose, read word by word. `turbo-classic` drew them in `#808080` on its navy: 128 channel values apart, so the rule above waved it through, and 4.05:1 to read, which is below the W3C's floor for body text. Comments were the dimmest colour in six of the eight shipped themes. |


The contrast rule is applied to the six themes this project authors. The two Catppuccin themes are exempt, and the exemption is written where it is made: their colours are somebody else's published palette, faithfully copied, and Catppuccin puts comments at 2.87:1 in Frappé and 2.83:1 in Latte. A theme called Catppuccin that is not those exact values is a different theme wearing a borrowed name, so the fix — if anyone wants one — is upstream.

It is applied to comments and to nothing else. `syntax.punctuation` is quieter still in several themes and stays that way: punctuation is recognised by shape, not read.

A sixth rule catches the mistake 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 — every colour readable, every key set, and the two simply equal. Both monochromes pass this rule with no hue at all, by using bold, italic and underline instead.

## Variants

**Override a shipped theme rather than adding one.** Name your file after it — `turbo-classic.toml` — and yours wins. The embedded one is not replaced, so deleting your file brings it back.

**Start from scratch.** Leave `inherits` out. Set at least `default`; every key you do not set falls back along the dots to it, so a theme with one line is still a usable theme.

**Colour only the syntax.** One key does it:

```toml
[colors]
syntax = { fg = "silver" }
```

`syntax.keyword`, `syntax.string` and the rest all fall back to it.

**Keep the terminal's own colours.** Use `default` as a colour value:

```toml
[colors]
"editor.text" = { fg = "default", bg = "default" }
```

**Test it in a checkout without installing it.** Point the editor at any directory:

```bash
TURBO_RUST_THEME_DIR=./my-themes turbo-rust -theme mine main.rs
```

**The cursor is hard to see.** `editor.cursor` does two things: its **background** is sent to the terminal as the cursor's own colour, and it also paints the cell underneath as a fallback for terminals that ignore that. Set it to something loud:

```toml
[colors]
"editor.cursor" = { fg = "#000000", bg = "#ff8700" }
```

Two things make a cursor colour a bad one, and the test suite rejects both: a plain reversal of the line — which terminals that draw their cursor by inverting the cell turn back into invisibility — and anything less than 64 channel values away from the line it sits on.

**The cursor's line is hard to find.** That is `editor.currentline`, and it is a different key. It has to differ from `editor.text` by at least 16 channel values to count as a highlight at all.

**Markdown and HTML look plain.** Five keys belong to the markup languages and have no equivalent in Rust, so a theme written before they existed does not set them:

```toml
[colors]
"syntax.heading"   = { fg = "white", bold = true }
"syntax.tag"       = { fg = "aqua" }
"syntax.attribute" = { fg = "yellow" }
"syntax.emphasis"  = { fg = "fuchsia", bold = true }
"syntax.link"      = { fg = "aqua", underline = true }
```

Give `syntax.link` a different colour from `syntax.string`: a link and an inline `code` span sit side by side in most prose, and sharing a colour makes them one blur. The shipped `turbo-classic` had exactly that fault until it was looked at on a real terminal.

**The project tree looks flat.** It has four keys of its own, and none of them falls back to `list`:

```toml
[colors]
"tree.text"      = { fg = "silver", bg = "navy" }
"tree.directory" = { fg = "white", bg = "navy", bold = true }
"tree.selected"  = { fg = "black", bg = "aqua" }
"tree.unfocused" = { fg = "black", bg = "gray" }
```

Give `tree.text` the same background as `window.body`, so the tree looks like part of its window, and make `tree.selected` clearly different from it — the test suite holds every shipped theme to at least 64 channel values between the two, because a highlight the same colour as the page is no highlight.

**Terminal windows look wrong.** They have two keys of their own, and neither falls back to `editor`:

```toml
[colors]
"terminal.text"   = { fg = "silver", bg = "black" }
"terminal.cursor" = { fg = "black", bg = "aqua" }
```

`terminal.text` is what a shell's output gets when it names no colour of its own — set it to something close to a real terminal rather than to your editor background, or `less` and `htop` will look out of place. A program that does name its colours keeps them either way.

## See also

- Every key you may set, and every colour name: [theme file reference](../reference/themes.md)
- Why the format is TOML with two kinds of inheritance: [Design decisions](../explanation/design-decisions.md)