Skip to content

Programmatic Changes

You can modify the document programmatically by dispatching transactions with change specifications.

Inserting text

Insert text at the beginning of the document:

                Button(onClick = {
                    session.insertAt(DocPos.ZERO, "// Inserted header\n")
                }) { Text("Insert at top") }

Appending text

Append text at the end of the document:

                Button(onClick = {
                    session.insertAt(session.state.doc.endPos, "\n// Appended footer")
                }) { Text("Append at end") }

Replacing a range

Replace the first line with new content:

                Button(onClick = {
                    val firstLineEnd = session.state.doc.line(LineNumber(1)).to
                    session.dispatch(
                        transactionSpec {
                            replace(
                                DocPos.ZERO,
                                firstLineEnd,
                                "// Replaced first line"
                            )
                        }
                    )

Deleting text

Omit insert to delete a range:

// Delete characters 0..5
view.dispatch(TransactionSpec(
    changes = ChangeSpec.Single(from = 0, to = 5)
))

Multiple changes

Use ChangeSpec.Multi to apply several changes at once. Positions refer to the original document — the system handles offsetting:

view.dispatch(TransactionSpec(
    changes = ChangeSpec.Multi(listOf(
        ChangeSpec.Single(from = 0, insert = InsertContent.StringContent("// header\n")),
        ChangeSpec.Single(from = 20, to = 25, insert = InsertContent.StringContent("new"))
    ))
))

Replacing the selection

state.replaceSelection() builds a TransactionSpec that replaces every selection range with the given text:

val spec = state.replaceSelection("inserted text")
view.dispatch(spec)

Per-range changes

state.changeByRange applies a function to each selection range independently, correctly handling multi-cursor edits:

val spec = state.changeByRange { range ->
    ChangeByRangeResult(
        changes = ChangeSpec.Single(
            range.from, range.to,
            insert = InsertContent.StringContent(
                state.sliceDoc(range.from, range.to).uppercase()
            )
        ),
        range = EditorSelection.range(
            range.from,
            range.from + (range.to - range.from)
        )
    )
}
view.dispatch(spec)

Composing and inverting changes

ChangeSet values support composition and inversion:

// Build a ChangeSet
val changes = state.changes(
    ChangeSpec.Single(from = 0, to = 5, insert = InsertContent.StringContent("Hi"))
)

// Apply to get a new document
val newDoc = changes.apply(state.doc)

// Create an undo operation
val undo = changes.invert(state.doc)

// Compose two sequential change sets
val combined = first.compose(second)

Mapping positions

When changes modify the document, use mapPos to translate positions from the old document to the new one:

val newPos = changes.mapPos(oldPos)

// Control which side of an insertion to stick to
val newPos = changes.mapPos(oldPos, assoc = 1) // after insertion

Based on the CodeMirror Changes example.