   )
 
 _trackDelta = (fn, options) ->
-  oldDelta = @doc.toDelta()
   oldIndex = @savedRange?.start.index
   fn()
   newDelta = @doc.toDelta()
   try
     newIndex = @selection.getRange()?.start.index     # this.getSelection() triggers infinite loop
-    if oldIndex? and newIndex? and oldIndex <= oldDelta.endLength and newIndex <= newDelta.endLength
-      [oldLeftDelta, oldRightDelta] = oldDelta.split(oldIndex)
+    if oldIndex? and newIndex? and oldIndex <= @delta.endLength and newIndex <= newDelta.endLength
+      [oldLeftDelta, oldRightDelta] = @delta.split(oldIndex)
       [newLeftDelta, newRightDelta] = newDelta.split(newIndex)
       decomposeLeft = newLeftDelta.decompose(oldLeftDelta)
       decomposeRight = newRightDelta.decompose(oldRightDelta)
       decomposeA = decomposeLeft.merge(decomposeRight)
   catch ignored
-  decomposeB = newDelta.decompose(oldDelta)
+  decomposeB = newDelta.decompose(@delta)
   if decomposeA and decomposeB
     decompose = if decomposeA.ops.length < decomposeB.ops.length then decomposeA else decomposeB
   else
     decompose = decomposeA or decomposeB
-  if !decompose.isIdentity() and !options.silent
-    eventName = if options.source == 'api' then ScribeEditor.events.API_TEXT_CHANGE else ScribeEditor.events.USER_TEXT_CHANGE
-    this.emit(eventName, decompose)
-    return decompose
-  return false
-  
-
-class ScribeEditor extends EventEmitter2
-  @editors: []
-
-  @ID_PREFIX: 'editor-'
-
-  @DEFAULTS:
-    cursor: 0
-    enabled: true
-    onReady: ->
-    formatManager: {}
-    renderer: {}
-    undoManager: {}
-
-  @events:
-    API_TEXT_CHANGE  : 'api-text-change'
-    PRE_EVENT        : 'pre-event'
-    POST_EVENT       : 'post-event'
-    SELECTION_CHANGE : 'selection-change'
-    USER_TEXT_CHANGE : 'user-text-change'
-
-
-  constructor: (@iframeContainer, options = {}) ->
-    @id = _.uniqueId(ScribeEditor.ID_PREFIX)
-    @options = _.defaults(options, ScribeEditor.DEFAULTS)
-    @options.renderer['id'] = @id
-    @iframeContainer = document.getElementById(@iframeContainer) if _.isString(@iframeContainer)
-    this.reset(true)
-    setInterval( =>
-      changed = _checkUpdate.call(this)
-      @selection.update(changed)
-    , 100)
-    this.on(ScribeEditor.events.SELECTION_CHANGE, (range) =>
-      @savedRange = range
-    )
-    this.enable() if @options.enabled
-
-  disable: ->
-    this.doSilently( =>
-      @root.setAttribute('contenteditable', false)
-    )
-
-  enable: ->
-    this.doSilently( =>
-      @root.setAttribute('contenteditable', true)
-    )
-
-  reset: (keepHTML = false) ->
-    @ignoreDomChanges = true
-    @options.renderer.keepHTML = keepHTML
-    @iframeContainer.innerHTML = @root.innerHTML if @root?
-    @renderer = new ScribeRenderer(@iframeContainer, @options)
-    @contentWindow = @renderer.iframe.contentWindow
-    @root = @renderer.root
-    @doc = new ScribeDocument(@root, @options)
-    @keyboard = new ScribeKeyboard(this)
-    @selection = new ScribeSelection(this)
-    @undoManager = new ScribeUndoManager(this, @options)
-    @pasteManager = new ScribePasteManager(this)
-    @renderer.runWhenLoaded(@options.onReady)
-    @ignoreDomChanges = false
-    ScribeEditor.editors.push(this)
-
-  applyDelta: (delta, options = {}) ->
-    options = _.defaults(options, DEFAULT_API_OPTIONS)
-    # Make exception for systems that assume editors start with empty text
-    if delta.startLength == 0 and this.getLength() == 1
-      return this.setDelta(delta, options)
-    return if delta.isIdentity()
-    this.doSilently( =>
-      throw new Error("Trying to apply delta to incorrect doc length") unless delta.startLength == this.getLength()
-      oldDelta = @doc.toDelta()
-      delta.apply(_insertAt, _deleteAt, _formatAt, this)
-      unless options.silent
-        eventName = if options.source == 'api' then ScribeEditor.events.API_TEXT_CHANGE else ScribeEditor.events.USER_TEXT_CHANGE
-        this.emit(eventName, delta)
-      # TODO enable when we figure out addNewline issue, currently will fail if we do add newline
-      # console.assert(delta.endLength == this.getLength(), "Applying delta resulted in incorrect end length", delta, this.getLength())
-      _forceTrailingNewline.call(this)
-    )
-
-  emit: (eventName, args...) ->
-    super(ScribeEditor.events.PRE_EVENT, eventName, args...)
-    super(eventName, args...)
-    super(ScribeEditor.events.POST_EVENT, eventName, args...)
-
-  deleteAt: (index, length, options = {}) ->
-    options = _.defaults(options, DEFAULT_API_OPTIONS)
-    _doAt.call(this, =>
-      _deleteAt.call(this, index, length)
-      _forceTrailingNewline.call(this)
-    , options)
-
-  doSilently: (fn) ->
-    oldIgnoreDomChange = @ignoreDomChanges
-    @ignoreDomChanges = true
-    fn()
-    @ignoreDomChanges = oldIgnoreDomChange
-
-  formatAt: (index, length, name, value, options = {}) ->
-    options = _.defaults(options, DEFAULT_API_OPTIONS)
-    _doAt.call(this, =>
-      _formatAt.call(this, index, length, name, value)
-    , options)
-    
-  getDelta: ->
-    _checkUpdate.call(this)
-    return @doc.toDelta()
-
-  getLength: ->
-    return this.getDelta().endLength
-
-  getSelection: ->
-    _checkUpdate.call(this)
-    return @selection.getRange()
-
-  insertAt: (index, text, formatting = {}, options = {}) ->
-    options = _.defaults(options, DEFAULT_API_OPTIONS)
-    _doAt.call(this, =>
-      _insertAt.call(this, index, text, formatting)
-      _forceTrailingNewline.call(this)
-    , options)
-
-  setDelta: (delta) ->
-    oldLength = delta.startLength
-    delta.startLength = this.getLength()
-    this.applyDelta(delta, { silent: true })
-    @undoManager.clear()
-    delta.startLength = oldLength
-
-  setSelection: (range, silent = false) ->
-    @selection.setRange(range, silent)
-
-  update: ->
-    return _doAt.call(this, =>
+  return decompose
+
+_update = ->
+  delta = _trackDelta.call(this, =>
+    this.doSilently( =>
       @selection.preserve( =>
         ScribeNormalizer.breakBlocks(@root)
         lines = @doc.lines.toArray()

          newLine = @doc.appendLine(lineNode)
          lineNode = lineNode.nextSibling
      )
      @innerHTML = @root.innerHTML    # trackDelta will emit events that may cause clients to call get functions
    , { silent: false, source: 'user' })


module.exports = ScribeEditor


DEFAULT_API_OPTIONS = { silent: false, source: 'api' }


_deleteAt = (index, length) ->
  return if length <= 0
