Skip to content

Highlight

re-ovo edited this page Oct 28, 2026 · 1 revision

Highlight

Module: com.github.re-ovo.bettertext:highlight, package me.rerere.bettertext.highlight.

A pure Kotlin syntax highlighter: highlight.js 11.11.1's parser and 30 of its grammars, ported to Kotlin. It turns code into an AnnotatedString, which BetterText (or Compose's Text) draws. It has no dependency on the other modules.

In composition

@Composable
fun Code(code: String, language: String) {
    BetterText(
        text = rememberHighlightedCode(code, language),
        modifier = Modifier.horizontalScroll(rememberScrollState()),
        style = TextStyle(
            color = HighlightTextColorPalette.Default.fallback,
            fontFamily = FontFamily.Monospace,
            fontSize = 13.sp,
            fontFeatureSettings = CODE_FONT_FEATURES,
        ),
    )
}
@Composable
fun rememberHighlightedCode(
    code: String,
    language: String,
    colors: HighlightTextColorPalette = HighlightTextColorPalette.Default,
): AnnotatedString
  • language is a highlight.js name or alias, case-insensitive (kotlin, kt, py, ts…). An unsupported or empty language returns the code unhighlighted.
  • Code longer than MAX_HIGHLIGHTED_LENGTH (4096 characters) is returned unhighlighted, since it's highlighted again on every change, for example while a reply streams in.
  • Only token colors (and italic comments, bold strong) are set. Give the text style the palette's fallback color for plain code, and a monospace font.
  • CODE_FONT_FEATURES turns off ligatures, so fonts such as JetBrains Mono show the characters as typed.

It uses the CodeHighlighter from LocalCodeHighlighter, one shared instance by default.

Colors

HighlightTextColorPalette maps highlight.js scopes to 15 color slots. Two palettes are built in:

Palette For
HighlightTextColorPalette.AtomOneDark (also Default) dark backgrounds, e.g. Color(0xFF282C34)
HighlightTextColorPalette.AtomOneLight light backgrounds, e.g. Color(0xFFFAFAFA)

Pick one by the background:

val background = MaterialTheme.colorScheme.surfaceContainerHigh
val colors = if (background.luminance() < 0.5f) {
    HighlightTextColorPalette.AtomOneDark
} else {
    HighlightTextColorPalette.AtomOneLight
}
val text = rememberHighlightedCode(code, language, colors)

Make your own with copy, or by passing every slot:

val colors = HighlightTextColorPalette.AtomOneDark.copy(
    keyword = Color(0xFFFF79C6),
    string = Color(0xFFF1FA8C),
)
Slot Scopes
keyword keyword, doctag, formula
string string, regexp, addition
number number, type, selector-class, selector-attr, selector-pseudo
comment comment, quote (italic)
function title, function, symbol, bullet, link, meta, selector-id
operator literal, char, operator
punctuation punctuation, params
className built_in
property section, name, selector-tag, deletion, subst, property
variable variable
tag tag
attrName attr, template-variable
attrValue attribute
fallback emphasis (italic), strong (bold), and any unknown scope

boolean is part of the palette but no scope uses it at the moment. A tiered scope such as title.function or char.escape falls back to its parent scope when it has no entry of its own, as highlight.js's CSS themes do.

Outside composition

val highlighter = CodeHighlighter()

highlighter.supports("kotlin")                     // true
val text: AnnotatedString = highlighter.highlightToAnnotatedString(code, "kotlin", colors)
val tokens: List<HighlightToken> = highlighter.highlight(code, "kotlin")

highlight returns tokens that concatenate to the code: HighlightToken.Plain(content) and HighlightToken.Styled(content, type), where type is the highlight.js scope (keyword, title.function…). Use them to build your own styling; AnnotatedString.Builder.buildHighlightText(token, colors) appends one token with the palette's style.

Each language is compiled the first time it's used. A CodeHighlighter isn't thread-safe: compiled languages keep matcher state, so call it from one thread at a time, or use one instance per thread. To highlight in the background, give the worker its own instance:

private val backgroundHighlighter = CodeHighlighter()
private val highlightDispatcher = Dispatchers.Default.limitedParallelism(1)

suspend fun highlight(code: String, language: String) = withContext(highlightDispatcher) {
    backgroundHighlighter.highlightToAnnotatedString(code, language)
}

Languages

Language Names and aliases
Bash bash, sh, zsh, shell
C c, h
C++ cpp, cc, c++, h++, hpp, hh, hxx, cxx
C# csharp, cs, c#
CMake cmake, cmake.in
CSS css
Dart dart
Diff diff, patch
Dockerfile dockerfile, docker
GLSL glsl
Go go, golang
INI / TOML ini, toml
Java java, jsp
JavaScript javascript, js, jsx, mjs, cjs
JSON json, jsonc, json5
Kotlin kotlin, kt, kts
LaTeX latex, tex
Lua lua, pluto
Markdown markdown, md, mkdown, mkd
PHP php
PowerShell powershell, pwsh, ps, ps1
Properties properties
Python python, py, gyp, ipython
Ruby ruby, rb, gemspec, podspec, thor, irb
Rust rust, rs
SQL sql
Swift swift
TypeScript typescript, ts, tsx, mts, cts
XML / HTML xml, html, xhtml, rss, atom, xjb, xsd, xsl, plist, wsf, svg
YAML yaml, yml

Embedded languages are highlighted too where highlight.js does it, such as CSS and JavaScript inside HTML.

With Markdown

Register a code block component that uses the block's language; see Markdown › Code blocks.

Clone this wiki locally