mirror of
https://github.com/atom/atom.git
synced 2026-02-05 04:05:05 -05:00
This prevents the editor from synchronously redrawing in specs with cursors in invalid locations. Markers are always updated under the hood before a change event is emitted from the Buffer or DisplayBuffer. We still wait to trigger marker observers, but if their position gets read, it is up to date.
771 lines
37 KiB
CoffeeScript
771 lines
37 KiB
CoffeeScript
DisplayBuffer = require 'display-buffer'
|
|
Buffer = require 'buffer'
|
|
|
|
describe "DisplayBuffer", ->
|
|
[editSession, displayBuffer, buffer, changeHandler, tabLength] = []
|
|
beforeEach ->
|
|
tabLength = 2
|
|
editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabLength })
|
|
{ buffer, displayBuffer } = editSession
|
|
changeHandler = jasmine.createSpy 'changeHandler'
|
|
displayBuffer.on 'changed', changeHandler
|
|
|
|
afterEach ->
|
|
editSession.destroy()
|
|
|
|
describe "when the buffer changes", ->
|
|
it "renders line numbers correctly", ->
|
|
originalLineCount = displayBuffer.lineCount()
|
|
oneHundredLines = [0..100].join("\n")
|
|
buffer.insert([0,0], oneHundredLines)
|
|
expect(displayBuffer.lineCount()).toBe 100 + originalLineCount
|
|
|
|
describe "soft wrapping", ->
|
|
beforeEach ->
|
|
displayBuffer.setSoftWrapColumn(50)
|
|
changeHandler.reset()
|
|
|
|
describe "rendering of soft-wrapped lines", ->
|
|
describe "when the line is shorter than the max line length", ->
|
|
it "renders the line unchanged", ->
|
|
expect(displayBuffer.lineForRow(0).text).toBe buffer.lineForRow(0)
|
|
|
|
describe "when the line is empty", ->
|
|
it "renders the empty line", ->
|
|
expect(displayBuffer.lineForRow(13).text).toBe ''
|
|
|
|
describe "when there is a non-whitespace character at the max length boundary", ->
|
|
describe "when there is whitespace before the boundary", ->
|
|
it "wraps the line at the end of the first whitespace preceding the boundary", ->
|
|
expect(displayBuffer.lineForRow(10).text).toBe ' return '
|
|
expect(displayBuffer.lineForRow(11).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
|
|
|
|
describe "when there is no whitespace before the boundary", ->
|
|
it "wraps the line exactly at the boundary since there's no more graceful place to wrap it", ->
|
|
buffer.change([[0, 0], [1, 0]], 'abcdefghijklmnopqrstuvwxyz\n')
|
|
displayBuffer.setSoftWrapColumn(10)
|
|
expect(displayBuffer.lineForRow(0).text).toBe 'abcdefghij'
|
|
expect(displayBuffer.lineForRow(1).text).toBe 'klmnopqrst'
|
|
expect(displayBuffer.lineForRow(2).text).toBe 'uvwxyz'
|
|
|
|
describe "when there is a whitespace character at the max length boundary", ->
|
|
it "wraps the line at the first non-whitespace character following the boundary", ->
|
|
expect(displayBuffer.lineForRow(3).text).toBe ' var pivot = items.shift(), current, left = [], '
|
|
expect(displayBuffer.lineForRow(4).text).toBe 'right = [];'
|
|
|
|
describe "when the buffer changes", ->
|
|
describe "when buffer lines are updated", ->
|
|
describe "when the update makes a soft-wrapped line shorter than the max line length", ->
|
|
it "rewraps the line and emits a change event", ->
|
|
buffer.delete([[6, 24], [6, 42]])
|
|
expect(displayBuffer.lineForRow(7).text).toBe ' current < pivot ? : right.push(current);'
|
|
expect(displayBuffer.lineForRow(8).text).toBe ' }'
|
|
|
|
expect(changeHandler).toHaveBeenCalled()
|
|
[[event]]= changeHandler.argsForCall
|
|
|
|
expect(event).toEqual(start: 7, end: 8, screenDelta: -1, bufferDelta: 0)
|
|
|
|
describe "when the update causes a line to softwrap an additional time", ->
|
|
it "rewraps the line and emits a change event", ->
|
|
buffer.insert([6, 28], '1234567890')
|
|
expect(displayBuffer.lineForRow(7).text).toBe ' current < pivot ? '
|
|
expect(displayBuffer.lineForRow(8).text).toBe 'left1234567890.push(current) : '
|
|
expect(displayBuffer.lineForRow(9).text).toBe 'right.push(current);'
|
|
expect(displayBuffer.lineForRow(10).text).toBe ' }'
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, screenDelta: 1, bufferDelta: 0)
|
|
|
|
describe "when buffer lines are inserted", ->
|
|
it "inserts / updates wrapped lines and emits a change event", ->
|
|
buffer.insert([6, 21], '1234567890 abcdefghij 1234567890\nabcdefghij')
|
|
expect(displayBuffer.lineForRow(7).text).toBe ' current < pivot1234567890 abcdefghij '
|
|
expect(displayBuffer.lineForRow(8).text).toBe '1234567890'
|
|
expect(displayBuffer.lineForRow(9).text).toBe 'abcdefghij ? left.push(current) : '
|
|
expect(displayBuffer.lineForRow(10).text).toBe 'right.push(current);'
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 7, end: 8, screenDelta: 2, bufferDelta: 1)
|
|
|
|
describe "when buffer lines are removed", ->
|
|
it "removes lines and emits a change event", ->
|
|
buffer.change([[3, 21], [7, 5]], ';')
|
|
expect(displayBuffer.lineForRow(3).text).toBe ' var pivot = items;'
|
|
expect(displayBuffer.lineForRow(4).text).toBe ' return '
|
|
expect(displayBuffer.lineForRow(5).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
|
|
expect(displayBuffer.lineForRow(6).text).toBe ' };'
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 9, screenDelta: -6, bufferDelta: -4)
|
|
|
|
describe "position translation", ->
|
|
it "translates positions accounting for wrapped lines", ->
|
|
# before any wrapped lines
|
|
expect(displayBuffer.screenPositionForBufferPosition([0, 5])).toEqual([0, 5])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([0, 5])).toEqual([0, 5])
|
|
expect(displayBuffer.screenPositionForBufferPosition([0, 29])).toEqual([0, 29])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([0, 29])).toEqual([0, 29])
|
|
|
|
# on a wrapped line
|
|
expect(displayBuffer.screenPositionForBufferPosition([3, 5])).toEqual([3, 5])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([3, 5])).toEqual([3, 5])
|
|
expect(displayBuffer.screenPositionForBufferPosition([3, 50])).toEqual([3, 50])
|
|
expect(displayBuffer.screenPositionForBufferPosition([3, 51])).toEqual([3, 50])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([4, 0])).toEqual([3, 51])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([3, 50])).toEqual([3, 50])
|
|
expect(displayBuffer.screenPositionForBufferPosition([3, 62])).toEqual([4, 11])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([4, 11])).toEqual([3, 62])
|
|
|
|
# following a wrapped line
|
|
expect(displayBuffer.screenPositionForBufferPosition([4, 5])).toEqual([5, 5])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([5, 5])).toEqual([4, 5])
|
|
|
|
# clip screen position inputs before translating
|
|
expect(displayBuffer.bufferPositionForScreenPosition([-5, -5])).toEqual([0, 0])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([Infinity, Infinity])).toEqual([12, 2])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([3, -5])).toEqual([3, 0])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([3, Infinity])).toEqual([3, 50])
|
|
|
|
describe ".setSoftWrapColumn(length)", ->
|
|
it "changes the length at which lines are wrapped and emits a change event for all screen lines", ->
|
|
displayBuffer.setSoftWrapColumn(40)
|
|
expect(tokensText displayBuffer.lineForRow(4).tokens).toBe 'left = [], right = [];'
|
|
expect(tokensText displayBuffer.lineForRow(5).tokens).toBe ' while(items.length > 0) {'
|
|
expect(tokensText displayBuffer.lineForRow(12).tokens).toBe 'sort(left).concat(pivot).concat(sort(rig'
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 0, end: 15, screenDelta: 3, bufferDelta: 0)
|
|
|
|
describe "structural folding", ->
|
|
describe ".unfoldAll()", ->
|
|
it "unfolds every folded line", ->
|
|
displayBuffer.foldBufferRow(0)
|
|
displayBuffer.foldBufferRow(1)
|
|
|
|
displayBuffer.unfoldAll()
|
|
expect(Object.keys(displayBuffer.activeFolds).length).toBe 0
|
|
|
|
describe ".foldAll()", ->
|
|
it "folds every foldable line", ->
|
|
displayBuffer.foldAll()
|
|
fold = displayBuffer.lineForRow(0).fold
|
|
expect(fold).toBeDefined()
|
|
expect([fold.startRow, fold.endRow]).toEqual [0,12]
|
|
|
|
expect(Object.keys(displayBuffer.activeFolds).length).toBe(3)
|
|
expect(displayBuffer.activeFolds[1].length).toBe(1)
|
|
expect(displayBuffer.activeFolds[4].length).toBe(1)
|
|
|
|
it "doesn't fold lines that are already folded", ->
|
|
displayBuffer.foldBufferRow(4)
|
|
displayBuffer.foldAll()
|
|
expect(Object.keys(displayBuffer.activeFolds).length).toBe(3)
|
|
expect(displayBuffer.activeFolds[0].length).toBe(1)
|
|
expect(displayBuffer.activeFolds[1].length).toBe(1)
|
|
expect(displayBuffer.activeFolds[4].length).toBe(1)
|
|
|
|
describe ".foldBufferRow(bufferRow)", ->
|
|
describe "when bufferRow can be folded", ->
|
|
it "creates a fold based on the syntactic region starting at the given row", ->
|
|
displayBuffer.foldBufferRow(1)
|
|
fold = displayBuffer.lineForRow(1).fold
|
|
expect(fold.startRow).toBe 1
|
|
expect(fold.endRow).toBe 9
|
|
|
|
describe "when bufferRow can't be folded", ->
|
|
it "searches upward for the first row that begins a syntatic region containing the given buffer row (and folds it)", ->
|
|
displayBuffer.foldBufferRow(8)
|
|
fold = displayBuffer.lineForRow(1).fold
|
|
expect(fold.startRow).toBe 1
|
|
expect(fold.endRow).toBe 9
|
|
|
|
describe "when the bufferRow is already folded", ->
|
|
it "searches upward for the first row that begins a syntatic region containing the folded row (and folds it)", ->
|
|
displayBuffer.foldBufferRow(2)
|
|
expect(displayBuffer.lineForRow(1).fold).toBeDefined()
|
|
expect(displayBuffer.lineForRow(0).fold).not.toBeDefined()
|
|
|
|
displayBuffer.foldBufferRow(1)
|
|
expect(displayBuffer.lineForRow(0).fold).toBeDefined()
|
|
|
|
describe ".unfoldBufferRow(bufferRow)", ->
|
|
describe "when bufferRow can be unfolded", ->
|
|
it "destroys a fold based on the syntactic region starting at the given row", ->
|
|
displayBuffer.foldBufferRow(1)
|
|
expect(displayBuffer.lineForRow(1).fold).toBeDefined()
|
|
|
|
displayBuffer.unfoldBufferRow(1)
|
|
expect(displayBuffer.lineForRow(1).fold).toBeUndefined()
|
|
|
|
describe "when bufferRow can't be unfolded", ->
|
|
it "does not throw an error", ->
|
|
expect(displayBuffer.lineForRow(1).fold).toBeUndefined()
|
|
displayBuffer.unfoldBufferRow(1)
|
|
expect(displayBuffer.lineForRow(1).fold).toBeUndefined()
|
|
|
|
describe "primitive folding", ->
|
|
editSession2 = null
|
|
|
|
beforeEach ->
|
|
editSession2 = fixturesProject.buildEditSessionForPath('two-hundred.txt')
|
|
{ buffer, displayBuffer } = editSession2
|
|
displayBuffer.on 'changed', changeHandler
|
|
|
|
afterEach ->
|
|
editSession2.destroy()
|
|
|
|
describe "when folds are created and destroyed", ->
|
|
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)
|
|
|
|
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
|
expect(line4.fold).toBe fold
|
|
expect(line4.text).toMatch /^4-+/
|
|
expect(line4.bufferRows).toBe 4
|
|
expect(line5.text).toBe '8'
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 7, screenDelta: -3, bufferDelta: 0)
|
|
changeHandler.reset()
|
|
|
|
fold.destroy()
|
|
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
|
expect(line4.fold).toBeUndefined()
|
|
expect(line4.text).toMatch /^4-+/
|
|
expect(line4.bufferRows).toEqual 1
|
|
expect(line5.text).toBe '5'
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 4, screenDelta: 3, bufferDelta: 0)
|
|
|
|
describe "when a fold spans a single line", ->
|
|
it "renders a fold placeholder for the folded line but does not skip any lines", ->
|
|
fold = displayBuffer.createFold(4, 4)
|
|
|
|
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
|
expect(line4.fold).toBe fold
|
|
expect(line4.text).toMatch /^4-+/
|
|
expect(line4.bufferRows).toEqual 1
|
|
expect(line5.text).toBe '5'
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 4, screenDelta: 0, bufferDelta: 0)
|
|
|
|
# Line numbers don't actually change, but it's not worth the complexity to have this
|
|
# be false for single line folds since they are so rare
|
|
changeHandler.reset()
|
|
|
|
fold.destroy()
|
|
|
|
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
|
expect(line4.fold).toBeUndefined()
|
|
expect(line4.text).toMatch /^4-+/
|
|
expect(line4.bufferRows).toEqual 1
|
|
expect(line5.text).toBe '5'
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 4, screenDelta: 0, bufferDelta: 0)
|
|
|
|
describe "when a fold is nested within another fold", ->
|
|
it "does not render the placeholder for the inner fold until the outer fold is destroyed", ->
|
|
innerFold = displayBuffer.createFold(6, 7)
|
|
outerFold = displayBuffer.createFold(4, 8)
|
|
|
|
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
|
expect(line4.fold).toBe outerFold
|
|
expect(line4.text).toMatch /4-+/
|
|
expect(line4.bufferRows).toEqual 5
|
|
expect(line5.text).toMatch /9-+/
|
|
|
|
outerFold.destroy()
|
|
|
|
[line4, line5, line6, line7] = displayBuffer.linesForRows(4, 7)
|
|
expect(line4.fold).toBeUndefined()
|
|
expect(line4.text).toMatch /^4-+/
|
|
expect(line4.bufferRows).toEqual 1
|
|
expect(line5.text).toBe '5'
|
|
expect(line6.fold).toBe innerFold
|
|
expect(line6.text).toBe '6'
|
|
expect(line6.bufferRows).toEqual 2
|
|
expect(line7.text).toBe '8'
|
|
|
|
it "allows the outer fold to start at the same location as the inner fold", ->
|
|
innerFold = displayBuffer.createFold(4, 6)
|
|
outerFold = displayBuffer.createFold(4, 8)
|
|
|
|
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
|
expect(line4.fold).toBe outerFold
|
|
expect(line4.text).toMatch /4-+/
|
|
expect(line4.bufferRows).toEqual 5
|
|
expect(line5.text).toMatch /9-+/
|
|
|
|
describe "when creating a fold where one already exists", ->
|
|
it "returns existing fold and does't create new fold", ->
|
|
fold = displayBuffer.createFold(0,10)
|
|
expect(displayBuffer.activeFolds[0].length).toBe 1
|
|
|
|
newFold = displayBuffer.createFold(0,10)
|
|
expect(newFold).toBe fold
|
|
expect(displayBuffer.activeFolds[0].length).toBe 1
|
|
|
|
describe "when a fold is created inside an existing folded region", ->
|
|
it "creates/destroys the fold, but does not trigger change event", ->
|
|
outerFold = displayBuffer.createFold(0, 10)
|
|
changeHandler.reset()
|
|
|
|
innerFold = displayBuffer.createFold(2, 5)
|
|
expect(changeHandler).not.toHaveBeenCalled()
|
|
[line0, line1] = displayBuffer.linesForRows(0, 1)
|
|
expect(line0.fold).toBe outerFold
|
|
expect(line1.fold).toBeUndefined()
|
|
|
|
changeHandler.reset()
|
|
innerFold.destroy()
|
|
expect(changeHandler).not.toHaveBeenCalled()
|
|
[line0, line1] = displayBuffer.linesForRows(0, 1)
|
|
expect(line0.fold).toBe outerFold
|
|
expect(line1.fold).toBeUndefined()
|
|
|
|
describe "when the buffer changes", ->
|
|
[fold1, fold2] = []
|
|
beforeEach ->
|
|
fold1 = displayBuffer.createFold(2, 4)
|
|
fold2 = displayBuffer.createFold(6, 8)
|
|
changeHandler.reset()
|
|
|
|
describe "when the old range surrounds a fold", ->
|
|
it "removes the fold and replaces the selection with the new text", ->
|
|
buffer.change([[1, 0], [5, 1]], 'party!')
|
|
|
|
expect(displayBuffer.lineForRow(0).text).toBe "0"
|
|
expect(displayBuffer.lineForRow(1).text).toBe "party!"
|
|
expect(displayBuffer.lineForRow(2).fold).toBe fold2
|
|
expect(displayBuffer.lineForRow(3).text).toMatch /^9-+/
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 3, screenDelta: -2, bufferDelta: -4)
|
|
|
|
describe "when the old range surrounds two nested folds", ->
|
|
it "removes both folds and replaces the selection with the new text", ->
|
|
displayBuffer.createFold(2, 9)
|
|
changeHandler.reset()
|
|
|
|
buffer.change([[1, 0], [10, 0]], 'goodbye')
|
|
|
|
expect(displayBuffer.lineForRow(0).text).toBe "0"
|
|
expect(displayBuffer.lineForRow(1).text).toBe "goodbye10"
|
|
expect(displayBuffer.lineForRow(2).text).toBe "11"
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 3, screenDelta: -2, bufferDelta: -9)
|
|
|
|
describe "when multiple changes happen above the fold", ->
|
|
it "repositions folds correctly", ->
|
|
buffer.delete([[1, 1], [2, 0]])
|
|
buffer.insert([0, 1], "\nnew")
|
|
|
|
expect(fold1.startRow).toBe 2
|
|
expect(fold1.endRow).toBe 4
|
|
|
|
describe "when the old range precedes lines with a fold", ->
|
|
describe "when the new range precedes lines with a fold", ->
|
|
it "updates the buffer and re-positions subsequent folds", ->
|
|
buffer.change([[0, 0], [1, 1]], 'abc')
|
|
|
|
expect(displayBuffer.lineForRow(0).text).toBe "abc"
|
|
expect(displayBuffer.lineForRow(1).fold).toBe fold1
|
|
expect(displayBuffer.lineForRow(2).text).toBe "5"
|
|
expect(displayBuffer.lineForRow(3).fold).toBe fold2
|
|
expect(displayBuffer.lineForRow(4).text).toMatch /^9-+/
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 0, end: 1, screenDelta: -1, bufferDelta: -1)
|
|
changeHandler.reset()
|
|
|
|
fold1.destroy()
|
|
expect(displayBuffer.lineForRow(0).text).toBe "abc"
|
|
expect(displayBuffer.lineForRow(1).text).toBe "2"
|
|
expect(displayBuffer.lineForRow(3).text).toMatch /^4-+/
|
|
expect(displayBuffer.lineForRow(4).text).toBe "5"
|
|
expect(displayBuffer.lineForRow(5).fold).toBe fold2
|
|
expect(displayBuffer.lineForRow(6).text).toMatch /^9-+/
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 1, end: 1, screenDelta: 2, bufferDelta: 0)
|
|
|
|
describe "when the old range straddles the beginning of a fold", ->
|
|
it "replaces lines in the portion of the range that precedes the fold and adjusts the end of the fold to encompass additional lines", ->
|
|
buffer.change([[1, 1], [3, 0]], "a\nb\nc\nd\n")
|
|
|
|
expect(fold1.startRow).toBe 2
|
|
expect(fold1.endRow).toBe 6
|
|
|
|
expect(displayBuffer.lineForRow(1).text).toBe '1a'
|
|
expect(displayBuffer.lineForRow(2).text).toBe 'b'
|
|
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
|
|
|
describe "when the old range follows a fold", ->
|
|
it "re-positions the screen ranges for the change event based on the preceding fold", ->
|
|
buffer.change([[10, 0], [11, 0]], 'abc')
|
|
|
|
expect(displayBuffer.lineForRow(1).text).toBe "1"
|
|
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
|
expect(displayBuffer.lineForRow(3).text).toBe "5"
|
|
expect(displayBuffer.lineForRow(4).fold).toBe fold2
|
|
expect(displayBuffer.lineForRow(5).text).toMatch /^9-+/
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 6, end: 7, screenDelta: -1, bufferDelta: -1)
|
|
|
|
describe "when the old range is inside a fold", ->
|
|
describe "when the end of the new range precedes the end of the fold", ->
|
|
it "updates the fold and ensures the change is present when the fold is destroyed", ->
|
|
buffer.insert([3, 0], '\n')
|
|
expect(fold1.startRow).toBe 2
|
|
expect(fold1.endRow).toBe 5
|
|
|
|
expect(displayBuffer.lineForRow(1).text).toBe "1"
|
|
expect(displayBuffer.lineForRow(2).text).toBe "2"
|
|
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
|
expect(displayBuffer.lineForRow(2).bufferRows).toEqual 4
|
|
expect(displayBuffer.lineForRow(3).text).toMatch "5"
|
|
expect(displayBuffer.lineForRow(4).fold).toBe fold2
|
|
expect(displayBuffer.lineForRow(5).text).toMatch /^9-+/
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 2, screenDelta: 0, bufferDelta: 1)
|
|
|
|
describe "when the end of the new range exceeds the end of the fold", ->
|
|
it "expands the fold to contain all the inserted lines", ->
|
|
buffer.change([[3, 0], [4, 0]], 'a\nb\nc\nd\n')
|
|
expect(fold1.startRow).toBe 2
|
|
expect(fold1.endRow).toBe 7
|
|
|
|
expect(displayBuffer.lineForRow(1).text).toBe "1"
|
|
expect(displayBuffer.lineForRow(2).text).toBe "2"
|
|
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
|
expect(displayBuffer.lineForRow(2).bufferRows).toEqual 6
|
|
expect(displayBuffer.lineForRow(3).text).toMatch "5"
|
|
expect(displayBuffer.lineForRow(4).fold).toBe fold2
|
|
expect(displayBuffer.lineForRow(5).text).toMatch /^9-+/
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 2, screenDelta: 0, bufferDelta: 3)
|
|
|
|
describe "when the old range straddles the end of the fold", ->
|
|
describe "when the end of the new range precedes the end of the fold", ->
|
|
it "shortens the fold so its end matches the end of the new range", ->
|
|
fold2.destroy()
|
|
buffer.change([[3, 0], [6, 0]], 'a\n')
|
|
|
|
expect(fold1.startRow).toBe 2
|
|
expect(fold1.endRow).toBe 4
|
|
|
|
describe "when the old range is contained to a single line in-between two folds", ->
|
|
it "re-renders the line with the placeholder and re-positions the second fold", ->
|
|
buffer.insert([5, 0], 'abc\n')
|
|
|
|
expect(displayBuffer.lineForRow(1).text).toBe "1"
|
|
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
|
expect(displayBuffer.lineForRow(3).text).toMatch "abc"
|
|
expect(displayBuffer.lineForRow(4).text).toBe "5"
|
|
expect(displayBuffer.lineForRow(5).fold).toBe fold2
|
|
expect(displayBuffer.lineForRow(6).text).toMatch /^9-+/
|
|
|
|
expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 3, screenDelta: 1, bufferDelta: 1)
|
|
|
|
describe "position translation", ->
|
|
it "translates positions to account for folded lines and characters and the placeholder", ->
|
|
displayBuffer.createFold(4, 7)
|
|
|
|
# preceding fold: identity
|
|
expect(displayBuffer.screenPositionForBufferPosition([3, 0])).toEqual [3, 0]
|
|
expect(displayBuffer.screenPositionForBufferPosition([4, 0])).toEqual [4, 0]
|
|
|
|
expect(displayBuffer.bufferPositionForScreenPosition([3, 0])).toEqual [3, 0]
|
|
expect(displayBuffer.bufferPositionForScreenPosition([4, 0])).toEqual [4, 0]
|
|
|
|
# inside of fold: translate to the start of the fold
|
|
expect(displayBuffer.screenPositionForBufferPosition([4, 35])).toEqual [4, 0]
|
|
expect(displayBuffer.screenPositionForBufferPosition([5, 5])).toEqual [4, 0]
|
|
|
|
# following fold
|
|
expect(displayBuffer.screenPositionForBufferPosition([8, 0])).toEqual [5, 0]
|
|
expect(displayBuffer.screenPositionForBufferPosition([11, 2])).toEqual [8, 2]
|
|
|
|
expect(displayBuffer.bufferPositionForScreenPosition([5, 0])).toEqual [8, 0]
|
|
expect(displayBuffer.bufferPositionForScreenPosition([9, 2])).toEqual [12, 2]
|
|
|
|
# clip screen positions before translating
|
|
expect(displayBuffer.bufferPositionForScreenPosition([-5, -5])).toEqual([0, 0])
|
|
expect(displayBuffer.bufferPositionForScreenPosition([Infinity, Infinity])).toEqual([200, 0])
|
|
|
|
describe ".destroyFoldsContainingBufferRow(row)", ->
|
|
it "destroys all folds containing the given row", ->
|
|
displayBuffer.createFold(2, 4)
|
|
displayBuffer.createFold(2, 6)
|
|
displayBuffer.createFold(7, 8)
|
|
displayBuffer.createFold(1, 9)
|
|
displayBuffer.createFold(11, 12)
|
|
|
|
expect(displayBuffer.lineForRow(1).text).toBe '1'
|
|
expect(displayBuffer.lineForRow(2).text).toBe '10'
|
|
|
|
displayBuffer.destroyFoldsContainingBufferRow(2)
|
|
expect(displayBuffer.lineForRow(1).text).toBe '1'
|
|
expect(displayBuffer.lineForRow(2).text).toBe '2'
|
|
expect(displayBuffer.lineForRow(7).fold).toBeDefined()
|
|
expect(displayBuffer.lineForRow(8).text).toMatch /^9-+/
|
|
expect(displayBuffer.lineForRow(10).fold).toBeDefined()
|
|
|
|
describe "when the line being deleted preceeds a fold", ->
|
|
describe "when the command is undone", ->
|
|
it "restores the line and preserves the fold", ->
|
|
editSession.setCursorBufferPosition([4])
|
|
editSession.foldCurrentRow()
|
|
expect(editSession.isFoldedAtScreenRow(4)).toBeTruthy()
|
|
editSession.setCursorBufferPosition([3])
|
|
editSession.deleteLine()
|
|
expect(editSession.isFoldedAtScreenRow(3)).toBeTruthy()
|
|
expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {'
|
|
editSession.undo()
|
|
expect(editSession.isFoldedAtScreenRow(4)).toBeTruthy()
|
|
expect(buffer.lineForRow(3)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
|
|
|
|
describe ".clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", ->
|
|
beforeEach ->
|
|
displayBuffer.setSoftWrapColumn(50)
|
|
|
|
it "allows valid positions", ->
|
|
expect(displayBuffer.clipScreenPosition([4, 5])).toEqual [4, 5]
|
|
expect(displayBuffer.clipScreenPosition([4, 11])).toEqual [4, 11]
|
|
|
|
it "disallows negative positions", ->
|
|
expect(displayBuffer.clipScreenPosition([-1, -1])).toEqual [0, 0]
|
|
expect(displayBuffer.clipScreenPosition([-1, 10])).toEqual [0, 0]
|
|
expect(displayBuffer.clipScreenPosition([0, -1])).toEqual [0, 0]
|
|
|
|
it "disallows positions beyond the last row", ->
|
|
expect(displayBuffer.clipScreenPosition([1000, 0])).toEqual [15, 2]
|
|
expect(displayBuffer.clipScreenPosition([1000, 1000])).toEqual [15, 2]
|
|
|
|
describe "when wrapBeyondNewlines is false (the default)", ->
|
|
it "wraps positions beyond the end of hard newlines to the end of the line", ->
|
|
expect(displayBuffer.clipScreenPosition([1, 10000])).toEqual [1, 30]
|
|
expect(displayBuffer.clipScreenPosition([4, 30])).toEqual [4, 11]
|
|
expect(displayBuffer.clipScreenPosition([4, 1000])).toEqual [4, 11]
|
|
|
|
describe "when wrapBeyondNewlines is true", ->
|
|
it "wraps positions past the end of hard newlines to the next line", ->
|
|
expect(displayBuffer.clipScreenPosition([0, 29], wrapBeyondNewlines: true)).toEqual [0, 29]
|
|
expect(displayBuffer.clipScreenPosition([0, 30], wrapBeyondNewlines: true)).toEqual [1, 0]
|
|
expect(displayBuffer.clipScreenPosition([0, 1000], wrapBeyondNewlines: true)).toEqual [1, 0]
|
|
|
|
it "wraps positions in the middle of fold lines to the next screen line", ->
|
|
displayBuffer.createFold(3, 5)
|
|
expect(displayBuffer.clipScreenPosition([3, 5], wrapBeyondNewlines: true)).toEqual [4, 0]
|
|
|
|
describe "when wrapAtSoftNewlines is false (the default)", ->
|
|
it "clips positions at the end of soft-wrapped lines to the character preceding the end of the line", ->
|
|
expect(displayBuffer.clipScreenPosition([3, 50])).toEqual [3, 50]
|
|
expect(displayBuffer.clipScreenPosition([3, 51])).toEqual [3, 50]
|
|
expect(displayBuffer.clipScreenPosition([3, 58])).toEqual [3, 50]
|
|
expect(displayBuffer.clipScreenPosition([3, 1000])).toEqual [3, 50]
|
|
|
|
describe "when wrapAtSoftNewlines is true", ->
|
|
it "wraps positions at the end of soft-wrapped lines to the next screen line", ->
|
|
expect(displayBuffer.clipScreenPosition([3, 50], wrapAtSoftNewlines: true)).toEqual [3, 50]
|
|
expect(displayBuffer.clipScreenPosition([3, 51], wrapAtSoftNewlines: true)).toEqual [4, 0]
|
|
expect(displayBuffer.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 0]
|
|
expect(displayBuffer.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 0]
|
|
|
|
describe "when skipAtomicTokens is false (the default)", ->
|
|
it "clips screen positions in the middle of atomic tab characters to the beginning of the character", ->
|
|
buffer.insert([0, 0], '\t')
|
|
expect(displayBuffer.clipScreenPosition([0, 0])).toEqual [0, 0]
|
|
expect(displayBuffer.clipScreenPosition([0, 1])).toEqual [0, 0]
|
|
expect(displayBuffer.clipScreenPosition([0, tabLength])).toEqual [0, tabLength]
|
|
|
|
describe "when skipAtomicTokens is true", ->
|
|
it "clips screen positions in the middle of atomic tab characters to the end of the character", ->
|
|
buffer.insert([0, 0], '\t')
|
|
expect(displayBuffer.clipScreenPosition([0, 0], skipAtomicTokens: true)).toEqual [0, 0]
|
|
expect(displayBuffer.clipScreenPosition([0, 1], skipAtomicTokens: true)).toEqual [0, tabLength]
|
|
expect(displayBuffer.clipScreenPosition([0, tabLength], skipAtomicTokens: true)).toEqual [0, tabLength]
|
|
|
|
describe "position translation in the presence of hard tabs", ->
|
|
it "correctly translates positions on either side of a tab", ->
|
|
buffer.setText('\t')
|
|
expect(displayBuffer.screenPositionForBufferPosition([0, 1])).toEqual [0, 2]
|
|
expect(displayBuffer.bufferPositionForScreenPosition([0, 2])).toEqual [0, 1]
|
|
|
|
describe ".maxLineLength()", ->
|
|
it "returns the length of the longest screen line", ->
|
|
expect(displayBuffer.maxLineLength()).toBe 65
|
|
|
|
describe "markers", ->
|
|
beforeEach ->
|
|
displayBuffer.foldBufferRow(4)
|
|
|
|
describe "marker creation and manipulation", ->
|
|
it "allows markers to be created in terms of both screen and buffer coordinates", ->
|
|
marker1 = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
|
marker2 = displayBuffer.markBufferRange([[8, 4], [8, 10]])
|
|
expect(displayBuffer.getMarkerBufferRange(marker1)).toEqual [[8, 4], [8, 10]]
|
|
expect(displayBuffer.getMarkerScreenRange(marker2)).toEqual [[5, 4], [5, 10]]
|
|
|
|
it "allows marker head and tail positions to be manipulated in both screen and buffer coordinates", ->
|
|
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
|
displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])
|
|
displayBuffer.setMarkerTailBufferPosition(marker, [5, 4])
|
|
expect(displayBuffer.isMarkerReversed(marker)).toBeFalsy()
|
|
expect(displayBuffer.getMarkerBufferRange(marker)).toEqual [[5, 4], [8, 4]]
|
|
|
|
displayBuffer.setMarkerHeadBufferPosition(marker, [5, 4])
|
|
displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])
|
|
expect(displayBuffer.isMarkerReversed(marker)).toBeTruthy()
|
|
expect(displayBuffer.getMarkerBufferRange(marker)).toEqual [[5, 4], [8, 4]]
|
|
|
|
it "returns whether a position changed when it is assigned", ->
|
|
marker = displayBuffer.markScreenRange([[0, 0], [0, 0]])
|
|
expect(displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])).toBeTruthy()
|
|
expect(displayBuffer.setMarkerHeadScreenPosition(marker, [5, 4])).toBeFalsy()
|
|
expect(displayBuffer.setMarkerHeadBufferPosition(marker, [1, 0])).toBeTruthy()
|
|
expect(displayBuffer.setMarkerHeadBufferPosition(marker, [1, 0])).toBeFalsy()
|
|
expect(displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])).toBeTruthy()
|
|
expect(displayBuffer.setMarkerTailScreenPosition(marker, [5, 4])).toBeFalsy()
|
|
expect(displayBuffer.setMarkerTailBufferPosition(marker, [1, 0])).toBeTruthy()
|
|
expect(displayBuffer.setMarkerTailBufferPosition(marker, [1, 0])).toBeFalsy()
|
|
|
|
describe ".observeMarker(marker, callback)", ->
|
|
[observeHandler, marker, subscription] = []
|
|
|
|
beforeEach ->
|
|
observeHandler = jasmine.createSpy("observeHandler")
|
|
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
|
subscription = displayBuffer.observeMarker(marker, observeHandler)
|
|
|
|
it "calls the callback whenever the markers head's screen position changes in the buffer or on screen", ->
|
|
displayBuffer.setMarkerHeadScreenPosition(marker, [8, 20])
|
|
expect(observeHandler).toHaveBeenCalled()
|
|
expect(observeHandler.argsForCall[0][0]).toEqual {
|
|
oldHeadScreenPosition: [5, 10]
|
|
oldHeadBufferPosition: [8, 10]
|
|
newHeadScreenPosition: [8, 20]
|
|
newHeadBufferPosition: [11, 20]
|
|
oldTailScreenPosition: [5, 4]
|
|
oldTailBufferPosition: [8, 4]
|
|
newTailScreenPosition: [5, 4]
|
|
newTailBufferPosition: [8, 4]
|
|
bufferChanged: false
|
|
}
|
|
observeHandler.reset()
|
|
|
|
buffer.insert([11, 0], '...')
|
|
expect(observeHandler).toHaveBeenCalled()
|
|
expect(observeHandler.argsForCall[0][0]).toEqual {
|
|
oldHeadScreenPosition: [8, 20]
|
|
oldHeadBufferPosition: [11, 20]
|
|
newHeadScreenPosition: [8, 23]
|
|
newHeadBufferPosition: [11, 23]
|
|
oldTailScreenPosition: [5, 4]
|
|
oldTailBufferPosition: [8, 4]
|
|
newTailScreenPosition: [5, 4]
|
|
newTailBufferPosition: [8, 4]
|
|
bufferChanged: true
|
|
}
|
|
observeHandler.reset()
|
|
|
|
displayBuffer.unfoldBufferRow(4)
|
|
expect(observeHandler).toHaveBeenCalled()
|
|
expect(observeHandler.argsForCall[0][0]).toEqual {
|
|
oldHeadScreenPosition: [8, 23]
|
|
oldHeadBufferPosition: [11, 23]
|
|
newHeadScreenPosition: [11, 23]
|
|
newHeadBufferPosition: [11, 23]
|
|
oldTailScreenPosition: [5, 4]
|
|
oldTailBufferPosition: [8, 4]
|
|
newTailScreenPosition: [8, 4]
|
|
newTailBufferPosition: [8, 4]
|
|
bufferChanged: false
|
|
}
|
|
observeHandler.reset()
|
|
|
|
displayBuffer.foldBufferRow(4)
|
|
expect(observeHandler).toHaveBeenCalled()
|
|
expect(observeHandler.argsForCall[0][0]).toEqual {
|
|
oldHeadScreenPosition: [11, 23]
|
|
oldHeadBufferPosition: [11, 23]
|
|
newHeadScreenPosition: [8, 23]
|
|
newHeadBufferPosition: [11, 23]
|
|
oldTailScreenPosition: [8, 4]
|
|
oldTailBufferPosition: [8, 4]
|
|
newTailScreenPosition: [5, 4]
|
|
newTailBufferPosition: [8, 4]
|
|
bufferChanged: false
|
|
}
|
|
|
|
it "calls the callback whenever the marker tail's position changes in the buffer or on screen", ->
|
|
displayBuffer.setMarkerTailScreenPosition(marker, [8, 20])
|
|
expect(observeHandler).toHaveBeenCalled()
|
|
expect(observeHandler.argsForCall[0][0]).toEqual {
|
|
oldHeadScreenPosition: [5, 10]
|
|
oldHeadBufferPosition: [8, 10]
|
|
newHeadScreenPosition: [5, 10]
|
|
newHeadBufferPosition: [8, 10]
|
|
oldTailScreenPosition: [5, 4]
|
|
oldTailBufferPosition: [8, 4]
|
|
newTailScreenPosition: [8, 20]
|
|
newTailBufferPosition: [11, 20]
|
|
bufferChanged: false
|
|
}
|
|
observeHandler.reset()
|
|
|
|
buffer.insert([11, 0], '...')
|
|
expect(observeHandler).toHaveBeenCalled()
|
|
expect(observeHandler.argsForCall[0][0]).toEqual {
|
|
oldHeadScreenPosition: [5, 10]
|
|
oldHeadBufferPosition: [8, 10]
|
|
newHeadScreenPosition: [5, 10]
|
|
newHeadBufferPosition: [8, 10]
|
|
oldTailScreenPosition: [8, 20]
|
|
oldTailBufferPosition: [11, 20]
|
|
newTailScreenPosition: [8, 23]
|
|
newTailBufferPosition: [11, 23]
|
|
bufferChanged: true
|
|
}
|
|
|
|
it "does not call the callback for screen changes that don't change the position of the marker", ->
|
|
displayBuffer.createFold(10, 11)
|
|
expect(observeHandler).not.toHaveBeenCalled()
|
|
|
|
it "allows observation subscriptions to be cancelled", ->
|
|
subscription.cancel()
|
|
displayBuffer.setMarkerHeadScreenPosition(marker, [8, 20])
|
|
displayBuffer.unfoldBufferRow(4)
|
|
expect(observeHandler).not.toHaveBeenCalled()
|
|
|
|
it "updates the position of markers before emitting buffer change events, but does not notify their observers until the change event", ->
|
|
changeHandler = jasmine.createSpy("changeHandler").andCallFake ->
|
|
# calls change handler first
|
|
expect(observeHandler).not.toHaveBeenCalled()
|
|
# but still updates the markers
|
|
expect(displayBuffer.getMarkerScreenRange(marker)).toEqual [[5, 7], [5, 13]]
|
|
expect(displayBuffer.getMarkerHeadScreenPosition(marker)).toEqual [5, 13]
|
|
expect(displayBuffer.getMarkerTailScreenPosition(marker)).toEqual [5, 7]
|
|
|
|
displayBuffer.on 'changed', changeHandler
|
|
|
|
buffer.insert([8, 1], "...")
|
|
|
|
expect(changeHandler).toHaveBeenCalled()
|
|
expect(observeHandler).toHaveBeenCalled()
|
|
|
|
it "updates the position of markers before emitting change events that aren't caused by a buffer change", ->
|
|
changeHandler = jasmine.createSpy("changeHandler").andCallFake ->
|
|
# calls change handler first
|
|
expect(observeHandler).not.toHaveBeenCalled()
|
|
# but still updates the markers
|
|
expect(displayBuffer.getMarkerScreenRange(marker)).toEqual [[8, 4], [8, 10]]
|
|
expect(displayBuffer.getMarkerHeadScreenPosition(marker)).toEqual [8, 10]
|
|
expect(displayBuffer.getMarkerTailScreenPosition(marker)).toEqual [8, 4]
|
|
displayBuffer.on 'changed', changeHandler
|
|
|
|
displayBuffer.unfoldBufferRow(4)
|
|
|
|
expect(changeHandler).toHaveBeenCalled()
|
|
expect(observeHandler).toHaveBeenCalled()
|
|
|
|
describe "marker destruction", ->
|
|
it "allows markers to be destroyed", ->
|
|
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
|
displayBuffer.destroyMarker(marker)
|
|
expect(displayBuffer.getMarkerBufferRange(marker)).toBeUndefined()
|