Decorations¶
Decorations add visual annotations to the editor — highlighting ranges, inserting widgets, styling lines — without modifying the document text.
Mark decorations¶
A mark decoration applies a SpanStyle to a range of text:
private val highlightMark = Decoration.mark(
MarkDecorationSpec(
style = SpanStyle(
background = Color(0x4400FF00),
fontWeight = FontWeight.Bold
)
)
)
In upstream CodeMirror, marks add CSS classes. In Kodemirror, marks apply Compose SpanStyle values via AnnotatedString.
Widget decorations¶
A widget decoration inserts a @Composable at a document position:
import androidx.compose.runtime.Composable
import androidx.compose.foundation.text.BasicText
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp
class CheckmarkWidget : WidgetType() {
@Composable
override fun Content() {
BasicText(
text = "\u2713",
style = TextStyle(color = Color.Green, fontSize = 12.sp)
)
}
override fun eq(other: WidgetType): Boolean =
other is CheckmarkWidget
}
val widgetDeco = Decoration.widget(WidgetDecorationSpec(
widget = CheckmarkWidget(),
side = 1 // 1 = after the position, -1 = before
))
Upstream CodeMirror widgets implement toDOM() returning an HTMLElement. Kodemirror widgets implement @Composable Content().
Line decorations¶
A line decoration styles an entire line:
val errorLine = Decoration.line(LineDecorationSpec(
style = SpanStyle(background = Color(0x20FF0000))
))
Replace decorations¶
A replace decoration hides a range, optionally replacing it with a widget:
val foldDeco = Decoration.replace(ReplaceDecorationSpec(
widget = FoldPlaceholderWidget() // optional
))
Building decoration sets¶
Decorations are collected into a DecorationSet using a builder. Ranges must be added in ascending from order:
val builder = RangeSetBuilder<Decoration>()
builder.add(from = 5, to = 10, value = highlightDeco)
builder.add(from = 20, to = 20, value = widgetDeco)
val decoSet: DecorationSet = builder.finish()
Providing decorations via a state field¶
To make decorations persistent and reactive, store them in a StateField and provide them through the decorations facet:
val highlightField = StateField.define(StateFieldSpec(
create = { state -> buildHighlights(state) },
update = { decos, tr ->
if (tr.docChanged) buildHighlights(tr.state)
else decos
},
provide = { field ->
decorations.from(field)
}
))
fun buildHighlights(state: EditorState): DecorationSet {
val builder = RangeSetBuilder<Decoration>()
// Find ranges to highlight and add them...
return builder.finish()
}
Include highlightField in your extensions list — the provide function feeds the field's value into the decorations facet automatically.
Providing decorations via a view plugin¶
For decorations that depend on view state (like viewport), use a ViewPlugin:
private class DecorationPlugin(
session: EditorSession
) : PluginValue, DecorationSource {
override var decorations: DecorationSet = buildDecorations(session.state.doc)
override fun update(update: ViewUpdate) {
if (update.docChanged) {
decorations = buildDecorations(update.state.doc)
}
}
}
private val decorationPlugin = ViewPlugin.fromDecorationSource { session ->
DecorationPlugin(session)
}
Include highlightPlugin.asExtension() in your extensions.
Related API¶
Decoration— base decoration classMarkDecorationSpec— spec for mark decorationsWidgetDecorationSpec— spec for widget decorationsLineDecorationSpec— spec for line decorationsReplaceDecorationSpec— spec for replace decorationsWidgetType— base class for custom widgetsRangeSetBuilder— builds ordered decoration setsViewPlugin— view plugin for viewport-aware decorationsStateField— state field for persistent decorations
Based on the CodeMirror Decoration example.