Update text buffer to use telepath markers

This commit is contained in:
Kevin Sawicki & Nathan Sobo
2013-07-03 18:06:02 -06:00
committed by probablycorey
parent 010fa219aa
commit abc20b3a05
21 changed files with 128 additions and 861 deletions

View File

@@ -9,7 +9,8 @@ describe "DisplayBuffer", ->
atom.activatePackage('javascript-tmbundle', sync: true)
buffer = project.bufferForPath('sample.js')
displayBuffer = new DisplayBuffer(buffer, { tabLength })
displayBuffer.on 'changed', changeHandler = jasmine.createSpy 'changeHandler'
changeHandler = jasmine.createSpy 'changeHandler'
displayBuffer.on 'changed', changeHandler
afterEach ->
displayBuffer.destroy()
@@ -152,6 +153,7 @@ describe "DisplayBuffer", ->
describe "when a fold spans multiple lines", ->
it "replaces the lines spanned by the fold with a placeholder that references the fold object", ->
fold = displayBuffer.createFold(4, 7)
expect(fold).toBeDefined()
[line4, line5] = displayBuffer.linesForRows(4, 5)
expect(line4.fold).toBe fold
@@ -274,7 +276,7 @@ describe "DisplayBuffer", ->
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 3, screenDelta: -2, bufferDelta: -4)
describe "when the changes is subsequently undone", ->
it "restores destroyed folds", ->
xit "restores destroyed folds", ->
buffer.undo()
expect(displayBuffer.lineForRow(2).text).toBe '2'
expect(displayBuffer.lineForRow(2).fold).toBe fold1
@@ -601,8 +603,8 @@ describe "DisplayBuffer", ->
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
valid: true
textChanged: false
isValid: true
}
markerChangedHandler.reset()
@@ -617,8 +619,8 @@ describe "DisplayBuffer", ->
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: true
valid: true
textChanged: true
isValid: true
}
markerChangedHandler.reset()
@@ -633,8 +635,8 @@ describe "DisplayBuffer", ->
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [8, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
valid: true
textChanged: false
isValid: true
}
markerChangedHandler.reset()
@@ -649,8 +651,8 @@ describe "DisplayBuffer", ->
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
valid: true
textChanged: false
isValid: true
}
it "triggers the 'changed' event whenever the marker tail's position changes in the buffer or on screen", ->
@@ -665,8 +667,8 @@ describe "DisplayBuffer", ->
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [8, 20]
newTailBufferPosition: [11, 20]
bufferChanged: false
valid: true
textChanged: false
isValid: true
}
markerChangedHandler.reset()
@@ -681,24 +683,24 @@ describe "DisplayBuffer", ->
oldTailBufferPosition: [11, 20]
newTailScreenPosition: [8, 23]
newTailBufferPosition: [11, 23]
bufferChanged: true
valid: true
textChanged: true
isValid: true
}
it "triggers the 'changed' event whenever the marker is invalidated or revalidated", ->
xit "triggers the 'changed' event whenever the marker is invalidated or revalidated", ->
buffer.deleteRow(8)
expect(markerChangedHandler).toHaveBeenCalled()
expect(markerChangedHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [5, 10]
oldHeadBufferPosition: [8, 10]
newHeadScreenPosition: [5, 10]
newHeadBufferPosition: [8, 10]
newHeadBufferPosition: [8, 0]
oldTailScreenPosition: [5, 4]
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: true
valid: false
newTailBufferPosition: [8, 0]
textChanged: true
isValid: false
}
markerChangedHandler.reset()
@@ -714,15 +716,15 @@ describe "DisplayBuffer", ->
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: true
valid: true
textChanged: true
isValid: true
}
it "does not call the callback for screen changes that don't change the position of the marker", ->
displayBuffer.createFold(10, 11)
expect(markerChangedHandler).not.toHaveBeenCalled()
it "updates markers before emitting buffer change events, but does not notify their observers until the change event", ->
xit "updates markers before emitting buffer change events, but does not notify their observers until the change event", ->
marker2 = displayBuffer.markBufferRange([[8, 1], [8, 1]])
marker2.on 'changed', marker2ChangedHandler = jasmine.createSpy("marker2ChangedHandler")
displayBuffer.on 'changed', changeHandler = jasmine.createSpy("changeHandler").andCallFake -> onDisplayBufferChange()

View File

@@ -418,7 +418,7 @@ describe "EditSession", ->
oldScreenPosition: [6, 0]
newBufferPosition: [9, 3]
newScreenPosition: [6, 3]
bufferChanged: true
textChanged: true
)
describe "when the position of the associated selection's tail changes, but not the cursor's position", ->
@@ -478,16 +478,18 @@ describe "EditSession", ->
expect(selection1.isReversed()).toBeFalsy()
it "merges selections when they intersect when moving up", ->
editSession.setSelectedBufferRanges([[[0,9], [0,13]], [[1,10], [1,20]]], reverse: true)
editSession.setSelectedBufferRanges([[[0,9], [0,13]], [[1,10], [1,20]]], isReversed: true)
[selection1, selection2] = editSession.getSelections()
editSession.selectUp()
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelections()).toEqual [selection1]
expect(selection1.getScreenRange()).toEqual([[0, 0], [1, 20]])
expect(selection1.isReversed()).toBeTruthy()
it "merges selections when they intersect when moving left", ->
editSession.setSelectedBufferRanges([[[0,9], [0,13]], [[0,14], [1,20]]], reverse: true)
editSession.setSelectedBufferRanges([[[0,9], [0,13]], [[0,14], [1,20]]], isReversed: true)
[selection1, selection2] = editSession.getSelections()
editSession.selectLeft()
@@ -1081,7 +1083,7 @@ describe "EditSession", ->
expect(cursor2.getBufferPosition()).toEqual [8,0]
describe ".insertNewlineBelow()", ->
describe "when the operation is undone", ->
xdescribe "when the operation is undone", ->
it "places the cursor back at the previous location", ->
editSession.setCursorBufferPosition([0,2])
editSession.insertNewlineBelow()
@@ -1808,7 +1810,7 @@ describe "EditSession", ->
editSession.toggleLineCommentsInSelection()
expect(buffer.lineForRow(10)).toBe " "
describe ".undo() and .redo()", ->
xdescribe ".undo() and .redo()", ->
it "undoes/redoes the last change", ->
editSession.insertText("foo")
editSession.undo()
@@ -1879,7 +1881,7 @@ describe "EditSession", ->
expect(editSession.isFoldedAtBufferRow(1)).toBeFalsy()
expect(editSession.isFoldedAtBufferRow(2)).toBeTruthy()
describe ".transact([fn])", ->
xdescribe ".transact([fn])", ->
describe "when called without a function", ->
it "restores the selection when the transaction is undone/redone", ->
buffer.setText('1234')
@@ -2104,7 +2106,7 @@ describe "EditSession", ->
expect(buffer.lineForRow(6)).toBe(line7)
expect(buffer.getLineCount()).toBe(count - 1)
describe "when the line being deleted preceeds a fold, and the command is undone", ->
xdescribe "when the line being deleted preceeds a fold, and the command is undone", ->
it "restores the line and preserves the fold", ->
editSession.setCursorBufferPosition([4])
editSession.foldCurrentRow()

