Mercurial
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 } |