Skip to content

Commit 441641e

Browse files
committed
[contenteditable input] Read from the DOM to get composition input
And do so only after a delay, so that subsequent input events get a chance to fire.
1 parent 0e54532 commit 441641e

File tree

3 files changed

+30
-39
lines changed

3 files changed

+30
-39
lines changed

src/edit/CodeMirror.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ function registerEventHandlers(cm) {
142142
}
143143
on(d.scroller, "touchstart", e => {
144144
if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) {
145+
d.input.ensurePolled()
145146
clearTimeout(touchFinished)
146147
let now = +new Date
147148
d.activeTouch = {start: now, moved: false,

src/edit/mouse_events.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { bind, countColumn, findColumn, sel_mouse } from "../util/misc"
2121
export function onMouseDown(e) {
2222
let cm = this, display = cm.display
2323
if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return
24+
display.input.ensurePolled()
2425
display.shift = e.shiftKey
2526

2627
if (eventInWidget(display, e)) {

src/input/ContentEditableInput.js

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export default function ContentEditableInput(cm) {
2020
this.cm = cm
2121
this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null
2222
this.polling = new Delayed()
23+
this.composing = null
2324
this.gracePeriod = false
25+
this.readDOMTimeout = null
2426
}
2527

2628
ContentEditableInput.prototype = copyObj({
@@ -37,44 +39,23 @@ ContentEditableInput.prototype = copyObj({
3739
}), 20)
3840
})
3941

40-
function startComposing(data) {
41-
input.composing = {sel: cm.doc.sel, data: data, startData: data}
42-
if (!data) return
43-
let prim = cm.doc.sel.primary()
44-
let line = cm.getLine(prim.head.line)
45-
let found = line.indexOf(data, Math.max(0, prim.head.ch - data.length))
46-
if (found > -1 && found <= prim.head.ch)
47-
input.composing.sel = simpleSelection(Pos(prim.head.line, found),
48-
Pos(prim.head.line, found + data.length))
49-
}
50-
51-
on(div, "compositionstart", e => startComposing(e.data))
42+
on(div, "compositionstart", e => {
43+
this.composing = {data: e.data}
44+
})
5245
on(div, "compositionupdate", e => {
53-
if (input.composing) input.composing.data = e.data
54-
else startComposing(e.data)
46+
if (!this.composing) this.composing = {data: e.data}
5547
})
5648
on(div, "compositionend", e => {
57-
let ours = input.composing
58-
if (!ours) return
59-
if (e.data != ours.startData && !/\u200b/.test(e.data))
60-
ours.data = e.data
61-
// Need a small delay to prevent other code (input event,
62-
// selection polling) from doing damage when fired right after
63-
// compositionend.
64-
setTimeout(() => {
65-
if (!ours.handled)
66-
input.applyComposition(ours)
67-
if (input.composing == ours)
68-
input.composing = null
69-
}, 50)
49+
if (this.composing) {
50+
if (e.data != this.composing.data) this.readFromDOMSoon()
51+
this.composing = null
52+
}
7053
})
7154

7255
on(div, "touchstart", () => input.forceCompositionEnd())
7356

7457
on(div, "input", () => {
75-
if (input.composing) return
76-
if (cm.isReadOnly() || !input.pollContent())
77-
runInOp(input.cm, () => regChange(cm))
58+
if (!this.composing) this.readFromDOMSoon()
7859
})
7960

8061
function onCopyCut(e) {
@@ -237,7 +218,7 @@ ContentEditableInput.prototype = copyObj({
237218
},
238219

239220
pollSelection: function() {
240-
if (!this.composing && !this.gracePeriod && this.selectionChanged()) {
221+
if (!this.composing && this.readDOMTimeout == null && !this.gracePeriod && this.selectionChanged()) {
241222
let sel = window.getSelection(), cm = this.cm
242223
this.rememberSelection()
243224
let anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset)
@@ -250,6 +231,11 @@ ContentEditableInput.prototype = copyObj({
250231
},
251232

252233
pollContent: function() {
234+
if (this.readDOMTimeout != null) {
235+
clearTimeout(this.readDOMTimeout)
236+
this.readDOMTimeout = null
237+
}
238+
253239
let cm = this.cm, display = cm.display, sel = cm.doc.sel.primary()
254240
let from = sel.from(), to = sel.to()
255241
if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false
@@ -309,17 +295,20 @@ ContentEditableInput.prototype = copyObj({
309295
this.forceCompositionEnd()
310296
},
311297
forceCompositionEnd: function() {
312-
if (!this.composing || this.composing.handled) return
313-
this.applyComposition(this.composing)
314-
this.composing.handled = true
298+
if (!this.composing) return
299+
this.composing = null
300+
if (!this.pollContent()) regChange(this.cm)
315301
this.div.blur()
316302
this.div.focus()
317303
},
318-
applyComposition: function(composing) {
319-
if (this.cm.isReadOnly())
320-
operation(this.cm, regChange)(this.cm)
321-
else if (composing.data && composing.data != composing.startData)
322-
operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel)
304+
readFromDOMSoon: function() {
305+
if (this.readDOMTimeout != null) return
306+
this.readDOMTimeout = setTimeout(() => {
307+
this.readDOMTimeout = null
308+
if (this.composing) return
309+
if (this.cm.isReadOnly() || !this.pollContent())
310+
runInOp(this.cm, () => regChange(this.cm))
311+
}, 80)
323312
},
324313

325314
setUneditable: function(node) {

0 commit comments

Comments
 (0)