diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee index 4a42d0b52..b23007ec2 100644 --- a/spec/app/buffer-spec.coffee +++ b/spec/app/buffer-spec.coffee @@ -739,6 +739,37 @@ describe 'Buffer', -> buffer.setMarkerHeadPosition(marker, [6, 2]) expect(observeHandler).not.toHaveBeenCalled() + describe ".observeMarkerRange(marker, callback)", -> + [observeHandler, marker] = [] + + beforeEach -> + observeHandler = jasmine.createSpy("observeHandler") + marker = buffer.markRange([[4, 20], [4, 23]]) + buffer.observeMarkerRange(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 { oldRange: [[4, 20], [4, 23]], newRange: [[4, 20], [6, 2]], bufferChanged: false } + observeHandler.reset() + + buffer.insert([6, 0], '...') + expect(observeHandler.argsForCall[0][0]).toEqual { oldRange: [[4, 20], [6, 2]], newRange: [[4, 20], [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 { 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 } + describe "marker destruction", -> marker = null diff --git a/src/app/buffer-marker.coffee b/src/app/buffer-marker.coffee index 341a9ae2a..c895f6c15 100644 --- a/src/app/buffer-marker.coffee +++ b/src/app/buffer-marker.coffee @@ -7,10 +7,13 @@ class BufferMarker headPosition: null tailPosition: null headPositionObservers: null + rangeObservers: null + disableRangeChanged: false stayValid: false constructor: ({@id, @buffer, range, @stayValid, noTail, reverse}) -> @headPositionObservers = [] + @rangeObservers = [] @setRange(range, {noTail, reverse}) setRange: (range, options={}) -> @@ -37,16 +40,38 @@ class BufferMarker setHeadPosition: (headPosition, options={}) -> oldPosition = @headPosition + oldRange = @getRange() @headPosition = Point.fromObject(headPosition) @headPosition = @buffer.clipPosition(@headPosition) if options.clip ? true newPosition = @headPosition + newRange = @getRange() bufferChanged = !!options.bufferChanged - observer({oldPosition, newPosition, bufferChanged}) for observer in @headPositionObservers + unless newPosition.isEqual(oldPosition) + @headPositionChanged({oldPosition, newPosition, bufferChanged}) + @rangeChanged({oldRange, newRange, 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() + bufferChanged = !!options.bufferChanged + @rangeChanged({oldRange, newRange, bufferChanged}) unless newRange.isEqual(oldRange) + @tailPosition getStartPosition: -> @getRange().start @@ -62,8 +87,17 @@ class BufferMarker observeHeadPosition: (callback) -> @headPositionObservers.push(callback) - cancel: => - _.remove(@headPositionObservers, callback) + cancel: => @unobserveHeadPosition(callback) + + unobserveHeadPosition: (callback) -> + _.remove(@headPositionObservers, callback) + + observeRange: (callback) -> + @rangeObservers.push(callback) + cancel: => @unobserveRange(callback) + + unobserveRange: (callback) -> + _.remove(@rangeObservers, callback) tryToInvalidate: (oldRange) -> containsStart = oldRange.containsPoint(@getStartPosition(), exclusive: true) @@ -84,8 +118,13 @@ 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) updatePosition: (position, bufferChange, isFirstPoint) -> { oldRange, newRange } = bufferChange diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index c05ac567a..45b81f123 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -323,6 +323,9 @@ class Buffer observeMarkerHeadPosition: (id, callback) -> @validMarkers[id]?.observeHeadPosition(callback) + observeMarkerRange: (id, callback) -> + @validMarkers[id]?.observeRange(callback) + getAnchors: -> new Array(@anchors...) addAnchor: (options) ->