text: import cledit from './cleditCore'
  
  function SelectionMgr(editor) {
    var debounce = cledit.Utils.debounce
    var contentElt = editor.$contentElt
    var scrollElt = editor.$scrollElt
    cledit.Utils.createEventHooks(this)
  
    var self = this
    var lastSelectionStart = 0
    var lastSelectionEnd = 0
    this.selectionStart = 0
    this.selectionEnd = 0
    this.cursorCoordinates = {}
  
    this.findContainer = function (offset) {
      var result = cledit.Utils.findContainer(contentElt, offset)
      if (result.container.nodeValue === '\n') {
        var hdLfElt = result.container.parentNode
        if (hdLfElt.className === 'hd-lf' && hdLfElt.previousSibling && hdLfElt.previousSibling.tagName === 'BR') {
          result.container = hdLfElt.parentNode
          result.offsetInContainer = Array.prototype.indexOf.call(result.container.childNodes, result.offsetInContainer === 0 ? hdLfElt.previousSibling : hdLfElt)
        }
      }
      return result
    }
  
    this.createRange = function (start, end) {
      var range = editor.$document.createRange()
      if (start === end) {
        end = start = isNaN(start) ? start : this.findContainer(start < 0 ? 0 : start)
      } else {
        start = isNaN(start) ? start : this.findContainer(start < 0 ? 0 : start)
        end = isNaN(end) ? end : this.findContainer(end < 0 ? 0 : end)
      }
      range.setStart(start.container, start.offsetInContainer)
      range.setEnd(end.container, end.offsetInContainer)
      return range
    }
  
    var adjustScroll
    var debouncedUpdateCursorCoordinates = debounce(function () {
      var coordinates = this.getCoordinates(this.selectionEnd, this.selectionEndContainer, this.selectionEndOffset)
      if (this.cursorCoordinates.top !== coordinates.top ||
        this.cursorCoordinates.height !== coordinates.height ||
        this.cursorCoordinates.left !== coordinates.left
      ) {
        this.cursorCoordinates = coordinates
        this.$trigger('cursorCoordinatesChanged', coordinates)
      }
      if (adjustScroll) {
        var scrollEltHeight = scrollElt.clientHeight;
        if (typeof adjustScroll === 'number') {
          scrollEltHeight -= adjustScroll
        }
        var adjustment = scrollEltHeight / 2 * editor.options.getCursorFocusRatio()
        var cursorTop = this.cursorCoordinates.top + this.cursorCoordinates.height / 2
        // Adjust cursorTop with contentElt position relative to scrollElt
        cursorTop += contentElt.getBoundingClientRect().top - scrollElt.getBoundingClientRect().top + scrollElt.scrollTop;
        var minScrollTop = cursorTop - adjustment
        var maxScrollTop = cursorTop + adjustment - scrollEltHeight
        if (scrollElt.scrollTop > minScrollTop) {
          scrollElt.scrollTop = minScrollTop
        } else if (scrollElt.scrollTop < maxScrollTop) {
          scrollElt.scrollTop = maxScrollTop
        }
      }
      adjustScroll = false
    }.cl_bind(this))
  
    this.updateCursorCoordinates = function (adjustScrollParam) {
      adjustScroll = adjustScroll || adjustScrollParam
      debouncedUpdateCursorCoordinates()
    }
  
    var oldSelectionRange
  
    function checkSelection(selectionRange) {
      if (!oldSelectionRange ||
        oldSelectionRange.startContainer !== selectionRange.startContainer ||
        oldSelectionRange.startOffset !== selectionRange.startOffset ||
        oldSelectionRange.endContainer !== selectionRange.endContainer ||
        oldSelectionRange.endOffset !== selectionRange.endOffset
      ) {
        oldSelectionRange = selectionRange
        self.$trigger('selectionChanged', self.selectionStart, self.selectionEnd, selectionRange)
        return true
      }
    }
  
    this.hasFocus = function() {
      return contentElt === editor.$document.activeElement;
    }
  
    this.restoreSelection = function () {
      var min = Math.min(this.selectionStart, this.selectionEnd)
      var max = Math.max(this.selectionStart, this.selectionEnd)
      var selectionRange = this.createRange(min, max)
      if (editor.$document.contains(selectionRange.commonAncestorContainer)) {
        var selection = editor.$window.getSelection()
        selection.removeAllRanges()
        var isBackward = this.selectionStart > this.selectionEnd
        if (selection.extend) {
          var beginRange = selectionRange.cloneRange()
          beginRange.collapse(!isBackward)
          selection.addRange(beginRange)
          if (isBackward) {
            selection.extend(selectionRange.startContainer, selectionRange.startOffset)
          } else {
            selection.extend(selectionRange.endContainer, selectionRange.endOffset)
          }
        } else {
          selection.addRange(selectionRange)
        }
        checkSelection(selectionRange)
        return selectionRange
      }
    }
  
    var saveLastSelection = debounce(function () {
      lastSelectionStart = self.selectionStart
      lastSelectionEnd = self.selectionEnd
    }, 50)
  
    function setSelection(start, end) {
      if (start === undefined) {
        start = self.selectionStart
      }
      if (start < 0) {
        start = 0
      }
      if (end === undefined) {
        end = this.selectionEnd
      }
      if (end < 0) {
        end = 0
      }
      self.selectionStart = start
      self.selectionEnd = end
      saveLastSelection()
    }
  
    this.setSelectionStartEnd = function (start, end) {
      setSelection(start, end)
      return this.hasFocus() && this.restoreSelection()
    }
  
    this.saveSelectionState = (function () {
      // Credit: https://github.com/timdown/rangy
      function arrayContains(arr, val) {
        var i = arr.length
        while (i--) {
          if (arr[i] === val) {
            return true
          }
        }
        return false
      }
  
      function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
        var p
        var n = selfIsAncestor ? node : node.parentNode
        while (n) {
          p = n.parentNode
          if (p === ancestor) {
            return n
          }
          n = p
        }
        return null
      }
  
      function getNodeIndex(node) {
        var i = 0
        while ((node = node.previousSibling)) {
          ++i
        }
        return i
      }
  
      function getCommonAncestor(node1, node2) {
        var ancestors = []
        var n
        for (n = node1; n; n = n.parentNode) {
          ancestors.push(n)
        }
  
        for (n = node2; n; n = n.parentNode) {
          if (arrayContains(ancestors, n)) {
            return n
          }
        }
  
        return null
      }
  
      function comparePoints(nodeA, offsetA, nodeB, offsetB) {
        // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
        var nodeC, root, childA, childB, n
        if (nodeA === nodeB) {
          // Case 1: nodes are the same
          return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1
        } else if (
          (nodeC = getClosestAncestorIn(nodeB, nodeA, true))
        ) {
          // Case 2: node C (container B or an ancestor) is a child node of A
          return offsetA <= getNodeIndex(nodeC) ? -1 : 1
        } else if (
          (nodeC = getClosestAncestorIn(nodeA, nodeB, true))
        ) {
          // Case 3: node C (container A or an ancestor) is a child node of B
          return getNodeIndex(nodeC) < offsetB ? -1 : 1
        } else {
          root = getCommonAncestor(nodeA, nodeB)
          if (!root) {
            throw new Error('comparePoints error: nodes have no common ancestor')
          }
  
          // Case 4: containers are siblings or descendants of siblings
          childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true)
          childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true)
  
          if (childA === childB) {
            // This shouldn't be possible
            throw module.createError('comparePoints got to case 4 and childA and childB are the same!')
          } else {
            n = root.firstChild
            while (n) {
              if (n === childA) {
                return -1
              } else if (n === childB) {
                return 1
              }
              n = n.nextSibling
            }
          }
        }
      }
  
      function save() {
        var result
        if (self.hasFocus()) {
          var selectionStart = self.selectionStart
          var selectionEnd = self.selectionEnd
          var selection = editor.$window.getSelection()
          if (selection.rangeCount > 0) {
            var selectionRange = selection.getRangeAt(0)
            var node = selectionRange.startContainer
            if ((contentElt.compareDocumentPosition(node) & window.Node.DOCUMENT_POSITION_CONTAINED_BY) || contentElt === node) {
              var offset = selectionRange.startOffset
              if (node.firstChild && offset > 0) {
                node = node.childNodes[offset - 1]
                offset = node.textContent.length
              }
              var container = node
              while (node !== contentElt) {
                while ((node = node.previousSibling)) {
                  offset += (node.textContent || '').length
                }
                node = container = container.parentNode
              }
              var selectionText = selectionRange + ''
              // Fix end of line when only br is selected
              var brElt = selectionRange.endContainer.firstChild
              if (brElt && brElt.tagName === 'BR' && selectionRange.endOffset === 1) {
                selectionText += '\n'
              }
              if (comparePoints(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset) === 1) {
                selectionStart = offset + selectionText.length
                selectionEnd = offset
              } else {
                selectionStart = offset
                selectionEnd = offset + selectionText.length
              }
  
              if (selectionStart === selectionEnd && selectionStart === editor.getContent().length) {
                // If cursor is after the trailingNode
                selectionStart = --selectionEnd
                result = self.setSelectionStartEnd(selectionStart, selectionEnd)
              } else {
                setSelection(selectionStart, selectionEnd)
                result = checkSelection(selectionRange)
                result = result || lastSelectionStart !== self.selectionStart // selectionRange doesn't change when selection is at the start of a section
              }
            }
          }
        }
        return result
      }
  
      function saveCheckChange() {
        return save() && (lastSelectionStart !== self.selectionStart || lastSelectionEnd !== self.selectionEnd)
      }
  
      var nextTickAdjustScroll = false
      var debouncedSave = debounce(function () {
        self.updateCursorCoordinates(saveCheckChange() && nextTickAdjustScroll)
        // In some cases we have to wait a little longer to see the selection change (Cmd+A on Chrome OSX)
        longerDebouncedSave()
      })
      var longerDebouncedSave = debounce(function () {
        self.updateCursorCoordinates(saveCheckChange() && nextTickAdjustScroll)
        nextTickAdjustScroll = false
      }, 10)
  
      return function (debounced, adjustScroll, forceAdjustScroll) {
        if (forceAdjustScroll) {
          lastSelectionStart = undefined
          lastSelectionEnd = undefined
        }
        if (debounced) {
          nextTickAdjustScroll = nextTickAdjustScroll || adjustScroll
          return debouncedSave()
        } else {
          save()
        }
      }
    })()
  
    this.getSelectedText = function () {
      var min = Math.min(this.selectionStart, this.selectionEnd)
      var max = Math.max(this.selectionStart, this.selectionEnd)
      return editor.getContent().substring(min, max)
    }
  
    this.getCoordinates = function (inputOffset, container, offsetInContainer) {
      if (!container) {
        var offset = this.findContainer(inputOffset)
        container = offset.container
        offsetInContainer = offset.offsetInContainer
      }
      var containerElt = container
      if (!containerElt.hasChildNodes()) {
        containerElt = container.parentNode
      }
      var isInvisible = false
      var index = editor.$allElements.indexOf(containerElt)
      while (containerElt.offsetHeight === 0 && index > 0) {
        isInvisible = true
        containerElt = editor.$allElements[--index]
      }
      var rect
      var contentRect
      var left = 'left'
      if (isInvisible || container.textContent === '\n') {
        rect = containerElt.getBoundingClientRect()
      } else {
        var selectedChar = editor.getContent()[inputOffset]
        var startOffset = {
          container: container,
          offsetInContainer: offsetInContainer
        }
        var endOffset = {
          container: container,
          offsetInContainer: offsetInContainer
        }
        if (inputOffset > 0 && (selectedChar === undefined || selectedChar === '\n')) {
          left = 'right'
          if (startOffset.offsetInContainer === 0) {
            // Need to calculate offset-1
            startOffset = inputOffset - 1
          } else {
            startOffset.offsetInContainer -= 1
          }
        } else {
          if (endOffset.offsetInContainer === container.textContent.length) {
            // Need to calculate offset+1
            endOffset = inputOffset + 1
          } else {
            endOffset.offsetInContainer += 1
          }
        }
        var range = this.createRange(startOffset, endOffset)
        rect = range.getBoundingClientRect()
      }
      contentRect = contentElt.getBoundingClientRect()
      return {
        top: Math.round(rect.top - contentRect.top + contentElt.scrollTop),
        height: Math.round(rect.height),
        left: Math.round(rect[left] - contentRect.left + contentElt.scrollLeft)
      }
    }
  
    this.getClosestWordOffset = function (offset) {
      var offsetStart = 0
      var offsetEnd = 0
      var nextOffset = 0
      editor.getContent().split(/\s/).cl_some(function (word) {
        if (word) {
          offsetStart = nextOffset
          offsetEnd = nextOffset + word.length
          if (offsetEnd > offset) {
            return true
          }
        }
        nextOffset += word.length + 1
      })
      return {
        start: offsetStart,
        end: offsetEnd
      }
    }
  }
  
  cledit.SelectionMgr = SelectionMgr
  