feat: add own Gruvbox tmTheme, drop the AGPL dependency

Syntax highlighting for the file preview no longer points at an external
repository. gruvbox-dark.tmTheme is written from scratch here: 39 scope rules
following morhetz/gruvbox's own vim highlight links (Comment=gray italic,
String=green, Constant=purple, Keyword=red, Identifier=blue, Function=green
bold, Type=yellow, StorageClass=orange, PreProc=aqua, Operator=Normal).

The theme commonly paired with Yazi is AGPL-3.0 and would have forced that
licence on this repository. Writing our own keeps everything MIT and makes the
repo self-contained.

Verified under syntect (the library Yazi uses): rendering a sample through bat
emits only palette colours, and a Yazi frame shows 14 distinct colours with no
foreign value.

regen-tmtheme.py reproduces the file byte-for-byte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
thomas.koppandClaude Opus 5 committed 2026-09-22 12:26:24 +02:00
1 parent 4ea70c669e
commit 6dc8e1b3ae
5 files changed
+644 -29

No files matched your search

+20 -19
View File
@@ -31,6 +31,13 @@ popups, picker, input, completion, task manager, help — plus:
**Icons** (`[icon]`): all 725 entries of Yazi's preset table, every glyph colour
remapped to its nearest Gruvbox tone.
**Syntax highlighting** (`gruvbox-dark.tmTheme`): 39 scope rules for the file
preview, written from scratch against
[morhetz/gruvbox](https://github.com/morhetz/gruvbox)'s own vim highlight links
— comments gray italic, strings green, constants purple, keywords red,
identifiers blue, functions green bold, types yellow, preprocessor aqua,
operators plain foreground.
## How the icon colours were derived
Yazi's preset ships 725 icon entries using 221 distinct colours from the
@@ -58,28 +65,14 @@ distinguishable; they just stop shouting.
```sh
git clone https://git.opennerds.org/templis/yazi-gruvbox
cp yazi-gruvbox/theme.toml ~/.config/yazi/theme.toml
cp yazi-gruvbox/theme.toml yazi-gruvbox/gruvbox-dark.tmTheme ~/.config/yazi/
```
`theme.toml` overrides Yazi's built-in `theme-dark.toml`; anything it does not
set keeps the default.
### Optional: Gruvbox syntax highlighting in previews
Not shipped here — the usual Gruvbox `.tmTheme` for Yazi is AGPL-3.0 and would
pull this repository along with it (see `THIRD-PARTY.md`). To get it:
```sh
curl -o ~/.config/yazi/Gruvbox-Dark.tmTheme \
https://raw.githubusercontent.com/poperigby/gruvbox-dark-yazi/main/Gruvbox-Dark.tmTheme
```
Then uncomment in `theme.toml`:
```toml
[mgr]
syntect_theme = "~/.config/yazi/Gruvbox-Dark.tmTheme"
```
Both files are needed — `theme.toml` points at the `.tmTheme` for syntax
highlighting in file previews.
## Keeping the icon table current
@@ -95,7 +88,15 @@ awk '/^\[icon\]/{f=1} f' yazi/yazi-config/preset/theme-dark.toml > icons-src.tom
Then replace the `[icon]` block in `theme.toml` with the generated file. The
script prints which Gruvbox tone each source colour landed on.
`regen-tmtheme.py` does the same for `gruvbox-dark.tmTheme` — edit the palette
or the scope rules at the top of the script and re-run it.
## Self-contained
Everything needed is in this repository — nothing has to be fetched from a
third-party theme repo, and no file here carries a copyleft licence.
## Licence
MIT — see `LICENSE`. Derived icon data is MIT as well; notices in
`THIRD-PARTY.md`.
MIT — see `LICENSE`. The `.tmTheme` is original work; the derived icon data is
MIT as well. Notices in `THIRD-PARTY.md`.
+14 -6
View File
@@ -42,10 +42,18 @@ SOFTWARE.
Colour values follow the Gruvbox palette by Pavel Pertsev:
https://github.com/morhetz/gruvbox — MIT/X11 License.
## Not included: syntax highlighting theme
## Syntax highlighting theme
This repository deliberately ships **no** `.tmTheme` file. The Gruvbox
`.tmTheme` commonly paired with Yazi comes from
https://github.com/poperigby/gruvbox-dark-yazi, which is licensed **AGPL-3.0**.
Bundling it would force AGPL-3.0 on this repository. Fetch it yourself if you
want Gruvbox-coloured file previews — see the README.
`gruvbox-dark.tmTheme` is original work, written for this repository and
covered by its MIT licence. It is not derived from any existing `.tmTheme`.
Its scope-to-colour mapping follows the highlight links defined in
morhetz/gruvbox's own vim colorscheme (Comment=gray, String=green,
Constant=purple, Statement/Keyword=red, Identifier=blue, Function=green bold,
Type=yellow, StorageClass=orange, PreProc=aqua, Operator=Normal) — a mapping
rule, not copied code.
The Gruvbox `.tmTheme` commonly paired with Yazi comes from
https://github.com/poperigby/gruvbox-dark-yazi and is licensed **AGPL-3.0**.
It is deliberately **not** used or bundled here: doing so would force
AGPL-3.0 on this repository.
+501
View File
@@ -0,0 +1,501 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Gruvbox Dark</string>
<key>author</key>
<string>Thomas Kopp</string>
<key>semanticClass</key>
<string>theme.dark.gruvbox_dark</string>
<key>colorSpaceName</key>
<string>sRGB</string>
<key>uuid</key>
<string>90bc21c4-94fc-545a-bb28-71547430f802</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#282828</string>
<key>foreground</key>
<string>#ebdbb2</string>
<key>caret</key>
<string>#ebdbb2</string>
<key>lineHighlight</key>
<string>#3c3836</string>
<key>selection</key>
<string>#504945</string>
<key>invisibles</key>
<string>#504945</string>
<key>findHighlight</key>
<string>#fabd2f</string>
<key>findHighlightForeground</key>
<string>#282828</string>
<key>gutterForeground</key>
<string>#7c6f64</string>
<key>guide</key>
<string>#3c3836</string>
<key>activeGuide</key>
<string>#504945</string>
<key>stackGuide</key>
<string>#3c3836</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment, punctuation.definition.comment</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#928374</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment tag</string>
<key>scope</key>
<string>comment.notation, keyword.codetag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fb4934</string>
<key>fontStyle</key>
<string>bold italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string, punctuation.definition.string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#b8bb26</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Regular expression</string>
<key>scope</key>
<string>string.regexp</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8ec07c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Escape sequence</string>
<key>scope</key>
<string>constant.character.escape, constant.other.placeholder</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fe8019</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Number, boolean, constant</string>
<key>scope</key>
<string>constant.numeric, constant.language, constant.character, constant.other</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#d3869b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Built-in constant</string>
<key>scope</key>
<string>support.constant</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#d3869b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable</string>
<key>scope</key>
<string>variable, variable.other, variable.parameter</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#83a598</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Language variable</string>
<key>scope</key>
<string>variable.language</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fe8019</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword</string>
<key>scope</key>
<string>keyword, keyword.control</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fb4934</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Operator</string>
<key>scope</key>
<string>keyword.operator</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#ebdbb2</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Preprocessor, import</string>
<key>scope</key>
<string>meta.preprocessor, keyword.control.import, keyword.control.export, keyword.other.import, keyword.other.using, punctuation.definition.preprocessor, entity.name.function.preprocessor</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8ec07c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage</string>
<key>scope</key>
<string>storage</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fb4934</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage type</string>
<key>scope</key>
<string>storage.type</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fabd2f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage modifier</string>
<key>scope</key>
<string>storage.modifier</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fe8019</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function</string>
<key>scope</key>
<string>entity.name.function, meta.function-call entity.name.function, support.function.any-method</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#b8bb26</string>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Built-in function</string>
<key>scope</key>
<string>support.function</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8ec07c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Macro</string>
<key>scope</key>
<string>support.macro, entity.name.function.macro</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8ec07c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Class, type, namespace</string>
<key>scope</key>
<string>entity.name.class, entity.name.type, entity.name.struct, entity.name.enum, entity.name.trait, entity.name.namespace, support.class, support.type</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fabd2f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Inherited class</string>
<key>scope</key>
<string>entity.other.inherited-class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fabd2f</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag</string>
<key>scope</key>
<string>entity.name.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8ec07c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag punctuation</string>
<key>scope</key>
<string>punctuation.definition.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#a89984</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Attribute</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fabd2f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Key, property name</string>
<key>scope</key>
<string>support.type.property-name, entity.name.tag.yaml, meta.mapping.key string, meta.object-literal.key</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#83a598</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>CSS property</string>
<key>scope</key>
<string>support.type.property-name.css, meta.property-name.css</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8ec07c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Punctuation</string>
<key>scope</key>
<string>punctuation, punctuation.separator, punctuation.terminator, punctuation.accessor, meta.brace</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#a89984</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid</string>
<key>scope</key>
<string>invalid, invalid.illegal</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fbf1c7</string>
<key>background</key>
<string>#fb4934</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid deprecated</string>
<key>scope</key>
<string>invalid.deprecated</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fbf1c7</string>
<key>background</key>
<string>#b16286</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup heading</string>
<key>scope</key>
<string>markup.heading, entity.name.section</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#b8bb26</string>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup bold</string>
<key>scope</key>
<string>markup.bold</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fe8019</string>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup italic</string>
<key>scope</key>
<string>markup.italic</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fe8019</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup link</string>
<key>scope</key>
<string>markup.underline.link, markup.underline</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#d3869b</string>
<key>fontStyle</key>
<string>underline</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup quote</string>
<key>scope</key>
<string>markup.quote</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#928374</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup list</string>
<key>scope</key>
<string>markup.list, punctuation.definition.list</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#83a598</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup code</string>
<key>scope</key>
<string>markup.raw, markup.inline.raw, markup.fenced_code</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8ec07c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup inserted</string>
<key>scope</key>
<string>markup.inserted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#b8bb26</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup deleted</string>
<key>scope</key>
<string>markup.deleted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fb4934</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup changed</string>
<key>scope</key>
<string>markup.changed</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fabd2f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Diff header</string>
<key>scope</key>
<string>meta.diff.header, meta.diff.range, meta.diff.index</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#83a598</string>
</dict>
</dict>
</array>
</dict>
</plist>
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
# Regenerate gruvbox-dark.tmTheme. Edit the palette or the rules below,
# then run: ./regen-tmtheme.py
import plistlib, uuid
BG0, BG1, BG2, BG3, BG4 = "#282828", "#3c3836", "#504945", "#665c54", "#7c6f64"
GRAY = "#928374"
FG0, FG1, FG2, FG4 = "#fbf1c7", "#ebdbb2", "#d5c4a1", "#a89984"
RED, GREEN, YELLOW, BLUE = "#fb4934", "#b8bb26", "#fabd2f", "#83a598"
PURPLE, AQUA, ORANGE = "#d3869b", "#8ec07c", "#fe8019"
PURPLE_DIM = "#b16286"
def rule(name, scope, fg=None, bg=None, style=None):
s = {}
if fg: s["foreground"] = fg
if bg: s["background"] = bg
if style is not None: s["fontStyle"] = style
return {"name": name, "scope": scope, "settings": s}
# Scope -> colour follows morhetz/gruvbox's vim highlight links:
# Comment=gray italic, String=green, Constant/Number/Boolean=purple,
# Statement/Keyword=red, Identifier=blue, Function=green bold,
# Type=yellow, StorageClass=orange, PreProc/Structure=aqua,
# Special=orange, Operator=Normal.
settings = [
{"settings": {
"background": BG0,
"foreground": FG1,
"caret": FG1,
"lineHighlight": BG1,
"selection": BG2,
"invisibles": BG2,
"findHighlight": YELLOW,
"findHighlightForeground": BG0,
"gutterForeground": BG4,
"guide": BG1,
"activeGuide": BG2,
"stackGuide": BG1,
}},
rule("Comment", "comment, punctuation.definition.comment", GRAY, style="italic"),
rule("Comment tag", "comment.notation, keyword.codetag", RED, style="bold italic"),
rule("String", "string, punctuation.definition.string", GREEN),
rule("Regular expression", "string.regexp", AQUA),
rule("Escape sequence", "constant.character.escape, constant.other.placeholder", ORANGE),
rule("Number, boolean, constant", "constant.numeric, constant.language, constant.character, constant.other", PURPLE),
rule("Built-in constant", "support.constant", PURPLE),
rule("Variable", "variable, variable.other, variable.parameter", BLUE),
rule("Language variable", "variable.language", ORANGE, style="italic"),
rule("Keyword", "keyword, keyword.control", RED),
rule("Operator", "keyword.operator", FG1),
rule("Preprocessor, import", "meta.preprocessor, keyword.control.import, keyword.control.export, keyword.other.import, keyword.other.using, punctuation.definition.preprocessor, entity.name.function.preprocessor", AQUA),
rule("Storage", "storage", RED),
rule("Storage type", "storage.type", YELLOW),
rule("Storage modifier", "storage.modifier", ORANGE),
rule("Function", "entity.name.function, meta.function-call entity.name.function, support.function.any-method", GREEN, style="bold"),
rule("Built-in function", "support.function", AQUA),
rule("Macro", "support.macro, entity.name.function.macro", AQUA),
rule("Class, type, namespace", "entity.name.class, entity.name.type, entity.name.struct, entity.name.enum, entity.name.trait, entity.name.namespace, support.class, support.type", YELLOW),
rule("Inherited class", "entity.other.inherited-class", YELLOW, style="italic"),
rule("Tag", "entity.name.tag", AQUA),
rule("Tag punctuation", "punctuation.definition.tag", FG4),
rule("Attribute", "entity.other.attribute-name", YELLOW),
rule("Key, property name", "support.type.property-name, entity.name.tag.yaml, meta.mapping.key string, meta.object-literal.key", BLUE),
rule("CSS property", "support.type.property-name.css, meta.property-name.css", AQUA),
rule("Punctuation", "punctuation, punctuation.separator, punctuation.terminator, punctuation.accessor, meta.brace", FG4),
rule("Invalid", "invalid, invalid.illegal", FG0, bg=RED),
rule("Invalid deprecated", "invalid.deprecated", FG0, bg=PURPLE_DIM),
rule("Markup heading", "markup.heading, entity.name.section", GREEN, style="bold"),
rule("Markup bold", "markup.bold", ORANGE, style="bold"),
rule("Markup italic", "markup.italic", ORANGE, style="italic"),
rule("Markup link", "markup.underline.link, markup.underline", PURPLE, style="underline"),
rule("Markup quote", "markup.quote", GRAY, style="italic"),
rule("Markup list", "markup.list, punctuation.definition.list", BLUE),
rule("Markup code", "markup.raw, markup.inline.raw, markup.fenced_code", AQUA),
rule("Markup inserted", "markup.inserted", GREEN),
rule("Markup deleted", "markup.deleted", RED),
rule("Markup changed", "markup.changed", YELLOW),
rule("Diff header", "meta.diff.header, meta.diff.range, meta.diff.index", BLUE),
]
theme = {
"name": "Gruvbox Dark",
"author": "Thomas Kopp",
"semanticClass": "theme.dark.gruvbox_dark",
"colorSpaceName": "sRGB",
"uuid": str(uuid.uuid5(uuid.NAMESPACE_DNS, "yazi-gruvbox.opennerds.org")),
"settings": settings,
}
out = "gruvbox-dark.tmTheme"
with open(out, "wb") as f:
plistlib.dump(theme, f, sort_keys=False)
print("wrote", out)
print("rules:", len(settings) - 1)
+2 -4
View File
@@ -36,10 +36,8 @@ count_selected = { fg = "#282828", bg = "#fabd2f" }
# Border
border_style = { fg = "#665c54" }
# Syntax highlighting in the file preview.
# Not shipped with this repo (the Gruvbox .tmTheme is AGPL-3.0, see THIRD-PARTY.md).
# Fetch it as described in the README, then uncomment:
# syntect_theme = "~/.config/yazi/Gruvbox-Dark.tmTheme"
# Syntax highlighting in the file preview (gruvbox-dark.tmTheme ships with this repo)
syntect_theme = "~/.config/yazi/gruvbox-dark.tmTheme"
# : }}}