Skip to content

Commit

Permalink
Freeze the edit context when non-typing changes come in during compos…
Browse files Browse the repository at this point in the history
…ition

FIX: Fix an issue with compositions on Chrome inserting their content in the wrong
position when another document change came in during composition.

Closes codemirror/dev#1472
  • Loading branch information
marijnh committed Nov 15, 2024
1 parent 869252a commit 9658d5e
Showing 1 changed file with 32 additions and 9 deletions.
41 changes: 32 additions & 9 deletions src/domobserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,9 @@ class EditContextManager {
// user action on some Android keyboards)
pendingContextChange: {from: number, to: number, insert: Text} | null = null
handlers: {[name: string]: (e: any) => void} = Object.create(null)
// Kludge to work around the fact that EditContext does not respond
// well to having its content updated during a composition (see #1472)
composing: {contextBase: number, editorBase: number, drifted: boolean} | null = null

constructor(view: EditorView) {
this.resetRange(view.state)
Expand All @@ -551,9 +554,10 @@ class EditContextManager {
})
this.handlers.textupdate = e => {
let {anchor} = view.state.selection.main
let change = {from: this.toEditorPos(e.updateRangeStart),
to: this.toEditorPos(e.updateRangeEnd),
insert: Text.of(e.text.split("\n"))}
let from = this.toEditorPos(e.updateRangeStart), to = this.toEditorPos(e.updateRangeEnd)
if (view.inputState.composing >= 0 && !this.composing)
this.composing = {contextBase: e.updateRangeStart, editorBase: from, drifted: false}
let change = {from, to, insert: Text.of(e.text.split("\n"))}
// If the window doesn't include the anchor, assume changes
// adjacent to a side go up to the anchor.
if (change.from == this.from && anchor < this.from) change.from = anchor
Expand Down Expand Up @@ -606,6 +610,11 @@ class EditContextManager {
this.handlers.compositionend = () => {
view.inputState.composing = -1
view.inputState.compositionFirstChange = null
if (this.composing) {
let {drifted} = this.composing
this.composing = null
if (drifted) this.reset(view.state)
}
}
for (let event in this.handlers) context.addEventListener(event as any, this.handlers[event])

Expand Down Expand Up @@ -654,11 +663,13 @@ class EditContextManager {

update(update: ViewUpdate) {
let reverted = this.pendingContextChange
if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
if (this.composing && (this.composing.drifted || update.transactions.some(
tr => !tr.isUserEvent("input.type") && tr.changes.touchesRange(this.from, this.to)))) {
this.composing.drifted = true
this.composing.editorBase = update.changes.mapPos(this.composing.editorBase)
} else if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
this.pendingContextChange = null
this.resetRange(update.state)
this.editContext.updateText(0, this.editContext.text.length, update.state.doc.sliceString(this.from, this.to))
this.setSelection(update.state)
this.reset(update.state)
} else if (update.docChanged || update.selectionSet || reverted) {
this.setSelection(update.state)
}
Expand All @@ -672,6 +683,12 @@ class EditContextManager {
this.to = Math.min(state.doc.length, head + CxVp.Margin)
}

reset(state: EditorState) {
this.resetRange(state)
this.editContext.updateText(0, this.editContext.text.length, state.doc.sliceString(this.from, this.to))
this.setSelection(state)
}

revertPending(state: EditorState) {
let pending = this.pendingContextChange!
this.pendingContextChange = null
Expand All @@ -695,8 +712,14 @@ class EditContextManager {
this.to - this.from > CxVp.Margin * 3)
}

toEditorPos(contextPos: number) { return contextPos + this.from }
toContextPos(editorPos: number) { return editorPos - this.from }
toEditorPos(contextPos: number) {
let c = this.composing
return c && c.drifted ? c.editorBase + (contextPos - c.contextBase) : contextPos + this.from
}
toContextPos(editorPos: number) {
let c = this.composing
return c && c.drifted ? c.contextBase + (editorPos - c.editorBase) : editorPos - this.from
}

destroy() {
for (let event in this.handlers) this.editContext.removeEventListener(event as any, this.handlers[event])
Expand Down

0 comments on commit 9658d5e

Please sign in to comment.