Describe the issue
Summary
When confirming an IME composition inside brackets with bracketMatching() enabled (default afterCursor: true), text before the opening bracket visually disappears on Chrome. The document data remains intact, and the display recovers when the cursor moves to another line. This does not reproduce on Safari.
Environment
@codemirror/language 6.12.2
@codemirror/view 6.40.0
@codemirror/state 6.6.0
- Browser: Chrome (also Firefox; Safari is unaffected)
- Input method: IME (tested with Japanese and Chinese)
Steps to reproduce
- Enable
bracketMatching() with the default afterCursor: true
- Type a line containing brackets, e.g.:
## Template (test)
- Place the cursor inside the brackets:
## Template (|test)
- Delete
test and re-type using IME; confirm with Enter or Space
- After confirmation,
## Template visually disappears — only (confirmed text) is rendered
Expected: ## Template (confirmed text) displays correctly.
Actual: Everything before ( vanishes from the display. Document state is correct. Moving the cursor to a different line restores the missing text.
Analysis
The paused flag added in @codemirror/language 6.12.2 (for #1324) correctly defers bracket matching decoration updates while view.composing is true. However, once compositionend fires, view.composing immediately becomes false, and the very next update cycle synchronously recalculates bracket decorations with no post-composition delay.
On Chrome, the sequence is:
- IME confirmation →
compositionend fires synchronously
view.composing drops to false
- DOM observer flushes, transaction is dispatched
bracketMatcher.update() runs, sees composition has ended, unpauses
- With
afterCursor: true, cursor is at …(confirmed text|) — matchBrackets finds ) before the cursor and its matching (, applies Decoration.mark (cm-matchingBracket) to both
- The mark wrapping
( mutates the DOM before Chrome's rendering engine has settled its post-composition state, causing the preceding text node to drop from the render tree
Safari is unaffected because @codemirror/view delays compositionend dispatch by 20ms via setTimeout when Safari fires an insertText event during composition. This gives the browser time to finalize DOM changes before decorations are reapplied. Chrome receives no such delay.
Workaround
Disable the default bracketMatching() and re-add it with afterCursor: false. This prevents the cursor-before-bracket match that triggers the problematic decoration, but at the cost of losing bracket highlighting when the cursor is immediately before a bracket.
Suggested fix
Add a short post-composition guard to the bracketMatcher plugin, similar to the compositionEndedAt checks already used in @codemirror/view for Safari key handling. For example, after compositionend, keep the plugin paused for ~80ms before allowing decoration recalculation. This would give all browsers time to settle their post-composition DOM state.
Browser and platform
MacOS: 15.7.4(24G517, Google Chrome: v46.0.7680.154
Reproduction link
No response
Describe the issue
Summary
When confirming an IME composition inside brackets with
bracketMatching()enabled (defaultafterCursor: true), text before the opening bracket visually disappears on Chrome. The document data remains intact, and the display recovers when the cursor moves to another line. This does not reproduce on Safari.Environment
@codemirror/language6.12.2@codemirror/view6.40.0@codemirror/state6.6.0Steps to reproduce
bracketMatching()with the defaultafterCursor: true## Template (test)## Template (|test)testand re-type using IME; confirm with Enter or Space## Templatevisually disappears — only(confirmed text)is renderedExpected:
## Template (confirmed text)displays correctly.Actual: Everything before
(vanishes from the display. Document state is correct. Moving the cursor to a different line restores the missing text.Analysis
The
pausedflag added in@codemirror/language6.12.2 (for #1324) correctly defers bracket matching decoration updates whileview.composingistrue. However, oncecompositionendfires,view.composingimmediately becomesfalse, and the very next update cycle synchronously recalculates bracket decorations with no post-composition delay.On Chrome, the sequence is:
compositionendfires synchronouslyview.composingdrops tofalsebracketMatcher.update()runs, sees composition has ended, unpausesafterCursor: true, cursor is at…(confirmed text|)—matchBracketsfinds)before the cursor and its matching(, appliesDecoration.mark(cm-matchingBracket) to both(mutates the DOM before Chrome's rendering engine has settled its post-composition state, causing the preceding text node to drop from the render treeSafari is unaffected because
@codemirror/viewdelayscompositionenddispatch by 20ms viasetTimeoutwhen Safari fires aninsertTextevent during composition. This gives the browser time to finalize DOM changes before decorations are reapplied. Chrome receives no such delay.Workaround
Disable the default
bracketMatching()and re-add it withafterCursor: false. This prevents the cursor-before-bracket match that triggers the problematic decoration, but at the cost of losing bracket highlighting when the cursor is immediately before a bracket.Suggested fix
Add a short post-composition guard to the
bracketMatcherplugin, similar to thecompositionEndedAtchecks already used in@codemirror/viewfor Safari key handling. For example, aftercompositionend, keep the plugin paused for ~80ms before allowing decoration recalculation. This would give all browsers time to settle their post-composition DOM state.Browser and platform
MacOS: 15.7.4(24G517, Google Chrome: v46.0.7680.154
Reproduction link
No response