Give Buffer only a single observeMarker method

It will fire the callback whenever the marker's head or tail position
changes, and it's up to the subscriber to use the information how they
want to.
This commit is contained in:
Nathan Sobo
2013-02-04 20:35:30 -07:00
parent 852b4c2f8a
commit 58450d6a65
3 changed files with 141 additions and 113 deletions

View File

@@ -713,62 +713,94 @@ describe 'Buffer', ->
expect(buffer.getMarkerRange(marker)).toEqual [[2, 0], [4, 23]]
expect(buffer.isMarkerReversed(marker)).toBeTruthy()
describe "marker observation", ->
describe ".observeMarkerHeadPosition(marker, callback)", ->
observeHandler = null
fdescribe ".observeMarker(marker, callback)", ->
[observeHandler, marker, subscription] = []
beforeEach ->
observeHandler = jasmine.createSpy("observeHandler")
beforeEach ->
observeHandler = jasmine.createSpy("observeHandler")
marker = buffer.markRange([[4, 20], [4, 23]])
subscription = buffer.observeMarker(marker, observeHandler)
it "calls the given callback whenever the marker's head position changes with the position and whether or not the move was caused by a buffer change", ->
marker = buffer.markRange([[4, 20], [4, 23]])
buffer.observeMarkerHeadPosition(marker, observeHandler)
it "calls the callback when the marker's head position changes", ->
buffer.setMarkerHeadPosition(marker, [6, 2])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [6, 2]
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
bufferChanged: false
}
observeHandler.reset()
buffer.setMarkerHeadPosition(marker, [6, 2])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual { oldPosition: [4, 23], newPosition: [6, 2], bufferChanged: false }
observeHandler.reset()
buffer.insert([6, 0], '...')
expect(observeHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
oldHeadPosition: [6, 2]
newHeadPosition: [6, 5]
bufferChanged: true
}
buffer.insert([6, 0], '...')
expect(observeHandler.argsForCall[0][0]).toEqual { oldPosition: [6, 2], newPosition: [6, 5], bufferChanged: true }
it "calls the given callback when the marker's tail position changes", ->
buffer.setMarkerTailPosition(marker, [6, 2])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [4, 20]
newTailPosition: [6, 2]
bufferChanged: false
}
observeHandler.reset()
it "allows the observation subscription to be cancelled", ->
marker = buffer.markRange([[4, 20], [4, 23]])
subscription = buffer.observeMarkerHeadPosition(marker, observeHandler)
subscription.cancel()
buffer.setMarkerHeadPosition(marker, [6, 2])
expect(observeHandler).not.toHaveBeenCalled()
buffer.insert([6, 0], '...')
describe ".observeMarkerRange(marker, callback)", ->
[observeHandler, marker] = []
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [6, 2]
newTailPosition: [6, 5]
bufferChanged: true
}
beforeEach ->
observeHandler = jasmine.createSpy("observeHandler")
marker = buffer.markRange([[4, 20], [4, 23]])
buffer.observeMarkerRange(marker, observeHandler)
it "calls the callback when the selection's tail is cleared", ->
buffer.clearMarkerTail(marker)
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [4, 20]
newTailPosition: null
bufferChanged: false
}
it "calls the callback when the marker's head position changes", ->
buffer.setMarkerHeadPosition(marker, [6, 2])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual { oldRange: [[4, 20], [4, 23]], newRange: [[4, 20], [6, 2]], bufferChanged: false }
observeHandler.reset()
it "only calls the callback once when both the marker's head and tail positions change due to the same operation", ->
buffer.insert([4, 0], '...')
expect(observeHandler.callCount).toBe 1
expect(observeHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 23]
oldHeadPosition: [4, 23]
newHeadPosition: [4, 26]
bufferChanged: true
}
observeHandler.reset()
buffer.insert([6, 0], '...')
expect(observeHandler.argsForCall[0][0]).toEqual { oldRange: [[4, 20], [6, 2]], newRange: [[4, 20], [6, 5]], bufferChanged: true }
buffer.setMarkerRange(marker, [[0, 0], [1, 1]])
expect(observeHandler.callCount).toBe 1
expect(observeHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 23]
newTailPosition: [0, 0]
oldHeadPosition: [4, 26]
newHeadPosition: [1, 1]
bufferChanged: false
}
it "calls the given callback when the marker's tail position changes", ->
buffer.setMarkerTailPosition(marker, [6, 2])
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual { oldRange: [[4, 20], [4, 23]], newRange: [[4, 23], [6, 2]], bufferChanged: false }
observeHandler.reset()
buffer.insert([6, 0], '...')
expect(observeHandler.argsForCall[0][0]).toEqual { oldRange: [[4, 23], [6, 2]], newRange: [[4, 23], [6, 5]], bufferChanged: true }
it "only calls the callback once when both the marker's head and tail positions change due to the same operation", ->
buffer.insert([4, 0], '...')
expect(observeHandler.callCount).toBe 1
expect(observeHandler.argsForCall[0][0]).toEqual { oldRange: [[4, 20], [4, 23]], newRange: [[4, 23], [4, 26]], bufferChanged: true }
it "allows the observation subscription to be cancelled", ->
subscription.cancel()
buffer.setMarkerHeadPosition(marker, [6, 2])
expect(observeHandler).not.toHaveBeenCalled()
describe "marker destruction", ->
marker = null

