Build DisplayBufferMarker object to help track marker screen positions

There are 3 failures, which I think are attributable to the fact that
I'm not emitting events from the when the selection tail changes, and
I'm not emitting redundant events when the head is changed to the
same position it was previously, whereas before I wasn't guarding
against redundant events.
This commit is contained in:
Nathan Sobo
2013-02-02 21:11:42 -07:00
parent 60304b76b1
commit df4ea9293d
2 changed files with 124 additions and 60 deletions

View File

@@ -0,0 +1,92 @@
_ = require 'underscore'
module.exports =
class DisplayBufferMarker
constructor: ({@id, @displayBuffer}) ->
@buffer = @displayBuffer.buffer
getScreenRange: ->
@displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true)
setScreenRange: (screenRange, options) ->
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange, options), options)
getBufferRange: ->
@buffer.getMarkerRange(@id)
setBufferRange: (bufferRange, options) ->
@buffer.setMarkerRange(@id, bufferRange, options)
getHeadScreenPosition: ->
@headScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
setHeadScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
getHeadBufferPosition: ->
@buffer.getMarkerHeadPosition(@id)
setHeadBufferPosition: (bufferPosition) ->
@buffer.setMarkerHeadPosition(@id, bufferPosition)
getTailScreenPosition: ->
@getMarker(@id).getTailScreenPosition()
setTailScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
getTailBufferPosition: ->
@buffer.getMarkerTailPosition(@id)
setTailBufferPosition: (bufferPosition) ->
@buffer.setMarkerTailPosition(@id, bufferPosition)
placeTail: ->
@buffer.placeMarkerTail(@id)
clearTail: ->
@buffer.clearMarkerTail(@id)
observeHeadPosition: (callback) ->
unless @headObservers
@observeBufferMarkerHeadPosition()
@displayBuffer.markers[@id] = this
@headObservers = []
@headObservers.push(callback)
cancel: => @unobserveHeadPosition(callback)
unobserveHeadPosition: (callback) ->
_.remove(@headObservers, callback)
@unsubscribe() unless @headObservers.length
observeBufferMarkerHeadPosition: ->
@getHeadScreenPosition()
@bufferMarkerHeadSubscription =
@buffer.observeMarkerHeadPosition @id, (e) =>
bufferChanged = e.bufferChanged
oldBufferPosition = e.oldPosition
newBufferPosition = e.newPosition
@refreshHeadScreenPosition({bufferChanged, oldBufferPosition, newBufferPosition})
refreshHeadScreenPosition: ({bufferChanged, oldBufferPosition, newBufferPosition}={}) ->
unless bufferChanged
oldBufferPosition ?= @getHeadBufferPosition()
newBufferPosition ?= oldBufferPosition
oldScreenPosition = @getHeadScreenPosition()
@headScreenPosition = null
newScreenPosition = @getHeadScreenPosition()
unless newScreenPosition.isEqual(oldScreenPosition)
@notifyHeadObservers({ oldBufferPosition, newBufferPosition, oldScreenPosition, newScreenPosition, bufferChanged })
notifyHeadObservers: (event) ->
observer(event) for observer in @getHeadObservers()
getHeadObservers: ->
new Array(@headObservers...)
unsubscribe: ->
@bufferMarkerHeadSubscription.cancel()
delete @displayBuffer.markers[@id]

View File

