<commit_before>#= require trix/models/location_range
#= require trix/observers/selection_change_observer

{DOM} = Trix
{defer, benchmark} = Trix.Helpers

class Trix.SelectionManager
  constructor: (@element) ->
    @lockCount = 0
    Trix.selectionChangeObserver.registerSelectionManager(this)

  getLocationRange: ->
    @lockedLocationRange ? @currentLocationRange

  setLocationRange: (start, end) ->
    return if @lockedLocationRange

    locationRange = if start instanceof Trix.LocationRange
      start
    else
      new Trix.LocationRange start, end

    @setDOMRange(locationRange)
    @updateCurrentLocationRange(locationRange)

  setLocationRangeFromPoint: (point) ->
    locationRange = @getLocationRangeAtPoint(point)
    @setLocationRange(locationRange)

  expandSelectionInDirectionWithGranularity: (direction, granularity) ->
    return unless selection = getDOMSelection()
    if selection.modify
      selection.modify("extend", direction, granularity)
    else if document.body.createTextRange
      [leftPoint, rightPoint] = @getSelectionEndPoints()

      leftRange = document.body.createTextRange()
      leftRange.moveToPoint(leftPoint...)

      rightRange = document.body.createTextRange()
      rightRange.moveToPoint(rightPoint...)

      if direction is "forward"
        rightRange.move(granularity, 1)
      else
        leftRange.move(granularity, -1)

      leftRange.setEndPoint("EndToEnd", rightRange)
      leftRange.select()
    Trix.selectionChangeObserver.update()

  locationIsCursorTarget: (location) ->
    [node, offset] = @findNodeAndOffsetForLocation(location)
    nodeIsCursorTarget(node)

  lock: ->
    if @lockCount++ is 0
      @lockedLocationRange = @getLocationRange()

  unlock: ->
    if --@lockCount is 0
      lockedLocationRange = @lockedLocationRange
      delete @lockedLocationRange
      @setLocationRange(lockedLocationRange) if lockedLocationRange?

  preserveSelection: (block) ->
    point = @getSelectionEndPoints()[0]
    block()
    locationRange = @getLocationRangeAtPoint(point)
    @setDOMRange(locationRange)
    locationRange

  clearSelection: ->
    getDOMSelection()?.removeAllRanges()

  # Private

  selectionDidChange: =>
    unless DOM.elementContainsNode(document.documentElement, @element)
      Trix.selectionChangeObserver.unregisterSelectionManager(this)
    @updateCurrentLocationRange()

  updateCurrentLocationRange: (locationRange) ->
    locationRange ?= @createLocationRangeFromDOMRange(getDOMRange())
    return unless locationRange
    if (@currentLocationRange and not locationRange) or not locationRange?.isEqualTo(@currentLocationRange)
      @currentLocationRange = locationRange
      @delegate?.locationRangeDidChange?(@currentLocationRange)

  setDOMRange: benchmark "SelectionManager#setDOMRange", (locationRange) ->
    rangeStart = @findContainerAndOffsetForLocation(locationRange.start)
    rangeEnd =
      if locationRange.isCollapsed()
        rangeStart
      else
        @findContainerAndOffsetForLocation(locationRange.end)

    range = document.createRange()
    range.setStart(rangeStart...)
    range.setEnd(rangeEnd...)

    applyRange = benchmark "  #setDOMRange -> applyRange", ->
      selection = window.getSelection()
      selection.removeAllRanges()
      selection.addRange(range)

    if Trix.env is "test"
      applyRange()
    else
      # Selection#addRange is unreasonably slow in WebKit when performed in the
      # same call stack as a mouse or key event so defer calling it.
      # https://code.google.com/p/chromium/issues/detail?id=423170
      # https://code.google.com/p/chromium/issues/detail?id=138439
      defer(applyRange)
<commit_msg>Fail fast when passing an invalid location range to #setLocationRange<commit_after>#= require trix/models/location_range
#= require trix/observers/selection_change_observer

{DOM} = Trix
{defer, benchmark} = Trix.Helpers

