annotate .cms/lib/codemirror/src/measurement/position_measurement.js @ 1:1d486627aa1e draft default tip

24.10
author Coffee CMS <info@coffee-cms.ru>
date Sat, 12 Oct 2024 02:51:39 +0000
parents 78edf6b517a0
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
1 import { buildLineContent, LineView } from "../line/line_data.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
2 import { clipPos, Pos } from "../line/pos.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
3 import { collapsedSpanAround, heightAtLine, lineIsHidden, visualLine } from "../line/spans.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
4 import { getLine, lineAtHeight, lineNo, updateLineHeight } from "../line/utils_line.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
5 import { bidiOther, getBidiPartAt, getOrder } from "../util/bidi.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
6 import { chrome, android, ie, ie_version } from "../util/browser.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
7 import { elt, removeChildren, range, removeChildrenAndAdd, doc } from "../util/dom.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
8 import { e_target } from "../util/event.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
9 import { hasBadZoomedRects } from "../util/feature_detection.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
10 import { countColumn, findFirst, isExtendingChar, scrollerGap, skipExtendingChars } from "../util/misc.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
11 import { updateLineForChanges } from "../display/update_line.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
12
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
13 import { widgetHeight } from "./widgets.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
14
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
15 // POSITION MEASUREMENT
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
16
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
17 export function paddingTop(display) {return display.lineSpace.offsetTop}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
18 export function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
19 export function paddingH(display) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
20 if (display.cachedPaddingH) return display.cachedPaddingH
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
21 let e = removeChildrenAndAdd(display.measure, elt("pre", "x", "CodeMirror-line-like"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
22 let style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
23 let data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
24 if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
25 return data
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
26 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
27
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
28 export function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
29 export function displayWidth(cm) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
30 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
31 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
32 export function displayHeight(cm) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
33 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
34 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
35
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
36 // Ensure the lineView.wrapping.heights array is populated. This is
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
37 // an array of bottom offsets for the lines that make up a drawn
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
38 // line. When lineWrapping is on, there might be more than one
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
39 // height.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
40 function ensureLineHeights(cm, lineView, rect) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
41 let wrapping = cm.options.lineWrapping
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
42 let curWidth = wrapping && displayWidth(cm)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
43 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
44 let heights = lineView.measure.heights = []
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
45 if (wrapping) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
46 lineView.measure.width = curWidth
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
47 let rects = lineView.text.firstChild.getClientRects()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
48 for (let i = 0; i < rects.length - 1; i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
49 let cur = rects[i], next = rects[i + 1]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
50 if (Math.abs(cur.bottom - next.bottom) > 2)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
51 heights.push((cur.bottom + next.top) / 2 - rect.top)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
52 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
53 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
54 heights.push(rect.bottom - rect.top)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
55 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
56 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
57
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
58 // Find a line map (mapping character offsets to text nodes) and a
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
59 // measurement cache for the given line number. (A line view might
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
60 // contain multiple lines when collapsed ranges are present.)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
61 export function mapFromLineView(lineView, line, lineN) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
62 if (lineView.line == line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
63 return {map: lineView.measure.map, cache: lineView.measure.cache}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
64 if (lineView.rest) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
65 for (let i = 0; i < lineView.rest.length; i++)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
66 if (lineView.rest[i] == line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
67 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
68 for (let i = 0; i < lineView.rest.length; i++)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
69 if (lineNo(lineView.rest[i]) > lineN)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
70 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
71 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
72 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
73
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
74 // Render a line into the hidden node display.externalMeasured. Used
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
75 // when measurement is needed for a line that's not in the viewport.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
76 function updateExternalMeasurement(cm, line) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
77 line = visualLine(line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
78 let lineN = lineNo(line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
79 let view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
80 view.lineN = lineN
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
81 let built = view.built = buildLineContent(cm, view)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
82 view.text = built.pre
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
83 removeChildrenAndAdd(cm.display.lineMeasure, built.pre)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
84 return view
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
85 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
86
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
87 // Get a {top, bottom, left, right} box (in line-local coordinates)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
88 // for a given character.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
89 export function measureChar(cm, line, ch, bias) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
90 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
91 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
92
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
93 // Find a line view that corresponds to the given line number.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
94 export function findViewForLine(cm, lineN) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
95 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
96 return cm.display.view[findViewIndex(cm, lineN)]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
97 let ext = cm.display.externalMeasured
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
98 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
99 return ext
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
100 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
101
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
102 // Measurement can be split in two steps, the set-up work that
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
103 // applies to the whole line, and the measurement of the actual
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
104 // character. Functions like coordsChar, that need to do a lot of
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
105 // measurements in a row, can thus ensure that the set-up work is
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
106 // only done once.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
107 export function prepareMeasureForLine(cm, line) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
108 let lineN = lineNo(line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
109 let view = findViewForLine(cm, lineN)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
110 if (view && !view.text) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
111 view = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
112 } else if (view && view.changes) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
113 updateLineForChanges(cm, view, lineN, getDimensions(cm))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
114 cm.curOp.forceUpdate = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
115 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
116 if (!view)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
117 view = updateExternalMeasurement(cm, line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
118
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
119 let info = mapFromLineView(view, line, lineN)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
120 return {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
121 line: line, view: view, rect: null,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
122 map: info.map, cache: info.cache, before: info.before,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
123 hasHeights: false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
124 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
125 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
126
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
127 // Given a prepared measurement object, measures the position of an
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
128 // actual character (or fetches it from the cache).
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
129 export function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
130 if (prepared.before) ch = -1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
131 let key = ch + (bias || ""), found
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
132 if (prepared.cache.hasOwnProperty(key)) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
133 found = prepared.cache[key]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
134 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
135 if (!prepared.rect)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
136 prepared.rect = prepared.view.text.getBoundingClientRect()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
137 if (!prepared.hasHeights) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
138 ensureLineHeights(cm, prepared.view, prepared.rect)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
139 prepared.hasHeights = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
140 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
141 found = measureCharInner(cm, prepared, ch, bias)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
142 if (!found.bogus) prepared.cache[key] = found
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
143 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
144 return {left: found.left, right: found.right,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
145 top: varHeight ? found.rtop : found.top,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
146 bottom: varHeight ? found.rbottom : found.bottom}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
147 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
148
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
149 let nullRect = {left: 0, right: 0, top: 0, bottom: 0}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
150
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
151 export function nodeAndOffsetInLineMap(map, ch, bias) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
152 let node, start, end, collapse, mStart, mEnd
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
153 // First, search the line map for the text node corresponding to,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
154 // or closest to, the target character.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
155 for (let i = 0; i < map.length; i += 3) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
156 mStart = map[i]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
157 mEnd = map[i + 1]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
158 if (ch < mStart) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
159 start = 0; end = 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
160 collapse = "left"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
161 } else if (ch < mEnd) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
162 start = ch - mStart
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
163 end = start + 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
164 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
165 end = mEnd - mStart
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
166 start = end - 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
167 if (ch >= mEnd) collapse = "right"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
168 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
169 if (start != null) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
170 node = map[i + 2]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
171 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
172 collapse = bias
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
173 if (bias == "left" && start == 0)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
174 while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
175 node = map[(i -= 3) + 2]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
176 collapse = "left"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
177 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
178 if (bias == "right" && start == mEnd - mStart)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
179 while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
180 node = map[(i += 3) + 2]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
181 collapse = "right"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
182 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
183 break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
184 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
185 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
186 return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
187 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
188
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
189 function getUsefulRect(rects, bias) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
190 let rect = nullRect
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
191 if (bias == "left") for (let i = 0; i < rects.length; i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
192 if ((rect = rects[i]).left != rect.right) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
193 } else for (let i = rects.length - 1; i >= 0; i--) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
194 if ((rect = rects[i]).left != rect.right) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
195 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
196 return rect
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
197 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
198
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
199 function measureCharInner(cm, prepared, ch, bias) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
200 let place = nodeAndOffsetInLineMap(prepared.map, ch, bias)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
201 let node = place.node, start = place.start, end = place.end, collapse = place.collapse
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
202
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
203 let rect
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
204 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
205 for (let i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
206 while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
207 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
208 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
209 rect = node.parentNode.getBoundingClientRect()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
210 else
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
211 rect = getUsefulRect(range(node, start, end).getClientRects(), bias)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
212 if (rect.left || rect.right || start == 0) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
213 end = start
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
214 start = start - 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
215 collapse = "right"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
216 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
217 if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
218 } else { // If it is a widget, simply get the box for the whole widget.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
219 if (start > 0) collapse = bias = "right"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
220 let rects
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
221 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
222 rect = rects[bias == "right" ? rects.length - 1 : 0]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
223 else
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
224 rect = node.getBoundingClientRect()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
225 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
226 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
227 let rSpan = node.parentNode.getClientRects()[0]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
228 if (rSpan)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
229 rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
230 else
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
231 rect = nullRect
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
232 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
233
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
234 let rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
235 let mid = (rtop + rbot) / 2
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
236 let heights = prepared.view.measure.heights
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
237 let i = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
238 for (; i < heights.length - 1; i++)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
239 if (mid < heights[i]) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
240 let top = i ? heights[i - 1] : 0, bot = heights[i]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
241 let result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
242 right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
243 top: top, bottom: bot}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
244 if (!rect.left && !rect.right) result.bogus = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
245 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
246
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
247 return result
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
248 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
249
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
250 // Work around problem with bounding client rects on ranges being
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
251 // returned incorrectly when zoomed on IE10 and below.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
252 function maybeUpdateRectForZooming(measure, rect) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
253 if (!window.screen || screen.logicalXDPI == null ||
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
254 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
255 return rect
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
256 let scaleX = screen.logicalXDPI / screen.deviceXDPI
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
257 let scaleY = screen.logicalYDPI / screen.deviceYDPI
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
258 return {left: rect.left * scaleX, right: rect.right * scaleX,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
259 top: rect.top * scaleY, bottom: rect.bottom * scaleY}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
260 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
261
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
262 export function clearLineMeasurementCacheFor(lineView) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
263 if (lineView.measure) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
264 lineView.measure.cache = {}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
265 lineView.measure.heights = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
266 if (lineView.rest) for (let i = 0; i < lineView.rest.length; i++)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
267 lineView.measure.caches[i] = {}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
268 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
269 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
270
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
271 export function clearLineMeasurementCache(cm) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
272 cm.display.externalMeasure = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
273 removeChildren(cm.display.lineMeasure)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
274 for (let i = 0; i < cm.display.view.length; i++)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
275 clearLineMeasurementCacheFor(cm.display.view[i])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
276 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
277
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
278 export function clearCaches(cm) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
279 clearLineMeasurementCache(cm)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
280 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
281 if (!cm.options.lineWrapping) cm.display.maxLineChanged = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
282 cm.display.lineNumChars = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
283 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
284
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
285 function pageScrollX(doc) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
286 // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
287 // which causes page_Offset and bounding client rects to use
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
288 // different reference viewports and invalidate our calculations.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
289 if (chrome && android) return -(doc.body.getBoundingClientRect().left - parseInt(getComputedStyle(doc.body).marginLeft))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
290 return doc.defaultView.pageXOffset || (doc.documentElement || doc.body).scrollLeft
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
291 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
292 function pageScrollY(doc) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
293 if (chrome && android) return -(doc.body.getBoundingClientRect().top - parseInt(getComputedStyle(doc.body).marginTop))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
294 return doc.defaultView.pageYOffset || (doc.documentElement || doc.body).scrollTop
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
295 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
296
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
297 function widgetTopHeight(lineObj) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
298 let {widgets} = visualLine(lineObj), height = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
299 if (widgets) for (let i = 0; i < widgets.length; ++i) if (widgets[i].above)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
300 height += widgetHeight(widgets[i])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
301 return height
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
302 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
303
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
304 // Converts a {top, bottom, left, right} box from line-local
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
305 // coordinates into another coordinate system. Context may be one of
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
306 // "line", "div" (display.lineDiv), "local"./null (editor), "window",
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
307 // or "page".
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
308 export function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
309 if (!includeWidgets) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
310 let height = widgetTopHeight(lineObj)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
311 rect.top += height; rect.bottom += height
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
312 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
313 if (context == "line") return rect
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
314 if (!context) context = "local"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
315 let yOff = heightAtLine(lineObj)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
316 if (context == "local") yOff += paddingTop(cm.display)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
317 else yOff -= cm.display.viewOffset
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
318 if (context == "page" || context == "window") {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
319 let lOff = cm.display.lineSpace.getBoundingClientRect()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
320 yOff += lOff.top + (context == "window" ? 0 : pageScrollY(doc(cm)))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
321 let xOff = lOff.left + (context == "window" ? 0 : pageScrollX(doc(cm)))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
322 rect.left += xOff; rect.right += xOff
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
323 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
324 rect.top += yOff; rect.bottom += yOff
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
325 return rect
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
326 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
327
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
328 // Coverts a box from "div" coords to another coordinate system.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
329 // Context may be "window", "page", "div", or "local"./null.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
330 export function fromCoordSystem(cm, coords, context) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
331 if (context == "div") return coords
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
332 let left = coords.left, top = coords.top
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
333 // First move into "page" coordinate system
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
334 if (context == "page") {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
335 left -= pageScrollX(doc(cm))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
336 top -= pageScrollY(doc(cm))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
337 } else if (context == "local" || !context) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
338 let localBox = cm.display.sizer.getBoundingClientRect()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
339 left += localBox.left
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
340 top += localBox.top
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
341 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
342
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
343 let lineSpaceBox = cm.display.lineSpace.getBoundingClientRect()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
344 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
345 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
346
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
347 export function charCoords(cm, pos, context, lineObj, bias) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
348 if (!lineObj) lineObj = getLine(cm.doc, pos.line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
349 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
350 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
351
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
352 // Returns a box for a given cursor position, which may have an
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
353 // 'other' property containing the position of the secondary cursor
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
354 // on a bidi boundary.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
355 // A cursor Pos(line, char, "before") is on the same visual line as `char - 1`
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
356 // and after `char - 1` in writing order of `char - 1`
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
357 // A cursor Pos(line, char, "after") is on the same visual line as `char`
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
358 // and before `char` in writing order of `char`
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
359 // Examples (upper-case letters are RTL, lower-case are LTR):
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
360 // Pos(0, 1, ...)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
361 // before after
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
362 // ab a|b a|b
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
363 // aB a|B aB|
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
364 // Ab |Ab A|b
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
365 // AB B|A B|A
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
366 // Every position after the last character on a line is considered to stick
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
367 // to the last character on the line.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
368 export function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
369 lineObj = lineObj || getLine(cm.doc, pos.line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
370 if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
371 function get(ch, right) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
372 let m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
373 if (right) m.left = m.right; else m.right = m.left
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
374 return intoCoordSystem(cm, lineObj, m, context)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
375 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
376 let order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
377 if (ch >= lineObj.text.length) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
378 ch = lineObj.text.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
379 sticky = "before"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
380 } else if (ch <= 0) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
381 ch = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
382 sticky = "after"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
383 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
384 if (!order) return get(sticky == "before" ? ch - 1 : ch, sticky == "before")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
385
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
386 function getBidi(ch, partPos, invert) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
387 let part = order[partPos], right = part.level == 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
388 return get(invert ? ch - 1 : ch, right != invert)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
389 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
390 let partPos = getBidiPartAt(order, ch, sticky)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
391 let other = bidiOther
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
392 let val = getBidi(ch, partPos, sticky == "before")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
393 if (other != null) val.other = getBidi(ch, other, sticky != "before")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
394 return val
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
395 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
396
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
397 // Used to cheaply estimate the coordinates for a position. Used for
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
398 // intermediate scroll updates.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
399 export function estimateCoords(cm, pos) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
400 let left = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
401 pos = clipPos(cm.doc, pos)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
402 if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
403 let lineObj = getLine(cm.doc, pos.line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
404 let top = heightAtLine(lineObj) + paddingTop(cm.display)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
405 return {left: left, right: left, top: top, bottom: top + lineObj.height}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
406 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
407
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
408 // Positions returned by coordsChar contain some extra information.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
409 // xRel is the relative x position of the input coordinates compared
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
410 // to the found position (so xRel > 0 means the coordinates are to
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
411 // the right of the character position, for example). When outside
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
412 // is true, that means the coordinates lie outside the line's
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
413 // vertical range.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
414 function PosWithInfo(line, ch, sticky, outside, xRel) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
415 let pos = Pos(line, ch, sticky)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
416 pos.xRel = xRel
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
417 if (outside) pos.outside = outside
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
418 return pos
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
419 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
420
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
421 // Compute the character position closest to the given coordinates.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
422 // Input must be lineSpace-local ("div" coordinate system).
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
423 export function coordsChar(cm, x, y) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
424 let doc = cm.doc
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
425 y += cm.display.viewOffset
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
426 if (y < 0) return PosWithInfo(doc.first, 0, null, -1, -1)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
427 let lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
428 if (lineN > last)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
429 return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
430 if (x < 0) x = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
431
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
432 let lineObj = getLine(doc, lineN)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
433 for (;;) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
434 let found = coordsCharInner(cm, lineObj, lineN, x, y)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
435 let collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
436 if (!collapsed) return found
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
437 let rangeEnd = collapsed.find(1)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
438 if (rangeEnd.line == lineN) return rangeEnd
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
439 lineObj = getLine(doc, lineN = rangeEnd.line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
440 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
441 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
442
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
443 function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
444 y -= widgetTopHeight(lineObj)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
445 let end = lineObj.text.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
446 let begin = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y, end, 0)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
447 end = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch).top > y, begin, end)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
448 return {begin, end}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
449 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
450
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
451 export function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
452 if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
453 let targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
454 return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
455 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
456
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
457 // Returns true if the given side of a box is after the given
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
458 // coordinates, in top-to-bottom, left-to-right order.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
459 function boxIsAfter(box, x, y, left) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
460 return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
461 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
462
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
463 function coordsCharInner(cm, lineObj, lineNo, x, y) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
464 // Move y into line-local coordinate space
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
465 y -= heightAtLine(lineObj)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
466 let preparedMeasure = prepareMeasureForLine(cm, lineObj)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
467 // When directly calling `measureCharPrepared`, we have to adjust
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
468 // for the widgets at this line.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
469 let widgetHeight = widgetTopHeight(lineObj)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
470 let begin = 0, end = lineObj.text.length, ltr = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
471
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
472 let order = getOrder(lineObj, cm.doc.direction)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
473 // If the line isn't plain left-to-right text, first figure out
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
474 // which bidi section the coordinates fall into.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
475 if (order) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
476 let part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
477 (cm, lineObj, lineNo, preparedMeasure, order, x, y)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
478 ltr = part.level != 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
479 // The awkward -1 offsets are needed because findFirst (called
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
480 // on these below) will treat its first bound as inclusive,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
481 // second as exclusive, but we want to actually address the
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
482 // characters in the part's range
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
483 begin = ltr ? part.from : part.to - 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
484 end = ltr ? part.to : part.from - 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
485 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
486
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
487 // A binary search to find the first character whose bounding box
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
488 // starts after the coordinates. If we run across any whose box wrap
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
489 // the coordinates, store that.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
490 let chAround = null, boxAround = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
491 let ch = findFirst(ch => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
492 let box = measureCharPrepared(cm, preparedMeasure, ch)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
493 box.top += widgetHeight; box.bottom += widgetHeight
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
494 if (!boxIsAfter(box, x, y, false)) return false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
495 if (box.top <= y && box.left <= x) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
496 chAround = ch
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
497 boxAround = box
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
498 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
499 return true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
500 }, begin, end)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
501
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
502 let baseX, sticky, outside = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
503 // If a box around the coordinates was found, use that
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
504 if (boxAround) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
505 // Distinguish coordinates nearer to the left or right side of the box
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
506 let atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
507 ch = chAround + (atStart ? 0 : 1)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
508 sticky = atStart ? "after" : "before"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
509 baseX = atLeft ? boxAround.left : boxAround.right
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
510 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
511 // (Adjust for extended bound, if necessary.)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
512 if (!ltr && (ch == end || ch == begin)) ch++
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
513 // To determine which side to associate with, get the box to the
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
514 // left of the character and compare it's vertical position to the
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
515 // coordinates
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
516 sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" :
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
517 (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ?
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
518 "after" : "before"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
519 // Now get accurate coordinates for this place, in order to get a
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
520 // base X position
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
521 let coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
522 baseX = coords.left
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
523 outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
524 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
525
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
526 ch = skipExtendingChars(lineObj.text, ch, 1)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
527 return PosWithInfo(lineNo, ch, sticky, outside, x - baseX)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
528 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
529
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
530 function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
531 // Bidi parts are sorted left-to-right, and in a non-line-wrapping
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
532 // situation, we can take this ordering to correspond to the visual
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
533 // ordering. This finds the first part whose end is after the given
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
534 // coordinates.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
535 let index = findFirst(i => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
536 let part = order[i], ltr = part.level != 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
537 return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"),
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
538 "line", lineObj, preparedMeasure), x, y, true)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
539 }, 0, order.length - 1)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
540 let part = order[index]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
541 // If this isn't the first part, the part's start is also after
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
542 // the coordinates, and the coordinates aren't on the same line as
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
543 // that start, move one part back.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
544 if (index > 0) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
545 let ltr = part.level != 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
546 let start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"),
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
547 "line", lineObj, preparedMeasure)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
548 if (boxIsAfter(start, x, y, true) && start.top > y)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
549 part = order[index - 1]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
550 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
551 return part
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
552 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
553
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
554 function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
555 // In a wrapped line, rtl text on wrapping boundaries can do things
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
556 // that don't correspond to the ordering in our `order` array at
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
557 // all, so a binary search doesn't work, and we want to return a
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
558 // part that only spans one line so that the binary search in
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
559 // coordsCharInner is safe. As such, we first find the extent of the
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
560 // wrapped line, and then do a flat search in which we discard any
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
561 // spans that aren't on the line.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
562 let {begin, end} = wrappedLineExtent(cm, lineObj, preparedMeasure, y)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
563 if (/\s/.test(lineObj.text.charAt(end - 1))) end--
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
564 let part = null, closestDist = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
565 for (let i = 0; i < order.length; i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
566 let p = order[i]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
567 if (p.from >= end || p.to <= begin) continue
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
568 let ltr = p.level != 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
569 let endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
570 // Weigh against spans ending before this, so that they are only
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
571 // picked if nothing ends after
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
572 let dist = endX < x ? x - endX + 1e9 : endX - x
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
573 if (!part || closestDist > dist) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
574 part = p
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
575 closestDist = dist
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
576 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
577 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
578 if (!part) part = order[order.length - 1]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
579 // Clip the part to the wrapped line.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
580 if (part.from < begin) part = {from: begin, to: part.to, level: part.level}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
581 if (part.to > end) part = {from: part.from, to: end, level: part.level}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
582 return part
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
583 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
584
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
585 let measureText
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
586 // Compute the default text height.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
587 export function textHeight(display) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
588 if (display.cachedTextHeight != null) return display.cachedTextHeight
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
589 if (measureText == null) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
590 measureText = elt("pre", null, "CodeMirror-line-like")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
591 // Measure a bunch of lines, for browsers that compute
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
592 // fractional heights.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
593 for (let i = 0; i < 49; ++i) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
594 measureText.appendChild(document.createTextNode("x"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
595 measureText.appendChild(elt("br"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
596 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
597 measureText.appendChild(document.createTextNode("x"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
598 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
599 removeChildrenAndAdd(display.measure, measureText)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
600 let height = measureText.offsetHeight / 50
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
601 if (height > 3) display.cachedTextHeight = height
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
602 removeChildren(display.measure)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
603 return height || 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
604 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
605
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
606 // Compute the default character width.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
607 export function charWidth(display) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
608 if (display.cachedCharWidth != null) return display.cachedCharWidth
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
609 let anchor = elt("span", "xxxxxxxxxx")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
610 let pre = elt("pre", [anchor], "CodeMirror-line-like")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
611 removeChildrenAndAdd(display.measure, pre)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
612 let rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
613 if (width > 2) display.cachedCharWidth = width
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
614 return width || 10
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
615 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
616
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
617 // Do a bulk-read of the DOM positions and sizes needed to draw the
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
618 // view, so that we don't interleave reading and writing to the DOM.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
619 export function getDimensions(cm) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
620 let d = cm.display, left = {}, width = {}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
621 let gutterLeft = d.gutters.clientLeft
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
622 for (let n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
623 let id = cm.display.gutterSpecs[i].className
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
624 left[id] = n.offsetLeft + n.clientLeft + gutterLeft
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
625 width[id] = n.clientWidth
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
626 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
627 return {fixedPos: compensateForHScroll(d),
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
628 gutterTotalWidth: d.gutters.offsetWidth,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
629 gutterLeft: left,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
630 gutterWidth: width,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
631 wrapperWidth: d.wrapper.clientWidth}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
632 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
633
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
634 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
635 // but using getBoundingClientRect to get a sub-pixel-accurate
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
636 // result.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
637 export function compensateForHScroll(display) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
638 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
639 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
640
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
641 // Returns a function that estimates the height of a line, to use as
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
642 // first approximation until the line becomes visible (and is thus
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
643 // properly measurable).
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
644 export function estimateHeight(cm) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
645 let th = textHeight(cm.display), wrapping = cm.options.lineWrapping
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
646 let perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
647 return line => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
648 if (lineIsHidden(cm.doc, line)) return 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
649
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
650 let widgetsHeight = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
651 if (line.widgets) for (let i = 0; i < line.widgets.length; i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
652 if (line.widgets[i].height) widgetsHeight += line.widgets[i].height
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
653 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
654
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
655 if (wrapping)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
656 return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
657 else
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
658 return widgetsHeight + th
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
659 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
660 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
661
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
662 export function estimateLineHeights(cm) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
663 let doc = cm.doc, est = estimateHeight(cm)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
664 doc.iter(line => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
665 let estHeight = est(line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
666 if (estHeight != line.height) updateLineHeight(line, estHeight)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
667 })
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
668 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
669
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
670 // Given a mouse event, find the corresponding position. If liberal
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
671 // is false, it checks whether a gutter or scrollbar was clicked,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
672 // and returns null if it was. forRect is used by rectangular
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
673 // selections, and tries to estimate a character position even for
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
674 // coordinates beyond the right of the text.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
675 export function posFromMouse(cm, e, liberal, forRect) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
676 let display = cm.display
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
677 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
678
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
679 let x, y, space = display.lineSpace.getBoundingClientRect()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
680 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
681 try { x = e.clientX - space.left; y = e.clientY - space.top }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
682 catch (e) { return null }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
683 let coords = coordsChar(cm, x, y), line
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
684 if (forRect && coords.xRel > 0 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
685 let colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
686 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
687 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
688 return coords
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
689 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
690
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
691 // Find the view element corresponding to a given line. Return null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
692 // when the line isn't visible.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
693 export function findViewIndex(cm, n) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
694 if (n >= cm.display.viewTo) return null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
695 n -= cm.display.viewFrom
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
696 if (n < 0) return null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
697 let view = cm.display.view
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
698 for (let i = 0; i < view.length; i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
699 n -= view[i].size
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
700 if (n < 0) return i
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
701 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
702 }