diff --git a/crates/fe-web/assets/fe-code-block.js b/crates/fe-web/assets/fe-code-block.js index 50c9cf7ff8..cc4d0791c5 100644 --- a/crates/fe-web/assets/fe-code-block.js +++ b/crates/fe-web/assets/fe-code-block.js @@ -161,16 +161,7 @@ class FeCodeBlock extends HTMLElement { // Create shadow root once if (!this.shadowRoot) { this.attachShadow({ mode: "open" }); - var sheet = _getCodeBlockSheet(); - if (sheet) { - this.shadowRoot.adoptedStyleSheets = [sheet]; - } else { - // Fallback: clone page styles into shadow root - var pageStyles = document.querySelectorAll("style"); - for (var i = 0; i < pageStyles.length; i++) { - this.shadowRoot.appendChild(pageStyles[i].cloneNode(true)); - } - } + this._adoptStyles(); } this._render(); @@ -201,6 +192,30 @@ class FeCodeBlock extends HTMLElement { }); } + /** Adopt highlight styles into shadow root, with retry for late-loading stylesheets. */ + _adoptStyles() { + var sheet = _getCodeBlockSheet(); + if (sheet) { + this.shadowRoot.adoptedStyleSheets = [sheet]; + } else { + // Fallback: clone page styles into shadow root + var pageStyles = document.querySelectorAll("style"); + for (var i = 0; i < pageStyles.length; i++) { + this.shadowRoot.appendChild(pageStyles[i].cloneNode(true)); + } + // If no styles found at all, retry after stylesheets finish loading + if (pageStyles.length === 0 && !this._styleRetryDone) { + this._styleRetryDone = true; + var self = this; + window.addEventListener("load", function () { + _invalidateCodeBlockSheet(); + self._adoptStyles(); + self._render(); + }); + } + } + } + /** Look up an item by path in per-component or global index. */ _findItem(path) { var index = this._index || window.FE_DOC_INDEX; diff --git a/crates/fe-web/assets/fe-highlight.css b/crates/fe-web/assets/fe-highlight.css index 2fa36b69de..437ebf135d 100644 --- a/crates/fe-web/assets/fe-highlight.css +++ b/crates/fe-web/assets/fe-highlight.css @@ -1,57 +1,119 @@ -/* Fe syntax highlighting (tree-sitter) — One Dark inspired - Standalone version with hardcoded colors for embedding in Starlight/Astro. - Override these CSS custom properties to theme the syntax colors: - --fe-hl-keyword, --fe-hl-type, --fe-hl-type-interface, --fe-hl-type-variant, - --fe-hl-function, --fe-hl-string, --fe-hl-string-escape, --fe-hl-number, - --fe-hl-comment, --fe-hl-operator, --fe-hl-variable, --fe-hl-attribute, - --fe-hl-constant, --fe-hl-punctuation, - --fe-code-bg, --fe-code-border, --fe-code-text, --fe-code-font, - --fe-code-size, --fe-code-line-height */ - -/* Light mode syntax colors */ -.hl-keyword { color: var(--fe-hl-keyword, #7c3aed); } -.hl-type, .hl-type-builtin { color: var(--fe-hl-type, #b45309); } -.hl-type-interface { color: var(--fe-hl-type-interface, #9333ea); } -.hl-type-enum-variant { color: var(--fe-hl-type-variant, #a16207); } -.hl-function, .hl-function-definition, .hl-function-method { color: var(--fe-hl-function, #2563eb); } -.hl-string { color: var(--fe-hl-string, #16a34a); } -.hl-string-escape { color: var(--fe-hl-string-escape, #0d9488); } -.hl-number { color: var(--fe-hl-number, #c2410c); } -.hl-comment, .hl-comment-doc { color: var(--fe-hl-comment, #6b7280); font-style: italic; } -.hl-operator { color: var(--fe-hl-operator, #475569); } -.hl-property, .hl-variable-parameter { color: var(--fe-hl-variable, #dc2626); } -.hl-variable { color: var(--fe-hl-variable, #dc2626); } -.hl-attribute { color: var(--fe-hl-attribute, #c2410c); } -.hl-constant { color: var(--fe-hl-constant, #c2410c); font-weight: bold; } -.hl-punctuation-bracket, .hl-punctuation-delimiter, .hl-punctuation-special { color: var(--fe-hl-punctuation, #64748b); } - -/* Dark mode syntax colors */ +/* Fe syntax highlighting — CSS custom properties define all colors. + Light mode defaults, dark mode via prefers-color-scheme. + Host pages can override any --fe-* variable to re-theme. */ + +/* ============================================================================ + Color variables — the single source of truth + ============================================================================ */ + +/* Light mode */ +:root { + --fe-hl-keyword: #7c3aed; + --fe-hl-variable-builtin: #0891b2; + --fe-hl-type: #b08800; + --fe-hl-type-interface: #7c3aed; + --fe-hl-type-variant: #b08800; + --fe-hl-function: #2563eb; + --fe-hl-string: #16a34a; + --fe-hl-string-escape: #0d9488; + --fe-hl-number: #0891b2; + --fe-hl-comment: #9ca0b0; + --fe-hl-operator: #0891b2; + --fe-hl-variable: #0d9488; + --fe-hl-attribute: #7c3aed; + --fe-hl-constant: #0891b2; + --fe-hl-punctuation: #6b7280; + + --fe-code-bg: #eff1f5; + --fe-code-border: #ccd0da; + --fe-code-text: #4c4f69; + --fe-code-line-number: #9ca0b0; + --fe-code-font: "JetBrains Mono", "Fira Code", monospace; + --fe-code-size: 0.8rem; + --fe-code-line-height: 1.6; + --fe-code-radius: 6px; +} + +/* Dark mode: Catppuccin Mocha — via prefers-color-scheme or data-theme */ @media (prefers-color-scheme: dark) { - .hl-keyword { color: var(--fe-hl-keyword, #c678dd); } - .hl-type, .hl-type-builtin { color: var(--fe-hl-type, #e5c07b); } - .hl-type-interface { color: var(--fe-hl-type-interface, #e77ee7); } - .hl-type-enum-variant { color: var(--fe-hl-type-variant, #e7c77e); } - .hl-function, .hl-function-definition, .hl-function-method { color: var(--fe-hl-function, #61afef); } - .hl-string { color: var(--fe-hl-string, #98c379); } - .hl-string-escape { color: var(--fe-hl-string-escape, #56b6c2); } - .hl-number { color: var(--fe-hl-number, #d19a66); } - .hl-comment, .hl-comment-doc { color: var(--fe-hl-comment, #5c6370); font-style: italic; } - .hl-operator { color: var(--fe-hl-operator, #abb2bf); } - .hl-property, .hl-variable-parameter { color: var(--fe-hl-variable, #e06c75); } - .hl-variable { color: var(--fe-hl-variable, #e06c75); } - .hl-attribute { color: var(--fe-hl-attribute, #d19a66); } - .hl-constant { color: var(--fe-hl-constant, #d19a66); font-weight: bold; } - .hl-punctuation-bracket, .hl-punctuation-delimiter, .hl-punctuation-special { color: var(--fe-hl-punctuation, #abb2bf); } + :root:not([data-theme="light"]) { + --fe-hl-keyword: #cba6f7; + --fe-hl-type: #f9e2af; + --fe-hl-type-interface: #cba6f7; + --fe-hl-type-variant: #f9e2af; + --fe-hl-function: #89b4fa; + --fe-hl-string: #a6e3a1; + --fe-hl-string-escape: #94e2d5; + --fe-hl-number: #fab387; + --fe-hl-comment: #9399b2; + --fe-hl-operator: #94e2d5; + --fe-hl-variable: #cdd6f4; + --fe-hl-attribute: #f9e2af; + --fe-hl-constant: #fab387; + --fe-hl-punctuation: #9399b2; + + --fe-code-bg: #1e1e2e; + --fe-code-border: #313244; + --fe-code-text: #cdd6f4; + --fe-code-line-number: #6c7086; + } } -/* Smooth transition for SCIP symbol hover highlights. - feHighlight() sets inline styles; the browser tweens the change. */ +/* Explicit dark toggle override — Catppuccin Mocha */ +:root[data-theme="dark"] { + --fe-hl-keyword: #cba6f7; + --fe-hl-variable-builtin: #94e2d5; + --fe-hl-type: #f9e2af; + --fe-hl-type-interface: #cba6f7; + --fe-hl-type-variant: #f9e2af; + --fe-hl-function: #89b4fa; + --fe-hl-string: #a6e3a1; + --fe-hl-string-escape: #94e2d5; + --fe-hl-number: #94e2d5; + --fe-hl-comment: #6c7086; + --fe-hl-operator: #94e2d5; + --fe-hl-variable: #a6e3a1; + --fe-hl-attribute: #cba6f7; + --fe-hl-constant: #94e2d5; + --fe-hl-punctuation: #6c7086; + + --fe-code-bg: #1e1e2e; + --fe-code-border: #313244; + --fe-code-text: #cdd6f4; + --fe-code-line-number: #6c7086; +} + +/* ============================================================================ + Syntax highlight rules — just read variables, no fallbacks + ============================================================================ */ + +.hl-keyword { color: var(--fe-hl-keyword); } +.hl-variable-builtin { color: var(--fe-hl-variable-builtin); font-style: italic; } +.hl-type, .hl-type-builtin { color: var(--fe-hl-type); } +.hl-type-interface { color: var(--fe-hl-type-interface); } +.hl-type-enum-variant { color: var(--fe-hl-type-variant); } +.hl-function, .hl-function-definition, .hl-function-method { color: var(--fe-hl-function); } +.hl-string { color: var(--fe-hl-string); } +.hl-string-escape { color: var(--fe-hl-string-escape); } +.hl-number { color: var(--fe-hl-number); } +.hl-comment, .hl-comment-doc { color: var(--fe-hl-comment); font-style: italic; } +.hl-operator { color: var(--fe-hl-operator); } +.hl-property, .hl-variable-parameter { color: var(--fe-hl-variable); } +.hl-variable { color: var(--fe-hl-variable); } +.hl-attribute { color: var(--fe-hl-attribute); } +.hl-constant { color: var(--fe-hl-constant); font-weight: bold; } +.hl-punctuation-bracket, .hl-punctuation-delimiter, .hl-punctuation-special { color: var(--fe-hl-punctuation); } + +/* SCIP symbol hover highlight transitions */ [class*="sym-"] { transition: background 0.2s ease-out, text-decoration 0.2s ease-out, text-decoration-color 0.2s ease-out; } -/* custom element */ +/* ============================================================================ + component layout — reads variables via inheritance + ============================================================================ */ + fe-code-block { display: block; margin: 1rem 0; @@ -59,24 +121,19 @@ fe-code-block { .fe-code-block-wrapper { display: flex; - background: var(--fe-code-bg, #0d1117); - border: 1px solid var(--fe-code-border, #2d3748); - border-radius: var(--fe-code-radius, 6px); + background: var(--fe-code-bg); + border: 1px solid var(--fe-code-border); + border-radius: var(--fe-code-radius); overflow-x: auto; } -/* Tighter margins in inline contexts (member headers, code headers, etc.) - These target the fe-code-block element in the light DOM. The shadow DOM - wrapper styles (border, background) are controlled via CSS custom - properties that inherit through the shadow boundary. */ +/* Inline signature contexts: suppress border */ .member-header fe-code-block, .code-header fe-code-block, .implementor-sig fe-code-block { margin: 0; } -/* Inline signature contexts: suppress border, keep code background. - (these inherit into the shadow root where .fe-code-block-wrapper reads them). */ .member-header, .code-header, .implementor-sig { @@ -87,17 +144,17 @@ fe-code-block { display: flex; flex-direction: column; padding: 0.75rem 0.5rem; - border-right: 1px solid var(--fe-code-border, #2d3748); + border-right: 1px solid var(--fe-code-border); text-align: right; user-select: none; - color: var(--fe-code-line-number, #9ba3af); - font-family: var(--fe-code-font, "JetBrains Mono", "Fira Code", monospace); - font-size: var(--fe-code-size, 0.8rem); - line-height: var(--fe-code-line-height, 1.6); + color: var(--fe-code-line-number); + font-family: var(--fe-code-font); + font-size: var(--fe-code-size); + line-height: var(--fe-code-line-height); } .fe-line-numbers span { - line-height: var(--fe-code-line-height, 1.6); + line-height: var(--fe-code-line-height); } .fe-code-pre { @@ -106,20 +163,14 @@ fe-code-block { padding: 0.75rem 1rem; background: transparent; overflow-x: auto; - font-family: var(--fe-code-font, "JetBrains Mono", "Fira Code", monospace); - font-size: var(--fe-code-size, 0.8rem); - line-height: var(--fe-code-line-height, 1.6); + font-family: var(--fe-code-font); + font-size: var(--fe-code-size); + line-height: var(--fe-code-line-height); } .fe-code-pre code { font-family: inherit; font-size: inherit; line-height: inherit; - color: var(--fe-code-text, #334155); -} - -@media (prefers-color-scheme: dark) { - .fe-code-pre code { - color: var(--fe-code-text, #e4e4e4); - } + color: var(--fe-code-text); } diff --git a/crates/fe-web/assets/styles.css b/crates/fe-web/assets/styles.css index c60d098403..53e32bf328 100644 --- a/crates/fe-web/assets/styles.css +++ b/crates/fe-web/assets/styles.css @@ -1,4 +1,5 @@ -/* Light mode (default) */ +/* Page layout variables — light mode default. + Code/syntax variables are in fe-highlight.css. */ :root { --bg: #ffffff; --bg-secondary: #f8fafc; @@ -8,7 +9,6 @@ --accent-hover: #6366f1; --text-accent: #4f46e5; --border: #e2e8f0; - --fe-code-bg: #f1f5f9; --code-bg: var(--fe-code-bg); --hl-ref-bg: rgba(79, 70, 229, 0.08); @@ -17,18 +17,16 @@ --target-bg: rgba(79, 70, 229, 0.06); } -/* Dark mode */ @media (prefers-color-scheme: dark) { - :root { - --bg: #0f0f1a; - --bg-secondary: #16162a; - --text: #e2e8f0; - --text-muted: #9ba3af; - --accent: #6366f1; - --accent-hover: #c7d2fe; - --text-accent: #a5b4fc; - --border: #2d3748; - --fe-code-bg: #0d1117; + :root:not([data-theme="light"]) { + --bg: #1e1e2e; + --bg-secondary: #181825; + --text: #cdd6f4; + --text-muted: #9399b2; + --accent: #89b4fa; + --accent-hover: #b4d0fb; + --text-accent: #89b4fa; + --border: #313244; --code-bg: var(--fe-code-bg); --hl-ref-bg: rgba(99, 102, 241, 0.18); @@ -38,6 +36,23 @@ } } +:root[data-theme="dark"] { + --bg: #1e1e2e; + --bg-secondary: #181825; + --text: #cdd6f4; + --text-muted: #9399b2; + --accent: #89b4fa; + --accent-hover: #b4d0fb; + --text-accent: #89b4fa; + --border: #313244; + --code-bg: var(--fe-code-bg); + + --hl-ref-bg: rgba(99, 102, 241, 0.18); + --hl-def-bg: rgba(99, 102, 241, 0.30); + --hl-def-underline: rgba(99, 102, 241, 0.6); + --target-bg: color-mix(in srgb, var(--accent) 10%, var(--bg-secondary)); +} + * { box-sizing: border-box; margin: 0; padding: 0; } body { @@ -288,15 +303,27 @@ body { .kind-badge.const { background: #ede9fe; color: #5b21b6; } @media (prefers-color-scheme: dark) { - .kind-badge.struct { background: #2d4a3e; color: #7ee787; } - .kind-badge.enum { background: #3d3a2d; color: #e7c77e; } - .kind-badge.function, .kind-badge.fn { background: #2d3a4a; color: #7eb8e7; } - .kind-badge.trait { background: #4a2d4a; color: #e77ee7; } - .kind-badge.module, .kind-badge.mod { background: #2d2d4a; color: #9d7ee7; } - .kind-badge.contract { background: #4a3d2d; color: #e7b87e; } - .kind-badge.type { background: #2d4a4a; color: #7ee7e7; } - .kind-badge.const { background: #3a2d4a; color: #b87ee7; } -} + :root:not([data-theme="light"]) .kind-badge.struct { background: #2d4a3e; color: #7ee787; } + :root:not([data-theme="light"]) .kind-badge.enum { background: #3d3a2d; color: #e7c77e; } + :root:not([data-theme="light"]) .kind-badge.function, + :root:not([data-theme="light"]) .kind-badge.fn { background: #2d3a4a; color: #7eb8e7; } + :root:not([data-theme="light"]) .kind-badge.trait { background: #4a2d4a; color: #e77ee7; } + :root:not([data-theme="light"]) .kind-badge.module, + :root:not([data-theme="light"]) .kind-badge.mod { background: #2d2d4a; color: #9d7ee7; } + :root:not([data-theme="light"]) .kind-badge.contract { background: #4a3d2d; color: #e7b87e; } + :root:not([data-theme="light"]) .kind-badge.type { background: #2d4a4a; color: #7ee7e7; } + :root:not([data-theme="light"]) .kind-badge.const { background: #3a2d4a; color: #b87ee7; } +} +:root[data-theme="dark"] .kind-badge.struct { background: #2d4a3e; color: #7ee787; } +:root[data-theme="dark"] .kind-badge.enum { background: #3d3a2d; color: #e7c77e; } +:root[data-theme="dark"] .kind-badge.function, +:root[data-theme="dark"] .kind-badge.fn { background: #2d3a4a; color: #7eb8e7; } +:root[data-theme="dark"] .kind-badge.trait { background: #4a2d4a; color: #e77ee7; } +:root[data-theme="dark"] .kind-badge.module, +:root[data-theme="dark"] .kind-badge.mod { background: #2d2d4a; color: #9d7ee7; } +:root[data-theme="dark"] .kind-badge.contract { background: #4a3d2d; color: #e7b87e; } +:root[data-theme="dark"] .kind-badge.type { background: #2d4a4a; color: #7ee7e7; } +:root[data-theme="dark"] .kind-badge.const { background: #3a2d4a; color: #b87ee7; } /* Main item signature at top of page. The wrapper provides the visual box; the inner fe-code-block border is suppressed to avoid doubling. */ @@ -1309,15 +1336,23 @@ fe-doc-viewer, .fe-doc-viewer { .fe-nav-badge.const { background: #ede9fe; color: #5b21b6; } @media (prefers-color-scheme: dark) { - .fe-nav-badge.struct { background: #2d4a3e; color: #7ee787; } - .fe-nav-badge.enum { background: #3d3a2d; color: #e7c77e; } - .fe-nav-badge.fn { background: #2d3a4a; color: #7eb8e7; } - .fe-nav-badge.trait { background: #4a2d4a; color: #e77ee7; } - .fe-nav-badge.mod { background: #2d2d4a; color: #9d7ee7; } - .fe-nav-badge.contract { background: #4a3d2d; color: #e7b87e; } - .fe-nav-badge.type { background: #2d4a4a; color: #7ee7e7; } - .fe-nav-badge.const { background: #3a2d4a; color: #b87ee7; } -} + :root:not([data-theme="light"]) .fe-nav-badge.struct { background: #2d4a3e; color: #7ee787; } + :root:not([data-theme="light"]) .fe-nav-badge.enum { background: #3d3a2d; color: #e7c77e; } + :root:not([data-theme="light"]) .fe-nav-badge.fn { background: #2d3a4a; color: #7eb8e7; } + :root:not([data-theme="light"]) .fe-nav-badge.trait { background: #4a2d4a; color: #e77ee7; } + :root:not([data-theme="light"]) .fe-nav-badge.mod { background: #2d2d4a; color: #9d7ee7; } + :root:not([data-theme="light"]) .fe-nav-badge.contract { background: #4a3d2d; color: #e7b87e; } + :root:not([data-theme="light"]) .fe-nav-badge.type { background: #2d4a4a; color: #7ee7e7; } + :root:not([data-theme="light"]) .fe-nav-badge.const { background: #3a2d4a; color: #b87ee7; } +} +:root[data-theme="dark"] .fe-nav-badge.struct { background: #2d4a3e; color: #7ee787; } +:root[data-theme="dark"] .fe-nav-badge.enum { background: #3d3a2d; color: #e7c77e; } +:root[data-theme="dark"] .fe-nav-badge.fn { background: #2d3a4a; color: #7eb8e7; } +:root[data-theme="dark"] .fe-nav-badge.trait { background: #4a2d4a; color: #e77ee7; } +:root[data-theme="dark"] .fe-nav-badge.mod { background: #2d2d4a; color: #9d7ee7; } +:root[data-theme="dark"] .fe-nav-badge.contract { background: #4a3d2d; color: #e7b87e; } +:root[data-theme="dark"] .fe-nav-badge.type { background: #2d4a4a; color: #7ee7e7; } +:root[data-theme="dark"] .fe-nav-badge.const { background: #3a2d4a; color: #b87ee7; } /* Scroll offset for anchored elements (clears sticky headers) */ .member-item[id], .impl-block[id], .method-item[id], .children-section h2[id], diff --git a/crates/tree-sitter-fe/queries/highlights.scm b/crates/tree-sitter-fe/queries/highlights.scm index b9fe5f1fc3..904e335b27 100644 --- a/crates/tree-sitter-fe/queries/highlights.scm +++ b/crates/tree-sitter-fe/queries/highlights.scm @@ -91,7 +91,6 @@ "mut" "own" "recv" - "self" "struct" "super" "trait" @@ -104,6 +103,9 @@ "with" ] @keyword +; self as a variable-like builtin +"self" @variable.builtin + (break_statement) @keyword (continue_statement) @keyword (return_statement "return" @keyword)