From df4ea9293dbbc28a29ce1cccd33c6199d4f9f3a2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 2 Feb 2013 21:11:42 -0700 Subject: [PATCH] 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. --- src/app/display-buffer-marker.coffee | 92 ++++++++++++++++++++++++++++ src/app/display-buffer.coffee | 92 ++++++++++------------------ 2 files changed, 124 insertions(+), 60 deletions(-) create mode 100644 src/app/display-buffer-marker.coffee diff --git a/src/app/display-buffer-marker.coffee b/src/app/display-buffer-marker.coffee new file mode 100644 index 000000000..95bcf1307 --- /dev/null +++ b/src/app/display-buffer-marker.coffee @@ -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] diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee index ab86c06ee..cb9a1de5a 100644 --- a/src/app/display-buffer.coffee +++ b/src/app/display-buffer.coffee @@ -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()