Troubleshooting¶
Common issues and solutions when working with Kodemirror.
Build issues¶
Java version errors¶
Kodemirror requires Java 11 or higher. If you see errors about unsupported class file versions or missing APIs, check your Java version:
For building from source, Java 21 is recommended:
Compose compiler version mismatch¶
If you see errors about incompatible Compose compiler versions, make sure your project's Kotlin and Compose plugin versions are compatible with the versions Kodemirror was built against. Check the gradle/libs.versions.toml in the Kodemirror repository for the expected versions.
wasmJs test failures¶
The wasmJs test environment requires skiko.mjs which may not be available in all setups. wasmJs tests are disabled by default in Kodemirror's build. If you're adding a new module, include this in your build.gradle.kts:
Runtime issues¶
"Field is not present in this state"¶
This error occurs when you try to read a StateField that wasn't included in the editor's extensions. Make sure the field (or the extension that provides it) is in your EditorStateConfig.extensions:
val myField = StateField.define(StateFieldSpec(
create = { 0 },
update = { v, tr -> v }
))
// Include the field in extensions
val session = rememberEditorSession(
doc = "...",
extensions = myField + // ... other extensions
)
// Now this works
state.field(myField)
Editor not rendering¶
If the editor composable appears blank:
- Verify you have at least
:stateand:viewdependencies - Check that your
EditorStateis created before the composable renders - Make sure the editor has non-zero size constraints in your layout
Decorations not appearing¶
Decorations must be added in ascending position order. If you build a RangeSetBuilder with positions out of order, the decorations will be silently dropped:
val builder = RangeSetBuilder<Decoration>()
// Positions must be ascending
builder.add(from = 5, to = 10, value = deco1)
builder.add(from = 15, to = 20, value = deco2) // OK: 15 > 10
// builder.add(from = 3, to = 8, value = deco3) // BAD: 3 < 15
Changes not being applied¶
If session.dispatch(TransactionSpec(...)) doesn't seem to do anything:
- Check that your
ChangeSpecpositions are within the document length - Verify the editor is not in read-only mode (
readOnly.of(true)in extensions) - Ensure change filters aren't rejecting the change
No syntax highlighting¶
The editor works without a language extension, but won't highlight anything. Make sure you include a language:
If using basicSetup, syntax highlighting infrastructure is included but you still need a language extension. Without one, all text renders in the default foreground color.
Editor not responding to key events¶
If typing works but shortcuts (Ctrl+Z, Ctrl+C, etc.) don't:
- Make sure you include a keymap extension.
basicSetupincludesdefaultKeymap— if you're building your own setup, add: - Check that another composable isn't intercepting key events before the editor.
Theme colors not applying¶
There are two separate styling systems:
EditorThemecontrols UI colors (background, gutter, cursor, panels, etc.) via theeditorThemefacet.HighlightStylecontrols syntax token colors (keywords, strings, comments, etc.) viasyntaxHighlighting().
A theme module like oneDark bundles both. If you only set editorTheme.of(myTheme), syntax tokens use the default highlight style. Add syntaxHighlighting(myHighlightStyle) as well:
val session = rememberEditorSession(
doc = code,
extensions = basicSetup + editorTheme.of(myTheme) +
syntaxHighlighting(myHighlightStyle)
)
Completion not showing¶
If autocompletion doesn't appear when you type:
- Add the
autocompletion()extension (included inbasicSetup). - Provide a completion source — either language-specific (e.g.,
javascript().extensionincludes JS completions) or custom: - Check
activateOnTyping— by default, completions activate on typing. If disabled, use Ctrl+Space.
readOnly vs editable¶
Two extensions control editing, but they behave differently:
| Extension | Typing blocked | Selection allowed | Copy allowed | Focus allowed |
|---|---|---|---|---|
readOnly.of(true) | Yes | Yes | Yes | Yes |
editable.of(false) | Yes | No | No | No |
Use readOnly for display-with-interaction (users can select and copy). Use editable.of(false) for fully inert display.
Extensions not taking effect¶
If an extension seems to have no effect:
- Check registration. The extension must be included in
EditorStateConfig.extensions(or viarememberEditorSession'sextensionsparameter). - Check precedence. Later extensions override earlier ones for facets using
"last wins"combine (likeeditorTheme). UsePrec.highest(ext)to force priority. - Check compartments. If an extension is inside a
Compartment, changes requirecompartment.reconfigure(newExt). - Check that it's the right facet. Some names are similar (e.g.,
readOnlyvseditable,onChangevsupdateListener).
State not updating after dispatch¶
EditorState is immutable. Dispatching a transaction creates a new state — it doesn't mutate the existing one:
val oldState = session.state
session.dispatch(TransactionSpec(
changes = ChangeSpec.Single(0, 0, InsertContent.StringContent("hi"))
))
val newState = session.state // Different from oldState
If you're holding a reference to the state, it becomes stale after dispatch. Always read session.state for the current state.
Undo not working¶
Undo/redo requires the history() extension:
If building your own setup without basicSetup, add history() explicitly. Without it, undo and redo commands do nothing.
Editor blank on first render¶
If the editor flashes blank before showing content:
- Make sure the editor has explicit size constraints. A
Modifier.fillMaxSize()or fixed height prevents zero-size issues. - The first render may occur before the layout pass completes. This is normal — the content should appear within one frame.