From 4b8c6a11ce754fce4b66c3ff5535ca501bbce072 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Sat, 16 May 2026 22:15:40 +0200 Subject: [PATCH 1/5] Add missing highlighting of the #Else directive. --- docs/_plugins/twinbasic.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_plugins/twinbasic.rb b/docs/_plugins/twinbasic.rb index a479bf56..e9374ef7 100644 --- a/docs/_plugins/twinbasic.rb +++ b/docs/_plugins/twinbasic.rb @@ -72,6 +72,7 @@ def self.builtins rule %r( [#]If\b .*? \bThen | [#]ElseIf\b .*? \bThen + | [#]Else\b | [#]End \s+ If | [#]Const | [#]Region .*? \n From 5bd234ac1b600fd0247cde1bd0fce461d5d29aac Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Sat, 16 May 2026 22:24:22 +0200 Subject: [PATCH 2/5] Extract the syntax highlighter theme from a tB installation. --- scripts/extract_theme_colors.py | 86 +++++++ scripts/themes/twinbasic-classic.css | 331 +++++++++++++++++++++++++++ scripts/themes/twinbasic-dark.css | 331 +++++++++++++++++++++++++++ scripts/themes/twinbasic-light.css | 331 +++++++++++++++++++++++++++ 4 files changed, 1079 insertions(+) create mode 100644 scripts/extract_theme_colors.py create mode 100644 scripts/themes/twinbasic-classic.css create mode 100644 scripts/themes/twinbasic-dark.css create mode 100644 scripts/themes/twinbasic-light.css diff --git a/scripts/extract_theme_colors.py b/scripts/extract_theme_colors.py new file mode 100644 index 00000000..2a2a9a77 --- /dev/null +++ b/scripts/extract_theme_colors.py @@ -0,0 +1,86 @@ +"""Extract Symbol* syntax-highlighting properties from twinBASIC IDE .theme files. + +Produces three CSS files in scripts/themes/ — twinbasic-classic.css, +twinbasic-dark.css, twinbasic-light.css — one CSS rule per Symbol with the +four properties (Color, FontStyle, FontWeight, TextDecoration) grouped +together. + +The Classic theme inherits from Light and overrides a subset; the script +resolves the inheritance so the emitted CSS reflects the effective theme. +""" + +import os +import re +from pathlib import Path + +THEMES_DIR = Path(os.environ["USERPROFILE"]) / "Desktop" / "twinBASIC_IDE_BETA_982" / "themes" +OUT_DIR = Path(__file__).resolve().parent / "themes" + +CSS_PROP = { + "Color": "color", + "FontStyle": "font-style", + "FontWeight": "font-weight", + "TextDecoration": "text-decoration", +} + +SYMBOL_LINE = re.compile( + r"^Symbol([A-Za-z]+?)(Color|FontStyle|FontWeight|TextDecoration)\s*:\s*(.+?)\s*;?\s*$" +) + + +def parse_theme(path: Path) -> dict[str, dict[str, str]]: + result: dict[str, dict[str, str]] = {} + text = re.sub(r"/\*.*?\*/", "", path.read_text(encoding="utf-8"), flags=re.DOTALL) + for raw in text.splitlines(): + line = raw.strip() + if not line.startswith("Symbol"): + continue + m = SYMBOL_LINE.match(line) + if not m: + continue + sym, prop, value = m.group(1), m.group(2), m.group(3).strip().rstrip(";").strip() + result.setdefault(sym, {})[prop] = value + return result + + +def render_css(theme: dict[str, dict[str, str]], header: str) -> str: + out = [f"/* {header} */\n\n"] + for sym in sorted(theme): + props = theme[sym] + out.append(f".Symbol{sym} {{\n") + for key in ("Color", "FontStyle", "FontWeight", "TextDecoration"): + if key in props: + out.append(f" {CSS_PROP[key]}: {props[key]};\n") + out.append("}\n\n") + return "".join(out) + + +def main() -> None: + light = parse_theme(THEMES_DIR / "Light.theme") + dark = parse_theme(THEMES_DIR / "Dark.theme") + classic_overrides = parse_theme(THEMES_DIR / "Classic.theme") + + classic = {sym: dict(props) for sym, props in light.items()} + for sym, props in classic_overrides.items(): + classic.setdefault(sym, {}).update(props) + + OUT_DIR.mkdir(parents=True, exist_ok=True) + (OUT_DIR / "twinbasic-light.css").write_text( + render_css(light, "twinBASIC Light theme - syntax highlighting colors"), + encoding="utf-8", + ) + (OUT_DIR / "twinbasic-dark.css").write_text( + render_css(dark, "twinBASIC Dark theme - syntax highlighting colors"), + encoding="utf-8", + ) + (OUT_DIR / "twinbasic-classic.css").write_text( + render_css( + classic, + "twinBASIC Classic theme - syntax highlighting colors (Light + Classic overrides)", + ), + encoding="utf-8", + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/themes/twinbasic-classic.css b/scripts/themes/twinbasic-classic.css new file mode 100644 index 00000000..a7f63aa6 --- /dev/null +++ b/scripts/themes/twinbasic-classic.css @@ -0,0 +1,331 @@ +/* twinBASIC Classic theme - syntax highlighting colors (Light + Classic overrides) */ + +.SymbolAttribute { + color: #bfbfbf; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolBuiltInDataType { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolClass { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolComment { + color: #00801D; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolConditionalCompilationDirective { + color: #ad8c98; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolConditionalCompilationExcludedCode { + color: #989599; + font-style: oblique; + font-weight: normal; + text-decoration: none; +} + +.SymbolConstant { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolContinuationCharacter { + color: #808080; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolDeclareFunction { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolDeclareSub { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolEnum { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolEnumMember { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolField { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolFunction { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGenericDataType { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGenericValue { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGlobalVariablePrivate { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGlobalVariablePublic { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolInterface { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolKeyword { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLateBoundFunction { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLibrary { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLineLabel { + color: #448f86; + font-style: normal; + font-weight: normal; + text-decoration: underline; +} + +.SymbolLineNumber { + color: #448f86; + font-style: normal; + font-weight: normal; + text-decoration: underline; +} + +.SymbolLiteralBoolean { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralDate { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralEmpty { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNothing { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNull { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNumeric { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralString { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolMe { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolModule { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolMultiLineSeperator { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolNamedArgument { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolNamedOperator { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolOperator { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolParamByRef { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolParamByVal { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertyGet { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertyLet { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertySet { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolReturnValue { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolSub { + color: #002DA6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolUDT { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolVariable { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolVariableUndeclared { + color: #000000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + diff --git a/scripts/themes/twinbasic-dark.css b/scripts/themes/twinbasic-dark.css new file mode 100644 index 00000000..ef6e0522 --- /dev/null +++ b/scripts/themes/twinbasic-dark.css @@ -0,0 +1,331 @@ +/* twinBASIC Dark theme - syntax highlighting colors */ + +.SymbolAttribute { + color: #5c5c53; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolBuiltInDataType { + color: #b1551f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolClass { + color: #e4c685; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolComment { + color: #448a63; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolConditionalCompilationDirective { + color: #ad8c98; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolConditionalCompilationExcludedCode { + color: #989599; + font-style: oblique; + font-weight: normal; + text-decoration: none; +} + +.SymbolConstant { + color: #6089b4; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolContinuationCharacter { + color: #808080; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolDeclareFunction { + color: #bb956a; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolDeclareSub { + color: #bb956a; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolEnum { + color: #738dc5; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolEnumMember { + color: #a1adc7; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolField { + color: #f59e1b; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolFunction { + color: #cf9a5d; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGenericDataType { + color: #eeda83; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGenericValue { + color: #86ee83; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGlobalVariablePrivate { + color: #f38096; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGlobalVariablePublic { + color: #d34056; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolInterface { + color: #ab3b4d; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolKeyword { + color: #6c8eda; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLateBoundFunction { + color: #e7ac5f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLibrary { + color: #bb6464; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLineLabel { + color: #ccc6be; + font-style: normal; + font-weight: normal; + text-decoration: underline; +} + +.SymbolLineNumber { + color: #ccc6be; + font-style: normal; + font-weight: normal; + text-decoration: underline; +} + +.SymbolLiteralBoolean { + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralDate { + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralEmpty { + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNothing { + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNull { + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNumeric { + color: #aeca89; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralString { + color: #aeca89; + font-style: oblique; + font-weight: normal; + text-decoration: none; +} + +.SymbolMe { + color: #a13838; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolModule { + color: #a8a887; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolMultiLineSeperator { + color: #74384c; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolNamedArgument { + color: #74384c; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolNamedOperator { + color: #80a1a5; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolOperator { + color: #80a1a5; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolParamByRef { + color: #9b79b3; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolParamByVal { + color: #9b79b3; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertyGet { + color: #864f0f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertyLet { + color: #864f0f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertySet { + color: #864f0f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolReturnValue { + color: #ee8391; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolSub { + color: #cf9a5d; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolUDT { + color: #a5630d; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolVariable { + color: #8b8b52; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolVariableUndeclared { + color: #b9929c; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + diff --git a/scripts/themes/twinbasic-light.css b/scripts/themes/twinbasic-light.css new file mode 100644 index 00000000..c9a9bdbe --- /dev/null +++ b/scripts/themes/twinbasic-light.css @@ -0,0 +1,331 @@ +/* twinBASIC Light theme - syntax highlighting colors */ + +.SymbolAttribute { + color: #bfbfbf; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolBuiltInDataType { + color: #b1551f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolClass { + color: #a87300; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolComment { + color: #448a63; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolConditionalCompilationDirective { + color: #ad8c98; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolConditionalCompilationExcludedCode { + color: #989599; + font-style: oblique; + font-weight: normal; + text-decoration: none; +} + +.SymbolConstant { + color: #6089b4; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolContinuationCharacter { + color: #808080; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolDeclareFunction { + color: #bb956a; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolDeclareSub { + color: #bb956a; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolEnum { + color: #2360e9; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolEnumMember { + color: #205ee6; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolField { + color: #ea8c00; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolFunction { + color: #b96300; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGenericDataType { + color: #eeda83; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGenericValue { + color: #86ee83; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGlobalVariablePrivate { + color: #f38096; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolGlobalVariablePublic { + color: #d34056; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolInterface { + color: #ab3b4d; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolKeyword { + color: #385ba9; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLateBoundFunction { + color: #e7ac5f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLibrary { + color: #bb6464; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLineLabel { + color: #a6a6a6; + font-style: normal; + font-weight: normal; + text-decoration: underline; +} + +.SymbolLineNumber { + color: #a6a6a6; + font-style: normal; + font-weight: normal; + text-decoration: underline; +} + +.SymbolLiteralBoolean { + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralDate { + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralEmpty { + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNothing { + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNull { + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralNumeric { + color: #457e12; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolLiteralString { + color: #679f1e; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolMe { + color: #a13838; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolModule { + color: #89894d; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolMultiLineSeperator { + color: #74384c; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolNamedArgument { + color: #74384c; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolNamedOperator { + color: #385ba9; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolOperator { + color: #80a1a5; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolParamByRef { + color: #9b79b3; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolParamByVal { + color: #8d5eaf; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertyGet { + color: #864f0f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertyLet { + color: #864f0f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolPropertySet { + color: #864f0f; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolReturnValue { + color: #ee8391; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolSub { + color: #cf9a5d; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolUDT { + color: #a5630d; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolVariable { + color: #939000; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + +.SymbolVariableUndeclared { + color: #b9929c; + font-style: normal; + font-weight: normal; + text-decoration: none; +} + From 7bbd2227b62315aa8a1c03362cde9d73f1b51cf0 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Sat, 16 May 2026 22:50:22 +0200 Subject: [PATCH 3/5] Map theme elements to Rouge highlighter classes. --- docs/_plugins/twinbasic.rb | 46 ++++- scripts/extract_theme_colors.py | 104 +++++++++-- scripts/themes/twinbasic-classic.css | 247 ++++++------------------ scripts/themes/twinbasic-dark.css | 267 +++++++------------------- scripts/themes/twinbasic-light.css | 269 +++++++-------------------- 5 files changed, 327 insertions(+), 606 deletions(-) diff --git a/docs/_plugins/twinbasic.rb b/docs/_plugins/twinbasic.rb index e9374ef7..661099b6 100644 --- a/docs/_plugins/twinbasic.rb +++ b/docs/_plugins/twinbasic.rb @@ -3,6 +3,22 @@ require "rouge" +# Register tB-specific token types so the highlighter can distinguish symbols +# the way the twinBASIC IDE's theme system does -- e.g. Boolean / Empty / +# Nothing / Null are each their own slot in tB's Symbol* palette, and the +# line-continuation '_' has its own SymbolContinuationCharacter colour. +module Rouge::Token::Tokens + [ + [Literal, :Boolean, 'lb'], + [Literal, :Empty, 'le'], + [Literal, :Nothing, 'ln'], + [Literal, :Null, 'lu'], + [Punctuation, :LineContinuation, 'lc'], + ].each do |parent, name, shortname| + parent.token(name, shortname) unless parent.const_defined?(name, false) + end +end + module Rouge module Lexers class TwinBasic < RegexLexer @@ -17,20 +33,30 @@ def self.keywords @keywords ||= Set.new %w( alias byref byval call case class close coclass const continue declare default delegate dim do each else elseif - empty end endif enum erase error event exit extends - false finally for friend function get global gosub + end endif enum erase error event exit extends + finally for friend function get global gosub goto handles if implements imports inherits input interface let lib line lock loop me mid module - namespace new next nothing null of on open option optional + namespace new next of on open option optional overloads paramarray preserve print private property public put raiseevent redim resume return select set shared static step stop structure sub - then throw to true try type unlock until + then throw to try type unlock until using wend when while width with withevents write ) end + def self.keyword_constants + @keyword_constants ||= { + 'true' => Literal::Boolean, + 'false' => Literal::Boolean, + 'empty' => Literal::Empty, + 'nothing' => Literal::Nothing, + 'null' => Literal::Null, + } + end + def self.keywords_type @keywords_type ||= Set.new %w( any boolean byte currency date decimal double integer long @@ -54,7 +80,7 @@ def self.builtins id = /[a-z_]\w*/i state :whitespace do - rule %r/_[ \t]*\n/, Keyword + rule %r/_[ \t]*\n/, Punctuation::LineContinuation rule %r/\n/, Text, :bol rule %r/[^\S\n]+/, Text rule %r/rem\b.*?$/i, Comment::Single @@ -79,6 +105,8 @@ def self.builtins | [#]End \s+ Region )xi, Comment::Preproc + rule %r/#\d[^#\n]*#/, Literal::Date + rule %r/\[/, Punctuation, :attribute rule %r/(\d+\.\d*|\d*\.\d+)(e[+-]?\d+)?[!#@]?/i, Num::Float @@ -104,7 +132,9 @@ def self.builtins rule id do |m| key = m[0].downcase - if self.class.keywords.include? key + if (kc = self.class.keyword_constants[key]) + token kc + elsif self.class.keywords.include? key token Keyword elsif self.class.keywords_type.include? key token Keyword::Type @@ -158,7 +188,9 @@ def self.builtins rule %r/\d+[%&!#@]?/, Num::Integer rule id do |m| key = m[0].downcase - if self.class.keywords.include? key + if (kc = self.class.keyword_constants[key]) + token kc + elsif self.class.keywords.include? key token Keyword elsif self.class.keywords_type.include? key token Keyword::Type diff --git a/scripts/extract_theme_colors.py b/scripts/extract_theme_colors.py index 2a2a9a77..bf7bd53f 100644 --- a/scripts/extract_theme_colors.py +++ b/scripts/extract_theme_colors.py @@ -1,9 +1,18 @@ -"""Extract Symbol* syntax-highlighting properties from twinBASIC IDE .theme files. +"""Extract Symbol* syntax-highlighting properties from twinBASIC IDE .theme files +and emit Rouge-compatible CSS. Produces three CSS files in scripts/themes/ — twinbasic-classic.css, -twinbasic-dark.css, twinbasic-light.css — one CSS rule per Symbol with the -four properties (Color, FontStyle, FontWeight, TextDecoration) grouped -together. +twinbasic-dark.css, twinbasic-light.css. Each rule uses the Rouge HTML +formatter class (e.g. .k, .nc, .cp) that docs/_plugins/twinbasic.rb emits +for that token, with the colors and font properties taken from the +corresponding tB theme Symbol. + +The mapping is many-to-one: several tB theme Symbols share a single Rouge +class because the lexer doesn't distinguish them (e.g. SymbolMe, +SymbolLiteralBoolean, SymbolLiteralNothing all fall under Rouge Keyword .k +alongside SymbolKeyword). The mapping below picks the canonical tB Symbol +per Rouge class; the unmapped Symbols are listed at the bottom of each +emitted file for reference. The Classic theme inherits from Light and overrides a subset; the script resolves the inheritance so the emitted CSS reflects the effective theme. @@ -23,6 +32,70 @@ "TextDecoration": "text-decoration", } +# Rouge HTML formatter class -> tB theme Symbol (canonical source for colors). +# Only includes Rouge classes that twinbasic.rb actually emits. Order here is +# the order the rules appear in the emitted CSS. +ROUGE_TO_SYMBOL: list[tuple[str, str, str]] = [ + ("c1", "Comment", "' comments and REM"), + ("cm", "Comment", "C-style block comments"), + ("cp", "ConditionalCompilationDirective", "#If / #ElseIf / #Else / #End If / #Const / #Region"), + ("k", "Keyword", "Dim, If, End, Sub, ..."), + ("kd", "Keyword", "Option Strict / Explicit / Compare / Base"), + ("kt", "BuiltInDataType", "Boolean, Integer, String, ..."), + ("lb", "LiteralBoolean", "True, False"), + ("lc", "ContinuationCharacter", "'_' line-continuation marker"), + ("ld", "LiteralDate", "#m/d/yyyy [h:mm:ss am/pm]# date-time literals"), + ("le", "LiteralEmpty", "Empty"), + ("ln", "LiteralNothing", "Nothing"), + ("lu", "LiteralNull", "Null"), + ("mi", "LiteralNumeric", "integer literals"), + ("mf", "LiteralNumeric", "float literals"), + ("s", "LiteralString", "string literals"), + ("se", "LiteralString", "\"\" escape inside string literals"), + ("o", "Operator", "+, -, =, <, >, &, ..."), + ("ow", "NamedOperator", "And, Or, Not, Is, Mod, ..."), + ("na", "Attribute", "[Documentation(...)] attribute names"), + ("nb", "Class", "Debug, Err"), + ("nc", "Class", "Class / CoClass / Enum / Interface / Type / Structure names"), + ("nf", "Function", "Function / Sub / Property names"), + ("nn", "Module", "Module / Namespace / Imports targets"), + ("nv", "Variable", "Dim / Const / ReDim variable names"), +] + +# tB Symbols that have no Rouge counterpart from twinbasic.rb. The lexer either +# doesn't tokenize them at all or folds them into a broader Rouge token (in which +# case the canonical Symbol in ROUGE_TO_SYMBOL wins). +UNMAPPED_SYMBOLS: list[tuple[str, str]] = [ + ("ConditionalCompilationExcludedCode", "not tokenized — would need preproc evaluation"), + ("Constant", "not distinguished from Name"), + ("DeclareFunction", "not distinguished from Keyword .k"), + ("DeclareSub", "not distinguished from Keyword .k"), + ("Enum", "folded into Name::Class .nc"), + ("EnumMember", "not distinguished from Name"), + ("Field", "not distinguished from Name"), + ("GenericDataType", "not tokenized"), + ("GenericValue", "not tokenized"), + ("GlobalVariablePrivate", "not distinguished from Name"), + ("GlobalVariablePublic", "not distinguished from Name"), + ("Interface", "folded into Name::Class .nc"), + ("LateBoundFunction", "not distinguished from Name"), + ("Library", "not distinguished from Name"), + ("LineLabel", "not tokenized"), + ("LineNumber", "not tokenized"), + ("Me", "folded into Keyword .k"), + ("MultiLineSeperator", "not tokenized"), + ("NamedArgument", "not tokenized"), + ("ParamByRef", "not tokenized"), + ("ParamByVal", "not tokenized"), + ("PropertyGet", "folded into Keyword .k"), + ("PropertyLet", "folded into Keyword .k"), + ("PropertySet", "folded into Keyword .k"), + ("ReturnValue", "not tokenized"), + ("Sub", "folded into Name::Function .nf"), + ("UDT", "folded into Name::Class .nc"), + ("VariableUndeclared", "not distinguished from Name"), +] + SYMBOL_LINE = re.compile( r"^Symbol([A-Za-z]+?)(Color|FontStyle|FontWeight|TextDecoration)\s*:\s*(.+?)\s*;?\s*$" ) @@ -44,14 +117,23 @@ def parse_theme(path: Path) -> dict[str, dict[str, str]]: def render_css(theme: dict[str, dict[str, str]], header: str) -> str: - out = [f"/* {header} */\n\n"] - for sym in sorted(theme): - props = theme[sym] - out.append(f".Symbol{sym} {{\n") + out = [f"/* {header} */\n"] + out.append("/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */\n\n") + + for rouge, sym, comment in ROUGE_TO_SYMBOL: + props = theme.get(sym) + if not props: + continue + out.append(f".{rouge} {{ /* Symbol{sym} — {comment} */\n") for key in ("Color", "FontStyle", "FontWeight", "TextDecoration"): if key in props: out.append(f" {CSS_PROP[key]}: {props[key]};\n") out.append("}\n\n") + + out.append("/* tB Symbols with no dedicated Rouge class in twinbasic.rb: */\n") + for sym, why in UNMAPPED_SYMBOLS: + out.append(f"/* Symbol{sym} — {why} */\n") + return "".join(out) @@ -66,17 +148,17 @@ def main() -> None: OUT_DIR.mkdir(parents=True, exist_ok=True) (OUT_DIR / "twinbasic-light.css").write_text( - render_css(light, "twinBASIC Light theme - syntax highlighting colors"), + render_css(light, "twinBASIC Light theme - Rouge syntax highlighting"), encoding="utf-8", ) (OUT_DIR / "twinbasic-dark.css").write_text( - render_css(dark, "twinBASIC Dark theme - syntax highlighting colors"), + render_css(dark, "twinBASIC Dark theme - Rouge syntax highlighting"), encoding="utf-8", ) (OUT_DIR / "twinbasic-classic.css").write_text( render_css( classic, - "twinBASIC Classic theme - syntax highlighting colors (Light + Classic overrides)", + "twinBASIC Classic theme - Rouge syntax highlighting (Light + Classic overrides)", ), encoding="utf-8", ) diff --git a/scripts/themes/twinbasic-classic.css b/scripts/themes/twinbasic-classic.css index a7f63aa6..a1cd7a57 100644 --- a/scripts/themes/twinbasic-classic.css +++ b/scripts/themes/twinbasic-classic.css @@ -1,331 +1,200 @@ -/* twinBASIC Classic theme - syntax highlighting colors (Light + Classic overrides) */ +/* twinBASIC Classic theme - Rouge syntax highlighting (Light + Classic overrides) */ +/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */ -.SymbolAttribute { - color: #bfbfbf; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolBuiltInDataType { - color: #002DA6; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolClass { - color: #000000; +.c1 { /* SymbolComment — ' comments and REM */ + color: #00801D; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolComment { +.cm { /* SymbolComment — C-style block comments */ color: #00801D; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolConditionalCompilationDirective { +.cp { /* SymbolConditionalCompilationDirective — #If / #ElseIf / #Else / #End If / #Const / #Region */ color: #ad8c98; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolConditionalCompilationExcludedCode { - color: #989599; - font-style: oblique; - font-weight: normal; - text-decoration: none; -} - -.SymbolConstant { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolContinuationCharacter { - color: #808080; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolDeclareFunction { +.k { /* SymbolKeyword — Dim, If, End, Sub, ... */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolDeclareSub { +.kd { /* SymbolKeyword — Option Strict / Explicit / Compare / Base */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolEnum { +.kt { /* SymbolBuiltInDataType — Boolean, Integer, String, ... */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolEnumMember { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolField { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolFunction { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGenericDataType { - color: #002DA6; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGenericValue { - color: #002DA6; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGlobalVariablePrivate { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGlobalVariablePublic { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolInterface { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolKeyword { +.lb { /* SymbolLiteralBoolean — True, False */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLateBoundFunction { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolLibrary { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolLineLabel { - color: #448f86; - font-style: normal; - font-weight: normal; - text-decoration: underline; -} - -.SymbolLineNumber { - color: #448f86; - font-style: normal; - font-weight: normal; - text-decoration: underline; -} - -.SymbolLiteralBoolean { - color: #002DA6; +.lc { /* SymbolContinuationCharacter — '_' line-continuation marker */ + color: #808080; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralDate { +.ld { /* SymbolLiteralDate — #m/d/yyyy [h:mm:ss am/pm]# date-time literals */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralEmpty { +.le { /* SymbolLiteralEmpty — Empty */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNothing { +.ln { /* SymbolLiteralNothing — Nothing */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNull { +.lu { /* SymbolLiteralNull — Null */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNumeric { +.mi { /* SymbolLiteralNumeric — integer literals */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralString { +.mf { /* SymbolLiteralNumeric — float literals */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolMe { +.s { /* SymbolLiteralString — string literals */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolModule { +.se { /* SymbolLiteralString — "" escape inside string literals */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolMultiLineSeperator { +.o { /* SymbolOperator — +, -, =, <, >, &, ... */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolNamedArgument { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolNamedOperator { +.ow { /* SymbolNamedOperator — And, Or, Not, Is, Mod, ... */ color: #002DA6; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolOperator { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolParamByRef { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolParamByVal { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolPropertyGet { - color: #000000; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolPropertyLet { - color: #000000; +.na { /* SymbolAttribute — [Documentation(...)] attribute names */ + color: #bfbfbf; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolPropertySet { +.nb { /* SymbolClass — Debug, Err */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolReturnValue { +.nc { /* SymbolClass — Class / CoClass / Enum / Interface / Type / Structure names */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolSub { - color: #002DA6; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolUDT { +.nf { /* SymbolFunction — Function / Sub / Property names */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolVariable { +.nn { /* SymbolModule — Module / Namespace / Imports targets */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolVariableUndeclared { +.nv { /* SymbolVariable — Dim / Const / ReDim variable names */ color: #000000; font-style: normal; font-weight: normal; text-decoration: none; } +/* tB Symbols with no dedicated Rouge class in twinbasic.rb: */ +/* SymbolConditionalCompilationExcludedCode — not tokenized — would need preproc evaluation */ +/* SymbolConstant — not distinguished from Name */ +/* SymbolDeclareFunction — not distinguished from Keyword .k */ +/* SymbolDeclareSub — not distinguished from Keyword .k */ +/* SymbolEnum — folded into Name::Class .nc */ +/* SymbolEnumMember — not distinguished from Name */ +/* SymbolField — not distinguished from Name */ +/* SymbolGenericDataType — not tokenized */ +/* SymbolGenericValue — not tokenized */ +/* SymbolGlobalVariablePrivate — not distinguished from Name */ +/* SymbolGlobalVariablePublic — not distinguished from Name */ +/* SymbolInterface — folded into Name::Class .nc */ +/* SymbolLateBoundFunction — not distinguished from Name */ +/* SymbolLibrary — not distinguished from Name */ +/* SymbolLineLabel — not tokenized */ +/* SymbolLineNumber — not tokenized */ +/* SymbolMe — folded into Keyword .k */ +/* SymbolMultiLineSeperator — not tokenized */ +/* SymbolNamedArgument — not tokenized */ +/* SymbolParamByRef — not tokenized */ +/* SymbolParamByVal — not tokenized */ +/* SymbolPropertyGet — folded into Keyword .k */ +/* SymbolPropertyLet — folded into Keyword .k */ +/* SymbolPropertySet — folded into Keyword .k */ +/* SymbolReturnValue — not tokenized */ +/* SymbolSub — folded into Name::Function .nf */ +/* SymbolUDT — folded into Name::Class .nc */ +/* SymbolVariableUndeclared — not distinguished from Name */ diff --git a/scripts/themes/twinbasic-dark.css b/scripts/themes/twinbasic-dark.css index ef6e0522..89bb56d8 100644 --- a/scripts/themes/twinbasic-dark.css +++ b/scripts/themes/twinbasic-dark.css @@ -1,331 +1,200 @@ -/* twinBASIC Dark theme - syntax highlighting colors */ +/* twinBASIC Dark theme - Rouge syntax highlighting */ +/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */ -.SymbolAttribute { - color: #5c5c53; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolBuiltInDataType { - color: #b1551f; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolClass { - color: #e4c685; +.c1 { /* SymbolComment — ' comments and REM */ + color: #448a63; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolComment { +.cm { /* SymbolComment — C-style block comments */ color: #448a63; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolConditionalCompilationDirective { +.cp { /* SymbolConditionalCompilationDirective — #If / #ElseIf / #Else / #End If / #Const / #Region */ color: #ad8c98; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolConditionalCompilationExcludedCode { - color: #989599; - font-style: oblique; - font-weight: normal; - text-decoration: none; -} - -.SymbolConstant { - color: #6089b4; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolContinuationCharacter { - color: #808080; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolDeclareFunction { - color: #bb956a; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolDeclareSub { - color: #bb956a; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolEnum { - color: #738dc5; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolEnumMember { - color: #a1adc7; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolField { - color: #f59e1b; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolFunction { - color: #cf9a5d; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGenericDataType { - color: #eeda83; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGenericValue { - color: #86ee83; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGlobalVariablePrivate { - color: #f38096; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGlobalVariablePublic { - color: #d34056; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolInterface { - color: #ab3b4d; +.k { /* SymbolKeyword — Dim, If, End, Sub, ... */ + color: #6c8eda; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolKeyword { +.kd { /* SymbolKeyword — Option Strict / Explicit / Compare / Base */ color: #6c8eda; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLateBoundFunction { - color: #e7ac5f; +.kt { /* SymbolBuiltInDataType — Boolean, Integer, String, ... */ + color: #b1551f; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLibrary { - color: #bb6464; +.lb { /* SymbolLiteralBoolean — True, False */ + color: #c495d3; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLineLabel { - color: #ccc6be; - font-style: normal; - font-weight: normal; - text-decoration: underline; -} - -.SymbolLineNumber { - color: #ccc6be; - font-style: normal; - font-weight: normal; - text-decoration: underline; -} - -.SymbolLiteralBoolean { - color: #c495d3; +.lc { /* SymbolContinuationCharacter — '_' line-continuation marker */ + color: #808080; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralDate { +.ld { /* SymbolLiteralDate — #m/d/yyyy [h:mm:ss am/pm]# date-time literals */ color: #c495d3; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralEmpty { +.le { /* SymbolLiteralEmpty — Empty */ color: #c495d3; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNothing { +.ln { /* SymbolLiteralNothing — Nothing */ color: #c495d3; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNull { +.lu { /* SymbolLiteralNull — Null */ color: #c495d3; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNumeric { +.mi { /* SymbolLiteralNumeric — integer literals */ color: #aeca89; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralString { +.mf { /* SymbolLiteralNumeric — float literals */ color: #aeca89; - font-style: oblique; - font-weight: normal; - text-decoration: none; -} - -.SymbolMe { - color: #a13838; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolModule { - color: #a8a887; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolMultiLineSeperator { - color: #74384c; - font-style: normal; +.s { /* SymbolLiteralString — string literals */ + color: #aeca89; + font-style: oblique; font-weight: normal; text-decoration: none; } -.SymbolNamedArgument { - color: #74384c; - font-style: normal; +.se { /* SymbolLiteralString — "" escape inside string literals */ + color: #aeca89; + font-style: oblique; font-weight: normal; text-decoration: none; } -.SymbolNamedOperator { +.o { /* SymbolOperator — +, -, =, <, >, &, ... */ color: #80a1a5; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolOperator { +.ow { /* SymbolNamedOperator — And, Or, Not, Is, Mod, ... */ color: #80a1a5; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolParamByRef { - color: #9b79b3; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolParamByVal { - color: #9b79b3; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolPropertyGet { - color: #864f0f; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolPropertyLet { - color: #864f0f; +.na { /* SymbolAttribute — [Documentation(...)] attribute names */ + color: #5c5c53; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolPropertySet { - color: #864f0f; +.nb { /* SymbolClass — Debug, Err */ + color: #e4c685; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolReturnValue { - color: #ee8391; +.nc { /* SymbolClass — Class / CoClass / Enum / Interface / Type / Structure names */ + color: #e4c685; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolSub { +.nf { /* SymbolFunction — Function / Sub / Property names */ color: #cf9a5d; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolUDT { - color: #a5630d; +.nn { /* SymbolModule — Module / Namespace / Imports targets */ + color: #a8a887; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolVariable { +.nv { /* SymbolVariable — Dim / Const / ReDim variable names */ color: #8b8b52; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolVariableUndeclared { - color: #b9929c; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - +/* tB Symbols with no dedicated Rouge class in twinbasic.rb: */ +/* SymbolConditionalCompilationExcludedCode — not tokenized — would need preproc evaluation */ +/* SymbolConstant — not distinguished from Name */ +/* SymbolDeclareFunction — not distinguished from Keyword .k */ +/* SymbolDeclareSub — not distinguished from Keyword .k */ +/* SymbolEnum — folded into Name::Class .nc */ +/* SymbolEnumMember — not distinguished from Name */ +/* SymbolField — not distinguished from Name */ +/* SymbolGenericDataType — not tokenized */ +/* SymbolGenericValue — not tokenized */ +/* SymbolGlobalVariablePrivate — not distinguished from Name */ +/* SymbolGlobalVariablePublic — not distinguished from Name */ +/* SymbolInterface — folded into Name::Class .nc */ +/* SymbolLateBoundFunction — not distinguished from Name */ +/* SymbolLibrary — not distinguished from Name */ +/* SymbolLineLabel — not tokenized */ +/* SymbolLineNumber — not tokenized */ +/* SymbolMe — folded into Keyword .k */ +/* SymbolMultiLineSeperator — not tokenized */ +/* SymbolNamedArgument — not tokenized */ +/* SymbolParamByRef — not tokenized */ +/* SymbolParamByVal — not tokenized */ +/* SymbolPropertyGet — folded into Keyword .k */ +/* SymbolPropertyLet — folded into Keyword .k */ +/* SymbolPropertySet — folded into Keyword .k */ +/* SymbolReturnValue — not tokenized */ +/* SymbolSub — folded into Name::Function .nf */ +/* SymbolUDT — folded into Name::Class .nc */ +/* SymbolVariableUndeclared — not distinguished from Name */ diff --git a/scripts/themes/twinbasic-light.css b/scripts/themes/twinbasic-light.css index c9a9bdbe..ce013531 100644 --- a/scripts/themes/twinbasic-light.css +++ b/scripts/themes/twinbasic-light.css @@ -1,331 +1,200 @@ -/* twinBASIC Light theme - syntax highlighting colors */ +/* twinBASIC Light theme - Rouge syntax highlighting */ +/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */ -.SymbolAttribute { - color: #bfbfbf; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolBuiltInDataType { - color: #b1551f; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolClass { - color: #a87300; +.c1 { /* SymbolComment — ' comments and REM */ + color: #448a63; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolComment { +.cm { /* SymbolComment — C-style block comments */ color: #448a63; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolConditionalCompilationDirective { +.cp { /* SymbolConditionalCompilationDirective — #If / #ElseIf / #Else / #End If / #Const / #Region */ color: #ad8c98; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolConditionalCompilationExcludedCode { - color: #989599; - font-style: oblique; - font-weight: normal; - text-decoration: none; -} - -.SymbolConstant { - color: #6089b4; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolContinuationCharacter { - color: #808080; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolDeclareFunction { - color: #bb956a; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolDeclareSub { - color: #bb956a; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolEnum { - color: #2360e9; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolEnumMember { - color: #205ee6; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolField { - color: #ea8c00; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolFunction { - color: #b96300; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGenericDataType { - color: #eeda83; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGenericValue { - color: #86ee83; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGlobalVariablePrivate { - color: #f38096; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolGlobalVariablePublic { - color: #d34056; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolInterface { - color: #ab3b4d; +.k { /* SymbolKeyword — Dim, If, End, Sub, ... */ + color: #385ba9; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolKeyword { +.kd { /* SymbolKeyword — Option Strict / Explicit / Compare / Base */ color: #385ba9; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLateBoundFunction { - color: #e7ac5f; +.kt { /* SymbolBuiltInDataType — Boolean, Integer, String, ... */ + color: #b1551f; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLibrary { - color: #bb6464; +.lb { /* SymbolLiteralBoolean — True, False */ + color: #b877ce; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLineLabel { - color: #a6a6a6; - font-style: normal; - font-weight: normal; - text-decoration: underline; -} - -.SymbolLineNumber { - color: #a6a6a6; - font-style: normal; - font-weight: normal; - text-decoration: underline; -} - -.SymbolLiteralBoolean { - color: #b877ce; +.lc { /* SymbolContinuationCharacter — '_' line-continuation marker */ + color: #808080; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralDate { +.ld { /* SymbolLiteralDate — #m/d/yyyy [h:mm:ss am/pm]# date-time literals */ color: #b877ce; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralEmpty { +.le { /* SymbolLiteralEmpty — Empty */ color: #b877ce; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNothing { +.ln { /* SymbolLiteralNothing — Nothing */ color: #b877ce; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNull { +.lu { /* SymbolLiteralNull — Null */ color: #b877ce; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralNumeric { +.mi { /* SymbolLiteralNumeric — integer literals */ color: #457e12; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolLiteralString { - color: #679f1e; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolMe { - color: #a13838; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolModule { - color: #89894d; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolMultiLineSeperator { - color: #74384c; +.mf { /* SymbolLiteralNumeric — float literals */ + color: #457e12; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolNamedArgument { - color: #74384c; +.s { /* SymbolLiteralString — string literals */ + color: #679f1e; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolNamedOperator { - color: #385ba9; +.se { /* SymbolLiteralString — "" escape inside string literals */ + color: #679f1e; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolOperator { +.o { /* SymbolOperator — +, -, =, <, >, &, ... */ color: #80a1a5; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolParamByRef { - color: #9b79b3; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolParamByVal { - color: #8d5eaf; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - -.SymbolPropertyGet { - color: #864f0f; +.ow { /* SymbolNamedOperator — And, Or, Not, Is, Mod, ... */ + color: #385ba9; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolPropertyLet { - color: #864f0f; +.na { /* SymbolAttribute — [Documentation(...)] attribute names */ + color: #bfbfbf; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolPropertySet { - color: #864f0f; +.nb { /* SymbolClass — Debug, Err */ + color: #a87300; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolReturnValue { - color: #ee8391; +.nc { /* SymbolClass — Class / CoClass / Enum / Interface / Type / Structure names */ + color: #a87300; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolSub { - color: #cf9a5d; +.nf { /* SymbolFunction — Function / Sub / Property names */ + color: #b96300; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolUDT { - color: #a5630d; +.nn { /* SymbolModule — Module / Namespace / Imports targets */ + color: #89894d; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolVariable { +.nv { /* SymbolVariable — Dim / Const / ReDim variable names */ color: #939000; font-style: normal; font-weight: normal; text-decoration: none; } -.SymbolVariableUndeclared { - color: #b9929c; - font-style: normal; - font-weight: normal; - text-decoration: none; -} - +/* tB Symbols with no dedicated Rouge class in twinbasic.rb: */ +/* SymbolConditionalCompilationExcludedCode — not tokenized — would need preproc evaluation */ +/* SymbolConstant — not distinguished from Name */ +/* SymbolDeclareFunction — not distinguished from Keyword .k */ +/* SymbolDeclareSub — not distinguished from Keyword .k */ +/* SymbolEnum — folded into Name::Class .nc */ +/* SymbolEnumMember — not distinguished from Name */ +/* SymbolField — not distinguished from Name */ +/* SymbolGenericDataType — not tokenized */ +/* SymbolGenericValue — not tokenized */ +/* SymbolGlobalVariablePrivate — not distinguished from Name */ +/* SymbolGlobalVariablePublic — not distinguished from Name */ +/* SymbolInterface — folded into Name::Class .nc */ +/* SymbolLateBoundFunction — not distinguished from Name */ +/* SymbolLibrary — not distinguished from Name */ +/* SymbolLineLabel — not tokenized */ +/* SymbolLineNumber — not tokenized */ +/* SymbolMe — folded into Keyword .k */ +/* SymbolMultiLineSeperator — not tokenized */ +/* SymbolNamedArgument — not tokenized */ +/* SymbolParamByRef — not tokenized */ +/* SymbolParamByVal — not tokenized */ +/* SymbolPropertyGet — folded into Keyword .k */ +/* SymbolPropertyLet — folded into Keyword .k */ +/* SymbolPropertySet — folded into Keyword .k */ +/* SymbolReturnValue — not tokenized */ +/* SymbolSub — folded into Name::Function .nf */ +/* SymbolUDT — folded into Name::Class .nc */ +/* SymbolVariableUndeclared — not distinguished from Name */ From ad005f035e7b120bb90cc0dde314f6d576d0ff07 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Sat, 16 May 2026 23:17:25 +0200 Subject: [PATCH 4/5] Render the light and dark themes into the site. --- docs/_sass/custom/_twinbasic-dark.scss | 173 ++++++++++++++++++++ docs/_sass/custom/_twinbasic-light.scss | 173 ++++++++++++++++++++ docs/assets/css/just-the-docs-combined.scss | 2 + scripts/extract_theme_colors.py | 74 +++++++-- 4 files changed, 406 insertions(+), 16 deletions(-) create mode 100644 docs/_sass/custom/_twinbasic-dark.scss create mode 100644 docs/_sass/custom/_twinbasic-light.scss diff --git a/docs/_sass/custom/_twinbasic-dark.scss b/docs/_sass/custom/_twinbasic-dark.scss new file mode 100644 index 00000000..a94e4928 --- /dev/null +++ b/docs/_sass/custom/_twinbasic-dark.scss @@ -0,0 +1,173 @@ +/* twinBASIC Dark theme - Rouge syntax highlighting */ +/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */ +/* Scoped under .language-tb .highlight so they only repaint tB fenced code blocks. */ + +.language-tb .highlight { + .c1 { /* SymbolComment — ' comments and REM */ + color: #448a63; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .cm { /* SymbolComment — C-style block comments */ + color: #448a63; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .cp { /* SymbolConditionalCompilationDirective — #If / #ElseIf / #Else / #End If / #Const / #Region */ + color: #ad8c98; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .k { /* SymbolKeyword — Dim, If, End, Sub, ... */ + color: #6c8eda; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .kd { /* SymbolKeyword — Option Strict / Explicit / Compare / Base */ + color: #6c8eda; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .kt { /* SymbolBuiltInDataType — Boolean, Integer, String, ... */ + color: #b1551f; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .lb { /* SymbolLiteralBoolean — True, False */ + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .lc { /* SymbolContinuationCharacter — '_' line-continuation marker */ + color: #808080; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .ld { /* SymbolLiteralDate — #m/d/yyyy [h:mm:ss am/pm]# date-time literals */ + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .le { /* SymbolLiteralEmpty — Empty */ + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .ln { /* SymbolLiteralNothing — Nothing */ + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .lu { /* SymbolLiteralNull — Null */ + color: #c495d3; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .mi { /* SymbolLiteralNumeric — integer literals */ + color: #aeca89; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .mf { /* SymbolLiteralNumeric — float literals */ + color: #aeca89; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .s { /* SymbolLiteralString — string literals */ + color: #aeca89; + font-style: oblique; + font-weight: normal; + text-decoration: none; + } + + .se { /* SymbolLiteralString — "" escape inside string literals */ + color: #aeca89; + font-style: oblique; + font-weight: normal; + text-decoration: none; + } + + .o { /* SymbolOperator — +, -, =, <, >, &, ... */ + color: #80a1a5; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .ow { /* SymbolNamedOperator — And, Or, Not, Is, Mod, ... */ + color: #80a1a5; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .na { /* SymbolAttribute — [Documentation(...)] attribute names */ + color: #5c5c53; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nb { /* SymbolClass — Debug, Err */ + color: #e4c685; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nc { /* SymbolClass — Class / CoClass / Enum / Interface / Type / Structure names */ + color: #e4c685; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nf { /* SymbolFunction — Function / Sub / Property names */ + color: #cf9a5d; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nn { /* SymbolModule — Module / Namespace / Imports targets */ + color: #a8a887; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nv { /* SymbolVariable — Dim / Const / ReDim variable names */ + color: #8b8b52; + font-style: normal; + font-weight: normal; + text-decoration: none; + } +} diff --git a/docs/_sass/custom/_twinbasic-light.scss b/docs/_sass/custom/_twinbasic-light.scss new file mode 100644 index 00000000..dd0d8d71 --- /dev/null +++ b/docs/_sass/custom/_twinbasic-light.scss @@ -0,0 +1,173 @@ +/* twinBASIC Light theme - Rouge syntax highlighting */ +/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */ +/* Scoped under .language-tb .highlight so they only repaint tB fenced code blocks. */ + +.language-tb .highlight { + .c1 { /* SymbolComment — ' comments and REM */ + color: #448a63; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .cm { /* SymbolComment — C-style block comments */ + color: #448a63; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .cp { /* SymbolConditionalCompilationDirective — #If / #ElseIf / #Else / #End If / #Const / #Region */ + color: #ad8c98; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .k { /* SymbolKeyword — Dim, If, End, Sub, ... */ + color: #385ba9; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .kd { /* SymbolKeyword — Option Strict / Explicit / Compare / Base */ + color: #385ba9; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .kt { /* SymbolBuiltInDataType — Boolean, Integer, String, ... */ + color: #b1551f; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .lb { /* SymbolLiteralBoolean — True, False */ + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .lc { /* SymbolContinuationCharacter — '_' line-continuation marker */ + color: #808080; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .ld { /* SymbolLiteralDate — #m/d/yyyy [h:mm:ss am/pm]# date-time literals */ + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .le { /* SymbolLiteralEmpty — Empty */ + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .ln { /* SymbolLiteralNothing — Nothing */ + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .lu { /* SymbolLiteralNull — Null */ + color: #b877ce; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .mi { /* SymbolLiteralNumeric — integer literals */ + color: #457e12; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .mf { /* SymbolLiteralNumeric — float literals */ + color: #457e12; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .s { /* SymbolLiteralString — string literals */ + color: #679f1e; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .se { /* SymbolLiteralString — "" escape inside string literals */ + color: #679f1e; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .o { /* SymbolOperator — +, -, =, <, >, &, ... */ + color: #80a1a5; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .ow { /* SymbolNamedOperator — And, Or, Not, Is, Mod, ... */ + color: #385ba9; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .na { /* SymbolAttribute — [Documentation(...)] attribute names */ + color: #bfbfbf; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nb { /* SymbolClass — Debug, Err */ + color: #a87300; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nc { /* SymbolClass — Class / CoClass / Enum / Interface / Type / Structure names */ + color: #a87300; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nf { /* SymbolFunction — Function / Sub / Property names */ + color: #b96300; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nn { /* SymbolModule — Module / Namespace / Imports targets */ + color: #89894d; + font-style: normal; + font-weight: normal; + text-decoration: none; + } + + .nv { /* SymbolVariable — Dim / Const / ReDim variable names */ + color: #939000; + font-style: normal; + font-weight: normal; + text-decoration: none; + } +} diff --git a/docs/assets/css/just-the-docs-combined.scss b/docs/assets/css/just-the-docs-combined.scss index 3f777688..1c38b237 100644 --- a/docs/assets/css/just-the-docs-combined.scss +++ b/docs/assets/css/just-the-docs-combined.scss @@ -1,6 +1,7 @@ --- --- {% include css/just-the-docs.scss.liquid color_scheme="light" %} +@import "custom/twinbasic-light"; span { color: $link-color; @@ -9,6 +10,7 @@ span { html.dark-mode { $color-scheme: dark; {% include css/just-the-docs.scss.liquid color_scheme="dark" %} + @import "custom/twinbasic-dark"; // --------------------------------------------------------------- // Workaround for the dark-mode + .search-active interaction. diff --git a/scripts/extract_theme_colors.py b/scripts/extract_theme_colors.py index bf7bd53f..aeefb6d5 100644 --- a/scripts/extract_theme_colors.py +++ b/scripts/extract_theme_colors.py @@ -1,18 +1,23 @@ """Extract Symbol* syntax-highlighting properties from twinBASIC IDE .theme files -and emit Rouge-compatible CSS. +and emit Rouge-compatible CSS/SCSS. -Produces three CSS files in scripts/themes/ — twinbasic-classic.css, -twinbasic-dark.css, twinbasic-light.css. Each rule uses the Rouge HTML -formatter class (e.g. .k, .nc, .cp) that docs/_plugins/twinbasic.rb emits -for that token, with the colors and font properties taken from the -corresponding tB theme Symbol. +Two outputs per run: + +1. scripts/themes/twinbasic-{classic,dark,light}.css -- flat CSS for inspection. + One rule per Rouge HTML formatter class (e.g. .k, .nc, .cp) that + docs/_plugins/twinbasic.rb emits, with colors and font properties taken + from the corresponding tB theme Symbol. + +2. docs/_sass/custom/_twinbasic-{light,dark}.scss -- SCSS partials shipped in + the site, scoped under `.language-tb .highlight` so they only repaint + fenced ```tb``` code blocks and leave OneLight/OneDark untouched on every + other language. Classic is inspection-only (no SCSS). The mapping is many-to-one: several tB theme Symbols share a single Rouge -class because the lexer doesn't distinguish them (e.g. SymbolMe, -SymbolLiteralBoolean, SymbolLiteralNothing all fall under Rouge Keyword .k -alongside SymbolKeyword). The mapping below picks the canonical tB Symbol -per Rouge class; the unmapped Symbols are listed at the bottom of each -emitted file for reference. +class because the lexer doesn't distinguish them (e.g. SymbolSub folds into +.nf alongside SymbolFunction). The mapping below picks the canonical tB +Symbol per Rouge class; the unmapped Symbols are listed at the bottom of +each emitted file for reference. The Classic theme inherits from Light and overrides a subset; the script resolves the inheritance so the emitted CSS reflects the effective theme. @@ -23,7 +28,9 @@ class because the lexer doesn't distinguish them (e.g. SymbolMe, from pathlib import Path THEMES_DIR = Path(os.environ["USERPROFILE"]) / "Desktop" / "twinBASIC_IDE_BETA_982" / "themes" -OUT_DIR = Path(__file__).resolve().parent / "themes" +REPO_ROOT = Path(__file__).resolve().parent.parent +CSS_OUT_DIR = REPO_ROOT / "scripts" / "themes" +SCSS_OUT_DIR = REPO_ROOT / "docs" / "_sass" / "custom" CSS_PROP = { "Color": "color", @@ -137,6 +144,29 @@ def render_css(theme: dict[str, dict[str, str]], header: str) -> str: return "".join(out) +def render_scss(theme: dict[str, dict[str, str]], header: str) -> str: + out = [f"/* {header} */\n"] + out.append("/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */\n") + out.append("/* Scoped under .language-tb .highlight so they only repaint tB fenced code blocks. */\n\n") + out.append(".language-tb .highlight {\n") + + rules = [] + for rouge, sym, comment in ROUGE_TO_SYMBOL: + props = theme.get(sym) + if not props: + continue + rule = [f" .{rouge} {{ /* Symbol{sym} — {comment} */\n"] + for key in ("Color", "FontStyle", "FontWeight", "TextDecoration"): + if key in props: + rule.append(f" {CSS_PROP[key]}: {props[key]};\n") + rule.append(" }\n") + rules.append("".join(rule)) + out.append("\n".join(rules)) + out.append("}\n") + + return "".join(out) + + def main() -> None: light = parse_theme(THEMES_DIR / "Light.theme") dark = parse_theme(THEMES_DIR / "Dark.theme") @@ -146,16 +176,17 @@ def main() -> None: for sym, props in classic_overrides.items(): classic.setdefault(sym, {}).update(props) - OUT_DIR.mkdir(parents=True, exist_ok=True) - (OUT_DIR / "twinbasic-light.css").write_text( + # Flat CSS for inspection. + CSS_OUT_DIR.mkdir(parents=True, exist_ok=True) + (CSS_OUT_DIR / "twinbasic-light.css").write_text( render_css(light, "twinBASIC Light theme - Rouge syntax highlighting"), encoding="utf-8", ) - (OUT_DIR / "twinbasic-dark.css").write_text( + (CSS_OUT_DIR / "twinbasic-dark.css").write_text( render_css(dark, "twinBASIC Dark theme - Rouge syntax highlighting"), encoding="utf-8", ) - (OUT_DIR / "twinbasic-classic.css").write_text( + (CSS_OUT_DIR / "twinbasic-classic.css").write_text( render_css( classic, "twinBASIC Classic theme - Rouge syntax highlighting (Light + Classic overrides)", @@ -163,6 +194,17 @@ def main() -> None: encoding="utf-8", ) + # SCSS partials shipped in the site (classic is inspection-only). + SCSS_OUT_DIR.mkdir(parents=True, exist_ok=True) + (SCSS_OUT_DIR / "_twinbasic-light.scss").write_text( + render_scss(light, "twinBASIC Light theme - Rouge syntax highlighting"), + encoding="utf-8", + ) + (SCSS_OUT_DIR / "_twinbasic-dark.scss").write_text( + render_scss(dark, "twinBASIC Dark theme - Rouge syntax highlighting"), + encoding="utf-8", + ) + if __name__ == "__main__": main() From deab8b4c2d9bfa090b454d677ae4ffc92eb18337 Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Sat, 16 May 2026 23:28:28 +0200 Subject: [PATCH 5/5] Also use the dark mode background color for dark mode code blocks. The contrast is better that way. --- docs/_sass/custom/_twinbasic-dark.scss | 14 ++++ .../extract_theme_colors.cpython-314.pyc | Bin 0 -> 13712 bytes scripts/extract_theme_colors.py | 78 +++++++++++++----- 3 files changed, 71 insertions(+), 21 deletions(-) create mode 100644 scripts/__pycache__/extract_theme_colors.cpython-314.pyc diff --git a/docs/_sass/custom/_twinbasic-dark.scss b/docs/_sass/custom/_twinbasic-dark.scss index a94e4928..4ec75280 100644 --- a/docs/_sass/custom/_twinbasic-dark.scss +++ b/docs/_sass/custom/_twinbasic-dark.scss @@ -171,3 +171,17 @@ text-decoration: none; } } + +/* tB CodePanelBackColor scoped to tB code-block containers (.language-tb). */ +/* The .language-tb class lives on the outer .highlighter-rouge div emitted */ +/* by kramdown for ```tb``` fenced blocks, so `.language-tb.highlighter-rouge` */ +/* (no space) hits the outer container and `.language-tb ` hits */ +/* the nested .highlight / pre / etc. The partial is imported inside */ +/* `html.dark-mode { ... }` by just-the-docs-combined.scss, so SCSS nesting */ +/* confines these rules to dark mode automatically. */ +.language-tb.highlighter-rouge, +.language-tb .highlight, +.language-tb pre.highlight, +.language-tb .highlight pre { + background-color: rgb(33, 33, 33); +} diff --git a/scripts/__pycache__/extract_theme_colors.cpython-314.pyc b/scripts/__pycache__/extract_theme_colors.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0991bdd332b851788e9a2acc8fefd04e0a84fe89 GIT binary patch literal 13712 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 dict[str, dict[str, str]]: - result: dict[str, dict[str, str]] = {} +def parse_theme(path: Path) -> dict[str, str]: + """Parse a .theme file into a flat `property -> value` dict. Properties with + an empty value (the IDE theme's `Name: ;` fall-back-to-parent form) are + omitted.""" + result: dict[str, str] = {} text = re.sub(r"/\*.*?\*/", "", path.read_text(encoding="utf-8"), flags=re.DOTALL) for raw in text.splitlines(): - line = raw.strip() - if not line.startswith("Symbol"): - continue - m = SYMBOL_LINE.match(line) + m = PROPERTY_LINE.match(raw.strip()) if not m: continue - sym, prop, value = m.group(1), m.group(2), m.group(3).strip().rstrip(";").strip() - result.setdefault(sym, {})[prop] = value + name, value = m.group(1), m.group(2).strip().rstrip(";").strip() + if not value: + continue + result[name] = value return result +def symbol_props(theme: dict[str, str]) -> dict[str, dict[str, str]]: + """Filter a flat theme dict down to its Symbol* entries, grouped by Symbol + name and keyed by the bare property suffix (Color / FontStyle / ...).""" + grouped: dict[str, dict[str, str]] = {} + for name, value in theme.items(): + m = SYMBOL_PROP.match(name) + if not m: + continue + sym, prop = m.group(1), m.group(2) + grouped.setdefault(sym, {})[prop] = value + return grouped + + def render_css(theme: dict[str, dict[str, str]], header: str) -> str: out = [f"/* {header} */\n"] out.append("/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */\n\n") @@ -144,7 +158,7 @@ def render_css(theme: dict[str, dict[str, str]], header: str) -> str: return "".join(out) -def render_scss(theme: dict[str, dict[str, str]], header: str) -> str: +def render_scss(theme: dict[str, dict[str, str]], header: str, code_bg: str | None = None) -> str: out = [f"/* {header} */\n"] out.append("/* Selectors are the Rouge HTML formatter classes emitted by docs/_plugins/twinbasic.rb. */\n") out.append("/* Scoped under .language-tb .highlight so they only repaint tB fenced code blocks. */\n\n") @@ -164,31 +178,49 @@ def render_scss(theme: dict[str, dict[str, str]], header: str) -> str: out.append("\n".join(rules)) out.append("}\n") + if code_bg: + out.append("\n") + out.append("/* tB CodePanelBackColor scoped to tB code-block containers (.language-tb). */\n") + out.append("/* The .language-tb class lives on the outer .highlighter-rouge div emitted */\n") + out.append("/* by kramdown for ```tb``` fenced blocks, so `.language-tb.highlighter-rouge` */\n") + out.append("/* (no space) hits the outer container and `.language-tb ` hits */\n") + out.append("/* the nested .highlight / pre / etc. The partial is imported inside */\n") + out.append("/* `html.dark-mode { ... }` by just-the-docs-combined.scss, so SCSS nesting */\n") + out.append("/* confines these rules to dark mode automatically. */\n") + out.append(".language-tb.highlighter-rouge,\n") + out.append(".language-tb .highlight,\n") + out.append(".language-tb pre.highlight,\n") + out.append(".language-tb .highlight pre {\n") + out.append(f" background-color: {code_bg};\n") + out.append("}\n") + return "".join(out) def main() -> None: light = parse_theme(THEMES_DIR / "Light.theme") dark = parse_theme(THEMES_DIR / "Dark.theme") - classic_overrides = parse_theme(THEMES_DIR / "Classic.theme") + classic = parse_theme(THEMES_DIR / "Classic.theme") - classic = {sym: dict(props) for sym, props in light.items()} - for sym, props in classic_overrides.items(): - classic.setdefault(sym, {}).update(props) + light_syms = symbol_props(light) + dark_syms = symbol_props(dark) + classic_syms = {sym: dict(props) for sym, props in light_syms.items()} + for sym, props in symbol_props(classic).items(): + classic_syms.setdefault(sym, {}).update(props) # Flat CSS for inspection. CSS_OUT_DIR.mkdir(parents=True, exist_ok=True) (CSS_OUT_DIR / "twinbasic-light.css").write_text( - render_css(light, "twinBASIC Light theme - Rouge syntax highlighting"), + render_css(light_syms, "twinBASIC Light theme - Rouge syntax highlighting"), encoding="utf-8", ) (CSS_OUT_DIR / "twinbasic-dark.css").write_text( - render_css(dark, "twinBASIC Dark theme - Rouge syntax highlighting"), + render_css(dark_syms, "twinBASIC Dark theme - Rouge syntax highlighting"), encoding="utf-8", ) (CSS_OUT_DIR / "twinbasic-classic.css").write_text( render_css( - classic, + classic_syms, "twinBASIC Classic theme - Rouge syntax highlighting (Light + Classic overrides)", ), encoding="utf-8", @@ -197,11 +229,15 @@ def main() -> None: # SCSS partials shipped in the site (classic is inspection-only). SCSS_OUT_DIR.mkdir(parents=True, exist_ok=True) (SCSS_OUT_DIR / "_twinbasic-light.scss").write_text( - render_scss(light, "twinBASIC Light theme - Rouge syntax highlighting"), + render_scss(light_syms, "twinBASIC Light theme - Rouge syntax highlighting"), encoding="utf-8", ) (SCSS_OUT_DIR / "_twinbasic-dark.scss").write_text( - render_scss(dark, "twinBASIC Dark theme - Rouge syntax highlighting"), + render_scss( + dark_syms, + "twinBASIC Dark theme - Rouge syntax highlighting", + code_bg=dark.get("CodePanelBackColor"), + ), encoding="utf-8", )