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) {