View File

@@ -2024,7 +2024,7 @@ describe "Editor", ->
it "adds/removes the 'selected' class to the fold's line element and hides the cursor if it is on the fold line", ->
editor.createFold(2, 4)
editor.setSelectedBufferRange([[1, 0], [2, 0]], preserveFolds: true, reverse: true)
editor.setSelectedBufferRange([[1, 0], [2, 0]], preserveFolds: true, isReversed: true)
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
editor.setSelectedBufferRange([[1, 0], [1, 1]], preserveFolds: true)

View File

@@ -60,7 +60,7 @@ describe "Selection", ->
describe "when only the selection's tail is moved (regression)", ->
it "emits the 'screen-range-changed' event", ->
selection.setBufferRange([[2, 0], [2, 10]], reverse: true)
selection.setBufferRange([[2, 0], [2, 10]], isReversed: true)
changeScreenRangeHandler = jasmine.createSpy('changeScreenRangeHandler')
selection.on 'screen-range-changed', changeScreenRangeHandler

View File

@@ -782,421 +782,6 @@ describe 'TextBuffer', ->
expect(buffer.positionForCharacterIndex(13)).toEqual [2, 0]
expect(buffer.positionForCharacterIndex(20)).toEqual [3, 0]
describe "markers", ->
markerCreatedHandler = null
beforeEach ->
buffer.on('marker-created', markerCreatedHandler = jasmine.createSpy("markerCreatedHandler"))
describe "marker creation", ->
it "allows markers to be created with ranges and positions", ->
marker1 = buffer.markRange([[4, 20], [4, 23]])
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
expect(marker1.getHeadPosition()).toEqual [4, 23]
expect(marker1.getTailPosition()).toEqual [4, 20]
marker2 = buffer.markPosition([4, 20])
expect(marker2.getRange()).toEqual [[4, 20], [4, 20]]
expect(marker2.getHeadPosition()).toEqual [4, 20]
expect(marker2.getTailPosition()).toEqual [4, 20]
it "allows markers to be created in a reversed orientation", ->
marker = buffer.markRange([[4, 20], [4, 23]], reverse: true)
expect(marker.isReversed()).toBeTruthy()
expect(marker.getRange()).toEqual [[4, 20], [4, 23]]
expect(marker.getHeadPosition()).toEqual [4, 20]
expect(marker.getTailPosition()).toEqual [4, 23]
it "emits the 'marker-created' event when markers are created", ->
marker = buffer.markRange([[4, 20], [4, 23]])
expect(markerCreatedHandler).toHaveBeenCalledWith(marker)
describe "marker manipulation", ->
marker = null
beforeEach ->
marker = buffer.markRange([[4, 20], [4, 23]])
it "allows a marker's head and tail positions to be changed", ->
marker.setHeadPosition([5, 3])
expect(marker.getRange()).toEqual [[4, 20], [5, 3]]
marker.setTailPosition([6, 3])
expect(marker.getRange()).toEqual [[5, 3], [6, 3]]
expect(marker.isReversed()).toBeTruthy()
it "clips head and tail positions to ensure they are in bounds", ->
marker.setHeadPosition([-100, -5])
expect(marker.getRange()).toEqual([[0, 0], [4, 20]])
marker.setTailPosition([Infinity, Infinity])
expect(marker.getRange()).toEqual([[0, 0], [12, 2]])
it "allows a marker's tail to be placed and cleared", ->
marker.clearTail()
expect(marker.getRange()).toEqual [[4, 23], [4, 23]]
marker.placeTail()
marker.setHeadPosition([2, 0])
expect(marker.getRange()).toEqual [[2, 0], [4, 23]]
expect(marker.isReversed()).toBeTruthy()
it "returns whether the position changed", ->
expect(marker.setHeadPosition([5, 3])).toBeTruthy()
expect(marker.setHeadPosition([5, 3])).toBeFalsy()
expect(marker.setTailPosition([6, 3])).toBeTruthy()
expect(marker.setTailPosition([6, 3])).toBeFalsy()
describe "change events", ->
[changedHandler, marker] = []
beforeEach ->
marker = buffer.markRange([[4, 20], [4, 23]])
marker.on 'changed', changedHandler = jasmine.createSpy("changedHandler")
it "triggers 'changed' events when the marker's head position changes", ->
marker.setHeadPosition([6, 2])
expect(changedHandler).toHaveBeenCalled()
expect(changedHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [6, 2]
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
bufferChanged: false
valid: true
}
changedHandler.reset()
buffer.insert([6, 0], '...')
expect(changedHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
oldHeadPosition: [6, 2]
newHeadPosition: [6, 5]
bufferChanged: true
valid: true
}
it "calls the given callback when the marker's tail position changes", ->
marker.setTailPosition([6, 2])
expect(changedHandler).toHaveBeenCalled()
expect(changedHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [4, 20]
newTailPosition: [6, 2]
bufferChanged: false
valid: true
}
changedHandler.reset()
buffer.insert([6, 0], '...')
expect(changedHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [6, 2]
newTailPosition: [6, 5]
bufferChanged: true
valid: true
}
it "triggers 'changed' events when the selection's tail is cleared", ->
marker.clearTail()
expect(changedHandler).toHaveBeenCalled()
expect(changedHandler.argsForCall[0][0]).toEqual {
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
oldTailPosition: [4, 20]
newTailPosition: [4, 23]
bufferChanged: false
valid: true
}
it "only triggers 'changed' events once when both the marker's head and tail positions change due to the same operation", ->
buffer.insert([4, 0], '...')
expect(changedHandler.callCount).toBe 1
expect(changedHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 23]
oldHeadPosition: [4, 23]
newHeadPosition: [4, 26]
bufferChanged: true
valid: true
}
changedHandler.reset()
marker.setRange([[0, 0], [1, 1]])
expect(changedHandler.callCount).toBe 1
expect(changedHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 23]
newTailPosition: [0, 0]
oldHeadPosition: [4, 26]
newHeadPosition: [1, 1]
bufferChanged: false
valid: true
}
it "triggers 'changed' events with the valid flag set to false when the marker is invalidated", ->
buffer.deleteRow(4)
expect(changedHandler.callCount).toBe 1
expect(changedHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
bufferChanged: true
valid: false
}
changedHandler.reset()
buffer.undo()
expect(changedHandler.callCount).toBe 1
expect(changedHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
bufferChanged: true
valid: true
}
describe ".findMarkers(attributes)", ->
[marker1, marker2, marker3, marker4] = []
beforeEach ->
marker1 = buffer.markRange([[0, 0], [3, 0]], class: 'a')
marker2 = buffer.markRange([[0, 0], [5, 0]], class: 'a')
marker3 = buffer.markRange([[6, 0], [7, 0]], class: 'a')
marker4 = buffer.markRange([[9, 0], [10, 0]], class: 'b')
it "returns the markers matching the given attributes, sorted by the buffer location and size of their ranges", ->
expect(buffer.findMarkers(class: 'a')).toEqual [marker2, marker1, marker3]
it "allows the startRow and endRow to be specified", ->
expect(buffer.findMarkers(class: 'a', startRow: 0)).toEqual [marker2, marker1]
expect(buffer.findMarkers(class: 'a', startRow: 0, endRow: 3)).toEqual [marker1]
expect(buffer.findMarkers(endRow: 10)).toEqual [marker4]
describe "marker destruction", ->
marker = null
beforeEach ->
marker = buffer.markRange([[4, 20], [4, 23]])
it "allows a marker to be destroyed", ->
marker.destroy()
expect(buffer.getMarker(marker.id)).toBeUndefined()
it "does not restore invalidated markers that have been destroyed", ->
buffer.delete([[4, 15], [4, 25]])
expect(buffer.getMarker(marker.id)).toBeUndefined()
marker.destroy()
buffer.undo()
expect(buffer.getMarker(marker.id)).toBeUndefined()
# even "invalidationStrategy: never" markers get destroyed properly
marker2 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'never')
buffer.delete([[4, 15], [4, 25]])
marker2.destroy()
buffer.undo()
expect(buffer.getMarker(marker2.id)).toBeUndefined()
it "emits 'destroyed' on the marker when it is destroyed", ->
marker.on 'destroyed', destroyedHandler = jasmine.createSpy("destroyedHandler")
marker.destroy()
expect(destroyedHandler).toHaveBeenCalled()
describe "marker updates due to buffer changes", ->
[marker1, marker2, marker3] = []
beforeEach ->
marker1 = buffer.markRange([[4, 20], [4, 23]])
marker2 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'never')
marker3 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'between')
describe "when the buffer changes due to a new operation", ->
describe "when the change precedes the marker range", ->
it "moves the marker", ->
buffer.insert([4, 5], '...')
expect(marker1.getRange()).toEqual [[4, 23], [4, 26]]
buffer.delete([[4, 5], [4, 8]])
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
buffer.insert([0, 0], '\nhi\n')
expect(marker1.getRange()).toEqual [[6, 20], [6, 23]]
# undo works
buffer.undo()
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
buffer.undo()
expect(marker1.getRange()).toEqual [[4, 23], [4, 26]]
it "restores the marker range exactly on undo", ->
marker = buffer.markRange([[3, 0], [3, 62]])
buffer.delete([[2, 0], [3, 0]])
expect(marker.getRange()).toEqual [[2, 0], [2, 62]]
buffer.undo()
expect(marker.getRange()).toEqual [[3, 0], [3, 62]]
describe "when the change follows the marker range", ->
it "does not move the marker", ->
buffer.insert([6, 5], '...')
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
buffer.delete([[6, 5], [6, 8]])
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
buffer.insert([10, 0], '\nhi\n')
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the change is an insertion at the start of the marker range", ->
it "does not move the start point, but does move the end point", ->
buffer.insert([4, 20], '...')
expect(marker1.getRange()).toEqual [[4, 20], [4, 26]]
describe "when the invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.insert([4, 20], '...')
expect(marker3.isValid()).toBeFalsy()
describe "when the change is an insertion at the end of the marker range", ->
it "moves the end point", ->
buffer.insert([4, 23], '...')
expect(marker1.getRange()).toEqual [[4, 20], [4, 26]]
describe "when the invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.insert([4, 23], '...')
expect(marker3.isValid()).toBeFalsy()
describe "when the change surrounds the marker range", ->
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 25]])
expect(marker1.isValid()).toBeFalsy()
buffer.undo()
expect(marker1.isValid()).toBeTruthy()
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 25]])
expect(marker3.isValid()).toBeFalsy()
buffer.undo()
expect(marker3.isValid()).toBeTruthy()
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'never'", ->
it "does not invalidate the marker, but sets it to an empty range at the end of the change", ->
buffer.change([[4, 15], [4, 25]], "...")
expect(marker2.isValid()).toBeTruthy()
expect(marker2.getRange()).toEqual [[4, 18], [4, 18]]
buffer.undo()
expect(marker2.isValid()).toBeTruthy()
expect(marker2.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the change straddles the start of the marker range", ->
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 22]])
expect(marker1.isValid()).toBeFalsy()
buffer.undo()
expect(marker1.isValid()).toBeTruthy()
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 22]])
expect(marker3.isValid()).toBeFalsy()
buffer.undo()
expect(marker3.isValid()).toBeTruthy()
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'never'", ->
it "moves the start of the marker range to the end of the change", ->
buffer.delete([[4, 15], [4, 22]])
expect(marker2.isValid()).toBeTruthy()
expect(marker2.getRange()).toEqual [[4, 15], [4, 16]]
buffer.undo()
expect(marker2.isValid()).toBeTruthy()
expect(marker2.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the change straddles the end of the marker range", ->
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 22], [4, 25]])
expect(marker1.isValid()).toBeFalsy()
buffer.undo()
expect(marker1.isValid()).toBeTruthy()
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.delete([[4, 22], [4, 25]])
expect(marker3.isValid()).toBeFalsy()
buffer.undo()
expect(marker3.isValid()).toBeTruthy()
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'never'", ->
it "moves the end of the marker range to the start of the change", ->
buffer.delete([[4, 22], [4, 25]])
expect(marker2.isValid()).toBeTruthy()
expect(marker2.getRange()).toEqual [[4, 20], [4, 22]]
buffer.undo()
expect(marker2.isValid()).toBeTruthy()
expect(marker2.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the change is between the start and the end of the marker range", ->
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
it "does not invalidate the marker", ->
buffer.insert([4, 21], 'x')
expect(marker1.isValid()).toBeTruthy()
expect(marker1.getRange()).toEqual [[4, 20], [4, 24]]
buffer.undo()
expect(marker1.isValid()).toBeTruthy()
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.insert([4, 21], 'x')
expect(marker3.isValid()).toBeFalsy()
buffer.undo()
expect(marker3.isValid()).toBeTruthy()
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'never'", ->
it "moves the end of the marker range to the start of the change", ->
buffer.insert([4, 21], 'x')
expect(marker2.isValid()).toBeTruthy()
expect(marker2.getRange()).toEqual [[4, 20], [4, 24]]
buffer.undo()
expect(marker2.isValid()).toBeTruthy()
expect(marker2.getRange()).toEqual [[4, 20], [4, 23]]
describe "when the buffer changes due to the undo or redo of a previous operation", ->
it "restores invalidated markers when undoing/redoing in the other direction", ->
buffer.change([[4, 21], [4, 24]], "foo")
expect(marker1.isValid()).toBeFalsy()
marker3 = buffer.markRange([[4, 20], [4, 23]])
buffer.undo()
expect(marker1.isValid()).toBeTruthy()
expect(marker1.getRange()).toEqual [[4, 20], [4, 23]]
expect(marker3.isValid()).toBeFalsy()
marker4 = buffer.markRange([[4, 20], [4, 23]])
buffer.redo()
expect(marker3.isValid()).toBeTruthy()
expect(marker3.getRange()).toEqual [[4, 20], [4, 23]]
expect(marker4.isValid()).toBeFalsy()
buffer.undo()
expect(marker4.isValid()).toBeTruthy()
expect(marker4.getRange()).toEqual [[4, 20], [4, 23]]
describe ".markersForPosition(position)", ->
it "returns all markers that intersect the given position", ->
m1 = buffer.markRange([[3, 4], [3, 10]])
m2 = buffer.markRange([[3, 4], [3, 5]])
m3 = buffer.markPosition([3, 5])
expect(_.difference(buffer.markersForPosition([3, 5]), [m1, m2, m3]).length).toBe 0
expect(_.difference(buffer.markersForPosition([3, 4]), [m1, m2]).length).toBe 0
expect(_.difference(buffer.markersForPosition([3, 10]), [m1]).length).toBe 0
describe ".usesSoftTabs()", ->
it "returns true if the first indented line begins with tabs", ->
buffer.setText("function() {\n foo();\n}")
@@ -1306,20 +891,6 @@ describe 'TextBuffer', ->
buffer.append("hello\n1\r\n2\n")
expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n"
describe ".clipPosition(position)", ->
describe "when the position is before the start of the buffer", ->
it "returns the first position in the buffer", ->
expect(buffer.clipPosition([-1,0])).toEqual [0,0]
expect(buffer.clipPosition([0,-1])).toEqual [0,0]
expect(buffer.clipPosition([-1,-1])).toEqual [0,0]
describe "when the position is after the end of the buffer", ->
it "returns the last position in the buffer", ->
buffer.setText('some text')
expect(buffer.clipPosition([1, 0])).toEqual [0,9]
expect(buffer.clipPosition([0,10])).toEqual [0,9]
expect(buffer.clipPosition([10,Infinity])).toEqual [0,9]
describe "serialization", ->
buffer2 = null

View File

@@ -19,7 +19,7 @@ class BufferChangeOperation
do: ->
@buffer.pauseEvents()
@pauseMarkerObservation()
@markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
# @markersToRestoreOnUndo = @invalidateMarkers(@oldRange)
if @oldRange?
@oldText = @buffer.getTextInRange(@oldRange)
@newRange = Range.fromText(@oldRange.start, @newText)
@@ -28,7 +28,7 @@ class BufferChangeOperation
newRange: @newRange
oldText: @oldText
newText: @newText
@restoreMarkers(@markersToRestoreOnRedo) if @markersToRestoreOnRedo
# @restoreMarkers(@markersToRestoreOnRedo) if @markersToRestoreOnRedo
@buffer.resumeEvents()
@resumeMarkerObservation()
newRange
@@ -36,14 +36,14 @@ class BufferChangeOperation
undo: ->
@buffer.pauseEvents()
@pauseMarkerObservation()
@markersToRestoreOnRedo = @invalidateMarkers(@newRange)
# @markersToRestoreOnRedo = @invalidateMarkers(@newRange)
if @oldRange?
@changeBuffer
oldRange: @newRange
newRange: @oldRange
oldText: @newText
newText: @oldText
@restoreMarkers(@markersToRestoreOnUndo)
# @restoreMarkers(@markersToRestoreOnUndo)
@buffer.resumeEvents()
@resumeMarkerObservation()

View File

@@ -1,255 +0,0 @@
_ = require 'underscore'
{Point, Range} = require 'telepath'
EventEmitter = require 'event-emitter'
module.exports =
class BufferMarker
headPosition: null
tailPosition: null
suppressObserverNotification: false
invalidationStrategy: null
### Internal ###
constructor: ({@id, @buffer, range, @invalidationStrategy, @attributes, noTail, reverse}) ->
@invalidationStrategy ?= 'contains'
@setRange(range, {noTail, reverse})
### Public ###
# Sets the marker's range, potentialy modifying both its head and tail.
#
# range - The new {Range} the marker should cover
# options - A hash of options with the following keys:
# reverse: if `true`, the marker is reversed; that is, its tail is "above" the head
# noTail: if `true`, the marker doesn't have a tail
setRange: (range, options={}) ->
@consolidateObserverNotifications false, =>
range = Range.fromObject(range)
if options.reverse
@setTailPosition(range.end) unless options.noTail
@setHeadPosition(range.start)
else
@setTailPosition(range.start) unless options.noTail
@setHeadPosition(range.end)
# Identifies if the ending position of a marker is greater than the starting position.
#
# This can happen when, for example, you highlight text "up" in a {Buffer}.
#
# Returns a {Boolean}.
isReversed: ->
@tailPosition? and @headPosition.isLessThan(@tailPosition)
# Checks that the marker's attributes match the given attributes
#
# Returns a {Boolean}.
matchesAttributes: (queryAttributes) ->
for key, value of queryAttributes
switch key
when 'startRow'
return false unless @getRange().start.row == value
when 'endRow'
return false unless @getRange().end.row == value
when 'containsRange'
return false unless @getRange().containsRange(value, exclusive: true)
when 'containsRow'
return false unless @getRange().containsRow(value)
else
return false unless _.isEqual(@attributes[key], value)
true
# Identifies if the marker's head position is equal to its tail.
#
# Returns a {Boolean}.
isRangeEmpty: ->
@getHeadPosition().isEqual(@getTailPosition())
# Retrieves the {Range} between a marker's head and its tail.
#
# Returns a {Range}.
getRange: ->
if @tailPosition
new Range(@getTailPosition(), @getHeadPosition())
else
new Range(@getHeadPosition(), @getHeadPosition())
# Retrieves the position of the marker's head.
#
# Returns a {Point}.
getHeadPosition: -> @headPosition?.copy()
# Retrieves the position of the marker's tail.
#
# Returns a {Point}.
getTailPosition: -> @tailPosition?.copy() ? @getHeadPosition()
# Sets the position of the marker's head.
#
# newHeadPosition - The new {Point} to place the head
# options - A hash with the following keys:
# clip: if `true`, the point is [clipped]{Buffer.clipPosition}
# bufferChanged: if `true`, indicates that the {Buffer} should trigger an event that it's changed
#
# Returns a {Point} representing the new head position.
setHeadPosition: (newHeadPosition, options={}) ->
oldHeadPosition = @getHeadPosition()
newHeadPosition = Point.fromObject(newHeadPosition)
newHeadPosition = @buffer.clipPosition(newHeadPosition) if options.clip ? true
return if newHeadPosition.isEqual(@headPosition)
@headPosition = newHeadPosition
bufferChanged = !!options.bufferChanged
@notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged})
@headPosition
# Sets the position of the marker's tail.
#
# newHeadPosition - The new {Point} to place the tail
# options - A hash with the following keys:
# clip: if `true`, the point is [clipped]{Buffer.clipPosition}
# bufferChanged: if `true`, indicates that the {Buffer} should trigger an event that it's changed
#
# Returns a {Point} representing the new tail position.
setTailPosition: (newTailPosition, options={}) ->
oldTailPosition = @getTailPosition()
newTailPosition = Point.fromObject(newTailPosition)
newTailPosition = @buffer.clipPosition(newTailPosition) if options.clip ? true
return if newTailPosition.isEqual(@tailPosition)
@tailPosition = newTailPosition
bufferChanged = !!options.bufferChanged
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged})
@tailPosition
# Retrieves the starting position of the marker.
#
# Returns a {Point}.
getStartPosition: ->
@getRange().start
# Retrieves the ending position of the marker.
#
# Returns a {Point}.
getEndPosition: ->
@getRange().end
# Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
placeTail: ->
@setTailPosition(@getHeadPosition()) unless @tailPosition
# Removes the tail from the marker.
clearTail: ->
oldTailPosition = @getTailPosition()
@tailPosition = null
newTailPosition = @getTailPosition()
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
# Identifies if a {Point} is within the marker.
#
# Returns a {Boolean}.
containsPoint: (point) ->
@getRange().containsPoint(point)
# Destroys the marker
destroy: ->
@buffer.destroyMarker(@id)
@trigger 'destroyed'
# Returns a {Boolean} indicating whether the marker is valid. Markers can be
# invalidated when a region surrounding them in the buffer is changed.
isValid: ->
@buffer.getMarker(@id)?
# Returns a {Boolean} indicating whether the marker has been destroyed. A marker
# can be invalid without being destroyed, in which case undoing the invalidating
# operation would restore the marker. Once a marker is destroyed by calling
# {BufferMarker.destroy}, no undo/redo operation can ever bring it back.
isDestroyed: ->
not (@buffer.validMarkers[@id]? or @buffer.invalidMarkers[@id]?)
### Internal ###
tryToInvalidate: (changedRange) ->
previousRange = @getRange()
if changedRange
betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false)
containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true)
containsEnd = changedRange.containsPoint(@getEndPosition(), exclusive: true)
switch @invalidationStrategy
when 'between'
@invalidate() if betweenStartAndEnd or containsStart or containsEnd
when 'contains'
@invalidate() if containsStart or containsEnd
when 'never'
if containsStart or containsEnd
if containsStart and containsEnd
@setRange([changedRange.end, changedRange.end])
else if containsStart
@setRange([changedRange.end, @getEndPosition()])
else
@setRange([@getStartPosition(), changedRange.start])
[@id, previousRange]
handleBufferChange: (bufferChange) ->
@consolidateObserverNotifications true, =>
@setHeadPosition(@updatePosition(@headPosition, bufferChange, true), clip: false, bufferChanged: true)
@setTailPosition(@updatePosition(@tailPosition, bufferChange, false), clip: false, bufferChanged: true) if @tailPosition
updatePosition: (position, bufferChange, isHead) ->
{ oldRange, newRange } = bufferChange
return position if not isHead and oldRange.start.isEqual(position)
return position 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
[newRow, newColumn]
notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged} = {}) ->
return if @suppressObserverNotification
if newHeadPosition? and newTailPosition?
return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition)
else if newHeadPosition?
return if _.isEqual(newHeadPosition, oldHeadPosition)
else if newTailPosition?
return if _.isEqual(newTailPosition, oldTailPosition)
oldHeadPosition ?= @getHeadPosition()
newHeadPosition ?= @getHeadPosition()
oldTailPosition ?= @getTailPosition()
newTailPosition ?= @getTailPosition()
valid = @isValid()
@trigger 'changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}
consolidateObserverNotifications: (bufferChanged, fn) ->
@suppressObserverNotification = true
oldHeadPosition = @getHeadPosition()
oldTailPosition = @getTailPosition()
fn()
newHeadPosition = @getHeadPosition()
newTailPosition = @getTailPosition()
@suppressObserverNotification = false
@notifyObservers({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged})
invalidate: ->
delete @buffer.validMarkers[@id]
@buffer.invalidMarkers[@id] = this
@notifyObservers(bufferChanged: true)
revalidate: ->
delete @buffer.invalidMarkers[@id]
@buffer.validMarkers[@id] = this
@notifyObservers(bufferChanged: true)
_.extend BufferMarker.prototype, EventEmitter

