<commit_before>#= require trix/text
#= require trix/input
#= require trix/text_view
#= require trix/selection_observer
#= require trix/attachment_controller

class Trix.TextController
  constructor: (@element) ->
    @text = new Trix.Text
    @text.delegate = this
    @input = new Trix.Input @element, this
    @textView = new Trix.TextView @element, @text
    @selectionObserver = new Trix.SelectionObserver
    @selectionObserver.delegate = this
    @attachmentController = new Trix.AttachmentController @element
    @attachmentController.delegate = this

    @currentAttributes = {}
    @element.addEventListener("focus", @didFocus)

  focus: ->
    @textView.focus()

  didFocus: =>
    if lockedRange = @unlockSelection()
      @setPosition(lockedRange[1])
    @delegate?.textControllerDidFocus?()

  # Text delegate

  didEditText: (text) ->
    @render()

  # Attachment controller delegate

  attachmentControllerDidChangeAttributesAtPosition: (attributes, position) ->
    @text.addAttributesAtRange(attributes, [position, position + 1])

  # Input responder

  insertText: (text, updatePosition = true) ->
    if selectedRange = @getSelectedRange()
      position = selectedRange[0]
      @text.replaceTextAtRange(text, selectedRange)
    else
      position = @getPosition()
      @text.insertTextAtPosition(text, position)

    @setPosition(position + (if updatePosition then text.getLength() else 0))

  insertString: (string, updatePosition = true) ->
    text = Trix.Text.textForStringWithAttributes(string, @currentAttributes)
    @insertText(text, updatePosition)

  insertAttachment: (attachment, updatePosition = true) ->
    text = Trix.Text.textForAttachmentWithAttributes(attachment, @currentAttributes)
    @insertText(text, updatePosition)

  deleteFromCurrentPosition: (distance = -1) ->
    unless range = @getSelectedRange()
      position = @getPosition()
      offset = position + distance
      range = if distance < 0 then [offset, position] else [position, offset]

    @text.removeTextAtRange(range)
    @setPosition(range[0])

  deleteBackward: ->
    @deleteFromCurrentPosition(-1)

  deleteForward: ->
    @deleteFromCurrentPosition(1)

  deleteWordBackward: ->
    if @getSelectedRange()
      @deleteBackward()
    else
      position = @getPosition()
      stringBeforePosition = @text.getStringAtRange([0, position])
      positionBeforeLastWord = stringBeforePosition.search(/(\b\w+)\W*$/)
      @deleteFromCurrentPosition(positionBeforeLastWord - position)

  positionAtPoint: (point) ->
    position = @textView.getPositionAtPoint(point)
    @setPosition(position)

  moveTextFromRange: (range) ->
    position = @getPosition()
    @text.moveTextFromRangeToPosition(range, position)
    @setPosition(position)

  getTextFromSelection: ->
    if selectedRange = @getSelectedRange()
      @text.getTextAtRange(selectedRange)

  beginComposing: ->
    @textView.lockSelection()
    @composing = true

  isComposing: ->
    @composing

  endComposing: (composedString) ->
    @render()
    @textView.unlockSelection()
    @insertString(composedString)
    delete @composing

  render: ->
    @textView.render()
    @delegate?.textControllerDidRender?()

  # Current attributes

  toggleCurrentAttribute: (attributeName) ->
    value = not @currentAttributes[attributeName]
    @setCurrentAttribute(attributeName, value)

  setCurrentAttribute: (attributeName, value) ->
    if selectedRange = @getSelectedRange()
      if value
        attributes = {}
        attributes[attributeName] = value
        @text.addAttributesAtRange(attributes, selectedRange)
      else
        @text.removeAttributeAtRange(attributeName, selectedRange)
    else
      if value
        @currentAttributes[attributeName] = value
      else
        delete @currentAttributes[attributeName]

    @notifyDelegateOfCurrentAttributesChange()

  updateCurrentAttributes: ->
    if selectedRange = @getSelectedRange()
      @currentAttributes = @text.getCommonAttributesAtRange(selectedRange)
      @notifyDelegateOfCurrentAttributesChange()
    else if (position = @getPosition())?
      # TODO: Consider whitelisting allowed current attributes from previous position
      @currentAttributes = @text.getAttributesAtPosition(position - 1)
      @adjustCurrentAttributesForPosition(position)
      @notifyDelegateOfCurrentAttributesChange()

  adjustCurrentAttributesForPosition: (position) ->
    # Don't consider these attributes current when the cursor is positioned after them
    for attribute in ["href", "width", "height"]
      if currentAttribute = @currentAttributes[attribute]
        if currentAttribute isnt @text.getAttributesAtPosition(position)[attribute]
          delete @currentAttributes[attribute]

  notifyDelegateOfCurrentAttributesChange: ->
    @delegate?.textControllerDidChangeCurrentAttributes?(@currentAttributes)

  # Selection observer delegate

  selectionDidChange: (range) ->
    @expireCachedSelectedRange()
    @updateCurrentAttributes()
    @delegate?.textControllerDidChangeSelection?()

  # Selection

  getCachedSelectedRangeFromTextView: ->
    @cachedSelectedRange ?= @textView.getSelectedRange()

  expireCachedSelectedRange: ->
    delete @cachedSelectedRange

  getSelectedRange: ->
    if selectedRange = @getCachedSelectedRangeFromTextView()
      selectedRange unless rangeIsCollapsed(selectedRange)

  getPosition: ->
    if selectedRange = @getCachedSelectedRangeFromTextView()
      selectedRange[0] if rangeIsCollapsed(selectedRange)

  setPosition: (position) ->
    @focus()<commit_msg>Whitelist the attributes that can be inherited from the previous position and considered current<commit_after>#= require trix/text
