-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrow-map.coffee
119 lines (104 loc) · 4.88 KB
/
row-map.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
{spliceWithArray} = require 'underscore-plus'
# Used by the display buffer to map screen rows to buffer rows and vice-versa.
# This mapping may not be 1:1 due to folds and soft-wraps. This object maintains
# an array of regions, which contain `bufferRows` and `screenRows` fields.
#
# Rectangular Regions:
# If a region has the same number of buffer rows and screen rows, it is referred
# to as "rectangular", and represents one or more non-soft-wrapped, non-folded
# lines.
#
# Trapezoidal Regions:
# If a region has one buffer row and more than one screen row, it represents a
# soft-wrapped line. If a region has one screen row and more than one buffer
# row, it represents folded lines
module.exports =
class RowMap
constructor: ->
@regions = []
# Public: Returns a copy of all the regions in the map
getRegions: ->
@regions.slice()
# Public: Returns an end-row-exclusive range of screen rows corresponding to
# the given buffer row. If the buffer row is soft-wrapped, the range may span
# multiple screen rows. Otherwise it will span a single screen row.
screenRowRangeForBufferRow: (targetBufferRow) ->
{region, bufferRows, screenRows} = @traverseToBufferRow(targetBufferRow)
if region? and region.bufferRows isnt region.screenRows
[screenRows, screenRows + region.screenRows]
else
screenRows += targetBufferRow - bufferRows
[screenRows, screenRows + 1]
# Public: Returns an end-row-exclusive range of buffer rows corresponding to
# the given screen row. If the screen row is the first line of a folded range
# of buffer rows, the range may span multiple buffer rows. Otherwise it will
# span a single buffer row.
bufferRowRangeForScreenRow: (targetScreenRow) ->
{region, screenRows, bufferRows} = @traverseToScreenRow(targetScreenRow)
if region? and region.bufferRows isnt region.screenRows
[bufferRows, bufferRows + region.bufferRows]
else
bufferRows += targetScreenRow - screenRows
[bufferRows, bufferRows + 1]
# Public: If the given buffer row is part of a folded row range, returns that
# row range. Otherwise returns a range spanning only the given buffer row.
bufferRowRangeForBufferRow: (targetBufferRow) ->
{region, bufferRows} = @traverseToBufferRow(targetBufferRow)
if region? and region.bufferRows isnt region.screenRows
[bufferRows, bufferRows + region.bufferRows]
else
[targetBufferRow, targetBufferRow + 1]
# Public: Given a starting buffer row, the number of buffer rows to replace,
# and an array of regions of shape {bufferRows: n, screenRows: m}, splices
# the regions at the appropriate location in the map. This method is used by
# display buffer to keep the map updated when the underlying buffer changes.
spliceRegions: (startBufferRow, bufferRowCount, regions) ->
endBufferRow = startBufferRow + bufferRowCount
{index, bufferRows} = @traverseToBufferRow(startBufferRow)
precedingRows = startBufferRow - bufferRows
count = 0
while region = @regions[index + count]
count++
bufferRows += region.bufferRows
if bufferRows >= endBufferRow
followingRows = bufferRows - endBufferRow
break
if precedingRows > 0
regions.unshift({bufferRows: precedingRows, screenRows: precedingRows})
if followingRows > 0
regions.push({bufferRows: followingRows, screenRows: followingRows})
spliceWithArray(@regions, index, count, regions)
@mergeAdjacentRectangularRegions(index - 1, index + regions.length)
traverseToBufferRow: (targetBufferRow) ->
bufferRows = 0
screenRows = 0
for region, index in @regions
if (bufferRows + region.bufferRows) > targetBufferRow
return {region, index, screenRows, bufferRows}
bufferRows += region.bufferRows
screenRows += region.screenRows
{index, screenRows, bufferRows}
traverseToScreenRow: (targetScreenRow) ->
bufferRows = 0
screenRows = 0
for region, index in @regions
if (screenRows + region.screenRows) > targetScreenRow
return {region, index, screenRows, bufferRows}
bufferRows += region.bufferRows
screenRows += region.screenRows
{index, screenRows, bufferRows}
mergeAdjacentRectangularRegions: (startIndex, endIndex) ->
for index in [endIndex..startIndex]
if 0 < index < @regions.length
leftRegion = @regions[index - 1]
rightRegion = @regions[index]
leftIsRectangular = leftRegion.bufferRows is leftRegion.screenRows
rightIsRectangular = rightRegion.bufferRows is rightRegion.screenRows
if leftIsRectangular and rightIsRectangular
@regions.splice index - 1, 2,
bufferRows: leftRegion.bufferRows + rightRegion.bufferRows
screenRows: leftRegion.screenRows + rightRegion.screenRows
# Public: Returns an array of strings describing the map's regions.
inspect: ->
for {bufferRows, screenRows} in @regions
"#{bufferRows}:#{screenRows}"