view_tracking.js
5.24 KB
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { buildViewArray } from "../line/line_data"
import { sawCollapsedSpans } from "../line/saw_special_spans"
import { visualLineEndNo, visualLineNo } from "../line/spans"
import { findViewIndex } from "../measurement/position_measurement"
import { indexOf } from "../util/misc"
// Updates the display.view data structure for a given change to the
// document. From and to are in pre-change coordinates. Lendiff is
// the amount of lines added or subtracted by the change. This is
// used for changes that span multiple lines, or change the way
// lines are divided into visual lines. regLineChange (below)
// registers single-line changes.
export function regChange(cm, from, to, lendiff) {
if (from == null) from = cm.doc.first
if (to == null) to = cm.doc.first + cm.doc.size
if (!lendiff) lendiff = 0
let display = cm.display
if (lendiff && to < display.viewTo &&
(display.updateLineNumbers == null || display.updateLineNumbers > from))
display.updateLineNumbers = from
cm.curOp.viewChanged = true
if (from >= display.viewTo) { // Change after
if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
resetView(cm)
} else if (to <= display.viewFrom) { // Change before
if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
resetView(cm)
} else {
display.viewFrom += lendiff
display.viewTo += lendiff
}
} else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
resetView(cm)
} else if (from <= display.viewFrom) { // Top overlap
let cut = viewCuttingPoint(cm, to, to + lendiff, 1)
if (cut) {
display.view = display.view.slice(cut.index)
display.viewFrom = cut.lineN
display.viewTo += lendiff
} else {
resetView(cm)
}
} else if (to >= display.viewTo) { // Bottom overlap
let cut = viewCuttingPoint(cm, from, from, -1)
if (cut) {
display.view = display.view.slice(0, cut.index)
display.viewTo = cut.lineN
} else {
resetView(cm)
}
} else { // Gap in the middle
let cutTop = viewCuttingPoint(cm, from, from, -1)
let cutBot = viewCuttingPoint(cm, to, to + lendiff, 1)
if (cutTop && cutBot) {
display.view = display.view.slice(0, cutTop.index)
.concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
.concat(display.view.slice(cutBot.index))
display.viewTo += lendiff
} else {
resetView(cm)
}
}
let ext = display.externalMeasured
if (ext) {
if (to < ext.lineN)
ext.lineN += lendiff
else if (from < ext.lineN + ext.size)
display.externalMeasured = null
}
}
// Register a change to a single line. Type must be one of "text",
// "gutter", "class", "widget"
export function regLineChange(cm, line, type) {
cm.curOp.viewChanged = true
let display = cm.display, ext = cm.display.externalMeasured
if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
display.externalMeasured = null
if (line < display.viewFrom || line >= display.viewTo) return
let lineView = display.view[findViewIndex(cm, line)]
if (lineView.node == null) return
let arr = lineView.changes || (lineView.changes = [])
if (indexOf(arr, type) == -1) arr.push(type)
}
// Clear the view.
export function resetView(cm) {
cm.display.viewFrom = cm.display.viewTo = cm.doc.first
cm.display.view = []
cm.display.viewOffset = 0
}
function viewCuttingPoint(cm, oldN, newN, dir) {
let index = findViewIndex(cm, oldN), diff, view = cm.display.view
if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
return {index: index, lineN: newN}
let n = cm.display.viewFrom
for (let i = 0; i < index; i++)
n += view[i].size
if (n != oldN) {
if (dir > 0) {
if (index == view.length - 1) return null
diff = (n + view[index].size) - oldN
index++
} else {
diff = n - oldN
}
oldN += diff; newN += diff
}
while (visualLineNo(cm.doc, newN) != newN) {
if (index == (dir < 0 ? 0 : view.length - 1)) return null
newN += dir * view[index - (dir < 0 ? 1 : 0)].size
index += dir
}
return {index: index, lineN: newN}
}
// Force the view to cover a given range, adding empty view element
// or clipping off existing ones as needed.
export function adjustView(cm, from, to) {
let display = cm.display, view = display.view
if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
display.view = buildViewArray(cm, from, to)
display.viewFrom = from
} else {
if (display.viewFrom > from)
display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view)
else if (display.viewFrom < from)
display.view = display.view.slice(findViewIndex(cm, from))
display.viewFrom = from
if (display.viewTo < to)
display.view = display.view.concat(buildViewArray(cm, display.viewTo, to))
else if (display.viewTo > to)
display.view = display.view.slice(0, findViewIndex(cm, to))
}
display.viewTo = to
}
// Count the number of lines in the view whose DOM representation is
// out of date (or nonexistent).
export function countDirtyView(cm) {
let view = cm.display.view, dirty = 0
for (let i = 0; i < view.length; i++) {
let lineView = view[i]
if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty
}
return dirty
}