Skip to content

PhraseHQ/HighlightKit

Repository files navigation

HighlightKit

highlight.js-compatible syntax highlighting in pure Swift — no JavaScript engine, no WebView, no HTML round-trip.

CI status Swift 6.1+ iOS, macOS, tvOS, watchOS, visionOS MIT license Made by Phrase

HighlightKit is a ground-up Swift port of highlight.js 11.11.1, built and maintained by the team behind Phrase — it highlights every code block in Phrase's AI notes on iOS and macOS. 65 languages, token-exact output verified against the reference fixture corpus on every test run, and native NSAttributedString rendering on every Apple platform. See fidelity and differential-testing details.

Light Dark — adaptive themes built in
HighlightKit rendering Swift, JavaScript, and SQL with the GitHub light theme The same showcase rendered with the GitHub dark theme

Generated straight from the library by Tests/HighlightKitTests/ScreenshotGenerator.swift — regenerate with HIGHLIGHTKIT_SCREENSHOTS=.github/assets swift test --filter Screenshot.

Why

The popular syntax highlighters for Apple platforms wrap highlight.js in JavaScriptCore, render HTML, and parse that HTML back into an attributed string — a JS context per highlighter, string round-trips on every call, and a failable initializer for when the bundle doesn't load. HighlightKit is the same battle-tested grammar semantics as a native library:

  • Pure Swift 6.1 — strict concurrency on the runtime API, Synchronization primitives, and zero package dependencies. Highlighter() cannot fail.
  • Tokens, not HTML — every token is an NSRange (UTF-16, NSTextStorage-aligned) plus a scope name, so you can drive your own renderer or editor, or use the built-in NSAttributedString path.
  • Token-exact — 375 upstream-derived and adversarial fixtures, with expectations generated by the actual pinned highlight.js source, pin every release token for token.
  • Typed themes — GitHub and Xcode styles in light, dark, and adaptive variants: one attributed string follows the system appearance without re-highlighting.
  • Built for editors — continuation-threaded incremental highlighting (~40 µs per line on an M-series), and processor-bounded parallel automatic language detection across all 65 grammars.

Installation

Swift Package Manager. In Package.swift:

dependencies: [
    .package(url: "https://github.com/PhraseHQ/HighlightKit.git", from: "0.1.0")
]

Platforms: iOS 18+, macOS 15+, tvOS 18+, watchOS 11+, visionOS 2+. The core engine (parsing, tokens) uses Foundation and Synchronization; the rendering layer uses UIKit/AppKit colors and fonts.

Usage

One call for the common case:

import HighlightKit

label.attributedText = Highlighter.shared.attributedString(
    for: code, language: "swift", theme: .xcode
)

Pass no language to auto-detect. Themes: .github / .xcode (adaptive), their Light/Dark variants, by-name lookup with HighlightTheme.named("github-dark"), or build your own from typed scope styles and a font:

let theme = HighlightTheme(
    foregroundColor: .black,
    backgroundColor: .white,
    font: .init(name: "SF Mono", size: 13),
    styles: [
        .keyword: ScopeStyle(color: .systemPink, bold: true),
        .string: ScopeStyle(color: .systemRed),
        .comment: ScopeStyle(color: .gray, italic: true),
        "my-custom-scope": ScopeStyle(color: .cyan), // open set
    ]
)

HighlightScope is typed like Notification.Name — autocomplete and typo safety for the ~50 standard scopes, string literals for custom ones, and dot-path scopes (.titleFunction) resolve through structural fallback.

Tokens

let result = Highlighter.shared.highlight(code, as: "swift")
for token in result.tokens {
    print(token.range, token.scope)   // NSRange + "keyword", "title.function", …
}

Automatic language detection

let result = await Highlighter.shared.highlightAuto(code) // processor-bounded parallel scan
print(result.language ?? "plain", result.relevance, result.secondBest?.language ?? "-")

The async overload runs candidates through a processor-bounded child-task window. When not cancelled, it returns the same ranked result as the synchronous overload; after cancellation it ranks only candidates that completed.

Incremental highlighting (editors)