#= require trix/input
#= require trix/text_view
#= require trix/selection_observer
#= require trix/attachment_controller

class Trix.TextController
  constructor: (@element) ->
    @text = new Trix.Text
    @text.delegate = this
    @input = new Trix.Input @element, this
    @textView = new Trix.TextView @element, @text
    @selectionObserver = new Trix.SelectionObserver
    @selectionObserver.delegate = this
    @attachmentController = new Trix.AttachmentController @element
    @attachmentController.delegate = this

    @currentAttributes = {}
    @element.addEventListener("focus", @didFocus)

  focus: ->
    @textView.focus()

  didFocus: =>
    if lockedRange = @unlockSelection()
      @setPosition(lockedRange[1])
    @delegate?.textControllerDidFocus?()

  # Text delegate

  didEditText: (text) ->
    @render()

  # Attachment controller delegate

  attachmentControllerDidChangeAttributesAtPosition: (attributes, position) ->
    @text.addAttributesAtRange(attributes, [position, position + 1])

  # Input responder

  insertText: (text, updatePosition = true) ->
    if selectedRange = @getSelectedRange()
      position = selectedRange[0]
      @text.replaceTextAtRange(text, selectedRange)
    else
      position = @getPosition()
      @text.insertTextAtPosition(text, position)

    @setPosition(position + (if updatePosition then text.getLength() else 0))

  insertString: (string, updatePosition = true) ->
    text = Trix.Text.textForStringWithAttributes(string, @currentAttributes)
    @insertText(text, updatePosition)

  insertAttachment: (attachment, updatePosition = true) ->
    text = Trix.Text.textForAttachmentWithAttributes(attachment, @currentAttributes)
    @insertText(text, updatePosition)

  deleteFromCurrentPosition: (distance = -1) ->
    unless range = @getSelectedRange()
      position = @getPosition()
      offset = position + distance
      range = if distance < 0 then [offset, position] else [position, offset]

    @text.removeTextAtRange(range)
    @setPosition(range[0])

  deleteBackward: ->
    @deleteFromCurrentPosition(-1)

  deleteForward: ->
    @deleteFromCurrentPosition(1)

  deleteWordBackward: ->
    if @getSelectedRange()
      @deleteBackward()
    else
      position = @getPosition()
      stringBeforePosition = @text.getStringAtRange([0, position])
      positionBeforeLastWord = stringBeforePosition.search(/(\b\w+)\W*$/)
      @deleteFromCurrentPosition(positionBeforeLastWord - position)

  positionAtPoint: (point) ->
    position = @textView.getPositionAtPoint(point)
    @setPosition(position)

  moveTextFromRange: (range) ->
    position = @getPosition()
    @text.moveTextFromRangeToPosition(range, position)
    @setPosition(position)

  getTextFromSelection: ->
    if selectedRange = @getSelectedRange()
      @text.getTextAtRange(selectedRange)

  beginComposing: ->
    @textView.lockSelection()
    @composing = true

  isComposing: ->
    @composing

  endComposing: (composedString) ->
    @render()
    @textView.unlockSelection()
    @insertString(composedString)
    delete @composing

  render: ->
    @textView.render()
    @delegate?.textControllerDidRender?()

  # Current attributes

  @inheritableAttributes = "bold italic underline".split(" ")

  toggleCurrentAttribute: (attributeName) ->
    value = not @currentAttributes[attributeName]
    @setCurrentAttribute(attributeName, value)

  setCurrentAttribute: (attributeName, value) ->
    if selectedRange = @getSelectedRange()
      if value
        attributes = {}
        attributes[attributeName] = value
        @text.addAttributesAtRange(attributes, selectedRange)
      else
        @text.removeAttributeAtRange(attributeName, selectedRange)
    else
      if value
        @currentAttributes[attributeName] = value
      else
        delete @currentAttributes[attributeName]

    @notifyDelegateOfCurrentAttributesChange()

  updateCurrentAttributes: ->
    if selectedRange = @getSelectedRange()
      @currentAttributes = @text.getCommonAttributesAtRange(selectedRange)
      @notifyDelegateOfCurrentAttributesChange()
    else if (position = @getPosition())?
      @currentAttributes = {}
      attributes = @text.getAttributesAtPosition(position)
      attributesLeft = @text.getAttributesAtPosition(position - 1)

      for key, value of attributesLeft
        if value is attributes[key] or key in @constructor.inheritableAttributes
          @currentAttributes[key] = value

      @notifyDelegateOfCurrentAttributesChange()

  notifyDelegateOfCurrentAttributesChange: ->
    @delegate?.textControllerDidChangeCurrentAttributes?(@currentAttributes)

  # Selection observer delegate

  selectionDidChange: (range) ->
    @expireCachedSelectedRange()
    @updateCurrentAttributes()
    @delegate?.textControllerDidChangeSelection?()

  # Selection

  getCachedSelectedRangeFromTextView: ->
    @cachedSelectedRange ?= @textView.getSelectedRange()

  expireCachedSelectedRange: ->
    delete @cachedSelectedRange

  getSelectedRange: ->
    if selectedRange = @getCachedSelectedRangeFromTextView()
      selectedRange unless rangeIsCollapsed(selectedRange)

  getPosition: ->
    if selectedRange = @getCachedSelectedRangeFromTextView()
      selectedRange[0] if rangeIsCollapsed(selectedRange)

  setPosition: (position) ->
    @focus()
{'fraction_non_alphanumeric': 0.09915323682054084, 'fraction_numerical': 0.002185195301830101, 'mean_word_length': 4.161654135338346, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 62, 'https://': 0, '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': '2113303', 'n_tokens_mistral': 2977, 'n_tokens_neox': 2892, 'n_words': 607}