From bc44540b1057fc561c757ea94931c49d219d94ac Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 31 Jan 2013 14:00:01 -0700 Subject: [PATCH] Start adding ability to observe marker head positions --- spec/app/buffer-spec.coffee | 11 +++++++ spec/app/display-buffer-spec.coffee | 45 +++++++++++++++++++++++++++-- src/app/buffer-marker.coffee | 19 ++++++++---- src/app/buffer.coffee | 3 ++ src/app/display-buffer.coffee | 36 ++++++++++++++++++++--- src/app/edit-session.coffee | 17 ++++++++--- 6 files changed, 114 insertions(+), 17 deletions(-) diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee index af485cb94..47eec56ff 100644 --- a/spec/app/buffer-spec.coffee +++ b/spec/app/buffer-spec.coffee @@ -705,6 +705,17 @@ describe 'Buffer', -> buffer.setMarkerTailPosition(marker, [Infinity, Infinity]) expect(buffer.getMarkerRange(marker)).toEqual([[0, 0], [12, 2]]) + describe "marker observation", -> + describe ".observeMarkerHeadPosition(marker, callback)", -> + it "calls the given callback whenever the marker's head position changes", -> + marker = buffer.markRange([[4, 20], [4, 23]]) + observeHandler = jasmine.createSpy("observeHandler") + buffer.observeMarkerHeadPosition(marker, observeHandler) + + buffer.setMarkerHeadPosition(marker, [6, 2]) + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual [6, 2] + describe "marker updates due to buffer changes", -> [marker1, marker2] = [] diff --git a/spec/app/display-buffer-spec.coffee b/spec/app/display-buffer-spec.coffee index 3146f6af0..c9f24b72c 100644 --- a/spec/app/display-buffer-spec.coffee +++ b/spec/app/display-buffer-spec.coffee @@ -581,10 +581,10 @@ describe "DisplayBuffer", -> expect(displayBuffer.maxLineLength()).toBe 65 fdescribe "markers", -> - describe "creation and manipulation", -> - beforeEach -> - displayBuffer.foldBufferRow(4) + beforeEach -> + displayBuffer.foldBufferRow(4) + describe "creation and manipulation", -> it "allows markers to be created in terms of both screen and buffer coordinates", -> marker1 = displayBuffer.markScreenRange([[5, 4], [5, 10]]) marker2 = displayBuffer.markBufferRange([[8, 4], [8, 10]]) @@ -603,10 +603,49 @@ describe "DisplayBuffer", -> expect(displayBuffer.isMarkerReversed(marker)).toBeTruthy() expect(displayBuffer.getMarkerBufferRange(marker)).toEqual [[5, 4], [8, 4]] + describe "observation", -> + describe ".observeMarkerHeadScreenPosition(marker, callback)", -> + it "calls the callback whenever the markers head's screen position changes", -> + marker = displayBuffer.markScreenRange([[5, 4], [5, 10]]) + observeHandler = jasmine.createSpy("observeHandler") + displayBuffer.observeMarkerHeadScreenPosition(marker, observeHandler) + displayBuffer.setMarkerHeadScreenPosition(marker, [8, 20]) + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual [8, 20] + observeHandler.reset() + buffer.insert([11, 0], '...') + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual [8, 23] + observeHandler.reset() + displayBuffer.unfoldBufferRow(4) + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual [11, 23] + observeHandler.reset() + displayBuffer.foldBufferRow(4) + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual [8, 23] + it "does not call the callback for screen changes that don't change the position of the marker", -> + marker = displayBuffer.markScreenPosition([3, 4]) + observeHandler = jasmine.createSpy("observeHandler") + displayBuffer.observeMarkerHeadScreenPosition(marker, observeHandler) + buffer.insert([3, 0], '...') + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual [3, 7] + observeHandler.reset() + displayBuffer.unfoldBufferRow(4) + expect(observeHandler).not.toHaveBeenCalled() + fold = displayBuffer.createFold(0, 2) + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual [1, 7] + observeHandler.reset() + + fold.destroy() + expect(observeHandler).toHaveBeenCalled() + expect(observeHandler.argsForCall[0][0]).toEqual [3, 7] diff --git a/src/app/buffer-marker.coffee b/src/app/buffer-marker.coffee index c97448887..84b91bc5a 100644 --- a/src/app/buffer-marker.coffee +++ b/src/app/buffer-marker.coffee @@ -6,19 +6,21 @@ module.exports = class BufferMarker headPosition: null tailPosition: null + headPositionObservers: null stayValid: false constructor: ({@id, @buffer, range, @stayValid, noTail, reverse}) -> + @headPositionObservers = [] @setRange(range, {noTail, reverse}) setRange: (range, options={}) -> - range = @buffer.clipRange(range) + range = Range.fromObject(range) if options.reverse - @tailPosition = range.end unless options.noTail - @headPosition = range.start + @setTailPosition(range.end) unless options.noTail + @setHeadPosition(range.start) else - @tailPosition = range.start unless options.noTail - @headPosition = range.end + @setTailPosition(range.start) unless options.noTail + @setHeadPosition(range.end) isReversed: -> @tailPosition? and @headPosition.isLessThan(@tailPosition) @@ -36,6 +38,8 @@ class BufferMarker setHeadPosition: (headPosition, options={}) -> @headPosition = Point.fromObject(headPosition) @headPosition = @buffer.clipPosition(@headPosition) if options.clip ? true + observer(@headPosition) for observer in @headPositionObservers + @headPosition setTailPosition: (tailPosition, options={}) -> @tailPosition = Point.fromObject(tailPosition) @@ -47,6 +51,9 @@ class BufferMarker getEndPosition: -> @getRange().end + observeHeadPosition: (callback) -> + @headPositionObservers.push(callback) + tryToInvalidate: (oldRange) -> containsStart = oldRange.containsPoint(@getStartPosition(), exclusive: true) containsEnd = oldRange.containsPoint(@getEndPosition(), exclusive: true) @@ -66,8 +73,8 @@ class BufferMarker [@id] handleBufferChange: (bufferChange) -> - @setTailPosition(@updatePosition(@tailPosition, bufferChange, true), clip: false) @setHeadPosition(@updatePosition(@headPosition, bufferChange, false), clip: false) + @setTailPosition(@updatePosition(@tailPosition, bufferChange, true), clip: false) if @tailPosition updatePosition: (position, bufferChange, isFirstPoint) -> { oldRange, newRange } = bufferChange diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index d588c9ce9..a12f08908 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -307,6 +307,9 @@ class Buffer isMarkerReversed: (id) -> @validMarkers[id]?.isReversed() + observeMarkerHeadPosition: (id, callback) -> + @validMarkers[id]?.observeHeadPosition(callback) + getAnchors: -> new Array(@anchors...) addAnchor: (options) -> diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee index 3d9e1f6a6..125c8647c 100644 --- a/src/app/display-buffer.coffee +++ b/src/app/display-buffer.coffee @@ -16,6 +16,8 @@ class DisplayBuffer tokenizedBuffer: null activeFolds: null foldsById: null + markerScreenPositionObservers: null + markerScreenPositions: null constructor: (@buffer, options={}) -> @id = @constructor.idCounter++ @@ -24,6 +26,9 @@ class DisplayBuffer @softWrapColumn = options.softWrapColumn ? Infinity @activeFolds = {} @foldsById = {} + @markerScreenPositionObservers = {} + @markerScreenPositions = {} + @buildLineMap() @tokenizedBuffer.on 'changed', (e) => @handleTokenizedBufferChange(e) @@ -33,13 +38,17 @@ class DisplayBuffer @lineMap = new LineMap @lineMap.insertAtScreenRow 0, @buildLinesForBufferRows(0, @buffer.getLastRow()) + triggerChanged: (eventProperties) -> + @notifyMarkerScreenPositionObservers() unless eventProperties.bufferChange + @trigger 'changed', eventProperties + setSoftWrapColumn: (@softWrapColumn) -> start = 0 end = @getLastRow() @buildLineMap() screenDelta = @getLastRow() - end bufferDelta = 0 - @trigger 'changed', { start, end, screenDelta, bufferDelta } + @triggerChanged({ start, end, screenDelta, bufferDelta }) lineForRow: (row) -> @lineMap.lineForScreenRow(row) @@ -95,7 +104,7 @@ class DisplayBuffer end = oldScreenRange.end.row screenDelta = newScreenRange.end.row - oldScreenRange.end.row bufferDelta = 0 - @trigger 'changed', { start, end, screenDelta, bufferDelta } + @triggerChanged({ start, end, screenDelta, bufferDelta }) fold @@ -124,7 +133,8 @@ class DisplayBuffer screenDelta = newScreenRange.end.row - oldScreenRange.end.row bufferDelta = 0 - @trigger 'changed', { start, end, screenDelta, bufferDelta } + @notifyMarkerScreenPositionObservers() + @triggerChanged({ start, end, screenDelta, bufferDelta }) destroyFoldsContainingBufferRow: (bufferRow) -> for row, folds of @activeFolds @@ -225,7 +235,7 @@ class DisplayBuffer @lineMap.replaceScreenRows(start, end, newScreenLines) screenDelta = @lastScreenRowForBufferRow(tokenizedBufferEnd + tokenizedBufferDelta) - end - @trigger 'changed', { start, end, screenDelta, bufferDelta, bufferChange } + @triggerChanged({ start, end, screenDelta, bufferDelta, bufferChange }) buildLineForBufferRow: (bufferRow) -> @buildLinesForBufferRows(bufferRow, bufferRow) @@ -341,6 +351,24 @@ class DisplayBuffer isMarkerReversed: (id) -> @buffer.isMarkerReversed(id) + observeMarkerHeadScreenPosition: (id, callback) -> + @markerScreenPositionObservers[id] ?= { head: [], tail: [] } + @cacheMarkerScreenPositions(id) unless @markerScreenPositions[id] + @markerScreenPositionObservers[id].head.push(callback) + @buffer.observeMarkerHeadPosition id, (bufferPosition) => + @cacheMarkerScreenPositions(id) + callback(@screenPositionForBufferPosition(bufferPosition)) + + cacheMarkerScreenPositions: (id) -> + @markerScreenPositions[id] = { head: @getMarkerHeadScreenPosition(id), tail: @getMarkerTailScreenPosition } + + notifyMarkerScreenPositionObservers: -> + for id, { head } of @markerScreenPositions + currentHeadPosition = @getMarkerHeadScreenPosition(id) + unless currentHeadPosition.isEqual(head) + @cacheMarkerScreenPositions(id) + observer(currentHeadPosition) for observer in @markerScreenPositionObservers[id].head + destroy: -> @tokenizedBuffer.destroy() diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index d832e8e0f..7fdb33959 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -440,11 +440,20 @@ class EditSession markBufferPosition: (args...) -> @displayBuffer.markBufferPosition(args...) - getMarkerBufferRange: (marker) -> - @displayBuffer.getMarkerBufferRange(marker) + getMarkerBufferRange: (args...) -> + @displayBuffer.getMarkerBufferRange(args...) - getMarkerScreenRange: (marker) -> - @displayBuffer.getMarkerScreenRange(marker) + getMarkerScreenRange: (args...) -> + @displayBuffer.getMarkerScreenRange(args...) + + getMarkerBufferPosition: (args...) -> + @displayBuffer.getMarkerBufferPosition(args...) + + getMarkerHeadBufferPosition: (args...) -> + @displayBuffer.getMarkerHeadBufferPosition(args...) + + getMarkerTailBufferPosition: (args...) -> + @displayBuffer.getMarkerTailBufferPosition(args...) addAnchor: (options={}) -> anchor = @buffer.addAnchor(_.extend({editSession: this}, options))