+doSilently = (fn) ->
+  oldIgnoreDomChange = @ignoreDomChanges
+  @ignoreDomChanges = true
+  fn()
+  @ignoreDomChanges = oldIgnoreDomChange
+
+initListeners = ->
+  onEditOnce = =>
+  onEdit = =>
+    onEditOnce = _.once(onEdit)
+    return if @ignoreDomChanges
+    update.call(this)
+  onSubtreeModified = =>
+    return if @ignoreDomChanges
+    toCall = onEditOnce
+    _.defer( =>
+      toCall.call(null)
+    )
+  onEditOnce = _.once(onEdit)
+  @root.addEventListener('DOMSubtreeModified', onSubtreeModified)
+
+keepNormalized = (fn) ->
+  fn.call(this)
+  @doc.rebuildDirty()
+  @doc.forceTrailingNewline()
+
+trackDelta = (fn) ->
+  oldDelta = @doc.toDelta()
+  fn()
+  newDelta = @doc.toDelta()
+  decompose = newDelta.decompose(oldDelta)
+  compose = oldDelta.compose(decompose)
+  console.assert(compose.isEqual(newDelta), oldDelta, newDelta, decompose, compose)
+  @undoManager.record(decompose, oldDelta)
+  this.emit(ScribeEditor.events.TEXT_CHANGE, decompose) unless decompose.isIdentity()
+  return decompose
+
+update = ->
+  doSilently.call(this, =>
+    @selection.preserve( =>
+      return trackDelta.call(this, =>
+        Scribe.Document.normalizeHtml(@root)
+        lines = @doc.lines.toArray()
+        lineNode = @root.firstChild
+        _.each(lines, (line, index) =>
+          while line.node != lineNode
+            if line.node.parentNode == @root
+              newLine = @doc.insertLineBefore(lineNode, line)
+              lineNode = lineNode.nextSibling
+            else
+              @doc.removeLine(line)
+              return
+          @doc.updateLine(line)
+          lineNode = lineNode.nextSibling
+        )
+        while lineNode != null
+          newLine = @doc.appendLine(lineNode)
+          lineNode = lineNode.nextSibling
+      )
+    )
+  )
+
+
 class ScribeEditor extends EventEmitter2
   @editors: []
 

    return @selection.getRange()

  insertAt: (index, text) ->
    this.doSilently( =>
      @selection.preserve( =>
        return this.trackDelta( =>
          this.keepNormalized( =>
            @doc.insertText(index, text)
          )
        )

    )

  deleteAt: (index, length) ->
    doSilently.call(this, =>
      @selection.preserve( =>
        return trackDelta.call(this, =>
          keepNormalized.call(this, =>
            @doc.deleteText(index, length)
          )
        )
      )
    )

  # formatAt: (Number index, Number length, String name, Mixed value) ->
  formatAt: (index, length, name, value) ->
    doSilently.call(this, =>
      @selection.preserve( =>
        return trackDelta.call(this, =>
          keepNormalized.call(this, =>
            @doc.formatText(index, length, name, value)
          )
        )
