Move anchor points to buffer; restore invalidated points on undo

This commit is contained in:
Nathan Sobo
2013-01-30 12:23:42 -07:00
parent 6c7b93872c
commit 30909a8e8f
6 changed files with 124 additions and 136 deletions

View File

@@ -658,6 +658,64 @@ describe 'Buffer', ->
expect(buffer.positionForCharacterIndex(61)).toEqual [2, 0]
expect(buffer.positionForCharacterIndex(408)).toEqual [12, 2]
fdescribe "anchor points", ->
[anchor1Id, anchor2Id, anchor3Id] = []
beforeEach ->
anchor1Id = buffer.addAnchorPoint([4, 23])
anchor2Id = buffer.addAnchorPoint([4, 23], ignoreSameLocationInserts: true)
anchor3Id = buffer.addAnchorPoint([4, 23], surviveSurroundingChanges: true)
describe "when the buffer changes", ->
describe "when the change precedes the anchor point", ->
it "moves the anchor", ->
buffer.insert([4, 5], '...')
expect(buffer.getAnchorPoint(anchor1Id)).toEqual [4, 26]
buffer.delete([[4, 5], [4, 8]])
expect(buffer.getAnchorPoint(anchor1Id)).toEqual [4, 23]
buffer.insert([0, 0], '\nhi\n')
expect(buffer.getAnchorPoint(anchor1Id)).toEqual [6, 23]
# undo works
buffer.undo()
expect(buffer.getAnchorPoint(anchor1Id)).toEqual [4, 23]
buffer.undo()
expect(buffer.getAnchorPoint(anchor1Id)).toEqual [4, 26]
describe "when the change follows the anchor point", ->
it "does not move the anchor", ->
buffer.insert([6, 5], '...')
expect(buffer.getAnchorPoint(anchor1Id)).toEqual [4, 23]
buffer.delete([[6, 5], [6, 8]])
expect(buffer.getAnchorPoint(anchor1Id)).toEqual [4, 23]
buffer.insert([10, 0], '\nhi\n')
expect(buffer.getAnchorPoint(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(buffer.getAnchorPoint(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(buffer.getAnchorPoint(anchor1Id)).toEqual [4, 26]
describe "when the change surrounds the anchor point", ->
describe "when the anchor survives surrounding changes", ->
it "preserves the anchor", ->
buffer.delete([[4, 20], [4, 26]])
expect(buffer.getAnchorPoint(anchor3Id)).toEqual [4, 20]
buffer.undo()
expect(buffer.getAnchorPoint(anchor3Id)).toEqual [4, 23]
describe "when the anchor does not survive surrounding changes", ->
it "invalidates the anchor but re-validates it on undo", ->
buffer.delete([[4, 20], [4, 26]])
expect(buffer.getAnchorPoint(anchor1Id)).toBeUndefined()
buffer.undo()
expect(buffer.getAnchorPoint(anchor1Id)).toEqual [4, 23]
describe "anchors", ->
[anchor, destroyHandler] = []

View File

@@ -1775,70 +1775,6 @@ describe "EditSession", ->
describe "anchors", ->
[anchor, 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 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]
# undo works
editSession.undo()
expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 23]
editSession.undo()
expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 26]
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", ->
describe "when the anchor survives surrounding changes", ->
it "preserves the anchor, moving it to the start of the change, but restores its location on undo", ->
buffer.delete([[4, 20], [4, 26]])
expect(editSession.getAnchorPointBufferPosition(anchor3Id)).toEqual [4, 20]
editSession.undo()
expect(editSession.getAnchorPointBufferPosition(anchor3Id)).toEqual [4, 23]
describe "when the anchor does not survive surrounding changes", ->
it "invalidates the anchor but re-validates it on undo", ->
buffer.delete([[4, 20], [4, 26]])
expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toBeUndefined()
editSession.undo()
expect(editSession.getAnchorPointBufferPosition(anchor1Id)).toEqual [4, 23]
describe ".clipBufferPosition(bufferPosition)", ->
it "clips the given position to a valid position", ->
expect(editSession.clipBufferPosition([-1, -1])).toEqual [0,0]
expect(editSession.clipBufferPosition([Infinity, Infinity])).toEqual [12,2]
expect(editSession.clipBufferPosition([8, 57])).toEqual [8, 56]
describe ".deleteLine()", ->
it "deletes the first line when the cursor is there", ->
editSession.getCursor().moveToTop()

View File

@@ -3,24 +3,27 @@ Point = require 'point'
module.exports =
class AnchorPoint
bufferPosition: null
screenPosition: null
position: null
ignoreSameLocationInserts: false
surviveSurroundingChanges: false
constructor: ({@id, @editSession, bufferPosition, @ignoreSameLocationInserts, @surviveSurroundingChanges}) ->
@setBufferPosition(bufferPosition)
constructor: ({@id, @buffer, position, @ignoreSameLocationInserts, @surviveSurroundingChanges}) ->
@setPosition(position)
tryToInvalidate: (oldRange) ->
if oldRange.containsPoint(@getPosition(), exclusive: true)
position = @getPosition()
if @surviveSurroundingChanges
@setPosition(oldRange.start)
else
@invalidate()
[@id, position]
handleBufferChange: (e) ->
{ oldRange, newRange } = e
position = @getBufferPosition()
position = @getPosition()
if oldRange.containsPoint(position, exclusive: true)
if @surviveSurroundingChanges
@setBufferPosition(oldRange.start)
else
@invalidate()
return
return if oldRange.containsPoint(position, exclusive: true)
return if @ignoreSameLocationInserts and position.isEqual(oldRange.start)
return if position.isLessThan(oldRange.end)
@@ -32,48 +35,16 @@ class AnchorPoint
newColumn = position.column
newRow += position.row - oldRange.end.row
@setBufferPosition([newRow, newColumn])
@setPosition([newRow, newColumn])
setBufferPosition: (position, options={}) ->
@bufferPosition = Point.fromObject(position)
setPosition: (position, options={}) ->
@position = Point.fromObject(position)
clip = options.clip ? true
@bufferPosition = @editSession.clipBufferPosition(@bufferPosition) if clip
@refreshScreenPosition(options)
@position = @buffer.clipPosition(@position) if clip
getBufferPosition: ->
@bufferPosition
getPosition: ->
@position
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)
invalidate: (preserve) ->
delete @buffer.validAnchorPointsById[@id]
@buffer.invalidAnchorPointsById[@id] = this

View File

@@ -8,25 +8,35 @@ class BufferChangeOperation
oldText: null
newRange: null
newText: null
anchorPointsToRestoreOnUndo: null
anchorPointsToRestoreOnRedo: null
constructor: ({@buffer, @oldRange, @newText, @options}) ->
@options ?= {}
do: ->
@oldText = @buffer.getTextInRange(@oldRange)
@newRange = @calculateNewRange(@oldRange, @newText)
@anchorPointsToRestoreOnUndo = @invalidateAnchorPoints(@oldRange)
@changeBuffer
oldRange: @oldRange
newRange: @newRange
oldText: @oldText
newText: @newText
redo: ->
@restoreAnchorPoints(@anchorPointsToRestoreOnRedo)
undo: ->
@anchorPointsToRestoreOnRedo = @invalidateAnchorPoints(@newRange)
@changeBuffer
oldRange: @newRange
newRange: @oldRange
oldText: @newText
newText: @oldText
@restoreAnchorPoints(@anchorPointsToRestoreOnUndo)
splitLines: (text) ->
lines = text.split('\n')
@@ -39,9 +49,18 @@ class BufferChangeOperation
lineEndings[index] = '\n'
{lines, lineEndings}
invalidateAnchorPoints: (oldRange) ->
_.compact(@buffer.getAnchorPoints().map (pt) -> pt.tryToInvalidate(oldRange))
restoreAnchorPoints: (anchorPoints) ->
for [id, position] in anchorPoints
if existingAnchorPoint = @buffer.validAnchorPointsById[id]
existingAnchorPoint.setPosition(position)
else
@buffer.validAnchorPointsById[id] = @buffer.invalidAnchorPointsById[id]
changeBuffer: ({ oldRange, newRange, newText, oldText }) ->
{ prefix, suffix } = @buffer.prefixAndSuffixForRange(oldRange)
{lines, lineEndings} = @splitLines(newText)
lastLineIndex = lines.length - 1
@@ -66,6 +85,7 @@ class BufferChangeOperation
@buffer.trigger 'changed', event
@buffer.scheduleStoppedChangingEvent()
@buffer.updateAnchors(event)
@buffer.updateAnchorPoints(event)
newRange
calculateNewRange: (oldRange, newText) ->

View File

@@ -8,6 +8,7 @@ UndoManager = require 'undo-manager'
BufferChangeOperation = require 'buffer-change-operation'
Anchor = require 'anchor'
AnchorRange = require 'anchor-range'
AnchorPoint = require 'anchor-point'
module.exports =
class Buffer
@@ -21,12 +22,17 @@ class Buffer
lines: null
lineEndings: null
file: null
validAnchorPointsById: null
invalidAnchorPointsById: null
anchors: null
anchorRanges: null
refcount: 0
constructor: (path, @project) ->
@id = @constructor.idCounter++
@nextAnchorPointId = 1
@validAnchorPointsById = {}
@invalidAnchorPointsById = {}
@anchors = []
@anchorRanges = []
@lines = ['']
@@ -258,6 +264,22 @@ class Buffer
isEmpty: -> @lines.length is 1 and @lines[0].length is 0
updateAnchorPoints: (bufferChange) ->
return unless bufferChange
anchorPoint.handleBufferChange(bufferChange) for anchorPoint in @getAnchorPoints()
getAnchorPoints: ->
_.values(@validAnchorPointsById)
addAnchorPoint: (position, options) ->
id = @nextAnchorPointId++
params = _.extend({buffer: this, id, position}, options)
@validAnchorPointsById[id] = new AnchorPoint(params)
id
getAnchorPoint: (id) ->
@validAnchorPointsById[id]?.getPosition()
getAnchors: -> new Array(@anchors...)
addAnchor: (options) ->

View File

@@ -355,25 +355,6 @@ 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...)