Files
atom/src/app/buffer-change-operation.coffee
Nathan Sobo 298a963148 Rework Buffer's 'contents-modified' event
This event now fires whenever the content of the buffer changes (after 
a rate-limiting delay) with a single boolean indicating the modified
status of the buffer. There's now a separate event called
'modified-status-changed' to indicate events that change the boolean
value of the isModified method, so we don't need to fire
'contents-modified' when the underlying file is deleted for instance.
2013-03-07 09:30:27 -08:00

111 lines
3.3 KiB
CoffeeScript

Range = require 'range'
_ = require 'underscore'
module.exports =
class BufferChangeOperation
buffer: null
oldRange: null
oldText: null
newRange: null
newText: null
markersToRestoreOnUndo: null
markersToRestoreOnRedo: null
constructor: ({@buffer, @oldRange, @newText, @options}) ->
@options ?= {}
do: ->
@oldText = @buffer.getTextInRange(@oldRange)
@newRange = @calculateNewRange(@oldRange, @newText)
@markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
@changeBuffer
oldRange: @oldRange
newRange: @newRange
oldText: @oldText
newText: @newText
redo: ->
@restoreMarkers(@markersToRestoreOnRedo)
undo: ->
@markersToRestoreOnRedo = @invalidateMarkers(@newRange)
@changeBuffer
oldRange: @newRange
newRange: @oldRange
oldText: @newText
newText: @oldText
@restoreMarkers(@markersToRestoreOnUndo)
splitLines: (text) ->
lines = text.split('\n')
lineEndings = []
for line, index in lines
if _.endsWith(line, '\r')
lines[index] = line[...-1]
lineEndings[index] = '\r\n'
else
lineEndings[index] = '\n'
{lines, lineEndings}
changeBuffer: ({ oldRange, newRange, newText, oldText }) ->
{ prefix, suffix } = @buffer.prefixAndSuffixForRange(oldRange)
{lines, lineEndings} = @splitLines(newText)
lastLineIndex = lines.length - 1
if lines.length == 1
lines = [prefix + newText + suffix]
else
lines[0] = prefix + lines[0]
lines[lastLineIndex] += suffix
startRow = oldRange.start.row
endRow = oldRange.end.row
normalizeLineEndings = @options.normalizeLineEndings ? true
if normalizeLineEndings and suggestedLineEnding = @buffer.suggestedLineEndingForRow(startRow)
lineEndings[index] = suggestedLineEnding for index in [0..lastLineIndex]
@buffer.lines[startRow..endRow] = lines
@buffer.lineEndings[startRow..endRow] = lineEndings
@buffer.cachedMemoryContents = null
@buffer.conflict = false if @buffer.conflict and !@buffer.isModified()
@pauseMarkerObservation()
event = { oldRange, newRange, oldText, newText }
@updateMarkers(event)
@buffer.trigger 'changed', event
@buffer.scheduleModifiedEvents()
@resumeMarkerObservation()
@buffer.trigger 'markers-updated'
newRange
calculateNewRange: (oldRange, newText) ->
newRange = new Range(oldRange.start.copy(), oldRange.start.copy())
{lines} = @splitLines(newText)
if lines.length == 1
newRange.end.column += newText.length
else
lastLineIndex = lines.length - 1
newRange.end.row += lastLineIndex
newRange.end.column = lines[lastLineIndex].length
newRange
invalidateMarkers: (oldRange) ->
_.compact(@buffer.getMarkers().map (marker) -> marker.tryToInvalidate(oldRange))
pauseMarkerObservation: ->
marker.pauseEvents() for marker in @buffer.getMarkers()
resumeMarkerObservation: ->
marker.resumeEvents() for marker in @buffer.getMarkers()
updateMarkers: (bufferChange) ->
marker.handleBufferChange(bufferChange) for marker in @buffer.getMarkers()
restoreMarkers: (markersToRestore) ->
for [id, previousRange] in markersToRestore
if validMarker = @buffer.validMarkers[id]
validMarker.setRange(previousRange)
else if invalidMarker = @buffer.invalidMarkers[id]
invalidMarker.revalidate()