Created
August 13, 2025 17:57
-
-
Save melonamin/650adaa0f460da1112ced797f71a6d6c to your computer and use it in GitHub Desktop.
Generate ZED theme from Alacrity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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