docs: function findPosH(doc, pos, dir, unit, visually) {
      var line = pos.line, ch = pos.ch, origDir = dir;
      var lineObj = getLine(doc, line);
      var possible = true;
      function findNextLine() {
        var l = line + dir;
        if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
        line = l;
        return lineObj = getLine(doc, l);
      }
      function moveOnce(boundToLine) {
        var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
        if (next == null) {
          if (!boundToLine && findNextLine()) {
            if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
            else ch = dir < 0 ? lineObj.text.length : 0;
          } else return (possible = false);
        } else ch = next;
        return true;
      }
  
      if (unit == "char") moveOnce();
      else if (unit == "column") moveOnce(true);
      else if (unit == "word" || unit == "group") {
        var sawType = null, group = unit == "group";
        var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
        for (var first = true;; first = false) {
          if (dir < 0 && !moveOnce(!first)) break;
          var cur = lineObj.text.charAt(ch) || "\n";
          var type = isWordChar(cur, helper) ? "w"
            : group && cur == "\n" ? "n"
            : !group || /\s/.test(cur) ? null
            : "p";
          if (group && !first && !type) type = "s";
          if (sawType && sawType != type) {
            if (dir < 0) {dir = 1; moveOnce();}
            break;
          }
  
          if (type) sawType = type;
          if (dir > 0 && !moveOnce(!first)) break;
        }
      }
      var result = skipAtomic(doc, Pos(line, ch), origDir, true);
      if (!possible) result.hitSide = true;
      return result;
    }