mirror of
https://github.com/atom/atom.git
synced 2026-02-14 16:45:14 -05:00
This prevents the editor from synchronously redrawing in specs with cursors in invalid locations. Markers are always updated under the hood before a change event is emitted from the Buffer or DisplayBuffer. We still wait to trigger marker observers, but if their position gets read, it is up to date.
112 lines
3.3 KiB
CoffeeScript
112 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.scheduleStoppedChangingEvent()
|
|
@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]
|
|
@buffer.validMarkers[id] = invalidMarker
|
|
|