From 864a681b4d57823687c2bb6251e5c4bccbe7d1d4 Mon Sep 17 00:00:00 2001
From: Adrian Heine <mail@adrianheine.de>
Date: Mon, 3 Oct 2016 19:36:23 +0200
Subject: [PATCH] Reimplement coordsChar

---
 src/edit/methods.js                     |  2 +-
 src/measurement/position_measurement.js | 90 +++++++++++--------------
 src/util/bidi.js                        | 28 --------
 test/emacs_test.js                      |  4 +-
 test/test.js                            |  4 +-
 5 files changed, 44 insertions(+), 84 deletions(-)

diff --git a/src/edit/methods.js b/src/edit/methods.js
index 52ce0aee..18412d31 100644
--- a/src/edit/methods.js
+++ b/src/edit/methods.js
@@ -336,7 +336,7 @@ export default function(CodeMirror) {
       let start = pos.ch, end = pos.ch
       if (line) {
         let helper = this.getHelper(pos, "wordChars")
-        if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end
+        if ((pos.sticky == "before" || end == line.length) && start) --start; else ++end
         let startChar = line.charAt(start)
         let check = isWordChar(startChar, helper)
           ? ch => isWordChar(ch, helper)
diff --git a/src/measurement/position_measurement.js b/src/measurement/position_measurement.js
index d87ec7a0..531ab1d7 100644
--- a/src/measurement/position_measurement.js
+++ b/src/measurement/position_measurement.js
@@ -1,13 +1,14 @@
+import { moveVisually } from "../input/movement"
 import { buildLineContent, LineView } from "../line/line_data"
 import { clipPos, Pos } from "../line/pos"
 import { collapsedSpanAtEnd, heightAtLine, lineIsHidden, visualLine } from "../line/spans"
 import { getLine, lineAtHeight, lineNo, updateLineHeight } from "../line/utils_line"
-import { bidiOther, getBidiPartAt, getOrder, lineLeft, lineRight, moveVisually } from "../util/bidi"
+import { bidiOther, getBidiPartAt, getOrder } from "../util/bidi"
 import { ie, ie_version } from "../util/browser"
 import { elt, removeChildren, range, removeChildrenAndAdd } from "../util/dom"
 import { e_target } from "../util/event"
 import { hasBadZoomedRects } from "../util/feature_detection"
-import { countColumn, isExtendingChar, scrollerGap } from "../util/misc"
+import { countColumn, findFirst, isExtendingChar, scrollerGap } from "../util/misc"
 import { updateLineForChanges } from "../display/update_line"
 
 import { widgetHeight } from "./widgets"
@@ -429,59 +430,46 @@ export function coordsChar(cm, x, y) {
 }
 
 function coordsCharInner(cm, lineObj, lineNo, x, y) {
-  let innerOff = y - heightAtLine(lineObj)
-  let wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth
+  y -= heightAtLine(lineObj)
+  let begin = 0, end = lineObj.text.length - 1
   let preparedMeasure = prepareMeasureForLine(cm, lineObj)
-
-  function getX(ch) {
-    let sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure)
-    wrongLine = true
-    if (innerOff > sp.bottom) return sp.left - adjust
-    else if (innerOff < sp.top) return sp.left + adjust
-    else wrongLine = false
-    return sp.left
+  if (cm.options.lineWrapping) {
+    let measure = ch => intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line")
+    begin = findFirst(ch => measure(ch).bottom < y, end, begin - 1) + 1
+    end = findFirst(ch => measure(ch).top > y, begin, end + 1) - 1
   }
-
-  let bidi = getOrder(lineObj), dist = lineObj.text.length
-  let from = lineLeft(lineObj), to = lineRight(lineObj)
-  let fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine
-
-  if (x > toX) return PosWithInfo(lineNo, to, null, toOutside, 1)
-  // Do a binary search between these bounds.
-  for (;;) {
-    if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
-      let ch = x < fromX || x - fromX <= toX - x ? from : to
-      let outside = ch == from ? fromOutside : toOutside
-      let xDiff = x - (ch == from ? fromX : toX)
-      // This is a kludge to handle the case where the coordinates
-      // are after a line-wrapped line. We should replace it with a
-      // more general handling of cursor positions around line
-      // breaks. (Issue #4078)
-      if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff > 0 &&
-          ch < lineObj.text.length && preparedMeasure.view.measure.heights.length > 1) {
-        let charSize = measureCharPrepared(cm, preparedMeasure, ch, "right")
-        if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.abs(x - charSize.right) < xDiff) {
-          outside = false
-          ch++
-          xDiff = x - charSize.right
-        }
-      let sticky = null
-      if (toX > cm.display.wrapper.clientWidth) {
-        sticky = "before"
+  let pos
+  let order = getOrder(lineObj)
+  if (order) {
+    if (end == lineObj.text.length - 1) ++end
+    pos = new Pos(lineNo, begin)
+    let beginLeft = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left
+    let dir = beginLeft < x ? 1 : -1
+    let prevDiff, diff = beginLeft - x
+    do {
+      prevDiff = diff
+      let prevPos = pos
+      pos = moveVisually(cm, lineObj, pos, dir)
+      if (pos == null || pos.ch < begin || end < pos.ch) {
+        pos = prevPos
+        break
       }
-      while (isExtendingChar(lineObj.text.charAt(ch))) ++ch
-      let pos = PosWithInfo(lineNo, ch, sticky, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0)
-      return pos
-    }
-    let step = Math.ceil(dist / 2), middle = from + step
-    if (bidi) {
-      middle = from
-      for (let i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1)
-    }
-    let middleX = getX(middle)
-    if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step}
-    else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step}
+      diff = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - x
+    } while ((dir < 0) != (diff < 0))
+    // moveVisually has the nice side effect of skipping extending chars and setting sticky
+    if (Math.abs(diff) > Math.abs(prevDiff)) pos = moveVisually(cm, lineObj, pos, -dir)
+  } else {
+    let ch = findFirst(ch => {
+      let box = measureCharPrepared(cm, preparedMeasure, ch)
+      return x - box.right < box.left - x
+    }, begin, end + 1)
+    while (isExtendingChar(lineObj.text.charAt(ch))) ++ch
+    pos = new Pos(lineNo, ch, (ch == end + 1) ? "before" : "after")
   }
+  let coords = cursorCoords(cm, pos, "line", lineObj, preparedMeasure)
+  if (y < coords.top || coords.bottom < y) pos.outside = true
+  pos.xRel = x < coords.left ? -1 : (x > coords.right ? 1 : 0)
+  return pos
 }
 
 let measureText
