mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Fixes #688 The DisplayBuffer applies buffer and screen deltas to the row map as rows are inserted/removed from the buffer/screen. This can leave some of the regions in a weird state, such as mapping multiple screen rows to zero buffer rows. But next the DisplayBuffer applies any new mappings based on the replaced lines over the top of existing regions. These weirdly shaped regions should be overwritten by newly inserted regions, so at the end of the operation the row map makes sense again. This fixes a corner case where regions spanning 0 buffer rows at the very beginning of the row range were not being included in the set of regions to replace. This was in turn causing the RowMap to get into a bad state in certain situations involving soft-wrapped lines.
180 lines
7.9 KiB
CoffeeScript
180 lines
7.9 KiB
CoffeeScript
## Internal ##
|
|
|
|
# Facilitates the mapping of screen rows to buffer rows and vice versa. All row
|
|
# ranges dealt with by this class are end-row exclusive. For example, a fold of
|
|
# rows 4 through 8 would be expressed as `mapBufferRowRange(4, 9, 1)`, which maps
|
|
# the region from 4 to 9 in the buffer to a single screen row. Conversely, a
|
|
# soft-wrapped screen line means there are multiple screen rows corresponding to
|
|
# a single buffer row, as follows: `mapBufferRowRange(4, 5, 3)`. That says that
|
|
# buffer row 4 maps to 3 rows on screen.
|
|
#
|
|
# The RowMap revolves around the `@regions` array. Each region describes a number
|
|
# of rows in both the screen and buffer coordinate spaces. So if you inserted a
|
|
# single fold from 5-10, the regions array would look like this:
|
|
#
|
|
# ```
|
|
# [{bufferRows: 5, screenRows: 5}, {bufferRows: 5, screenRows: 1}]
|
|
# ```
|
|
#
|
|
# The first region expresses an iso-mapping, a region in which one buffer row
|
|
# is equivalent to one screen row. The second region expresses the fold, with
|
|
# 5 buffer rows mapping to a single screen row. Position translation functions
|
|
# by traversing through these regions and summing the number of rows traversed
|
|
# in both the screen and the buffer.
|
|
module.exports =
|
|
class RowMap
|
|
constructor: ->
|
|
@regions = []
|
|
|
|
screenRowRangeForBufferRow: (targetBufferRow) ->
|
|
{ region, screenRow, bufferRow } = @traverseToBufferRow(targetBufferRow)
|
|
if region and region.bufferRows != region.screenRows # 1:n region
|
|
[screenRow, screenRow + region.screenRows]
|
|
else # 1:1 region
|
|
screenRow += targetBufferRow - bufferRow
|
|
[screenRow, screenRow + 1]
|
|
|
|
# This will return just the given buffer row if it is part of an iso region,
|
|
# but if it is part of a fold it will return the range of the entire fold. This
|
|
# helps the DisplayBuffer always start processing at the beginning of a fold
|
|
# for changes that occur inside the fold.
|
|
bufferRowRangeForBufferRow: (targetBufferRow) ->
|
|
{ region, screenRow, bufferRow } = @traverseToBufferRow(targetBufferRow)
|
|
if region and region.bufferRows != region.screenRows # 1:n region
|
|
[bufferRow, bufferRow + region.bufferRows]
|
|
else # 1:1 region
|
|
[targetBufferRow, targetBufferRow + 1]
|
|
|
|
bufferRowRangeForScreenRow: (targetScreenRow) ->
|
|
{ region, screenRow, bufferRow } = @traverseToScreenRow(targetScreenRow)
|
|
if region and region.bufferRows != region.screenRows # 1:n region
|
|
[bufferRow, bufferRow + region.bufferRows]
|
|
else # 1:1 region
|
|
bufferRow += targetScreenRow - screenRow
|
|
[bufferRow, bufferRow + 1]
|
|
|
|
# This method is used to create new regions, storing a mapping between a range
|
|
# of buffer rows to a certain number of screen rows. It will never add or remove
|
|
# rows in either coordinate space, meaning that it never changes the position
|
|
# of subsequent regions. It will overwrite or split existing regions that overlap
|
|
# with the region being stored however.
|
|
mapBufferRowRange: (startBufferRow, endBufferRow, screenRows) ->
|
|
{ index, bufferRow, screenRow } = @traverseToBufferRow(startBufferRow)
|
|
|
|
overlapStartIndex = index
|
|
overlapStartBufferRow = bufferRow
|
|
preRows = startBufferRow - overlapStartBufferRow
|
|
endScreenRow = screenRow + preRows + screenRows
|
|
overlapEndIndex = index
|
|
overlapEndBufferRow = bufferRow
|
|
overlapEndScreenRow = screenRow
|
|
|
|
# determine regions that the new region overlaps. they will need replacement.
|
|
while overlapEndIndex < @regions.length
|
|
region = @regions[overlapEndIndex]
|
|
overlapEndBufferRow += region.bufferRows
|
|
overlapEndScreenRow += region.screenRows
|
|
break if overlapEndBufferRow >= endBufferRow and overlapEndScreenRow >= endScreenRow
|
|
overlapEndIndex++
|
|
|
|
# we will replace overlapStartIndex..overlapEndIndex with these regions
|
|
newRegions = []
|
|
|
|
# if we straddle the first overlapping region, push a smaller region representing
|
|
# the portion before the new region
|
|
if preRows > 0
|
|
newRegions.push(bufferRows: preRows, screenRows: preRows)
|
|
|
|
# push the new region
|
|
newRegions.push(bufferRows: endBufferRow - startBufferRow, screenRows: screenRows)
|
|
|
|
# if we straddle the last overlapping region, push a smaller region representing
|
|
# the portion after the new region
|
|
if overlapEndBufferRow > endBufferRow
|
|
newRegions.push(bufferRows: overlapEndBufferRow - endBufferRow, screenRows: overlapEndScreenRow - endScreenRow)
|
|
|
|
@regions[overlapStartIndex..overlapEndIndex] = newRegions
|
|
@mergeIsomorphicRegions(Math.max(0, overlapStartIndex - 1), Math.min(@regions.length - 1, overlapEndIndex + 1))
|
|
|
|
mergeIsomorphicRegions: (startIndex, endIndex) ->
|
|
return if startIndex == endIndex
|
|
|
|
region = @regions[startIndex]
|
|
nextRegion = @regions[startIndex + 1]
|
|
if region.bufferRows == region.screenRows and nextRegion.bufferRows == nextRegion.screenRows
|
|
@regions[startIndex..startIndex + 1] =
|
|
bufferRows: region.bufferRows + nextRegion.bufferRows
|
|
screenRows: region.screenRows + nextRegion.screenRows
|
|
@mergeIsomorphicRegions(startIndex, endIndex - 1)
|
|
else
|
|
@mergeIsomorphicRegions(startIndex + 1, endIndex)
|
|
|
|
# This method records insertion or removal of rows in the buffer, adjusting the
|
|
# buffer dimension of regions following the start row accordingly.
|
|
applyBufferDelta: (startBufferRow, delta) ->
|
|
return if delta is 0
|
|
{ index, bufferRow } = @traverseToBufferRow(startBufferRow)
|
|
if delta > 0 and index < @regions.length
|
|
{ bufferRows, screenRows } = @regions[index]
|
|
bufferRows += delta
|
|
@regions[index] = { bufferRows, screenRows }
|
|
else
|
|
delta = -delta
|
|
while delta > 0 and index < @regions.length
|
|
{ bufferRows, screenRows } = @regions[index]
|
|
regionStartBufferRow = bufferRow
|
|
regionEndBufferRow = bufferRow + bufferRows
|
|
maxDelta = regionEndBufferRow - Math.max(regionStartBufferRow, startBufferRow)
|
|
regionDelta = Math.min(delta, maxDelta)
|
|
bufferRows -= regionDelta
|
|
@regions[index] = { bufferRows, screenRows }
|
|
delta -= regionDelta
|
|
bufferRow += bufferRows
|
|
index++
|
|
|
|
# This method records insertion or removal of rows on the screen, adjusting the
|
|
# screen dimension of regions following the start row accordingly.
|
|
applyScreenDelta: (startScreenRow, delta) ->
|
|
return if delta is 0
|
|
{ index, screenRow } = @traverseToScreenRow(startScreenRow)
|
|
if delta > 0 and index < @regions.length
|
|
{ bufferRows, screenRows } = @regions[index]
|
|
screenRows += delta
|
|
@regions[index] = { bufferRows, screenRows }
|
|
else
|
|
delta = -delta
|
|
while delta > 0 and index < @regions.length
|
|
{ bufferRows, screenRows } = @regions[index]
|
|
regionStartScreenRow = screenRow
|
|
regionEndScreenRow = screenRow + screenRows
|
|
maxDelta = regionEndScreenRow - Math.max(regionStartScreenRow, startScreenRow)
|
|
regionDelta = Math.min(delta, maxDelta)
|
|
screenRows -= regionDelta
|
|
@regions[index] = { bufferRows, screenRows }
|
|
delta -= regionDelta
|
|
screenRow += screenRows
|
|
index++
|
|
|
|
traverseToBufferRow: (targetBufferRow) ->
|
|
bufferRow = 0
|
|
screenRow = 0
|
|
for region, index in @regions
|
|
if (bufferRow + region.bufferRows) > targetBufferRow or region.bufferRows == 0 and bufferRow == targetBufferRow
|
|
return { region, index, screenRow, bufferRow }
|
|
bufferRow += region.bufferRows
|
|
screenRow += region.screenRows
|
|
{ index, screenRow, bufferRow }
|
|
|
|
traverseToScreenRow: (targetScreenRow) ->
|
|
bufferRow = 0
|
|
screenRow = 0
|
|
for region, index in @regions
|
|
if (screenRow + region.screenRows) > targetScreenRow
|
|
return { region, index, screenRow, bufferRow }
|
|
bufferRow += region.bufferRows
|
|
screenRow += region.screenRows
|
|
{ index, screenRow, bufferRow }
|
|
|
|
inspect: ->
|
|
@regions.map(({screenRows, bufferRows}) -> "#{screenRows}:#{bufferRows}").join(', ')
|