Files
atom/src/app/buffer-marker.coffee
Nathan Sobo 3625a55f68 Update markers before triggering 'changed' events
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.
2013-02-12 17:09:05 -07:00

149 lines
5.0 KiB
CoffeeScript

_ = require 'underscore'
Point = require 'point'
Range = require 'range'
EventEmitter = require 'event-emitter'
module.exports =
class BufferMarker
headPosition: null
tailPosition: null
suppressObserverNotification: false
stayValid: false
constructor: ({@id, @buffer, range, @stayValid, noTail, reverse}) ->
@setRange(range, {noTail, reverse})
setRange: (range, options={}) ->
@consolidateObserverNotifications false, =>
range = Range.fromObject(range)
if options.reverse
@setTailPosition(range.end) unless options.noTail
@setHeadPosition(range.start)
else
@setTailPosition(range.start) unless options.noTail
@setHeadPosition(range.end)
isReversed: ->
@tailPosition? and @headPosition.isLessThan(@tailPosition)
getRange: ->
if @tailPosition
new Range(@tailPosition, @headPosition)
else
new Range(@headPosition, @headPosition)
getHeadPosition: -> @headPosition
getTailPosition: -> @tailPosition ? @getHeadPosition()
setHeadPosition: (newHeadPosition, options={}) ->
oldHeadPosition = @getHeadPosition()
newHeadPosition = Point.fromObject(newHeadPosition)
newHeadPosition = @buffer.clipPosition(newHeadPosition) if options.clip ? true
return if newHeadPosition.isEqual(@headPosition)
@headPosition = newHeadPosition
bufferChanged = !!options.bufferChanged
@notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged})
@headPosition
setTailPosition: (newTailPosition, options={}) ->
oldTailPosition = @getTailPosition()
newTailPosition = Point.fromObject(newTailPosition)
newTailPosition = @buffer.clipPosition(newTailPosition) if options.clip ? true
return if newTailPosition.isEqual(@tailPosition)
@tailPosition = newTailPosition
bufferChanged = !!options.bufferChanged
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged})
@tailPosition
getStartPosition: ->
@getRange().start
getEndPosition: ->
@getRange().end
placeTail: ->
@setTailPosition(@getHeadPosition()) unless @tailPosition
clearTail: ->
oldTailPosition = @getTailPosition()
@tailPosition = null
newTailPosition = @getTailPosition()
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
tryToInvalidate: (oldRange) ->
containsStart = oldRange.containsPoint(@getStartPosition(), exclusive: true)
containsEnd = oldRange.containsPoint(@getEndPosition(), exclusive: true)
return unless containsEnd or containsStart
if @stayValid
previousRange = @getRange()
if containsStart and containsEnd
@setRange([oldRange.end, oldRange.end])
else if containsStart
@setRange([oldRange.end, @getEndPosition()])
else
@setRange([@getStartPosition(), oldRange.start])
[@id, previousRange]
else
@invalidate()
[@id]
handleBufferChange: (bufferChange) ->
@consolidateObserverNotifications true, =>
@setHeadPosition(@updatePosition(@headPosition, bufferChange, false), clip: false, bufferChanged: true)
@setTailPosition(@updatePosition(@tailPosition, bufferChange, true), clip: false, bufferChanged: true) if @tailPosition
updatePosition: (position, bufferChange, isFirstPoint) ->
{ oldRange, newRange } = bufferChange
return position if oldRange.containsPoint(position, exclusive: true)
return position if isFirstPoint and oldRange.start.isEqual(position)
return position if position.isLessThan(oldRange.end)
newRow = newRange.end.row
newColumn = newRange.end.column
if position.row == oldRange.end.row
newColumn += position.column - oldRange.end.column
else
newColumn = position.column
newRow += position.row - oldRange.end.row
[newRow, newColumn]
observe: (callback) ->
@on 'position-changed', callback
cancel: => @unobserve(callback)
unobserve: (callback) ->
@off 'position-changed', callback
containsPoint: (point) ->
@getRange().containsPoint(point)
notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}) ->
return if @suppressObserverNotification
return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition)
oldHeadPosition ?= @getHeadPosition()
newHeadPosition ?= @getHeadPosition()
oldTailPosition ?= @getTailPosition()
newTailPosition ?= @getTailPosition()
@trigger 'position-changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}
consolidateObserverNotifications: (bufferChanged, fn) ->
@suppressObserverNotification = true
oldHeadPosition = @getHeadPosition()
oldTailPosition = @getTailPosition()
fn()
newHeadPosition = @getHeadPosition()
newTailPosition = @getTailPosition()
@suppressObserverNotification = false
@notifyObservers({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged})
invalidate: (preserve) ->
delete @buffer.validMarkers[@id]
@buffer.invalidMarkers[@id] = this
_.extend BufferMarker.prototype, EventEmitter