DisplayBuffer specs passing with greatly simplified LineMap

This commit is contained in:
Nathan Sobo
2012-10-31 19:04:45 -06:00
parent 8ae08dc21d
commit 3d12269315
8 changed files with 455 additions and 233 deletions

View File

@@ -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-+/

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View 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)

View File

@@ -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)

View File

@@ -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]