mirror of
https://github.com/atom/atom.git
synced 2026-01-26 07:19:06 -05:00
Move anchor points to buffer; restore invalidated points on undo
This commit is contained in:
@@ -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] = []
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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...)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user