From b0393b8e851f3f4e8355f0e513e9129bfc115611 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 7 Jun 2026 16:45:31 -0500 Subject: feat(theme-selector): add browser-based theme design tool A self-contained tool for building Emacs color themes by eye. generate.py emits one HTML page with six languages of tree-sitter-tokenized code, a category-to-color assignment table, a UI-faces table, and an editable palette. Reassign colors from the palette, toggle weight and slant per category, set foreground and background per UI face, then export a theme.json a later build step turns into theme files. The export carries the name, palette, syntax assignments, bold and italic sets, and a ui object of per-face foreground and background. The theme name is both the json name field and the download filename. samples.py holds the language samples and the default color map. theme-selector.html is the generated output. The json-to-theme converter is the next piece, and the part worth TDD. --- scripts/theme-selector/generate.py | 209 +++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 scripts/theme-selector/generate.py (limited to 'scripts/theme-selector/generate.py') diff --git a/scripts/theme-selector/generate.py b/scripts/theme-selector/generate.py new file mode 100644 index 00000000..94910d1c --- /dev/null +++ b/scripts/theme-selector/generate.py @@ -0,0 +1,209 @@ +import json, re, os +HERE=os.path.dirname(os.path.abspath(__file__)) +ns={} +src=open(os.path.join(HERE,'samples.py')).read() +exec(src[:src.index('cols=')], ns) +SAMPLES={"Elisp":ns['ELS'],"Go":ns['GOS'],"Python":ns['PYS'],"TypeScript":ns['TSS'],"Shell":ns['SHS'],"C/C++":ns['CS']} +COLS=ns['COLS'] +MAP={k:v[0] for k,v in COLS.items()}; BOLD={k:v[1] for k,v in COLS.items()}; MAP['str']='#5d9b86'; MAP['bg']='#0d0b0a' +PALETTE=[["#67809c","blue"],["#e8bd30","gold"],["#9b5fd0","regal"],["#2ba178","emerald"],["#5d9b86","sage"], + ["#cb6b4d","terracotta"],["#be9e74","tan"],["#cdced1","white"],["#a9b2bb","silver"],["#838d97","steel"], + ["#5e6770","pewter"],["#2f343a","gunmetal"],["#264364","navy"],["#0d0b0a","ground"],["#1a1714","bg-dim"]] +CATS=[["bg","background (ground)","Aa Bb 123"],["p","fg · default text","other / whitespace"],["kw","keyword","class def if return"],["bi","builtin","len echo printf"], + ["pp","preprocessor","#include #define"],["fnd","function · def","resolve push"], + ["fnc","function · call","printf rsync get"],["dec","decorator","@dataclass"], + ["ty","type / class","int str Order Queue"],["prop","property / field","id name items"], + ["con","constant","None nil NULL true"],["num","number","8080 100 -1"], + ["str","string",'"dupre" "fmt"'],["esc","escape","\\n \\t"],["re","regexp","/^#[0-9a-f]+/"], + ["doc","docstring",'"""..."""'],["cm","comment","# reject nil"],["cmd","comment delim","# // ;;"], + ["var","variable / use","value key self"],["op","operator",": = -> =="], + ["punc","punctuation","{ } ( ) ;"]] +UI_FACES=[["cursor","cursor","Aa|"],["region","region (selection)","selected text"], + ["hl-line","hl-line (current line)","current line"],["highlight","highlight","hover"], + ["mode-line","mode-line","status active"],["mode-line-inactive","mode-line-inactive","status idle"], + ["fringe","fringe","| |"],["line-number","line-number"," 42"], + ["line-number-current-line","line-number-current-line","> 42"],["minibuffer-prompt","minibuffer-prompt","M-x "], + ["isearch","isearch (match)","match"],["lazy-highlight","lazy-highlight","other match"], + ["isearch-fail","isearch-fail","no match"],["show-paren-match","show-paren-match","( )"], + ["show-paren-mismatch","show-paren-mismatch",") ("],["link","link","https://"], + ["error","error","error!"],["warning","warning","warning"], + ["success","success","ok"],["vertical-border","vertical-border","|"]] +UIMAP={"cursor":{"fg":None,"bg":"#a9b2bb"},"region":{"fg":None,"bg":"#264364"}, + "hl-line":{"fg":None,"bg":"#1a1714"},"highlight":{"fg":None,"bg":"#2f343a"}, + "mode-line":{"fg":"#cdced1","bg":"#2f343a"},"mode-line-inactive":{"fg":"#838d97","bg":"#1a1714"}, + "fringe":{"fg":None,"bg":"#0d0b0a"},"line-number":{"fg":"#5e6770","bg":None}, + "line-number-current-line":{"fg":"#e8bd30","bg":"#1a1714"},"minibuffer-prompt":{"fg":"#67809c","bg":None}, + "isearch":{"fg":"#0d0b0a","bg":"#e8bd30"},"lazy-highlight":{"fg":"#0d0b0a","bg":"#838d97"}, + "isearch-fail":{"fg":"#cb6b4d","bg":None},"show-paren-match":{"fg":None,"bg":"#264364"}, + "show-paren-mismatch":{"fg":"#0d0b0a","bg":"#cb6b4d"},"link":{"fg":"#67809c","bg":None}, + "error":{"fg":"#cb6b4d","bg":None},"warning":{"fg":"#e8bd30","bg":None}, + "success":{"fg":"#5d9b86","bg":None},"vertical-border":{"fg":"#2f343a","bg":None}} +def cid(l): return re.sub(r'\W','',l) +code_cont="".join(f'

{l}

' for l in SAMPLES) +HTML = """theme-selector + +

Untitled: color palette

+

code samples

+
CODE_CONT
+

color → category — chip reassigns · N/B/I sets weight & slant · click a header to sort

+
color △stylecategory △example
+

UI / interface faces — foreground & background per face

+
faceforegroundbackgroundpreview
+

palette — add / remove / rename / drag to reorder

+
+
+ + + + +
+

save / load theme

+
+ +
+
+ + + +
+ +""" +HTML=(HTML.replace("CODE_CONT",code_cont).replace("SAMPLES_J",json.dumps(SAMPLES)) + .replace("PALETTE_J",json.dumps(PALETTE)).replace("CATS_J",json.dumps(CATS)) + .replace("UIFACES_J",json.dumps(UI_FACES)).replace("UIMAP_J",json.dumps(UIMAP)) + .replace("BOLD_J",json.dumps(BOLD)).replace("MAP_J",json.dumps(MAP))) +OUT=os.path.join(HERE,'theme-selector.html') +open(OUT,"w").write(HTML) +print("wrote",OUT) -- cgit v1.2.3