diff --git a/src/util/bidi.js b/src/util/bidi.js
index 4f0e5a37..f10ee707 100644
--- a/src/util/bidi.js
+++ b/src/util/bidi.js
@@ -53,34 +53,6 @@ function moveInLine(line, pos, dir) {
   return pos
 }
 
-// This is needed in order to move 'visually' through bi-directional
-// text -- i.e., pressing left should make the cursor go left, even
-// when in RTL text. The tricky part is the 'jumps', where RTL and
-// LTR text touch each other. This often requires the cursor offset
-// to move more than one unit, in order to visually move one unit.
-export function moveVisually(line, start, dir, byUnit) {
-  let bidi = getOrder(line)
-  if (!bidi) return moveLogically(line, start, dir, byUnit)
-  let pos = getBidiPartAt(bidi, start), part = bidi[pos]
-  let target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit)
-
-  for (;;) {
-    if (target > part.from && target < part.to) return target
-    if (target == part.from || target == part.to) {
-      if (getBidiPartAt(bidi, target) == pos) return target
-      part = bidi[pos += dir]
-      return (dir > 0) == part.level % 2 ? part.to : part.from
-    } else {
-      part = bidi[pos += dir]
-      if (!part) return null
-      if ((dir > 0) == part.level % 2)
-        target = moveInLine(line, part.to, -1, byUnit)
-      else
-        target = moveInLine(line, part.from, 1, byUnit)
-    }
-  }
-}
-
 export function moveLogically(line, start, dir) {
   let target = moveInLine(line, start.ch, dir)
   return target < 0 || target > line.text.length ? null : target
diff --git a/test/emacs_test.js b/test/emacs_test.js
index acb4ba2b..2e176423 100644
--- a/test/emacs_test.js
+++ b/test/emacs_test.js
@@ -56,8 +56,8 @@
 
   sim("motionVSimple", "a\nb\nc\n", "Ctrl-N", "Ctrl-N", "Ctrl-P", at(1, 0, "after"));
   sim("motionVMulti", "a\nb\nc\nd\ne\n",
-      "Ctrl-2", "Ctrl-N", at(2, 0), "Ctrl-F", "Ctrl--", "Ctrl-N", at(1, 1),
-      "Ctrl--", "Ctrl-3", "Ctrl-P", at(4, 1));
+      "Ctrl-2", "Ctrl-N", at(2, 0, "after"), "Ctrl-F", "Ctrl--", "Ctrl-N", at(1, 1, "before"),
+      "Ctrl--", "Ctrl-3", "Ctrl-P", at(4, 1, "before"));
 
   sim("killYank", "abc\ndef\nghi",
       "Ctrl-F", "Ctrl-Space", "Ctrl-N", "Ctrl-N", "Ctrl-W", "Ctrl-E", "Ctrl-Y",
diff --git a/test/test.js b/test/test.js
index e74f11d8..2367de6a 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1104,7 +1104,7 @@ testCM("measureEndOfLine", function(cm) {
   is(endPos.top > lh * .8, "not at top");
   is(endPos.left > w - 20, "not at right");
   endPos = cm.charCoords(Pos(0, 18));
-  eqPos(cm.coordsChar({left: endPos.left, top: endPos.top + 5}), Pos(0, 18));
+  eqCursorPos(cm.coordsChar({left: endPos.left, top: endPos.top + 5}), Pos(0, 18, "before"));
 
   var wrapPos = cm.cursorCoords(Pos(0, 9, "before"));
   is(wrapPos.top < endPos.top, "wrapPos is actually in first line");
@@ -1153,7 +1153,7 @@ testCM("moveVstuck", function(cm) {
   }
   cm.setCursor(Pos(0, val.length - 1));
   cm.moveV(-1, "line");
-  eqPos(cm.getCursor(), Pos(0, 26, "before"));
+  eqPos(cm.getCursor(), Pos(0, 27, "before"));
   is(cm.cursorCoords(null, "local").top < h0, "cursor is in first visual line");
 }, {lineWrapping: true}, ie_lt8 || opera_lt10);
 
-- 
GitLab