Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
All notable changes to the equisdots theme-sync engine are documented here.
Dates use YYYY-MM-DD.

## [2026-10-25]

### Fixed
- VS Code target: process an installed Code / Code - Insiders variant even when
its `User/settings.json` does not exist yet (fresh profile). The file is
created with the generated theme instead of the target silently doing
nothing. This was why VSCode stayed unthemed on a fresh niri profile while
working where the file already existed.
- `scripts/check.sh`: temp-home fixture that asserts the missing settings file
is created and contains the managed theme block.

## [2026-09-30]

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Defaults target the equisdots desktop layout:
| `kitty` | `~/.config/kitty/themes/<slug>.conf` + include and active border in `kitty.conf` |
| `starship` | per-palette themes + fixed `~/.config/starship.toml` + `STARSHIP_CONFIG` in `.zshrc`/`.bashrc` |
| `xtop` | `~/.config/xtop/themes/<slug>.jsonc` + active theme (`xtop --ct`) |
| `vscode` | `workbench.colorTheme` / `workbench.iconTheme` for palettes bundled in the xscriptor-themes extension; any other palette (community/, user-created) gets an intelligent live tint via generated `colorCustomizations` + token/semantic colors, in Code / Code - Insiders |
| `vscode` | `workbench.colorTheme` / `workbench.iconTheme` for palettes bundled in the xscriptor-themes extension; any other palette (community/, user-created) gets an intelligent live tint via generated `colorCustomizations` + token/semantic colors, in Code / Code - Insiders. Creates `User/settings.json` when the editor is installed but the file is missing (fresh profile). |
| `nvim` | `lua/themes/palettes.lua` + active-theme bootstrap |
| `browsers` | Brave/Beta prefs (`color_scheme2`, accent) + Firefox `user.js` |
| `opencode` | `~/.config/opencode/themes/<slug>.json` + active theme in `tui.json` |
Expand Down
33 changes: 33 additions & 0 deletions scripts/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,39 @@ PY
echo "vscode mapping FAIL"
fail=1
fi

# vscode fresh-profile: an installed variant with no settings.json must be
# created and themed instead of silently skipped.
tmp_home="$(mktemp -d)"
mkdir -p "$tmp_home/.config/Code - Insiders/User"
if ! PYTHONPATH="$PWD" python3 - "$PALETTES" "$tmp_home" <<'PY'
import sys
from pathlib import Path
from themesync.core import Env, load_palettes
from themesync.targets import vscode

home = Path(sys.argv[2])
pals = load_palettes(Path(sys.argv[1]))
pal = pals[0]
env = Env(home=home, palettes_dir=Path(sys.argv[1]),
settings_path=home / ".config/hypr/settings.json",
slug=pal.get("slug"), palettes=pals, palette=pal)
vscode.apply(env)
path = home / ".config/Code - Insiders/User/settings.json"
if not path.is_file():
print("vscode create: settings.json missing")
sys.exit(1)
text = path.read_text()
if '"workbench.colorTheme"' not in text or "equisdots theme-sync" not in text:
print("vscode create: theme block missing")
sys.exit(1)
print("vscode create: OK")
PY
then
echo "vscode create FAIL"
fail=1
fi
rm -rf "$tmp_home"
else
echo "note: no palette source found, CLI smoke test skipped" >&2
fi
Expand Down
28 changes: 21 additions & 7 deletions themesync/targets/vscode.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,22 @@


def available(env) -> bool:
# Always: if no variant is installed, apply() does nothing.
# Always: apply() decides per variant whether the editor is installed.
return True


def _variant_present(env, rel: str, vendor: str) -> bool:
"""Is this editor variant installed (settings file, user dir or binary)?"""
path = Path(env.home) / rel
if path.is_file() or path.parent.is_dir():
return True
ext = Path(env.home) / EXT_DIRS.get(vendor, "")
if ext.is_dir():
return True
binary = "code-insiders" if vendor == "Code - Insiders" else "code"
return env.binary(binary) is not None


# ── native theme discovery (dynamic; no palette list) ─────────────────────────

def _native(env, slug: str, vendor: str):
Expand Down Expand Up @@ -172,11 +184,12 @@ def apply(env) -> list:
out = []
for rel in VARIANTS:
path = env.home / rel
if not path.is_file():
continue
vendor = Path(rel).parent.parent.name # "Code" / "Code - Insiders"
original = path.read_text(encoding="utf-8", errors="replace")
text = _strip_block(original)
if not _variant_present(env, rel, vendor):
continue
created = not path.is_file()
original = "" if created else path.read_text(encoding="utf-8", errors="replace")
text = "{\n}\n" if not original.strip() else _strip_block(original)

display = _native(env, env.slug, vendor)
icon = _icon(env, env.slug, vendor)
Expand Down Expand Up @@ -214,7 +227,8 @@ def apply(env) -> list:
"; skipped user keys: %s" % ", ".join(skipped) if skipped else "",
)

if text != original:
if created or text != original:
env.write(path, text)
out.append("vscode (%s) → %s" % (vendor, note))
suffix = " [created]" if created else ""
out.append("vscode (%s) → %s%s" % (vendor, note, suffix))
return out
Loading