Skip to content

Commit

Permalink
Fix cursor motion loop on misplaced zero-width non-joiner
Browse files Browse the repository at this point in the history
FIX: Fix an issue in the bidirectional motion that could cause the cursor to get stuck
in a loop when a zero-width non-joiner char was placed on a direction boundary.

Closes codemirror/dev#1302
  • Loading branch information
marijnh committed Dec 1, 2023
1 parent fc4446a commit d5a8035
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 5 deletions.
7 changes: 3 additions & 4 deletions src/bidi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ function charType(ch: number) {
0x590 <= ch && ch <= 0x5f4 ? T.R :
0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
0x6ee <= ch && ch <= 0x8ac ? T.AL :
0x2000 <= ch && ch <= 0x200b ? T.NI :
0xfb50 <= ch && ch <= 0xfdff ? T.AL :
ch == 0x200c ? T.NI : T.L
0x2000 <= ch && ch <= 0x200c ? T.NI :
0xfb50 <= ch && ch <= 0xfdff ? T.AL : T.L
}

const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\ufb50-\ufdff]/
Expand Down Expand Up @@ -436,7 +435,7 @@ export function moveVisually(line: Line, order: readonly BidiSpan[], dir: Direct
let nextIndex = findClusterBreak(line.text, startIndex, indexForward)
movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex))

if (nextIndex != span.side(forward, dir))
if (nextIndex > span.from && nextIndex < span.to)
return EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level)
let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)]
if (!nextSpan && span.level != dir)
Expand Down
17 changes: 16 additions & 1 deletion test/webtest-bidi.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {tempView} from "./tempview.js"
import ist from "ist"
import {__test, BidiSpan, Direction, Decoration, DecorationSet, EditorView} from "@codemirror/view"
import {Text, EditorSelection, Range, StateField, Extension} from "@codemirror/state"
import {Text, EditorSelection, SelectionRange, Range, StateField, Extension} from "@codemirror/state"

function queryBrowserOrder(strings: readonly string[]) {
let scratch = document.body.appendChild(document.createElement("div"))
Expand Down Expand Up @@ -104,6 +104,21 @@ function tests(dir: Direction) {
ist(__test.moveVisually(line, order, Direction.LTR, EditorSelection.cursor(points[i], 0, 0), false)!.from, points[i - 1])
}
})

it("handles a misplaced non-joiner without going in a loop", () => {
let doc = "ءAB\u200cء", line = Text.of([doc]).line(1)
let order = __test.computeOrder(doc, Direction.RTL, [])
for (let pos: SelectionRange | null = EditorSelection.cursor(0), count = 0; count++;) {
ist(count, 6, "<")
pos = __test.moveVisually(line, order, Direction.RTL, pos, true)
if (!pos) break
}
for (let pos: SelectionRange | null = EditorSelection.cursor(doc.length), count = 0; count++;) {
ist(count, 6, "<")
pos = __test.moveVisually(line, order, Direction.RTL, pos, false)
if (!pos) break
}
})
})
}

Expand Down

0 comments on commit d5a8035

Please sign in to comment.