mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
DisplayBuffer specs passing with greatly simplified LineMap
This commit is contained in:
@@ -121,7 +121,7 @@ describe "DisplayBuffer", ->
|
||||
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([4, 0])
|
||||
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])
|
||||
@@ -245,8 +245,7 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBe fold
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferDelta).toEqual [4, 0]
|
||||
expect(line4.screenDelta).toEqual [1, 0]
|
||||
expect(line4.bufferRows).toBe 4
|
||||
expect(line5.text).toBe '8'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
@@ -260,8 +259,7 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBeUndefined()
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferDelta).toEqual [1, 0]
|
||||
expect(line4.screenDelta).toEqual [1, 0]
|
||||
expect(line4.bufferRows).toEqual 1
|
||||
expect(line5.text).toBe '5'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
@@ -277,8 +275,7 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBe fold
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferDelta).toEqual [1, 0]
|
||||
expect(line4.screenDelta).toEqual [1, 0]
|
||||
expect(line4.bufferRows).toEqual 1
|
||||
expect(line5.text).toBe '5'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
@@ -296,8 +293,7 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBeUndefined()
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferDelta).toEqual [1, 0]
|
||||
expect(line4.screenDelta).toEqual [1, 0]
|
||||
expect(line4.bufferRows).toEqual 1
|
||||
expect(line5.text).toBe '5'
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
@@ -315,8 +311,7 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBe outerFold
|
||||
expect(line4.text).toMatch /4-+/
|
||||
expect(line4.bufferDelta).toEqual [5, 0]
|
||||
expect(line4.screenDelta).toEqual [1, 0]
|
||||
expect(line4.bufferRows).toEqual 5
|
||||
expect(line5.text).toMatch /9-+/
|
||||
|
||||
outerFold.destroy()
|
||||
@@ -324,13 +319,11 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5, line6, line7] = displayBuffer.linesForRows(4, 7)
|
||||
expect(line4.fold).toBeUndefined()
|
||||
expect(line4.text).toMatch /^4-+/
|
||||
expect(line4.bufferDelta).toEqual [1, 0]
|
||||
expect(line4.screenDelta).toEqual [1, 0]
|
||||
expect(line4.bufferRows).toEqual 1
|
||||
expect(line5.text).toBe '5'
|
||||
expect(line6.fold).toBe innerFold
|
||||
expect(line6.text).toBe '6'
|
||||
expect(line6.bufferDelta).toEqual [2, 0]
|
||||
expect(line6.screenDelta).toEqual [1, 0]
|
||||
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", ->
|
||||
@@ -340,8 +333,7 @@ describe "DisplayBuffer", ->
|
||||
[line4, line5] = displayBuffer.linesForRows(4, 5)
|
||||
expect(line4.fold).toBe outerFold
|
||||
expect(line4.text).toMatch /4-+/
|
||||
expect(line4.bufferDelta).toEqual [5, 0]
|
||||
expect(line4.screenDelta).toEqual [1, 0]
|
||||
expect(line4.bufferRows).toEqual 5
|
||||
expect(line5.text).toMatch /9-+/
|
||||
|
||||
describe "when creating a fold where one already exists", ->
|
||||
@@ -487,7 +479,7 @@ describe "DisplayBuffer", ->
|
||||
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).bufferDelta).toEqual [4, 0]
|
||||
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-+/
|
||||
@@ -507,7 +499,7 @@ describe "DisplayBuffer", ->
|
||||
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).bufferDelta).toEqual [6, 0]
|
||||
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-+/
|
||||
|
||||
@@ -31,7 +31,7 @@ class DisplayBuffer
|
||||
|
||||
buildLineMap: ->
|
||||
@lineMap = new LineMap
|
||||
@lineMap.insertAtBufferRow 0, @buildLinesForBufferRows(0, @buffer.getLastRow())
|
||||
@lineMap.insertAtScreenRow 0, @buildLinesForBufferRows(0, @buffer.getLastRow())
|
||||
|
||||
setSoftWrapColumn: (@softWrapColumn) ->
|
||||
oldRange = @rangeForAllLines()
|
||||
@@ -223,16 +223,16 @@ class DisplayBuffer
|
||||
if fold = @largestFoldStartingAtBufferRow(currentBufferRow)
|
||||
screenLine = screenLine.copy()
|
||||
screenLine.fold = fold
|
||||
screenLine.bufferDelta = fold.getBufferDelta()
|
||||
screenLine.bufferRows = fold.getBufferRowCount()
|
||||
lineFragments.push(screenLine)
|
||||
currentBufferRow = fold.endRow + 1
|
||||
continue
|
||||
|
||||
startBufferColumn ?= 0
|
||||
screenLine = screenLine.splitAt(startBufferColumn)[1] if startBufferColumn > 0
|
||||
screenLine = screenLine.softWrapAt(startBufferColumn)[1] if startBufferColumn > 0
|
||||
wrapScreenColumn = @findWrapColumn(screenLine.text, @softWrapColumn)
|
||||
if wrapScreenColumn?
|
||||
screenLine = screenLine.splitAt(wrapScreenColumn)[0]
|
||||
screenLine = screenLine.softWrapAt(wrapScreenColumn)[0]
|
||||
screenLine.screenDelta = new Point(1, 0)
|
||||
startBufferColumn += wrapScreenColumn
|
||||
else
|
||||
|
||||
@@ -26,8 +26,8 @@ class Fold
|
||||
|
||||
new Range([@startRow, 0], end)
|
||||
|
||||
getBufferDelta: ->
|
||||
new Point(@endRow - @startRow + 1, 0)
|
||||
getBufferRowCount: ->
|
||||
@endRow - @startRow + 1
|
||||
|
||||
handleBufferChange: (event) ->
|
||||
oldStartRow = @startRow
|
||||
|
||||
@@ -1,71 +1,132 @@
|
||||
_ = require 'underscore'
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
class LineMap
|
||||
constructor: ->
|
||||
@lineFragments = []
|
||||
@screenLines = []
|
||||
|
||||
insertAtBufferRow: (bufferRow, lineFragments) ->
|
||||
@spliceAtBufferRow(bufferRow, 0, lineFragments)
|
||||
insertAtScreenRow: (bufferRow, screenLines) ->
|
||||
@spliceAtScreenRow(bufferRow, 0, screenLines)
|
||||
|
||||
spliceAtBufferRow: (startRow, rowCount, lineFragments) ->
|
||||
@spliceByDelta('bufferDelta', startRow, rowCount, lineFragments)
|
||||
replaceScreenRows: (start, end, screenLines) ->
|
||||
@spliceAtScreenRow(start, end - start + 1, screenLines)
|
||||
|
||||
spliceAtScreenRow: (startRow, rowCount, lineFragments) ->
|
||||
@spliceByDelta('screenDelta', startRow, rowCount, lineFragments)
|
||||
|
||||
replaceBufferRows: (start, end, lineFragments) ->
|
||||
@spliceAtBufferRow(start, end - start + 1, lineFragments)
|
||||
|
||||
replaceScreenRows: (start, end, lineFragments) ->
|
||||
@spliceAtScreenRow(start, end - start + 1, lineFragments)
|
||||
spliceAtScreenRow: (startRow, rowCount, screenLines) ->
|
||||
@screenLines.splice(startRow, rowCount, screenLines...)
|
||||
|
||||
lineForScreenRow: (row) ->
|
||||
@linesForScreenRows(row, row)[0]
|
||||
|
||||
linesForScreenRows: (startRow, endRow) ->
|
||||
@linesByDelta('screenDelta', startRow, endRow)
|
||||
|
||||
lineForBufferRow: (row) ->
|
||||
@linesForBufferRows(row, row)[0]
|
||||
|
||||
linesForBufferRows: (startRow, endRow) ->
|
||||
@linesByDelta('bufferDelta', startRow, endRow)
|
||||
@screenLines[startRow..endRow]
|
||||
|
||||
bufferRowsForScreenRows: (startRow, endRow=@lastScreenRow()) ->
|
||||
bufferRows = []
|
||||
currentScreenRow = -1
|
||||
@traverseByDelta 'screenDelta', [startRow, 0], [endRow, 0], ({ screenDelta, bufferDelta }) ->
|
||||
bufferRows.push(bufferDelta.row) if screenDelta.row > currentScreenRow
|
||||
currentScreenRow = screenDelta.row
|
||||
bufferRows
|
||||
|
||||
bufferLineCount: ->
|
||||
@lineCountByDelta('bufferDelta')
|
||||
[startRow..endRow]
|
||||
|
||||
screenLineCount: ->
|
||||
@lineCountByDelta('screenDelta')
|
||||
|
||||
lineCountByDelta: (deltaType) ->
|
||||
@traverseByDelta(deltaType, new Point(Infinity, 0))[deltaType].row
|
||||
@screenLines.length
|
||||
|
||||
lastScreenRow: ->
|
||||
@screenLineCount() - 1
|
||||
|
||||
maxScreenLineLength: ->
|
||||
maxLength = 0
|
||||
@traverseByDelta 'screenDelta', [0, 0], [@lastScreenRow(), 0], ({lineFragment}) ->
|
||||
length = lineFragment.text.length
|
||||
maxLength = length if length > maxLength
|
||||
for screenLine in @screenLines
|
||||
maxLength = Math.max(maxLength, screenLine.text.length)
|
||||
maxLength
|
||||
|
||||
screenPositionForBufferPosition: (bufferPosition, options) ->
|
||||
@translatePosition('bufferDelta', 'screenDelta', bufferPosition, options)
|
||||
clipScreenPosition: (screenPosition, options={}) ->
|
||||
{ wrapBeyondNewlines, wrapAtSoftNewlines } = options
|
||||
{ row, column } = Point.fromObject(screenPosition)
|
||||
|
||||
if row < 0
|
||||
row = 0
|
||||
column = 0
|
||||
else if row > @lastScreenRow()
|
||||
row = @lastScreenRow()
|
||||
column = Infinity
|
||||
else if column < 0
|
||||
column = 0
|
||||
|
||||
screenLine = @lineForScreenRow(row)
|
||||
maxScreenColumn = screenLine.getMaxScreenColumn()
|
||||
|
||||
if screenLine.isSoftWrapped() and column >= maxScreenColumn
|
||||
if wrapAtSoftNewlines
|
||||
row++
|
||||
column = 0
|
||||
else
|
||||
column = screenLine.clipScreenColumn(maxScreenColumn - 1)
|
||||
else if wrapBeyondNewlines and column > maxScreenColumn and row < @lastScreenRow()
|
||||
row++
|
||||
column = 0
|
||||
else
|
||||
column = screenLine.clipScreenColumn(column, options)
|
||||
new Point(row, column)
|
||||
|
||||
screenPositionForBufferPosition: (bufferPosition, options={}) ->
|
||||
{ wrapBeyondNewlines, wrapAtSoftNewlines } = options
|
||||
{ row, column } = Point.fromObject(bufferPosition)
|
||||
|
||||
[screenRow, screenLines] = @screenRowAndScreenLinesForBufferRow(row)
|
||||
|
||||
for screenLine in screenLines
|
||||
maxBufferColumn = screenLine.getMaxBufferColumn()
|
||||
if screenLine.isSoftWrapped()
|
||||
if column <= maxBufferColumn
|
||||
if column == maxBufferColumn
|
||||
if wrapAtSoftNewlines
|
||||
screenRow++
|
||||
screenColumn = 0
|
||||
else
|
||||
screenColumn = screenLine.screenColumnForBufferColumn(column - 1, options)
|
||||
else
|
||||
screenColumn = screenLine.screenColumnForBufferColumn(column, options)
|
||||
break
|
||||
else
|
||||
screenRow++
|
||||
else
|
||||
if wrapBeyondNewlines and column > maxBufferColumn and screenRow < @lastScreenRow()
|
||||
screenRow++
|
||||
screenColumn = 0
|
||||
else
|
||||
screenColumn = screenLine.screenColumnForBufferColumn(column)
|
||||
break
|
||||
|
||||
new Point(screenRow, screenColumn)
|
||||
|
||||
screenRowAndScreenLinesForBufferRow: (bufferRow) ->
|
||||
screenLines = []
|
||||
screenRow = 0
|
||||
currentBufferRow = 0
|
||||
for screenLine in @screenLines
|
||||
nextBufferRow = currentBufferRow + screenLine.bufferRows
|
||||
if currentBufferRow > bufferRow
|
||||
break
|
||||
else if currentBufferRow == bufferRow or currentBufferRow <= bufferRow < nextBufferRow
|
||||
screenLines.push(screenLine)
|
||||
else
|
||||
screenRow++
|
||||
currentBufferRow = nextBufferRow
|
||||
|
||||
[screenRow, screenLines]
|
||||
|
||||
bufferPositionForScreenPosition: (screenPosition, options) ->
|
||||
@translatePosition('screenDelta', 'bufferDelta', screenPosition, options)
|
||||
{ row, column } = Point.fromObject(screenPosition)
|
||||
[bufferRow, screenLine] = @bufferRowAndScreenLineForScreenRow(row)
|
||||
bufferColumn = screenLine.bufferColumnForScreenColumn(column)
|
||||
new Point(bufferRow, bufferColumn)
|
||||
|
||||
bufferRowAndScreenLineForScreenRow: (screenRow) ->
|
||||
bufferRow = 0
|
||||
for screenLine, currentScreenRow in @screenLines
|
||||
if currentScreenRow == screenRow
|
||||
break
|
||||
else
|
||||
bufferRow += screenLine.bufferRows
|
||||
|
||||
[bufferRow, screenLine]
|
||||
|
||||
screenRangeForBufferRange: (bufferRange) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
@@ -78,106 +139,6 @@ class LineMap
|
||||
end = @bufferPositionForScreenPosition(screenRange.end)
|
||||
new Range(start, end)
|
||||
|
||||
clipScreenPosition: (screenPosition, options) ->
|
||||
@clipPosition('screenDelta', screenPosition, options)
|
||||
|
||||
clipPosition: (deltaType, position, options={}) ->
|
||||
options.clipToBounds = true
|
||||
@translatePosition(deltaType, deltaType, position, options)
|
||||
|
||||
spliceByDelta: (deltaType, startRow, rowCount, lineFragments) ->
|
||||
stopRow = startRow + rowCount
|
||||
startIndex = undefined
|
||||
stopIndex = 0
|
||||
|
||||
delta = new Point
|
||||
for lineFragment, i in @lineFragments
|
||||
startIndex ?= i if delta.row == startRow
|
||||
break if delta.row == stopRow
|
||||
delta = delta.add(lineFragment[deltaType])
|
||||
stopIndex++
|
||||
startIndex ?= i
|
||||
|
||||
@lineFragments[startIndex...stopIndex] = lineFragments
|
||||
|
||||
linesByDelta: (deltaType, startRow, endRow) ->
|
||||
lines = []
|
||||
pendingFragment = null
|
||||
@traverseByDelta deltaType, new Point(startRow, 0), new Point(endRow, Infinity), ({lineFragment}) ->
|
||||
if pendingFragment
|
||||
pendingFragment = pendingFragment.concat(lineFragment)
|
||||
else
|
||||
pendingFragment = lineFragment
|
||||
if pendingFragment[deltaType].row > 0
|
||||
lines.push pendingFragment
|
||||
pendingFragment = null
|
||||
lines
|
||||
|
||||
translatePosition: (sourceDeltaType, targetDeltaType, sourcePosition, options={}) ->
|
||||
sourcePosition = Point.fromObject(sourcePosition)
|
||||
wrapBeyondNewlines = options.wrapBeyondNewlines ? false
|
||||
wrapAtSoftNewlines = options.wrapAtSoftNewlines ? false
|
||||
skipAtomicTokens = options.skipAtomicTokens ? false
|
||||
clipToBounds = options.clipToBounds ? false
|
||||
|
||||
@clipToBounds(sourceDeltaType, sourcePosition) if clipToBounds
|
||||
traversalResult = @traverseByDelta(sourceDeltaType, sourcePosition)
|
||||
lastLineFragment = traversalResult.lastLineFragment
|
||||
traversedAllFragments = traversalResult.traversedAllFragments
|
||||
sourceDelta = traversalResult[sourceDeltaType]
|
||||
targetDelta = traversalResult[targetDeltaType]
|
||||
|
||||
maxSourceColumn = sourceDelta.column + lastLineFragment.textLength()
|
||||
maxTargetColumn = targetDelta.column + lastLineFragment.textLength()
|
||||
|
||||
if lastLineFragment.isSoftWrapped() and sourcePosition.column >= maxSourceColumn
|
||||
if wrapAtSoftNewlines
|
||||
targetDelta.row++
|
||||
targetDelta.column = 0
|
||||
else
|
||||
targetDelta.column = maxTargetColumn - 1
|
||||
return @clipPosition(targetDeltaType, targetDelta)
|
||||
else if sourcePosition.column > maxSourceColumn and wrapBeyondNewlines and not traversedAllFragments
|
||||
targetDelta.row++
|
||||
targetDelta.column = 0
|
||||
else
|
||||
additionalColumns = sourcePosition.column - sourceDelta.column
|
||||
additionalColumns = lastLineFragment.translateColumn(sourceDeltaType, targetDeltaType, additionalColumns, { skipAtomicTokens })
|
||||
targetDelta.column += additionalColumns
|
||||
|
||||
targetDelta
|
||||
|
||||
clipToBounds: (deltaType, position) ->
|
||||
if position.column < 0
|
||||
position.column = 0
|
||||
|
||||
if position.row < 0
|
||||
position.row = 0
|
||||
position.column = 0
|
||||
|
||||
maxSourceRow = @lineCountByDelta(deltaType) - 1
|
||||
if position.row > maxSourceRow
|
||||
position.row = maxSourceRow
|
||||
position.column = Infinity
|
||||
|
||||
traverseByDelta: (deltaType, startPosition, endPosition=startPosition, iterator=null) ->
|
||||
traversalDelta = new Point
|
||||
screenDelta = new Point
|
||||
bufferDelta = new Point
|
||||
startPosition = Point.fromObject(startPosition)
|
||||
endPosition = Point.fromObject(endPosition)
|
||||
|
||||
for lineFragment, index in @lineFragments
|
||||
iterator({ lineFragment, screenDelta, bufferDelta }) if traversalDelta.isGreaterThanOrEqual(startPosition) and iterator?
|
||||
traversalDelta = traversalDelta.add(lineFragment[deltaType])
|
||||
break if traversalDelta.isGreaterThan(endPosition)
|
||||
screenDelta = screenDelta.add(lineFragment.screenDelta)
|
||||
bufferDelta = bufferDelta.add(lineFragment.bufferDelta)
|
||||
|
||||
lastLineFragment = lineFragment
|
||||
traversedAllFragments = (index == @lineFragments.length - 1)
|
||||
{ screenDelta, bufferDelta, lastLineFragment, traversedAllFragments }
|
||||
|
||||
logLines: (start=0, end=@screenLineCount() - 1)->
|
||||
for row in [start..end]
|
||||
line = @lineForScreenRow(row).text
|
||||
|
||||
185
src/app/old-line-map.coffee
Normal file
185
src/app/old-line-map.coffee
Normal file
@@ -0,0 +1,185 @@
|
||||
_ = require 'underscore'
|
||||
Point = require 'point'
|
||||
Range = require 'range'
|
||||
|
||||
module.exports =
|
||||
class LineMap
|
||||
constructor: ->
|
||||
@lineFragments = []
|
||||
|
||||
insertAtBufferRow: (bufferRow, lineFragments) ->
|
||||
@spliceAtBufferRow(bufferRow, 0, lineFragments)
|
||||
|
||||
spliceAtBufferRow: (startRow, rowCount, lineFragments) ->
|
||||
@spliceByDelta('bufferDelta', startRow, rowCount, lineFragments)
|
||||
|
||||
spliceAtScreenRow: (startRow, rowCount, lineFragments) ->
|
||||
@spliceByDelta('screenDelta', startRow, rowCount, lineFragments)
|
||||
|
||||
replaceBufferRows: (start, end, lineFragments) ->
|
||||
@spliceAtBufferRow(start, end - start + 1, lineFragments)
|
||||
|
||||
replaceScreenRows: (start, end, lineFragments) ->
|
||||
@spliceAtScreenRow(start, end - start + 1, lineFragments)
|
||||
|
||||
lineForScreenRow: (row) ->
|
||||
@linesForScreenRows(row, row)[0]
|
||||
|
||||
linesForScreenRows: (startRow, endRow) ->
|
||||
@linesByDelta('screenDelta', startRow, endRow)
|
||||
|
||||
lineForBufferRow: (row) ->
|
||||
@linesForBufferRows(row, row)[0]
|
||||
|
||||
linesForBufferRows: (startRow, endRow) ->
|
||||
@linesByDelta('bufferDelta', startRow, endRow)
|
||||
|
||||
bufferRowsForScreenRows: (startRow, endRow=@lastScreenRow()) ->
|
||||
bufferRows = []
|
||||
currentScreenRow = -1
|
||||
@traverseByDelta 'screenDelta', [startRow, 0], [endRow, 0], ({ screenDelta, bufferDelta }) ->
|
||||
bufferRows.push(bufferDelta.row) if screenDelta.row > currentScreenRow
|
||||
currentScreenRow = screenDelta.row
|
||||
bufferRows
|
||||
|
||||
bufferLineCount: ->
|
||||
@lineCountByDelta('bufferDelta')
|
||||
|
||||
screenLineCount: ->
|
||||
@lineCountByDelta('screenDelta')
|
||||
|
||||
lineCountByDelta: (deltaType) ->
|
||||
@traverseByDelta(deltaType, new Point(Infinity, 0))[deltaType].row
|
||||
|
||||
lastScreenRow: ->
|
||||
@screenLineCount() - 1
|
||||
|
||||
maxScreenLineLength: ->
|
||||
maxLength = 0
|
||||
@traverseByDelta 'screenDelta', [0, 0], [@lastScreenRow(), 0], ({lineFragment}) ->
|
||||
length = lineFragment.text.length
|
||||
maxLength = length if length > maxLength
|
||||
maxLength
|
||||
|
||||
screenPositionForBufferPosition: (bufferPosition, options) ->
|
||||
@translatePosition('bufferDelta', 'screenDelta', bufferPosition, options)
|
||||
|
||||
bufferPositionForScreenPosition: (screenPosition, options) ->
|
||||
@translatePosition('screenDelta', 'bufferDelta', screenPosition, options)
|
||||
|
||||
screenRangeForBufferRange: (bufferRange) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
start = @screenPositionForBufferPosition(bufferRange.start)
|
||||
end = @screenPositionForBufferPosition(bufferRange.end)
|
||||
new Range(start, end)
|
||||
|
||||
bufferRangeForScreenRange: (screenRange) ->
|
||||
start = @bufferPositionForScreenPosition(screenRange.start)
|
||||
end = @bufferPositionForScreenPosition(screenRange.end)
|
||||
new Range(start, end)
|
||||
|
||||
clipScreenPosition: (screenPosition, options) ->
|
||||
@clipPosition('screenDelta', screenPosition, options)
|
||||
|
||||
clipPosition: (deltaType, position, options={}) ->
|
||||
options.clipToBounds = true
|
||||
@translatePosition(deltaType, deltaType, position, options)
|
||||
|
||||
spliceByDelta: (deltaType, startRow, rowCount, lineFragments) ->
|
||||
stopRow = startRow + rowCount
|
||||
startIndex = undefined
|
||||
stopIndex = 0
|
||||
|
||||
delta = new Point
|
||||
for lineFragment, i in @lineFragments
|
||||
startIndex ?= i if delta.row == startRow
|
||||
break if delta.row == stopRow
|
||||
delta = delta.add(lineFragment[deltaType])
|
||||
stopIndex++
|
||||
startIndex ?= i
|
||||
|
||||
@lineFragments[startIndex...stopIndex] = lineFragments
|
||||
|
||||
linesByDelta: (deltaType, startRow, endRow) ->
|
||||
lines = []
|
||||
pendingFragment = null
|
||||
@traverseByDelta deltaType, new Point(startRow, 0), new Point(endRow, Infinity), ({lineFragment}) ->
|
||||
if pendingFragment
|
||||
pendingFragment = pendingFragment.concat(lineFragment)
|
||||
else
|
||||
pendingFragment = lineFragment
|
||||
if pendingFragment[deltaType].row > 0
|
||||
lines.push pendingFragment
|
||||
pendingFragment = null
|
||||
lines
|
||||
|
||||
translatePosition: (sourceDeltaType, targetDeltaType, sourcePosition, options={}) ->
|
||||
sourcePosition = Point.fromObject(sourcePosition)
|
||||
wrapBeyondNewlines = options.wrapBeyondNewlines ? false
|
||||
wrapAtSoftNewlines = options.wrapAtSoftNewlines ? false
|
||||
skipAtomicTokens = options.skipAtomicTokens ? false
|
||||
clipToBounds = options.clipToBounds ? false
|
||||
|
||||
@clipToBounds(sourceDeltaType, sourcePosition) if clipToBounds
|
||||
traversalResult = @traverseByDelta(sourceDeltaType, sourcePosition)
|
||||
lastLineFragment = traversalResult.lastLineFragment
|
||||
traversedAllFragments = traversalResult.traversedAllFragments
|
||||
sourceDelta = traversalResult[sourceDeltaType]
|
||||
targetDelta = traversalResult[targetDeltaType]
|
||||
|
||||
maxSourceColumn = sourceDelta.column + lastLineFragment.textLength()
|
||||
maxTargetColumn = targetDelta.column + lastLineFragment.textLength()
|
||||
|
||||
if lastLineFragment.isSoftWrapped() and sourcePosition.column >= maxSourceColumn
|
||||
if wrapAtSoftNewlines
|
||||
targetDelta.row++
|
||||
targetDelta.column = 0
|
||||
else
|
||||
targetDelta.column = maxTargetColumn - 1
|
||||
return @clipPosition(targetDeltaType, targetDelta)
|
||||
else if sourcePosition.column > maxSourceColumn and wrapBeyondNewlines and not traversedAllFragments
|
||||
targetDelta.row++
|
||||
targetDelta.column = 0
|
||||
else
|
||||
additionalColumns = sourcePosition.column - sourceDelta.column
|
||||
additionalColumns = lastLineFragment.translateColumn(sourceDeltaType, targetDeltaType, additionalColumns, { skipAtomicTokens })
|
||||
targetDelta.column += additionalColumns
|
||||
|
||||
targetDelta
|
||||
|
||||
clipToBounds: (deltaType, position) ->
|
||||
if position.column < 0
|
||||
position.column = 0
|
||||
|
||||
if position.row < 0
|
||||
position.row = 0
|
||||
position.column = 0
|
||||
|
||||
maxSourceRow = @lineCountByDelta(deltaType) - 1
|
||||
if position.row > maxSourceRow
|
||||
position.row = maxSourceRow
|
||||
position.column = Infinity
|
||||
|
||||
traverseByDelta: (deltaType, startPosition, endPosition=startPosition, iterator=null) ->
|
||||
traversalDelta = new Point
|
||||
screenDelta = new Point
|
||||
bufferDelta = new Point
|
||||
startPosition = Point.fromObject(startPosition)
|
||||
endPosition = Point.fromObject(endPosition)
|
||||
|
||||
for lineFragment, index in @lineFragments
|
||||
iterator({ lineFragment, screenDelta, bufferDelta }) if traversalDelta.isGreaterThanOrEqual(startPosition) and iterator?
|
||||
traversalDelta = traversalDelta.add(lineFragment[deltaType])
|
||||
break if traversalDelta.isGreaterThan(endPosition)
|
||||
screenDelta = screenDelta.add(lineFragment.screenDelta)
|
||||
bufferDelta = bufferDelta.add(lineFragment.bufferDelta)
|
||||
|
||||
lastLineFragment = lineFragment
|
||||
traversedAllFragments = (index == @lineFragments.length - 1)
|
||||
{ screenDelta, bufferDelta, lastLineFragment, traversedAllFragments }
|
||||
|
||||
logLines: (start=0, end=@screenLineCount() - 1)->
|
||||
for row in [start..end]
|
||||
line = @lineForScreenRow(row).text
|
||||
console.log row, line, line.length
|
||||
|
||||
94
src/app/old-screen-line.coffee
Normal file
94
src/app/old-screen-line.coffee
Normal file
@@ -0,0 +1,94 @@
|
||||
_ = require 'underscore'
|
||||
Point = require 'point'
|
||||
|
||||
module.exports =
|
||||
class ScreenLine
|
||||
stack: null
|
||||
text: null
|
||||
tokens: null
|
||||
screenDelta: null
|
||||
bufferDelta: null
|
||||
foldable: null
|
||||
|
||||
constructor: (@tokens, @text, screenDelta, bufferDelta, extraFields) ->
|
||||
@screenDelta = Point.fromObject(screenDelta)
|
||||
@bufferDelta = Point.fromObject(bufferDelta)
|
||||
_.extend(this, extraFields)
|
||||
|
||||
copy: ->
|
||||
new ScreenLine(@tokens, @text, @screenDelta, @bufferDelta, { @stack, @foldable })
|
||||
|
||||
splitAt: (column) ->
|
||||
return [new ScreenLine([], '', [0, 0], [0, 0]), this] if column == 0
|
||||
|
||||
rightTokens = new Array(@tokens...)
|
||||
leftTokens = []
|
||||
leftTextLength = 0
|
||||
while leftTextLength < column
|
||||
if leftTextLength + rightTokens[0].value.length > column
|
||||
rightTokens[0..0] = rightTokens[0].splitAt(column - leftTextLength)
|
||||
nextToken = rightTokens.shift()
|
||||
leftTextLength += nextToken.value.length
|
||||
leftTokens.push nextToken
|
||||
|
||||
leftText = _.pluck(leftTokens, 'value').join('')
|
||||
rightText = _.pluck(rightTokens, 'value').join('')
|
||||
|
||||
[leftScreenDelta, rightScreenDelta] = @screenDelta.splitAt(column)
|
||||
[leftBufferDelta, rightBufferDelta] = @bufferDelta.splitAt(column)
|
||||
|
||||
leftFragment = new ScreenLine(leftTokens, leftText, leftScreenDelta, leftBufferDelta, {@stack, @foldable})
|
||||
rightFragment = new ScreenLine(rightTokens, rightText, rightScreenDelta, rightBufferDelta, {@stack})
|
||||
[leftFragment, rightFragment]
|
||||
|
||||
tokenAtBufferColumn: (bufferColumn) ->
|
||||
delta = 0
|
||||
for token in @tokens
|
||||
delta += token.bufferDelta
|
||||
return token if delta >= bufferColumn
|
||||
token
|
||||
|
||||
concat: (other) ->
|
||||
tokens = @tokens.concat(other.tokens)
|
||||
text = @text + other.text
|
||||
screenDelta = @screenDelta.add(other.screenDelta)
|
||||
bufferDelta = @bufferDelta.add(other.bufferDelta)
|
||||
new ScreenLine(tokens, text, screenDelta, bufferDelta, {stack: other.stack})
|
||||
|
||||
translateColumn: (sourceDeltaType, targetDeltaType, sourceColumn, options={}) ->
|
||||
{ skipAtomicTokens } = options
|
||||
sourceColumn = Math.min(sourceColumn, @textLength())
|
||||
|
||||
isSourceColumnBeforeLastToken = false
|
||||
tokenStartTargetColumn = 0
|
||||
tokenStartSourceColumn = 0
|
||||
|
||||
for token in @tokens
|
||||
tokenEndSourceColumn = tokenStartSourceColumn + token[sourceDeltaType]
|
||||
tokenEndTargetColumn = tokenStartTargetColumn + token[targetDeltaType]
|
||||
break if tokenEndSourceColumn > sourceColumn
|
||||
tokenStartTargetColumn = tokenEndTargetColumn
|
||||
tokenStartSourceColumn = tokenEndSourceColumn
|
||||
|
||||
sourceColumnIsInsideToken = tokenStartSourceColumn < sourceColumn < tokenEndSourceColumn
|
||||
|
||||
if token?.isAtomic and sourceColumnIsInsideToken
|
||||
if skipAtomicTokens
|
||||
tokenEndTargetColumn
|
||||
else
|
||||
tokenStartTargetColumn
|
||||
else
|
||||
remainingColumns = sourceColumn - tokenStartSourceColumn
|
||||
tokenStartTargetColumn + remainingColumns
|
||||
|
||||
textLength: ->
|
||||
if @fold
|
||||
textLength = 0
|
||||
else
|
||||
textLength = @text.length
|
||||
|
||||
isSoftWrapped: ->
|
||||
@screenDelta.row == 1 and @bufferDelta.row == 0
|
||||
|
||||
isEqual: (other) ->
|
||||
_.isEqual(@tokens, other.tokens) and @screenDelta.isEqual(other.screenDelta) and @bufferDelta.isEqual(other.bufferDelta)
|
||||
@@ -1,24 +1,49 @@
|
||||
_ = require 'underscore'
|
||||
Point = require 'point'
|
||||
|
||||
module.exports =
|
||||
class ScreenLine
|
||||
stack: null
|
||||
text: null
|
||||
tokens: null
|
||||
screenDelta: null
|
||||
bufferDelta: null
|
||||
foldable: null
|
||||
|
||||
constructor: (@tokens, @text, screenDelta, bufferDelta, extraFields) ->
|
||||
@screenDelta = Point.fromObject(screenDelta)
|
||||
@bufferDelta = Point.fromObject(bufferDelta)
|
||||
_.extend(this, extraFields)
|
||||
constructor: ({@tokens, @stack, @bufferRows, @startBufferColumn, @fold, @foldable}) ->
|
||||
@bufferRows ?= 1
|
||||
@startBufferColumn ?= 0
|
||||
@foldable ?= false
|
||||
@text = _.pluck(@tokens, 'value').join('')
|
||||
|
||||
copy: ->
|
||||
new ScreenLine(@tokens, @text, @screenDelta, @bufferDelta, { @stack, @foldable })
|
||||
new ScreenLine({@tokens, @stack, @bufferRows, @startBufferColumn, @fold, @foldable})
|
||||
|
||||
splitAt: (column) ->
|
||||
clipScreenColumn: (column, options={}) ->
|
||||
{ skipAtomicTokens } = options
|
||||
column = Math.min(column, @getMaxScreenColumn())
|
||||
|
||||
tokenStartColumn = 0
|
||||
for token in @tokens
|
||||
break if tokenStartColumn + token.screenDelta > column
|
||||
tokenStartColumn += token.screenDelta
|
||||
|
||||
if token.isAtomic and tokenStartColumn < column
|
||||
if skipAtomicTokens
|
||||
tokenStartColumn + token.screenDelta
|
||||
else
|
||||
tokenStartColumn
|
||||
else
|
||||
column
|
||||
|
||||
screenColumnForBufferColumn: (bufferColumn, options) ->
|
||||
@clipScreenColumn(bufferColumn - @startBufferColumn)
|
||||
|
||||
bufferColumnForScreenColumn: (screenColumn, options) ->
|
||||
@startBufferColumn + screenColumn
|
||||
|
||||
getMaxScreenColumn: ->
|
||||
if @fold
|
||||
0
|
||||
else
|
||||
@text.length
|
||||
|
||||
getMaxBufferColumn: ->
|
||||
@startBufferColumn + @getMaxScreenColumn()
|
||||
|
||||
softWrapAt: (column) ->
|
||||
return [new ScreenLine([], '', [0, 0], [0, 0]), this] if column == 0
|
||||
|
||||
rightTokens = new Array(@tokens...)
|
||||
@@ -31,64 +56,26 @@ class ScreenLine
|
||||
leftTextLength += nextToken.value.length
|
||||
leftTokens.push nextToken
|
||||
|
||||
leftText = _.pluck(leftTokens, 'value').join('')
|
||||
rightText = _.pluck(rightTokens, 'value').join('')
|
||||
|
||||
[leftScreenDelta, rightScreenDelta] = @screenDelta.splitAt(column)
|
||||
[leftBufferDelta, rightBufferDelta] = @bufferDelta.splitAt(column)
|
||||
|
||||
leftFragment = new ScreenLine(leftTokens, leftText, leftScreenDelta, leftBufferDelta, {@stack, @foldable})
|
||||
rightFragment = new ScreenLine(rightTokens, rightText, rightScreenDelta, rightBufferDelta, {@stack})
|
||||
leftFragment = new ScreenLine(
|
||||
tokens: leftTokens
|
||||
bufferRows: 0
|
||||
startBufferColumn: @startBufferColumn
|
||||
stack: @stack
|
||||
foldable: @foldable
|
||||
)
|
||||
rightFragment = new ScreenLine(
|
||||
tokens: rightTokens
|
||||
startBufferColumn: @startBufferColumn + column
|
||||
stack: @stack
|
||||
)
|
||||
[leftFragment, rightFragment]
|
||||
|
||||
isSoftWrapped: ->
|
||||
@bufferRows == 0
|
||||
|
||||
tokenAtBufferColumn: (bufferColumn) ->
|
||||
delta = 0
|
||||
for token in @tokens
|
||||
delta += token.bufferDelta
|
||||
return token if delta >= bufferColumn
|
||||
token
|
||||
|
||||
concat: (other) ->
|
||||
tokens = @tokens.concat(other.tokens)
|
||||
text = @text + other.text
|
||||
screenDelta = @screenDelta.add(other.screenDelta)
|
||||
bufferDelta = @bufferDelta.add(other.bufferDelta)
|
||||
new ScreenLine(tokens, text, screenDelta, bufferDelta, {stack: other.stack})
|
||||
|
||||
translateColumn: (sourceDeltaType, targetDeltaType, sourceColumn, options={}) ->
|
||||
{ skipAtomicTokens } = options
|
||||
sourceColumn = Math.min(sourceColumn, @textLength())
|
||||
|
||||
isSourceColumnBeforeLastToken = false
|
||||
tokenStartTargetColumn = 0
|
||||
tokenStartSourceColumn = 0
|
||||
|
||||
for token in @tokens
|
||||
tokenEndSourceColumn = tokenStartSourceColumn + token[sourceDeltaType]
|
||||
tokenEndTargetColumn = tokenStartTargetColumn + token[targetDeltaType]
|
||||
break if tokenEndSourceColumn > sourceColumn
|
||||
tokenStartTargetColumn = tokenEndTargetColumn
|
||||
tokenStartSourceColumn = tokenEndSourceColumn
|
||||
|
||||
sourceColumnIsInsideToken = tokenStartSourceColumn < sourceColumn < tokenEndSourceColumn
|
||||
|
||||
if token?.isAtomic and sourceColumnIsInsideToken
|
||||
if skipAtomicTokens
|
||||
tokenEndTargetColumn
|
||||
else
|
||||
tokenStartTargetColumn
|
||||
else
|
||||
remainingColumns = sourceColumn - tokenStartSourceColumn
|
||||
tokenStartTargetColumn + remainingColumns
|
||||
|
||||
textLength: ->
|
||||
if @fold
|
||||
textLength = 0
|
||||
else
|
||||
textLength = @text.length
|
||||
|
||||
isSoftWrapped: ->
|
||||
@screenDelta.row == 1 and @bufferDelta.row == 0
|
||||
|
||||
isEqual: (other) ->
|
||||
_.isEqual(@tokens, other.tokens) and @screenDelta.isEqual(other.screenDelta) and @bufferDelta.isEqual(other.bufferDelta)
|
||||
|
||||
@@ -67,7 +67,10 @@ class TokenizedBuffer
|
||||
token = new Token(tokenProperties)
|
||||
tokenObjects.push(token.breakOutTabCharacters(@tabLength)...)
|
||||
text = _.pluck(tokenObjects, 'value').join('')
|
||||
new ScreenLine(tokenObjects, text, [1, 0], [1, 0], { stack })
|
||||
new ScreenLine(
|
||||
tokens: tokenObjects
|
||||
stack: stack
|
||||
)
|
||||
|
||||
lineForScreenRow: (row) ->
|
||||
@screenLines[row]
|
||||
|
||||
Reference in New Issue
Block a user