@@ -7,6 +7,7 @@ Range = require 'range'
Fold = require 'fold'
ScreenLine = require 'screen-line'
Token = require 'token'
DisplayBufferMarker = require 'display-buffer-marker'
module.exports =
class DisplayBuffer
@@ -16,8 +17,7 @@ class DisplayBuffer
tokenizedBuffer: null
activeFolds: null
foldsById: null
markerScreenPositionObservers: null
markerScreenPositions: null
markers: null
constructor: (@buffer, options={}) ->
@id = @constructor.idCounter++
@@ -26,9 +26,7 @@ class DisplayBuffer
@softWrapColumn = options.softWrapColumn ? Infinity
@activeFolds = {}
@foldsById = {}
@markerScreenPositionObservers = {}
@markerScreenPositions = {}
@markers = {}
@buildLineMap()
@tokenizedBuffer.on 'changed', (e) => @handleTokenizedBufferChange(e)
@@ -38,8 +36,8 @@ class DisplayBuffer
@lineMap = new LineMap
@lineMap.insertAtScreenRow 0, @buildLinesForBufferRows(0, @buffer.getLastRow())
triggerChanged: (eventProperties) ->
@notifyMarkerScreenPositionObservers() unless eventProperties.bufferChange
triggerChanged: (eventProperties, refreshMarkers=true) ->
@refreshMarkerScreenPositions() if refreshMarkers
@trigger 'changed', eventProperties
setSoftWrapColumn: (@softWrapColumn) ->
@@ -133,7 +131,7 @@ class DisplayBuffer
screenDelta = newScreenRange.end.row - oldScreenRange.end.row
bufferDelta = 0
@notifyMarkerScreenPositionObservers()
@refreshMarkerScreenPositions()
@triggerChanged({ start, end, screenDelta, bufferDelta })
destroyFoldsContainingBufferRow: (bufferRow) ->
@@ -233,7 +231,7 @@ class DisplayBuffer
@lineMap.replaceScreenRows(start, end, newScreenLines)
screenDelta = @lastScreenRowForBufferRow(tokenizedBufferEnd + tokenizedBufferDelta) - end
@triggerChanged({ start, end, screenDelta, bufferDelta })
@triggerChanged({ start, end, screenDelta, bufferDelta }, false)
buildLineForBufferRow: (bufferRow) ->
@buildLinesForBufferRows(bufferRow, bufferRow)
@@ -298,6 +296,12 @@ class DisplayBuffer
rangeForAllLines: ->
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
getMarker: (id) ->
@markers[id] ? new DisplayBufferMarker({id, displayBuffer: this})
getMarkers: ->
_.values(@markers)
markScreenRange: (screenRange) ->
@markBufferRange(@bufferRangeForScreenRange(screenRange))
@@ -312,20 +316,19 @@ class DisplayBuffer
destroyMarker: (id) ->
@buffer.destroyMarker(id)
delete @markerScreenPositionObservers[id]
delete @markerScreenPositions[id]
delete @markers[id]
getMarkerScreenRange: (id) ->
@screenRangeForBufferRange(@getMarkerBufferRange(id), wrapAtSoftNewlines: true)
@getMarker(id).getScreenRange()
setMarkerScreenRange: (id, screenRange, options) ->
@setMarkerBufferRange(id, @bufferRangeForScreenRange(screenRange), options)
@getMarker(id).setScreenRange(screenRange, options)
getMarkerBufferRange: (id) ->
@buffer.getMarkerRange(id)
@getMarker(id).getBufferRange()
setMarkerBufferRange: (id, bufferRange, options) ->
@buffer.setMarkerRange(id, bufferRange, options)
@getMarker(id).setBufferRange(bufferRange, options)
getMarkerScreenPosition: (id) ->
@getMarkerHeadScreenPosition(id)
@@ -334,75 +337,44 @@ class DisplayBuffer
@getMarkerHeadBufferPosition(id)
getMarkerHeadScreenPosition: (id) ->
@screenPositionForBufferPosition(@getMarkerHeadBufferPosition(id), wrapAtSoftNewlines: true)
@getMarker(id).getHeadScreenPosition()
setMarkerHeadScreenPosition: (id, screenPosition, options) ->
screenPosition = @clipScreenPosition(screenPosition, options)
@setMarkerHeadBufferPosition(id, @bufferPositionForScreenPosition(screenPosition, options))
@getMarker(id).setHeadScreenPosition(screenPosition, options)
getMarkerHeadBufferPosition: (id) ->
@buffer.getMarkerHeadPosition(id)
@getMarker(id).getHeadBufferPosition()
setMarkerHeadBufferPosition: (id, bufferPosition) ->
@buffer.setMarkerHeadPosition(id, bufferPosition)
@getMarker(id).setHeadBufferPosition(bufferPosition)
getMarkerTailScreenPosition: (id) ->
@screenPositionForBufferPosition(@getMarkerTailBufferPosition(id), wrapAtSoftNewlines: true)
@getMarker(id).getTailScreenPosition()
setMarkerTailScreenPosition: (id, screenPosition, options) ->
screenPosition = @clipScreenPosition(screenPosition, options)
@setMarkerTailBufferPosition(id, @bufferPositionForScreenPosition(screenPosition, options))
@getMarker(id).setTailScreenPosition(screenPosition, options)
getMarkerTailBufferPosition: (id) ->
@buffer.getMarkerTailPosition(id)
@getMarker(id).getTailBufferPosition()
setMarkerTailBufferPosition: (id, bufferPosition) ->
@buffer.setMarkerTailPosition(id, bufferPosition)
@getMarker(id).setTailBufferPosition(bufferPosition)
placeMarkerTail: (id) ->
@buffer.placeMarkerTail(id)
@getMarker(id).placeTail()
clearMarkerTail: (id) ->
@buffer.clearMarkerTail(id)
@getMarker(id).clearTail()
isMarkerReversed: (id) ->
@buffer.isMarkerReversed(id)
observeMarkerHeadScreenPosition: (id, callback) ->
@markerScreenPositionObservers[id] ?= { head: [], tail: [] }
@cacheMarkerScreenPositions(id) unless @markerScreenPositions[id]
@markerScreenPositionObservers[id].head.push(callback)
subscription = @buffer.observeMarkerHeadPosition id, (e) =>
bufferChanged = e.bufferChanged
oldBufferPosition = e.oldPosition
newBufferPosition = e.newPosition
oldScreenPosition = @markerScreenPositions[id].head
@cacheMarkerScreenPositions(id)
newScreenPosition = @getMarkerHeadScreenPosition(id)
callback({ oldBufferPosition, newBufferPosition, oldScreenPosition, newScreenPosition, bufferChanged })
@getMarker(id).observeHeadPosition(callback)
cancel: =>
subscription.cancel()
{ head, tail } = @markerScreenPositionObservers[id]
_.remove(head, callback)
unless head.length + tail.length
delete @markerScreenPositionObservers[id]
delete @markerScreenPositions[id]
cacheMarkerScreenPositions: (id) ->
@markerScreenPositions[id] = { head: @getMarkerHeadScreenPosition(id), tail: @getMarkerTailScreenPosition }
notifyMarkerScreenPositionObservers: ->
for id, { head } of @markerScreenPositions
currentHeadPosition = @getMarkerHeadScreenPosition(id)
unless currentHeadPosition.isEqual(head)
bufferChanged = false
oldBufferPosition = newBufferPosition = @buffer.getMarkerHeadPosition(id)
oldScreenPosition = @markerScreenPositions[id].head
@cacheMarkerScreenPositions(id)
newScreenPosition = @getMarkerHeadScreenPosition(id)
for observer in @markerScreenPositionObservers[id].head
observer({oldScreenPosition, newScreenPosition, oldBufferPosition, newBufferPosition, bufferChanged})
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
marker.refreshHeadScreenPosition(bufferChanged: false)
destroy: ->
@tokenizedBuffer.destroy()