Getting Started¶
This tutorial walks through creating a minimal Kodemirror editor from scratch in a Kotlin Multiplatform / Compose Multiplatform project.
Prerequisites¶
- JDK 11 or later (JDK 21 recommended)
- Android Studio or IntelliJ IDEA with Kotlin plugin
- Gradle 8.x
1. Create a Compose Multiplatform project¶
Use the Kotlin Multiplatform Wizard or create a new Compose Multiplatform project in your IDE.
2. Add dependencies¶
In your module's build.gradle.kts, add the Kodemirror dependencies:
dependencies {
// Core
implementation("com.monkopedia.kodemirror:state:0.3.5")
implementation("com.monkopedia.kodemirror:view:0.3.5")
// Commands (undo, redo, indentation, etc.)
implementation("com.monkopedia.kodemirror:commands:0.3.5")
// Language infrastructure (syntax highlighting, bracket matching)
implementation("com.monkopedia.kodemirror:language:0.3.5")
// Language support (pick the languages you need)
implementation("com.monkopedia.kodemirror:lang-javascript:0.3.5")
// Optional: theme
implementation("com.monkopedia.kodemirror:theme-one-dark:0.3.5")
// Optional: all-in-one setup bundle
// implementation("com.monkopedia.kodemirror:basic-setup:0.3.5")
}
3. Create a minimal editor¶
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.monkopedia.kodemirror.commands.*
import com.monkopedia.kodemirror.lang.javascript.javascript
import com.monkopedia.kodemirror.language.defaultHighlightStyle
import com.monkopedia.kodemirror.language.syntaxHighlighting
import com.monkopedia.kodemirror.state.plus
import com.monkopedia.kodemirror.view.*
@Composable
fun MyEditor() {
val session = rememberEditorSession(
doc = "console.log(\"Hello, Kodemirror!\")",
extensions = lineNumbers +
history() +
javascript().extension +
syntaxHighlighting(defaultHighlightStyle) +
keymapOf(defaultKeymap)
)
KodeMirror(
session = session,
modifier = Modifier.fillMaxSize()
)
}
This gives you: - A JavaScript-aware editor with syntax highlighting - Line numbers in the gutter - Undo/redo with Ctrl-Z / Ctrl-Y / Ctrl-Shift-Z — history() installs its own key bindings, so there is nothing extra to add to the keymap - Standard cursor movement and selection commands
Note that lineNumbers is a value, not a function, and that language factories such as javascript() return a LanguageSupport — use its .extension property to combine it with other extensions.
Or use basicSetup for a batteries-included experience:
import com.monkopedia.kodemirror.basicsetup.basicSetup
import com.monkopedia.kodemirror.lang.javascript.javascript
import com.monkopedia.kodemirror.state.plus
import com.monkopedia.kodemirror.view.*
@Composable
fun MyEditor() {
val session = rememberEditorSession(
doc = "console.log(\"Hello!\")",
extensions = basicSetup + javascript().extension
)
KodeMirror(session = session, modifier = Modifier.fillMaxSize())
}
4. Understanding the pattern¶
Kodemirror uses rememberEditorSession to manage editor state internally:
- Session —
rememberEditorSessioncreates anEditorSessionthat holds theEditorState(document, selection, extension state) and handles updates. - View —
KodeMirrorrenders the session as a Compose UI. - Transactions — when the user types or triggers a command, a
Transactionis created with the changes and applied automatically.
Extensions are combined using the + operator:
5. Adding more features¶
Bracket matching¶
import com.monkopedia.kodemirror.language.bracketMatching
// Add to your extensions:
basicSetup + javascript().extension + bracketMatching()
Autocompletion¶
import com.monkopedia.kodemirror.autocomplete.*
basicSetup + autocompletion(CompletionConfig(
override = listOf(
completeFromList(listOf(
Completion(label = "console", type = "variable"),
Completion(label = "document", type = "variable"),
Completion(label = "window", type = "variable")
))
)
))
Search¶
Custom theme¶
import com.monkopedia.kodemirror.view.*
val myTheme = EditorTheme(
background = Color(0xFF1E1E1E),
foreground = Color(0xFFD4D4D4),
cursor = Color.White,
selection = Color(0xFF264F78),
dark = true
)
basicSetup + editorTheme.of(myTheme)
Or use the Material Design bridge for automatic theme integration:
import com.monkopedia.kodemirror.materialtheme.rememberMaterialEditorTheme
@Composable
fun MyEditor() {
val materialTheme = rememberMaterialEditorTheme()
val session = rememberEditorSession(
doc = "Hello",
extensions = basicSetup + materialTheme
)
KodeMirror(session = session)
}
This reads colors from MaterialTheme.colorScheme automatically and adapts when switching between light and dark mode. Requires the com.monkopedia.kodemirror:material-theme dependency.
Read-only mode¶
readOnly vs editable¶
EditorState.readOnly.of(true) | EditorSession.editable (set to false) | |
|---|---|---|
| Editing | Blocked | Blocked |
| Selection | Allowed | Blocked |
| Copy | Allowed | Blocked |
| Use case | Display code that users can select/copy | Fully inert display |
6. Reacting to changes¶
Use the onChange convenience to get notified when the document changes:
val session = rememberEditorSession(
doc = "Hello",
extensions = basicSetup + onChange { newText ->
println("Document changed: $newText")
}
)
For selection changes:
val session = rememberEditorSession(
doc = "Hello",
extensions = basicSetup + onSelection { selection ->
println("Cursor at: ${selection.main.head}")
}
)
7. Programmatic changes¶
Use the convenience methods on EditorSession. Document offsets are passed as DocPos (com.monkopedia.kodemirror.state.DocPos), an inline value class over Int that keeps positions from being confused with line numbers or counts:
// Replace entire document
session.setDoc("new content")
// Insert text at a position
session.insertAt(DocPos(0), "// Header\n")
// Delete a range
session.deleteRange(from = DocPos(0), to = DocPos(10))
// Set cursor position
session.select(anchor = DocPos(5))
// Set selection range
session.select(anchor = DocPos(0), head = DocPos(10))
// Select all
session.selectAll()
For more complex changes, use dispatch with a transaction spec:
8. Sample project¶
A complete working sample editor is available at samples/editor/ with tab switching between languages and themes.
Next steps¶
- Architecture — understand the module structure
- Data Model — deep dive into documents and state
- Extending — build custom extensions
- Extension Index — find available extensions
- Examples — see specific feature examples