feat: integrate syntax highlighting into render pipeline

- src/render.rs: Rewrite with event-based pulldown-cmark parsing.
  Intercepts CodeBlock events and applies tree-sitter highlighting.
  4 new tests covering highlighted and unhighlighted code blocks.

- src/highlight.rs: Fix HtmlRenderer callback to return attributes
  only (not full span tags). Static HTML_ATTRS array for zero-alloc.

- static/style.css: Add 20 hl-* CSS classes with CSS variables.
  Light and dark mode via prefers-color-scheme.

Supported languages: rust, bash, json. Phase 2 complete.
This commit is contained in:
Timothy DeHerrera
2026-01-24 20:47:31 -07:00
parent ba5e97dfb7
commit c145bf86b9
4 changed files with 381 additions and 38 deletions

View File

@@ -40,7 +40,9 @@
}
/* Reset */
*, *::before, *::after {
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
@@ -62,7 +64,9 @@ body {
}
/* Layout */
nav, main, footer {
nav,
main,
footer {
width: 100%;
max-width: var(--max-width);
margin: 0 auto;
@@ -89,16 +93,28 @@ footer {
}
/* Typography */
h1, h2, h3 {
h1,
h2,
h3 {
line-height: 1.3;
margin-block: var(--space-lg) var(--space-md);
}
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.25rem; }
h1 {
font-size: 2rem;
}
h1:first-child, h2:first-child, h3:first-child {
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
h1:first-child,
h2:first-child,
h3:first-child {
margin-top: 0;
}
@@ -117,7 +133,8 @@ a:hover {
}
/* Code */
code, pre {
code,
pre {
font-family: var(--font-mono);
font-size: 0.9em;
}
@@ -142,7 +159,8 @@ pre code {
}
/* Lists */
ul, ol {
ul,
ol {
margin-block: var(--space-md);
padding-left: var(--space-lg);
}
@@ -249,3 +267,117 @@ article.post header {
font-size: 1.25rem;
color: var(--text-muted);
}
/* Syntax Highlighting */
:root {
--hl-keyword: #d73a49;
--hl-string: #22863a;
--hl-comment: #6a737d;
--hl-function: #6f42c1;
--hl-type: #005cc5;
--hl-number: #005cc5;
--hl-operator: #d73a49;
--hl-variable: #24292e;
--hl-constant: #005cc5;
--hl-property: #005cc5;
--hl-punctuation: #24292e;
--hl-attribute: #22863a;
}
@media (prefers-color-scheme: dark) {
:root {
--hl-keyword: #f97583;
--hl-string: #9ecbff;
--hl-comment: #6a737d;
--hl-function: #b392f0;
--hl-type: #79b8ff;
--hl-number: #79b8ff;
--hl-operator: #f97583;
--hl-variable: #e1e4e8;
--hl-constant: #79b8ff;
--hl-property: #79b8ff;
--hl-punctuation: #e1e4e8;
--hl-attribute: #85e89d;
}
}
.hl-keyword {
color: var(--hl-keyword);
}
.hl-string {
color: var(--hl-string);
}
.hl-comment {
color: var(--hl-comment);
font-style: italic;
}
.hl-function {
color: var(--hl-function);
}
.hl-function-builtin {
color: var(--hl-function);
}
.hl-type {
color: var(--hl-type);
}
.hl-type-builtin {
color: var(--hl-type);
}
.hl-number {
color: var(--hl-number);
}
.hl-operator {
color: var(--hl-operator);
}
.hl-variable {
color: var(--hl-variable);
}
.hl-variable-builtin {
color: var(--hl-variable);
}
.hl-variable-parameter {
color: var(--hl-variable);
}
.hl-constant {
color: var(--hl-constant);
}
.hl-constant-builtin {
color: var(--hl-constant);
}
.hl-property {
color: var(--hl-property);
}
.hl-punctuation {
color: var(--hl-punctuation);
}
.hl-punctuation-bracket {
color: var(--hl-punctuation);
}
.hl-punctuation-delimiter {
color: var(--hl-punctuation);
}
.hl-attribute {
color: var(--hl-attribute);
}
.hl-constructor {
color: var(--hl-type);
}