Highlight line by line, threading parser state, so edits only re-highlight what changed — multi-line strings and block comments carry across lines:

var continuation: Continuation?
for chunk in linesIncludingTerminators {
    let result = Highlighter.shared.highlight(chunk, as: "swift", continuation: continuation)
    render(result.tokens, for: chunk)
    continuation = result.continuation
}

After an edit, re-highlight forward until the new continuation equals the stored end state. A line-isolated regex cannot observe lookaround or boundaries across a chunk edge, so whole-block highlighting remains the exact path for constructs that depend on cross-line regex context. See the documented incremental limits.

Custom grammars

let highlighter = Highlighter(languages: [.swift, .javascript]) // just what you need
highlighter.register(myLanguage)                                // or your own grammar

Grammars compile lazily on first use and are cached per instance; the shared instance's cost scales with the languages you actually highlight.

Performance

Measured in a production release build on an Apple M3 Max (see BENCHMARKS.md for the dated environment, workloads, and reproduction protocol):

Workload Result
JavaScript throughput (62 KB, grammar-heavy) 1.12 MU/s (≈ MB/s for ASCII)
Swift throughput (real Swift source) 1.84 MU/s
SQL / JSON throughput ~2 MU/s
Incremental JavaScript, per line (the editor path) ~36 µs
Auto-detect across all 65 languages, warm 3.3 ms
NSAttributedString rendering ~7.6 MU/s
Cold start (first highlight, incl. grammar compile) ~4 ms

The engine replaces highlight.js's combined-alternation scan with per-rule windowed match caching plus shape-specialized prefilters — roughly 10× the naive port. Every optimization was adopted or rejected on measurements, and differential test suites keep each hand-written fast path pinned to ICU semantics.

Languages

All 65 grammars ship in the package; Highlighter.shared.languageNames lists them. Ada, Apache, AppleScript, ARM assembly, Bash, BASIC, C, Clojure, CMake, CoffeeScript, C++, C#, CSS, Dart, Delphi, Diff, Dockerfile, DOS, Elm, Erlang, Fortran, F#, Go, Groovy, Haskell, HTTP, INI/TOML, Java, JavaScript, JSON, Kotlin, Leaf, Less, Lisp, LLVM IR, Lua, Makefile, Markdown, MATLAB, Nginx, Nim, Nix, Objective-C, OCaml, Perl, PHP, plaintext, PowerShell, Prolog, Properties, Python, R, Ruby, Rust, Scala, Scheme, SCSS, Shell session, SQL, Stylus, Swift, TypeScript, Vim script, XML/HTML, YAML.

Testing

swift test runs the unit, integration, stress, and 375-case token-exact fidelity suites, including differential and adversarial checks for specialized matcher paths, incremental-state regressions, hostile-input handling, and memory-stability stress. Run concurrency instrumentation separately after registry, continuation, callback, or other shared-state changes:

swift test --sanitize=thread \
  --filter 'LanguageRegistryTests|IncrementalTests|StressTests|CoreCoverageCompletionTests'

Phrase — Turn Recordings & PDFs into AI Notes (app icon)

Capture anything. Turn it into notes you can trust.
Phrase turns recordings, PDFs, images, and videos into source-backed AI notes,
slide decks, study tools, and dashboards — Apple-native, for notes that need proof.
HighlightKit is the engine that highlights every code block in Phrase.

Download Phrase on the App Store   Phrase website   Follow Phrase on X


Contributing

See CONTRIBUTING.md — the short version: keep the 375 token-exact fixtures green, pin matcher fast paths with differential tests, and bring numbers for performance claims. Security reports: SECURITY.md.

License

MIT — see LICENSE. HighlightKit derives from highlight.js (BSD 3-Clause); see THIRD_PARTY_NOTICES.md.

Built by Phrase.

About

Syntax highlighter in pure Swift. No JavaScript, no WebView, no HTML round-trip. One Swift core → iOS, macOS, tvOS, watchOS, visionOS. NSRange tokens · NSAttributedString. 36 µs/line incremental re-highlighting for block editors. By Phrase (phrase.so).

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages