Skip to content

Add syntax highlighting with bundled Prism.js and GitHub theme - #6

Merged
ronaldmannak merged 6 commits into
mainfrom
claude/code-block-syntax-highlighting-g6l056
Jul 6, 2026
Merged

Add syntax highlighting with bundled Prism.js and GitHub theme#6
ronaldmannak merged 6 commits into
mainfrom
claude/code-block-syntax-highlighting-g6l056

Conversation

@ronaldmannak

Copy link
Copy Markdown
Contributor

Summary

Adds production-ready syntax highlighting to code blocks using a curated Prism.js bundle running in JavaScriptCore. Includes a GitHub-flavored color palette that adapts to light/dark mode, normalization of fence info strings (e.g., C++cpp, golanggo), and intelligent caching to handle large streaming blocks efficiently.

Key Changes

  • Prism.js bundling infrastructure

    • Scripts/bundle-prism.sh and Scripts/bundle-prism.js: Automated build system that fetches Prism v1.29.0, resolves language dependencies topologically, concatenates components, and smoke-tests the bundle with sample code in all 65 supported languages
    • Scripts/prism-wrapper.js: JavaScript bridge that flattens Prism's nested token tree into simple {content, type, alias} objects and provides the tokenizeCode() entry point
  • Language normalization

    • PrismLanguageNormalizer.swift: Maps fence info strings to bundled grammar names, handling casing, aliases (c++→cpp, objective-c→objc, golang→go, vb.net→vbnet, etc.), and explicit plain-text markers
    • Comprehensive test coverage in PrismLanguageNormalizerTests.swift
  • Tokenizer improvements

    • PrismTokenizer: Added 16-entry LRU cache to avoid re-tokenizing identical (language, code) pairs during re-renders and width changes
    • PrismToken: Now carries optional alias field for grammar-specific type standardization (e.g., INI keyattr-name)
    • Parses alias information from Prism tokens for theme fallback coloring
  • GitHub theme

    • CodeBlockTheme.gitHub(): New preset using Primer syntax colors (keyword red, string blue, function purple, type orange, tag green, etc.) with separate light/dark palettes
    • Covers all predefined token types with intentional plain-text rendering for punctuation and operators (matching GitHub's design)
    • Test coverage in CodeBlockThemingTests.swift
  • Highlighting policy

    • CodeHighlightingPolicy.swift: Prevents O(n²) re-tokenization of large streaming blocks by deferring highlighting until the fence closes (threshold: 16 KB, hard limit: 512 KB)
    • Test coverage in CodeHighlightingPolicyTests.swift
  • Integration

    • Updated MarkdownRenderTheme to default to .gitHub() theme and added withCodeHighlighting() convenience method
    • Updated MarkdownAttributeBuilder to use language normalization before tokenization
    • End-to-end tests in PrismCodeHighlighterTests.swift and PrismTokenizerTests.swift
  • Token type additions

    • Extended PrismTokenType with .rule, .section, .parameter and added allPredefined collection for test assertions
  • Documentation

    • Updated README with syntax highlighting feature overview, language list, theme presets, and customization guidance

Implementation Details

  • The Prism bundle is generated deterministically from a pinned version (1.29.0) with a curated language list that includes ~65 languages across web, systems, functional, scripting, classic, and data/config categories
  • Dependency resolution uses depth-first topological sort to ensure correct load order (e.g., sparql pulls in turtle)
  • Smoke testing evaluates the bundle and tokenizes sample code in every language before writing output
  • Caching is keyed by (language, code) pairs with copy-on-write string storage to minimize memory overhead
  • Streaming blocks re-highlight live up to 16 KB; larger blocks render plain until the fence closes, then receive a single full pass
  • Unknown languages gracefully fall back to plain monospaced text

https://claude.ai/code/session_01NRUDZwowe1T836zcYLwQoo

claude added 4 commits July 4, 2026 17:41
…e + language normalization

The shipped prism-bundle.js threw during evaluation (sparql extends
turtle, which was never bundled), so tokenizeCode — defined after the
throw — never existed in the JSContext and every language silently fell
back to plain text. Highlighting has been completely dead.

- Recreate the missing Scripts/bundle-prism.sh (+ bundle-prism.js,
  prism-wrapper.js): pins prismjs 1.29.0, resolves component order from
  Prism's own components.json (no more hand-ordered dependency bugs),
  and refuses to write a bundle that fails a load/tokenize smoke test.
- Regenerate the bundle: 69 components incl. new basic, pascal, vbnet,
  makefile, ini, r, batch, cmake, julia, matlab, protobuf, hcl, nasm,
  armasm (147 KB). Every language in the CodeBlocks.md demo now
  tokenizes.
- Normalize fence info strings before grammar lookup (first word,
  lowercased, alias table: c++→cpp, golang→go, vb.net→vbnet, …) in new
  PrismLanguageNormalizer; previously the raw string was matched
  case-sensitively against grammar names.
- Emit Prism token aliases from the JS wrapper and use them as a theme
  color fallback, so grammar-specific types (INI keys, diff signs, asm
  registers) pick up standard colors.
- First tests for the subsystem: normalizer (all platforms) and
  JSC-gated tokenizer round-trip/coverage tests (Apple platforms).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRUDZwowe1T836zcYLwQoo
Fenced code re-renders on every streamed chunk, so an unbounded block
re-tokenized its whole accumulated text through JavaScriptCore each
time — O(n²) over a stream. Now:

- CodeHighlightingPolicy: blocks over 16 KB render plain while the
  fence is open and get one full highlight pass on close (the existing
  blockEnded → refreshBlock path); blocks over 512 KB never highlight.
- PrismTokenizer gains a 16-entry LRU keyed by (language, code), so
  verbatim re-renders of closed blocks (width changes, out-of-band
  refreshes) skip JavaScriptCore entirely.
- Truth-table tests for the policy (all platforms) and end-to-end
  highlighter tests (Apple platforms).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRUDZwowe1T836zcYLwQoo
- CodeBlockTheme.gitHub(): Primer syntax colors for light and dark,
  covering every predefined PrismTokenType (except intentionally
  plain-foreground ones like punctuation), enforced by a coverage test.
- New token types surfaced by the expanded grammar set: rule (CSS),
  section (INI headers), parameter (bash/PHP).
- MarkdownRenderTheme.default() now uses .gitHub(); .prismDefault()
  remains available for the previous look.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRUDZwowe1T836zcYLwQoo
…lighter example

- README: highlighting is on by default (bundled Prism via
  JavaScriptCore, GitHub palette, ~65 languages, alias normalization,
  streaming size guard), CodeBlockTheme presets, and the bundle
  regeneration workflow.
- The Splash example didn't compile: MarkdownRenderTheme properties are
  let and the protocol method is async. Add
  MarkdownRenderTheme.withCodeHighlighting(codeBlockTheme:codeHighlighter:)
  so swapping palettes or engines is a one-liner, and fix the example.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRUDZwowe1T836zcYLwQoo

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust, bundled Prism.js syntax highlighting integration for code blocks. It includes scripts to automatically resolve dependencies and bundle Prism grammars, a new GitHub-flavored theme, a normalization utility for fence info strings, a caching mechanism for tokenized blocks, and a highlighting policy to prevent performance degradation on large streaming blocks. The review feedback suggests a minor performance optimization in PrismLanguageNormalizer.swift to limit the string splitting operation to only the first word, avoiding unnecessary allocations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread Sources/PicoMarkdownView/Renderer/PrismLanguageNormalizer.swift
claude added 2 commits July 4, 2026 23:18
Review suggestion on #6: maxSplits: 1 avoids scanning/allocating past
the first whitespace-delimited word in normalize(), which runs on every
streamed chunk of a code block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRUDZwowe1T836zcYLwQoo
Both branches fixed the O(n²) streaming re-highlight in
MarkdownAttributeBuilder's fencedCode path: main by highlighting only
when the fence closes, this branch by highlighting live with a 16 KB
size guard (CodeHighlightingPolicy) that defers oversized blocks to a
single pass on close. Resolved by keeping the live+guard behavior —
small chat-sized blocks stay colored while streaming with bounded
per-chunk cost (and an LRU cache), and oversized blocks get main's
render-plain-then-highlight-on-close treatment via the same
blockEnded → refreshBlock path. Adopted main's structure of resolving
theme fonts/colors before the highlight branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRUDZwowe1T836zcYLwQoo
@ronaldmannak
ronaldmannak merged commit dded15d into main Jul 6, 2026
2 checks passed
@ronaldmannak
ronaldmannak deleted the claude/code-block-syntax-highlighting-g6l056 branch July 6, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants