Skip to content

Gutters

The gutter system displays markers (line numbers, breakpoint indicators, etc.) alongside the editor content.

Line numbers

The simplest gutter is the built-in line number gutter:

import com.monkopedia.kodemirror.view.lineNumbers

// Add to your extensions:
basicSetup + lineNumbers

lineNumbers is a top-level Extension property — just add it to your extensions list.

Custom gutter markers

Create a custom gutter by implementing GutterMarker and providing a GutterConfig:

import com.monkopedia.kodemirror.view.*

class BreakpointMarker : GutterMarker() {
    @Composable
    override fun Content(theme: EditorTheme) {
        Text("●", color = Color.Red)
    }
}

val breakpointGutter = gutter(GutterConfig(
    cssClass = "cm-breakpoints",
    lineMarker = { view, lineFrom ->
        if (isBreakpointLine(view.state, lineFrom)) BreakpointMarker()
        else null
    }
))

The lineMarker callback receives the EditorSession and the character offset of the line start (line.from), not the line number. Return a GutterMarker to display something, or null for no marker.

GutterMarker

GutterMarker is an abstract class with a Compose Content() method (replacing toDOM() in upstream CodeMirror):

abstract class GutterMarker : RangeValue() {
    @Composable
    abstract fun Content(theme: EditorTheme)

    open val elementClass: String? get() = null
}

GutterConfig

Property Type Description
cssClass String? CSS class for the gutter column
lineMarker (EditorSession, Int) -> GutterMarker? Called per line with (view, lineFrom)
lineMarkerChange (ViewUpdate) -> Boolean When to re-call lineMarker
renderEmptyElements Boolean Render markers even when null
initialSpacer (EditorSession) -> GutterMarker Spacer for initial width measurement
updateSpacer (GutterMarker, ViewUpdate) -> GutterMarker Update the spacer

Tracking breakpoints with a StateField

A complete pattern storing breakpoints in a StateField:

private val toggleBreakpoint = StateEffect.define<LineNumber>()

private class BreakpointMarker : GutterMarker() {
    @Composable
    override fun Content(theme: EditorTheme) {
        Box(
            modifier = Modifier
                .size(12.dp)
                .clip(CircleShape)
                .background(Color(0xFFE06C75))
        )
    }
}

private val breakpointState: StateField<Set<LineNumber>> = StateField.define(
    StateFieldSpec(
        create = { emptySet() },
        update = { value, tr ->
            var result = value
            for (effect in tr.effects) {
                val e = effect.asType(toggleBreakpoint)
                if (e != null) {
                    val line = e.value
                    result = if (line in result) result - line else result + line
                }
            }
            result
        }
    )
)

private val breakpointGutter = gutter(
    GutterConfig(
        type = GutterType.Custom("breakpoints"),
        lineMarker = { session, lineFrom ->
            val breakpoints = session.state.field(breakpointState)
            val lineNumber = session.state.doc.lineAt(DocPos(lineFrom)).number
            if (lineNumber in breakpoints) BreakpointMarker() else null
        },
        lineMarkerChange = { update ->
            update.transactions.any { tr ->
                tr.effects.any { it.asType(toggleBreakpoint) != null }
            }
        }
    )
)

Toggle a breakpoint by dispatching:

view.dispatch(TransactionSpec(
    effects = listOf(toggleBreakpoint.of(lineNumber))
))

Active line gutter

Highlight the gutter for the current line:

import com.monkopedia.kodemirror.view.highlightActiveLineGutter

lineNumbers + highlightActiveLineGutter + // ...

Based on the CodeMirror Gutter example.