View File

@@ -21,17 +21,17 @@ class Cursor
@updateVisibility()
{oldHeadScreenPosition, newHeadScreenPosition} = e
{oldHeadBufferPosition, newHeadBufferPosition} = e
{bufferChanged} = e
{textChanged} = e
return if oldHeadScreenPosition.isEqual(newHeadScreenPosition)
@needsAutoscroll ?= @isLastCursor() and !bufferChanged
@needsAutoscroll ?= @isLastCursor() and !textChanged
movedEvent =
oldBufferPosition: oldHeadBufferPosition
oldScreenPosition: oldHeadScreenPosition
newBufferPosition: newHeadBufferPosition
newScreenPosition: newHeadScreenPosition
bufferChanged: bufferChanged
textChanged: textChanged
@trigger 'moved', movedEvent
@editSession.trigger 'cursor-moved', movedEvent

View File

@@ -6,15 +6,24 @@ Subscriber = require 'subscriber'
module.exports =
class DisplayBufferMarker
bufferMarkerSubscription: null
headScreenPosition: null
tailScreenPosition: null
valid: true
oldHeadBufferPosition: null
oldHeadScreenPosition: null
oldTailBufferPosition: null
oldTailScreenPosition: null
wasValid: true
### Internal ###
constructor: ({@bufferMarker, @displayBuffer}) ->
@id = @bufferMarker.id
@observeBufferMarker()
@oldHeadBufferPosition = @getHeadBufferPosition()
@oldHeadScreenPosition = @getHeadScreenPosition()
@oldTailBufferPosition = @getTailBufferPosition()
@oldTailScreenPosition = @getTailScreenPosition()
@wasValid = @isValid()
@subscribe @bufferMarker, 'destroyed', => @destroyed()
@subscribe @bufferMarker, 'changed', (event) => @notifyObservers(event)
### Public ###
@@ -48,7 +57,7 @@ class DisplayBufferMarker
#
# Returns a {Point}.
getHeadScreenPosition: ->
@headScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
@displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
# Sets the screen position of the marker's head.
#
@@ -75,7 +84,7 @@ class DisplayBufferMarker
#
# Returns a {Point}.
getTailScreenPosition: ->
@tailScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
@displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
# Sets the screen position of the marker's tail.
#
@@ -103,8 +112,8 @@ class DisplayBufferMarker
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
placeTail: ->
@bufferMarker.placeTail()
plantTail: ->
@bufferMarker.plantTail()
# Removes the tail from the marker.
clearTail: ->
@@ -144,54 +153,37 @@ class DisplayBufferMarker
delete @displayBuffer.markers[@id]
@trigger 'destroyed'
observeBufferMarker: ->
@subscribe @bufferMarker, 'destroyed', => @destroyed()
notifyObservers: ({textChanged}) ->
textChanged ?= false
@getHeadScreenPosition() # memoize current value
@getTailScreenPosition() # memoize current value
@subscribe @bufferMarker, 'changed', ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}) =>
@notifyObservers
oldHeadBufferPosition: oldHeadPosition
newHeadBufferPosition: newHeadPosition
oldTailBufferPosition: oldTailPosition
newTailBufferPosition: newTailPosition
bufferChanged: bufferChanged
valid: valid
newHeadBufferPosition = @getHeadBufferPosition()
newHeadScreenPosition = @getHeadScreenPosition()
newTailBufferPosition = @getTailBufferPosition()
newTailScreenPosition = @getTailScreenPosition()
isValid = @isValid()
notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged, valid} = {}) ->
return unless @valid or @isValid()
oldHeadScreenPosition = @getHeadScreenPosition()
newHeadScreenPosition = oldHeadScreenPosition
oldTailScreenPosition = @getTailScreenPosition()
newTailScreenPosition = oldTailScreenPosition
valid ?= true
if valid
@headScreenPosition = null
newHeadScreenPosition = @getHeadScreenPosition()
@tailScreenPosition = null
newTailScreenPosition = @getTailScreenPosition()
validChanged = valid isnt @valid
headScreenPositionChanged = not _.isEqual(newHeadScreenPosition, oldHeadScreenPosition)
tailScreenPositionChanged = not _.isEqual(newTailScreenPosition, oldTailScreenPosition)
return unless validChanged or headScreenPositionChanged or tailScreenPositionChanged
oldHeadBufferPosition ?= @getHeadBufferPosition()
newHeadBufferPosition = @getHeadBufferPosition() ? oldHeadBufferPosition
oldTailBufferPosition ?= @getTailBufferPosition()
newTailBufferPosition = @getTailBufferPosition() ? oldTailBufferPosition
@valid = valid
changed = false
changed = true unless _.isEqual(newHeadBufferPosition, @oldHeadBufferPosition)
changed = true unless _.isEqual(newHeadScreenPosition, @oldHeadScreenPosition)
changed = true unless _.isEqual(newTailBufferPosition, @oldTailBufferPosition)
changed = true unless _.isEqual(newTailScreenPosition, @oldTailScreenPosition)
changed = true unless _.isEqual(isValid, @wasValid)
return unless changed
@trigger 'changed', {
oldHeadScreenPosition, newHeadScreenPosition,
oldTailScreenPosition, newTailScreenPosition,
oldHeadBufferPosition, newHeadBufferPosition,
oldTailBufferPosition, newTailBufferPosition,
bufferChanged
valid
@oldHeadScreenPosition, newHeadScreenPosition,
@oldTailScreenPosition, newTailScreenPosition,
@oldHeadBufferPosition, newHeadBufferPosition,
@oldTailBufferPosition, newTailBufferPosition,
textChanged,
isValid
}
@oldHeadBufferPosition = newHeadBufferPosition
@oldHeadScreenPosition = newHeadScreenPosition
@oldTailBufferPosition = newTailBufferPosition
@oldTailScreenPosition = newTailScreenPosition
@wasValid = isValid
_.extend DisplayBufferMarker.prototype, EventEmitter
_.extend DisplayBufferMarker.prototype, Subscriber

