From 0ecfba32620d82a30b88cf7553f8e13b5dbaa6ef Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 29 Jan 2013 18:21:56 -0700 Subject: [PATCH] WIP: Start adding new anchorPoint API on edit session These will replace anchors, but they won't be stored on the Buffer at all. The API user will access them by a returned scalar id rather than calling methods on the returned anchor object directly. --- spec/app/edit-session-spec.coffee | 62 +++++++++++++++++------- src/app/anchor-point.coffee | 79 +++++++++++++++++++++++++++++++ src/app/display-buffer.coffee | 2 +- src/app/edit-session.coffee | 23 +++++++++ 4 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 src/app/anchor-point.coffee diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index 6f72cd8bc..8816b2a79 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -1775,24 +1775,54 @@ describe "EditSession", -> describe "anchors", -> [anchor, destroyHandler] = [] - beforeEach -> - destroyHandler = jasmine.createSpy("destroyHandler") - anchor = editSession.addAnchorAtBufferPosition([4, 25]) - anchor.on 'destroyed', destroyHandler + fdescribe "anchor points", -> + [anchor1Id, anchor2Id, anchor3Id] = [] + beforeEach -> + anchor1Id = editSession.addAnchorPointAtBufferPosition([4, 23]) + anchor2Id = editSession.addAnchorPointAtBufferPosition([4, 23], ignoreSameLocationInserts: true) + anchor3Id = editSession.addAnchorPointAtBufferPosition([4, 23], surviveSurroundingChanges: true) - describe "when a buffer change precedes an anchor", -> - it "moves the anchor in accordance with the change", -> - editSession.setSelectedBufferRange([[3, 0], [4, 10]]) - editSession.delete() - expect(anchor.getBufferPosition()).toEqual [3, 15] - expect(destroyHandler).not.toHaveBeenCalled() + describe "when the buffer changes", -> + describe "when the change precedes the anchor point", -> + it "moves the anchor", -> + buffer.insert([4, 5], '...') + expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 26] + buffer.delete([[4, 5], [4, 8]]) + expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 23] + buffer.insert([0, 0], '\nhi\n') + expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [6, 23] - describe "when a buffer change surrounds an anchor", -> - it "destroys the anchor", -> - editSession.setSelectedBufferRange([[3, 0], [5, 0]]) - editSession.delete() - expect(destroyHandler).toHaveBeenCalled() - expect(editSession.getAnchors().indexOf(anchor)).toBe -1 + describe "when the change follows the anchor point", -> + it "does not move the anchor", -> + buffer.insert([6, 5], '...') + expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 23] + buffer.delete([[6, 5], [6, 8]]) + expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 23] + buffer.insert([10, 0], '\nhi\n') + expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 23] + + describe "when the change is an insertion at the same location as the anchor point", -> + describe "if the anchor ignores same location inserts", -> + it "treats the insertion as being to the right of the anchor and does not move it", -> + buffer.insert([4, 23], '...') + expect(editSession.getAnchorPointBufferPosition(anchor2Id)).toEqual [4, 23] + + describe "if the anchor observes same location inserts", -> + it "treats the insertion as being to the left of the anchor and moves it accordingly", -> + buffer.insert([4, 23], '...') + expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 26] + + describe "when the change surrounds the anchor point", -> + beforeEach -> + buffer.delete([[4, 20], [4, 26]]) + + describe "when the anchor survives surrounding changes", -> + it "moves the anchor to the start of the change, but does not invalidate it", -> + expect(editSession.getAnchorPointBufferPosition(anchor3Id)).toEqual [4, 20] + + describe "when the anchor does not survive surrounding changes", -> + it "invalidates the anchor", -> + expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toBeUndefined() describe ".clipBufferPosition(bufferPosition)", -> it "clips the given position to a valid position", -> diff --git a/src/app/anchor-point.coffee b/src/app/anchor-point.coffee new file mode 100644 index 000000000..6bbfc66fc --- /dev/null +++ b/src/app/anchor-point.coffee @@ -0,0 +1,79 @@ +_ = require 'underscore' +Point = require 'point' + +module.exports = +class AnchorPoint + bufferPosition: null + screenPosition: null + ignoreSameLocationInserts: false + surviveSurroundingChanges: false + + constructor: ({@id, @editSession, bufferPosition, @ignoreSameLocationInserts, @surviveSurroundingChanges}) -> + @setBufferPosition(bufferPosition) + + handleBufferChange: (e) -> + { oldRange, newRange } = e + position = @getBufferPosition() + + if oldRange.containsPoint(position, exclusive: true) + if @surviveSurroundingChanges + @setBufferPosition(oldRange.start) + else + @invalidate() + return + return if @ignoreSameLocationInserts and position.isEqual(oldRange.start) + return if position.isLessThan(oldRange.end) + + newRow = newRange.end.row + newColumn = newRange.end.column + if position.row == oldRange.end.row + newColumn += position.column - oldRange.end.column + else + newColumn = position.column + newRow += position.row - oldRange.end.row + + @setBufferPosition([newRow, newColumn]) + + setBufferPosition: (position, options={}) -> + @bufferPosition = Point.fromObject(position) + clip = options.clip ? true + @bufferPosition = @editSession.clipBufferPosition(@bufferPosition) if clip + @refreshScreenPosition(options) + + getBufferPosition: -> + @bufferPosition + + setScreenPosition: (position, options={}) -> + oldScreenPosition = @screenPosition + oldBufferPosition = @bufferPosition + @screenPosition = Point.fromObject(position) + clip = options.clip ? true + assignBufferPosition = options.assignBufferPosition ? true + + @screenPosition = @editSession.clipScreenPosition(@screenPosition, options) if clip + @bufferPosition = @editSession.bufferPositionForScreenPosition(@screenPosition, options) if assignBufferPosition + + Object.freeze @screenPosition + Object.freeze @bufferPosition + +# unless @screenPosition.isEqual(oldScreenPosition) +# @trigger 'moved', +# oldScreenPosition: oldScreenPosition +# newScreenPosition: @screenPosition +# oldBufferPosition: oldBufferPosition +# newBufferPosition: @bufferPosition +# bufferChange: options.bufferChange + + getScreenPosition: -> + @screenPosition + + getScreenRow: -> + @screenPosition.row + + refreshScreenPosition: (options={}) -> + return unless @editSession + screenPosition = @editSession.screenPositionForBufferPosition(@bufferPosition, options) + @setScreenPosition(screenPosition, bufferChange: options.bufferChange, clip: false, assignBufferPosition: false) + + invalidate: -> + @editSession.removeAnchorPoint(@id) \ No newline at end of file diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee index 91e8af4b3..4057c101a 100644 --- a/src/app/display-buffer.coffee +++ b/src/app/display-buffer.coffee @@ -225,7 +225,7 @@ class DisplayBuffer @lineMap.replaceScreenRows(start, end, newScreenLines) screenDelta = @lastScreenRowForBufferRow(tokenizedBufferEnd + tokenizedBufferDelta) - end - @trigger 'changed', { start, end, screenDelta, bufferDelta } + @trigger 'changed', { start, end, screenDelta, bufferDelta, bufferChange } buildLineForBufferRow: (bufferRow) -> @buildLinesForBufferRows(bufferRow, bufferRow) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 40e99f09b..8856dba0f 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -9,6 +9,7 @@ EventEmitter = require 'event-emitter' Subscriber = require 'subscriber' Range = require 'range' AnchorRange = require 'anchor-range' +AnchorPoint = require 'anchor-point' _ = require 'underscore' fs = require 'fs' @@ -40,6 +41,8 @@ class EditSession @softTabs = @buffer.usesSoftTabs() ? softTabs ? true @languageMode = new LanguageMode(this, @buffer.getExtension()) @displayBuffer = new DisplayBuffer(@buffer, { @languageMode, tabLength }) + @nextAnchorPointId = 1 + @anchorPointsById = {} @anchors = [] @anchorRanges = [] @cursors = [] @@ -54,6 +57,7 @@ class EditSession @preserveCursorPositionOnBufferReload() @subscribe @displayBuffer, "changed", (e) => + @updateAnchorPoints(e.bufferChange) @refreshAnchorScreenPositions() unless e.bufferDelta @trigger 'screen-lines-changed', e @@ -351,6 +355,25 @@ class EditSession pushOperation: (operation) -> @buffer.pushOperation(operation, this) + updateAnchorPoints: (bufferChange) -> + return unless bufferChange + anchorPoint.handleBufferChange(bufferChange) for anchorPoint in @getAnchorPoints() + + getAnchorPoints: -> + _.values(@anchorPointsById) + + addAnchorPointAtBufferPosition: (bufferPosition, options) -> + id = @nextAnchorPointId++ + params = _.extend({editSession: this, id, bufferPosition}, options) + @anchorPointsById[id] = new AnchorPoint(params) + id + + getAnchorPointBufferPosition: (id) -> + @anchorPointsById[id]?.getBufferPosition() + + removeAnchorPoint: (id) -> + delete @anchorPointsById[id] + getAnchors: -> new Array(@anchors...)