     {width} = @newState
     {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
 
-    classes = ''
+    lineNode = document.createElement("div")
+    lineNode.classList.add("line")
     if decorationClasses?
       for decorationClass in decorationClasses
-        classes += decorationClass + ' '
-    classes += 'line'
-
-    lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{width}px;\" data-screen-row=\"#{screenRow}\">"
+        lineNode.classList.add(decorationClass)
+
+    lineNode.style.position = "absolute"
+    lineNode.style.top = top + "px"
+    lineNode.style.width = width + "px"
+    lineNode.dataset.screenRow = screenRow
 
     if text is ""
-      lineHTML += @buildEmptyLineInnerHTML(id)
+      @appendEmptyLineInnerNodes(id, lineNode)
     else
-      lineHTML += @buildLineInnerHTML(id)
-
-    lineHTML += '<span class="fold-marker"></span>' if fold
-    lineHTML += "</div>"
-    lineHTML
-
-  buildEmptyLineInnerHTML: (id) ->
+      @appendLineInnerNodes(id, lineNode)
+
+    if fold
+      foldMarker = document.createElement("span")
+      foldMarker.classList.add("fold-marker")
+      lineNode.appendChild(foldMarker)
+
+    lineNode.outerHTML
+
+  appendEmptyLineInnerNodes: (id, lineNode) ->
     {indentGuidesVisible} = @newState
     {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
 

        (invisibles?.tab and isHardTab) or
          (invisibles?.space and (hasLeadingWhitespace or hasTrailingWhitespace))

      innerHTML += @buildTokenHTML(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)

    for scope in @tokenIterator.getScopeEnds()
      innerHTML += "</span>"

    for scope in @tokenIterator.getScopes()
      innerHTML += "</span>"

    innerHTML += @buildEndOfLineHTML(id)
    innerHTML

  buildTokenHTML: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
    if isHardTab
      classes = 'hard-tab'
      classes += ' leading-whitespace' if firstNonWhitespaceIndex?
      classes += ' trailing-whitespace' if firstTrailingWhitespaceIndex?
      classes += ' indent-guide' if hasIndentGuide
      classes += ' invisible-character' if hasInvisibleCharacters
      return "<span class='#{classes}'>#{@escapeTokenText(tokenText)}</span>"
    else
      startIndex = 0
      endIndex = tokenText.length

      leadingHtml = ''
      trailingHtml = ''

      if firstNonWhitespaceIndex?
        leadingWhitespace = tokenText.substring(0, firstNonWhitespaceIndex)

        classes = 'leading-whitespace'
        classes += ' indent-guide' if hasIndentGuide
        classes += ' invisible-character' if hasInvisibleCharacters

        leadingHtml = "<span class='#{classes}'>#{leadingWhitespace}</span>"
        startIndex = firstNonWhitespaceIndex

      if firstTrailingWhitespaceIndex?
        tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
        trailingWhitespace = tokenText.substring(firstTrailingWhitespaceIndex)

        classes = 'trailing-whitespace'
        classes += ' indent-guide' if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
        classes += ' invisible-character' if hasInvisibleCharacters

        trailingHtml = "<span class='#{classes}'>#{trailingWhitespace}</span>"

        endIndex = firstTrailingWhitespaceIndex

      html = leadingHtml
      if tokenText.length > MaxTokenLength
        while startIndex < endIndex
          html += "<span>" + @escapeTokenText(tokenText, startIndex, startIndex + MaxTokenLength) + "</span>"
          startIndex += MaxTokenLength
      else
        html += @escapeTokenText(tokenText, startIndex, endIndex)

      html += trailingHtml
    html

  escapeTokenText: (tokenText, startIndex, endIndex) ->
    if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
      tokenText = tokenText.slice(startIndex, endIndex)
    tokenText.replace(TokenTextEscapeRegex, @escapeTokenTextReplace)

  escapeTokenTextReplace: (match) ->
    switch match
      when '&' then '&amp;'
      when '"' then '&quot;'
      when "'" then '&#39;'
      when '<' then '&lt;'
      when '>' then '&gt;'
      else match

  buildEndOfLineHTML: (id) ->
    {endOfLineInvisibles} = @newTileState.lines[id]

    html = ''
    if endOfLineInvisibles?
      for invisible in endOfLineInvisibles
        html += "<span class='invisible-character'>#{invisible}</span>"
    html

  updateLineNode: (id) ->
    oldLineState = @oldTileState.lines[id]

      invisibleIndex = 0
      lineHTML = ''
      for i in [0...indentLevel]
        indentGuide = document.createElement("span")
        indentGuide.classList.add("indent-guide")
        for j in [0...tabLength]
          if invisible = endOfLineInvisibles?[invisibleIndex++]
            invisibleCharacter = document.createElement("span")
            invisibleCharacter.classList.add("invisible-character")
            invisibleCharacter.textContent = invisible
            indentGuide.appendChild(invisibleCharacter)
          else
            indentGuide.insertAdjacentText("beforeend", " ")
        lineNode.appendChild(indentGuide)

      while invisibleIndex < endOfLineInvisibles?.length
        invisibleCharacter = document.createElement("span")
        invisibleCharacter.classList.add("invisible-character")
        invisibleCharacter.textContent = endOfLineInvisibles[invisibleIndex++]
        lineNode.appendChild(invisibleCharacter)
    else
      unless @appendEndOfLineNodes(id, lineNode)
        lineNode.insertAdjacentHTML("beforeend", "&nbsp;")

  appendLineInnerNodes: (id, lineNode) ->
    lineState = @newTileState.lines[id]
    {firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, invisibles} = lineState
    lineIsWhitespaceOnly = firstTrailingWhitespaceIndex is 0

    innerHTML = ""
    @tokenIterator.reset(lineState)
    openScopeNode = lineNode

    while @tokenIterator.next()
      for scope in @tokenIterator.getScopeEnds()
        openScopeNode = openScopeNode.parentElement

      for scope in @tokenIterator.getScopeStarts()
        newScopeNode = document.createElement("span")
        newScopeNode.className = scope.replace(/\.+/g, ' ')
        openScopeNode.appendChild(newScopeNode)

        openScopeNode = newScopeNode

      tokenStart = @tokenIterator.getScreenStart()
      tokenEnd = @tokenIterator.getScreenEnd()