View File

@@ -466,12 +466,11 @@ class DisplayBuffer
#
# Returns an {Array} of {DisplayBufferMarker}s
findMarkers: (attributes) ->
{ startBufferRow, endBufferRow, containsBufferRange, containsBufferRow } = attributes
{ startBufferRow, endBufferRow, containsBufferRange } = attributes
attributes.startRow = startBufferRow if startBufferRow?
attributes.endRow = endBufferRow if endBufferRow?
attributes.containsRange = containsBufferRange if containsBufferRange?
attributes.containsRow = containsBufferRow if containsBufferRow?
attributes = _.omit(attributes, ['startBufferRow', 'endBufferRow', 'containsBufferRange', 'containsBufferRow'])
attributes = _.omit(attributes, ['startBufferRow', 'endBufferRow', 'containsBufferRange'])
@buffer.findMarkers(attributes).map ({id}) => @getMarker(id)
findFoldMarker: (attributes) ->
@@ -491,7 +490,7 @@ class DisplayBuffer
refreshMarkerScreenPositions: ->
for marker in @getMarkers()
marker.notifyObservers(bufferChanged: false)
marker.notifyObservers(textChanged: false)
destroy: ->
marker.unsubscribe() for marker in @getMarkers()

View File

@@ -74,7 +74,7 @@ class EditSession
@displayBuffer.on 'grammar-changed', => @handleGrammarChange()
@state.observe ({key, newValue}) =>
@state.on 'changed', ({key, newValue}) =>
switch key
when 'scrollTop'
@trigger 'scroll-top-changed', newValue
@@ -1112,7 +1112,7 @@ class EditSession
selectToScreenPosition: (position) ->
lastSelection = @getLastSelection()
lastSelection.selectToScreenPosition(position)
@mergeIntersectingSelections(reverse: lastSelection.isReversed())
@mergeIntersectingSelections(isReversed: lastSelection.isReversed())
# Selects the text one position right of the cursor.
selectRight: ->
@@ -1247,7 +1247,7 @@ class EditSession
expandSelectionsBackward: (fn) ->
fn(selection) for selection in @getSelections()
@mergeIntersectingSelections(reverse: true)
@mergeIntersectingSelections(isReversed: true)
finalizeSelections: ->
selection.finalize() for selection in @getSelections()

