Skip to content

Instantly share code, notes, and snippets.

@melonamin
Created August 13, 2025 17:57
Show Gist options
  • Select an option

  • Save melonamin/650adaa0f460da1112ced797f71a6d6c to your computer and use it in GitHub Desktop.

Select an option

Save melonamin/650adaa0f460da1112ced797f71a6d6c to your computer and use it in GitHub Desktop.
Generate ZED theme from Alacrity
#!/usr/bin/env python3
import sys
import os
import json
import tomllib
from pathlib import Path
def normalize_color(color):
"""Convert 0xRRGGBB or #RRGGBB to #RRGGBB format"""
if isinstance(color, str):
if color.startswith('0x'):
return '#' + color[2:]
return color
return color
def parse_alacritty_colors(toml_path):
"""Parse alacritty.toml and extract colors"""
with open(toml_path, 'rb') as f:
data = tomllib.load(f)
colors = data.get('colors', {})
# Normalize all color values
result = {}
for section in ['primary', 'normal', 'bright', 'cursor', 'selection', 'search']:
section_data = colors.get(section, {})
if section_data:
result[section] = {k: normalize_color(v) for k, v in section_data.items()}
else:
result[section] = {}
return result
def generate_zed_theme(theme_name, alacritty_colors, is_light=False):
"""Generate Zed theme JSON from alacritty colors"""
# Extract main colors
bg = alacritty_colors['primary'].get('background', '#1e1e1e')
fg = alacritty_colors['primary'].get('foreground', '#d4d4d4')
# Normal colors
normal = alacritty_colors['normal']
bright = alacritty_colors['bright']
# Calculate UI colors based on background
# Parse hex to RGB
bg_hex = bg.lstrip('#')
bg_rgb = tuple(int(bg_hex[i:i+2], 16) for i in (0, 2, 4))
# Create variations of background for UI elements
def darken(hex_color, factor=0.8):
hex_color = hex_color.lstrip('#')
rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
rgb = tuple(int(c * factor) for c in rgb)
return '#{:02x}{:02x}{:02x}'.format(*rgb)
def lighten(hex_color, factor=1.2):
hex_color = hex_color.lstrip('#')
rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
rgb = tuple(min(255, int(c * factor)) for c in rgb)
return '#{:02x}{:02x}{:02x}'.format(*rgb)
def with_alpha(hex_color, alpha):
return f"{hex_color}{alpha}"
# UI color variations
border_color = darken(bg, 0.7) if not is_light else lighten(bg, 1.1)
surface_color = darken(bg, 0.9) if not is_light else lighten(bg, 1.05)
hover_color = lighten(bg, 1.2) if not is_light else darken(bg, 0.95)
active_color = lighten(bg, 1.4) if not is_light else darken(bg, 0.9)
# Selection and cursor colors
cursor_color = alacritty_colors['cursor'].get('cursor', normal.get('yellow', '#ffff00'))
selection_bg = alacritty_colors['selection'].get('background', lighten(bg, 1.5))
# Muted text color (between foreground and background)
muted_factor = 0.7
fg_hex = fg.lstrip('#')
fg_rgb = tuple(int(fg_hex[i:i+2], 16) for i in (0, 2, 4))
muted_rgb = tuple(int(bg_rgb[i] + (fg_rgb[i] - bg_rgb[i]) * muted_factor) for i in range(3))
muted_color = '#{:02x}{:02x}{:02x}'.format(*muted_rgb)
theme = {
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": f"Omarchy {theme_name.replace('-', ' ').title()}",
"author": "Omarchy",
"themes": [
{
"name": f"Omarchy {theme_name.replace('-', ' ').title()}",
"appearance": "light" if is_light else "dark",
"style": {
# Base colors
"background": bg,
"foreground": fg,
"border": border_color,
"border.variant": surface_color,
"border.focused": normal.get('blue', '#0000ff'),
"border.selected": normal.get('blue', '#0000ff'),
"border.transparent": "#00000000",
"border.disabled": surface_color,
# Surfaces
"elevated_surface.background": border_color,
"surface.background": bg,
"drop_target.background": with_alpha(surface_color, "80"),
# Ghost elements (invisible interactive elements)
"ghost_element.background": "#00000000",
"ghost_element.hover": hover_color,
"ghost_element.active": active_color,
"ghost_element.selected": active_color,
"ghost_element.disabled": surface_color,
# Text colors
"text": fg,
"text.muted": muted_color,
"text.placeholder": muted_color,
"text.disabled": muted_color,
"text.accent": normal.get('blue', '#0000ff'),
# Icons
"icon": fg,
"icon.muted": muted_color,
"icon.disabled": muted_color,
"icon.placeholder": muted_color,
"icon.accent": normal.get('blue', '#0000ff'),
# UI elements
"status_bar.background": border_color,
"title_bar.background": border_color,
"toolbar.background": bg,
"tab_bar.background": border_color,
"tab.inactive_background": border_color,
"tab.active_background": bg,
"search.match_background": surface_color,
"panel.background": border_color,
"panel.focused_border": normal.get('blue', '#0000ff'),
"pane.focused_border": normal.get('blue', '#0000ff'),
# Scrollbar
"scrollbar.thumb.background": with_alpha(surface_color, "88"),
"scrollbar.thumb.hover_background": hover_color,
"scrollbar.thumb.border": with_alpha(surface_color, "44"),
"scrollbar.track.background": border_color,
"scrollbar.track.border": darken(border_color, 0.8),
# Editor
"editor.foreground": fg,
"editor.background": bg,
"editor.gutter.background": bg,
"editor.subheader.background": border_color,
"editor.active_line.background": surface_color,
"editor.highlighted_line.background": surface_color,
"editor.line_number": muted_color,
"editor.active_line_number": fg,
"editor.invisible": muted_color,
"editor.wrap_guide": surface_color,
"editor.active_wrap_guide": muted_color,
"editor.document_highlight.read_background": with_alpha(surface_color, "44"),
"editor.document_highlight.write_background": with_alpha(surface_color, "66"),
# Terminal colors
"terminal.background": bg,
"terminal.foreground": fg,
"terminal.bright_foreground": fg,
"terminal.dim_foreground": fg,
"terminal.ansi.black": normal.get('black', '#000000'),
"terminal.ansi.bright_black": bright.get('black', '#808080'),
"terminal.ansi.dim_black": normal.get('black', '#000000'),
"terminal.ansi.red": normal.get('red', '#ff0000'),
"terminal.ansi.bright_red": bright.get('red', '#ff8080'),
"terminal.ansi.dim_red": normal.get('red', '#ff0000'),
"terminal.ansi.green": normal.get('green', '#00ff00'),
"terminal.ansi.bright_green": bright.get('green', '#80ff80'),
"terminal.ansi.dim_green": normal.get('green', '#00ff00'),
"terminal.ansi.yellow": normal.get('yellow', '#ffff00'),
"terminal.ansi.bright_yellow": bright.get('yellow', '#ffff80'),
"terminal.ansi.dim_yellow": normal.get('yellow', '#ffff00'),
"terminal.ansi.blue": normal.get('blue', '#0000ff'),
"terminal.ansi.bright_blue": bright.get('blue', '#8080ff'),
"terminal.ansi.dim_blue": normal.get('blue', '#0000ff'),
"terminal.ansi.magenta": normal.get('magenta', '#ff00ff'),
"terminal.ansi.bright_magenta": bright.get('magenta', '#ff80ff'),
"terminal.ansi.dim_magenta": normal.get('magenta', '#ff00ff'),
"terminal.ansi.cyan": normal.get('cyan', '#00ffff'),
"terminal.ansi.bright_cyan": bright.get('cyan', '#80ffff'),
"terminal.ansi.dim_cyan": normal.get('cyan', '#00ffff'),
"terminal.ansi.white": normal.get('white', '#ffffff'),
"terminal.ansi.bright_white": bright.get('white', '#ffffff'),
"terminal.ansi.dim_white": normal.get('white', '#ffffff'),
# Links
"link_text.hover": normal.get('blue', '#0000ff'),
# Git colors
"conflict": normal.get('yellow', '#ffff00'),
"conflict.background": with_alpha(normal.get('yellow', '#ffff00'), "20"),
"conflict.border": normal.get('yellow', '#ffff00'),
"created": normal.get('green', '#00ff00'),
"created.background": with_alpha(normal.get('green', '#00ff00'), "20"),
"created.border": normal.get('green', '#00ff00'),
"deleted": normal.get('red', '#ff0000'),
"deleted.background": with_alpha(normal.get('red', '#ff0000'), "20"),
"deleted.border": normal.get('red', '#ff0000'),
"error": normal.get('red', '#ff0000'),
"error.background": with_alpha(normal.get('red', '#ff0000'), "20"),
"error.border": normal.get('red', '#ff0000'),
"hidden": muted_color,
"hidden.background": bg,
"hidden.border": surface_color,
"hint": muted_color,
"hint.background": with_alpha(normal.get('blue', '#0000ff'), "20"),
"hint.border": normal.get('blue', '#0000ff'),
"ignored": muted_color,
"ignored.background": bg,
"ignored.border": surface_color,
"info": normal.get('blue', '#0000ff'),
"info.background": with_alpha(normal.get('blue', '#0000ff'), "20"),
"info.border": normal.get('blue', '#0000ff'),
"modified": normal.get('yellow', '#ffff00'),
"modified.background": with_alpha(normal.get('yellow', '#ffff00'), "20"),
"modified.border": normal.get('yellow', '#ffff00'),
"predictive": muted_color,
"predictive.background": with_alpha(muted_color, "20"),
"predictive.border": muted_color,
"renamed": normal.get('blue', '#0000ff'),
"renamed.background": with_alpha(normal.get('blue', '#0000ff'), "20"),
"renamed.border": normal.get('blue', '#0000ff'),
"success": normal.get('green', '#00ff00'),
"success.background": with_alpha(normal.get('green', '#00ff00'), "20"),
"success.border": normal.get('green', '#00ff00'),
"unreachable": muted_color,
"unreachable.background": bg,
"unreachable.border": surface_color,
"warning": normal.get('yellow', '#ffff00'),
"warning.background": with_alpha(normal.get('yellow', '#ffff00'), "20"),
"warning.border": normal.get('yellow', '#ffff00'),
# Collaboration colors
"players": [
{
"cursor": cursor_color,
"background": cursor_color,
"selection": with_alpha(cursor_color, "20")
},
{
"cursor": normal.get('magenta', '#ff00ff'),
"background": normal.get('magenta', '#ff00ff'),
"selection": with_alpha(normal.get('magenta', '#ff00ff'), "20")
},
{
"cursor": normal.get('cyan', '#00ffff'),
"background": normal.get('cyan', '#00ffff'),
"selection": with_alpha(normal.get('cyan', '#00ffff'), "20")
},
{
"cursor": normal.get('yellow', '#ffff00'),
"background": normal.get('yellow', '#ffff00'),
"selection": with_alpha(normal.get('yellow', '#ffff00'), "20")
}
],
# Syntax highlighting
"syntax": {
"attribute": {
"color": normal.get('yellow', '#ffff00'),
"font_style": None,
"font_weight": None
},
"boolean": {
"color": bright.get('red', '#ff8080'),
"font_style": None,
"font_weight": None
},
"comment": {
"color": muted_color,
"font_style": "italic",
"font_weight": None
},
"comment.doc": {
"color": muted_color,
"font_style": "italic",
"font_weight": None
},
"constant": {
"color": bright.get('red', '#ff8080'),
"font_style": None,
"font_weight": None
},
"constructor": {
"color": normal.get('magenta', '#ff00ff'),
"font_style": None,
"font_weight": None
},
"embedded": {
"color": fg,
"font_style": None,
"font_weight": None
},
"emphasis": {
"color": normal.get('red', '#ff0000'),
"font_style": "italic",
"font_weight": None
},
"emphasis.strong": {
"color": normal.get('red', '#ff0000'),
"font_style": None,
"font_weight": 700
},
"enum": {
"color": normal.get('cyan', '#00ffff'),
"font_style": None,
"font_weight": None
},
"function": {
"color": normal.get('blue', '#0000ff'),
"font_style": None,
"font_weight": None
},
"hint": {
"color": normal.get('cyan', '#00ffff'),
"font_style": None,
"font_weight": 700
},
"keyword": {
"color": normal.get('magenta', '#ff00ff'),
"font_style": None,
"font_weight": None
},
"label": {
"color": normal.get('blue', '#0000ff'),
"font_style": None,
"font_weight": None
},
"link_text": {
"color": normal.get('blue', '#0000ff'),
"font_style": "italic",
"font_weight": None
},
"link_uri": {
"color": normal.get('magenta', '#ff00ff'),
"font_style": None,
"font_weight": None
},
"number": {
"color": bright.get('red', '#ff8080'),
"font_style": None,
"font_weight": None
},
"operator": {
"color": normal.get('cyan', '#00ffff'),
"font_style": None,
"font_weight": None
},
"predictive": {
"color": muted_color,
"font_style": "italic",
"font_weight": None
},
"preproc": {
"color": fg,
"font_style": None,
"font_weight": None
},
"primary": {
"color": fg,
"font_style": None,
"font_weight": None
},
"property": {
"color": normal.get('blue', '#0000ff'),
"font_style": None,
"font_weight": None
},
"punctuation": {
"color": muted_color,
"font_style": None,
"font_weight": None
},
"punctuation.bracket": {
"color": muted_color,
"font_style": None,
"font_weight": None
},
"punctuation.delimiter": {
"color": muted_color,
"font_style": None,
"font_weight": None
},
"punctuation.list_marker": {
"color": muted_color,
"font_style": None,
"font_weight": None
},
"punctuation.special": {
"color": normal.get('cyan', '#00ffff'),
"font_style": None,
"font_weight": None
},
"string": {
"color": normal.get('green', '#00ff00'),
"font_style": None,
"font_weight": None
},
"string.escape": {
"color": normal.get('magenta', '#ff00ff'),
"font_style": None,
"font_weight": None
},
"string.regex": {
"color": normal.get('cyan', '#00ffff'),
"font_style": None,
"font_weight": None
},
"string.special": {
"color": normal.get('magenta', '#ff00ff'),
"font_style": None,
"font_weight": None
},
"string.special.symbol": {
"color": normal.get('green', '#00ff00'),
"font_style": None,
"font_weight": None
},
"tag": {
"color": normal.get('blue', '#0000ff'),
"font_style": None,
"font_weight": None
},
"text.literal": {
"color": normal.get('green', '#00ff00'),
"font_style": None,
"font_weight": None
},
"title": {
"color": normal.get('blue', '#0000ff'),
"font_style": None,
"font_weight": 700
},
"type": {
"color": normal.get('yellow', '#ffff00'),
"font_style": None,
"font_weight": None
},
"variable": {
"color": fg,
"font_style": None,
"font_weight": None
},
"variable.special": {
"color": normal.get('red', '#ff0000'),
"font_style": None,
"font_weight": None
},
"variant": {
"color": normal.get('blue', '#0000ff'),
"font_style": None,
"font_weight": None
}
}
}
}
]
}
return theme
def main():
if len(sys.argv) > 1:
theme_dir = Path(sys.argv[1])
else:
# Process all themes
themes_dir = Path(os.path.expanduser("~/.local/share/omarchy/themes"))
if not themes_dir.exists():
print(f"Themes directory not found: {themes_dir}")
sys.exit(1)
for theme_dir in themes_dir.iterdir():
if theme_dir.is_dir():
process_theme(theme_dir)
return
process_theme(theme_dir)
def process_theme(theme_dir):
theme_name = theme_dir.name
alacritty_path = theme_dir / "alacritty.toml"
if not alacritty_path.exists():
print(f"Skipping {theme_name}: No alacritty.toml found")
return
# Check if it's a light theme
is_light = (theme_dir / "light.mode").exists()
try:
colors = parse_alacritty_colors(alacritty_path)
theme_json = generate_zed_theme(theme_name, colors, is_light)
output_path = theme_dir / "zed.json"
with open(output_path, 'w') as f:
json.dump(theme_json, f, indent=2)
print(f"✅ Generated Zed theme for {theme_name} at {output_path}")
except Exception as e:
print(f"❌ Failed to generate theme for {theme_name}: {e}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment