comparison .cms/lib/codemirror/src/display/update_display.js @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:78edf6b517a0
1 import { sawCollapsedSpans } from "../line/saw_special_spans.js"
2 import { heightAtLine, visualLineEndNo, visualLineNo } from "../line/spans.js"
3 import { getLine, lineNumberFor } from "../line/utils_line.js"
4 import { displayHeight, displayWidth, getDimensions, paddingVert, scrollGap } from "../measurement/position_measurement.js"
5 import { mac, webkit } from "../util/browser.js"
6 import { activeElt, removeChildren, contains, win, root, rootNode } from "../util/dom.js"
7 import { hasHandler, signal } from "../util/event.js"
8 import { signalLater } from "../util/operation_group.js"
9 import { indexOf } from "../util/misc.js"
10
11 import { buildLineElement, updateLineForChanges } from "./update_line.js"
12 import { startWorker } from "./highlight_worker.js"
13 import { maybeUpdateLineNumberWidth } from "./line_numbers.js"
14 import { measureForScrollbars, updateScrollbars } from "./scrollbars.js"
15 import { updateSelection } from "./selection.js"
16 import { updateHeightsInViewport, visibleLines } from "./update_lines.js"
17 import { adjustView, countDirtyView, resetView } from "./view_tracking.js"
18
19 // DISPLAY DRAWING
20
21 export class DisplayUpdate {
22 constructor(cm, viewport, force) {
23 let display = cm.display
24
25 this.viewport = viewport
26 // Store some values that we'll need later (but don't want to force a relayout for)
27 this.visible = visibleLines(display, cm.doc, viewport)
28 this.editorIsHidden = !display.wrapper.offsetWidth
29 this.wrapperHeight = display.wrapper.clientHeight
30 this.wrapperWidth = display.wrapper.clientWidth
31 this.oldDisplayWidth = displayWidth(cm)
32 this.force = force
33 this.dims = getDimensions(cm)
34 this.events = []
35 }
36
37 signal(emitter, type) {
38 if (hasHandler(emitter, type))
39 this.events.push(arguments)
40 }
41 finish() {
42 for (let i = 0; i < this.events.length; i++)
43 signal.apply(null, this.events[i])
44 }
45 }
46
47 export function maybeClipScrollbars(cm) {
48 let display = cm.display
49 if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
50 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth
51 display.heightForcer.style.height = scrollGap(cm) + "px"
52 display.sizer.style.marginBottom = -display.nativeBarWidth + "px"
53 display.sizer.style.borderRightWidth = scrollGap(cm) + "px"
54 display.scrollbarsClipped = true
55 }
56 }
57
58 function selectionSnapshot(cm) {
59 if (cm.hasFocus()) return null
60 let active = activeElt(root(cm))
61 if (!active || !contains(cm.display.lineDiv, active)) return null
62 let result = {activeElt: active}
63 if (window.getSelection) {
64 let sel = win(cm).getSelection()
65 if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {
66 result.anchorNode = sel.anchorNode
67 result.anchorOffset = sel.anchorOffset
68 result.focusNode = sel.focusNode
69 result.focusOffset = sel.focusOffset
70 }
71 }
72 return result
73 }
74
75 function restoreSelection(snapshot) {
76 if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(rootNode(snapshot.activeElt))) return
77 snapshot.activeElt.focus()
78 if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&
79 snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
80 let doc = snapshot.activeElt.ownerDocument
81 let sel = doc.defaultView.getSelection(), range = doc.createRange()
82 range.setEnd(snapshot.anchorNode, snapshot.anchorOffset)
83 range.collapse(false)
84 sel.removeAllRanges()
85 sel.addRange(range)
86 sel.extend(snapshot.focusNode, snapshot.focusOffset)
87 }
88 }
89
90 // Does the actual updating of the line display. Bails out
91 // (returning false) when there is nothing to be done and forced is
92 // false.
93 export function updateDisplayIfNeeded(cm, update) {
94 let display = cm.display, doc = cm.doc
95
96 if (update.editorIsHidden) {
97 resetView(cm)
98 return false
99 }
100
101 // Bail out if the visible area is already rendered and nothing changed.
102 if (!update.force &&
103 update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
104 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
105 display.renderedView == display.view && countDirtyView(cm) == 0)
106 return false
107
108 if (maybeUpdateLineNumberWidth(cm)) {
109 resetView(cm)
110 update.dims = getDimensions(cm)
111 }
112
113 // Compute a suitable new viewport (from & to)
114 let end = doc.first + doc.size
115 let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first)
116 let to = Math.min(end, update.visible.to + cm.options.viewportMargin)
117 if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom)
118 if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo)
119 if (sawCollapsedSpans) {
120 from = visualLineNo(cm.doc, from)
121 to = visualLineEndNo(cm.doc, to)
122 }
123
124 let different = from != display.viewFrom || to != display.viewTo ||
125 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth
126 adjustView(cm, from, to)
127
128 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom))
129 // Position the mover div to align with the current scroll position
130 cm.display.mover.style.top = display.viewOffset + "px"
131
132 let toUpdate = countDirtyView(cm)
133 if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
134 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
135 return false
136
137 // For big changes, we hide the enclosing element during the
138 // update, since that speeds up the operations on most browsers.
139 let selSnapshot = selectionSnapshot(cm)
140 if (toUpdate > 4) display.lineDiv.style.display = "none"
141 patchDisplay(cm, display.updateLineNumbers, update.dims)
142 if (toUpdate > 4) display.lineDiv.style.display = ""
143 display.renderedView = display.view
144 // There might have been a widget with a focused element that got
145 // hidden or updated, if so re-focus it.
146 restoreSelection(selSnapshot)
147
148 // Prevent selection and cursors from interfering with the scroll
149 // width and height.
150 removeChildren(display.cursorDiv)
151 removeChildren(display.selectionDiv)
152 display.gutters.style.height = display.sizer.style.minHeight = 0
153
154 if (different) {
155 display.lastWrapHeight = update.wrapperHeight
156 display.lastWrapWidth = update.wrapperWidth
157 startWorker(cm, 400)
158 }
159
160 display.updateLineNumbers = null
161
162 return true
163 }
164
165 export function postUpdateDisplay(cm, update) {
166 let viewport = update.viewport
167
168 for (let first = true;; first = false) {
169 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
170 // Clip forced viewport to actual scrollable area.
171 if (viewport && viewport.top != null)
172 viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}
173 // Updated line heights might result in the drawn area not
174 // actually covering the viewport. Keep looping until it does.
175 update.visible = visibleLines(cm.display, cm.doc, viewport)
176 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
177 break
178 } else if (first) {
179 update.visible = visibleLines(cm.display, cm.doc, viewport)
180 }
181 if (!updateDisplayIfNeeded(cm, update)) break
182 updateHeightsInViewport(cm)
183 let barMeasure = measureForScrollbars(cm)
184 updateSelection(cm)
185 updateScrollbars(cm, barMeasure)
186 setDocumentHeight(cm, barMeasure)
187 update.force = false
188 }
189
190 update.signal(cm, "update", cm)
191 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
192 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo)
193 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo
194 }
195 }
196
197 export function updateDisplaySimple(cm, viewport) {
198 let update = new DisplayUpdate(cm, viewport)
199 if (updateDisplayIfNeeded(cm, update)) {
200 updateHeightsInViewport(cm)
201 postUpdateDisplay(cm, update)
202 let barMeasure = measureForScrollbars(cm)
203 updateSelection(cm)
204 updateScrollbars(cm, barMeasure)
205 setDocumentHeight(cm, barMeasure)
206 update.finish()
207 }
208 }
209
210 // Sync the actual display DOM structure with display.view, removing
211 // nodes for lines that are no longer in view, and creating the ones
212 // that are not there yet, and updating the ones that are out of
213 // date.
214 function patchDisplay(cm, updateNumbersFrom, dims) {
215 let display = cm.display, lineNumbers = cm.options.lineNumbers
216 let container = display.lineDiv, cur = container.firstChild
217
218 function rm(node) {
219 let next = node.nextSibling
220 // Works around a throw-scroll bug in OS X Webkit
221 if (webkit && mac && cm.display.currentWheelTarget == node)
222 node.style.display = "none"
223 else
224 node.parentNode.removeChild(node)
225 return next
226 }
227
228 let view = display.view, lineN = display.viewFrom
229 // Loop over the elements in the view, syncing cur (the DOM nodes
230 // in display.lineDiv) with the view as we go.
231 for (let i = 0; i < view.length; i++) {
232 let lineView = view[i]
233 if (lineView.hidden) {
234 } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
235 let node = buildLineElement(cm, lineView, lineN, dims)
236 container.insertBefore(node, cur)
237 } else { // Already drawn
238 while (cur != lineView.node) cur = rm(cur)
239 let updateNumber = lineNumbers && updateNumbersFrom != null &&
240 updateNumbersFrom <= lineN && lineView.lineNumber
241 if (lineView.changes) {
242 if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false
243 updateLineForChanges(cm, lineView, lineN, dims)
244 }
245 if (updateNumber) {
246 removeChildren(lineView.lineNumber)
247 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)))
248 }
249 cur = lineView.node.nextSibling
250 }
251 lineN += lineView.size
252 }
253 while (cur) cur = rm(cur)
254 }
255
256 export function updateGutterSpace(display) {
257 let width = display.gutters.offsetWidth
258 display.sizer.style.marginLeft = width + "px"
259 // Send an event to consumers responding to changes in gutter width.
260 signalLater(display, "gutterChanged", display)
261 }
262
263 export function setDocumentHeight(cm, measure) {
264 cm.display.sizer.style.minHeight = measure.docHeight + "px"
265 cm.display.heightForcer.style.top = measure.docHeight + "px"
266 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"
267 }