View File

@@ -17,10 +17,14 @@ class Fold
@displayBuffer.foldsByMarkerId[@marker.id] = this
@updateDisplayBuffer()
@marker.on 'destroyed', => @destroyed()
@marker.on 'changed', ({isValid}) => @destroy() unless isValid
# Returns whether this fold is contained within another fold
isInsideLargerFold: ->
@displayBuffer.findMarker(class: 'fold', containsBufferRange: @getBufferRange())?
if largestContainingFoldMarker = @displayBuffer.findMarker(class: 'fold', containsBufferRange: @getBufferRange())
not largestContainingFoldMarker.getBufferRange().isEqual(@getBufferRange())
else
false
# Destroys this fold
destroy: ->

View File

@@ -18,7 +18,7 @@ class PaneAxis extends View
@state = telepath.Document.create(deserializer: @className(), children: [])
@addChild(child) for child in args
@state.get('children').observe ({index, inserted, removed, site}) =>
@state.get('children').on 'changed', ({index, inserted, removed, site}) =>
return if site is @state.site.id
for childState in removed
@removeChild(@children(":eq(#{index})").view(), updateState: false)

View File

@@ -24,7 +24,7 @@ class PaneContainer extends View
else
@state = telepath.Document.create(deserializer: 'PaneContainer')
@state.observe ({key, newValue, site}) =>
@state.on 'changed', ({key, newValue, site}) =>
return if site is @state.site.id
if key is 'root'
@setRoot(deserialize(newValue), updateState: false)

