Compare commits
11 Commits
08abf6393f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 437356272e | |||
| 2b0cee20ba | |||
| f414bf0170 | |||
| 7cb4ce2902 | |||
| 4136aa53c8 | |||
| 08135a407b | |||
| 90cf08729e | |||
| 92200c76ec | |||
| b42e789f29 | |||
| 586e1c75e7 | |||
| c60890eb4a |
@@ -11,27 +11,29 @@ DARK_NOTIFY_LOG := $(HOME)/Library/Logs/dark-notify.log
|
|||||||
CONFIG_MARKER := $(CONFIG_DIR)/.tint-installed-config
|
CONFIG_MARKER := $(CONFIG_DIR)/.tint-installed-config
|
||||||
STATE_MARKER := $(CONFIG_DIR)/.tint-installed-state
|
STATE_MARKER := $(CONFIG_DIR)/.tint-installed-state
|
||||||
|
|
||||||
.PHONY: install uninstall
|
.PHONY: install install-schemes uninstall
|
||||||
install:
|
install: default-state.toml config.toml
|
||||||
@command -v tinty >/dev/null || { echo "tinty is required" >&2; exit 1; }
|
@command -v tinty >/dev/null || { echo "tinty is required" >&2; exit 1; }
|
||||||
@command -v dark-notify >/dev/null || { echo "dark-notify is required" >&2; exit 1; }
|
@command -v dark-notify >/dev/null || { echo "dark-notify is required" >&2; exit 1; }
|
||||||
mkdir -p "$(BIN_DIR)" "$(CONFIG_DIR)" "$(HERDR_DIR)" "$(LAUNCH_AGENT_DIR)" "$(dir $(DARK_NOTIFY_LOG))"
|
mkdir -p "$(BIN_DIR)" "$(CONFIG_DIR)" "$(HERDR_DIR)" "$(LAUNCH_AGENT_DIR)" "$(dir $(DARK_NOTIFY_LOG))"
|
||||||
go build -o "$(BIN_DIR)/tint" .
|
go build -o "$(BIN_DIR)/tint" .
|
||||||
@if [ "$(CURDIR)" != "$(CONFIG_DIR)" ]; then \
|
@set -e; if [ "$(CURDIR)" != "$(CONFIG_DIR)" ]; then \
|
||||||
if [ -e "$(CONFIG_MARKER)" ] || [ ! -e "$(CONFIG_DIR)/config.toml" ]; then \
|
if [ -e "$(CONFIG_MARKER)" ] || [ ! -e "$(CONFIG_DIR)/config.toml" ]; then \
|
||||||
cp config.toml "$(CONFIG_DIR)/config.toml"; \
|
cp config.toml "$(CONFIG_DIR)/config.toml"; \
|
||||||
touch "$(CONFIG_MARKER)"; \
|
touch "$(CONFIG_MARKER)"; \
|
||||||
fi; \
|
fi; \
|
||||||
if [ ! -e "$(CONFIG_DIR)/state.toml" ]; then \
|
fi; \
|
||||||
cp state.toml "$(CONFIG_DIR)/state.toml"; \
|
if [ ! -e "$(CONFIG_DIR)/state.toml" ]; then \
|
||||||
touch "$(STATE_MARKER)"; \
|
cp default-state.toml "$(CONFIG_DIR)/state.toml"; \
|
||||||
fi; \
|
touch "$(STATE_MARKER)"; \
|
||||||
fi
|
fi
|
||||||
@if [ ! -e "$(HERDR_CONFIG)" ]; then \
|
@if [ ! -e "$(HERDR_CONFIG)" ]; then \
|
||||||
touch "$(HERDR_CONFIG)" "$(HERDR_MARKER)"; \
|
touch "$(HERDR_CONFIG)" "$(HERDR_MARKER)"; \
|
||||||
fi
|
fi
|
||||||
XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty sync
|
XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty sync
|
||||||
|
$(MAKE) install-schemes
|
||||||
@dark_notify="$$(command -v dark-notify)"; \
|
@dark_notify="$$(command -v dark-notify)"; \
|
||||||
|
tinty_dir="$$(dirname "$$(command -v tinty)")"; \
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
'<?xml version="1.0" encoding="UTF-8"?>' \
|
'<?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">' \
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' \
|
||||||
@@ -45,6 +47,15 @@ install:
|
|||||||
' <string>-c</string>' \
|
' <string>-c</string>' \
|
||||||
' <string>$(BIN_DIR)/tint</string>' \
|
' <string>$(BIN_DIR)/tint</string>' \
|
||||||
' </array>' \
|
' </array>' \
|
||||||
|
' <key>EnvironmentVariables</key>' \
|
||||||
|
' <dict>' \
|
||||||
|
' <key>HOME</key>' \
|
||||||
|
' <string>$(HOME)</string>' \
|
||||||
|
' <key>PATH</key>' \
|
||||||
|
" <string>$$tinty_dir:/usr/bin:/bin:/usr/sbin:/sbin</string>" \
|
||||||
|
' <key>XDG_CONFIG_HOME</key>' \
|
||||||
|
' <string>$(XDG_CONFIG_HOME)</string>' \
|
||||||
|
' </dict>' \
|
||||||
' <key>RunAtLoad</key>' \
|
' <key>RunAtLoad</key>' \
|
||||||
' <true/>' \
|
' <true/>' \
|
||||||
' <key>KeepAlive</key>' \
|
' <key>KeepAlive</key>' \
|
||||||
@@ -59,9 +70,19 @@ install:
|
|||||||
@launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true
|
@launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true
|
||||||
launchctl bootstrap "gui/$$(id -u)" "$(LAUNCH_AGENT)"
|
launchctl bootstrap "gui/$$(id -u)" "$(LAUNCH_AGENT)"
|
||||||
|
|
||||||
|
install-schemes: schemes/base16/pumpkin.yaml schemes/base16/pumpkin-light.yaml
|
||||||
|
@set -e; \
|
||||||
|
data_dir="$$(XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty config --data-dir-path)"; \
|
||||||
|
mkdir -p "$$data_dir/custom-schemes/base16"; \
|
||||||
|
cp schemes/base16/pumpkin.yaml schemes/base16/pumpkin-light.yaml "$$data_dir/custom-schemes/base16/"
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
@launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true
|
@launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true
|
||||||
rm -f "$(BIN_DIR)/tint" "$(LAUNCH_AGENT)" "$(DARK_NOTIFY_LOG)"
|
rm -f "$(BIN_DIR)/tint" "$(LAUNCH_AGENT)" "$(DARK_NOTIFY_LOG)"
|
||||||
|
@if command -v tinty >/dev/null; then \
|
||||||
|
data_dir="$$(XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty config --data-dir-path)" && \
|
||||||
|
rm -f "$$data_dir/custom-schemes/base16/pumpkin.yaml" "$$data_dir/custom-schemes/base16/pumpkin-light.yaml"; \
|
||||||
|
fi
|
||||||
@if [ -e "$(STATE_MARKER)" ]; then \
|
@if [ -e "$(STATE_MARKER)" ]; then \
|
||||||
rm -f "$(CONFIG_DIR)/state.toml" "$(STATE_MARKER)"; \
|
rm -f "$(CONFIG_DIR)/state.toml" "$(STATE_MARKER)"; \
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# tint
|
# tint
|
||||||
|
|
||||||
A small wrapper around [Tinty](https://github.com/tinted-theming/tinty) that manages light/dark theme pairs and applies the selected theme to Ghostty, Zed, Herdr, and OMP.
|
A small wrapper around [Tinty](https://github.com/tinted-theming/tinty) that manages light/dark theme pairs and applies the selected theme to Ghostty, Zed, Herdr, OMP, and Conjure.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ brew install tinty go
|
|||||||
brew install cormacrelf/tap/dark-notify
|
brew install cormacrelf/tap/dark-notify
|
||||||
```
|
```
|
||||||
|
|
||||||
Ghostty, Zed, Herdr, and OMP are optional consumers. Install only the applications you use.
|
Ghostty, Zed, Herdr, OMP, and Conjure are optional consumers. Install only the applications you use.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@@ -35,9 +35,10 @@ The installer:
|
|||||||
|
|
||||||
- Builds `~/.local/bin/tint`.
|
- Builds `~/.local/bin/tint`.
|
||||||
- Installs `config.toml` under `$XDG_CONFIG_HOME/tinted-theming/tinty`, or `~/.config/tinted-theming/tinty` when `XDG_CONFIG_HOME` is unset.
|
- Installs `config.toml` under `$XDG_CONFIG_HOME/tinted-theming/tinty`, or `~/.config/tinted-theming/tinty` when `XDG_CONFIG_HOME` is unset.
|
||||||
- Copies `state.toml` only when no state file already exists.
|
- Copies the versioned `default-state.toml` to the installed `state.toml` only when no state file already exists.
|
||||||
- Creates `~/.config/herdr/config.toml` if needed.
|
- Creates `~/.config/herdr/config.toml` if needed.
|
||||||
- Runs `tinty sync` to download the configured theme repositories.
|
- Runs `tinty sync` to download the configured theme repositories.
|
||||||
|
- Installs bundled custom schemes into Tinty's data directory.
|
||||||
- Creates and loads a `dark-notify` LaunchAgent for automatic macOS appearance switching.
|
- Creates and loads a `dark-notify` LaunchAgent for automatic macOS appearance switching.
|
||||||
|
|
||||||
Add the binary directory to your shell path:
|
Add the binary directory to your shell path:
|
||||||
@@ -119,6 +120,18 @@ The wrapper writes:
|
|||||||
|
|
||||||
Configure OMP to use `tinty-current` where required.
|
Configure OMP to use `tinty-current` where required.
|
||||||
|
|
||||||
|
### Conjure
|
||||||
|
|
||||||
|
The `apply-apps` flow writes `$XDG_CONFIG_HOME/conjure/theme.json`, falling back to `~/.config/conjure/theme.json` when `XDG_CONFIG_HOME` is unset, empty, or relative. The generated JSON contains the selected scheme's `variant` (`light` or `dark`) and seven opaque colors: `background`, `text`, `secondaryText`, `accent`, `glow`, `keyBackground`, and `error`, using Tinty semantic UI colors with Base16/Base24 or tinted8 palette fallbacks. Conjure uses the variant to match its native window appearance to the palette without an additional user setting.
|
||||||
|
|
||||||
|
Opt in by adding this to `conjure/config.toml` in the same configuration directory:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
theme = "tint"
|
||||||
|
```
|
||||||
|
|
||||||
|
The wrapper never changes `config.toml`. Optional entries in its `[colors]` table override generated colors. Only the machine-generated `theme.json` uses JSON; edit the TOML configuration instead. Conjure watches both files and applies changes live, including automatic macOS light/dark switches, without restarting or discarding active tool state. The wrapper does not need to signal or restart Conjure.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Show the current pair and available commands:
|
Show the current pair and available commands:
|
||||||
@@ -160,6 +173,24 @@ TINTY_PAIR_APPEARANCE=light tint swamp
|
|||||||
TINTY_PAIR_APPEARANCE=dark tint swamp
|
TINTY_PAIR_APPEARANCE=dark tint swamp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Pumpkin
|
||||||
|
|
||||||
|
The bundled [`base16-pumpkin`](schemes/base16/pumpkin.yaml) and [`base16-pumpkin-light`](schemes/base16/pumpkin-light.yaml) palettes come from the sibling Terebi project. Dark Pumpkin uses brown, cream and vivid autumn accents; Pumpkin Light uses parchment, brown ink and deeper orange, persimmon, toasted amber, caramel and olive accents. Its ANSI cyan slot uses caramel so inverted terminal bars stay warm rather than olive. All light text/accent slots exceed 4.5:1 contrast against the background.
|
||||||
|
|
||||||
|
`make install` installs both palettes automatically. To install the palettes and updated wrapper without syncing repositories or reloading the appearance agent, then save and select their named pair:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make install-schemes
|
||||||
|
go build -o "$HOME/.local/bin/tint" .
|
||||||
|
tint pair pumpkin dark:base16-pumpkin light:base16-pumpkin-light
|
||||||
|
tint favorite pumpkin
|
||||||
|
tint pumpkin
|
||||||
|
```
|
||||||
|
|
||||||
|
`tint pumpkin` sets both sides and applies the side matching macOS appearance. Use `tint light` or `tint dark` to apply a saved side immediately. Saving/favoriting the pair without selecting it leaves the current pair and active theme unchanged.
|
||||||
|
|
||||||
|
Tinty stores both YAML files under `$(tinty config --data-dir-path)/custom-schemes/base16/` and builds the installed app templates when applying either theme. Both theme validation and app palette lookup support [Tinty's native custom schemes](https://github.com/tinted-theming/tinty/blob/v0.34.0/USAGE.md#use-your-own-schemes), so Ghostty, Zed, Herdr, OMP and Conjure use the normal application flow. List local palettes with `tinty list --custom-schemes`. Reinstalling updates the bundled palettes without changing saved state; fresh installs include the favorite `pumpkin` pair, and uninstalling removes both bundled YAML files.
|
||||||
|
|
||||||
## Update
|
## Update
|
||||||
|
|
||||||
After changing or updating the wrapper source:
|
After changing or updating the wrapper source:
|
||||||
@@ -168,7 +199,7 @@ After changing or updating the wrapper source:
|
|||||||
make install
|
make install
|
||||||
```
|
```
|
||||||
|
|
||||||
Existing `state.toml` content is preserved.
|
Existing `state.toml` content is preserved. Missing state is recreated from `default-state.toml`, including the bundled named pairs and favorites. The source directory's ignored `state.toml` is personal state and is not used by the installer.
|
||||||
|
|
||||||
## Uninstall
|
## Uninstall
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ func applyApps(zedSource string) error {
|
|||||||
if err := writeHerdrTheme(colors, ui, syntax); err != nil {
|
if err := writeHerdrTheme(colors, ui, syntax); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := writeConjureTheme(scheme.Variant, colors, ui); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return writeZedTheme(zedSource)
|
return writeZedTheme(zedSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,18 +51,20 @@ func currentScheme() (tintyScheme, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return tintyScheme{}, err
|
return tintyScheme{}, err
|
||||||
}
|
}
|
||||||
output, err := exec.Command("tinty", "list", "--json").Output()
|
|
||||||
if err != nil {
|
|
||||||
return tintyScheme{}, err
|
|
||||||
}
|
|
||||||
var schemes []tintyScheme
|
|
||||||
if err := json.Unmarshal(output, &schemes); err != nil {
|
|
||||||
return tintyScheme{}, err
|
|
||||||
}
|
|
||||||
id := strings.TrimSpace(string(current))
|
id := strings.TrimSpace(string(current))
|
||||||
for _, scheme := range schemes {
|
for _, args := range [][]string{{"list", "--json"}, {"list", "--json", "--custom-schemes"}} {
|
||||||
if scheme.ID == id {
|
output, err := exec.Command("tinty", args...).Output()
|
||||||
return scheme, nil
|
if err != nil {
|
||||||
|
return tintyScheme{}, err
|
||||||
|
}
|
||||||
|
var schemes []tintyScheme
|
||||||
|
if err := json.Unmarshal(output, &schemes); err != nil {
|
||||||
|
return tintyScheme{}, err
|
||||||
|
}
|
||||||
|
for _, scheme := range schemes {
|
||||||
|
if scheme.ID == id {
|
||||||
|
return scheme, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tintyScheme{}, fmt.Errorf("scheme not found: %s", id)
|
return tintyScheme{}, fmt.Errorf("scheme not found: %s", id)
|
||||||
@@ -186,6 +191,32 @@ func semanticTheme(colors, ui, syntax map[string]string) (map[string]string, map
|
|||||||
return omp, herdr
|
return omp, herdr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeConjureTheme(variant string, colors, ui map[string]string) error {
|
||||||
|
if variant != "light" && variant != "dark" {
|
||||||
|
return fmt.Errorf("invalid Conjure variant: %q", variant)
|
||||||
|
}
|
||||||
|
mapping := map[string]string{
|
||||||
|
"background": pick(ui, "global.background.normal", colors, "base00"),
|
||||||
|
"text": pick(ui, "global.foreground.normal", colors, "base05"),
|
||||||
|
"secondaryText": pick(ui, "global.foreground.dark", colors, "base04"),
|
||||||
|
"accent": pick(ui, "accent.normal", colors, "base0D"),
|
||||||
|
"glow": pick(ui, "status.error", colors, "base08"),
|
||||||
|
"keyBackground": pick(ui, "chrome.background.normal", colors, "base01"),
|
||||||
|
"error": pick(ui, "status.error", colors, "base08"),
|
||||||
|
}
|
||||||
|
validColor := regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
|
||||||
|
for key, value := range mapping {
|
||||||
|
if !validColor.MatchString(value) {
|
||||||
|
return fmt.Errorf("invalid Conjure color %s: %q", key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configHome := os.Getenv("XDG_CONFIG_HOME")
|
||||||
|
if !filepath.IsAbs(configHome) {
|
||||||
|
configHome = filepath.Join(homeDir(), ".config")
|
||||||
|
}
|
||||||
|
return writeJSON(filepath.Join(configHome, "conjure", "theme.json"), map[string]any{"variant": variant, "colors": mapping})
|
||||||
|
}
|
||||||
|
|
||||||
func writeOMPTheme(colors, ui, syntax map[string]string) error {
|
func writeOMPTheme(colors, ui, syntax map[string]string) error {
|
||||||
semantic, _ := semanticTheme(colors, ui, syntax)
|
semantic, _ := semanticTheme(colors, ui, syntax)
|
||||||
theme := map[string]any{
|
theme := map[string]any{
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCustomSchemeCanBeFavoritedAndAppliedToApps(t *testing.T) {
|
||||||
|
tinty, err := exec.LookPath("tinty")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("requires tinty 0.34+")
|
||||||
|
}
|
||||||
|
home := t.TempDir()
|
||||||
|
data := filepath.Join(home, "data")
|
||||||
|
bin := filepath.Join(home, "bin")
|
||||||
|
for _, dir := range []string{bin, filepath.Join(data, "repos", "schemes", "base16"), filepath.Join(data, "custom-schemes", "base16"), filepath.Join(home, ".config", "herdr")} {
|
||||||
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
palette, err := os.ReadFile("schemes/base16/pumpkin.yaml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
files := map[string][]byte{
|
||||||
|
filepath.Join(data, "custom-schemes", "base16", "pumpkin.yaml"): palette,
|
||||||
|
filepath.Join(home, "config.toml"): []byte("items = []\n"),
|
||||||
|
filepath.Join(home, ".config", "herdr", "config.toml"): []byte("[server]\nport = 8080\n"),
|
||||||
|
filepath.Join(home, "zed.json"): []byte(`{"name":"Pumpkin","themes":[{"name":"Pumpkin","appearance":"dark"}]}`),
|
||||||
|
}
|
||||||
|
for path, contents := range files {
|
||||||
|
if err := os.WriteFile(path, contents, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh
|
||||||
|
exec "$TINTY_TEST_BINARY" --data-dir "$TINTY_TEST_DATA" --config "$TINTY_TEST_CONFIG" "$@"
|
||||||
|
`)
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
||||||
|
t.Setenv("PATH", bin+":/usr/bin:/bin")
|
||||||
|
t.Setenv("TINTY_TEST_BINARY", tinty)
|
||||||
|
t.Setenv("TINTY_TEST_DATA", data)
|
||||||
|
t.Setenv("TINTY_TEST_CONFIG", filepath.Join(home, "config.toml"))
|
||||||
|
statePath := filepath.Join(home, "state.toml")
|
||||||
|
t.Setenv("TINT_STATE_FILE", statePath)
|
||||||
|
initial := state{Current: currentPair{Light: "personal-light", Dark: "personal-dark"}}
|
||||||
|
if err := saveState(statePath, initial); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
if err := runCLI([]string{"favorite", "base16-pumpkin"}, &stdout, &stderr); err != nil {
|
||||||
|
t.Fatalf("custom scheme could not be favorited: %v: %s", err, stderr.String())
|
||||||
|
}
|
||||||
|
value, err := loadState(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if value.Current != initial.Current || len(value.Catalog.FavoriteThemes) != 1 || value.Catalog.FavoriteThemes[0] != "base16-pumpkin" {
|
||||||
|
t.Fatalf("favorite changed the current pair or omitted the scheme: %#v", value)
|
||||||
|
}
|
||||||
|
before := readFile(t, statePath)
|
||||||
|
if err := runCLI([]string{"favorite", "base16-missing"}, &stdout, &stderr); err == nil {
|
||||||
|
t.Fatal("accepted a nonexistent custom scheme")
|
||||||
|
}
|
||||||
|
if readFile(t, statePath) != before {
|
||||||
|
t.Fatal("invalid scheme changed saved state")
|
||||||
|
}
|
||||||
|
if output, err := exec.Command("tinty", "apply", "base16-pumpkin").CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("Tinty could not apply the custom scheme: %v\n%s", err, output)
|
||||||
|
}
|
||||||
|
if err := applyApps(filepath.Join(home, "zed.json")); err != nil {
|
||||||
|
t.Fatalf("custom scheme could not be applied: %v", err)
|
||||||
|
}
|
||||||
|
var omp struct {
|
||||||
|
Colors map[string]string `json:"colors"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(home, ".omp", "agent", "themes", "tinty-current.json"))), &omp); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if omp.Colors["text"] != "#ffe3b5" || omp.Colors["syntaxNumber"] != "#f2943b" {
|
||||||
|
t.Fatalf("OMP did not receive the custom scheme: %#v", omp.Colors)
|
||||||
|
}
|
||||||
|
var conjure struct {
|
||||||
|
Variant string `json:"variant"`
|
||||||
|
Colors map[string]string `json:"colors"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(home, ".config", "conjure", "theme.json"))), &conjure); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if conjure.Variant != "dark" || conjure.Colors["background"] != "#1f1714" || conjure.Colors["secondaryText"] != "#d9c7a6" {
|
||||||
|
t.Fatalf("Conjure did not receive the custom scheme: %#v", conjure)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
[current]
|
||||||
|
light = "base16-swamp-light"
|
||||||
|
dark = "base16-darktooth"
|
||||||
|
|
||||||
|
[[favorites.pairs]]
|
||||||
|
name = "gruvbox-material-medium"
|
||||||
|
dark = "base16-gruvbox-material-dark-medium"
|
||||||
|
light = "base16-gruvbox-material-light-medium"
|
||||||
|
|
||||||
|
[[favorites.pairs]]
|
||||||
|
name = "gruvbox-pale"
|
||||||
|
dark = "base16-gruvbox-dark-pale"
|
||||||
|
light = "base16-gruvbox-light-soft"
|
||||||
|
|
||||||
|
[[favorites.pairs]]
|
||||||
|
name = "pumpkin"
|
||||||
|
dark = "base16-pumpkin"
|
||||||
|
light = "base16-pumpkin-light"
|
||||||
|
|
||||||
|
[[favorites.pairs]]
|
||||||
|
name = "swamp"
|
||||||
|
dark = "base16-swamp-dark"
|
||||||
|
light = "base16-swamp-light"
|
||||||
|
|
||||||
|
[favorites]
|
||||||
|
themes = [
|
||||||
|
"base16-0x96f",
|
||||||
|
"base16-arroz-con-dulce",
|
||||||
|
"base16-ascendancy",
|
||||||
|
"base16-atelier-plateau-light",
|
||||||
|
"base16-cacao",
|
||||||
|
"base16-caroline",
|
||||||
|
"base16-chinoiserie-midnight",
|
||||||
|
"base16-corduroy",
|
||||||
|
"base16-cupcake",
|
||||||
|
"base16-darktooth",
|
||||||
|
"base16-embers",
|
||||||
|
"base16-gruvbox-dark-pale",
|
||||||
|
"base16-pastelon-de-amarillos-dark",
|
||||||
|
"base16-precious-light-warm",
|
||||||
|
"base16-rose-pine-dawn",
|
||||||
|
"base16-selenized-light",
|
||||||
|
"base16-swamp-dark",
|
||||||
|
"base16-swamp-light",
|
||||||
|
"base24-ic-orange-ppl",
|
||||||
|
"base24-later-this-evening",
|
||||||
|
"tinted8-gruvbox-dark",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[others.pairs]]
|
||||||
|
name = "amarillos"
|
||||||
|
dark = "base16-pastelon-de-amarillos-dark"
|
||||||
|
light = "base16-pastelon-de-amarillos"
|
||||||
|
|
||||||
|
[[others.pairs]]
|
||||||
|
name = "charcoal"
|
||||||
|
dark = "base16-charcoal-dark"
|
||||||
|
light = "base16-charcoal-light"
|
||||||
|
|
||||||
|
[[others.pairs]]
|
||||||
|
name = "embers"
|
||||||
|
dark = "base16-embers"
|
||||||
|
light = "base16-embers-light"
|
||||||
|
|
||||||
|
[[others.pairs]]
|
||||||
|
name = "gruvbox-material-hard"
|
||||||
|
dark = "base16-gruvbox-material-dark-hard"
|
||||||
|
light = "base16-gruvbox-material-light-hard"
|
||||||
|
|
||||||
|
[[others.pairs]]
|
||||||
|
name = "gruvbox-material-soft"
|
||||||
|
dark = "base16-gruvbox-material-dark-soft"
|
||||||
|
light = "base16-gruvbox-material-light-soft"
|
||||||
|
|
||||||
|
[[others.pairs]]
|
||||||
|
name = "pale-swamp"
|
||||||
|
dark = "base16-gruvbox-dark-pale"
|
||||||
|
light = "base16-swamp-light"
|
||||||
|
|
||||||
|
[others]
|
||||||
|
themes = [
|
||||||
|
"base16-apprentice",
|
||||||
|
"base16-ashes",
|
||||||
|
"base16-atelier-heath",
|
||||||
|
"base16-atelier-heath-light",
|
||||||
|
"base16-black-metal",
|
||||||
|
"base16-black-metal-bathory",
|
||||||
|
"base16-black-metal-khold",
|
||||||
|
"base16-black-metal-mayhem",
|
||||||
|
"base16-chinoiserie-morandi",
|
||||||
|
"base16-corduroy-dark",
|
||||||
|
"base16-da-one-gray",
|
||||||
|
"base16-default-dark",
|
||||||
|
"base16-equilibrium-light",
|
||||||
|
"base16-helios",
|
||||||
|
"base16-kanagawa-dragon",
|
||||||
|
"base16-mezcal",
|
||||||
|
"base16-railscasts",
|
||||||
|
"base16-sandcastle",
|
||||||
|
"base16-tarot",
|
||||||
|
"base16-vesper",
|
||||||
|
"base16-zenbones",
|
||||||
|
"base24-alucard",
|
||||||
|
"base24-earthsong",
|
||||||
|
"base24-flexoki-light",
|
||||||
|
"base24-mountain",
|
||||||
|
"base24-pastelon-de-amarillos",
|
||||||
|
"base24-sleepy-hollow",
|
||||||
|
]
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInstallFromFreshCheckout(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
source := filepath.Join(tmp, "source")
|
||||||
|
bin := filepath.Join(tmp, "bin")
|
||||||
|
for _, dir := range []string{source, bin} {
|
||||||
|
if err := os.Mkdir(dir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries, err := os.ReadDir(".")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.Type().IsRegular() || entry.Name() == "state.toml" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(entry.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(source, entry.Name()), data, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.CopyFS(filepath.Join(source, "schemes"), os.DirFS("schemes")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh
|
||||||
|
case "$1" in
|
||||||
|
config) printf '%s/tinted-theming/tinty\n' "$XDG_DATA_HOME" ;;
|
||||||
|
sync|info) exit 0 ;;
|
||||||
|
apply) printf '%s\n' "$2" >> "$TINTY_LOG" ;;
|
||||||
|
*) exit 2 ;;
|
||||||
|
esac
|
||||||
|
`)
|
||||||
|
for _, name := range []string{"dark-notify", "launchctl"} {
|
||||||
|
writeExecutable(t, filepath.Join(bin, name), "#!/bin/sh\nexit 0\n")
|
||||||
|
}
|
||||||
|
home := filepath.Join(tmp, "home")
|
||||||
|
config := filepath.Join(home, ".config")
|
||||||
|
log := filepath.Join(tmp, "applied")
|
||||||
|
cache, err := exec.Command("go", "env", "GOMODCACHE").Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Setenv("GOMODCACHE", strings.TrimSpace(string(cache)))
|
||||||
|
t.Setenv("PATH", bin+":"+os.Getenv("PATH"))
|
||||||
|
t.Setenv("TINTY_LOG", log)
|
||||||
|
t.Setenv("XDG_DATA_HOME", filepath.Join(tmp, "data"))
|
||||||
|
install := func() {
|
||||||
|
t.Helper()
|
||||||
|
cmd := exec.Command("make", "install", "HOME="+home, "XDG_CONFIG_HOME="+config)
|
||||||
|
cmd.Dir = source
|
||||||
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("install failed: %v\n%s", err, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
install()
|
||||||
|
statePath := filepath.Join(config, "tinted-theming", "tinty", "state.toml")
|
||||||
|
value, err := loadState(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("fresh install did not provide usable state: %v", err)
|
||||||
|
}
|
||||||
|
cmd := exec.Command(filepath.Join(home, ".local", "bin", "tint"), "swamp")
|
||||||
|
cmd.Env = append(os.Environ(), "TINT_STATE_FILE="+statePath, "TINTY_PAIR_APPEARANCE=dark")
|
||||||
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("selecting an installed pair failed: %v\n%s", err, output)
|
||||||
|
}
|
||||||
|
if got := strings.TrimSpace(readFile(t, log)); got != "base16-swamp-dark" {
|
||||||
|
t.Fatalf("applied theme = %q, want base16-swamp-dark", got)
|
||||||
|
}
|
||||||
|
value.Current = currentPair{Light: "personal-light", Dark: "personal-dark"}
|
||||||
|
if err := saveState(statePath, value); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
before, err := os.ReadFile(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
install()
|
||||||
|
after, err := os.ReadFile(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(before, after) {
|
||||||
|
t.Fatal("reinstall overwrote personal state")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -192,6 +192,11 @@ func validatePair(pair currentPair, stderr io.Writer) error {
|
|||||||
func validateTheme(theme string, stderr io.Writer) error {
|
func validateTheme(theme string, stderr io.Writer) error {
|
||||||
cmd := exec.Command("tinty", "info", theme)
|
cmd := exec.Command("tinty", "info", theme)
|
||||||
cmd.Stdout = io.Discard
|
cmd.Stdout = io.Discard
|
||||||
|
if err := cmd.Run(); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cmd = exec.Command("tinty", "info", theme, "--custom-schemes")
|
||||||
|
cmd.Stdout = io.Discard
|
||||||
cmd.Stderr = stderr
|
cmd.Stderr = stderr
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
+137
@@ -138,6 +138,7 @@ func TestApplyAppsUsesTinted8Semantics(t *testing.T) {
|
|||||||
"black-normal": {Hex: "#000000"}, "black-bright": {Hex: "#111111"},
|
"black-normal": {Hex: "#000000"}, "black-bright": {Hex: "#111111"},
|
||||||
"gray-dim": {Hex: "#222222"}, "gray-normal": {Hex: "#333333"}, "gray-bright": {Hex: "#444444"},
|
"gray-dim": {Hex: "#222222"}, "gray-normal": {Hex: "#333333"}, "gray-bright": {Hex: "#444444"},
|
||||||
"white-normal": {Hex: "#eeeeee"}, "white-bright": {Hex: "#ffffff"},
|
"white-normal": {Hex: "#eeeeee"}, "white-bright": {Hex: "#ffffff"},
|
||||||
|
"black-dim": {Hex: "#111111"}, "white-dim": {Hex: "#dddddd"},
|
||||||
"red-normal": {Hex: "#aa0000"}, "orange-normal": {Hex: "#bb5500"},
|
"red-normal": {Hex: "#aa0000"}, "orange-normal": {Hex: "#bb5500"},
|
||||||
"yellow-normal": {Hex: "#aaaa00"}, "green-normal": {Hex: "#00aa00"},
|
"yellow-normal": {Hex: "#aaaa00"}, "green-normal": {Hex: "#00aa00"},
|
||||||
"cyan-normal": {Hex: "#00aaaa"}, "blue-normal": {Hex: "#0000aa"},
|
"cyan-normal": {Hex: "#00aaaa"}, "blue-normal": {Hex: "#0000aa"},
|
||||||
@@ -167,6 +168,7 @@ case "$1" in
|
|||||||
esac
|
esac
|
||||||
`)
|
`)
|
||||||
t.Setenv("HOME", tmp)
|
t.Setenv("HOME", tmp)
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", filepath.Join(tmp, "xdg"))
|
||||||
t.Setenv("PATH", bin+":"+os.Getenv("PATH"))
|
t.Setenv("PATH", bin+":"+os.Getenv("PATH"))
|
||||||
t.Setenv("TINTY_TEST_SCHEME", scheme.ID)
|
t.Setenv("TINTY_TEST_SCHEME", scheme.ID)
|
||||||
t.Setenv("TINTY_TEST_LIST", listPath)
|
t.Setenv("TINTY_TEST_LIST", listPath)
|
||||||
@@ -180,6 +182,14 @@ esac
|
|||||||
if err := os.WriteFile(zed, []byte(`{"name":"old","themes":[{"name":"old"}]}`), 0o644); err != nil {
|
if err := os.WriteFile(zed, []byte(`{"name":"old","themes":[{"name":"old"}]}`), 0o644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
conjureConfig := filepath.Join(tmp, "xdg", "conjure", "config.toml")
|
||||||
|
if err := os.MkdirAll(filepath.Dir(conjureConfig), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
const userConfig = "theme = \"tint\"\n\n[colors]\naccent = \"#abcdef\"\n"
|
||||||
|
if err := os.WriteFile(conjureConfig, []byte(userConfig), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := applyApps(zed); err != nil {
|
if err := applyApps(zed); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -201,6 +211,58 @@ esac
|
|||||||
if !strings.Contains(zedOutput, `"name": "Tinty Current"`) {
|
if !strings.Contains(zedOutput, `"name": "Tinty Current"`) {
|
||||||
t.Fatalf("Zed theme was not renamed:\n%s", zedOutput)
|
t.Fatalf("Zed theme was not renamed:\n%s", zedOutput)
|
||||||
}
|
}
|
||||||
|
var conjure struct {
|
||||||
|
Variant string `json:"variant"`
|
||||||
|
Colors map[string]string `json:"colors"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(filepath.Dir(conjureConfig), "theme.json"))), &conjure); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if conjure.Variant != "dark" {
|
||||||
|
t.Fatalf("Conjure variant = %q, want dark", conjure.Variant)
|
||||||
|
}
|
||||||
|
expected := map[string]string{
|
||||||
|
"background": "#010101", "text": "#fefefe", "secondaryText": "#444444",
|
||||||
|
"accent": "#123456", "glow": "#aa0000", "keyBackground": "#111111", "error": "#aa0000",
|
||||||
|
}
|
||||||
|
if len(conjure.Colors) != len(expected) {
|
||||||
|
t.Fatalf("wrong Conjure roles: %#v", conjure.Colors)
|
||||||
|
}
|
||||||
|
for key, value := range expected {
|
||||||
|
if conjure.Colors[key] != value {
|
||||||
|
t.Errorf("Conjure %s = %q, want %q", key, conjure.Colors[key], value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if readFile(t, conjureConfig) != userConfig {
|
||||||
|
t.Fatal("Conjure user configuration changed")
|
||||||
|
}
|
||||||
|
scheme.Variant = "light"
|
||||||
|
expected["secondaryText"] = "#222222"
|
||||||
|
expected["keyBackground"] = "#dddddd"
|
||||||
|
data, err = json.Marshal([]tintyScheme{scheme})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(listPath, data, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := applyApps(zed); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(filepath.Dir(conjureConfig), "theme.json"))), &conjure); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if conjure.Variant != "light" {
|
||||||
|
t.Fatalf("Conjure variant = %q, want light", conjure.Variant)
|
||||||
|
}
|
||||||
|
for key, value := range expected {
|
||||||
|
if conjure.Colors[key] != value {
|
||||||
|
t.Errorf("light Conjure %s = %q, want %q", key, conjure.Colors[key], value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if readFile(t, conjureConfig) != userConfig {
|
||||||
|
t.Fatal("Conjure user configuration changed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBase16SemanticFallback(t *testing.T) {
|
func TestBase16SemanticFallback(t *testing.T) {
|
||||||
@@ -226,6 +288,81 @@ func TestBase16SemanticFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConjureThemeFallbacks(t *testing.T) {
|
||||||
|
for _, system := range []string{"base16", "base24"} {
|
||||||
|
for _, xdg := range []string{"", "relative", "absolute"} {
|
||||||
|
t.Run(system+"/"+xdg, func(t *testing.T) {
|
||||||
|
home := t.TempDir()
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
configHome := filepath.Join(home, ".config")
|
||||||
|
value := xdg
|
||||||
|
if xdg == "absolute" {
|
||||||
|
value = filepath.Join(home, "xdg")
|
||||||
|
configHome = value
|
||||||
|
}
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", value)
|
||||||
|
palette := make(map[string]tintyColor)
|
||||||
|
for i := range 24 {
|
||||||
|
palette[fmt.Sprintf("base%02X", i)] = tintyColor{Hex: fmt.Sprintf("#%06x", i)}
|
||||||
|
}
|
||||||
|
colors, err := semanticColors(tintyScheme{System: system, Palette: palette})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writeConjureTheme("dark", colors, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var theme struct {
|
||||||
|
Colors map[string]string `json:"colors"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(configHome, "conjure", "theme.json"))), &theme); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for key, base := range map[string]string{
|
||||||
|
"background": "base00", "text": "base05", "secondaryText": "base04",
|
||||||
|
"accent": "base0D", "glow": "base08", "keyBackground": "base01", "error": "base08",
|
||||||
|
} {
|
||||||
|
if theme.Colors[key] != colors[base] {
|
||||||
|
t.Errorf("%s = %q, want %q", key, theme.Colors[key], colors[base])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConjureThemeRejectsInvalidColors(t *testing.T) {
|
||||||
|
t.Setenv("HOME", t.TempDir())
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", "")
|
||||||
|
for _, value := range []string{"", "#12345", "#12345678", "#gggggg", "#123456"} {
|
||||||
|
t.Run(value, func(t *testing.T) {
|
||||||
|
colors := map[string]string{
|
||||||
|
"base00": "#000000", "base01": "#111111", "base04": "#444444",
|
||||||
|
"base05": "#555555", "base08": "#880000", "base0D": value,
|
||||||
|
}
|
||||||
|
if err := writeConjureTheme("dark", colors, nil); err == nil {
|
||||||
|
t.Fatalf("accepted invalid accent %q", value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConjureThemeRejectsInvalidVariants(t *testing.T) {
|
||||||
|
t.Setenv("HOME", t.TempDir())
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", "")
|
||||||
|
colors := map[string]string{
|
||||||
|
"base00": "#000000", "base01": "#111111", "base04": "#444444",
|
||||||
|
"base05": "#555555", "base08": "#880000", "base0D": "#0000dd",
|
||||||
|
}
|
||||||
|
for _, variant := range []string{"", "auto", "Light", "DARK"} {
|
||||||
|
t.Run(variant, func(t *testing.T) {
|
||||||
|
if err := writeConjureTheme(variant, colors, nil); err == nil {
|
||||||
|
t.Fatalf("accepted invalid variant %q", variant)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func writeExecutable(t *testing.T, path, content string) {
|
func writeExecutable(t *testing.T, path, content string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err := os.WriteFile(path, []byte(content), 0o755); err != nil {
|
if err := os.WriteFile(path, []byte(content), 0o755); err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Matching light companion to Pumpkin; deeper accents for parchment backgrounds.
|
||||||
|
# Styling roles: https://github.com/tinted-theming/home/blob/main/styling.md
|
||||||
|
system: "base16"
|
||||||
|
name: "Pumpkin Light"
|
||||||
|
author: "Terebi"
|
||||||
|
variant: "light"
|
||||||
|
palette:
|
||||||
|
base00: "#fff6e6" # Parchment background
|
||||||
|
base01: "#f4e6ce" # Raised surface
|
||||||
|
base02: "#e6ceaa" # Selection
|
||||||
|
base03: "#7c624e" # Comments / muted text
|
||||||
|
base04: "#70543f" # Secondary foreground
|
||||||
|
base05: "#453126" # Brown foreground
|
||||||
|
base06: "#30221b" # Deep foreground
|
||||||
|
base07: "#1f1714" # Darkest foreground
|
||||||
|
base08: "#b13425" # Red — variables / errors / deletions
|
||||||
|
base09: "#a64b00" # Burnt orange — numbers / constants
|
||||||
|
base0A: "#8a621c" # Toasted amber — types / search
|
||||||
|
base0B: "#52651f" # Olive — strings / additions
|
||||||
|
base0C: "#995b2b" # Caramel — escapes / support / inverted terminal bars
|
||||||
|
base0D: "#b34d00" # Pumpkin orange — functions / headings / UI accent
|
||||||
|
base0E: "#b63d24" # Persimmon — keywords
|
||||||
|
base0F: "#c2452e" # Ember — deprecated
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Palette source: terebi/Themes/pumpkin.yaml (sibling project).
|
||||||
|
# App colors are rounded to 8-bit sRGB; the translucent backdrop becomes opaque.
|
||||||
|
# Styling roles: https://github.com/tinted-theming/home/blob/main/styling.md
|
||||||
|
system: "base16"
|
||||||
|
name: "Pumpkin"
|
||||||
|
author: "Terebi"
|
||||||
|
variant: "dark"
|
||||||
|
palette:
|
||||||
|
base00: "#1f1714" # Background — app backdrop, without alpha
|
||||||
|
base01: "#2c221c" # Raised background / status bars
|
||||||
|
base02: "#45352b" # Selection
|
||||||
|
base03: "#9b8775" # Comments / muted text
|
||||||
|
base04: "#d9c7a6" # Secondary foreground — app secondaryText
|
||||||
|
base05: "#ffe3b5" # Foreground — app text
|
||||||
|
base06: "#ffedcf" # Light foreground
|
||||||
|
base07: "#fff6e6" # Lightest foreground
|
||||||
|
base08: "#e07864" # Red — variables / errors / deletions
|
||||||
|
base09: "#f2943b" # Orange — numbers / constants; app accent
|
||||||
|
base0A: "#e6bc65" # Yellow — types / search
|
||||||
|
base0B: "#a9b665" # Green — strings / additions
|
||||||
|
base0C: "#d6c56b" # Harvest gold — escapes / support
|
||||||
|
base0D: "#ff9e3b" # Pumpkin orange — functions / headings / UI accent
|
||||||
|
base0E: "#ef7956" # Persimmon — keywords
|
||||||
|
base0F: "#c2452e" # Brown/red — deprecated; app ember (3.51:1 on base00)
|
||||||
Reference in New Issue
Block a user