From 6dc8e1b3aec9fbf69cbffacf523ea49ca0f2cae7 Mon Sep 17 00:00:00 2001 From: "thomas.kopp" Date: Tue, 22 Sep 2026 12:26:24 +0200 Subject: [PATCH] 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 --- README.md | 39 ++-- THIRD-PARTY.md | 20 +- gruvbox-dark.tmTheme | 501 +++++++++++++++++++++++++++++++++++++++++++ regen-tmtheme.py | 107 +++++++++ theme.toml | 6 +- 5 files changed, 644 insertions(+), 29 deletions(-) create mode 100644 gruvbox-dark.tmTheme create mode 100755 regen-tmtheme.py diff --git a/README.md b/README.md index ecabf29..5e192f4 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/THIRD-PARTY.md b/THIRD-PARTY.md index b3f6039..98ed8af 100644 --- a/THIRD-PARTY.md +++ b/THIRD-PARTY.md @@ -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. diff --git a/gruvbox-dark.tmTheme b/gruvbox-dark.tmTheme new file mode 100644 index 0000000..460027e --- /dev/null +++ b/gruvbox-dark.tmTheme @@ -0,0 +1,501 @@ + + + + + name + Gruvbox Dark + author + Thomas Kopp + semanticClass + theme.dark.gruvbox_dark + colorSpaceName + sRGB + uuid + 90bc21c4-94fc-545a-bb28-71547430f802 + settings + + + settings + + background + #282828 + foreground + #ebdbb2 + caret + #ebdbb2 + lineHighlight + #3c3836 + selection + #504945 + invisibles + #504945 + findHighlight + #fabd2f + findHighlightForeground + #282828 + gutterForeground + #7c6f64 + guide + #3c3836 + activeGuide + #504945 + stackGuide + #3c3836 + + + + name + Comment + scope + comment, punctuation.definition.comment + settings + + foreground + #928374 + fontStyle + italic + + + + name + Comment tag + scope + comment.notation, keyword.codetag + settings + + foreground + #fb4934 + fontStyle + bold italic + + + + name + String + scope + string, punctuation.definition.string + settings + + foreground + #b8bb26 + + + + name + Regular expression + scope + string.regexp + settings + + foreground + #8ec07c + + + + name + Escape sequence + scope + constant.character.escape, constant.other.placeholder + settings + + foreground + #fe8019 + + + + name + Number, boolean, constant + scope + constant.numeric, constant.language, constant.character, constant.other + settings + + foreground + #d3869b + + + + name + Built-in constant + scope + support.constant + settings + + foreground + #d3869b + + + + name + Variable + scope + variable, variable.other, variable.parameter + settings + + foreground + #83a598 + + + + name + Language variable + scope + variable.language + settings + + foreground + #fe8019 + fontStyle + italic + + + + name + Keyword + scope + keyword, keyword.control + settings + + foreground + #fb4934 + + + + name + Operator + scope + keyword.operator + settings + + foreground + #ebdbb2 + + + + name + Preprocessor, import + scope + meta.preprocessor, keyword.control.import, keyword.control.export, keyword.other.import, keyword.other.using, punctuation.definition.preprocessor, entity.name.function.preprocessor + settings + + foreground + #8ec07c + + + + name + Storage + scope + storage + settings + + foreground + #fb4934 + + + + name + Storage type + scope + storage.type + settings + + foreground + #fabd2f + + + + name + Storage modifier + scope + storage.modifier + settings + + foreground + #fe8019 + + + + name + Function + scope + entity.name.function, meta.function-call entity.name.function, support.function.any-method + settings + + foreground + #b8bb26 + fontStyle + bold + + + + name + Built-in function + scope + support.function + settings + + foreground + #8ec07c + + + + name + Macro + scope + support.macro, entity.name.function.macro + settings + + foreground + #8ec07c + + + + name + Class, type, namespace + scope + entity.name.class, entity.name.type, entity.name.struct, entity.name.enum, entity.name.trait, entity.name.namespace, support.class, support.type + settings + + foreground + #fabd2f + + + + name + Inherited class + scope + entity.other.inherited-class + settings + + foreground + #fabd2f + fontStyle + italic + + + + name + Tag + scope + entity.name.tag + settings + + foreground + #8ec07c + + + + name + Tag punctuation + scope + punctuation.definition.tag + settings + + foreground + #a89984 + + + + name + Attribute + scope + entity.other.attribute-name + settings + + foreground + #fabd2f + + + + name + Key, property name + scope + support.type.property-name, entity.name.tag.yaml, meta.mapping.key string, meta.object-literal.key + settings + + foreground + #83a598 + + + + name + CSS property + scope + support.type.property-name.css, meta.property-name.css + settings + + foreground + #8ec07c + + + + name + Punctuation + scope + punctuation, punctuation.separator, punctuation.terminator, punctuation.accessor, meta.brace + settings + + foreground + #a89984 + + + + name + Invalid + scope + invalid, invalid.illegal + settings + + foreground + #fbf1c7 + background + #fb4934 + + + + name + Invalid deprecated + scope + invalid.deprecated + settings + + foreground + #fbf1c7 + background + #b16286 + + + + name + Markup heading + scope + markup.heading, entity.name.section + settings + + foreground + #b8bb26 + fontStyle + bold + + + + name + Markup bold + scope + markup.bold + settings + + foreground + #fe8019 + fontStyle + bold + + + + name + Markup italic + scope + markup.italic + settings + + foreground + #fe8019 + fontStyle + italic + + + + name + Markup link + scope + markup.underline.link, markup.underline + settings + + foreground + #d3869b + fontStyle + underline + + + + name + Markup quote + scope + markup.quote + settings + + foreground + #928374 + fontStyle + italic + + + + name + Markup list + scope + markup.list, punctuation.definition.list + settings + + foreground + #83a598 + + + + name + Markup code + scope + markup.raw, markup.inline.raw, markup.fenced_code + settings + + foreground + #8ec07c + + + + name + Markup inserted + scope + markup.inserted + settings + + foreground + #b8bb26 + + + + name + Markup deleted + scope + markup.deleted + settings + + foreground + #fb4934 + + + + name + Markup changed + scope + markup.changed + settings + + foreground + #fabd2f + + + + name + Diff header + scope + meta.diff.header, meta.diff.range, meta.diff.index + settings + + foreground + #83a598 + + + + + diff --git a/regen-tmtheme.py b/regen-tmtheme.py new file mode 100755 index 0000000..5701c78 --- /dev/null +++ b/regen-tmtheme.py @@ -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) diff --git a/theme.toml b/theme.toml index c353873..de551cc 100644 --- a/theme.toml +++ b/theme.toml @@ -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" # : }}}