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.
This commit is contained in:
Nathan Sobo
2013-01-29 18:21:56 -07:00
parent 3f50dbe1f8
commit 0ecfba3262
4 changed files with 149 additions and 17 deletions

View File

@@ -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", ->

View File

@@ -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)

View File

@@ -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)

View File

@@ -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...)