View File

@@ -35,14 +35,14 @@ class Pane extends View
deserializer: 'Pane'
items: @items.map (item) -> item.getState?() ? item.serialize()
@state.get('items').observe ({index, removed, inserted, site}) =>
@state.get('items').on 'changed', ({index, removed, inserted, site}) =>
return if site is @state.site.id
for itemState in removed
@removeItemAtIndex(index, updateState: false)
for itemState, i in inserted
@addItem(deserialize(itemState), index + i, updateState: false)
@state.observe ({key, newValue, site}) =>
@state.on 'changed', ({key, newValue, site}) =>
return if site is @state.site.id
@showItemForUri(newValue) if key is 'activeItemUri'

View File

@@ -63,7 +63,7 @@ class Project
@state = telepath.Document.create(deserializer: @constructor.name, version: @constructor.version, buffers: [])
@setPath(pathOrState)
@state.get('buffers').observe ({inserted, removed, index, site}) =>
@state.get('buffers').on 'changed', ({inserted, removed, index, site}) =>
return if site is @state.site.id
for removedBuffer in removed

View File

@@ -149,7 +149,7 @@ class Selection
@modifySelection =>
if @initialScreenRange
if position.isLessThan(@initialScreenRange.start)
@marker.setScreenRange([position, @initialScreenRange.end], reverse: true)
@marker.setScreenRange([position, @initialScreenRange.end], isReversed: true)
else
@marker.setScreenRange([@initialScreenRange.start, position])
else
@@ -271,7 +271,7 @@ class Selection
newBufferRange = @editSession.buffer.change(oldBufferRange, text)
if options.select
@setBufferRange(newBufferRange, reverse: wasReversed)
@setBufferRange(newBufferRange, isReversed: wasReversed)
else
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
@@ -478,7 +478,7 @@ class Selection
modifySelection: (fn) ->
@retainSelection = true
@placeTail()
@plantTail()
fn()
@retainSelection = false
@@ -487,8 +487,8 @@ class Selection
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
placeTail: ->
@marker.placeTail()
plantTail: ->
@marker.plantTail()
# Identifies if a selection intersects with a given buffer range.
#

