Highlight Engine
Core engine that manages the hidden WebView and executes Highlight.js highlighting.
Thread safety: WebView is always accessed on the Main thread. Concurrent highlight calls are serialized via mutex.
How highlighting works
Highlight.js runs inside a hidden off-screen WebView. When you call highlight, the engine:
Tokenizes - calls
highlightCode(code, lang)viaevaluateJavascript(), which returns an HTML string where each token is wrapped in a<span class="hljs-keyword">(or similar). highlight.js only assigns class names - it does not apply any colors itself.Resolves colors - HighlightTheme lazily parses its CSS file via ThemeParser, which translates CSS rules like
.hljs-keyword { color: #7928a1 }into a map of"hljs-keyword" -> SpanStyle(color=Color(0xFF7928a1)). This is the bridge between CSS-based theming and Compose's styling model.Builds AnnotatedString -
HtmlToAnnotatedStringwalks the HTML token tree with jsoup, looks up each span's class name in the theme's color map, and applies the matching SpanStyle. The result is a fully colored AnnotatedString ready for ComposeText.
Lifecycle
The engine holds a hidden WebView resource and implements Closeable for safe resource management. Always call close (or destroy) when the engine is no longer needed.
When used inside a Composable, use rememberHighlightEngine() which calls destroy automatically via DisposableEffect. For manual usage (e.g. in a ViewModel), call close or destroy in onCleared(). Since highlighting APIs are suspend, prefer coroutine-friendly try/finally cleanup for scoped usage:
val engine = HighlightEngine(context.applicationContext)
try {
val result = engine.highlight(code, "kotlin", theme)
} finally {
engine.close()
}WebView requirement
This library requires WebView to be installed and enabled on the device. When WebView is unavailable (e.g. Android Go devices, during a system WebView update, or when disabled by MDM policy), all highlight methods return Result.failure wrapping HighlightException.WebViewInitFailed. Check for this specific exception type to distinguish WebView availability issues from JavaScript errors.
Composable usage (lower-level)
For most cases, prefer SyntaxHighlightedCode inside a HighlightThemeProvider - it handles the engine lifecycle automatically. Use rememberHighlightEngine directly only when you need lower-level control, such as calling highlightBothThemes or building a custom UI.
@Composable
fun MyCodeBlock(code: String) {
val engine = rememberHighlightEngine()
val theme = rememberTomorrowTheme()
var highlighted by remember(code) { mutableStateOf<AnnotatedString?>(null) }
LaunchedEffect(code) {
engine.highlight(code, "kotlin", theme).onSuccess { highlighted = it.annotated }
}
Text(text = highlighted ?: AnnotatedString(code))
}Manual usage (e.g. ViewModel or background work)
val engine = HighlightEngine(context.applicationContext)
// Suspend calls must run inside a coroutine (e.g. viewModelScope.launch).
viewModelScope.launch {
// Optional: warm up before first use to reduce first-call latency.
engine.initialize().onFailure { /* handle WebViewInitFailed if needed */}
val result = engine.highlight(
code = "val x = 42",
language = "kotlin",
theme = HighlightTheme.atomOneDark(),
)
result.onSuccess { highlighted ->
display(highlighted.annotated) // AnnotatedString
log("spans: ${highlighted.spanCount}") // 0 = unsupported language
log("time: ${highlighted.durationMs} ms")
}
}
// Release resources when done (e.g. in ViewModel.onCleared())
engine.destroy() // or engine.close()Highlight once, render in two themes
// Inside a coroutine (e.g. viewModelScope.launch or LaunchedEffect):
engine.highlightBothThemes(
code = sourceCode,
language = "typescript",
lightTheme = HighlightTheme.tomorrow(),
darkTheme = HighlightTheme.tomorrowNight(),
).onSuccess { result ->
val display = if (isDark) result.dark else result.light
}Properties
true once initialize has completed successfully (or the first highlight / highlightToHtml call has finished warming up the WebView).
Functions
Returns metadata for a Highlight.js language name or alias.
Full pipeline: tokenise → apply theme → convert to HighlightResult.
Highlights code with Highlight.js automatic language detection.
Highlights code once and produces a ThemedHighlightResult with both a light and a dark androidx.compose.ui.text.AnnotatedString.
Returns the version string of the bundled Highlight.js library (e.g. "11.11.1").
Highlights code and returns raw HTML with <span class="hljs-*"> tokens, together with the time taken for the JavaScript round-trip.
Warms up the hidden WebView and loads bridge.html.
Returns the list of language identifiers supported by the bundled Highlight.js.