class Trix.SelectionManager
  constructor: (@element) ->
    @lockCount = 0
    Trix.selectionChangeObserver.registerSelectionManager(this)

  getLocationRange: ->
    @lockedLocationRange ? @currentLocationRange

  setLocationRange: (start, end) ->
    return if @lockedLocationRange

    locationRange = if start instanceof Trix.LocationRange
      start
    else
      new Trix.LocationRange start, end

    if locationRange = @setDOMRange(locationRange)
      @updateCurrentLocationRange(locationRange)
      locationRange

  setLocationRangeFromPoint: (point) ->
    locationRange = @getLocationRangeAtPoint(point)
    @setLocationRange(locationRange)

  expandSelectionInDirectionWithGranularity: (direction, granularity) ->
    return unless selection = getDOMSelection()
    if selection.modify
      selection.modify("extend", direction, granularity)
    else if document.body.createTextRange
      [leftPoint, rightPoint] = @getSelectionEndPoints()

      leftRange = document.body.createTextRange()
      leftRange.moveToPoint(leftPoint...)

      rightRange = document.body.createTextRange()
      rightRange.moveToPoint(rightPoint...)

      if direction is "forward"
        rightRange.move(granularity, 1)
      else
        leftRange.move(granularity, -1)

      leftRange.setEndPoint("EndToEnd", rightRange)
      leftRange.select()
    Trix.selectionChangeObserver.update()

  locationIsCursorTarget: (location) ->
    [node, offset] = @findNodeAndOffsetForLocation(location)
    nodeIsCursorTarget(node)

  lock: ->
    if @lockCount++ is 0
      @lockedLocationRange = @getLocationRange()

  unlock: ->
    if --@lockCount is 0
      lockedLocationRange = @lockedLocationRange
      delete @lockedLocationRange
      @setLocationRange(lockedLocationRange) if lockedLocationRange?

  preserveSelection: (block) ->
    point = @getSelectionEndPoints()[0]
    block()
    locationRange = @getLocationRangeAtPoint(point)
    @setDOMRange(locationRange)
    locationRange

  clearSelection: ->
    getDOMSelection()?.removeAllRanges()

  # Private

  selectionDidChange: =>
    unless DOM.elementContainsNode(document.documentElement, @element)
      Trix.selectionChangeObserver.unregisterSelectionManager(this)
    @updateCurrentLocationRange()

  updateCurrentLocationRange: (locationRange) ->
    locationRange ?= @createLocationRangeFromDOMRange(getDOMRange())
    return unless locationRange
    if (@currentLocationRange and not locationRange) or not locationRange?.isEqualTo(@currentLocationRange)
      @currentLocationRange = locationRange
      @delegate?.locationRangeDidChange?(@currentLocationRange)

  setDOMRange: benchmark "SelectionManager#setDOMRange", (locationRange) ->
    rangeStart = @findContainerAndOffsetForLocation(locationRange.start)
    rangeEnd =
      if locationRange.isCollapsed()
        rangeStart
      else
        @findContainerAndOffsetForLocation(locationRange.end)

    return unless rangeStart? and rangeEnd?

    range = document.createRange()
    range.setStart(rangeStart...)
    range.setEnd(rangeEnd...)

    applyRange = benchmark "  #setDOMRange -> applyRange", ->
      selection = window.getSelection()
      selection.removeAllRanges()
      selection.addRange(range)

    if Trix.env is "test"
      applyRange()
    else
      # Selection#addRange is unreasonably slow in WebKit when performed in the
      # same call stack as a mouse or key event so defer calling it.
      # https://code.google.com/p/chromium/issues/detail?id=423170
      # https://code.google.com/p/chromium/issues/detail?id=138439
      defer(applyRange)

    locationRange

{'fraction_non_alphanumeric': 0.09203351509509243, 'fraction_numerical': 0.0047878707274903575, 'mean_word_length': 3.927916120576671, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 33, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2965903', 'n_tokens_mistral': 2116, 'n_tokens_neox': 2025, 'n_words': 414}