Mercurial
comparison .cms/lib/codemirror/src/display/operations.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 { clipPos } from "../line/pos.js" | |
2 import { findMaxLine } from "../line/spans.js" | |
3 import { displayWidth, measureChar, scrollGap } from "../measurement/position_measurement.js" | |
4 import { signal } from "../util/event.js" | |
5 import { activeElt, root } from "../util/dom.js" | |
6 import { finishOperation, pushOperation } from "../util/operation_group.js" | |
7 | |
8 import { ensureFocus } from "./focus.js" | |
9 import { measureForScrollbars, updateScrollbars } from "./scrollbars.js" | |
10 import { restartBlink } from "./selection.js" | |
11 import { maybeScrollWindow, scrollPosIntoView, setScrollLeft, setScrollTop } from "./scrolling.js" | |
12 import { DisplayUpdate, maybeClipScrollbars, postUpdateDisplay, setDocumentHeight, updateDisplayIfNeeded } from "./update_display.js" | |
13 import { updateHeightsInViewport } from "./update_lines.js" | |
14 | |
15 // Operations are used to wrap a series of changes to the editor | |
16 // state in such a way that each change won't have to update the | |
17 // cursor and display (which would be awkward, slow, and | |
18 // error-prone). Instead, display updates are batched and then all | |
19 // combined and executed at once. | |
20 | |
21 let nextOpId = 0 | |
22 // Start a new operation. | |
23 export function startOperation(cm) { | |
24 cm.curOp = { | |
25 cm: cm, | |
26 viewChanged: false, // Flag that indicates that lines might need to be redrawn | |
27 startHeight: cm.doc.height, // Used to detect need to update scrollbar | |
28 forceUpdate: false, // Used to force a redraw | |
29 updateInput: 0, // Whether to reset the input textarea | |
30 typing: false, // Whether this reset should be careful to leave existing text (for compositing) | |
31 changeObjs: null, // Accumulated changes, for firing change events | |
32 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on | |
33 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already | |
34 selectionChanged: false, // Whether the selection needs to be redrawn | |
35 updateMaxLine: false, // Set when the widest line needs to be determined anew | |
36 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet | |
37 scrollToPos: null, // Used to scroll to a specific position | |
38 focus: false, | |
39 id: ++nextOpId, // Unique ID | |
40 markArrays: null // Used by addMarkedSpan | |
41 } | |
42 pushOperation(cm.curOp) | |
43 } | |
44 | |
45 // Finish an operation, updating the display and signalling delayed events | |
46 export function endOperation(cm) { | |
47 let op = cm.curOp | |
48 if (op) finishOperation(op, group => { | |
49 for (let i = 0; i < group.ops.length; i++) | |
50 group.ops[i].cm.curOp = null | |
51 endOperations(group) | |
52 }) | |
53 } | |
54 | |
55 // The DOM updates done when an operation finishes are batched so | |
56 // that the minimum number of relayouts are required. | |
57 function endOperations(group) { | |
58 let ops = group.ops | |
59 for (let i = 0; i < ops.length; i++) // Read DOM | |
60 endOperation_R1(ops[i]) | |
61 for (let i = 0; i < ops.length; i++) // Write DOM (maybe) | |
62 endOperation_W1(ops[i]) | |
63 for (let i = 0; i < ops.length; i++) // Read DOM | |
64 endOperation_R2(ops[i]) | |
65 for (let i = 0; i < ops.length; i++) // Write DOM (maybe) | |
66 endOperation_W2(ops[i]) | |
67 for (let i = 0; i < ops.length; i++) // Read DOM | |
68 endOperation_finish(ops[i]) | |
69 } | |
70 | |
71 function endOperation_R1(op) { | |
72 let cm = op.cm, display = cm.display | |
73 maybeClipScrollbars(cm) | |
74 if (op.updateMaxLine) findMaxLine(cm) | |
75 | |
76 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || | |
77 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || | |
78 op.scrollToPos.to.line >= display.viewTo) || | |
79 display.maxLineChanged && cm.options.lineWrapping | |
80 op.update = op.mustUpdate && | |
81 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate) | |
82 } | |
83 | |
84 function endOperation_W1(op) { | |
85 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) | |
86 } | |
87 | |
88 function endOperation_R2(op) { | |
89 let cm = op.cm, display = cm.display | |
90 if (op.updatedDisplay) updateHeightsInViewport(cm) | |
91 | |
92 op.barMeasure = measureForScrollbars(cm) | |
93 | |
94 // If the max line changed since it was last measured, measure it, | |
95 // and ensure the document's width matches it. | |
96 // updateDisplay_W2 will use these properties to do the actual resizing | |
97 if (display.maxLineChanged && !cm.options.lineWrapping) { | |
98 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3 | |
99 cm.display.sizerWidth = op.adjustWidthTo | |
100 op.barMeasure.scrollWidth = | |
101 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth) | |
102 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)) | |
103 } | |
104 | |
105 if (op.updatedDisplay || op.selectionChanged) | |
106 op.preparedSelection = display.input.prepareSelection() | |
107 } | |
108 | |
109 function endOperation_W2(op) { | |
110 let cm = op.cm | |
111 | |
112 if (op.adjustWidthTo != null) { | |
113 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" | |
114 if (op.maxScrollLeft < cm.doc.scrollLeft) | |
115 setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) | |
116 cm.display.maxLineChanged = false | |
117 } | |
118 | |
119 let takeFocus = op.focus && op.focus == activeElt(root(cm)) | |
120 if (op.preparedSelection) | |
121 cm.display.input.showSelection(op.preparedSelection, takeFocus) | |
122 if (op.updatedDisplay || op.startHeight != cm.doc.height) | |
123 updateScrollbars(cm, op.barMeasure) | |
124 if (op.updatedDisplay) | |
125 setDocumentHeight(cm, op.barMeasure) | |
126 | |
127 if (op.selectionChanged) restartBlink(cm) | |
128 | |
129 if (cm.state.focused && op.updateInput) | |
130 cm.display.input.reset(op.typing) | |
131 if (takeFocus) ensureFocus(op.cm) | |
132 } | |
133 | |
134 function endOperation_finish(op) { | |
135 let cm = op.cm, display = cm.display, doc = cm.doc | |
136 | |
137 if (op.updatedDisplay) postUpdateDisplay(cm, op.update) | |
138 | |
139 // Abort mouse wheel delta measurement, when scrolling explicitly | |
140 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) | |
141 display.wheelStartX = display.wheelStartY = null | |
142 | |
143 // Propagate the scroll position to the actual DOM scroller | |
144 if (op.scrollTop != null) setScrollTop(cm, op.scrollTop, op.forceScroll) | |
145 | |
146 if (op.scrollLeft != null) setScrollLeft(cm, op.scrollLeft, true, true) | |
147 // If we need to scroll a specific position into view, do so. | |
148 if (op.scrollToPos) { | |
149 let rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), | |
150 clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin) | |
151 maybeScrollWindow(cm, rect) | |
152 } | |
153 | |
154 // Fire events for markers that are hidden/unidden by editing or | |
155 // undoing | |
156 let hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers | |
157 if (hidden) for (let i = 0; i < hidden.length; ++i) | |
158 if (!hidden[i].lines.length) signal(hidden[i], "hide") | |
159 if (unhidden) for (let i = 0; i < unhidden.length; ++i) | |
160 if (unhidden[i].lines.length) signal(unhidden[i], "unhide") | |
161 | |
162 if (display.wrapper.offsetHeight) | |
163 doc.scrollTop = cm.display.scroller.scrollTop | |
164 | |
165 // Fire change events, and delayed event handlers | |
166 if (op.changeObjs) | |
167 signal(cm, "changes", cm, op.changeObjs) | |
168 if (op.update) | |
169 op.update.finish() | |
170 } | |
171 | |
172 // Run the given function in an operation | |
173 export function runInOp(cm, f) { | |
174 if (cm.curOp) return f() | |
175 startOperation(cm) | |
176 try { return f() } | |
177 finally { endOperation(cm) } | |
178 } | |
179 // Wraps a function in an operation. Returns the wrapped function. | |
180 export function operation(cm, f) { | |
181 return function() { | |
182 if (cm.curOp) return f.apply(cm, arguments) | |
183 startOperation(cm) | |
184 try { return f.apply(cm, arguments) } | |
185 finally { endOperation(cm) } | |
186 } | |
187 } | |
188 // Used to add methods to editor and doc instances, wrapping them in | |
189 // operations. | |
190 export function methodOp(f) { | |
191 return function() { | |
192 if (this.curOp) return f.apply(this, arguments) | |
193 startOperation(this) | |
194 try { return f.apply(this, arguments) } | |
195 finally { endOperation(this) } | |
196 } | |
197 } | |
198 export function docMethodOp(f) { | |
199 return function() { | |
200 let cm = this.cm | |
201 if (!cm || cm.curOp) return f.apply(this, arguments) | |
202 startOperation(cm) | |
203 try { return f.apply(this, arguments) } | |
204 finally { endOperation(cm) } | |
205 } | |
206 } |