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