Skip to content

feat(mac): restore syntax highlighting in AppKit code blocks - #449

Merged
SergeSerb2 merged 3 commits into
mainfrom
surgecode/appkit-code-highlight
Aug 2, 2026
Merged

feat(mac): restore syntax highlighting in AppKit code blocks#449
SergeSerb2 merged 3 commits into
mainfrom
surgecode/appkit-code-highlight

Conversation

@SergeSerb2

Copy link
Copy Markdown
Owner

Summary

Code blocks have rendered monochrome since the AppKit port. Highlighting is back, done right for a reusable-cell world:

  • Async off the configure path (configure runs inside tableView(_:viewFor:)); a 120 ms settle debounce collapses a streaming fence's per-delta reconfigures into one highlight; a generation token drops results that land on a recycled cell.
  • Colors only: the highlighter's output is converted to UTF-16 foreground spans and applied onto existing TextKit storage — string, font, and measuredHeight are byte-identical before/after (test-asserted), so row heights never move.
  • Appearance-correct: palettes resolve under the view's own effectiveAppearance and refresh on appearance change (same pattern as fix(mac): AppKit markdown emphasis/appearance and sidebar selection fixes #441's chrome fix in this file).
  • One implementation for both renderers: CodeHighlighter now caches the scope-neutral result and serves AppKit and SwiftUI callers from one cache, one language map, one size bound (4 KB engine cap before anything enters JS, on top of the existing 128 KB cap).
  • Unmapped/plaintext fences stay monochrome, matching the retired SwiftUI behavior.

New suite AppKitCodeHighlightTests (6 tests: multi-color output, light/dark palettes, stale-generation drop, monochrome fallbacks, mismatched-text rejection, UTF-16/emoji offsets).

Carries the #447 compile fix (byte-identical commit; merges clean either order).

Follow-up noted by the implementer: threading fragment.isStreamingTail into the code view's configure would let settled blocks highlight immediately instead of after the 120 ms debounce — deferred to avoid crossing into #448's files.

Area

  • apps/mac — native macOS app
  • apps/windows
  • apps/mobile
  • apps/server
  • Shared packages or relay
  • Build, CI, or release tooling
  • Docs

Release size

  • size:XS
  • size:S
  • size:M
  • size:L
  • size:XL

Verification

  • pnpm run verify after the last edit (Swift-only diff → full Swift suite): pass
  • AppKitCodeHighlightTests 6/6

🤖 Generated with Claude Code

SergeSerb2 and others added 2 commits August 2, 2026 14:51
Two PRs merged into main on the same day: one added the
`requestTimeout(tag:)` case to `T3Error` so a wedged RPC handler cannot
pin the UI, and one added `UserFacingError`'s exhaustive switch over
`T3Error`. Neither saw the other, so `main` no longer compiles.

A request timeout is not a rejected session and not a dead transport —
the connection still answers `Ping` — so it gets its own message rather
than being folded into the reconnect advice or the generic fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SwiftUI renderer highlighted fenced code through the vendored
HighlightSwift bridge; the AppKit port typeset every fence in
`labelColor` and the feature was silently lost. Bring it back on the
native path without putting JavaScript anywhere near `configure`.

`configure` runs inside `tableView(_:viewFor:)`, so it only schedules:
a settle delay collapses a streaming fence's per-delta reconfigurations
into a single highlight once the block stops growing, and a generation
token — bumped by every configure and reset — drops any result that
comes back after the cell has been recycled onto another block.

The highlight contributes colors and nothing else. `AppKitCodeHighlight`
turns the highlighter's attributed result into UTF-16 foreground spans
and refuses to emit any when the returned text no longer matches the
block, because highlight.js round trips through generated HTML and one
lost character would shift every subsequent range. Applying spans writes
`.foregroundColor` over the existing storage, so the string, the font,
and `measuredHeight(for:)` are all untouched.

Palettes are baked per theme rather than dynamic, so the request carries
the view's own `effectiveAppearance` and `viewDidChangeEffectiveAppearance`
re-resolves it — the same reason PR #441 moved this view's chrome colors
into `updateLayer`. Work stays bounded: unmapped and plaintext fences
never enter JavaScript at all (matching the retired path, which did not
fall back to automatic detection), and anything above
`MarkdownPresentationEngine.maximumCodeCharacters` stays monochrome.

`CodeHighlighter` now caches the untranslated AppKit-scoped result and
translates to the SwiftUI attribute scope only for its SwiftUI callers,
so both renderers share one cache, one language mapping, and one size
bound instead of growing a second highlighter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SergeSerb2 SergeSerb2 added the size:M Normal feature or meaningful behavior change label Aug 2, 2026
…-highlight

# Conflicts:
#	apps/mac/Sources/SurgeCodeMac/Support/UserFacingError.swift
@SergeSerb2
SergeSerb2 merged commit 96db0be into main Aug 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M Normal feature or meaningful behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant