diff --git a/src/line/pos.js b/src/line/pos.js index 73b2e0b8e8bd3086586d0fe36763fd056a988c44..4f5e4c55941342e3f5871b69daf429fd672fec85 100644 --- a/src/line/pos.js +++ b/src/line/pos.js @@ -1,15 +1,19 @@ import { getLine } from "./utils_line" // A Pos instance represents a position within the text. -export function Pos (line, ch) { - if (!(this instanceof Pos)) return new Pos(line, ch) - this.line = line; this.ch = ch +export function Pos(line, ch, sticky = null) { + if (!(this instanceof Pos)) return new Pos(line, ch, sticky) + this.line = line + this.ch = ch + this.sticky = sticky } // Compare two positions, return 0 if they are the same, a negative // number when a is less, and a positive number otherwise. export function cmp(a, b) { return a.line - b.line || a.ch - b.ch } +export function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } + export function copyPos(x) {return Pos(x.line, x.ch)} export function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } export function minPos(a, b) { return cmp(a, b) < 0 ? a : b } diff --git a/src/measurement/position_measurement.js b/src/measurement/position_measurement.js index f62672c494f000fda5f907e7f3610e804085c7f7..4e8a3f839a8ea041e14c1607f0f49b7cab4c82e8 100644 --- a/src/measurement/position_measurement.js +++ b/src/measurement/position_measurement.js @@ -357,7 +357,7 @@ export function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeig return get(ch, right) } let order = getOrder(lineObj), ch = pos.ch - if (!order) return get(ch) + if (!order) return get(pos.sticky == "before" ? ch - 1 : ch, pos.sticky == "before") let partPos = getBidiPartAt(order, ch) let val = getBidi(ch, partPos) if (bidiOther != null) val.other = getBidi(ch, bidiOther) @@ -381,8 +381,8 @@ export function estimateCoords(cm, pos) { // the right of the character position, for example). When outside // is true, that means the coordinates lie outside the line's // vertical range. -function PosWithInfo(line, ch, outside, xRel) { - let pos = Pos(line, ch) +function PosWithInfo(line, ch, sticky, outside, xRel) { + let pos = Pos(line, ch, sticky) pos.xRel = xRel if (outside) pos.outside = true return pos @@ -393,10 +393,10 @@ function PosWithInfo(line, ch, outside, xRel) { export function coordsChar(cm, x, y) { let doc = cm.doc y += cm.display.viewOffset - if (y < 0) return PosWithInfo(doc.first, 0, true, -1) + if (y < 0) return PosWithInfo(doc.first, 0, null, true, -1) let lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 if (lineN > last) - return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) if (x < 0) x = 0 let lineObj = getLine(doc, lineN) @@ -429,7 +429,7 @@ function coordsCharInner(cm, lineObj, lineNo, x, y) { 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, toOutside, 1) + 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) { @@ -448,9 +448,12 @@ function coordsCharInner(cm, lineObj, lineNo, x, y) { ch++ xDiff = x - charSize.right } + let sticky = null + if (toX > cm.display.wrapper.clientWidth) { + sticky = "before" } while (isExtendingChar(lineObj.text.charAt(ch))) ++ch - let pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0) + 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 diff --git a/test/driver.js b/test/driver.js index c61d4c1f36720e2c92dbc79ab60a3b0c22ba7210..01736450f36b48453b9e40de83bc097b3d38319f 100644 --- a/test/driver.js +++ b/test/driver.js @@ -107,11 +107,11 @@ function near(a, b, margin, msg) { throw new Failure(label(a + " is not close to " + b + " (" + margin + ")", msg)); } function eqPos(a, b, msg) { - function str(p) { return "{line:" + p.line + ",ch:" + p.ch + "}"; } + function str(p) { return "{line:" + p.line + ",ch:" + p.ch + ",sticky:" + p.sticky + "}"; } if (a == b) return; if (a == null) throw new Failure(label("comparing null to " + str(b), msg)); if (b == null) throw new Failure(label("comparing " + str(a) + " to null", msg)); - if (a.line != b.line || a.ch != b.ch) throw new Failure(label(str(a) + " != " + str(b), msg)); + if (a.line != b.line || a.ch != b.ch || a.sticky != b.sticky) throw new Failure(label(str(a) + " != " + str(b), msg)); } function is(a, msg) { if (!a) throw new Failure(label("assertion failed", msg)); diff --git a/test/test.js b/test/test.js index 07d029fba876b84a6a75e8a7597dda89d116113e..37894190db57b4914e2a38bf5cc8b559997d14b9 100644 --- a/test/test.js +++ b/test/test.js @@ -1105,6 +1105,10 @@ testCM("measureEndOfLine", function(cm) { 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)); + + var wrapPos = cm.cursorCoords(Pos(0, 9, "before")); + is(wrapPos.top < endPos.top, "wrapPos is actually in first line"); + eqPos(cm.coordsChar({left: wrapPos.left + 10, top: wrapPos.top}), Pos(0, 9, "before")); }, {mode: "text/html", value: "<!-- foo barrr -->", lineWrapping: true}, ie_lt8 || opera_lt10); testCM("measureWrappedEndOfLine", function(cm) { @@ -1121,9 +1125,9 @@ testCM("measureWrappedEndOfLine", function(cm) { } var endPos = cm.charCoords(Pos(0, 12)); // Next-to-last since last would wrap (#1862) endPos.left += w; // Add width of editor just to be sure that we are behind last character - eqPos(cm.coordsChar(endPos), Pos(0, 13)); + eqPos(cm.coordsChar(endPos), Pos(0, 13, "before")); endPos.left += w * 100; - eqPos(cm.coordsChar(endPos), Pos(0, 13)); + eqPos(cm.coordsChar(endPos), Pos(0, 13, "before")); }, {mode: "text/html", value: "0123456789abcde0123456789", lineWrapping: true}, ie_lt8 || opera_lt10); testCM("scrollVerticallyAndHorizontally", function(cm) { @@ -1149,7 +1153,8 @@ testCM("moveVstuck", function(cm) { } cm.setCursor(Pos(0, val.length - 1)); cm.moveV(-1, "line"); - eqPos(cm.getCursor(), Pos(0, 26)); + eqPos(cm.getCursor(), Pos(0, 26, "before")); + is(cm.cursorCoords(null, "local").top < h0, "cursor is in first visual line"); }, {lineWrapping: true}, ie_lt8 || opera_lt10); testCM("collapseOnMove", function(cm) {