     deletedScreenLineIds = @screenLineIndex.splice(startScreenRow, oldRowExtent, newScreenLines)
     for screenLineId in deletedScreenLineIds
       @invalidationCountsByScreenLineId.delete(screenLineId)
+      @screenLinesBySpatialLineId.delete(screenLineId)
     return
 
   invalidateScreenLines: (screenRange) ->
     for screenLineId in @screenLineIdsForScreenRange(screenRange)
       count = @invalidationCountsByScreenLineId.get(screenLineId) ? 0
       @invalidationCountsByScreenLineId.set(screenLineId, count + 1)
+      @screenLinesBySpatialLineId.delete(screenLineId)
     return
 
   decorationLayerDidInvalidateRange: (bufferRange) ->

    @textDecorationLayer = new EmptyDecorationLayer
    @emitter = new Emitter
    @invalidationCountsByScreenLineId = new Map
    @reset({
      invisibles: settings.invisibles ? {}
      tabLength: settings.tabLength ? 4

    screenLines = []
    @screenLineIterator.seekToScreenRow(startRow)
    containingTags = decorationIterator.seek(@screenLineIterator.getBufferStart())
    previousLineWasCached = false

    while @screenLineIterator.getScreenRow() < endRow
      screenLineId = @screenLineIterator.getId()
      if @screenLinesBySpatialLineId.has(screenLineId)
        screenLines.push(@screenLinesBySpatialLineId.get(screenLineId))
        previousLineWasCached = true
      else
        bufferStart = @screenLineIterator.getBufferStart()
        if previousLineWasCached
          containingTags = decorationIterator.seek(bufferStart)
          previousLineWasCached = false
        tokens = []
        spatialDecoration = null
        closeTags = []
        openTags = containingTags.slice()
        atLineStart = true

        if comparePoints(decorationIterator.getPosition(), bufferStart) < 0
          bufferRow = decorationIterator.getPosition().row
          iteratorPosition = decorationIterator.getPosition()
          throw new Error("""
            Invalid text decoration iterator position: (#{iteratorPosition.row}, #{iteratorPosition.column}).
            Buffer row #{bufferRow} has length #{@buffer.lineLengthForRow(bufferRow)}.
          """)

        for {screenExtent, bufferExtent, metadata} in @screenLineIterator.getTokens()
          spatialTokenBufferEnd = traverse(bufferStart, bufferExtent)
          tagsToClose = []
          tagsToOpen = []

          if metadata?.fold
            @updateTags(closeTags, openTags, containingTags, containingTags.slice().reverse(), [], atLineStart)
            tagsToReopenAfterFold = decorationIterator.seek(spatialTokenBufferEnd)
            while comparePoints(decorationIterator.getPosition(), spatialTokenBufferEnd) is 0
              for closeTag in decorationIterator.getCloseTags()
                tagsToReopenAfterFold.splice(tagsToReopenAfterFold.lastIndexOf(closeTag), 1)
              tagsToReopenAfterFold.push(decorationIterator.getOpenTags()...)
              decorationIterator.moveToSuccessor()
          else
            if spatialDecoration?
              tagsToClose.push(spatialDecoration)

            if not metadata?.softLineBreak
              if tagsToReopenAfterFold?
                tagsToOpen.push(tagsToReopenAfterFold...)
                tagsToReopenAfterFold = null

              if comparePoints(decorationIterator.getPosition(), bufferStart) is 0
                tagsToClose.push(decorationIterator.getCloseTags()...)
                tagsToOpen.push(decorationIterator.getOpenTags()...)
                decorationIterator.moveToSuccessor()

          if spatialDecoration = @getSpatialTokenTextDecoration(metadata)
            tagsToOpen.push(spatialDecoration)

          @updateTags(closeTags, openTags, containingTags, tagsToClose, tagsToOpen, atLineStart)

          text = @buildTokenText(metadata, screenExtent, bufferStart, spatialTokenBufferEnd)
          startIndex = 0
          while comparePoints(decorationIterator.getPosition(), spatialTokenBufferEnd) < 0
            endIndex = startIndex + decorationIterator.getPosition().column - bufferStart.column
            tokens.push({closeTags, openTags, text: text.substring(startIndex, endIndex)})
            bufferStart = decorationIterator.getPosition()
            startIndex = endIndex
            closeTags = []
            openTags = []
            @updateTags(closeTags, openTags, containingTags, decorationIterator.getCloseTags(), decorationIterator.getOpenTags())
            decorationIterator.moveToSuccessor()

          tokens.push({closeTags, openTags, text: text.substring(startIndex)})

          closeTags = []
          openTags = []
          bufferStart = spatialTokenBufferEnd
          atLineStart = false

        if containingTags.length > 0
          tokens.push({closeTags: containingTags.slice().reverse(), openTags: [], text: ''})

        if tagsToReopenAfterFold?
          containingTags = tagsToReopenAfterFold
          tagsToReopenAfterFold = null
        else if spatialDecoration?
          containingTags.splice(containingTags.indexOf(spatialDecoration), 1)

        while not @screenLineIterator.isSoftWrappedAtEnd() and comparePoints(decorationIterator.getPosition(), spatialTokenBufferEnd) is 0
          @updateTags(closeTags, openTags, containingTags, decorationIterator.getCloseTags(), decorationIterator.getOpenTags())
          decorationIterator.moveToSuccessor()

        invalidationCount = @invalidationCountsByScreenLineId.get(screenLineId) ? 0
        screenLine = {id: "#{screenLineId}-#{invalidationCount}", tokens}
        @screenLinesBySpatialLineId.set(screenLineId, screenLine)
        screenLines.push(screenLine)

      break unless @screenLineIterator.moveToSuccessor()
    screenLines