View File

@@ -6,24 +6,24 @@ module.exports =
class BufferMarker
headPosition: null
tailPosition: null
headPositionObservers: null
rangeObservers: null
disableRangeChanged: false
observers: null
suppressObserverNotification: false
stayValid: false
constructor: ({@id, @buffer, range, @stayValid, noTail, reverse}) ->
@headPositionObservers = []
@rangeObservers = []
@observers = []
@setRange(range, {noTail, reverse})
setRange: (range, options={}) ->
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)
@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)
@@ -38,39 +38,24 @@ class BufferMarker
getTailPosition: -> @tailPosition
setHeadPosition: (headPosition, options={}) ->
oldPosition = @headPosition
oldRange = @getRange()
@headPosition = Point.fromObject(headPosition)
@headPosition = @buffer.clipPosition(@headPosition) if options.clip ? true
newPosition = @headPosition
newRange = @getRange()
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
unless newPosition.isEqual(oldPosition)
@headPositionChanged({oldPosition, newPosition, bufferChanged})
@rangeChanged({oldRange, newRange, bufferChanged})
@notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged})
@headPosition
headPositionChanged: ({oldPosition, newPosition, bufferChanged}) ->
observer({oldPosition, newPosition, bufferChanged}) for observer in @getHeadPositionObservers()
getHeadPositionObservers: ->
new Array(@headPositionObservers...)
rangeChanged: ({oldRange, newRange, bufferChanged}) ->
unless @disableRangeChanged
observer({oldRange, newRange, bufferChanged}) for observer in @getRangeObservers()
getRangeObservers: ->
new Array(@rangeObservers...)
setTailPosition: (tailPosition, options={}) ->
oldRange = @getRange()
@tailPosition = Point.fromObject(tailPosition)
@tailPosition = @buffer.clipPosition(@tailPosition) if options.clip ? true
newRange = @getRange()
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
@rangeChanged({oldRange, newRange, bufferChanged}) unless newRange.isEqual(oldRange)
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged})
@tailPosition
getStartPosition: ->
@@ -83,21 +68,9 @@ class BufferMarker
@setTailPosition(@headPosition) unless @tailPosition
clearTail: ->
@tailPosition = null
observeHeadPosition: (callback) ->
@headPositionObservers.push(callback)
cancel: => @unobserveHeadPosition(callback)
unobserveHeadPosition: (callback) ->
_.remove(@headPositionObservers, callback)
observeRange: (callback) ->
@rangeObservers.push(callback)
cancel: => @unobserveRange(callback)
unobserveRange: (callback) ->
_.remove(@rangeObservers, callback)
oldTailPosition = @getTailPosition()
@tailPosition = newTailPosition = null
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
tryToInvalidate: (oldRange) ->
containsStart = oldRange.containsPoint(@getStartPosition(), exclusive: true)
@@ -118,13 +91,9 @@ class BufferMarker
[@id]
handleBufferChange: (bufferChange) ->
@disableRangeChanged = true
oldRange = @getRange()
@setHeadPosition(@updatePosition(@headPosition, bufferChange, false), clip: false, bufferChanged: true)
@setTailPosition(@updatePosition(@tailPosition, bufferChange, true), clip: false, bufferChanged: true) if @tailPosition
newRange = @getRange()
@disableRangeChanged = false
@rangeChanged({oldRange, newRange, bufferChanged: true}) unless newRange.isEqual(oldRange)
@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
@@ -144,6 +113,36 @@ class BufferMarker
[newRow, newColumn]
observe: (callback) ->
@observers.push(callback)
cancel: => @unobserve(callback)
unobserve: (callback) ->
_.remove(@observers, callback)
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()
for observer in @getObservers()
observer({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged})
getObservers: ->
new Array(@observers...)
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

View File

@@ -320,11 +320,8 @@ class Buffer
isMarkerReversed: (id) ->
@validMarkers[id]?.isReversed()
observeMarkerHeadPosition: (id, callback) ->
@validMarkers[id]?.observeHeadPosition(callback)
observeMarkerRange: (id, callback) ->
@validMarkers[id]?.observeRange(callback)
observeMarker: (id, callback) ->
@validMarkers[id]?.observe(callback)
getAnchors: -> new Array(@anchors...)