View File

@@ -6,7 +6,6 @@ File = require 'file'
EventEmitter = require 'event-emitter'
UndoManager = require 'undo-manager'
BufferChangeOperation = require 'buffer-change-operation'
BufferMarker = require 'buffer-marker'
guid = require 'guid'
# Public: Represents the contents of a file.
@@ -29,8 +28,6 @@ class TextBuffer
cachedMemoryContents: null
conflict: false
file: null
validMarkers: null
invalidMarkers: null
refcount: 0
# Creates a new buffer.
@@ -38,10 +35,6 @@ class TextBuffer
# path - A {String} representing the file path
# initialText - A {String} setting the starting text
constructor: (args...) ->
@nextMarkerId = 1
@validMarkers = {}
@invalidMarkers = {}
if args[0] instanceof telepath.Document
@state = args[0]
@text = @state.get('text')
@@ -67,7 +60,9 @@ class TextBuffer
@text ?= telepath.Document.create('', shareStrings: true)
@state.set('text', @text)
@text.observe(@handleTextChange)
@text.on 'changed', @handleTextChange
@text.on 'marker-created', (marker) => @trigger 'marker-created', marker
@text.on 'markers-updated', => @trigger 'markers-updated'
@undoManager = new UndoManager(this)
### Internal ###
@@ -76,10 +71,8 @@ class TextBuffer
@cachedMemoryContents = null
@conflict = false if @conflict and !@isModified()
bufferChangeEvent = _.pick(event, 'oldRange', 'newRange', 'oldText', 'newText')
marker.handleBufferChange(bufferChangeEvent) for marker in @getMarkers()
@trigger 'changed', bufferChangeEvent
@scheduleModifiedEvents()
@trigger 'markers-updated' if @state.site.id isnt event.site
destroy: ->
unless @destroyed
@@ -314,7 +307,6 @@ class TextBuffer
else
startPoint = [start, 0]
endPoint = [end + 1, 0]
@delete(new Range(startPoint, endPoint))
# Adds text to the end of the buffer.
@@ -325,10 +317,10 @@ class TextBuffer
# Adds text to a specific point in the buffer
#
# point - A {Point} in the buffer to insert into
# position - A {Point} in the buffer to insert into
# text - A {String} of text to add
insert: (point, text) ->
@change(new Range(point, point), text)
insert: (position, text) ->
@change(new Range(position, position), text)
# Deletes text from the buffer
#
@@ -344,15 +336,7 @@ class TextBuffer
#
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
clipPosition: (position) ->
position = Point.fromObject(position)
eofPosition = @getEofPosition()
if position.isGreaterThan(eofPosition)
eofPosition
else
row = Math.max(position.row, 0)
column = Math.max(position.column, 0)
column = Math.min(@lineLengthForRow(row), column)
new Point(row, column)
@text.clipPosition(position)
# Given a range, this clips it to a real range.
#
@@ -414,22 +398,18 @@ class TextBuffer
isEmpty: -> @text.isEmpty()
# Returns all valid {BufferMarker}s on the buffer.
getMarkers: ({includeInvalid} = {}) ->
markers = _.values(@validMarkers)
if includeInvalid
markers.concat(_.values(@invalidMarkers))
else
markers
getMarkers: ->
@text.getMarkers()
# Returns the {BufferMarker} with the given id.
getMarker: (id) ->
@validMarkers[id]
@text.getMarker(id)
# Public: Finds the first marker satisfying the given attributes
#
# Returns a {String} marker-identifier
findMarker: (attributes) ->
@findMarkers(attributes)[0]
@text.findMarker(attributes)
# Public: Finds all markers satisfying the given attributes
#
@@ -440,14 +420,13 @@ class TextBuffer
#
# Returns an {Array} of {BufferMarker}s
findMarkers: (attributes) ->
markers = @getMarkers().filter (marker) -> marker.matchesAttributes(attributes)
markers.sort (a, b) -> a.getRange().compare(b.getRange())
@text.findMarkers(attributes)
# Retrieves the quantity of markers in a buffer.
#
# Returns a {Number}.
getMarkerCount: ->
_.size(@validMarkers)
@text.getMarkers().length
# Constructs a new marker at a given range.
#
@@ -456,23 +435,12 @@ class TextBuffer
# Any attributes you pass will be associated with the marker and can be retrieved
# or used in marker queries.
# The following attribute keys reserved, and control the marker's initial range
# reverse - if `true`, the marker is reversed; that is, its head precedes the tail
# noTail - if `true`, the marker is created without a tail
# isReversed - if `true`, the marker is reversed; that is, its head precedes the tail
# hasTail - if `false`, the marker is created without a tail
#
# Returns a {Number} representing the new marker's ID.
markRange: (range, attributes={}) ->
optionKeys = ['invalidationStrategy', 'noTail', 'reverse']
options = _.pick(attributes, optionKeys)
attributes = _.omit(attributes, optionKeys)
marker = new BufferMarker(_.defaults({
id: (@nextMarkerId++).toString()
buffer: this
range
attributes
}, options))
@validMarkers[marker.id] = marker
@trigger 'marker-created', marker
marker
@text.markRange(range, attributes)
# Constructs a new marker at a given position.
#
@@ -481,16 +449,7 @@ class TextBuffer
#
# Returns a {Number} representing the new marker's ID.
markPosition: (position, options) ->
@markRange([position, position], _.defaults({noTail: true}, options))
# Given a buffer position, this finds all markers that contain the position.
#
# bufferPosition - A {Point} to check
#
# Returns an {Array} of {Numbers}, representing marker IDs containing `bufferPosition`.
markersForPosition: (position) ->
position = Point.fromObject(position)
@getMarkers().filter (marker) -> marker.containsPoint(position)
@text.markPosition(position, options)
# Identifies if a character sequence is within a certain range.
#
@@ -662,9 +621,7 @@ class TextBuffer
change: (oldRange, newText, options={}) ->
oldRange = @clipRange(oldRange)
newText = @normalizeLineEndings(oldRange.start.row, newText) if options.normalizeLineEndings ? true
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
range = @pushOperation(operation)
range
@text.change(oldRange, newText, options)
normalizeLineEndings: (startRow, text) ->
if lineEnding = @suggestedLineEndingForRow(startRow)
@@ -672,11 +629,6 @@ class TextBuffer
else
text
destroyMarker: (id) ->
if marker = @validMarkers[id] ? @invalidMarkers[id]
delete @validMarkers[id]
delete @invalidMarkers[id]
scheduleModifiedEvents: ->
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
stoppedChangingCallback = =>

View File

@@ -203,7 +203,7 @@ module.exports =
return if selection.isEmpty()
range = selection.getBufferRange()
options = reverse: selection.isReversed()
options = isReversed: selection.isReversed()
selection.insertText("#{bracket}#{selection.getText()}#{pair}")
selectionStart = range.start.add([0, 1])
if range.start.row is range.end.row

View File

@@ -23,8 +23,8 @@ class SnippetExpansion
@editSession.normalizeTabsInBufferRange(newRange)
@indentSubsequentLines(startPosition.row, snippet) if snippet.lineCount > 1
cursorMoved: ({oldBufferPosition, newBufferPosition, bufferChanged}) ->
return if @settingTabStop or bufferChanged
cursorMoved: ({oldBufferPosition, newBufferPosition, textChanged}) ->
return if @settingTabStop or textChanged
oldTabStops = @tabStopsForBufferPosition(oldBufferPosition)
newTabStops = @tabStopsForBufferPosition(newBufferPosition)
@destroy() unless _.intersection(oldTabStops, newTabStops).length