Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
1import { RangeSet, MapMode, RangeValue, findClusterBreak, EditorSelection, Facet, StateEffect, ChangeSet, Text, findColumn, CharCategory, EditorState, Annotation, Transaction, Prec, codePointAt, codePointSize, combineConfig, StateField, RangeSetBuilder, countColumn } from '@codemirror/state';
2import { StyleModule } from 'style-mod';
3import { keyName, base, shift } from 'w3c-keyname';
4import elt from 'crelt';
5
6let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
7let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
8const ie_edge = /*@__PURE__*//Edge\/(\d+)/.exec(nav.userAgent);
9const ie_upto10 = /*@__PURE__*//MSIE \d/.test(nav.userAgent);
10const ie_11up = /*@__PURE__*//Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
11const ie = !!(ie_upto10 || ie_11up || ie_edge);
12const gecko = !ie && /*@__PURE__*//gecko\/(\d+)/i.test(nav.userAgent);
13const chrome = !ie && /*@__PURE__*//Chrome\/(\d+)/.exec(nav.userAgent);
14const webkit = "webkitFontSmoothing" in doc.documentElement.style;
15const safari = !ie && /*@__PURE__*//Apple Computer/.test(nav.vendor);
16const ios = safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
17var browser = {
18 mac: ios || /*@__PURE__*//Mac/.test(nav.platform),
19 windows: /*@__PURE__*//Win/.test(nav.platform),
20 linux: /*@__PURE__*//Linux|X11/.test(nav.platform),
21 ie,
22 ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
23 gecko,
24 gecko_version: gecko ? +(/*@__PURE__*//Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
25 chrome: !!chrome,
26 chrome_version: chrome ? +chrome[1] : 0,
27 ios,
28 android: /*@__PURE__*//Android\b/.test(nav.userAgent),
29 webkit,
30 webkit_version: webkit ? +(/*@__PURE__*//\bAppleWebKit\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
31 safari,
32 safari_version: safari ? +(/*@__PURE__*//\bVersion\/(\d+(\.\d+)?)/.exec(nav.userAgent) || [0, 0])[1] : 0,
33 tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
34};
35
36function combineAttrs(source, target) {
37 for (let name in source) {
38 if (name == "class" && target.class)
39 target.class += " " + source.class;
40 else if (name == "style" && target.style)
41 target.style += ";" + source.style;
42 else
43 target[name] = source[name];
44 }
45 return target;
46}
47const noAttrs = /*@__PURE__*/Object.create(null);
48function attrsEq(a, b, ignore) {
49 if (a == b)
50 return true;
51 if (!a)
52 a = noAttrs;
53 if (!b)
54 b = noAttrs;
55 let keysA = Object.keys(a), keysB = Object.keys(b);
56 if (keysA.length - (ignore && keysA.indexOf(ignore) > -1 ? 1 : 0) !=
57 keysB.length - (ignore && keysB.indexOf(ignore) > -1 ? 1 : 0))
58 return false;
59 for (let key of keysA) {
60 if (key != ignore && (keysB.indexOf(key) == -1 || a[key] !== b[key]))
61 return false;
62 }
63 return true;
64}
65function setAttrs(dom, attrs) {
66 for (let i = dom.attributes.length - 1; i >= 0; i--) {
67 let name = dom.attributes[i].name;
68 if (attrs[name] == null)
69 dom.removeAttribute(name);
70 }
71 for (let name in attrs) {
72 let value = attrs[name];
73 if (name == "style")
74 dom.style.cssText = value;
75 else if (dom.getAttribute(name) != value)
76 dom.setAttribute(name, value);
77 }
78}
79function updateAttrs(dom, prev, attrs) {
80 let changed = false;
81 if (prev)
82 for (let name in prev)
83 if (!(attrs && name in attrs)) {
84 changed = true;
85 if (name == "style")
86 dom.style.cssText = "";
87 else
88 dom.removeAttribute(name);
89 }
90 if (attrs)
91 for (let name in attrs)
92 if (!(prev && prev[name] == attrs[name])) {
93 changed = true;
94 if (name == "style")
95 dom.style.cssText = attrs[name];
96 else
97 dom.setAttribute(name, attrs[name]);
98 }
99 return changed;
100}
101function getAttrs(dom) {
102 let attrs = Object.create(null);
103 for (let i = 0; i < dom.attributes.length; i++) {
104 let attr = dom.attributes[i];
105 attrs[attr.name] = attr.value;
106 }
107 return attrs;
108}
109
110/**
111Widgets added to the content are described by subclasses of this
112class. Using a description object like that makes it possible to
113delay creating of the DOM structure for a widget until it is
114needed, and to avoid redrawing widgets even if the decorations
115that define them are recreated.
116*/
117class WidgetType {
118 /**
119 Compare this instance to another instance of the same type.
120 (TypeScript can't express this, but only instances of the same
121 specific class will be passed to this method.) This is used to
122 avoid redrawing widgets when they are replaced by a new
123 decoration of the same type. The default implementation just
124 returns `false`, which will cause new instances of the widget to
125 always be redrawn.
126 */
127 eq(widget) { return false; }
128 /**
129 Update a DOM element created by a widget of the same type (but
130 different, non-`eq` content) to reflect this widget. May return
131 true to indicate that it could update, false to indicate it
132 couldn't (in which case the widget will be redrawn). The default
133 implementation just returns false.
134 */
135 updateDOM(dom, view) { return false; }
136 /**
137 @internal
138 */
139 compare(other) {
140 return this == other || this.constructor == other.constructor && this.eq(other);
141 }
142 /**
143 The estimated height this widget will have, to be used when
144 estimating the height of content that hasn't been drawn. May
145 return -1 to indicate you don't know. The default implementation
146 returns -1.
147 */
148 get estimatedHeight() { return -1; }
149 /**
150 For inline widgets that are displayed inline (as opposed to
151 `inline-block`) and introduce line breaks (through `<br>` tags
152 or textual newlines), this must indicate the amount of line
153 breaks they introduce. Defaults to 0.
154 */
155 get lineBreaks() { return 0; }
156 /**
157 Can be used to configure which kinds of events inside the widget
158 should be ignored by the editor. The default is to ignore all
159 events.
160 */
161 ignoreEvent(event) { return true; }
162 /**
163 Override the way screen coordinates for positions at/in the
164 widget are found. `pos` will be the offset into the widget, and
165 `side` the side of the position that is being queried—less than
166 zero for before, greater than zero for after, and zero for
167 directly at that position.
168 */
169 coordsAt(dom, pos, side) { return null; }
170 /**
171 @internal
172 */
173 get isHidden() { return false; }
174 /**
175 @internal
176 */
177 get editable() { return false; }
178 /**
179 This is called when the an instance of the widget is removed
180 from the editor view.
181 */
182 destroy(dom) { }
183}
184/**
185The different types of blocks that can occur in an editor view.
186*/
187var BlockType = /*@__PURE__*/(function (BlockType) {
188 /**
189 A line of text.
190 */
191 BlockType[BlockType["Text"] = 0] = "Text";
192 /**
193 A block widget associated with the position after it.
194 */
195 BlockType[BlockType["WidgetBefore"] = 1] = "WidgetBefore";
196 /**
197 A block widget associated with the position before it.
198 */
199 BlockType[BlockType["WidgetAfter"] = 2] = "WidgetAfter";
200 /**
201 A block widget [replacing](https://codemirror.net/6/docs/ref/#view.Decoration^replace) a range of content.
202 */
203 BlockType[BlockType["WidgetRange"] = 3] = "WidgetRange";
204return BlockType})(BlockType || (BlockType = {}));
205/**
206A decoration provides information on how to draw or style a piece
207of content. You'll usually use it wrapped in a
208[`Range`](https://codemirror.net/6/docs/ref/#state.Range), which adds a start and end position.
209@nonabstract
210*/
211class Decoration extends RangeValue {
212 constructor(
213 /**
214 @internal
215 */
216 startSide,
217 /**
218 @internal
219 */
220 endSide,
221 /**
222 @internal
223 */
224 widget,
225 /**
226 The config object used to create this decoration. You can
227 include additional properties in there to store metadata about
228 your decoration.
229 */
230 spec) {
231 super();
232 this.startSide = startSide;
233 this.endSide = endSide;
234 this.widget = widget;
235 this.spec = spec;
236 }
237 /**
238 @internal
239 */
240 get heightRelevant() { return false; }
241 /**
242 Create a mark decoration, which influences the styling of the
243 content in its range. Nested mark decorations will cause nested
244 DOM elements to be created. Nesting order is determined by
245 precedence of the [facet](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), with
246 the higher-precedence decorations creating the inner DOM nodes.
247 Such elements are split on line boundaries and on the boundaries
248 of lower-precedence decorations.
249 */
250 static mark(spec) {
251 return new MarkDecoration(spec);
252 }
253 /**
254 Create a widget decoration, which displays a DOM element at the
255 given position.
256 */
257 static widget(spec) {
258 let side = Math.max(-10000, Math.min(10000, spec.side || 0)), block = !!spec.block;
259 side += (block && !spec.inlineOrder)
260 ? (side > 0 ? 300000000 /* Side.BlockAfter */ : -400000000 /* Side.BlockBefore */)
261 : (side > 0 ? 100000000 /* Side.InlineAfter */ : -100000000 /* Side.InlineBefore */);
262 return new PointDecoration(spec, side, side, block, spec.widget || null, false);
263 }
264 /**
265 Create a replace decoration which replaces the given range with
266 a widget, or simply hides it.
267 */
268 static replace(spec) {
269 let block = !!spec.block, startSide, endSide;
270 if (spec.isBlockGap) {
271 startSide = -500000000 /* Side.GapStart */;
272 endSide = 400000000 /* Side.GapEnd */;
273 }
274 else {
275 let { start, end } = getInclusive(spec, block);
276 startSide = (start ? (block ? -300000000 /* Side.BlockIncStart */ : -1 /* Side.InlineIncStart */) : 500000000 /* Side.NonIncStart */) - 1;
277 endSide = (end ? (block ? 200000000 /* Side.BlockIncEnd */ : 1 /* Side.InlineIncEnd */) : -600000000 /* Side.NonIncEnd */) + 1;
278 }
279 return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
280 }
281 /**
282 Create a line decoration, which can add DOM attributes to the
283 line starting at the given position.
284 */
285 static line(spec) {
286 return new LineDecoration(spec);
287 }
288 /**
289 Build a [`DecorationSet`](https://codemirror.net/6/docs/ref/#view.DecorationSet) from the given
290 decorated range or ranges. If the ranges aren't already sorted,
291 pass `true` for `sort` to make the library sort them for you.
292 */
293 static set(of, sort = false) {
294 return RangeSet.of(of, sort);
295 }
296 /**
297 @internal
298 */
299 hasHeight() { return this.widget ? this.widget.estimatedHeight > -1 : false; }
300}
301/**
302The empty set of decorations.
303*/
304Decoration.none = RangeSet.empty;
305class MarkDecoration extends Decoration {
306 constructor(spec) {
307 let { start, end } = getInclusive(spec);
308 super(start ? -1 /* Side.InlineIncStart */ : 500000000 /* Side.NonIncStart */, end ? 1 /* Side.InlineIncEnd */ : -600000000 /* Side.NonIncEnd */, null, spec);
309 this.tagName = spec.tagName || "span";
310 this.attrs = spec.class && spec.attributes ? combineAttrs(spec.attributes, { class: spec.class })
311 : spec.class ? { class: spec.class } : spec.attributes || noAttrs;
312 }
313 eq(other) {
314 return this == other || other instanceof MarkDecoration && this.tagName == other.tagName && attrsEq(this.attrs, other.attrs);
315 }
316 range(from, to = from) {
317 if (from >= to)
318 throw new RangeError("Mark decorations may not be empty");
319 return super.range(from, to);
320 }
321}
322MarkDecoration.prototype.point = false;
323class LineDecoration extends Decoration {
324 constructor(spec) {
325 super(-200000000 /* Side.Line */, -200000000 /* Side.Line */, null, spec);
326 }
327 eq(other) {
328 return other instanceof LineDecoration &&
329 this.spec.class == other.spec.class &&
330 attrsEq(this.spec.attributes, other.spec.attributes);
331 }
332 range(from, to = from) {
333 if (to != from)
334 throw new RangeError("Line decoration ranges must be zero-length");
335 return super.range(from, to);
336 }
337}
338LineDecoration.prototype.mapMode = MapMode.TrackBefore;
339LineDecoration.prototype.point = true;
340class PointDecoration extends Decoration {
341 constructor(spec, startSide, endSide, block, widget, isReplace) {
342 super(startSide, endSide, widget, spec);
343 this.block = block;
344 this.isReplace = isReplace;
345 this.mapMode = !block ? MapMode.TrackDel : startSide <= 0 ? MapMode.TrackBefore : MapMode.TrackAfter;
346 }
347 // Only relevant when this.block == true
348 get type() {
349 return this.startSide != this.endSide ? BlockType.WidgetRange
350 : this.startSide <= 0 ? BlockType.WidgetBefore : BlockType.WidgetAfter;
351 }
352 get heightRelevant() {
353 return this.block || !!this.widget && (this.widget.estimatedHeight >= 5 || this.widget.lineBreaks > 0);
354 }
355 eq(other) {
356 return other instanceof PointDecoration &&
357 widgetsEq(this.widget, other.widget) &&
358 this.block == other.block &&
359 this.startSide == other.startSide && this.endSide == other.endSide;
360 }
361 range(from, to = from) {
362 if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide <= 0)))
363 throw new RangeError("Invalid range for replacement decoration");
364 if (!this.isReplace && to != from)
365 throw new RangeError("Widget decorations can only have zero-length ranges");
366 return super.range(from, to);
367 }
368}
369PointDecoration.prototype.point = true;
370function getInclusive(spec, block = false) {
371 let { inclusiveStart: start, inclusiveEnd: end } = spec;
372 if (start == null)
373 start = spec.inclusive;
374 if (end == null)
375 end = spec.inclusive;
376 return { start: start !== null && start !== void 0 ? start : block, end: end !== null && end !== void 0 ? end : block };
377}
378function widgetsEq(a, b) {
379 return a == b || !!(a && b && a.compare(b));
380}
381function addRange(from, to, ranges, margin = 0) {
382 let last = ranges.length - 1;
383 if (last >= 0 && ranges[last] + margin >= from)
384 ranges[last] = Math.max(ranges[last], to);
385 else
386 ranges.push(from, to);
387}
388/**
389A block wrapper defines a DOM node that wraps lines or other block
390wrappers at the top of the document. It affects any line or block
391widget that starts inside its range, including blocks starting
392directly at `from` but not including `to`.
393*/
394class BlockWrapper extends RangeValue {
395 constructor(tagName, attributes) {
396 super();
397 this.tagName = tagName;
398 this.attributes = attributes;
399 }
400 eq(other) {
401 return other == this ||
402 other instanceof BlockWrapper && this.tagName == other.tagName && attrsEq(this.attributes, other.attributes);
403 }
404 /**
405 Create a block wrapper object with the given tag name and
406 attributes.
407 */
408 static create(spec) {
409 return new BlockWrapper(spec.tagName, spec.attributes || noAttrs);
410 }
411 /**
412 Create a range set from the given block wrapper ranges.
413 */
414 static set(of, sort = false) {
415 return RangeSet.of(of, sort);
416 }
417}
418BlockWrapper.prototype.startSide = BlockWrapper.prototype.endSide = -1;
419
420function getSelection(root) {
421 let target;
422 // Browsers differ on whether shadow roots have a getSelection
423 // method. If it exists, use that, otherwise, call it on the
424 // document.
425 if (root.nodeType == 11) { // Shadow root
426 target = root.getSelection ? root : root.ownerDocument;
427 }
428 else {
429 target = root;
430 }
431 return target.getSelection();
432}
433function contains(dom, node) {
434 return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
435}
436function hasSelection(dom, selection) {
437 if (!selection.anchorNode)
438 return false;
439 try {
440 // Firefox will raise 'permission denied' errors when accessing
441 // properties of `sel.anchorNode` when it's in a generated CSS
442 // element.
443 return contains(dom, selection.anchorNode);
444 }
445 catch (_) {
446 return false;
447 }
448}
449function clientRectsFor(dom) {
450 if (dom.nodeType == 3)
451 return textRange(dom, 0, dom.nodeValue.length).getClientRects();
452 else if (dom.nodeType == 1)
453 return dom.getClientRects();
454 else
455 return [];
456}
457// Scans forward and backward through DOM positions equivalent to the
458// given one to see if the two are in the same place (i.e. after a
459// text node vs at the end of that text node)
460function isEquivalentPosition(node, off, targetNode, targetOff) {
461 return targetNode ? (scanFor(node, off, targetNode, targetOff, -1) ||
462 scanFor(node, off, targetNode, targetOff, 1)) : false;
463}
464function domIndex(node) {
465 for (var index = 0;; index++) {
466 node = node.previousSibling;
467 if (!node)
468 return index;
469 }
470}
471function isBlockElement(node) {
472 return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
473}
474function scanFor(node, off, targetNode, targetOff, dir) {
475 for (;;) {
476 if (node == targetNode && off == targetOff)
477 return true;
478 if (off == (dir < 0 ? 0 : maxOffset(node))) {
479 if (node.nodeName == "DIV")
480 return false;
481 let parent = node.parentNode;
482 if (!parent || parent.nodeType != 1)
483 return false;
484 off = domIndex(node) + (dir < 0 ? 0 : 1);
485 node = parent;
486 }
487 else if (node.nodeType == 1) {
488 node = node.childNodes[off + (dir < 0 ? -1 : 0)];
489 if (node.nodeType == 1 && node.contentEditable == "false")
490 return false;
491 off = dir < 0 ? maxOffset(node) : 0;
492 }
493 else {
494 return false;
495 }
496 }
497}
498function maxOffset(node) {
499 return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
500}
501function flattenRect(rect, left) {
502 let x = left ? rect.left : rect.right;
503 return { left: x, right: x, top: rect.top, bottom: rect.bottom };
504}
505function windowRect(win) {
506 let vp = win.visualViewport;
507 if (vp)
508 return {
509 left: 0, right: vp.width,
510 top: 0, bottom: vp.height
511 };
512 return { left: 0, right: win.innerWidth,
513 top: 0, bottom: win.innerHeight };
514}
515function getScale(elt, rect) {
516 let scaleX = rect.width / elt.offsetWidth;
517 let scaleY = rect.height / elt.offsetHeight;
518 if (scaleX > 0.995 && scaleX < 1.005 || !isFinite(scaleX) || Math.abs(rect.width - elt.offsetWidth) < 1)
519 scaleX = 1;
520 if (scaleY > 0.995 && scaleY < 1.005 || !isFinite(scaleY) || Math.abs(rect.height - elt.offsetHeight) < 1)
521 scaleY = 1;
522 return { scaleX, scaleY };
523}
524function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
525 let doc = dom.ownerDocument, win = doc.defaultView || window;
526 for (let cur = dom, stop = false; cur && !stop;) {
527 if (cur.nodeType == 1) { // Element
528 let bounding, top = cur == doc.body;
529 let scaleX = 1, scaleY = 1;
530 if (top) {
531 bounding = windowRect(win);
532 }
533 else {
534 if (/^(fixed|sticky)$/.test(getComputedStyle(cur).position))
535 stop = true;
536 if (cur.scrollHeight <= cur.clientHeight && cur.scrollWidth <= cur.clientWidth) {
537 cur = cur.assignedSlot || cur.parentNode;
538 continue;
539 }
540 let rect = cur.getBoundingClientRect();
541 ({ scaleX, scaleY } = getScale(cur, rect));
542 // Make sure scrollbar width isn't included in the rectangle
543 bounding = { left: rect.left, right: rect.left + cur.clientWidth * scaleX,
544 top: rect.top, bottom: rect.top + cur.clientHeight * scaleY };
545 }
546 let moveX = 0, moveY = 0;
547 if (y == "nearest") {
548 if (rect.top < bounding.top) {
549 moveY = rect.top - (bounding.top + yMargin);
550 if (side > 0 && rect.bottom > bounding.bottom + moveY)
551 moveY = rect.bottom - bounding.bottom + yMargin;
552 }
553 else if (rect.bottom > bounding.bottom) {
554 moveY = rect.bottom - bounding.bottom + yMargin;
555 if (side < 0 && (rect.top - moveY) < bounding.top)
556 moveY = rect.top - (bounding.top + yMargin);
557 }
558 }
559 else {
560 let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
561 let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
562 y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
563 rect.bottom - boundingHeight + yMargin;
564 moveY = targetTop - bounding.top;
565 }
566 if (x == "nearest") {
567 if (rect.left < bounding.left) {
568 moveX = rect.left - (bounding.left + xMargin);
569 if (side > 0 && rect.right > bounding.right + moveX)
570 moveX = rect.right - bounding.right + xMargin;
571 }
572 else if (rect.right > bounding.right) {
573 moveX = rect.right - bounding.right + xMargin;
574 if (side < 0 && rect.left < bounding.left + moveX)
575 moveX = rect.left - (bounding.left + xMargin);
576 }
577 }
578 else {
579 let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
580 (x == "start") == ltr ? rect.left - xMargin :
581 rect.right - (bounding.right - bounding.left) + xMargin;
582 moveX = targetLeft - bounding.left;
583 }
584 if (moveX || moveY) {
585 if (top) {
586 win.scrollBy(moveX, moveY);
587 }
588 else {
589 let movedX = 0, movedY = 0;
590 if (moveY) {
591 let start = cur.scrollTop;
592 cur.scrollTop += moveY / scaleY;
593 movedY = (cur.scrollTop - start) * scaleY;
594 }
595 if (moveX) {
596 let start = cur.scrollLeft;
597 cur.scrollLeft += moveX / scaleX;
598 movedX = (cur.scrollLeft - start) * scaleX;
599 }
600 rect = { left: rect.left - movedX, top: rect.top - movedY,
601 right: rect.right - movedX, bottom: rect.bottom - movedY };
602 if (movedX && Math.abs(movedX - moveX) < 1)
603 x = "nearest";
604 if (movedY && Math.abs(movedY - moveY) < 1)
605 y = "nearest";
606 }
607 }
608 if (top)
609 break;
610 if (rect.top < bounding.top || rect.bottom > bounding.bottom ||
611 rect.left < bounding.left || rect.right > bounding.right)
612 rect = { left: Math.max(rect.left, bounding.left), right: Math.min(rect.right, bounding.right),
613 top: Math.max(rect.top, bounding.top), bottom: Math.min(rect.bottom, bounding.bottom) };
614 cur = cur.assignedSlot || cur.parentNode;
615 }
616 else if (cur.nodeType == 11) { // A shadow root
617 cur = cur.host;
618 }
619 else {
620 break;
621 }
622 }
623}
624function scrollableParents(dom, getX = true) {
625 let doc = dom.ownerDocument, x = null, y = null;
626 for (let cur = dom.parentNode; cur;) {
627 if (cur == doc.body || ((!getX || x) && y)) {
628 break;
629 }
630 else if (cur.nodeType == 1) {
631 if (!y && cur.scrollHeight > cur.clientHeight)
632 y = cur;
633 if (getX && !x && cur.scrollWidth > cur.clientWidth)
634 x = cur;
635 cur = cur.assignedSlot || cur.parentNode;
636 }
637 else if (cur.nodeType == 11) {
638 cur = cur.host;
639 }
640 else {
641 break;
642 }
643 }
644 return { x, y };
645}
646class DOMSelectionState {
647 constructor() {
648 this.anchorNode = null;
649 this.anchorOffset = 0;
650 this.focusNode = null;
651 this.focusOffset = 0;
652 }
653 eq(domSel) {
654 return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
655 this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
656 }
657 setRange(range) {
658 let { anchorNode, focusNode } = range;
659 // Clip offsets to node size to avoid crashes when Safari reports bogus offsets (#1152)
660 this.set(anchorNode, Math.min(range.anchorOffset, anchorNode ? maxOffset(anchorNode) : 0), focusNode, Math.min(range.focusOffset, focusNode ? maxOffset(focusNode) : 0));
661 }
662 set(anchorNode, anchorOffset, focusNode, focusOffset) {
663 this.anchorNode = anchorNode;
664 this.anchorOffset = anchorOffset;
665 this.focusNode = focusNode;
666 this.focusOffset = focusOffset;
667 }
668}
669let preventScrollSupported = null;
670// Safari 26 breaks preventScroll support
671if (browser.safari && browser.safari_version >= 26)
672 preventScrollSupported = false;
673// Feature-detects support for .focus({preventScroll: true}), and uses
674// a fallback kludge when not supported.
675function focusPreventScroll(dom) {
676 if (dom.setActive)
677 return dom.setActive(); // in IE
678 if (preventScrollSupported)
679 return dom.focus(preventScrollSupported);
680 let stack = [];
681 for (let cur = dom; cur; cur = cur.parentNode) {
682 stack.push(cur, cur.scrollTop, cur.scrollLeft);
683 if (cur == cur.ownerDocument)
684 break;
685 }
686 dom.focus(preventScrollSupported == null ? {
687 get preventScroll() {
688 preventScrollSupported = { preventScroll: true };
689 return true;
690 }
691 } : undefined);
692 if (!preventScrollSupported) {
693 preventScrollSupported = false;
694 for (let i = 0; i < stack.length;) {
695 let elt = stack[i++], top = stack[i++], left = stack[i++];
696 if (elt.scrollTop != top)
697 elt.scrollTop = top;
698 if (elt.scrollLeft != left)
699 elt.scrollLeft = left;
700 }
701 }
702}
703let scratchRange;
704function textRange(node, from, to = from) {
705 let range = scratchRange || (scratchRange = document.createRange());
706 range.setEnd(node, to);
707 range.setStart(node, from);
708 return range;
709}
710function dispatchKey(elt, name, code, mods) {
711 let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
712 if (mods)
713 ({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
714 let down = new KeyboardEvent("keydown", options);
715 down.synthetic = true;
716 elt.dispatchEvent(down);
717 let up = new KeyboardEvent("keyup", options);
718 up.synthetic = true;
719 elt.dispatchEvent(up);
720 return down.defaultPrevented || up.defaultPrevented;
721}
722function getRoot(node) {
723 while (node) {
724 if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
725 return node;
726 node = node.assignedSlot || node.parentNode;
727 }
728 return null;
729}
730function atElementStart(doc, selection) {
731 let node = selection.focusNode, offset = selection.focusOffset;
732 if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
733 return false;
734 // Safari can report bogus offsets (#1152)
735 offset = Math.min(offset, maxOffset(node));
736 for (;;) {
737 if (offset) {
738 if (node.nodeType != 1)
739 return false;
740 let prev = node.childNodes[offset - 1];
741 if (prev.contentEditable == "false")
742 offset--;
743 else {
744 node = prev;
745 offset = maxOffset(node);
746 }
747 }
748 else if (node == doc) {
749 return true;
750 }
751 else {
752 offset = domIndex(node);
753 node = node.parentNode;
754 }
755 }
756}
757function isScrolledToBottom(elt) {
758 if (elt instanceof Window)
759 return elt.pageYOffset > Math.max(0, elt.document.documentElement.scrollHeight - elt.innerHeight - 4);
760 return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
761}
762function textNodeBefore(startNode, startOffset) {
763 for (let node = startNode, offset = startOffset;;) {
764 if (node.nodeType == 3 && offset > 0) {
765 return { node: node, offset: offset };
766 }
767 else if (node.nodeType == 1 && offset > 0) {
768 if (node.contentEditable == "false")
769 return null;
770 node = node.childNodes[offset - 1];
771 offset = maxOffset(node);
772 }
773 else if (node.parentNode && !isBlockElement(node)) {
774 offset = domIndex(node);
775 node = node.parentNode;
776 }
777 else {
778 return null;
779 }
780 }
781}
782function textNodeAfter(startNode, startOffset) {
783 for (let node = startNode, offset = startOffset;;) {
784 if (node.nodeType == 3 && offset < node.nodeValue.length) {
785 return { node: node, offset: offset };
786 }
787 else if (node.nodeType == 1 && offset < node.childNodes.length) {
788 if (node.contentEditable == "false")
789 return null;
790 node = node.childNodes[offset];
791 offset = 0;
792 }
793 else if (node.parentNode && !isBlockElement(node)) {
794 offset = domIndex(node) + 1;
795 node = node.parentNode;
796 }
797 else {
798 return null;
799 }
800 }
801}
802class DOMPos {
803 constructor(node, offset, precise = true) {
804 this.node = node;
805 this.offset = offset;
806 this.precise = precise;
807 }
808 static before(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom), precise); }
809 static after(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom) + 1, precise); }
810}
811
812/**
813Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
814*/
815var Direction = /*@__PURE__*/(function (Direction) {
816 // (These are chosen to match the base levels, in bidi algorithm
817 // terms, of spans in that direction.)
818 /**
819 Left-to-right.
820 */
821 Direction[Direction["LTR"] = 0] = "LTR";
822 /**
823 Right-to-left.
824 */
825 Direction[Direction["RTL"] = 1] = "RTL";
826return Direction})(Direction || (Direction = {}));
827const LTR = Direction.LTR, RTL = Direction.RTL;
828// Decode a string with each type encoded as log2(type)
829function dec(str) {
830 let result = [];
831 for (let i = 0; i < str.length; i++)
832 result.push(1 << +str[i]);
833 return result;
834}
835// Character types for codepoints 0 to 0xf8
836const LowTypes = /*@__PURE__*/dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
837// Character types for codepoints 0x600 to 0x6f9
838const ArabicTypes = /*@__PURE__*/dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
839const Brackets = /*@__PURE__*/Object.create(null), BracketStack = [];
840// There's a lot more in
841// https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
842// which are left out to keep code size down.
843for (let p of ["()", "[]", "{}"]) {
844 let l = /*@__PURE__*/p.charCodeAt(0), r = /*@__PURE__*/p.charCodeAt(1);
845 Brackets[l] = r;
846 Brackets[r] = -l;
847}
848function charType(ch) {
849 return ch <= 0xf7 ? LowTypes[ch] :
850 0x590 <= ch && ch <= 0x5f4 ? 2 /* T.R */ :
851 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
852 0x6ee <= ch && ch <= 0x8ac ? 4 /* T.AL */ :
853 0x2000 <= ch && ch <= 0x200c ? 256 /* T.NI */ :
854 0xfb50 <= ch && ch <= 0xfdff ? 4 /* T.AL */ : 1 /* T.L */;
855}
856const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\ufb50-\ufdff]/;
857/**
858Represents a contiguous range of text that has a single direction
859(as in left-to-right or right-to-left).
860*/
861class BidiSpan {
862 /**
863 The direction of this span.
864 */
865 get dir() { return this.level % 2 ? RTL : LTR; }
866 /**
867 @internal
868 */
869 constructor(
870 /**
871 The start of the span (relative to the start of the line).
872 */
873 from,
874 /**
875 The end of the span.
876 */
877 to,
878 /**
879 The ["bidi
880 level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
881 of the span (in this context, 0 means
882 left-to-right, 1 means right-to-left, 2 means left-to-right
883 number inside right-to-left text).
884 */
885 level) {
886 this.from = from;
887 this.to = to;
888 this.level = level;
889 }
890 /**
891 @internal
892 */
893 side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
894 /**
895 @internal
896 */
897 forward(forward, dir) { return forward == (this.dir == dir); }
898 /**
899 @internal
900 */
901 static find(order, index, level, assoc) {
902 let maybe = -1;
903 for (let i = 0; i < order.length; i++) {
904 let span = order[i];
905 if (span.from <= index && span.to >= index) {
906 if (span.level == level)
907 return i;
908 // When multiple spans match, if assoc != 0, take the one that
909 // covers that side, otherwise take the one with the minimum
910 // level.
911 if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
912 maybe = i;
913 }
914 }
915 if (maybe < 0)
916 throw new RangeError("Index out of range");
917 return maybe;
918 }
919}
920function isolatesEq(a, b) {
921 if (a.length != b.length)
922 return false;
923 for (let i = 0; i < a.length; i++) {
924 let iA = a[i], iB = b[i];
925 if (iA.from != iB.from || iA.to != iB.to || iA.direction != iB.direction || !isolatesEq(iA.inner, iB.inner))
926 return false;
927 }
928 return true;
929}
930// Reused array of character types
931const types = [];
932// Fill in the character types (in `types`) from `from` to `to` and
933// apply W normalization rules.
934function computeCharTypes(line, rFrom, rTo, isolates, outerType) {
935 for (let iI = 0; iI <= isolates.length; iI++) {
936 let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
937 let prevType = iI ? 256 /* T.NI */ : outerType;
938 // W1. Examine each non-spacing mark (NSM) in the level run, and
939 // change the type of the NSM to the type of the previous
940 // character. If the NSM is at the start of the level run, it will
941 // get the type of sor.
942 // W2. Search backwards from each instance of a European number
943 // until the first strong type (R, L, AL, or sor) is found. If an
944 // AL is found, change the type of the European number to Arabic
945 // number.
946 // W3. Change all ALs to R.
947 // (Left after this: L, R, EN, AN, ET, CS, NI)
948 for (let i = from, prev = prevType, prevStrong = prevType; i < to; i++) {
949 let type = charType(line.charCodeAt(i));
950 if (type == 512 /* T.NSM */)
951 type = prev;
952 else if (type == 8 /* T.EN */ && prevStrong == 4 /* T.AL */)
953 type = 16 /* T.AN */;
954 types[i] = type == 4 /* T.AL */ ? 2 /* T.R */ : type;
955 if (type & 7 /* T.Strong */)
956 prevStrong = type;
957 prev = type;
958 }
959 // W5. A sequence of European terminators adjacent to European
960 // numbers changes to all European numbers.
961 // W6. Otherwise, separators and terminators change to Other
962 // Neutral.
963 // W7. Search backwards from each instance of a European number
964 // until the first strong type (R, L, or sor) is found. If an L is
965 // found, then change the type of the European number to L.
966 // (Left after this: L, R, EN+AN, NI)
967 for (let i = from, prev = prevType, prevStrong = prevType; i < to; i++) {
968 let type = types[i];
969 if (type == 128 /* T.CS */) {
970 if (i < to - 1 && prev == types[i + 1] && (prev & 24 /* T.Num */))
971 type = types[i] = prev;
972 else
973 types[i] = 256 /* T.NI */;
974 }
975 else if (type == 64 /* T.ET */) {
976 let end = i + 1;
977 while (end < to && types[end] == 64 /* T.ET */)
978 end++;
979 let replace = (i && prev == 8 /* T.EN */) || (end < rTo && types[end] == 8 /* T.EN */) ? (prevStrong == 1 /* T.L */ ? 1 /* T.L */ : 8 /* T.EN */) : 256 /* T.NI */;
980 for (let j = i; j < end; j++)
981 types[j] = replace;
982 i = end - 1;
983 }
984 else if (type == 8 /* T.EN */ && prevStrong == 1 /* T.L */) {
985 types[i] = 1 /* T.L */;
986 }
987 prev = type;
988 if (type & 7 /* T.Strong */)
989 prevStrong = type;
990 }
991 }
992}
993// Process brackets throughout a run sequence.
994function processBracketPairs(line, rFrom, rTo, isolates, outerType) {
995 let oppositeType = outerType == 1 /* T.L */ ? 2 /* T.R */ : 1 /* T.L */;
996 for (let iI = 0, sI = 0, context = 0; iI <= isolates.length; iI++) {
997 let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
998 // N0. Process bracket pairs in an isolating run sequence
999 // sequentially in the logical order of the text positions of the
1000 // opening paired brackets using the logic given below. Within this
1001 // scope, bidirectional types EN and AN are treated as R.
1002 for (let i = from, ch, br, type; i < to; i++) {
1003 // Keeps [startIndex, type, strongSeen] triples for each open
1004 // bracket on BracketStack.
1005 if (br = Brackets[ch = line.charCodeAt(i)]) {
1006 if (br < 0) { // Closing bracket
1007 for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
1008 if (BracketStack[sJ + 1] == -br) {
1009 let flags = BracketStack[sJ + 2];
1010 let type = (flags & 2 /* Bracketed.EmbedInside */) ? outerType :
1011 !(flags & 4 /* Bracketed.OppositeInside */) ? 0 :
1012 (flags & 1 /* Bracketed.OppositeBefore */) ? oppositeType : outerType;
1013 if (type)
1014 types[i] = types[BracketStack[sJ]] = type;
1015 sI = sJ;
1016 break;
1017 }
1018 }
1019 }
1020 else if (BracketStack.length == 189 /* Bracketed.MaxDepth */) {
1021 break;
1022 }
1023 else {
1024 BracketStack[sI++] = i;
1025 BracketStack[sI++] = ch;
1026 BracketStack[sI++] = context;
1027 }
1028 }
1029 else if ((type = types[i]) == 2 /* T.R */ || type == 1 /* T.L */) {
1030 let embed = type == outerType;
1031 context = embed ? 0 : 1 /* Bracketed.OppositeBefore */;
1032 for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
1033 let cur = BracketStack[sJ + 2];
1034 if (cur & 2 /* Bracketed.EmbedInside */)
1035 break;
1036 if (embed) {
1037 BracketStack[sJ + 2] |= 2 /* Bracketed.EmbedInside */;
1038 }
1039 else {
1040 if (cur & 4 /* Bracketed.OppositeInside */)
1041 break;
1042 BracketStack[sJ + 2] |= 4 /* Bracketed.OppositeInside */;
1043 }
1044 }
1045 }
1046 }
1047 }
1048}
1049function processNeutrals(rFrom, rTo, isolates, outerType) {
1050 for (let iI = 0, prev = outerType; iI <= isolates.length; iI++) {
1051 let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
1052 // N1. A sequence of neutrals takes the direction of the
1053 // surrounding strong text if the text on both sides has the same
1054 // direction. European and Arabic numbers act as if they were R in
1055 // terms of their influence on neutrals. Start-of-level-run (sor)
1056 // and end-of-level-run (eor) are used at level run boundaries.
1057 // N2. Any remaining neutrals take the embedding direction.
1058 // (Left after this: L, R, EN+AN)
1059 for (let i = from; i < to;) {
1060 let type = types[i];
1061 if (type == 256 /* T.NI */) {
1062 let end = i + 1;
1063 for (;;) {
1064 if (end == to) {
1065 if (iI == isolates.length)
1066 break;
1067 end = isolates[iI++].to;
1068 to = iI < isolates.length ? isolates[iI].from : rTo;
1069 }
1070 else if (types[end] == 256 /* T.NI */) {
1071 end++;
1072 }
1073 else {
1074 break;
1075 }
1076 }
1077 let beforeL = prev == 1 /* T.L */;
1078 let afterL = (end < rTo ? types[end] : outerType) == 1 /* T.L */;
1079 let replace = beforeL == afterL ? (beforeL ? 1 /* T.L */ : 2 /* T.R */) : outerType;
1080 for (let j = end, jI = iI, fromJ = jI ? isolates[jI - 1].to : rFrom; j > i;) {
1081 if (j == fromJ) {
1082 j = isolates[--jI].from;
1083 fromJ = jI ? isolates[jI - 1].to : rFrom;
1084 }
1085 types[--j] = replace;
1086 }
1087 i = end;
1088 }
1089 else {
1090 prev = type;
1091 i++;
1092 }
1093 }
1094 }
1095}
1096// Find the contiguous ranges of character types in a given range, and
1097// emit spans for them. Flip the order of the spans as appropriate
1098// based on the level, and call through to compute the spans for
1099// isolates at the proper point.
1100function emitSpans(line, from, to, level, baseLevel, isolates, order) {
1101 let ourType = level % 2 ? 2 /* T.R */ : 1 /* T.L */;
1102 if ((level % 2) == (baseLevel % 2)) { // Same dir as base direction, don't flip
1103 for (let iCh = from, iI = 0; iCh < to;) {
1104 // Scan a section of characters in direction ourType, unless
1105 // there's another type of char right after iCh, in which case
1106 // we scan a section of other characters (which, if ourType ==
1107 // T.L, may contain both T.R and T.AN chars).
1108 let sameDir = true, isNum = false;
1109 if (iI == isolates.length || iCh < isolates[iI].from) {
1110 let next = types[iCh];
1111 if (next != ourType) {
1112 sameDir = false;
1113 isNum = next == 16 /* T.AN */;
1114 }
1115 }
1116 // Holds an array of isolates to pass to a recursive call if we
1117 // must recurse (to distinguish T.AN inside an RTL section in
1118 // LTR text), null if we can emit directly
1119 let recurse = !sameDir && ourType == 1 /* T.L */ ? [] : null;
1120 let localLevel = sameDir ? level : level + 1;
1121 let iScan = iCh;
1122 run: for (;;) {
1123 if (iI < isolates.length && iScan == isolates[iI].from) {
1124 if (isNum)
1125 break run;
1126 let iso = isolates[iI];
1127 // Scan ahead to verify that there is another char in this dir after the isolate(s)
1128 if (!sameDir)
1129 for (let upto = iso.to, jI = iI + 1;;) {
1130 if (upto == to)
1131 break run;
1132 if (jI < isolates.length && isolates[jI].from == upto)
1133 upto = isolates[jI++].to;
1134 else if (types[upto] == ourType)
1135 break run;
1136 else
1137 break;
1138 }
1139 iI++;
1140 if (recurse) {
1141 recurse.push(iso);
1142 }
1143 else {
1144 if (iso.from > iCh)
1145 order.push(new BidiSpan(iCh, iso.from, localLevel));
1146 let dirSwap = (iso.direction == LTR) != !(localLevel % 2);
1147 computeSectionOrder(line, dirSwap ? level + 1 : level, baseLevel, iso.inner, iso.from, iso.to, order);
1148 iCh = iso.to;
1149 }
1150 iScan = iso.to;
1151 }
1152 else if (iScan == to || (sameDir ? types[iScan] != ourType : types[iScan] == ourType)) {
1153 break;
1154 }
1155 else {
1156 iScan++;
1157 }
1158 }
1159 if (recurse)
1160 emitSpans(line, iCh, iScan, level + 1, baseLevel, recurse, order);
1161 else if (iCh < iScan)
1162 order.push(new BidiSpan(iCh, iScan, localLevel));
1163 iCh = iScan;
1164 }
1165 }
1166 else {
1167 // Iterate in reverse to flip the span order. Same code again, but
1168 // going from the back of the section to the front
1169 for (let iCh = to, iI = isolates.length; iCh > from;) {
1170 let sameDir = true, isNum = false;
1171 if (!iI || iCh > isolates[iI - 1].to) {
1172 let next = types[iCh - 1];
1173 if (next != ourType) {
1174 sameDir = false;
1175 isNum = next == 16 /* T.AN */;
1176 }
1177 }
1178 let recurse = !sameDir && ourType == 1 /* T.L */ ? [] : null;
1179 let localLevel = sameDir ? level : level + 1;
1180 let iScan = iCh;
1181 run: for (;;) {
1182 if (iI && iScan == isolates[iI - 1].to) {
1183 if (isNum)
1184 break run;
1185 let iso = isolates[--iI];
1186 // Scan ahead to verify that there is another char in this dir after the isolate(s)
1187 if (!sameDir)
1188 for (let upto = iso.from, jI = iI;;) {
1189 if (upto == from)
1190 break run;
1191 if (jI && isolates[jI - 1].to == upto)
1192 upto = isolates[--jI].from;
1193 else if (types[upto - 1] == ourType)
1194 break run;
1195 else
1196 break;
1197 }
1198 if (recurse) {
1199 recurse.push(iso);
1200 }
1201 else {
1202 if (iso.to < iCh)
1203 order.push(new BidiSpan(iso.to, iCh, localLevel));
1204 let dirSwap = (iso.direction == LTR) != !(localLevel % 2);
1205 computeSectionOrder(line, dirSwap ? level + 1 : level, baseLevel, iso.inner, iso.from, iso.to, order);
1206 iCh = iso.from;
1207 }
1208 iScan = iso.from;
1209 }
1210 else if (iScan == from || (sameDir ? types[iScan - 1] != ourType : types[iScan - 1] == ourType)) {
1211 break;
1212 }
1213 else {
1214 iScan--;
1215 }
1216 }
1217 if (recurse)
1218 emitSpans(line, iScan, iCh, level + 1, baseLevel, recurse, order);
1219 else if (iScan < iCh)
1220 order.push(new BidiSpan(iScan, iCh, localLevel));
1221 iCh = iScan;
1222 }
1223 }
1224}
1225function computeSectionOrder(line, level, baseLevel, isolates, from, to, order) {
1226 let outerType = (level % 2 ? 2 /* T.R */ : 1 /* T.L */);
1227 computeCharTypes(line, from, to, isolates, outerType);
1228 processBracketPairs(line, from, to, isolates, outerType);
1229 processNeutrals(from, to, isolates, outerType);
1230 emitSpans(line, from, to, level, baseLevel, isolates, order);
1231}
1232function computeOrder(line, direction, isolates) {
1233 if (!line)
1234 return [new BidiSpan(0, 0, direction == RTL ? 1 : 0)];
1235 if (direction == LTR && !isolates.length && !BidiRE.test(line))
1236 return trivialOrder(line.length);
1237 if (isolates.length)
1238 while (line.length > types.length)
1239 types[types.length] = 256 /* T.NI */; // Make sure types array has no gaps
1240 let order = [], level = direction == LTR ? 0 : 1;
1241 computeSectionOrder(line, level, level, isolates, 0, line.length, order);
1242 return order;
1243}
1244function trivialOrder(length) {
1245 return [new BidiSpan(0, length, 0)];
1246}
1247let movedOver = "";
1248// This implementation moves strictly visually, without concern for a
1249// traversal visiting every logical position in the string. It will
1250// still do so for simple input, but situations like multiple isolates
1251// with the same level next to each other, or text going against the
1252// main dir at the end of the line, will make some positions
1253// unreachable with this motion. Each visible cursor position will
1254// correspond to the lower-level bidi span that touches it.
1255//
1256// The alternative would be to solve an order globally for a given
1257// line, making sure that it includes every position, but that would
1258// require associating non-canonical (higher bidi span level)
1259// positions with a given visual position, which is likely to confuse
1260// people. (And would generally be a lot more complicated.)
1261function moveVisually(line, order, dir, start, forward) {
1262 var _a;
1263 let startIndex = start.head - line.from;
1264 let spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
1265 let span = order[spanI], spanEnd = span.side(forward, dir);
1266 // End of span
1267 if (startIndex == spanEnd) {
1268 let nextI = spanI += forward ? 1 : -1;
1269 if (nextI < 0 || nextI >= order.length)
1270 return null;
1271 span = order[spanI = nextI];
1272 startIndex = span.side(!forward, dir);
1273 spanEnd = span.side(forward, dir);
1274 }
1275 let nextIndex = findClusterBreak(line.text, startIndex, span.forward(forward, dir));
1276 if (nextIndex < span.from || nextIndex > span.to)
1277 nextIndex = spanEnd;
1278 movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
1279 let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
1280 if (nextSpan && nextIndex == spanEnd && nextSpan.level + (forward ? 0 : 1) < span.level)
1281 return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, nextSpan.forward(forward, dir) ? 1 : -1, nextSpan.level);
1282 return EditorSelection.cursor(nextIndex + line.from, span.forward(forward, dir) ? -1 : 1, span.level);
1283}
1284function autoDirection(text, from, to) {
1285 for (let i = from; i < to; i++) {
1286 let type = charType(text.charCodeAt(i));
1287 if (type == 1 /* T.L */)
1288 return LTR;
1289 if (type == 2 /* T.R */ || type == 4 /* T.AL */)
1290 return RTL;
1291 }
1292 return LTR;
1293}
1294
1295const clickAddsSelectionRange = /*@__PURE__*/Facet.define();
1296const dragMovesSelection$1 = /*@__PURE__*/Facet.define();
1297const mouseSelectionStyle = /*@__PURE__*/Facet.define();
1298const exceptionSink = /*@__PURE__*/Facet.define();
1299const updateListener = /*@__PURE__*/Facet.define();
1300const inputHandler = /*@__PURE__*/Facet.define();
1301const focusChangeEffect = /*@__PURE__*/Facet.define();
1302const clipboardInputFilter = /*@__PURE__*/Facet.define();
1303const clipboardOutputFilter = /*@__PURE__*/Facet.define();
1304const perLineTextDirection = /*@__PURE__*/Facet.define({
1305 combine: values => values.some(x => x)
1306});
1307const nativeSelectionHidden = /*@__PURE__*/Facet.define({
1308 combine: values => values.some(x => x)
1309});
1310const scrollHandler = /*@__PURE__*/Facet.define();
1311class ScrollTarget {
1312 constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
1313 // This data structure is abused to also store precise scroll
1314 // snapshots, instead of a `scrollIntoView` request. When this
1315 // flag is `true`, `range` points at a position in the reference
1316 // line, `yMargin` holds the difference between the top of that
1317 // line and the top of the editor, and `xMargin` holds the
1318 // editor's `scrollLeft`.
1319 isSnapshot = false) {
1320 this.range = range;
1321 this.y = y;
1322 this.x = x;
1323 this.yMargin = yMargin;
1324 this.xMargin = xMargin;
1325 this.isSnapshot = isSnapshot;
1326 }
1327 map(changes) {
1328 return changes.empty ? this :
1329 new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
1330 }
1331 clip(state) {
1332 return this.range.to <= state.doc.length ? this :
1333 new ScrollTarget(EditorSelection.cursor(state.doc.length), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
1334 }
1335}
1336const scrollIntoView = /*@__PURE__*/StateEffect.define({ map: (t, ch) => t.map(ch) });
1337const setEditContextFormatting = /*@__PURE__*/StateEffect.define();
1338/**
1339Log or report an unhandled exception in client code. Should
1340probably only be used by extension code that allows client code to
1341provide functions, and calls those functions in a context where an
1342exception can't be propagated to calling code in a reasonable way
1343(for example when in an event handler).
1344
1345Either calls a handler registered with
1346[`EditorView.exceptionSink`](https://codemirror.net/6/docs/ref/#view.EditorView^exceptionSink),
1347`window.onerror`, if defined, or `console.error` (in which case
1348it'll pass `context`, when given, as first argument).
1349*/
1350function logException(state, exception, context) {
1351 let handler = state.facet(exceptionSink);
1352 if (handler.length)
1353 handler[0](exception);
1354 else if (window.onerror && window.onerror(String(exception), context, undefined, undefined, exception)) ;
1355 else if (context)
1356 console.error(context + ":", exception);
1357 else
1358 console.error(exception);
1359}
1360const editable = /*@__PURE__*/Facet.define({ combine: values => values.length ? values[0] : true });
1361let nextPluginID = 0;
1362const viewPlugin = /*@__PURE__*/Facet.define({
1363 combine(plugins) {
1364 return plugins.filter((p, i) => {
1365 for (let j = 0; j < i; j++)
1366 if (plugins[j].plugin == p.plugin)
1367 return false;
1368 return true;
1369 });
1370 }
1371});
1372/**
1373View plugins associate stateful values with a view. They can
1374influence the way the content is drawn, and are notified of things
1375that happen in the view. They optionally take an argument, in
1376which case you need to call [`of`](https://codemirror.net/6/docs/ref/#view.ViewPlugin.of) to create
1377an extension for the plugin. When the argument type is undefined,
1378you can use the plugin instance as an extension directly.
1379*/
1380class ViewPlugin {
1381 constructor(
1382 /**
1383 @internal
1384 */
1385 id,
1386 /**
1387 @internal
1388 */
1389 create,
1390 /**
1391 @internal
1392 */
1393 domEventHandlers,
1394 /**
1395 @internal
1396 */
1397 domEventObservers, buildExtensions) {
1398 this.id = id;
1399 this.create = create;
1400 this.domEventHandlers = domEventHandlers;
1401 this.domEventObservers = domEventObservers;
1402 this.baseExtensions = buildExtensions(this);
1403 this.extension = this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg: undefined }));
1404 }
1405 /**
1406 Create an extension for this plugin with the given argument.
1407 */
1408 of(arg) {
1409 return this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg }));
1410 }
1411 /**
1412 Define a plugin from a constructor function that creates the
1413 plugin's value, given an editor view.
1414 */
1415 static define(create, spec) {
1416 const { eventHandlers, eventObservers, provide, decorations: deco } = spec || {};
1417 return new ViewPlugin(nextPluginID++, create, eventHandlers, eventObservers, plugin => {
1418 let ext = [];
1419 if (deco)
1420 ext.push(decorations.of(view => {
1421 let pluginInst = view.plugin(plugin);
1422 return pluginInst ? deco(pluginInst) : Decoration.none;
1423 }));
1424 if (provide)
1425 ext.push(provide(plugin));
1426 return ext;
1427 });
1428 }
1429 /**
1430 Create a plugin for a class whose constructor takes a single
1431 editor view as argument.
1432 */
1433 static fromClass(cls, spec) {
1434 return ViewPlugin.define((view, arg) => new cls(view, arg), spec);
1435 }
1436}
1437class PluginInstance {
1438 constructor(spec) {
1439 this.spec = spec;
1440 // When starting an update, all plugins have this field set to the
1441 // update object, indicating they need to be updated. When finished
1442 // updating, it is set to `null`. Retrieving a plugin that needs to
1443 // be updated with `view.plugin` forces an eager update.
1444 this.mustUpdate = null;
1445 // This is null when the plugin is initially created, but
1446 // initialized on the first update.
1447 this.value = null;
1448 }
1449 get plugin() { return this.spec && this.spec.plugin; }
1450 update(view) {
1451 if (!this.value) {
1452 if (this.spec) {
1453 try {
1454 this.value = this.spec.plugin.create(view, this.spec.arg);
1455 }
1456 catch (e) {
1457 logException(view.state, e, "CodeMirror plugin crashed");
1458 this.deactivate();
1459 }
1460 }
1461 }
1462 else if (this.mustUpdate) {
1463 let update = this.mustUpdate;
1464 this.mustUpdate = null;
1465 if (this.value.update) {
1466 try {
1467 this.value.update(update);
1468 }
1469 catch (e) {
1470 logException(update.state, e, "CodeMirror plugin crashed");
1471 if (this.value.destroy)
1472 try {
1473 this.value.destroy();
1474 }
1475 catch (_) { }
1476 this.deactivate();
1477 }
1478 }
1479 }
1480 return this;
1481 }
1482 destroy(view) {
1483 var _a;
1484 if ((_a = this.value) === null || _a === void 0 ? void 0 : _a.destroy) {
1485 try {
1486 this.value.destroy();
1487 }
1488 catch (e) {
1489 logException(view.state, e, "CodeMirror plugin crashed");
1490 }
1491 }
1492 }
1493 deactivate() {
1494 this.spec = this.value = null;
1495 }
1496}
1497const editorAttributes = /*@__PURE__*/Facet.define();
1498const contentAttributes = /*@__PURE__*/Facet.define();
1499// Provide decorations
1500const decorations = /*@__PURE__*/Facet.define();
1501const blockWrappers = /*@__PURE__*/Facet.define();
1502const outerDecorations = /*@__PURE__*/Facet.define();
1503const atomicRanges = /*@__PURE__*/Facet.define();
1504const bidiIsolatedRanges = /*@__PURE__*/Facet.define();
1505function getIsolatedRanges(view, line) {
1506 let isolates = view.state.facet(bidiIsolatedRanges);
1507 if (!isolates.length)
1508 return isolates;
1509 let sets = isolates.map(i => i instanceof Function ? i(view) : i);
1510 let result = [];
1511 RangeSet.spans(sets, line.from, line.to, {
1512 point() { },
1513 span(fromDoc, toDoc, active, open) {
1514 let from = fromDoc - line.from, to = toDoc - line.from;
1515 let level = result;
1516 for (let i = active.length - 1; i >= 0; i--, open--) {
1517 let direction = active[i].spec.bidiIsolate, update;
1518 if (direction == null)
1519 direction = autoDirection(line.text, from, to);
1520 if (open > 0 && level.length &&
1521 (update = level[level.length - 1]).to == from && update.direction == direction) {
1522 update.to = to;
1523 level = update.inner;
1524 }
1525 else {
1526 let add = { from, to, direction, inner: [] };
1527 level.push(add);
1528 level = add.inner;
1529 }
1530 }
1531 }
1532 });
1533 return result;
1534}
1535const scrollMargins = /*@__PURE__*/Facet.define();
1536function getScrollMargins(view) {
1537 let left = 0, right = 0, top = 0, bottom = 0;
1538 for (let source of view.state.facet(scrollMargins)) {
1539 let m = source(view);
1540 if (m) {
1541 if (m.left != null)
1542 left = Math.max(left, m.left);
1543 if (m.right != null)
1544 right = Math.max(right, m.right);
1545 if (m.top != null)
1546 top = Math.max(top, m.top);
1547 if (m.bottom != null)
1548 bottom = Math.max(bottom, m.bottom);
1549 }
1550 }
1551 return { left, right, top, bottom };
1552}
1553const styleModule = /*@__PURE__*/Facet.define();
1554class ChangedRange {
1555 constructor(fromA, toA, fromB, toB) {
1556 this.fromA = fromA;
1557 this.toA = toA;
1558 this.fromB = fromB;
1559 this.toB = toB;
1560 }
1561 join(other) {
1562 return new ChangedRange(Math.min(this.fromA, other.fromA), Math.max(this.toA, other.toA), Math.min(this.fromB, other.fromB), Math.max(this.toB, other.toB));
1563 }
1564 addToSet(set) {
1565 let i = set.length, me = this;
1566 for (; i > 0; i--) {
1567 let range = set[i - 1];
1568 if (range.fromA > me.toA)
1569 continue;
1570 if (range.toA < me.fromA)
1571 break;
1572 me = me.join(range);
1573 set.splice(i - 1, 1);
1574 }
1575 set.splice(i, 0, me);
1576 return set;
1577 }
1578 // Extend a set to cover all the content in `ranges`, which is a
1579 // flat array with each pair of numbers representing fromB/toB
1580 // positions. These pairs are generated in unchanged ranges, so the
1581 // offset between doc A and doc B is the same for their start and
1582 // end points.
1583 static extendWithRanges(diff, ranges) {
1584 if (ranges.length == 0)
1585 return diff;
1586 let result = [];
1587 for (let dI = 0, rI = 0, off = 0;;) {
1588 let nextD = dI < diff.length ? diff[dI].fromB : 1e9;
1589 let nextR = rI < ranges.length ? ranges[rI] : 1e9;
1590 let fromB = Math.min(nextD, nextR);
1591 if (fromB == 1e9)
1592 break;
1593 let fromA = fromB + off, toB = fromB, toA = fromA;
1594 for (;;) {
1595 if (rI < ranges.length && ranges[rI] <= toB) {
1596 let end = ranges[rI + 1];
1597 rI += 2;
1598 toB = Math.max(toB, end);
1599 for (let i = dI; i < diff.length && diff[i].fromB <= toB; i++)
1600 off = diff[i].toA - diff[i].toB;
1601 toA = Math.max(toA, end + off);
1602 }
1603 else if (dI < diff.length && diff[dI].fromB <= toB) {
1604 let next = diff[dI++];
1605 toB = Math.max(toB, next.toB);
1606 toA = Math.max(toA, next.toA);
1607 off = next.toA - next.toB;
1608 }
1609 else {
1610 break;
1611 }
1612 }
1613 result.push(new ChangedRange(fromA, toA, fromB, toB));
1614 }
1615 return result;
1616 }
1617}
1618/**
1619View [plugins](https://codemirror.net/6/docs/ref/#view.ViewPlugin) are given instances of this
1620class, which describe what happened, whenever the view is updated.
1621*/
1622class ViewUpdate {
1623 constructor(
1624 /**
1625 The editor view that the update is associated with.
1626 */
1627 view,
1628 /**
1629 The new editor state.
1630 */
1631 state,
1632 /**
1633 The transactions involved in the update. May be empty.
1634 */
1635 transactions) {
1636 this.view = view;
1637 this.state = state;
1638 this.transactions = transactions;
1639 /**
1640 @internal
1641 */
1642 this.flags = 0;
1643 this.startState = view.state;
1644 this.changes = ChangeSet.empty(this.startState.doc.length);
1645 for (let tr of transactions)
1646 this.changes = this.changes.compose(tr.changes);
1647 let changedRanges = [];
1648 this.changes.iterChangedRanges((fromA, toA, fromB, toB) => changedRanges.push(new ChangedRange(fromA, toA, fromB, toB)));
1649 this.changedRanges = changedRanges;
1650 }
1651 /**
1652 @internal
1653 */
1654 static create(view, state, transactions) {
1655 return new ViewUpdate(view, state, transactions);
1656 }
1657 /**
1658 Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
1659 [visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
1660 update.
1661 */
1662 get viewportChanged() {
1663 return (this.flags & 4 /* UpdateFlag.Viewport */) > 0;
1664 }
1665 /**
1666 Returns true when
1667 [`viewportChanged`](https://codemirror.net/6/docs/ref/#view.ViewUpdate.viewportChanged) is true
1668 and the viewport change is not just the result of mapping it in
1669 response to document changes.
1670 */
1671 get viewportMoved() {
1672 return (this.flags & 8 /* UpdateFlag.ViewportMoved */) > 0;
1673 }
1674 /**
1675 Indicates whether the height of a block element in the editor
1676 changed in this update.
1677 */
1678 get heightChanged() {
1679 return (this.flags & 2 /* UpdateFlag.Height */) > 0;
1680 }
1681 /**
1682 Returns true when the document was modified or the size of the
1683 editor, or elements within the editor, changed.
1684 */
1685 get geometryChanged() {
1686 return this.docChanged || (this.flags & (16 /* UpdateFlag.Geometry */ | 2 /* UpdateFlag.Height */)) > 0;
1687 }
1688 /**
1689 True when this update indicates a focus change.
1690 */
1691 get focusChanged() {
1692 return (this.flags & 1 /* UpdateFlag.Focus */) > 0;
1693 }
1694 /**
1695 Whether the document changed in this update.
1696 */
1697 get docChanged() {
1698 return !this.changes.empty;
1699 }
1700 /**
1701 Whether the selection was explicitly set in this update.
1702 */
1703 get selectionSet() {
1704 return this.transactions.some(tr => tr.selection);
1705 }
1706 /**
1707 @internal
1708 */
1709 get empty() { return this.flags == 0 && this.transactions.length == 0; }
1710}
1711
1712const noChildren = [];
1713class Tile {
1714 constructor(dom, length, flags = 0) {
1715 this.dom = dom;
1716 this.length = length;
1717 this.flags = flags;
1718 this.parent = null;
1719 dom.cmTile = this;
1720 }
1721 get breakAfter() { return (this.flags & 1 /* TileFlag.BreakAfter */); }
1722 get children() { return noChildren; }
1723 isWidget() { return false; }
1724 get isHidden() { return false; }
1725 isComposite() { return false; }
1726 isLine() { return false; }
1727 isText() { return false; }
1728 isBlock() { return false; }
1729 get domAttrs() { return null; }
1730 sync(track) {
1731 this.flags |= 2 /* TileFlag.Synced */;
1732 if (this.flags & 4 /* TileFlag.AttrsDirty */) {
1733 this.flags &= ~4 /* TileFlag.AttrsDirty */;
1734 let attrs = this.domAttrs;
1735 if (attrs)
1736 setAttrs(this.dom, attrs);
1737 }
1738 }
1739 toString() {
1740 return this.constructor.name + (this.children.length ? `(${this.children})` : "") + (this.breakAfter ? "#" : "");
1741 }
1742 destroy() { this.parent = null; }
1743 setDOM(dom) {
1744 this.dom = dom;
1745 dom.cmTile = this;
1746 }
1747 get posAtStart() {
1748 return this.parent ? this.parent.posBefore(this) : 0;
1749 }
1750 get posAtEnd() {
1751 return this.posAtStart + this.length;
1752 }
1753 posBefore(tile, start = this.posAtStart) {
1754 let pos = start;
1755 for (let child of this.children) {
1756 if (child == tile)
1757 return pos;
1758 pos += child.length + child.breakAfter;
1759 }
1760 throw new RangeError("Invalid child in posBefore");
1761 }
1762 posAfter(tile) {
1763 return this.posBefore(tile) + tile.length;
1764 }
1765 covers(side) { return true; }
1766 coordsIn(pos, side) { return null; }
1767 domPosFor(off, side) {
1768 let index = domIndex(this.dom);
1769 let after = this.length ? off > 0 : side > 0;
1770 return new DOMPos(this.parent.dom, index + (after ? 1 : 0), off == 0 || off == this.length);
1771 }
1772 markDirty(attrs) {
1773 this.flags &= ~2 /* TileFlag.Synced */;
1774 if (attrs)
1775 this.flags |= 4 /* TileFlag.AttrsDirty */;
1776 if (this.parent && (this.parent.flags & 2 /* TileFlag.Synced */))
1777 this.parent.markDirty(false);
1778 }
1779 get overrideDOMText() { return null; }
1780 get root() {
1781 for (let t = this; t; t = t.parent)
1782 if (t instanceof DocTile)
1783 return t;
1784 return null;
1785 }
1786 static get(dom) {
1787 return dom.cmTile;
1788 }
1789}
1790class CompositeTile extends Tile {
1791 constructor(dom) {
1792 super(dom, 0);
1793 this._children = [];
1794 }
1795 isComposite() { return true; }
1796 get children() { return this._children; }
1797 get lastChild() { return this.children.length ? this.children[this.children.length - 1] : null; }
1798 append(child) {
1799 this.children.push(child);
1800 child.parent = this;
1801 }
1802 sync(track) {
1803 if (this.flags & 2 /* TileFlag.Synced */)
1804 return;
1805 super.sync(track);
1806 let parent = this.dom, prev = null, next;
1807 let tracking = (track === null || track === void 0 ? void 0 : track.node) == parent ? track : null;
1808 let length = 0;
1809 for (let child of this.children) {
1810 child.sync(track);
1811 length += child.length + child.breakAfter;
1812 next = prev ? prev.nextSibling : parent.firstChild;
1813 if (tracking && next != child.dom)
1814 tracking.written = true;
1815 if (child.dom.parentNode == parent) {
1816 while (next && next != child.dom)
1817 next = rm$1(next);
1818 }
1819 else {
1820 parent.insertBefore(child.dom, next);
1821 }
1822 prev = child.dom;
1823 }
1824 next = prev ? prev.nextSibling : parent.firstChild;
1825 if (tracking && next)
1826 tracking.written = true;
1827 while (next)
1828 next = rm$1(next);
1829 this.length = length;
1830 }
1831}
1832// Remove a DOM node and return its next sibling.
1833function rm$1(dom) {
1834 let next = dom.nextSibling;
1835 dom.parentNode.removeChild(dom);
1836 return next;
1837}
1838// The top-level tile. Its dom property equals view.contentDOM.
1839class DocTile extends CompositeTile {
1840 constructor(view, dom) {
1841 super(dom);
1842 this.view = view;
1843 }
1844 owns(tile) {
1845 for (; tile; tile = tile.parent)
1846 if (tile == this)
1847 return true;
1848 return false;
1849 }
1850 isBlock() { return true; }
1851 nearest(dom) {
1852 for (;;) {
1853 if (!dom)
1854 return null;
1855 let tile = Tile.get(dom);
1856 if (tile && this.owns(tile))
1857 return tile;
1858 dom = dom.parentNode;
1859 }
1860 }
1861 blockTiles(f) {
1862 for (let stack = [], cur = this, i = 0, pos = 0;;) {
1863 if (i == cur.children.length) {
1864 if (!stack.length)
1865 return;
1866 cur = cur.parent;
1867 if (cur.breakAfter)
1868 pos++;
1869 i = stack.pop();
1870 }
1871 else {
1872 let next = cur.children[i++];
1873 if (next instanceof BlockWrapperTile) {
1874 stack.push(i);
1875 cur = next;
1876 i = 0;
1877 }
1878 else {
1879 let end = pos + next.length;
1880 let result = f(next, pos);
1881 if (result !== undefined)
1882 return result;
1883 pos = end + next.breakAfter;
1884 }
1885 }
1886 }
1887 }
1888 // Find the block at the given position. If side < -1, make sure to
1889 // stay before block widgets at that position, if side > 1, after
1890 // such widgets (used for selection drawing, which needs to be able
1891 // to get coordinates for positions that aren't valid cursor positions).
1892 resolveBlock(pos, side) {
1893 let before, beforeOff = -1, after, afterOff = -1;
1894 this.blockTiles((tile, off) => {
1895 let end = off + tile.length;
1896 if (pos >= off && pos <= end) {
1897 if (tile.isWidget() && side >= -1 && side <= 1) {
1898 if (tile.flags & 32 /* TileFlag.After */)
1899 return true;
1900 if (tile.flags & 16 /* TileFlag.Before */)
1901 before = undefined;
1902 }
1903 if ((off < pos || pos == end && (side < -1 ? tile.length : tile.covers(1))) &&
1904 (!before || !tile.isWidget() && before.isWidget())) {
1905 before = tile;
1906 beforeOff = pos - off;
1907 }
1908 if ((end > pos || pos == off && (side > 1 ? tile.length : tile.covers(-1))) &&
1909 (!after || !tile.isWidget() && after.isWidget())) {
1910 after = tile;
1911 afterOff = pos - off;
1912 }
1913 }
1914 });
1915 if (!before && !after)
1916 throw new Error("No tile at position " + pos);
1917 return before && side < 0 || !after ? { tile: before, offset: beforeOff } : { tile: after, offset: afterOff };
1918 }
1919}
1920class BlockWrapperTile extends CompositeTile {
1921 constructor(dom, wrapper) {
1922 super(dom);
1923 this.wrapper = wrapper;
1924 }
1925 isBlock() { return true; }
1926 covers(side) {
1927 if (!this.children.length)
1928 return false;
1929 return side < 0 ? this.children[0].covers(-1) : this.lastChild.covers(1);
1930 }
1931 get domAttrs() { return this.wrapper.attributes; }
1932 static of(wrapper, dom) {
1933 let tile = new BlockWrapperTile(dom || document.createElement(wrapper.tagName), wrapper);
1934 if (!dom)
1935 tile.flags |= 4 /* TileFlag.AttrsDirty */;
1936 return tile;
1937 }
1938}
1939class LineTile extends CompositeTile {
1940 constructor(dom, attrs) {
1941 super(dom);
1942 this.attrs = attrs;
1943 }
1944 isLine() { return true; }
1945 static start(attrs, dom, keepAttrs) {
1946 let line = new LineTile(dom || document.createElement("div"), attrs);
1947 if (!dom || !keepAttrs)
1948 line.flags |= 4 /* TileFlag.AttrsDirty */;
1949 return line;
1950 }
1951 get domAttrs() { return this.attrs; }
1952 // Find the tile associated with a given position in this line.
1953 resolveInline(pos, side, forCoords) {
1954 let before = null, beforeOff = -1, after = null, afterOff = -1;
1955 function scan(tile, pos) {
1956 for (let i = 0, off = 0; i < tile.children.length && off <= pos; i++) {
1957 let child = tile.children[i], end = off + child.length;
1958 if (end >= pos) {
1959 if (child.isComposite()) {
1960 scan(child, pos - off);
1961 }
1962 else if ((!after || after.isHidden && (side > 0 || forCoords && onSameLine(after, child))) &&
1963 (end > pos || (child.flags & 32 /* TileFlag.After */))) {
1964 after = child;
1965 afterOff = pos - off;
1966 }
1967 else if (off < pos || (child.flags & 16 /* TileFlag.Before */) && !child.isHidden) {
1968 before = child;
1969 beforeOff = pos - off;
1970 }
1971 }
1972 off = end;
1973 }
1974 }
1975 scan(this, pos);
1976 let target = ((side < 0 ? before : after) || before || after);
1977 return target ? { tile: target, offset: target == before ? beforeOff : afterOff } : null;
1978 }
1979 coordsIn(pos, side) {
1980 let found = this.resolveInline(pos, side, true);
1981 if (!found)
1982 return fallbackRect(this);
1983 return found.tile.coordsIn(Math.max(0, found.offset), side);
1984 }
1985 domIn(pos, side) {
1986 let found = this.resolveInline(pos, side);
1987 if (found) {
1988 let { tile, offset } = found;
1989 if (this.dom.contains(tile.dom)) {
1990 if (tile.isText())
1991 return new DOMPos(tile.dom, Math.min(tile.dom.nodeValue.length, offset));
1992 return tile.domPosFor(offset, tile.flags & 16 /* TileFlag.Before */ ? 1 : tile.flags & 32 /* TileFlag.After */ ? -1 : side);
1993 }
1994 let parent = found.tile.parent, saw = false;
1995 for (let ch of parent.children) {
1996 if (saw)
1997 return new DOMPos(ch.dom, 0);
1998 if (ch == found.tile) {
1999 saw = true;
2000 }
2001 }
2002 }
2003 return new DOMPos(this.dom, 0);
2004 }
2005}
2006function fallbackRect(tile) {
2007 let last = tile.dom.lastChild;
2008 if (!last)
2009 return tile.dom.getBoundingClientRect();
2010 let rects = clientRectsFor(last);
2011 return rects[rects.length - 1] || null;
2012}
2013function onSameLine(a, b) {
2014 let posA = a.coordsIn(0, 1), posB = b.coordsIn(0, 1);
2015 return posA && posB && posB.top < posA.bottom;
2016}
2017class MarkTile extends CompositeTile {
2018 constructor(dom, mark) {
2019 super(dom);
2020 this.mark = mark;
2021 }
2022 get domAttrs() { return this.mark.attrs; }
2023 static of(mark, dom) {
2024 let tile = new MarkTile(dom || document.createElement(mark.tagName), mark);
2025 if (!dom)
2026 tile.flags |= 4 /* TileFlag.AttrsDirty */;
2027 return tile;
2028 }
2029}
2030class TextTile extends Tile {
2031 constructor(dom, text) {
2032 super(dom, text.length);
2033 this.text = text;
2034 }
2035 sync(track) {
2036 if (this.flags & 2 /* TileFlag.Synced */)
2037 return;
2038 super.sync(track);
2039 if (this.dom.nodeValue != this.text) {
2040 if (track && track.node == this.dom)
2041 track.written = true;
2042 this.dom.nodeValue = this.text;
2043 }
2044 }
2045 isText() { return true; }
2046 toString() { return JSON.stringify(this.text); }
2047 coordsIn(pos, side) {
2048 let length = this.dom.nodeValue.length;
2049 if (pos > length)
2050 pos = length;
2051 let from = pos, to = pos, flatten = 0;
2052 if (pos == 0 && side < 0 || pos == length && side >= 0) {
2053 if (!(browser.chrome || browser.gecko)) { // These browsers reliably return valid rectangles for empty ranges
2054 if (pos) {
2055 from--;
2056 flatten = 1;
2057 } // FIXME this is wrong in RTL text
2058 else if (to < length) {
2059 to++;
2060 flatten = -1;
2061 }
2062 }
2063 }
2064 else {
2065 if (side < 0)
2066 from--;
2067 else if (to < length)
2068 to++;
2069 }
2070 let rects = textRange(this.dom, from, to).getClientRects();
2071 if (!rects.length)
2072 return null;
2073 let rect = rects[(flatten ? flatten < 0 : side >= 0) ? 0 : rects.length - 1];
2074 if (browser.safari && !flatten && rect.width == 0)
2075 rect = Array.prototype.find.call(rects, r => r.width) || rect;
2076 return flatten ? flattenRect(rect, flatten < 0) : rect || null;
2077 }
2078 static of(text, dom) {
2079 let tile = new TextTile(dom || document.createTextNode(text), text);
2080 if (!dom)
2081 tile.flags |= 2 /* TileFlag.Synced */;
2082 return tile;
2083 }
2084}
2085class WidgetTile extends Tile {
2086 constructor(dom, length, widget, flags) {
2087 super(dom, length, flags);
2088 this.widget = widget;
2089 }
2090 isWidget() { return true; }
2091 get isHidden() { return this.widget.isHidden; }
2092 covers(side) {
2093 if (this.flags & 48 /* TileFlag.PointWidget */)
2094 return false;
2095 return (this.flags & (side < 0 ? 64 /* TileFlag.IncStart */ : 128 /* TileFlag.IncEnd */)) > 0;
2096 }
2097 coordsIn(pos, side) { return this.coordsInWidget(pos, side, false); }
2098 coordsInWidget(pos, side, block) {
2099 let custom = this.widget.coordsAt(this.dom, pos, side);
2100 if (custom)
2101 return custom;
2102 if (block) {
2103 return flattenRect(this.dom.getBoundingClientRect(), this.length ? pos == 0 : side <= 0);
2104 }
2105 else {
2106 let rects = this.dom.getClientRects(), rect = null;
2107 if (!rects.length)
2108 return null;
2109 let fromBack = (this.flags & 16 /* TileFlag.Before */) ? true : (this.flags & 32 /* TileFlag.After */) ? false : pos > 0;
2110 for (let i = fromBack ? rects.length - 1 : 0;; i += (fromBack ? -1 : 1)) {
2111 rect = rects[i];
2112 if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
2113 break;
2114 }
2115 return flattenRect(rect, !fromBack);
2116 }
2117 }
2118 get overrideDOMText() {
2119 if (!this.length)
2120 return Text.empty;
2121 let { root } = this;
2122 if (!root)
2123 return Text.empty;
2124 let start = this.posAtStart;
2125 return root.view.state.doc.slice(start, start + this.length);
2126 }
2127 destroy() {
2128 super.destroy();
2129 this.widget.destroy(this.dom);
2130 }
2131 static of(widget, view, length, flags, dom) {
2132 if (!dom) {
2133 dom = widget.toDOM(view);
2134 if (!widget.editable)
2135 dom.contentEditable = "false";
2136 }
2137 return new WidgetTile(dom, length, widget, flags);
2138 }
2139}
2140// These are drawn around uneditable widgets to avoid a number of
2141// browser bugs that show up when the cursor is directly next to
2142// uneditable inline content.
2143class WidgetBufferTile extends Tile {
2144 constructor(flags) {
2145 let img = document.createElement("img");
2146 img.className = "cm-widgetBuffer";
2147 img.setAttribute("aria-hidden", "true");
2148 super(img, 0, flags);
2149 }
2150 get isHidden() { return true; }
2151 get overrideDOMText() { return Text.empty; }
2152 coordsIn(pos) { return this.dom.getBoundingClientRect(); }
2153}
2154// Represents a position in the tile tree.
2155class TilePointer {
2156 constructor(top) {
2157 this.index = 0;
2158 this.beforeBreak = false;
2159 this.parents = [];
2160 this.tile = top;
2161 }
2162 // Advance by the given distance. If side is -1, stop leaving or
2163 // entering tiles, or skipping zero-length tiles, once the distance
2164 // has been traversed. When side is 1, leave, enter, or skip
2165 // everything at the end position.
2166 advance(dist, side, walker) {
2167 let { tile, index, beforeBreak, parents } = this;
2168 while (dist || side > 0) {
2169 if (!tile.isComposite()) {
2170 if (index == tile.length) {
2171 beforeBreak = !!tile.breakAfter;
2172 ({ tile, index } = parents.pop());
2173 index++;
2174 }
2175 else if (!dist) {
2176 break;
2177 }
2178 else {
2179 let take = Math.min(dist, tile.length - index);
2180 if (walker)
2181 walker.skip(tile, index, index + take);
2182 dist -= take;
2183 index += take;
2184 }
2185 }
2186 else if (beforeBreak) {
2187 if (!dist)
2188 break;
2189 if (walker)
2190 walker.break();
2191 dist--;
2192 beforeBreak = false;
2193 }
2194 else if (index == tile.children.length) {
2195 if (!dist && !parents.length)
2196 break;
2197 if (walker)
2198 walker.leave(tile);
2199 beforeBreak = !!tile.breakAfter;
2200 ({ tile, index } = parents.pop());
2201 index++;
2202 }
2203 else {
2204 let next = tile.children[index], brk = next.breakAfter;
2205 if ((side > 0 ? next.length <= dist : next.length < dist) &&
2206 (!walker || walker.skip(next, 0, next.length) !== false || !next.isComposite)) {
2207 beforeBreak = !!brk;
2208 index++;
2209 dist -= next.length;
2210 }
2211 else {
2212 parents.push({ tile, index });
2213 tile = next;
2214 index = 0;
2215 if (walker && next.isComposite())
2216 walker.enter(next);
2217 }
2218 }
2219 }
2220 this.tile = tile;
2221 this.index = index;
2222 this.beforeBreak = beforeBreak;
2223 return this;
2224 }
2225 get root() { return (this.parents.length ? this.parents[0].tile : this.tile); }
2226}
2227
2228// Used to track open block wrappers
2229class OpenWrapper {
2230 constructor(from, to, wrapper, rank) {
2231 this.from = from;
2232 this.to = to;
2233 this.wrapper = wrapper;
2234 this.rank = rank;
2235 }
2236}
2237// This class builds up a new document tile using input from either
2238// iteration over the old tree or iteration over the document +
2239// decorations. The add* methods emit elements into the tile
2240// structure. To avoid awkward synchronization issues, marks and block
2241// wrappers are treated as belonging to to their content, rather than
2242// opened/closed independently.
2243//
2244// All composite tiles that are touched by changes are rebuilt,
2245// reusing as much of the old tree (either whole nodes or just DOM
2246// elements) as possible. The new tree is built without the Synced
2247// flag, and then synced (during which DOM parent/child relations are
2248// fixed up, text nodes filled in, and attributes added) in a second
2249// phase.
2250class TileBuilder {
2251 constructor(cache, root, blockWrappers) {
2252 this.cache = cache;
2253 this.root = root;
2254 this.blockWrappers = blockWrappers;
2255 this.curLine = null;
2256 this.lastBlock = null;
2257 this.afterWidget = null;
2258 this.pos = 0;
2259 this.wrappers = [];
2260 this.wrapperPos = 0;
2261 }
2262 addText(text, marks, openStart, tile) {
2263 var _a;
2264 this.flushBuffer();
2265 let parent = this.ensureMarks(marks, openStart);
2266 let prev = parent.lastChild;
2267 if (prev && prev.isText() && !(prev.flags & 8 /* TileFlag.Composition */) && prev.length + text.length < 512 /* C.Chunk */) {
2268 this.cache.reused.set(prev, 2 /* Reused.DOM */);
2269 let tile = parent.children[parent.children.length - 1] = new TextTile(prev.dom, prev.text + text);
2270 tile.parent = parent;
2271 }
2272 else {
2273 parent.append(tile || TextTile.of(text, (_a = this.cache.find(TextTile)) === null || _a === void 0 ? void 0 : _a.dom));
2274 }
2275 this.pos += text.length;
2276 this.afterWidget = null;
2277 }
2278 addComposition(composition, context) {
2279 let line = this.curLine;
2280 if (line.dom != context.line.dom) {
2281 line.setDOM(this.cache.reused.has(context.line) ? freeNode(context.line.dom) : context.line.dom);
2282 this.cache.reused.set(context.line, 2 /* Reused.DOM */);
2283 }
2284 let head = line;
2285 for (let i = context.marks.length - 1; i >= 0; i--) {
2286 let mark = context.marks[i];
2287 let last = head.lastChild;
2288 if (last instanceof MarkTile && last.mark.eq(mark.mark)) {
2289 if (last.dom != mark.dom)
2290 last.setDOM(freeNode(mark.dom));
2291 head = last;
2292 }
2293 else {
2294 if (this.cache.reused.get(mark)) {
2295 let tile = Tile.get(mark.dom);
2296 if (tile)
2297 tile.setDOM(freeNode(mark.dom));
2298 }
2299 let nw = MarkTile.of(mark.mark, mark.dom);
2300 head.append(nw);
2301 head = nw;
2302 }
2303 this.cache.reused.set(mark, 2 /* Reused.DOM */);
2304 }
2305 let oldTile = Tile.get(composition.text);
2306 if (oldTile)
2307 this.cache.reused.set(oldTile, 2 /* Reused.DOM */);
2308 let text = new TextTile(composition.text, composition.text.nodeValue);
2309 text.flags |= 8 /* TileFlag.Composition */;
2310 head.append(text);
2311 }
2312 addInlineWidget(widget, marks, openStart) {
2313 // Adjacent same-side-facing non-replacing widgets don't need buffers between them
2314 let noSpace = this.afterWidget && (widget.flags & 48 /* TileFlag.PointWidget */) &&
2315 (this.afterWidget.flags & 48 /* TileFlag.PointWidget */) == (widget.flags & 48 /* TileFlag.PointWidget */);
2316 if (!noSpace)
2317 this.flushBuffer();
2318 let parent = this.ensureMarks(marks, openStart);
2319 if (!noSpace && !(widget.flags & 16 /* TileFlag.Before */))
2320 parent.append(this.getBuffer(1));
2321 parent.append(widget);
2322 this.pos += widget.length;
2323 this.afterWidget = widget;
2324 }
2325 addMark(tile, marks, openStart) {
2326 this.flushBuffer();
2327 let parent = this.ensureMarks(marks, openStart);
2328 parent.append(tile);
2329 this.pos += tile.length;
2330 this.afterWidget = null;
2331 }
2332 addBlockWidget(widget) {
2333 this.getBlockPos().append(widget);
2334 this.pos += widget.length;
2335 this.lastBlock = widget;
2336 this.endLine();
2337 }
2338 continueWidget(length) {
2339 let widget = this.afterWidget || this.lastBlock;
2340 widget.length += length;
2341 this.pos += length;
2342 }
2343 addLineStart(attrs, dom) {
2344 var _a;
2345 if (!attrs)
2346 attrs = lineBaseAttrs;
2347 let tile = LineTile.start(attrs, dom || ((_a = this.cache.find(LineTile)) === null || _a === void 0 ? void 0 : _a.dom), !!dom);
2348 this.getBlockPos().append(this.lastBlock = this.curLine = tile);
2349 }
2350 addLine(tile) {
2351 this.getBlockPos().append(tile);
2352 this.pos += tile.length;
2353 this.lastBlock = tile;
2354 this.endLine();
2355 }
2356 addBreak() {
2357 this.lastBlock.flags |= 1 /* TileFlag.BreakAfter */;
2358 this.endLine();
2359 this.pos++;
2360 }
2361 addLineStartIfNotCovered(attrs) {
2362 if (!this.blockPosCovered())
2363 this.addLineStart(attrs);
2364 }
2365 ensureLine(attrs) {
2366 if (!this.curLine)
2367 this.addLineStart(attrs);
2368 }
2369 ensureMarks(marks, openStart) {
2370 var _a;
2371 let parent = this.curLine;
2372 for (let i = marks.length - 1; i >= 0; i--) {
2373 let mark = marks[i], last;
2374 if (openStart > 0 && (last = parent.lastChild) && last instanceof MarkTile && last.mark.eq(mark)) {
2375 parent = last;
2376 openStart--;
2377 }
2378 else {
2379 let tile = MarkTile.of(mark, (_a = this.cache.find(MarkTile, m => m.mark.eq(mark))) === null || _a === void 0 ? void 0 : _a.dom);
2380 parent.append(tile);
2381 parent = tile;
2382 openStart = 0;
2383 }
2384 }
2385 return parent;
2386 }
2387 endLine() {
2388 if (this.curLine) {
2389 this.flushBuffer();
2390 let last = this.curLine.lastChild;
2391 if (!last || !hasContent(this.curLine, false) ||
2392 last.dom.nodeName != "BR" && last.isWidget() && !(browser.ios && hasContent(this.curLine, true)))
2393 this.curLine.append(this.cache.findWidget(BreakWidget, 0, 32 /* TileFlag.After */) ||
2394 new WidgetTile(BreakWidget.toDOM(), 0, BreakWidget, 32 /* TileFlag.After */));
2395 this.curLine = this.afterWidget = null;
2396 }
2397 }
2398 updateBlockWrappers() {
2399 if (this.wrapperPos > this.pos + 10000 /* C.WrapperReset */) {
2400 this.blockWrappers.goto(this.pos);
2401 this.wrappers.length = 0;
2402 }
2403 for (let i = this.wrappers.length - 1; i >= 0; i--)
2404 if (this.wrappers[i].to < this.pos)
2405 this.wrappers.splice(i, 1);
2406 for (let cur = this.blockWrappers; cur.value && cur.from <= this.pos; cur.next())
2407 if (cur.to >= this.pos) {
2408 let wrap = new OpenWrapper(cur.from, cur.to, cur.value, cur.rank), i = this.wrappers.length;
2409 while (i > 0 && (this.wrappers[i - 1].rank - wrap.rank || this.wrappers[i - 1].to - wrap.to) < 0)
2410 i--;
2411 this.wrappers.splice(i, 0, wrap);
2412 }
2413 this.wrapperPos = this.pos;
2414 }
2415 getBlockPos() {
2416 var _a;
2417 this.updateBlockWrappers();
2418 let parent = this.root;
2419 for (let wrap of this.wrappers) {
2420 let last = parent.lastChild;
2421 if (wrap.from < this.pos && last instanceof BlockWrapperTile && last.wrapper.eq(wrap.wrapper)) {
2422 parent = last;
2423 }
2424 else {
2425 let tile = BlockWrapperTile.of(wrap.wrapper, (_a = this.cache.find(BlockWrapperTile, t => t.wrapper.eq(wrap.wrapper))) === null || _a === void 0 ? void 0 : _a.dom);
2426 parent.append(tile);
2427 parent = tile;
2428 }
2429 }
2430 return parent;
2431 }
2432 blockPosCovered() {
2433 let last = this.lastBlock;
2434 return last != null && !last.breakAfter && (!last.isWidget() || (last.flags & (32 /* TileFlag.After */ | 128 /* TileFlag.IncEnd */)) > 0);
2435 }
2436 getBuffer(side) {
2437 let flags = 2 /* TileFlag.Synced */ | (side < 0 ? 16 /* TileFlag.Before */ : 32 /* TileFlag.After */);
2438 let found = this.cache.find(WidgetBufferTile, undefined, 1 /* Reused.Full */);
2439 if (found)
2440 found.flags = flags;
2441 return found || new WidgetBufferTile(flags);
2442 }
2443 flushBuffer() {
2444 if (this.afterWidget && !(this.afterWidget.flags & 32 /* TileFlag.After */)) {
2445 this.afterWidget.parent.append(this.getBuffer(-1));
2446 this.afterWidget = null;
2447 }
2448 }
2449}
2450// Helps getting efficient access to the document text.
2451class TextStream {
2452 constructor(doc) {
2453 this.skipCount = 0;
2454 this.text = "";
2455 this.textOff = 0;
2456 this.cursor = doc.iter();
2457 }
2458 skip(len) {
2459 // Advance the iterator past the replaced content
2460 if (this.textOff + len <= this.text.length) {
2461 this.textOff += len;
2462 }
2463 else {
2464 this.skipCount += len - (this.text.length - this.textOff);
2465 this.text = "";
2466 this.textOff = 0;
2467 }
2468 }
2469 next(maxLen) {
2470 if (this.textOff == this.text.length) {
2471 let { value, lineBreak, done } = this.cursor.next(this.skipCount);
2472 this.skipCount = 0;
2473 if (done)
2474 throw new Error("Ran out of text content when drawing inline views");
2475 this.text = value;
2476 let len = this.textOff = Math.min(maxLen, value.length);
2477 return lineBreak ? null : value.slice(0, len);
2478 }
2479 let end = Math.min(this.text.length, this.textOff + maxLen);
2480 let chars = this.text.slice(this.textOff, end);
2481 this.textOff = end;
2482 return chars;
2483 }
2484}
2485// Assign the tile classes bucket numbers for caching.
2486const buckets = [WidgetTile, LineTile, TextTile, MarkTile, WidgetBufferTile, BlockWrapperTile, DocTile];
2487for (let i = 0; i < buckets.length; i++)
2488 buckets[i].bucket = i;
2489// Leaf tiles and line tiles may be reused in their entirety. All
2490// others will get new tiles allocated, using the old DOM when
2491// possible.
2492class TileCache {
2493 constructor(view) {
2494 this.view = view;
2495 // Buckets are circular buffers, using `index` as the current
2496 // position.
2497 this.buckets = buckets.map(() => []);
2498 this.index = buckets.map(() => 0);
2499 this.reused = new Map;
2500 }
2501 // Put a tile in the cache.
2502 add(tile) {
2503 let i = tile.constructor.bucket, bucket = this.buckets[i];
2504 if (bucket.length < 6 /* C.Bucket */)
2505 bucket.push(tile);
2506 else
2507 bucket[this.index[i] = (this.index[i] + 1) % 6 /* C.Bucket */] = tile;
2508 }
2509 find(cls, test, type = 2 /* Reused.DOM */) {
2510 let i = cls.bucket;
2511 let bucket = this.buckets[i], off = this.index[i];
2512 for (let j = bucket.length - 1; j >= 0; j--) {
2513 // Look at the most recently added items first (last-in, first-out)
2514 let index = (j + off) % bucket.length, tile = bucket[index];
2515 if ((!test || test(tile)) && !this.reused.has(tile)) {
2516 bucket.splice(index, 1);
2517 if (index < off)
2518 this.index[i]--;
2519 this.reused.set(tile, type);
2520 return tile;
2521 }
2522 }
2523 return null;
2524 }
2525 findWidget(widget, length, flags) {
2526 let widgets = this.buckets[0];
2527 if (widgets.length)
2528 for (let i = 0, pass = 0;; i++) {
2529 if (i == widgets.length) {
2530 if (pass)
2531 return null;
2532 pass = 1;
2533 i = 0;
2534 }
2535 let tile = widgets[i];
2536 if (!this.reused.has(tile) &&
2537 (pass == 0 ? tile.widget.compare(widget)
2538 : tile.widget.constructor == widget.constructor && widget.updateDOM(tile.dom, this.view))) {
2539 widgets.splice(i, 1);
2540 if (i < this.index[0])
2541 this.index[0]--;
2542 if (tile.widget == widget && tile.length == length && (tile.flags & (496 /* TileFlag.Widget */ | 1 /* TileFlag.BreakAfter */)) == flags) {
2543 this.reused.set(tile, 1 /* Reused.Full */);
2544 return tile;
2545 }
2546 else {
2547 this.reused.set(tile, 2 /* Reused.DOM */);
2548 return new WidgetTile(tile.dom, length, widget, (tile.flags & ~(496 /* TileFlag.Widget */ | 1 /* TileFlag.BreakAfter */)) | flags);
2549 }
2550 }
2551 }
2552 }
2553 reuse(tile) {
2554 this.reused.set(tile, 1 /* Reused.Full */);
2555 return tile;
2556 }
2557 maybeReuse(tile, type = 2 /* Reused.DOM */) {
2558 if (this.reused.has(tile))
2559 return undefined;
2560 this.reused.set(tile, type);
2561 return tile.dom;
2562 }
2563 clear() {
2564 for (let i = 0; i < this.buckets.length; i++)
2565 this.buckets[i].length = this.index[i] = 0;
2566 }
2567}
2568// This class organizes a pass over the document, guided by the array
2569// of replaced ranges. For ranges that haven't changed, it iterates
2570// the old tree and copies its content into the new document. For
2571// changed ranges, it runs a decoration iterator to guide generation
2572// of content.
2573class TileUpdate {
2574 constructor(view, old, blockWrappers, decorations, disallowBlockEffectsFor) {
2575 this.view = view;
2576 this.decorations = decorations;
2577 this.disallowBlockEffectsFor = disallowBlockEffectsFor;
2578 this.openWidget = false;
2579 this.openMarks = 0;
2580 this.cache = new TileCache(view);
2581 this.text = new TextStream(view.state.doc);
2582 this.builder = new TileBuilder(this.cache, new DocTile(view, view.contentDOM), RangeSet.iter(blockWrappers));
2583 this.cache.reused.set(old, 2 /* Reused.DOM */);
2584 this.old = new TilePointer(old);
2585 this.reuseWalker = {
2586 skip: (tile, from, to) => {
2587 this.cache.add(tile);
2588 if (tile.isComposite())
2589 return false;
2590 },
2591 enter: tile => this.cache.add(tile),
2592 leave: () => { },
2593 break: () => { }
2594 };
2595 }
2596 run(changes, composition) {
2597 let compositionContext = composition && this.getCompositionContext(composition.text);
2598 for (let posA = 0, posB = 0, i = 0;;) {
2599 let next = i < changes.length ? changes[i++] : null;
2600 let skipA = next ? next.fromA : this.old.root.length;
2601 if (skipA > posA) {
2602 let len = skipA - posA;
2603 this.preserve(len, !i, !next);
2604 posA = skipA;
2605 posB += len;
2606 }
2607 if (!next)
2608 break;
2609 // Compositions need to be handled specially, forcing the
2610 // focused text node and its parent nodes to remain stable at
2611 // that point in the document.
2612 if (composition && next.fromA <= composition.range.fromA && next.toA >= composition.range.toA) {
2613 this.forward(next.fromA, composition.range.fromA, composition.range.fromA < composition.range.toA ? 1 : -1);
2614 this.emit(posB, composition.range.fromB);
2615 this.cache.clear(); // Must not reuse DOM across composition
2616 this.builder.addComposition(composition, compositionContext);
2617 this.text.skip(composition.range.toB - composition.range.fromB);
2618 this.forward(composition.range.fromA, next.toA);
2619 this.emit(composition.range.toB, next.toB);
2620 }
2621 else {
2622 this.forward(next.fromA, next.toA);
2623 this.emit(posB, next.toB);
2624 }
2625 posB = next.toB;
2626 posA = next.toA;
2627 }
2628 if (this.builder.curLine)
2629 this.builder.endLine();
2630 return this.builder.root;
2631 }
2632 preserve(length, incStart, incEnd) {
2633 let activeMarks = getMarks(this.old), openMarks = this.openMarks;
2634 this.old.advance(length, incEnd ? 1 : -1, {
2635 skip: (tile, from, to) => {
2636 if (tile.isWidget()) {
2637 if (this.openWidget) {
2638 this.builder.continueWidget(to - from);
2639 }
2640 else {
2641 let widget = to > 0 || from < tile.length
2642 ? WidgetTile.of(tile.widget, this.view, to - from, tile.flags & 496 /* TileFlag.Widget */, this.cache.maybeReuse(tile))
2643 : this.cache.reuse(tile);
2644 if (widget.flags & 256 /* TileFlag.Block */) {
2645 widget.flags &= ~1 /* TileFlag.BreakAfter */;
2646 this.builder.addBlockWidget(widget);
2647 }
2648 else {
2649 this.builder.ensureLine(null);
2650 this.builder.addInlineWidget(widget, activeMarks, openMarks);
2651 openMarks = activeMarks.length;
2652 }
2653 }
2654 }
2655 else if (tile.isText()) {
2656 this.builder.ensureLine(null);
2657 if (!from && to == tile.length && !this.cache.reused.has(tile)) {
2658 this.builder.addText(tile.text, activeMarks, openMarks, this.cache.reuse(tile));
2659 }
2660 else {
2661 this.cache.add(tile);
2662 this.builder.addText(tile.text.slice(from, to), activeMarks, openMarks);
2663 }
2664 openMarks = activeMarks.length;
2665 }
2666 else if (tile.isLine()) {
2667 tile.flags &= ~1 /* TileFlag.BreakAfter */;
2668 this.cache.reused.set(tile, 1 /* Reused.Full */);
2669 this.builder.addLine(tile);
2670 }
2671 else if (tile instanceof WidgetBufferTile) {
2672 this.cache.add(tile);
2673 }
2674 else if (tile instanceof MarkTile) {
2675 this.builder.ensureLine(null);
2676 this.builder.addMark(tile, activeMarks, openMarks);
2677 this.cache.reused.set(tile, 1 /* Reused.Full */);
2678 openMarks = activeMarks.length;
2679 }
2680 else {
2681 return false;
2682 }
2683 this.openWidget = false;
2684 },
2685 enter: (tile) => {
2686 if (tile.isLine()) {
2687 this.builder.addLineStart(tile.attrs, this.cache.maybeReuse(tile));
2688 }
2689 else {
2690 this.cache.add(tile);
2691 if (tile instanceof MarkTile)
2692 activeMarks.unshift(tile.mark);
2693 }
2694 this.openWidget = false;
2695 },
2696 leave: (tile) => {
2697 if (tile.isLine()) {
2698 if (activeMarks.length)
2699 activeMarks.length = openMarks = 0;
2700 }
2701 else if (tile instanceof MarkTile) {
2702 activeMarks.shift();
2703 openMarks = Math.min(openMarks, activeMarks.length);
2704 }
2705 },
2706 break: () => {
2707 this.builder.addBreak();
2708 this.openWidget = false;
2709 },
2710 });
2711 this.text.skip(length);
2712 }
2713 emit(from, to) {
2714 let pendingLineAttrs = null;
2715 let b = this.builder, markCount = 0;
2716 let openEnd = RangeSet.spans(this.decorations, from, to, {
2717 point: (from, to, deco, active, openStart, index) => {
2718 if (deco instanceof PointDecoration) {
2719 if (this.disallowBlockEffectsFor[index]) {
2720 if (deco.block)
2721 throw new RangeError("Block decorations may not be specified via plugins");
2722 if (to > this.view.state.doc.lineAt(from).to)
2723 throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
2724 }
2725 markCount = active.length;
2726 if (openStart > active.length) {
2727 b.continueWidget(to - from);
2728 }
2729 else {
2730 let widget = deco.widget || (deco.block ? NullWidget.block : NullWidget.inline);
2731 let flags = widgetFlags(deco);
2732 let tile = this.cache.findWidget(widget, to - from, flags) || WidgetTile.of(widget, this.view, to - from, flags);
2733 if (deco.block) {
2734 if (deco.startSide > 0)
2735 b.addLineStartIfNotCovered(pendingLineAttrs);
2736 b.addBlockWidget(tile);
2737 }
2738 else {
2739 b.ensureLine(pendingLineAttrs);
2740 b.addInlineWidget(tile, active, openStart);
2741 }
2742 }
2743 pendingLineAttrs = null;
2744 }
2745 else {
2746 pendingLineAttrs = addLineDeco(pendingLineAttrs, deco);
2747 }
2748 if (to > from)
2749 this.text.skip(to - from);
2750 },
2751 span: (from, to, active, openStart) => {
2752 for (let pos = from; pos < to;) {
2753 let chars = this.text.next(Math.min(512 /* C.Chunk */, to - pos));
2754 if (chars == null) { // Line break
2755 b.addLineStartIfNotCovered(pendingLineAttrs);
2756 b.addBreak();
2757 pos++;
2758 }
2759 else {
2760 b.ensureLine(pendingLineAttrs);
2761 b.addText(chars, active, pos == from ? openStart : active.length);
2762 pos += chars.length;
2763 }
2764 pendingLineAttrs = null;
2765 }
2766 }
2767 });
2768 b.addLineStartIfNotCovered(pendingLineAttrs);
2769 this.openWidget = openEnd > markCount;
2770 this.openMarks = openEnd;
2771 }
2772 forward(from, to, side = 1) {
2773 if (to - from <= 10) {
2774 this.old.advance(to - from, side, this.reuseWalker);
2775 }
2776 else {
2777 this.old.advance(5, -1, this.reuseWalker);
2778 this.old.advance(to - from - 10, -1);
2779 this.old.advance(5, side, this.reuseWalker);
2780 }
2781 }
2782 getCompositionContext(text) {
2783 let marks = [], line = null;
2784 for (let parent = text.parentNode;; parent = parent.parentNode) {
2785 let tile = Tile.get(parent);
2786 if (parent == this.view.contentDOM)
2787 break;
2788 if (tile instanceof MarkTile)
2789 marks.push(tile);
2790 else if (tile === null || tile === void 0 ? void 0 : tile.isLine())
2791 line = tile;
2792 else if (tile instanceof BlockWrapperTile) ; // Ignore
2793 else if (parent.nodeName == "DIV" && !line && parent != this.view.contentDOM)
2794 line = new LineTile(parent, lineBaseAttrs);
2795 else if (!line)
2796 marks.push(MarkTile.of(new MarkDecoration({ tagName: parent.nodeName.toLowerCase(), attributes: getAttrs(parent) }), parent));
2797 }
2798 return { line: line, marks };
2799 }
2800}
2801function hasContent(tile, requireText) {
2802 let scan = (tile) => {
2803 for (let ch of tile.children)
2804 if ((requireText ? ch.isText() : ch.length) || scan(ch))
2805 return true;
2806 return false;
2807 };
2808 return scan(tile);
2809}
2810function widgetFlags(deco) {
2811 let flags = deco.isReplace ? (deco.startSide < 0 ? 64 /* TileFlag.IncStart */ : 0) | (deco.endSide > 0 ? 128 /* TileFlag.IncEnd */ : 0)
2812 : (deco.startSide > 0 ? 32 /* TileFlag.After */ : 16 /* TileFlag.Before */);
2813 if (deco.block)
2814 flags |= 256 /* TileFlag.Block */;
2815 return flags;
2816}
2817const lineBaseAttrs = { class: "cm-line" };
2818function addLineDeco(value, deco) {
2819 let attrs = deco.spec.attributes, cls = deco.spec.class;
2820 if (!attrs && !cls)
2821 return value;
2822 if (!value)
2823 value = { class: "cm-line" };
2824 if (attrs)
2825 combineAttrs(attrs, value);
2826 if (cls)
2827 value.class += " " + cls;
2828 return value;
2829}
2830function getMarks(ptr) {
2831 let found = [];
2832 for (let i = ptr.parents.length; i > 1; i--) {
2833 let tile = i == ptr.parents.length ? ptr.tile : ptr.parents[i].tile;
2834 if (tile instanceof MarkTile)
2835 found.push(tile.mark);
2836 }
2837 return found;
2838}
2839function freeNode(node) {
2840 let tile = Tile.get(node);
2841 if (tile)
2842 tile.setDOM(node.cloneNode());
2843 return node;
2844}
2845class NullWidget extends WidgetType {
2846 constructor(tag) {
2847 super();
2848 this.tag = tag;
2849 }
2850 eq(other) { return other.tag == this.tag; }
2851 toDOM() { return document.createElement(this.tag); }
2852 updateDOM(elt) { return elt.nodeName.toLowerCase() == this.tag; }
2853 get isHidden() { return true; }
2854}
2855NullWidget.inline = /*@__PURE__*/new NullWidget("span");
2856NullWidget.block = /*@__PURE__*/new NullWidget("div");
2857const BreakWidget = /*@__PURE__*/new class extends WidgetType {
2858 toDOM() { return document.createElement("br"); }
2859 get isHidden() { return true; }
2860 get editable() { return true; }
2861};
2862
2863class DocView {
2864 constructor(view) {
2865 this.view = view;
2866 this.decorations = [];
2867 this.blockWrappers = [];
2868 this.dynamicDecorationMap = [false];
2869 this.domChanged = null;
2870 this.hasComposition = null;
2871 this.editContextFormatting = Decoration.none;
2872 this.lastCompositionAfterCursor = false;
2873 // Track a minimum width for the editor. When measuring sizes in
2874 // measureVisibleLineHeights, this is updated to point at the width
2875 // of a given element and its extent in the document. When a change
2876 // happens in that range, these are reset. That way, once we've seen
2877 // a line/element of a given length, we keep the editor wide enough
2878 // to fit at least that element, until it is changed, at which point
2879 // we forget it again.
2880 this.minWidth = 0;
2881 this.minWidthFrom = 0;
2882 this.minWidthTo = 0;
2883 // Track whether the DOM selection was set in a lossy way, so that
2884 // we don't mess it up when reading it back it
2885 this.impreciseAnchor = null;
2886 this.impreciseHead = null;
2887 this.forceSelection = false;
2888 // Used by the resize observer to ignore resizes that we caused
2889 // ourselves
2890 this.lastUpdate = Date.now();
2891 this.updateDeco();
2892 this.tile = new DocTile(view, view.contentDOM);
2893 this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], null);
2894 }
2895 // Update the document view to a given state.
2896 update(update) {
2897 var _a;
2898 let changedRanges = update.changedRanges;
2899 if (this.minWidth > 0 && changedRanges.length) {
2900 if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
2901 this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
2902 }
2903 else {
2904 this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
2905 this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2906 }
2907 }
2908 this.updateEditContextFormatting(update);
2909 let readCompositionAt = -1;
2910 if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
2911 if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
2912 readCompositionAt = this.domChanged.newSel.head;
2913 else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
2914 readCompositionAt = update.state.selection.main.head;
2915 }
2916 let composition = readCompositionAt > -1 ? findCompositionRange(this.view, update.changes, readCompositionAt) : null;
2917 this.domChanged = null;
2918 if (this.hasComposition) {
2919 let { from, to } = this.hasComposition;
2920 changedRanges = new ChangedRange(from, to, update.changes.mapPos(from, -1), update.changes.mapPos(to, 1))
2921 .addToSet(changedRanges.slice());
2922 }
2923 this.hasComposition = composition ? { from: composition.range.fromB, to: composition.range.toB } : null;
2924 // When the DOM nodes around the selection are moved to another
2925 // parent, Chrome sometimes reports a different selection through
2926 // getSelection than the one that it actually shows to the user.
2927 // This forces a selection update when lines are joined to work
2928 // around that. Issue #54
2929 if ((browser.ie || browser.chrome) && !composition && update &&
2930 update.state.doc.lines != update.startState.doc.lines)
2931 this.forceSelection = true;
2932 let prevDeco = this.decorations, prevWrappers = this.blockWrappers;
2933 this.updateDeco();
2934 let decoDiff = findChangedDeco(prevDeco, this.decorations, update.changes);
2935 if (decoDiff.length)
2936 changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
2937 let blockDiff = findChangedWrappers(prevWrappers, this.blockWrappers, update.changes);
2938 if (blockDiff.length)
2939 changedRanges = ChangedRange.extendWithRanges(changedRanges, blockDiff);
2940 if (composition && !changedRanges.some(r => r.fromA <= composition.range.fromA && r.toA >= composition.range.toA))
2941 changedRanges = composition.range.addToSet(changedRanges.slice());
2942 if ((this.tile.flags & 2 /* TileFlag.Synced */) && changedRanges.length == 0) {
2943 return false;
2944 }
2945 else {
2946 this.updateInner(changedRanges, composition);
2947 if (update.transactions.length)
2948 this.lastUpdate = Date.now();
2949 return true;
2950 }
2951 }
2952 // Used by update and the constructor do perform the actual DOM
2953 // update
2954 updateInner(changes, composition) {
2955 this.view.viewState.mustMeasureContent = true;
2956 let { observer } = this.view;
2957 observer.ignore(() => {
2958 if (composition || changes.length) {
2959 let oldTile = this.tile;
2960 let builder = new TileUpdate(this.view, oldTile, this.blockWrappers, this.decorations, this.dynamicDecorationMap);
2961 if (composition && Tile.get(composition.text))
2962 builder.cache.reused.set(Tile.get(composition.text), 2 /* Reused.DOM */);
2963 this.tile = builder.run(changes, composition);
2964 destroyDropped(oldTile, builder.cache.reused);
2965 }
2966 // Lock the height during redrawing, since Chrome sometimes
2967 // messes with the scroll position during DOM mutation (though
2968 // no relayout is triggered and I cannot imagine how it can
2969 // recompute the scroll position without a layout)
2970 this.tile.dom.style.height = this.view.viewState.contentHeight / this.view.scaleY + "px";
2971 this.tile.dom.style.flexBasis = this.minWidth ? this.minWidth + "px" : "";
2972 // Chrome will sometimes, when DOM mutations occur directly
2973 // around the selection, get confused and report a different
2974 // selection from the one it displays (issue #218). This tries
2975 // to detect that situation.
2976 let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2977 this.tile.sync(track);
2978 if (track && (track.written || observer.selectionRange.focusNode != track.node || !this.tile.dom.contains(track.node)))
2979 this.forceSelection = true;
2980 this.tile.dom.style.height = "";
2981 });
2982 let gaps = [];
2983 if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2984 for (let child of this.tile.children)
2985 if (child.isWidget() && child.widget instanceof BlockGapWidget)
2986 gaps.push(child.dom);
2987 observer.updateGaps(gaps);
2988 }
2989 updateEditContextFormatting(update) {
2990 this.editContextFormatting = this.editContextFormatting.map(update.changes);
2991 for (let tr of update.transactions)
2992 for (let effect of tr.effects)
2993 if (effect.is(setEditContextFormatting)) {
2994 this.editContextFormatting = effect.value;
2995 }
2996 }
2997 // Sync the DOM selection to this.state.selection
2998 updateSelection(mustRead = false, fromPointer = false) {
2999 if (mustRead || !this.view.observer.selectionRange.focusNode)
3000 this.view.observer.readSelectionRange();
3001 let { dom } = this.tile;
3002 let activeElt = this.view.root.activeElement, focused = activeElt == dom;
3003 let selectionNotFocus = !focused && !(this.view.state.facet(editable) || dom.tabIndex > -1) &&
3004 hasSelection(dom, this.view.observer.selectionRange) && !(activeElt && dom.contains(activeElt));
3005 if (!(focused || fromPointer || selectionNotFocus))
3006 return;
3007 let force = this.forceSelection;
3008 this.forceSelection = false;
3009 let main = this.view.state.selection.main, anchor, head;
3010 if (main.empty) {
3011 head = anchor = this.inlineDOMNearPos(main.anchor, main.assoc || 1);
3012 }
3013 else {
3014 head = this.inlineDOMNearPos(main.head, main.head == main.from ? 1 : -1);
3015 anchor = this.inlineDOMNearPos(main.anchor, main.anchor == main.from ? 1 : -1);
3016 }
3017 // Always reset on Firefox when next to an uneditable node to
3018 // avoid invisible cursor bugs (#111)
3019 if (browser.gecko && main.empty && !this.hasComposition && betweenUneditable(anchor)) {
3020 let dummy = document.createTextNode("");
3021 this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
3022 anchor = head = new DOMPos(dummy, 0);
3023 force = true;
3024 }
3025 let domSel = this.view.observer.selectionRange;
3026 // If the selection is already here, or in an equivalent position, don't touch it
3027 if (force || !domSel.focusNode || (!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
3028 !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) && !this.suppressWidgetCursorChange(domSel, main)) {
3029 this.view.observer.ignore(() => {
3030 // Chrome Android will hide the virtual keyboard when tapping
3031 // inside an uneditable node, and not bring it back when we
3032 // move the cursor to its proper position. This tries to
3033 // restore the keyboard by cycling focus.
3034 if (browser.android && browser.chrome && dom.contains(domSel.focusNode) &&
3035 inUneditable(domSel.focusNode, dom)) {
3036 dom.blur();
3037 dom.focus({ preventScroll: true });
3038 }
3039 let rawSel = getSelection(this.view.root);
3040 if (!rawSel) ;
3041 else if (main.empty) {
3042 // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
3043 if (browser.gecko) {
3044 let nextTo = nextToUneditable(anchor.node, anchor.offset);
3045 if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
3046 let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
3047 if (text)
3048 anchor = new DOMPos(text.node, text.offset);
3049 }
3050 }
3051 rawSel.collapse(anchor.node, anchor.offset);
3052 if (main.bidiLevel != null && rawSel.caretBidiLevel !== undefined)
3053 rawSel.caretBidiLevel = main.bidiLevel;
3054 }
3055 else if (rawSel.extend) {
3056 // Selection.extend can be used to create an 'inverted' selection
3057 // (one where the focus is before the anchor), but not all
3058 // browsers support it yet.
3059 rawSel.collapse(anchor.node, anchor.offset);
3060 // Safari will ignore the call above when the editor is
3061 // hidden, and then raise an error on the call to extend
3062 // (#940).
3063 try {
3064 rawSel.extend(head.node, head.offset);
3065 }
3066 catch (_) { }
3067 }
3068 else {
3069 // Primitive (IE) way
3070 let range = document.createRange();
3071 if (main.anchor > main.head)
3072 [anchor, head] = [head, anchor];
3073 range.setEnd(head.node, head.offset);
3074 range.setStart(anchor.node, anchor.offset);
3075 rawSel.removeAllRanges();
3076 rawSel.addRange(range);
3077 }
3078 if (selectionNotFocus && this.view.root.activeElement == dom) {
3079 dom.blur();
3080 if (activeElt)
3081 activeElt.focus();
3082 }
3083 });
3084 this.view.observer.setSelectionRange(anchor, head);
3085 }
3086 this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
3087 this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
3088 }
3089 // If a zero-length widget is inserted next to the cursor during
3090 // composition, avoid moving it across it and disrupting the
3091 // composition.
3092 suppressWidgetCursorChange(sel, cursor) {
3093 return this.hasComposition && cursor.empty &&
3094 isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset) &&
3095 this.posFromDOM(sel.focusNode, sel.focusOffset) == cursor.head;
3096 }
3097 enforceCursorAssoc() {
3098 if (this.hasComposition)
3099 return;
3100 let { view } = this, cursor = view.state.selection.main;
3101 let sel = getSelection(view.root);
3102 let { anchorNode, anchorOffset } = view.observer.selectionRange;
3103 if (!sel || !cursor.empty || !cursor.assoc || !sel.modify)
3104 return;
3105 let line = this.lineAt(cursor.head, cursor.assoc);
3106 if (!line)
3107 return;
3108 let lineStart = line.posAtStart;
3109 if (cursor.head == lineStart || cursor.head == lineStart + line.length)
3110 return;
3111 let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
3112 if (!before || !after || before.bottom > after.top)
3113 return;
3114 let dom = this.domAtPos(cursor.head + cursor.assoc, cursor.assoc);
3115 sel.collapse(dom.node, dom.offset);
3116 sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
3117 // This can go wrong in corner cases like single-character lines,
3118 // so check and reset if necessary.
3119 view.observer.readSelectionRange();
3120 let newRange = view.observer.selectionRange;
3121 if (view.docView.posFromDOM(newRange.anchorNode, newRange.anchorOffset) != cursor.from)
3122 sel.collapse(anchorNode, anchorOffset);
3123 }
3124 posFromDOM(node, offset) {
3125 let tile = this.tile.nearest(node);
3126 if (!tile)
3127 return this.tile.dom.compareDocumentPosition(node) & 2 /* PRECEDING */ ? 0 : this.view.state.doc.length;
3128 let start = tile.posAtStart;
3129 if (tile.isComposite()) {
3130 let after;
3131 if (node == tile.dom) {
3132 after = tile.dom.childNodes[offset];
3133 }
3134 else {
3135 let bias = maxOffset(node) == 0 ? 0 : offset == 0 ? -1 : 1;
3136 for (;;) {
3137 let parent = node.parentNode;
3138 if (parent == tile.dom)
3139 break;
3140 if (bias == 0 && parent.firstChild != parent.lastChild) {
3141 if (node == parent.firstChild)
3142 bias = -1;
3143 else
3144 bias = 1;
3145 }
3146 node = parent;
3147 }
3148 if (bias < 0)
3149 after = node;
3150 else
3151 after = node.nextSibling;
3152 }
3153 if (after == tile.dom.firstChild)
3154 return start;
3155 while (after && !Tile.get(after))
3156 after = after.nextSibling;
3157 if (!after)
3158 return start + tile.length;
3159 for (let i = 0, pos = start;; i++) {
3160 let child = tile.children[i];
3161 if (child.dom == after)
3162 return pos;
3163 pos += child.length + child.breakAfter;
3164 }
3165 }
3166 else if (tile.isText()) {
3167 return node == tile.dom ? start + offset : start + (offset ? tile.length : 0);
3168 }
3169 else {
3170 return start;
3171 }
3172 }
3173 domAtPos(pos, side) {
3174 let { tile, offset } = this.tile.resolveBlock(pos, side);
3175 if (tile.isWidget())
3176 return tile.domPosFor(pos, side);
3177 return tile.domIn(offset, side);
3178 }
3179 inlineDOMNearPos(pos, side) {
3180 let before, beforeOff = -1, beforeBad = false;
3181 let after, afterOff = -1, afterBad = false;
3182 this.tile.blockTiles((tile, off) => {
3183 if (tile.isWidget()) {
3184 if ((tile.flags & 32 /* TileFlag.After */) && off >= pos)
3185 return true;
3186 if (tile.flags & 16 /* TileFlag.Before */)
3187 beforeBad = true;
3188 }
3189 else {
3190 let end = off + tile.length;
3191 if (off <= pos) {
3192 before = tile;
3193 beforeOff = pos - off;
3194 beforeBad = end < pos;
3195 }
3196 if (end >= pos && !after) {
3197 after = tile;
3198 afterOff = pos - off;
3199 afterBad = off > pos;
3200 }
3201 if (off > pos && after)
3202 return true;
3203 }
3204 });
3205 if (!before && !after)
3206 return this.domAtPos(pos, side);
3207 if (beforeBad && after)
3208 before = null;
3209 else if (afterBad && before)
3210 after = null;
3211 return before && side < 0 || !after ? before.domIn(beforeOff, side) : after.domIn(afterOff, side);
3212 }
3213 coordsAt(pos, side) {
3214 let { tile, offset } = this.tile.resolveBlock(pos, side);
3215 if (tile.isWidget()) {
3216 if (tile.widget instanceof BlockGapWidget)
3217 return null;
3218 return tile.coordsInWidget(offset, side, true);
3219 }
3220 return tile.coordsIn(offset, side);
3221 }
3222 lineAt(pos, side) {
3223 let { tile } = this.tile.resolveBlock(pos, side);
3224 return tile.isLine() ? tile : null;
3225 }
3226 coordsForChar(pos) {
3227 let { tile, offset } = this.tile.resolveBlock(pos, 1);
3228 if (!tile.isLine())
3229 return null;
3230 function scan(tile, offset) {
3231 if (tile.isComposite()) {
3232 for (let ch of tile.children) {
3233 if (ch.length >= offset) {
3234 let found = scan(ch, offset);
3235 if (found)
3236 return found;
3237 }
3238 offset -= ch.length;
3239 if (offset < 0)
3240 break;
3241 }
3242 }
3243 else if (tile.isText() && offset < tile.length) {
3244 let end = findClusterBreak(tile.text, offset);
3245 if (end == offset)
3246 return null;
3247 let rects = textRange(tile.dom, offset, end).getClientRects();
3248 for (let i = 0; i < rects.length; i++) {
3249 let rect = rects[i];
3250 if (i == rects.length - 1 || rect.top < rect.bottom && rect.left < rect.right)
3251 return rect;
3252 }
3253 }
3254 return null;
3255 }
3256 return scan(tile, offset);
3257 }
3258 measureVisibleLineHeights(viewport) {
3259 let result = [], { from, to } = viewport;
3260 let contentWidth = this.view.contentDOM.clientWidth;
3261 let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
3262 let widest = -1, ltr = this.view.textDirection == Direction.LTR;
3263 let spaceAbove = 0;
3264 let scan = (tile, pos, measureBounds) => {
3265 for (let i = 0; i < tile.children.length; i++) {
3266 if (pos > to)
3267 break;
3268 let child = tile.children[i], end = pos + child.length;
3269 let childRect = child.dom.getBoundingClientRect(), { height } = childRect;
3270 if (measureBounds && !i)
3271 spaceAbove += childRect.top - measureBounds.top;
3272 if (child instanceof BlockWrapperTile) {
3273 if (end > from)
3274 scan(child, pos, childRect);
3275 }
3276 else if (pos >= from) {
3277 if (spaceAbove > 0)
3278 result.push(-spaceAbove);
3279 result.push(height + spaceAbove);
3280 spaceAbove = 0;
3281 if (isWider) {
3282 let last = child.dom.lastChild;
3283 let rects = last ? clientRectsFor(last) : [];
3284 if (rects.length) {
3285 let rect = rects[rects.length - 1];
3286 let width = ltr ? rect.right - childRect.left : childRect.right - rect.left;
3287 if (width > widest) {
3288 widest = width;
3289 this.minWidth = contentWidth;
3290 this.minWidthFrom = pos;
3291 this.minWidthTo = end;
3292 }
3293 }
3294 }
3295 }
3296 if (measureBounds && i == tile.children.length - 1)
3297 spaceAbove += measureBounds.bottom - childRect.bottom;
3298 pos = end + child.breakAfter;
3299 }
3300 };
3301 scan(this.tile, 0, null);
3302 return result;
3303 }
3304 textDirectionAt(pos) {
3305 let { tile } = this.tile.resolveBlock(pos, 1);
3306 return getComputedStyle(tile.dom).direction == "rtl" ? Direction.RTL : Direction.LTR;
3307 }
3308 measureTextSize() {
3309 let lineMeasure = this.tile.blockTiles(tile => {
3310 if (tile.isLine() && tile.children.length && tile.length <= 20) {
3311 let totalWidth = 0, textHeight;
3312 for (let child of tile.children) {
3313 if (!child.isText() || /[^ -~]/.test(child.text))
3314 return undefined;
3315 let rects = clientRectsFor(child.dom);
3316 if (rects.length != 1)
3317 return undefined;
3318 totalWidth += rects[0].width;
3319 textHeight = rects[0].height;
3320 }
3321 if (totalWidth)
3322 return {
3323 lineHeight: tile.dom.getBoundingClientRect().height,
3324 charWidth: totalWidth / tile.length,
3325 textHeight
3326 };
3327 }
3328 });
3329 if (lineMeasure)
3330 return lineMeasure;
3331 // If no workable line exists, force a layout of a measurable element
3332 let dummy = document.createElement("div"), lineHeight, charWidth, textHeight;
3333 dummy.className = "cm-line";
3334 dummy.style.width = "99999px";
3335 dummy.style.position = "absolute";
3336 dummy.textContent = "abc def ghi jkl mno pqr stu";
3337 this.view.observer.ignore(() => {
3338 this.tile.dom.appendChild(dummy);
3339 let rect = clientRectsFor(dummy.firstChild)[0];
3340 lineHeight = dummy.getBoundingClientRect().height;
3341 charWidth = rect && rect.width ? rect.width / 27 : 7;
3342 textHeight = rect && rect.height ? rect.height : lineHeight;
3343 dummy.remove();
3344 });
3345 return { lineHeight, charWidth, textHeight };
3346 }
3347 computeBlockGapDeco() {
3348 let deco = [], vs = this.view.viewState;
3349 for (let pos = 0, i = 0;; i++) {
3350 let next = i == vs.viewports.length ? null : vs.viewports[i];
3351 let end = next ? next.from - 1 : this.view.state.doc.length;
3352 if (end > pos) {
3353 let height = (vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top) / this.view.scaleY;
3354 deco.push(Decoration.replace({
3355 widget: new BlockGapWidget(height),
3356 block: true,
3357 inclusive: true,
3358 isBlockGap: true,
3359 }).range(pos, end));
3360 }
3361 if (!next)
3362 break;
3363 pos = next.to + 1;
3364 }
3365 return Decoration.set(deco);
3366 }
3367 updateDeco() {
3368 let i = 1;
3369 let allDeco = this.view.state.facet(decorations).map(d => {
3370 let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
3371 return dynamic ? d(this.view) : d;
3372 });
3373 let dynamicOuter = false, outerDeco = this.view.state.facet(outerDecorations).map((d, i) => {
3374 let dynamic = typeof d == "function";
3375 if (dynamic)
3376 dynamicOuter = true;
3377 return dynamic ? d(this.view) : d;
3378 });
3379 if (outerDeco.length) {
3380 this.dynamicDecorationMap[i++] = dynamicOuter;
3381 allDeco.push(RangeSet.join(outerDeco));
3382 }
3383 this.decorations = [
3384 this.editContextFormatting,
3385 ...allDeco,
3386 this.computeBlockGapDeco(),
3387 this.view.viewState.lineGapDeco
3388 ];
3389 while (i < this.decorations.length)
3390 this.dynamicDecorationMap[i++] = false;
3391 this.blockWrappers = this.view.state.facet(blockWrappers).map(v => typeof v == "function" ? v(this.view) : v);
3392 }
3393 scrollIntoView(target) {
3394 if (target.isSnapshot) {
3395 let ref = this.view.viewState.lineBlockAt(target.range.head);
3396 this.view.scrollDOM.scrollTop = ref.top - target.yMargin;
3397 this.view.scrollDOM.scrollLeft = target.xMargin;
3398 return;
3399 }
3400 for (let handler of this.view.state.facet(scrollHandler)) {
3401 try {
3402 if (handler(this.view, target.range, target))
3403 return true;
3404 }
3405 catch (e) {
3406 logException(this.view.state, e, "scroll handler");
3407 }
3408 }
3409 let { range } = target;
3410 let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
3411 if (!rect)
3412 return;
3413 if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
3414 rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
3415 right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
3416 let margins = getScrollMargins(this.view);
3417 let targetRect = {
3418 left: rect.left - margins.left, top: rect.top - margins.top,
3419 right: rect.right + margins.right, bottom: rect.bottom + margins.bottom
3420 };
3421 let { offsetWidth, offsetHeight } = this.view.scrollDOM;
3422 scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, Math.max(Math.min(target.xMargin, offsetWidth), -offsetWidth), Math.max(Math.min(target.yMargin, offsetHeight), -offsetHeight), this.view.textDirection == Direction.LTR);
3423 // On mobile browsers, the visual viewport may be smaller than the
3424 // actual reported viewport, causing scrollRectIntoView to fail to
3425 // scroll properly. Unfortunately, this visual viewport cannot be
3426 // updated directly, and scrollIntoView is the only way a script
3427 // can affect it. So this tries to kludge around the problem by
3428 // calling scrollIntoView on the scroll target's line.
3429 if (window.visualViewport && window.innerHeight - window.visualViewport.height > 1 &&
3430 (rect.top > window.pageYOffset + window.visualViewport.offsetTop + window.visualViewport.height ||
3431 rect.bottom < window.pageYOffset + window.visualViewport.offsetTop)) {
3432 let line = this.view.docView.lineAt(range.head, 1);
3433 if (line)
3434 line.dom.scrollIntoView({ block: "nearest" });
3435 }
3436 }
3437 lineHasWidget(pos) {
3438 let scan = (child) => child.isWidget() || child.children.some(scan);
3439 return scan(this.tile.resolveBlock(pos, 1).tile);
3440 }
3441 destroy() {
3442 destroyDropped(this.tile);
3443 }
3444}
3445function destroyDropped(tile, reused) {
3446 let r = reused === null || reused === void 0 ? void 0 : reused.get(tile);
3447 if (r != 1 /* Reused.Full */) {
3448 if (r == null)
3449 tile.destroy();
3450 for (let ch of tile.children)
3451 destroyDropped(ch, reused);
3452 }
3453}
3454function betweenUneditable(pos) {
3455 return pos.node.nodeType == 1 && pos.node.firstChild &&
3456 (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
3457 (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
3458}
3459function findCompositionNode(view, headPos) {
3460 let sel = view.observer.selectionRange;
3461 if (!sel.focusNode)
3462 return null;
3463 let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset);
3464 let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset);
3465 let textNode = textBefore || textAfter;
3466 if (textAfter && textBefore && textAfter.node != textBefore.node) {
3467 let tileAfter = Tile.get(textAfter.node);
3468 if (!tileAfter || tileAfter.isText() && tileAfter.text != textAfter.node.nodeValue) {
3469 textNode = textAfter;
3470 }
3471 else if (view.docView.lastCompositionAfterCursor) {
3472 let tileBefore = Tile.get(textBefore.node);
3473 if (!(!tileBefore || tileBefore.isText() && tileBefore.text != textBefore.node.nodeValue))
3474 textNode = textAfter;
3475 }
3476 }
3477 view.docView.lastCompositionAfterCursor = textNode != textBefore;
3478 if (!textNode)
3479 return null;
3480 let from = headPos - textNode.offset;
3481 return { from, to: from + textNode.node.nodeValue.length, node: textNode.node };
3482}
3483function findCompositionRange(view, changes, headPos) {
3484 let found = findCompositionNode(view, headPos);
3485 if (!found)
3486 return null;
3487 let { node: textNode, from, to } = found, text = textNode.nodeValue;
3488 // Don't try to preserve multi-line compositions
3489 if (/[\n\r]/.test(text))
3490 return null;
3491 if (view.state.doc.sliceString(found.from, found.to) != text)
3492 return null;
3493 let inv = changes.invertedDesc;
3494 return { range: new ChangedRange(inv.mapPos(from), inv.mapPos(to), from, to), text: textNode };
3495}
3496function nextToUneditable(node, offset) {
3497 if (node.nodeType != 1)
3498 return 0;
3499 return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* NextTo.Before */ : 0) |
3500 (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* NextTo.After */ : 0);
3501}
3502let DecorationComparator$1 = class DecorationComparator {
3503 constructor() {
3504 this.changes = [];
3505 }
3506 compareRange(from, to) { addRange(from, to, this.changes); }
3507 comparePoint(from, to) { addRange(from, to, this.changes); }
3508 boundChange(pos) { addRange(pos, pos, this.changes); }
3509};
3510function findChangedDeco(a, b, diff) {
3511 let comp = new DecorationComparator$1;
3512 RangeSet.compare(a, b, diff, comp);
3513 return comp.changes;
3514}
3515class WrapperComparator {
3516 constructor() {
3517 this.changes = [];
3518 }
3519 compareRange(from, to) { addRange(from, to, this.changes); }
3520 comparePoint() { }
3521 boundChange(pos) { addRange(pos, pos, this.changes); }
3522}
3523function findChangedWrappers(a, b, diff) {
3524 let comp = new WrapperComparator;
3525 RangeSet.compare(a, b, diff, comp);
3526 return comp.changes;
3527}
3528function inUneditable(node, inside) {
3529 for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
3530 if (cur.nodeType == 1 && cur.contentEditable == 'false') {
3531 return true;
3532 }
3533 }
3534 return false;
3535}
3536function touchesComposition(changes, composition) {
3537 let touched = false;
3538 if (composition)
3539 changes.iterChangedRanges((from, to) => {
3540 if (from < composition.to && to > composition.from)
3541 touched = true;
3542 });
3543 return touched;
3544}
3545class BlockGapWidget extends WidgetType {
3546 constructor(height) {
3547 super();
3548 this.height = height;
3549 }
3550 toDOM() {
3551 let elt = document.createElement("div");
3552 elt.className = "cm-gap";
3553 this.updateDOM(elt);
3554 return elt;
3555 }
3556 eq(other) { return other.height == this.height; }
3557 updateDOM(elt) {
3558 elt.style.height = this.height + "px";
3559 return true;
3560 }
3561 get editable() { return true; }
3562 get estimatedHeight() { return this.height; }
3563 ignoreEvent() { return false; }
3564}
3565
3566function groupAt(state, pos, bias = 1) {
3567 let categorize = state.charCategorizer(pos);
3568 let line = state.doc.lineAt(pos), linePos = pos - line.from;
3569 if (line.length == 0)
3570 return EditorSelection.cursor(pos);
3571 if (linePos == 0)
3572 bias = 1;
3573 else if (linePos == line.length)
3574 bias = -1;
3575 let from = linePos, to = linePos;
3576 if (bias < 0)
3577 from = findClusterBreak(line.text, linePos, false);
3578 else
3579 to = findClusterBreak(line.text, linePos);
3580 let cat = categorize(line.text.slice(from, to));
3581 while (from > 0) {
3582 let prev = findClusterBreak(line.text, from, false);
3583 if (categorize(line.text.slice(prev, from)) != cat)
3584 break;
3585 from = prev;
3586 }
3587 while (to < line.length) {
3588 let next = findClusterBreak(line.text, to);
3589 if (categorize(line.text.slice(to, next)) != cat)
3590 break;
3591 to = next;
3592 }
3593 return EditorSelection.range(from + line.from, to + line.from);
3594}
3595function posAtCoordsImprecise(view, contentRect, block, x, y) {
3596 let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
3597 if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
3598 let textHeight = view.viewState.heightOracle.textHeight;
3599 let line = Math.floor((y - block.top - (view.defaultLineHeight - textHeight) * 0.5) / textHeight);
3600 into += line * view.viewState.heightOracle.lineLength;
3601 }
3602 let content = view.state.sliceDoc(block.from, block.to);
3603 return block.from + findColumn(content, into, view.state.tabSize);
3604}
3605function blockAt(view, pos, side) {
3606 let line = view.lineBlockAt(pos);
3607 if (Array.isArray(line.type)) {
3608 let best;
3609 for (let l of line.type) {
3610 if (l.from > pos)
3611 break;
3612 if (l.to < pos)
3613 continue;
3614 if (l.from < pos && l.to > pos)
3615 return l;
3616 if (!best || (l.type == BlockType.Text && (best.type != l.type || (side < 0 ? l.from < pos : l.to > pos))))
3617 best = l;
3618 }
3619 return best || line;
3620 }
3621 return line;
3622}
3623function moveToLineBoundary(view, start, forward, includeWrap) {
3624 let line = blockAt(view, start.head, start.assoc || -1);
3625 let coords = !includeWrap || line.type != BlockType.Text || !(view.lineWrapping || line.widgetLineBreaks) ? null
3626 : view.coordsAtPos(start.assoc < 0 && start.head > line.from ? start.head - 1 : start.head);
3627 if (coords) {
3628 let editorRect = view.dom.getBoundingClientRect();
3629 let direction = view.textDirectionAt(line.from);
3630 let pos = view.posAtCoords({ x: forward == (direction == Direction.LTR) ? editorRect.right - 1 : editorRect.left + 1,
3631 y: (coords.top + coords.bottom) / 2 });
3632 if (pos != null)
3633 return EditorSelection.cursor(pos, forward ? -1 : 1);
3634 }
3635 return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1);
3636}
3637function moveByChar(view, start, forward, by) {
3638 let line = view.state.doc.lineAt(start.head), spans = view.bidiSpans(line);
3639 let direction = view.textDirectionAt(line.from);
3640 for (let cur = start, check = null;;) {
3641 let next = moveVisually(line, spans, direction, cur, forward), char = movedOver;
3642 if (!next) {
3643 if (line.number == (forward ? view.state.doc.lines : 1))
3644 return cur;
3645 char = "\n";
3646 line = view.state.doc.line(line.number + (forward ? 1 : -1));
3647 spans = view.bidiSpans(line);
3648 next = view.visualLineSide(line, !forward);
3649 }
3650 if (!check) {
3651 if (!by)
3652 return next;
3653 check = by(char);
3654 }
3655 else if (!check(char)) {
3656 return cur;
3657 }
3658 cur = next;
3659 }
3660}
3661function byGroup(view, pos, start) {
3662 let categorize = view.state.charCategorizer(pos);
3663 let cat = categorize(start);
3664 return (next) => {
3665 let nextCat = categorize(next);
3666 if (cat == CharCategory.Space)
3667 cat = nextCat;
3668 return cat == nextCat;
3669 };
3670}
3671function moveVertically(view, start, forward, distance) {
3672 let startPos = start.head, dir = forward ? 1 : -1;
3673 if (startPos == (forward ? view.state.doc.length : 0))
3674 return EditorSelection.cursor(startPos, start.assoc);
3675 let goal = start.goalColumn, startY;
3676 let rect = view.contentDOM.getBoundingClientRect();
3677 let startCoords = view.coordsAtPos(startPos, (start.empty ? start.assoc : 0) || (forward ? 1 : -1)), docTop = view.documentTop;
3678 if (startCoords) {
3679 if (goal == null)
3680 goal = startCoords.left - rect.left;
3681 startY = dir < 0 ? startCoords.top : startCoords.bottom;
3682 }
3683 else {
3684 let line = view.viewState.lineBlockAt(startPos);
3685 if (goal == null)
3686 goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3687 startY = (dir < 0 ? line.top : line.bottom) + docTop;
3688 }
3689 let resolvedGoal = rect.left + goal;
3690 let dist = distance !== null && distance !== void 0 ? distance : (view.viewState.heightOracle.textHeight >> 1);
3691 let pos = posAtCoords(view, { x: resolvedGoal, y: startY + dist * dir }, false, dir);
3692 return EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3693}
3694function skipAtomicRanges(atoms, pos, bias) {
3695 for (;;) {
3696 let moved = 0;
3697 for (let set of atoms) {
3698 set.between(pos - 1, pos + 1, (from, to, value) => {
3699 if (pos > from && pos < to) {
3700 let side = moved || bias || (pos - from < to - pos ? -1 : 1);
3701 pos = side < 0 ? from : to;
3702 moved = side;
3703 }
3704 });
3705 }
3706 if (!moved)
3707 return pos;
3708 }
3709}
3710function skipAtomsForSelection(atoms, sel) {
3711 let ranges = null;
3712 for (let i = 0; i < sel.ranges.length; i++) {
3713 let range = sel.ranges[i], updated = null;
3714 if (range.empty) {
3715 let pos = skipAtomicRanges(atoms, range.from, 0);
3716 if (pos != range.from)
3717 updated = EditorSelection.cursor(pos, -1);
3718 }
3719 else {
3720 let from = skipAtomicRanges(atoms, range.from, -1);
3721 let to = skipAtomicRanges(atoms, range.to, 1);
3722 if (from != range.from || to != range.to)
3723 updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
3724 }
3725 if (updated) {
3726 if (!ranges)
3727 ranges = sel.ranges.slice();
3728 ranges[i] = updated;
3729 }
3730 }
3731 return ranges ? EditorSelection.create(ranges, sel.mainIndex) : sel;
3732}
3733function skipAtoms(view, oldPos, pos) {
3734 let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map(f => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
3735 return newPos == pos.from ? pos : EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
3736}
3737class PosAssoc {
3738 constructor(pos, assoc) {
3739 this.pos = pos;
3740 this.assoc = assoc;
3741 }
3742}
3743function posAtCoords(view, coords, precise, scanY) {
3744 let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
3745 let { x, y } = coords, yOffset = y - docTop, block;
3746 // First find the block at the given Y position, if any. If scanY is
3747 // given (used for vertical cursor motion), try to skip widgets and
3748 // line padding.
3749 for (;;) {
3750 if (yOffset < 0)
3751 return new PosAssoc(0, 1);
3752 if (yOffset > view.viewState.docHeight)
3753 return new PosAssoc(view.state.doc.length, -1);
3754 block = view.elementAtHeight(yOffset);
3755 if (scanY == null)
3756 break;
3757 if (block.type == BlockType.Text) {
3758 if (scanY < 0 ? block.to < view.viewport.from : block.from > view.viewport.to)
3759 break;
3760 // Check whether we aren't landing on the top/bottom padding of the line
3761 let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY > 0 ? -1 : 1);
3762 if (rect && (scanY < 0 ? rect.top <= yOffset + docTop : rect.bottom >= yOffset + docTop))
3763 break;
3764 }
3765 let halfLine = view.viewState.heightOracle.textHeight / 2;
3766 yOffset = scanY > 0 ? block.bottom + halfLine : block.top - halfLine;
3767 }
3768 // If outside the viewport, return null if precise==true, an
3769 // estimate otherwise.
3770 if (view.viewport.from >= block.to || view.viewport.to <= block.from) {
3771 if (precise)
3772 return null;
3773 if (block.type == BlockType.Text) {
3774 let pos = posAtCoordsImprecise(view, content, block, x, y);
3775 return new PosAssoc(pos, pos == block.from ? 1 : -1);
3776 }
3777 }
3778 if (block.type != BlockType.Text)
3779 return yOffset < (block.top + block.bottom) / 2 ? new PosAssoc(block.from, 1) : new PosAssoc(block.to, -1);
3780 // Here we know we're in a line, so run the logic for inline layout
3781 let line = view.docView.lineAt(block.from, 2);
3782 if (!line || line.length != block.length)
3783 line = view.docView.lineAt(block.from, -2);
3784 return new InlineCoordsScan(view, x, y, view.textDirectionAt(block.from)).scanTile(line, block.from);
3785}
3786class InlineCoordsScan {
3787 constructor(view, x, y, baseDir) {
3788 this.view = view;
3789 this.x = x;
3790 this.y = y;
3791 this.baseDir = baseDir;
3792 // Cached bidi info
3793 this.line = null;
3794 this.spans = null;
3795 }
3796 bidiSpansAt(pos) {
3797 if (!this.line || this.line.from > pos || this.line.to < pos) {
3798 this.line = this.view.state.doc.lineAt(pos);
3799 this.spans = this.view.bidiSpans(this.line);
3800 }
3801 return this;
3802 }
3803 baseDirAt(pos, side) {
3804 let { line, spans } = this.bidiSpansAt(pos);
3805 let level = spans[BidiSpan.find(spans, pos - line.from, -1, side)].level;
3806 return level == this.baseDir;
3807 }
3808 dirAt(pos, side) {
3809 let { line, spans } = this.bidiSpansAt(pos);
3810 return spans[BidiSpan.find(spans, pos - line.from, -1, side)].dir;
3811 }
3812 // Used to short-circuit bidi tests for content with a uniform direction
3813 bidiIn(from, to) {
3814 let { spans, line } = this.bidiSpansAt(from);
3815 return spans.length > 1 || spans.length && (spans[0].level != this.baseDir || spans[0].to + line.from < to);
3816 }
3817 // Scan through the rectangles for the content of a tile with inline
3818 // content, looking for one that overlaps the queried position
3819 // vertically andis
3820 // closest horizontally. The caller is responsible for dividing its
3821 // content into N pieces, and pass an array with N+1 positions
3822 // (including the position after the last piece). For a text tile,
3823 // these will be character clusters, for a composite tile, these
3824 // will be child tiles.
3825 scan(positions, getRects) {
3826 let lo = 0, hi = positions.length - 1, seen = new Set();
3827 let bidi = this.bidiIn(positions[0], positions[hi]);
3828 let above, below;
3829 let closestI = -1, closestDx = 1e9, closestRect;
3830 // Because, when the content is bidirectional, a regular binary
3831 // search is hard to perform (the content order does not
3832 // correspond to visual order), this loop does something between a
3833 // regular binary search and a full scan, depending on what it can
3834 // get away with. The outer hi/lo bounds are only adjusted for
3835 // elements that are part of the base order.
3836 //
3837 // To make sure all elements inside those bounds are visited,
3838 // eventually, we keep a set of seen indices, and if the midpoint
3839 // has already been handled, we start in a random index within the
3840 // current bounds and scan forward until we find an index that
3841 // hasn't been seen yet.
3842 search: while (lo < hi) {
3843 let dist = hi - lo, mid = (lo + hi) >> 1;
3844 adjust: if (seen.has(mid)) {
3845 let scan = lo + Math.floor(Math.random() * dist);
3846 for (let i = 0; i < dist; i++) {
3847 if (!seen.has(scan)) {
3848 mid = scan;
3849 break adjust;
3850 }
3851 scan++;
3852 if (scan == hi)
3853 scan = lo; // Wrap around
3854 }
3855 break search; // No index found, we're done
3856 }
3857 seen.add(mid);
3858 let rects = getRects(mid);
3859 if (rects)
3860 for (let i = 0; i < rects.length; i++) {
3861 let rect = rects[i], side = 0;
3862 if (rect.bottom < this.y) {
3863 if (!above || above.bottom < rect.bottom)
3864 above = rect;
3865 side = 1;
3866 }
3867 else if (rect.top > this.y) {
3868 if (!below || below.top > rect.top)
3869 below = rect;
3870 side = -1;
3871 }
3872 else {
3873 let off = rect.left > this.x ? this.x - rect.left : rect.right < this.x ? this.x - rect.right : 0;
3874 let dx = Math.abs(off);
3875 if (dx < closestDx) {
3876 closestI = mid;
3877 closestDx = dx;
3878 closestRect = rect;
3879 }
3880 if (off)
3881 side = (off < 0) == (this.baseDir == Direction.LTR) ? -1 : 1;
3882 }
3883 // Narrow binary search when it is safe to do so
3884 if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
3885 hi = mid;
3886 else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
3887 lo = mid + 1;
3888 }
3889 }
3890 // If no element with y overlap is found, find the nearest element
3891 // on the y axis, move this.y into it, and retry the scan.
3892 if (!closestRect) {
3893 let side = above && (!below || (this.y - above.bottom < below.top - this.y)) ? above : below;
3894 this.y = (side.top + side.bottom) / 2;
3895 return this.scan(positions, getRects);
3896 }
3897 let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == Direction.LTR;
3898 return {
3899 i: closestI,
3900 // Test whether x is closes to the start or end of this element
3901 after: (this.x > (closestRect.left + closestRect.right) / 2) == ltr
3902 };
3903 }
3904 scanText(tile, offset) {
3905 let positions = [];
3906 for (let i = 0; i < tile.length; i = findClusterBreak(tile.text, i))
3907 positions.push(offset + i);
3908 positions.push(offset + tile.length);
3909 let scan = this.scan(positions, i => {
3910 let off = positions[i] - offset, end = positions[i + 1] - offset;
3911 return textRange(tile.dom, off, end).getClientRects();
3912 });
3913 return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(positions[scan.i], 1);
3914 }
3915 scanTile(tile, offset) {
3916 if (!tile.length)
3917 return new PosAssoc(offset, 1);
3918 if (tile.children.length == 1) { // Short-circuit single-child tiles
3919 let child = tile.children[0];
3920 if (child.isText())
3921 return this.scanText(child, offset);
3922 else if (child.isComposite())
3923 return this.scanTile(child, offset);
3924 }
3925 let positions = [offset];
3926 for (let i = 0, pos = offset; i < tile.children.length; i++)
3927 positions.push(pos += tile.children[i].length);
3928 let scan = this.scan(positions, i => {
3929 let child = tile.children[i];
3930 if (child.flags & 48 /* TileFlag.PointWidget */)
3931 return null;
3932 return (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
3933 });
3934 let child = tile.children[scan.i], pos = positions[scan.i];
3935 if (child.isText())
3936 return this.scanText(child, pos);
3937 if (child.isComposite())
3938 return this.scanTile(child, pos);
3939 return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(pos, 1);
3940 }
3941}
3942
3943const LineBreakPlaceholder = "\uffff";
3944class DOMReader {
3945 constructor(points, view) {
3946 this.points = points;
3947 this.view = view;
3948 this.text = "";
3949 this.lineSeparator = view.state.facet(EditorState.lineSeparator);
3950 }
3951 append(text) {
3952 this.text += text;
3953 }
3954 lineBreak() {
3955 this.text += LineBreakPlaceholder;
3956 }
3957 readRange(start, end) {
3958 if (!start)
3959 return this;
3960 let parent = start.parentNode;
3961 for (let cur = start;;) {
3962 this.findPointBefore(parent, cur);
3963 let oldLen = this.text.length;
3964 this.readNode(cur);
3965 let tile = Tile.get(cur), next = cur.nextSibling;
3966 if (next == end) {
3967 if ((tile === null || tile === void 0 ? void 0 : tile.breakAfter) && !next && parent != this.view.contentDOM)
3968 this.lineBreak();
3969 break;
3970 }
3971 let nextTile = Tile.get(next);
3972 if ((tile && nextTile ? tile.breakAfter :
3973 (tile ? tile.breakAfter : isBlockElement(cur)) ||
3974 (isBlockElement(next) && (cur.nodeName != "BR" || (tile === null || tile === void 0 ? void 0 : tile.isWidget())) && this.text.length > oldLen)) &&
3975 !isEmptyToEnd(next, end))
3976 this.lineBreak();
3977 cur = next;
3978 }
3979 this.findPointBefore(parent, end);
3980 return this;
3981 }
3982 readTextNode(node) {
3983 let text = node.nodeValue;
3984 for (let point of this.points)
3985 if (point.node == node)
3986 point.pos = this.text.length + Math.min(point.offset, text.length);
3987 for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
3988 let nextBreak = -1, breakSize = 1, m;
3989 if (this.lineSeparator) {
3990 nextBreak = text.indexOf(this.lineSeparator, off);
3991 breakSize = this.lineSeparator.length;
3992 }
3993 else if (m = re.exec(text)) {
3994 nextBreak = m.index;
3995 breakSize = m[0].length;
3996 }
3997 this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
3998 if (nextBreak < 0)
3999 break;
4000 this.lineBreak();
4001 if (breakSize > 1)
4002 for (let point of this.points)
4003 if (point.node == node && point.pos > this.text.length)
4004 point.pos -= breakSize - 1;
4005 off = nextBreak + breakSize;
4006 }
4007 }
4008 readNode(node) {
4009 let tile = Tile.get(node);
4010 let fromView = tile && tile.overrideDOMText;
4011 if (fromView != null) {
4012 this.findPointInside(node, fromView.length);
4013 for (let i = fromView.iter(); !i.next().done;) {
4014 if (i.lineBreak)
4015 this.lineBreak();
4016 else
4017 this.append(i.value);
4018 }
4019 }
4020 else if (node.nodeType == 3) {
4021 this.readTextNode(node);
4022 }
4023 else if (node.nodeName == "BR") {
4024 if (node.nextSibling)
4025 this.lineBreak();
4026 }
4027 else if (node.nodeType == 1) {
4028 this.readRange(node.firstChild, null);
4029 }
4030 }
4031 findPointBefore(node, next) {
4032 for (let point of this.points)
4033 if (point.node == node && node.childNodes[point.offset] == next)
4034 point.pos = this.text.length;
4035 }
4036 findPointInside(node, length) {
4037 for (let point of this.points)
4038 if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
4039 point.pos = this.text.length + (isAtEnd(node, point.node, point.offset) ? length : 0);
4040 }
4041}
4042function isAtEnd(parent, node, offset) {
4043 for (;;) {
4044 if (!node || offset < maxOffset(node))
4045 return false;
4046 if (node == parent)
4047 return true;
4048 offset = domIndex(node) + 1;
4049 node = node.parentNode;
4050 }
4051}
4052function isEmptyToEnd(node, end) {
4053 let widgets;
4054 for (;; node = node.nextSibling) {
4055 if (node == end || !node)
4056 break;
4057 let view = Tile.get(node);
4058 if (!(view === null || view === void 0 ? void 0 : view.isWidget()))
4059 return false;
4060 if (view)
4061 (widgets || (widgets = [])).push(view);
4062 }
4063 if (widgets)
4064 for (let w of widgets) {
4065 let override = w.overrideDOMText;
4066 if (override === null || override === void 0 ? void 0 : override.length)
4067 return false;
4068 }
4069 return true;
4070}
4071class DOMPoint {
4072 constructor(node, offset) {
4073 this.node = node;
4074 this.offset = offset;
4075 this.pos = -1;
4076 }
4077}
4078
4079class DOMChange {
4080 constructor(view, start, end, typeOver) {
4081 this.typeOver = typeOver;
4082 this.bounds = null;
4083 this.text = "";
4084 this.domChanged = start > -1;
4085 let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
4086 if (view.state.readOnly && start > -1) {
4087 // Ignore changes when the editor is read-only
4088 this.newSel = null;
4089 }
4090 else if (start > -1 && (this.bounds = domBoundsAround(view.docView.tile, start, end, 0))) {
4091 let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
4092 let reader = new DOMReader(selPoints, view);
4093 reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
4094 this.text = reader.text;
4095 this.newSel = selectionFromPoints(selPoints, this.bounds.from);
4096 }
4097 else {
4098 let domSel = view.observer.selectionRange;
4099 let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
4100 !contains(view.contentDOM, domSel.focusNode)
4101 ? view.state.selection.main.head
4102 : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
4103 let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
4104 !contains(view.contentDOM, domSel.anchorNode)
4105 ? view.state.selection.main.anchor
4106 : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
4107 // iOS will refuse to select the block gaps when doing
4108 // select-all.
4109 // Chrome will put the selection *inside* them, confusing
4110 // posFromDOM
4111 let vp = view.viewport;
4112 if ((browser.ios || browser.chrome) && view.state.selection.main.empty && head != anchor &&
4113 (vp.from > 0 || vp.to < view.state.doc.length)) {
4114 let from = Math.min(head, anchor), to = Math.max(head, anchor);
4115 let offFrom = vp.from - from, offTo = vp.to - to;
4116 if ((offFrom == 0 || offFrom == 1 || from == 0) && (offTo == 0 || offTo == -1 || to == view.state.doc.length)) {
4117 head = 0;
4118 anchor = view.state.doc.length;
4119 }
4120 }
4121 if (view.inputState.composing > -1 && view.state.selection.ranges.length > 1)
4122 this.newSel = view.state.selection.replaceRange(EditorSelection.range(anchor, head));
4123 else
4124 this.newSel = EditorSelection.single(anchor, head);
4125 }
4126 }
4127}
4128function domBoundsAround(tile, from, to, offset) {
4129 if (tile.isComposite()) {
4130 let fromI = -1, fromStart = -1, toI = -1, toEnd = -1;
4131 for (let i = 0, pos = offset, prevEnd = offset; i < tile.children.length; i++) {
4132 let child = tile.children[i], end = pos + child.length;
4133 if (pos < from && end > to)
4134 return domBoundsAround(child, from, to, pos);
4135 if (end >= from && fromI == -1) {
4136 fromI = i;
4137 fromStart = pos;
4138 }
4139 if (pos > to && child.dom.parentNode == tile.dom) {
4140 toI = i;
4141 toEnd = prevEnd;
4142 break;
4143 }
4144 prevEnd = end;
4145 pos = end + child.breakAfter;
4146 }
4147 return { from: fromStart, to: toEnd < 0 ? offset + tile.length : toEnd,
4148 startDOM: (fromI ? tile.children[fromI - 1].dom.nextSibling : null) || tile.dom.firstChild,
4149 endDOM: toI < tile.children.length && toI >= 0 ? tile.children[toI].dom : null };
4150 }
4151 else if (tile.isText()) {
4152 return { from: offset, to: offset + tile.length, startDOM: tile.dom, endDOM: tile.dom.nextSibling };
4153 }
4154 else {
4155 return null;
4156 }
4157}
4158function applyDOMChange(view, domChange) {
4159 let change;
4160 let { newSel } = domChange, { state } = view, sel = state.selection.main;
4161 let lastKey = view.inputState.lastKeyTime > Date.now() - 100 ? view.inputState.lastKeyCode : -1;
4162 if (domChange.bounds) {
4163 let { from, to } = domChange.bounds;
4164 let preferredPos = sel.from, preferredSide = null;
4165 // Prefer anchoring to end when Backspace is pressed (or, on
4166 // Android, when something was deleted)
4167 if (lastKey === 8 || browser.android && domChange.text.length < to - from) {
4168 preferredPos = sel.to;
4169 preferredSide = "end";
4170 }
4171 let cmp = state.doc.sliceString(from, to, LineBreakPlaceholder), selEnd, diff;
4172 if (!sel.empty && sel.from >= from && sel.to <= to && (domChange.typeOver || cmp != domChange.text) &&
4173 cmp.slice(0, sel.from - from) == domChange.text.slice(0, sel.from - from) &&
4174 cmp.slice(sel.to - from) == domChange.text.slice(selEnd = domChange.text.length - (cmp.length - (sel.to - from)))) {
4175 // This looks like a selection replacement
4176 change = { from: sel.from, to: sel.to,
4177 insert: Text.of(domChange.text.slice(sel.from - from, selEnd).split(LineBreakPlaceholder)) };
4178 }
4179 else if (diff = findDiff(cmp, domChange.text, preferredPos - from, preferredSide)) {
4180 // Chrome inserts two newlines when pressing shift-enter at the
4181 // end of a line. DomChange drops one of those.
4182 if (browser.chrome && lastKey == 13 &&
4183 diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
4184 diff.toB--;
4185 change = { from: from + diff.from, to: from + diff.toA,
4186 insert: Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
4187 }
4188 }
4189 else if (newSel && (!view.hasFocus && state.facet(editable) || sameSelPos(newSel, sel))) {
4190 newSel = null;
4191 }
4192 if (!change && !newSel)
4193 return false;
4194 if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
4195 /^\. ?$/.test(change.insert.toString()) && view.contentDOM.getAttribute("autocorrect") == "off") {
4196 // Detect insert-period-on-double-space Mac and Android behavior,
4197 // and transform it into a regular space insert.
4198 if (newSel && change.insert.length == 2)
4199 newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
4200 change = { from: change.from, to: change.to, insert: Text.of([change.insert.toString().replace(".", " ")]) };
4201 }
4202 else if (state.doc.lineAt(sel.from).to < sel.to && view.docView.lineHasWidget(sel.to) &&
4203 view.inputState.insertingTextAt > Date.now() - 50) {
4204 // For a cross-line insertion, Chrome and Safari will crudely take
4205 // the text of the line after the selection, flattening any
4206 // widgets, and move it into the joined line. This tries to detect
4207 // such a situation, and replaces the change with a selection
4208 // replace of the text provided by the beforeinput event.
4209 change = {
4210 from: sel.from, to: sel.to,
4211 insert: state.toText(view.inputState.insertingText)
4212 };
4213 }
4214 else if (browser.chrome && change && change.from == change.to && change.from == sel.head &&
4215 change.insert.toString() == "\n " && view.lineWrapping) {
4216 // In Chrome, if you insert a space at the start of a wrapped
4217 // line, it will actually insert a newline and a space, causing a
4218 // bogus new line to be created in CodeMirror (#968)
4219 if (newSel)
4220 newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
4221 change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
4222 }
4223 if (change) {
4224 return applyDOMChangeInner(view, change, newSel, lastKey);
4225 }
4226 else if (newSel && !sameSelPos(newSel, sel)) {
4227 let scrollIntoView = false, userEvent = "select";
4228 if (view.inputState.lastSelectionTime > Date.now() - 50) {
4229 if (view.inputState.lastSelectionOrigin == "select")
4230 scrollIntoView = true;
4231 userEvent = view.inputState.lastSelectionOrigin;
4232 if (userEvent == "select.pointer")
4233 newSel = skipAtomsForSelection(state.facet(atomicRanges).map(f => f(view)), newSel);
4234 }
4235 view.dispatch({ selection: newSel, scrollIntoView, userEvent });
4236 return true;
4237 }
4238 else {
4239 return false;
4240 }
4241}
4242function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
4243 if (browser.ios && view.inputState.flushIOSKey(change))
4244 return true;
4245 let sel = view.state.selection.main;
4246 // Android browsers don't fire reasonable key events for enter,
4247 // backspace, or delete. So this detects changes that look like
4248 // they're caused by those keys, and reinterprets them as key
4249 // events. (Some of these keys are also handled by beforeinput
4250 // events and the pendingAndroidKey mechanism, but that's not
4251 // reliable in all situations.)
4252 if (browser.android &&
4253 ((change.to == sel.to &&
4254 // GBoard will sometimes remove a space it just inserted
4255 // after a completion when you press enter
4256 (change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
4257 change.insert.length == 1 && change.insert.lines == 2 &&
4258 dispatchKey(view.contentDOM, "Enter", 13)) ||
4259 ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
4260 lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
4261 dispatchKey(view.contentDOM, "Backspace", 8)) ||
4262 (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
4263 dispatchKey(view.contentDOM, "Delete", 46))))
4264 return true;
4265 let text = change.insert.toString();
4266 if (view.inputState.composing >= 0)
4267 view.inputState.composing++;
4268 let defaultTr;
4269 let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
4270 if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
4271 view.dispatch(defaultInsert());
4272 return true;
4273}
4274function applyDefaultInsert(view, change, newSel) {
4275 let tr, startState = view.state, sel = startState.selection.main, inAtomic = -1;
4276 if (change.from == change.to && change.from < sel.from || change.from > sel.to) {
4277 let side = change.from < sel.from ? -1 : 1, pos = side < 0 ? sel.from : sel.to;
4278 let moved = skipAtomicRanges(startState.facet(atomicRanges).map(f => f(view)), pos, side);
4279 if (change.from == moved)
4280 inAtomic = moved;
4281 }
4282 if (inAtomic > -1) {
4283 tr = {
4284 changes: change,
4285 selection: EditorSelection.cursor(change.from + change.insert.length, -1)
4286 };
4287 }
4288 else if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
4289 (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
4290 view.inputState.composing < 0) {
4291 let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
4292 let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
4293 tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
4294 }
4295 else {
4296 let changes = startState.changes(change);
4297 let mainSel = newSel && newSel.main.to <= changes.newLength ? newSel.main : undefined;
4298 // Try to apply a composition change to all cursors
4299 if (startState.selection.ranges.length > 1 && (view.inputState.composing >= 0 || view.inputState.compositionPendingChange) &&
4300 change.to <= sel.to + 10 && change.to >= sel.to - 10) {
4301 let replaced = view.state.sliceDoc(change.from, change.to);
4302 let compositionRange, composition = newSel && findCompositionNode(view, newSel.main.head);
4303 if (composition) {
4304 let dLen = change.insert.length - (change.to - change.from);
4305 compositionRange = { from: composition.from, to: composition.to - dLen };
4306 }
4307 else {
4308 compositionRange = view.state.doc.lineAt(sel.head);
4309 }
4310 let offset = sel.to - change.to;
4311 tr = startState.changeByRange(range => {
4312 if (range.from == sel.from && range.to == sel.to)
4313 return { changes, range: mainSel || range.map(changes) };
4314 let to = range.to - offset, from = to - replaced.length;
4315 if (view.state.sliceDoc(from, to) != replaced ||
4316 // Unfortunately, there's no way to make multiple
4317 // changes in the same node work without aborting
4318 // composition, so cursors in the composition range are
4319 // ignored.
4320 to >= compositionRange.from && from <= compositionRange.to)
4321 return { range };
4322 let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
4323 return {
4324 changes: rangeChanges,
4325 range: !mainSel ? range.map(rangeChanges) :
4326 EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
4327 };
4328 });
4329 }
4330 else {
4331 tr = {
4332 changes,
4333 selection: mainSel && startState.selection.replaceRange(mainSel)
4334 };
4335 }
4336 }
4337 let userEvent = "input.type";
4338 if (view.composing ||
4339 view.inputState.compositionPendingChange && view.inputState.compositionEndedAt > Date.now() - 50) {
4340 view.inputState.compositionPendingChange = false;
4341 userEvent += ".compose";
4342 if (view.inputState.compositionFirstChange) {
4343 userEvent += ".start";
4344 view.inputState.compositionFirstChange = false;
4345 }
4346 }
4347 return startState.update(tr, { userEvent, scrollIntoView: true });
4348}
4349function findDiff(a, b, preferredPos, preferredSide) {
4350 let minLen = Math.min(a.length, b.length);
4351 let from = 0;
4352 while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
4353 from++;
4354 if (from == minLen && a.length == b.length)
4355 return null;
4356 let toA = a.length, toB = b.length;
4357 while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
4358 toA--;
4359 toB--;
4360 }
4361 if (preferredSide == "end") {
4362 let adjust = Math.max(0, from - Math.min(toA, toB));
4363 preferredPos -= toA + adjust - from;
4364 }
4365 if (toA < from && a.length < b.length) {
4366 let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
4367 from -= move;
4368 toB = from + (toB - toA);
4369 toA = from;
4370 }
4371 else if (toB < from) {
4372 let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
4373 from -= move;
4374 toA = from + (toA - toB);
4375 toB = from;
4376 }
4377 return { from, toA, toB };
4378}
4379function selectionPoints(view) {
4380 let result = [];
4381 if (view.root.activeElement != view.contentDOM)
4382 return result;
4383 let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
4384 if (anchorNode) {
4385 result.push(new DOMPoint(anchorNode, anchorOffset));
4386 if (focusNode != anchorNode || focusOffset != anchorOffset)
4387 result.push(new DOMPoint(focusNode, focusOffset));
4388 }
4389 return result;
4390}
4391function selectionFromPoints(points, base) {
4392 if (points.length == 0)
4393 return null;
4394 let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
4395 return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
4396}
4397function sameSelPos(selection, range) {
4398 return range.head == selection.main.head && range.anchor == selection.main.anchor;
4399}
4400
4401class InputState {
4402 setSelectionOrigin(origin) {
4403 this.lastSelectionOrigin = origin;
4404 this.lastSelectionTime = Date.now();
4405 }
4406 constructor(view) {
4407 this.view = view;
4408 this.lastKeyCode = 0;
4409 this.lastKeyTime = 0;
4410 this.lastTouchTime = 0;
4411 this.lastFocusTime = 0;
4412 this.lastScrollTop = 0;
4413 this.lastScrollLeft = 0;
4414 this.lastWheelEvent = 0;
4415 // On iOS, some keys need to have their default behavior happen
4416 // (after which we retroactively handle them and reset the DOM) to
4417 // avoid messing up the virtual keyboard state.
4418 this.pendingIOSKey = undefined;
4419 /**
4420 When enabled (>-1), tab presses are not given to key handlers,
4421 leaving the browser's default behavior. If >0, the mode expires
4422 at that timestamp, and any other keypress clears it.
4423 Esc enables temporary tab focus mode for two seconds when not
4424 otherwise handled.
4425 */
4426 this.tabFocusMode = -1;
4427 this.lastSelectionOrigin = null;
4428 this.lastSelectionTime = 0;
4429 this.lastContextMenu = 0;
4430 this.scrollHandlers = [];
4431 this.handlers = Object.create(null);
4432 // -1 means not in a composition. Otherwise, this counts the number
4433 // of changes made during the composition. The count is used to
4434 // avoid treating the start state of the composition, before any
4435 // changes have been made, as part of the composition.
4436 this.composing = -1;
4437 // Tracks whether the next change should be marked as starting the
4438 // composition (null means no composition, true means next is the
4439 // first, false means first has already been marked for this
4440 // composition)
4441 this.compositionFirstChange = null;
4442 // End time of the previous composition
4443 this.compositionEndedAt = 0;
4444 // Used in a kludge to detect when an Enter keypress should be
4445 // considered part of the composition on Safari, which fires events
4446 // in the wrong order
4447 this.compositionPendingKey = false;
4448 // Used to categorize changes as part of a composition, even when
4449 // the mutation events fire shortly after the compositionend event
4450 this.compositionPendingChange = false;
4451 // Set by beforeinput, used in DOM change reader
4452 this.insertingText = "";
4453 this.insertingTextAt = 0;
4454 this.mouseSelection = null;
4455 // When a drag from the editor is active, this points at the range
4456 // being dragged.
4457 this.draggedContent = null;
4458 this.handleEvent = this.handleEvent.bind(this);
4459 this.notifiedFocused = view.hasFocus;
4460 // On Safari adding an input event handler somehow prevents an
4461 // issue where the composition vanishes when you press enter.
4462 if (browser.safari)
4463 view.contentDOM.addEventListener("input", () => null);
4464 if (browser.gecko)
4465 firefoxCopyCutHack(view.contentDOM.ownerDocument);
4466 }
4467 handleEvent(event) {
4468 if (!eventBelongsToEditor(this.view, event) || this.ignoreDuringComposition(event))
4469 return;
4470 if (event.type == "keydown" && this.keydown(event))
4471 return;
4472 if (this.view.updateState != 0 /* UpdateState.Idle */)
4473 Promise.resolve().then(() => this.runHandlers(event.type, event));
4474 else
4475 this.runHandlers(event.type, event);
4476 }
4477 runHandlers(type, event) {
4478 let handlers = this.handlers[type];
4479 if (handlers) {
4480 for (let observer of handlers.observers)
4481 observer(this.view, event);
4482 for (let handler of handlers.handlers) {
4483 if (event.defaultPrevented)
4484 break;
4485 if (handler(this.view, event)) {
4486 event.preventDefault();
4487 break;
4488 }
4489 }
4490 }
4491 }
4492 ensureHandlers(plugins) {
4493 let handlers = computeHandlers(plugins), prev = this.handlers, dom = this.view.contentDOM;
4494 for (let type in handlers)
4495 if (type != "scroll") {
4496 let passive = !handlers[type].handlers.length;
4497 let exists = prev[type];
4498 if (exists && passive != !exists.handlers.length) {
4499 dom.removeEventListener(type, this.handleEvent);
4500 exists = null;
4501 }
4502 if (!exists)
4503 dom.addEventListener(type, this.handleEvent, { passive });
4504 }
4505 for (let type in prev)
4506 if (type != "scroll" && !handlers[type])
4507 dom.removeEventListener(type, this.handleEvent);
4508 this.handlers = handlers;
4509 }
4510 keydown(event) {
4511 // Must always run, even if a custom handler handled the event
4512 this.lastKeyCode = event.keyCode;
4513 this.lastKeyTime = Date.now();
4514 if (event.keyCode == 9 && this.tabFocusMode > -1 && (!this.tabFocusMode || Date.now() <= this.tabFocusMode))
4515 return true;
4516 if (this.tabFocusMode > 0 && event.keyCode != 27 && modifierCodes.indexOf(event.keyCode) < 0)
4517 this.tabFocusMode = -1;
4518 // Chrome for Android usually doesn't fire proper key events, but
4519 // occasionally does, usually surrounded by a bunch of complicated
4520 // composition changes. When an enter or backspace key event is
4521 // seen, hold off on handling DOM events for a bit, and then
4522 // dispatch it.
4523 if (browser.android && browser.chrome && !event.synthetic &&
4524 (event.keyCode == 13 || event.keyCode == 8)) {
4525 this.view.observer.delayAndroidKey(event.key, event.keyCode);
4526 return true;
4527 }
4528 // Preventing the default behavior of Enter on iOS makes the
4529 // virtual keyboard get stuck in the wrong (lowercase)
4530 // state. So we let it go through, and then, in
4531 // applyDOMChange, notify key handlers of it and reset to
4532 // the state they produce.
4533 let pending;
4534 if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey &&
4535 ((pending = PendingKeys.find(key => key.keyCode == event.keyCode)) && !event.ctrlKey ||
4536 EmacsyPendingKeys.indexOf(event.key) > -1 && event.ctrlKey && !event.shiftKey)) {
4537 this.pendingIOSKey = pending || event;
4538 setTimeout(() => this.flushIOSKey(), 250);
4539 return true;
4540 }
4541 if (event.keyCode != 229)
4542 this.view.observer.forceFlush();
4543 return false;
4544 }
4545 flushIOSKey(change) {
4546 let key = this.pendingIOSKey;
4547 if (!key)
4548 return false;
4549 // This looks like an autocorrection before Enter
4550 if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
4551 return false;
4552 this.pendingIOSKey = undefined;
4553 return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
4554 }
4555 ignoreDuringComposition(event) {
4556 if (!/^key/.test(event.type) || event.synthetic)
4557 return false;
4558 if (this.composing > 0)
4559 return true;
4560 // See https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/.
4561 // On some input method editors (IMEs), the Enter key is used to
4562 // confirm character selection. On Safari, when Enter is pressed,
4563 // compositionend and keydown events are sometimes emitted in the
4564 // wrong order. The key event should still be ignored, even when
4565 // it happens after the compositionend event.
4566 if (browser.safari && !browser.ios && this.compositionPendingKey && Date.now() - this.compositionEndedAt < 100) {
4567 this.compositionPendingKey = false;
4568 return true;
4569 }
4570 return false;
4571 }
4572 startMouseSelection(mouseSelection) {
4573 if (this.mouseSelection)
4574 this.mouseSelection.destroy();
4575 this.mouseSelection = mouseSelection;
4576 }
4577 update(update) {
4578 this.view.observer.update(update);
4579 if (this.mouseSelection)
4580 this.mouseSelection.update(update);
4581 if (this.draggedContent && update.docChanged)
4582 this.draggedContent = this.draggedContent.map(update.changes);
4583 if (update.transactions.length)
4584 this.lastKeyCode = this.lastSelectionTime = 0;
4585 }
4586 destroy() {
4587 if (this.mouseSelection)
4588 this.mouseSelection.destroy();
4589 }
4590}
4591function bindHandler(plugin, handler) {
4592 return (view, event) => {
4593 try {
4594 return handler.call(plugin, event, view);
4595 }
4596 catch (e) {
4597 logException(view.state, e);
4598 }
4599 };
4600}
4601function computeHandlers(plugins) {
4602 let result = Object.create(null);
4603 function record(type) {
4604 return result[type] || (result[type] = { observers: [], handlers: [] });
4605 }
4606 for (let plugin of plugins) {
4607 let spec = plugin.spec, handlers = spec && spec.plugin.domEventHandlers, observers = spec && spec.plugin.domEventObservers;
4608 if (handlers)
4609 for (let type in handlers) {
4610 let f = handlers[type];
4611 if (f)
4612 record(type).handlers.push(bindHandler(plugin.value, f));
4613 }
4614 if (observers)
4615 for (let type in observers) {
4616 let f = observers[type];
4617 if (f)
4618 record(type).observers.push(bindHandler(plugin.value, f));
4619 }
4620 }
4621 for (let type in handlers)
4622 record(type).handlers.push(handlers[type]);
4623 for (let type in observers)
4624 record(type).observers.push(observers[type]);
4625 return result;
4626}
4627const PendingKeys = [
4628 { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
4629 { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
4630 { key: "Enter", keyCode: 13, inputType: "insertLineBreak" },
4631 { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
4632];
4633const EmacsyPendingKeys = "dthko";
4634// Key codes for modifier keys
4635const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
4636const dragScrollMargin = 6;
4637function dragScrollSpeed(dist) {
4638 return Math.max(0, dist) * 0.7 + 8;
4639}
4640function dist(a, b) {
4641 return Math.max(Math.abs(a.clientX - b.clientX), Math.abs(a.clientY - b.clientY));
4642}
4643class MouseSelection {
4644 constructor(view, startEvent, style, mustSelect) {
4645 this.view = view;
4646 this.startEvent = startEvent;
4647 this.style = style;
4648 this.mustSelect = mustSelect;
4649 this.scrollSpeed = { x: 0, y: 0 };
4650 this.scrolling = -1;
4651 this.lastEvent = startEvent;
4652 this.scrollParents = scrollableParents(view.contentDOM);
4653 this.atoms = view.state.facet(atomicRanges).map(f => f(view));
4654 let doc = view.contentDOM.ownerDocument;
4655 doc.addEventListener("mousemove", this.move = this.move.bind(this));
4656 doc.addEventListener("mouseup", this.up = this.up.bind(this));
4657 this.extend = startEvent.shiftKey;
4658 this.multiple = view.state.facet(EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
4659 this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
4660 }
4661 start(event) {
4662 // When clicking outside of the selection, immediately apply the
4663 // effect of starting the selection
4664 if (this.dragging === false)
4665 this.select(event);
4666 }
4667 move(event) {
4668 if (event.buttons == 0)
4669 return this.destroy();
4670 if (this.dragging || this.dragging == null && dist(this.startEvent, event) < 10)
4671 return;
4672 this.select(this.lastEvent = event);
4673 let sx = 0, sy = 0;
4674 let left = 0, top = 0, right = this.view.win.innerWidth, bottom = this.view.win.innerHeight;
4675 if (this.scrollParents.x)
4676 ({ left, right } = this.scrollParents.x.getBoundingClientRect());
4677 if (this.scrollParents.y)
4678 ({ top, bottom } = this.scrollParents.y.getBoundingClientRect());
4679 let margins = getScrollMargins(this.view);
4680 if (event.clientX - margins.left <= left + dragScrollMargin)
4681 sx = -dragScrollSpeed(left - event.clientX);
4682 else if (event.clientX + margins.right >= right - dragScrollMargin)
4683 sx = dragScrollSpeed(event.clientX - right);
4684 if (event.clientY - margins.top <= top + dragScrollMargin)
4685 sy = -dragScrollSpeed(top - event.clientY);
4686 else if (event.clientY + margins.bottom >= bottom - dragScrollMargin)
4687 sy = dragScrollSpeed(event.clientY - bottom);
4688 this.setScrollSpeed(sx, sy);
4689 }
4690 up(event) {
4691 if (this.dragging == null)
4692 this.select(this.lastEvent);
4693 if (!this.dragging)
4694 event.preventDefault();
4695 this.destroy();
4696 }
4697 destroy() {
4698 this.setScrollSpeed(0, 0);
4699 let doc = this.view.contentDOM.ownerDocument;
4700 doc.removeEventListener("mousemove", this.move);
4701 doc.removeEventListener("mouseup", this.up);
4702 this.view.inputState.mouseSelection = this.view.inputState.draggedContent = null;
4703 }
4704 setScrollSpeed(sx, sy) {
4705 this.scrollSpeed = { x: sx, y: sy };
4706 if (sx || sy) {
4707 if (this.scrolling < 0)
4708 this.scrolling = setInterval(() => this.scroll(), 50);
4709 }
4710 else if (this.scrolling > -1) {
4711 clearInterval(this.scrolling);
4712 this.scrolling = -1;
4713 }
4714 }
4715 scroll() {
4716 let { x, y } = this.scrollSpeed;
4717 if (x && this.scrollParents.x) {
4718 this.scrollParents.x.scrollLeft += x;
4719 x = 0;
4720 }
4721 if (y && this.scrollParents.y) {
4722 this.scrollParents.y.scrollTop += y;
4723 y = 0;
4724 }
4725 if (x || y)
4726 this.view.win.scrollBy(x, y);
4727 if (this.dragging === false)
4728 this.select(this.lastEvent);
4729 }
4730 select(event) {
4731 let { view } = this, selection = skipAtomsForSelection(this.atoms, this.style.get(event, this.extend, this.multiple));
4732 if (this.mustSelect || !selection.eq(view.state.selection, this.dragging === false))
4733 this.view.dispatch({
4734 selection,
4735 userEvent: "select.pointer"
4736 });
4737 this.mustSelect = false;
4738 }
4739 update(update) {
4740 if (update.transactions.some(tr => tr.isUserEvent("input.type")))
4741 this.destroy();
4742 else if (this.style.update(update))
4743 setTimeout(() => this.select(this.lastEvent), 20);
4744 }
4745}
4746function addsSelectionRange(view, event) {
4747 let facet = view.state.facet(clickAddsSelectionRange);
4748 return facet.length ? facet[0](event) : browser.mac ? event.metaKey : event.ctrlKey;
4749}
4750function dragMovesSelection(view, event) {
4751 let facet = view.state.facet(dragMovesSelection$1);
4752 return facet.length ? facet[0](event) : browser.mac ? !event.altKey : !event.ctrlKey;
4753}
4754function isInPrimarySelection(view, event) {
4755 let { main } = view.state.selection;
4756 if (main.empty)
4757 return false;
4758 // On boundary clicks, check whether the coordinates are inside the
4759 // selection's client rectangles
4760 let sel = getSelection(view.root);
4761 if (!sel || sel.rangeCount == 0)
4762 return true;
4763 let rects = sel.getRangeAt(0).getClientRects();
4764 for (let i = 0; i < rects.length; i++) {
4765 let rect = rects[i];
4766 if (rect.left <= event.clientX && rect.right >= event.clientX &&
4767 rect.top <= event.clientY && rect.bottom >= event.clientY)
4768 return true;
4769 }
4770 return false;
4771}
4772function eventBelongsToEditor(view, event) {
4773 if (!event.bubbles)
4774 return true;
4775 if (event.defaultPrevented)
4776 return false;
4777 for (let node = event.target, tile; node != view.contentDOM; node = node.parentNode)
4778 if (!node || node.nodeType == 11 ||
4779 ((tile = Tile.get(node)) && tile.isWidget() && !tile.isHidden && tile.widget.ignoreEvent(event)))
4780 return false;
4781 return true;
4782}
4783const handlers = /*@__PURE__*/Object.create(null);
4784const observers = /*@__PURE__*/Object.create(null);
4785// This is very crude, but unfortunately both these browsers _pretend_
4786// that they have a clipboard API—all the objects and methods are
4787// there, they just don't work, and they are hard to test.
4788const brokenClipboardAPI = (browser.ie && browser.ie_version < 15) ||
4789 (browser.ios && browser.webkit_version < 604);
4790function capturePaste(view) {
4791 let parent = view.dom.parentNode;
4792 if (!parent)
4793 return;
4794 let target = parent.appendChild(document.createElement("textarea"));
4795 target.style.cssText = "position: fixed; left: -10000px; top: 10px";
4796 target.focus();
4797 setTimeout(() => {
4798 view.focus();
4799 target.remove();
4800 doPaste(view, target.value);
4801 }, 50);
4802}
4803function textFilter(state, facet, text) {
4804 for (let filter of state.facet(facet))
4805 text = filter(text, state);
4806 return text;
4807}
4808function doPaste(view, input) {
4809 input = textFilter(view.state, clipboardInputFilter, input);
4810 let { state } = view, changes, i = 1, text = state.toText(input);
4811 let byLine = text.lines == state.selection.ranges.length;
4812 let linewise = lastLinewiseCopy != null && state.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
4813 if (linewise) {
4814 let lastLine = -1;
4815 changes = state.changeByRange(range => {
4816 let line = state.doc.lineAt(range.from);
4817 if (line.from == lastLine)
4818 return { range };
4819 lastLine = line.from;
4820 let insert = state.toText((byLine ? text.line(i++).text : input) + state.lineBreak);
4821 return { changes: { from: line.from, insert },
4822 range: EditorSelection.cursor(range.from + insert.length) };
4823 });
4824 }
4825 else if (byLine) {
4826 changes = state.changeByRange(range => {
4827 let line = text.line(i++);
4828 return { changes: { from: range.from, to: range.to, insert: line.text },
4829 range: EditorSelection.cursor(range.from + line.length) };
4830 });
4831 }
4832 else {
4833 changes = state.replaceSelection(text);
4834 }
4835 view.dispatch(changes, {
4836 userEvent: "input.paste",
4837 scrollIntoView: true
4838 });
4839}
4840observers.scroll = view => {
4841 view.inputState.lastScrollTop = view.scrollDOM.scrollTop;
4842 view.inputState.lastScrollLeft = view.scrollDOM.scrollLeft;
4843};
4844observers.wheel = observers.mousewheel = view => {
4845 view.inputState.lastWheelEvent = Date.now();
4846};
4847handlers.keydown = (view, event) => {
4848 view.inputState.setSelectionOrigin("select");
4849 if (event.keyCode == 27 && view.inputState.tabFocusMode != 0)
4850 view.inputState.tabFocusMode = Date.now() + 2000;
4851 return false;
4852};
4853observers.touchstart = (view, e) => {
4854 view.inputState.lastTouchTime = Date.now();
4855 view.inputState.setSelectionOrigin("select.pointer");
4856};
4857observers.touchmove = view => {
4858 view.inputState.setSelectionOrigin("select.pointer");
4859};
4860handlers.mousedown = (view, event) => {
4861 view.observer.flush();
4862 if (view.inputState.lastTouchTime > Date.now() - 2000)
4863 return false; // Ignore touch interaction
4864 let style = null;
4865 for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
4866 style = makeStyle(view, event);
4867 if (style)
4868 break;
4869 }
4870 if (!style && event.button == 0)
4871 style = basicMouseSelection(view, event);
4872 if (style) {
4873 let mustFocus = !view.hasFocus;
4874 view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
4875 if (mustFocus)
4876 view.observer.ignore(() => {
4877 focusPreventScroll(view.contentDOM);
4878 let active = view.root.activeElement;
4879 if (active && !active.contains(view.contentDOM))
4880 active.blur();
4881 });
4882 let mouseSel = view.inputState.mouseSelection;
4883 if (mouseSel) {
4884 mouseSel.start(event);
4885 return mouseSel.dragging === false;
4886 }
4887 }
4888 else {
4889 view.inputState.setSelectionOrigin("select.pointer");
4890 }
4891 return false;
4892};
4893function rangeForClick(view, pos, bias, type) {
4894 if (type == 1) { // Single click
4895 return EditorSelection.cursor(pos, bias);
4896 }
4897 else if (type == 2) { // Double click
4898 return groupAt(view.state, pos, bias);
4899 }
4900 else { // Triple click
4901 let visual = view.docView.lineAt(pos, bias), line = view.state.doc.lineAt(visual ? visual.posAtEnd : pos);
4902 let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
4903 if (to < view.state.doc.length && to == line.to)
4904 to++;
4905 return EditorSelection.range(from, to);
4906 }
4907}
4908const BadMouseDetail = browser.ie && browser.ie_version <= 11;
4909let lastMouseDown = null, lastMouseDownCount = 0, lastMouseDownTime = 0;
4910function getClickType(event) {
4911 if (!BadMouseDetail)
4912 return event.detail;
4913 let last = lastMouseDown, lastTime = lastMouseDownTime;
4914 lastMouseDown = event;
4915 lastMouseDownTime = Date.now();
4916 return lastMouseDownCount = !last || (lastTime > Date.now() - 400 && Math.abs(last.clientX - event.clientX) < 2 &&
4917 Math.abs(last.clientY - event.clientY) < 2) ? (lastMouseDownCount + 1) % 3 : 1;
4918}
4919function basicMouseSelection(view, event) {
4920 let start = view.posAndSideAtCoords({ x: event.clientX, y: event.clientY }, false), type = getClickType(event);
4921 let startSel = view.state.selection;
4922 return {
4923 update(update) {
4924 if (update.docChanged) {
4925 start.pos = update.changes.mapPos(start.pos);
4926 startSel = startSel.map(update.changes);
4927 }
4928 },
4929 get(event, extend, multiple) {
4930 let cur = view.posAndSideAtCoords({ x: event.clientX, y: event.clientY }, false), removed;
4931 let range = rangeForClick(view, cur.pos, cur.assoc, type);
4932 if (start.pos != cur.pos && !extend) {
4933 let startRange = rangeForClick(view, start.pos, start.assoc, type);
4934 let from = Math.min(startRange.from, range.from), to = Math.max(startRange.to, range.to);
4935 range = from < range.from ? EditorSelection.range(from, to) : EditorSelection.range(to, from);
4936 }
4937 if (extend)
4938 return startSel.replaceRange(startSel.main.extend(range.from, range.to));
4939 else if (multiple && type == 1 && startSel.ranges.length > 1 && (removed = removeRangeAround(startSel, cur.pos)))
4940 return removed;
4941 else if (multiple)
4942 return startSel.addRange(range);
4943 else
4944 return EditorSelection.create([range]);
4945 }
4946 };
4947}
4948function removeRangeAround(sel, pos) {
4949 for (let i = 0; i < sel.ranges.length; i++) {
4950 let { from, to } = sel.ranges[i];
4951 if (from <= pos && to >= pos)
4952 return EditorSelection.create(sel.ranges.slice(0, i).concat(sel.ranges.slice(i + 1)), sel.mainIndex == i ? 0 : sel.mainIndex - (sel.mainIndex > i ? 1 : 0));
4953 }
4954 return null;
4955}
4956handlers.dragstart = (view, event) => {
4957 let { selection: { main: range } } = view.state;
4958 if (event.target.draggable) {
4959 let tile = view.docView.tile.nearest(event.target);
4960 if (tile && tile.isWidget()) {
4961 let from = tile.posAtStart, to = from + tile.length;
4962 if (from >= range.to || to <= range.from)
4963 range = EditorSelection.range(from, to);
4964 }
4965 }
4966 let { inputState } = view;
4967 if (inputState.mouseSelection)
4968 inputState.mouseSelection.dragging = true;
4969 inputState.draggedContent = range;
4970 if (event.dataTransfer) {
4971 event.dataTransfer.setData("Text", textFilter(view.state, clipboardOutputFilter, view.state.sliceDoc(range.from, range.to)));
4972 event.dataTransfer.effectAllowed = "copyMove";
4973 }
4974 return false;
4975};
4976handlers.dragend = view => {
4977 view.inputState.draggedContent = null;
4978 return false;
4979};
4980function dropText(view, event, text, direct) {
4981 text = textFilter(view.state, clipboardInputFilter, text);
4982 if (!text)
4983 return;
4984 let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
4985 let { draggedContent } = view.inputState;
4986 let del = direct && draggedContent && dragMovesSelection(view, event)
4987 ? { from: draggedContent.from, to: draggedContent.to } : null;
4988 let ins = { from: dropPos, insert: text };
4989 let changes = view.state.changes(del ? [del, ins] : ins);
4990 view.focus();
4991 view.dispatch({
4992 changes,
4993 selection: { anchor: changes.mapPos(dropPos, -1), head: changes.mapPos(dropPos, 1) },
4994 userEvent: del ? "move.drop" : "input.drop"
4995 });
4996 view.inputState.draggedContent = null;
4997}
4998handlers.drop = (view, event) => {
4999 if (!event.dataTransfer)
5000 return false;
5001 if (view.state.readOnly)
5002 return true;
5003 let files = event.dataTransfer.files;
5004 if (files && files.length) { // For a file drop, read the file's text.
5005 let text = Array(files.length), read = 0;
5006 let finishFile = () => {
5007 if (++read == files.length)
5008 dropText(view, event, text.filter(s => s != null).join(view.state.lineBreak), false);
5009 };
5010 for (let i = 0; i < files.length; i++) {
5011 let reader = new FileReader;
5012 reader.onerror = finishFile;
5013 reader.onload = () => {
5014 if (!/[\x00-\x08\x0e-\x1f]{2}/.test(reader.result))
5015 text[i] = reader.result;
5016 finishFile();
5017 };
5018 reader.readAsText(files[i]);
5019 }
5020 return true;
5021 }
5022 else {
5023 let text = event.dataTransfer.getData("Text");
5024 if (text) {
5025 dropText(view, event, text, true);
5026 return true;
5027 }
5028 }
5029 return false;
5030};
5031handlers.paste = (view, event) => {
5032 if (view.state.readOnly)
5033 return true;
5034 view.observer.flush();
5035 let data = brokenClipboardAPI ? null : event.clipboardData;
5036 if (data) {
5037 doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
5038 return true;
5039 }
5040 else {
5041 capturePaste(view);
5042 return false;
5043 }
5044};
5045function captureCopy(view, text) {
5046 // The extra wrapper is somehow necessary on IE/Edge to prevent the
5047 // content from being mangled when it is put onto the clipboard
5048 let parent = view.dom.parentNode;
5049 if (!parent)
5050 return;
5051 let target = parent.appendChild(document.createElement("textarea"));
5052 target.style.cssText = "position: fixed; left: -10000px; top: 10px";
5053 target.value = text;
5054 target.focus();
5055 target.selectionEnd = text.length;
5056 target.selectionStart = 0;
5057 setTimeout(() => {
5058 target.remove();
5059 view.focus();
5060 }, 50);
5061}
5062function copiedRange(state) {
5063 let content = [], ranges = [], linewise = false;
5064 for (let range of state.selection.ranges)
5065 if (!range.empty) {
5066 content.push(state.sliceDoc(range.from, range.to));
5067 ranges.push(range);
5068 }
5069 if (!content.length) {
5070 // Nothing selected, do a line-wise copy
5071 let upto = -1;
5072 for (let { from } of state.selection.ranges) {
5073 let line = state.doc.lineAt(from);
5074 if (line.number > upto) {
5075 content.push(line.text);
5076 ranges.push({ from: line.from, to: Math.min(state.doc.length, line.to + 1) });
5077 }
5078 upto = line.number;
5079 }
5080 linewise = true;
5081 }
5082 return { text: textFilter(state, clipboardOutputFilter, content.join(state.lineBreak)), ranges, linewise };
5083}
5084let lastLinewiseCopy = null;
5085handlers.copy = handlers.cut = (view, event) => {
5086 // If the DOM selection is outside this editor, don't intercept.
5087 // This happens when a parent editor (like ProseMirror) selects content that
5088 // spans multiple elements including this CodeMirror. The copy event may
5089 // bubble through CodeMirror (e.g. when CodeMirror is the first or the last
5090 // element in the selection), but we should let the parent handle it.
5091 if (!hasSelection(view.contentDOM, view.observer.selectionRange))
5092 return false;
5093 let { text, ranges, linewise } = copiedRange(view.state);
5094 if (!text && !linewise)
5095 return false;
5096 lastLinewiseCopy = linewise ? text : null;
5097 if (event.type == "cut" && !view.state.readOnly)
5098 view.dispatch({
5099 changes: ranges,
5100 scrollIntoView: true,
5101 userEvent: "delete.cut"
5102 });
5103 let data = brokenClipboardAPI ? null : event.clipboardData;
5104 if (data) {
5105 data.clearData();
5106 data.setData("text/plain", text);
5107 return true;
5108 }
5109 else {
5110 captureCopy(view, text);
5111 return false;
5112 }
5113};
5114const isFocusChange = /*@__PURE__*/Annotation.define();
5115function focusChangeTransaction(state, focus) {
5116 let effects = [];
5117 for (let getEffect of state.facet(focusChangeEffect)) {
5118 let effect = getEffect(state, focus);
5119 if (effect)
5120 effects.push(effect);
5121 }
5122 return effects.length ? state.update({ effects, annotations: isFocusChange.of(true) }) : null;
5123}
5124function updateForFocusChange(view) {
5125 setTimeout(() => {
5126 let focus = view.hasFocus;
5127 if (focus != view.inputState.notifiedFocused) {
5128 let tr = focusChangeTransaction(view.state, focus);
5129 if (tr)
5130 view.dispatch(tr);
5131 else
5132 view.update([]);
5133 }
5134 }, 10);
5135}
5136observers.focus = view => {
5137 view.inputState.lastFocusTime = Date.now();
5138 // When focusing reset the scroll position, move it back to where it was
5139 if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
5140 view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
5141 view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
5142 }
5143 updateForFocusChange(view);
5144};
5145observers.blur = view => {
5146 view.observer.clearSelectionRange();
5147 updateForFocusChange(view);
5148};
5149observers.compositionstart = observers.compositionupdate = view => {
5150 if (view.observer.editContext)
5151 return; // Composition handled by edit context
5152 if (view.inputState.compositionFirstChange == null)
5153 view.inputState.compositionFirstChange = true;
5154 if (view.inputState.composing < 0) {
5155 // FIXME possibly set a timeout to clear it again on Android
5156 view.inputState.composing = 0;
5157 }
5158};
5159observers.compositionend = view => {
5160 if (view.observer.editContext)
5161 return; // Composition handled by edit context
5162 view.inputState.composing = -1;
5163 view.inputState.compositionEndedAt = Date.now();
5164 view.inputState.compositionPendingKey = true;
5165 view.inputState.compositionPendingChange = view.observer.pendingRecords().length > 0;
5166 view.inputState.compositionFirstChange = null;
5167 if (browser.chrome && browser.android) {
5168 // Delay flushing for a bit on Android because it'll often fire a
5169 // bunch of contradictory changes in a row at end of compositon
5170 view.observer.flushSoon();
5171 }
5172 else if (view.inputState.compositionPendingChange) {
5173 // If we found pending records, schedule a flush.
5174 Promise.resolve().then(() => view.observer.flush());
5175 }
5176 else {
5177 // Otherwise, make sure that, if no changes come in soon, the
5178 // composition view is cleared.
5179 setTimeout(() => {
5180 if (view.inputState.composing < 0 && view.docView.hasComposition)
5181 view.update([]);
5182 }, 50);
5183 }
5184};
5185observers.contextmenu = view => {
5186 view.inputState.lastContextMenu = Date.now();
5187};
5188handlers.beforeinput = (view, event) => {
5189 var _a, _b;
5190 if (event.inputType == "insertText" || event.inputType == "insertCompositionText") {
5191 view.inputState.insertingText = event.data;
5192 view.inputState.insertingTextAt = Date.now();
5193 }
5194 // In EditContext mode, we must handle insertReplacementText events
5195 // directly, to make spell checking corrections work
5196 if (event.inputType == "insertReplacementText" && view.observer.editContext) {
5197 let text = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData("text/plain"), ranges = event.getTargetRanges();
5198 if (text && ranges.length) {
5199 let r = ranges[0];
5200 let from = view.posAtDOM(r.startContainer, r.startOffset), to = view.posAtDOM(r.endContainer, r.endOffset);
5201 applyDOMChangeInner(view, { from, to, insert: view.state.toText(text) }, null);
5202 return true;
5203 }
5204 }
5205 // Because Chrome Android doesn't fire useful key events, use
5206 // beforeinput to detect backspace (and possibly enter and delete,
5207 // but those usually don't even seem to fire beforeinput events at
5208 // the moment) and fake a key event for it.
5209 //
5210 // (preventDefault on beforeinput, though supported in the spec,
5211 // seems to do nothing at all on Chrome).
5212 let pending;
5213 if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
5214 view.observer.delayAndroidKey(pending.key, pending.keyCode);
5215 if (pending.key == "Backspace" || pending.key == "Delete") {
5216 let startViewHeight = ((_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.height) || 0;
5217 setTimeout(() => {
5218 var _a;
5219 // Backspacing near uneditable nodes on Chrome Android sometimes
5220 // closes the virtual keyboard. This tries to crudely detect
5221 // that and refocus to get it back.
5222 if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
5223 view.contentDOM.blur();
5224 view.focus();
5225 }
5226 }, 100);
5227 }
5228 }
5229 if (browser.ios && event.inputType == "deleteContentForward") {
5230 // For some reason, DOM changes (and beforeinput) happen _before_
5231 // the key event for ctrl-d on iOS when using an external
5232 // keyboard.
5233 view.observer.flushSoon();
5234 }
5235 // Safari will occasionally forget to fire compositionend at the end of a dead-key composition
5236 if (browser.safari && event.inputType == "insertText" && view.inputState.composing >= 0) {
5237 setTimeout(() => observers.compositionend(view, event), 20);
5238 }
5239 return false;
5240};
5241const appliedFirefoxHack = /*@__PURE__*/new Set;
5242// In Firefox, when cut/copy handlers are added to the document, that
5243// somehow avoids a bug where those events aren't fired when the
5244// selection is empty. See https://github.com/codemirror/dev/issues/1082
5245// and https://bugzilla.mozilla.org/show_bug.cgi?id=995961
5246function firefoxCopyCutHack(doc) {
5247 if (!appliedFirefoxHack.has(doc)) {
5248 appliedFirefoxHack.add(doc);
5249 doc.addEventListener("copy", () => { });
5250 doc.addEventListener("cut", () => { });
5251 }
5252}
5253
5254const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line", "break-spaces"];
5255// Used to track, during updateHeight, if any actual heights changed
5256let heightChangeFlag = false;
5257function clearHeightChangeFlag() { heightChangeFlag = false; }
5258class HeightOracle {
5259 constructor(lineWrapping) {
5260 this.lineWrapping = lineWrapping;
5261 this.doc = Text.empty;
5262 this.heightSamples = {};
5263 this.lineHeight = 14; // The height of an entire line (line-height)
5264 this.charWidth = 7;
5265 this.textHeight = 14; // The height of the actual font (font-size)
5266 this.lineLength = 30;
5267 }
5268 heightForGap(from, to) {
5269 let lines = this.doc.lineAt(to).number - this.doc.lineAt(from).number + 1;
5270 if (this.lineWrapping)
5271 lines += Math.max(0, Math.ceil(((to - from) - (lines * this.lineLength * 0.5)) / this.lineLength));
5272 return this.lineHeight * lines;
5273 }
5274 heightForLine(length) {
5275 if (!this.lineWrapping)
5276 return this.lineHeight;
5277 let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / Math.max(1, this.lineLength - 5)));
5278 return lines * this.lineHeight;
5279 }
5280 setDoc(doc) { this.doc = doc; return this; }
5281 mustRefreshForWrapping(whiteSpace) {
5282 return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping;
5283 }
5284 mustRefreshForHeights(lineHeights) {
5285 let newHeight = false;
5286 for (let i = 0; i < lineHeights.length; i++) {
5287 let h = lineHeights[i];
5288 if (h < 0) {
5289 i++;
5290 }
5291 else if (!this.heightSamples[Math.floor(h * 10)]) { // Round to .1 pixels
5292 newHeight = true;
5293 this.heightSamples[Math.floor(h * 10)] = true;
5294 }
5295 }
5296 return newHeight;
5297 }
5298 refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
5299 let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
5300 let changed = Math.abs(lineHeight - this.lineHeight) > 0.3 || this.lineWrapping != lineWrapping ||
5301 Math.abs(charWidth - this.charWidth) > 0.1;
5302 this.lineWrapping = lineWrapping;
5303 this.lineHeight = lineHeight;
5304 this.charWidth = charWidth;
5305 this.textHeight = textHeight;
5306 this.lineLength = lineLength;
5307 if (changed) {
5308 this.heightSamples = {};
5309 for (let i = 0; i < knownHeights.length; i++) {
5310 let h = knownHeights[i];
5311 if (h < 0)
5312 i++;
5313 else
5314 this.heightSamples[Math.floor(h * 10)] = true;
5315 }
5316 }
5317 return changed;
5318 }
5319}
5320// This object is used by `updateHeight` to make DOM measurements
5321// arrive at the right nodes. The `heights` array is a sequence of
5322// block heights, starting from position `from`.
5323class MeasuredHeights {
5324 constructor(from, heights) {
5325 this.from = from;
5326 this.heights = heights;
5327 this.index = 0;
5328 }
5329 get more() { return this.index < this.heights.length; }
5330}
5331/**
5332Record used to represent information about a block-level element
5333in the editor view.
5334*/
5335class BlockInfo {
5336 /**
5337 @internal
5338 */
5339 constructor(
5340 /**
5341 The start of the element in the document.
5342 */
5343 from,
5344 /**
5345 The length of the element.
5346 */
5347 length,
5348 /**
5349 The top position of the element (relative to the top of the
5350 document).
5351 */
5352 top,
5353 /**
5354 Its height.
5355 */
5356 height,
5357 /**
5358 @internal Weird packed field that holds an array of children
5359 for composite blocks, a decoration for block widgets, and a
5360 number indicating the amount of widget-created line breaks for
5361 text blocks.
5362 */
5363 _content) {
5364 this.from = from;
5365 this.length = length;
5366 this.top = top;
5367 this.height = height;
5368 this._content = _content;
5369 }
5370 /**
5371 The type of element this is. When querying lines, this may be
5372 an array of all the blocks that make up the line.
5373 */
5374 get type() {
5375 return typeof this._content == "number" ? BlockType.Text :
5376 Array.isArray(this._content) ? this._content : this._content.type;
5377 }
5378 /**
5379 The end of the element as a document position.
5380 */
5381 get to() { return this.from + this.length; }
5382 /**
5383 The bottom position of the element.
5384 */
5385 get bottom() { return this.top + this.height; }
5386 /**
5387 If this is a widget block, this will return the widget
5388 associated with it.
5389 */
5390 get widget() {
5391 return this._content instanceof PointDecoration ? this._content.widget : null;
5392 }
5393 /**
5394 If this is a textblock, this holds the number of line breaks
5395 that appear in widgets inside the block.
5396 */
5397 get widgetLineBreaks() {
5398 return typeof this._content == "number" ? this._content : 0;
5399 }
5400 /**
5401 @internal
5402 */
5403 join(other) {
5404 let content = (Array.isArray(this._content) ? this._content : [this])
5405 .concat(Array.isArray(other._content) ? other._content : [other]);
5406 return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, content);
5407 }
5408}
5409var QueryType = /*@__PURE__*/(function (QueryType) {
5410 QueryType[QueryType["ByPos"] = 0] = "ByPos";
5411 QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
5412 QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
5413return QueryType})(QueryType || (QueryType = {}));
5414const Epsilon = 1e-3;
5415class HeightMap {
5416 constructor(length, // The number of characters covered
5417 height, // Height of this part of the document
5418 flags = 2 /* Flag.Outdated */) {
5419 this.length = length;
5420 this.height = height;
5421 this.flags = flags;
5422 }
5423 get outdated() { return (this.flags & 2 /* Flag.Outdated */) > 0; }
5424 set outdated(value) { this.flags = (value ? 2 /* Flag.Outdated */ : 0) | (this.flags & ~2 /* Flag.Outdated */); }
5425 setHeight(height) {
5426 if (this.height != height) {
5427 if (Math.abs(this.height - height) > Epsilon)
5428 heightChangeFlag = true;
5429 this.height = height;
5430 }
5431 }
5432 // Base case is to replace a leaf node, which simply builds a tree
5433 // from the new nodes and returns that (HeightMapBranch and
5434 // HeightMapGap override this to actually use from/to)
5435 replace(_from, _to, nodes) {
5436 return HeightMap.of(nodes);
5437 }
5438 // Again, these are base cases, and are overridden for branch and gap nodes.
5439 decomposeLeft(_to, result) { result.push(this); }
5440 decomposeRight(_from, result) { result.push(this); }
5441 applyChanges(decorations, oldDoc, oracle, changes) {
5442 let me = this, doc = oracle.doc;
5443 for (let i = changes.length - 1; i >= 0; i--) {
5444 let { fromA, toA, fromB, toB } = changes[i];
5445 let start = me.lineAt(fromA, QueryType.ByPosNoHeight, oracle.setDoc(oldDoc), 0, 0);
5446 let end = start.to >= toA ? start : me.lineAt(toA, QueryType.ByPosNoHeight, oracle, 0, 0);
5447 toB += end.to - toA;
5448 toA = end.to;
5449 while (i > 0 && start.from <= changes[i - 1].toA) {
5450 fromA = changes[i - 1].fromA;
5451 fromB = changes[i - 1].fromB;
5452 i--;
5453 if (fromA < start.from)
5454 start = me.lineAt(fromA, QueryType.ByPosNoHeight, oracle, 0, 0);
5455 }
5456 fromB += start.from - fromA;
5457 fromA = start.from;
5458 let nodes = NodeBuilder.build(oracle.setDoc(doc), decorations, fromB, toB);
5459 me = replace(me, me.replace(fromA, toA, nodes));
5460 }
5461 return me.updateHeight(oracle, 0);
5462 }
5463 static empty() { return new HeightMapText(0, 0, 0); }
5464 // nodes uses null values to indicate the position of line breaks.
5465 // There are never line breaks at the start or end of the array, or
5466 // two line breaks next to each other, and the array isn't allowed
5467 // to be empty (same restrictions as return value from the builder).
5468 static of(nodes) {
5469 if (nodes.length == 1)
5470 return nodes[0];
5471 let i = 0, j = nodes.length, before = 0, after = 0;
5472 for (;;) {
5473 if (i == j) {
5474 if (before > after * 2) {
5475 let split = nodes[i - 1];
5476 if (split.break)
5477 nodes.splice(--i, 1, split.left, null, split.right);
5478 else
5479 nodes.splice(--i, 1, split.left, split.right);
5480 j += 1 + split.break;
5481 before -= split.size;
5482 }
5483 else if (after > before * 2) {
5484 let split = nodes[j];
5485 if (split.break)
5486 nodes.splice(j, 1, split.left, null, split.right);
5487 else
5488 nodes.splice(j, 1, split.left, split.right);
5489 j += 2 + split.break;
5490 after -= split.size;
5491 }
5492 else {
5493 break;
5494 }
5495 }
5496 else if (before < after) {
5497 let next = nodes[i++];
5498 if (next)
5499 before += next.size;
5500 }
5501 else {
5502 let next = nodes[--j];
5503 if (next)
5504 after += next.size;
5505 }
5506 }
5507 let brk = 0;
5508 if (nodes[i - 1] == null) {
5509 brk = 1;
5510 i--;
5511 }
5512 else if (nodes[i] == null) {
5513 brk = 1;
5514 j++;
5515 }
5516 return new HeightMapBranch(HeightMap.of(nodes.slice(0, i)), brk, HeightMap.of(nodes.slice(j)));
5517 }
5518}
5519function replace(old, val) {
5520 if (old == val)
5521 return old;
5522 if (old.constructor != val.constructor)
5523 heightChangeFlag = true;
5524 return val;
5525}
5526HeightMap.prototype.size = 1;
5527const SpaceDeco = /*@__PURE__*/Decoration.replace({});
5528class HeightMapBlock extends HeightMap {
5529 constructor(length, height, deco) {
5530 super(length, height);
5531 this.deco = deco;
5532 this.spaceAbove = 0;
5533 }
5534 mainBlock(top, offset) {
5535 return new BlockInfo(offset, this.length, top + this.spaceAbove, this.height - this.spaceAbove, this.deco || 0);
5536 }
5537 blockAt(height, _oracle, top, offset) {
5538 return this.spaceAbove && height < top + this.spaceAbove ? new BlockInfo(offset, 0, top, this.spaceAbove, SpaceDeco)
5539 : this.mainBlock(top, offset);
5540 }
5541 lineAt(_value, _type, oracle, top, offset) {
5542 let main = this.mainBlock(top, offset);
5543 return this.spaceAbove ? this.blockAt(0, oracle, top, offset).join(main) : main;
5544 }
5545 forEachLine(from, to, oracle, top, offset, f) {
5546 if (from <= offset + this.length && to >= offset)
5547 f(this.lineAt(0, QueryType.ByPos, oracle, top, offset));
5548 }
5549 setMeasuredHeight(measured) {
5550 let next = measured.heights[measured.index++];
5551 if (next < 0) {
5552 this.spaceAbove = -next;
5553 next = measured.heights[measured.index++];
5554 }
5555 else {
5556 this.spaceAbove = 0;
5557 }
5558 this.setHeight(next);
5559 }
5560 updateHeight(oracle, offset = 0, _force = false, measured) {
5561 if (measured && measured.from <= offset && measured.more)
5562 this.setMeasuredHeight(measured);
5563 this.outdated = false;
5564 return this;
5565 }
5566 toString() { return `block(${this.length})`; }
5567}
5568class HeightMapText extends HeightMapBlock {
5569 constructor(length, height, above) {
5570 super(length, height, null);
5571 this.collapsed = 0; // Amount of collapsed content in the line
5572 this.widgetHeight = 0; // Maximum inline widget height
5573 this.breaks = 0; // Number of widget-introduced line breaks on the line
5574 this.spaceAbove = above;
5575 }
5576 mainBlock(top, offset) {
5577 return new BlockInfo(offset, this.length, top + this.spaceAbove, this.height - this.spaceAbove, this.breaks);
5578 }
5579 replace(_from, _to, nodes) {
5580 let node = nodes[0];
5581 if (nodes.length == 1 && (node instanceof HeightMapText || node instanceof HeightMapGap && (node.flags & 4 /* Flag.SingleLine */)) &&
5582 Math.abs(this.length - node.length) < 10) {
5583 if (node instanceof HeightMapGap)
5584 node = new HeightMapText(node.length, this.height, this.spaceAbove);
5585 else
5586 node.height = this.height;
5587 if (!this.outdated)
5588 node.outdated = false;
5589 return node;
5590 }
5591 else {
5592 return HeightMap.of(nodes);
5593 }
5594 }
5595 updateHeight(oracle, offset = 0, force = false, measured) {
5596 if (measured && measured.from <= offset && measured.more) {
5597 this.setMeasuredHeight(measured);
5598 }
5599 else if (force || this.outdated) {
5600 this.spaceAbove = 0;
5601 this.setHeight(Math.max(this.widgetHeight, oracle.heightForLine(this.length - this.collapsed)) +
5602 this.breaks * oracle.lineHeight);
5603 }
5604 this.outdated = false;
5605 return this;
5606 }
5607 toString() {
5608 return `line(${this.length}${this.collapsed ? -this.collapsed : ""}${this.widgetHeight ? ":" + this.widgetHeight : ""})`;
5609 }
5610}
5611class HeightMapGap extends HeightMap {
5612 constructor(length) { super(length, 0); }
5613 heightMetrics(oracle, offset) {
5614 let firstLine = oracle.doc.lineAt(offset).number, lastLine = oracle.doc.lineAt(offset + this.length).number;
5615 let lines = lastLine - firstLine + 1;
5616 let perLine, perChar = 0;
5617 if (oracle.lineWrapping) {
5618 let totalPerLine = Math.min(this.height, oracle.lineHeight * lines);
5619 perLine = totalPerLine / lines;
5620 if (this.length > lines + 1)
5621 perChar = (this.height - totalPerLine) / (this.length - lines - 1);
5622 }
5623 else {
5624 perLine = this.height / lines;
5625 }
5626 return { firstLine, lastLine, perLine, perChar };
5627 }
5628 blockAt(height, oracle, top, offset) {
5629 let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
5630 if (oracle.lineWrapping) {
5631 let guess = offset + (height < oracle.lineHeight ? 0
5632 : Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
5633 let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
5634 let lineTop = Math.max(top, height - lineHeight / 2);
5635 return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
5636 }
5637 else {
5638 let line = Math.max(0, Math.min(lastLine - firstLine, Math.floor((height - top) / perLine)));
5639 let { from, length } = oracle.doc.line(firstLine + line);
5640 return new BlockInfo(from, length, top + perLine * line, perLine, 0);
5641 }
5642 }
5643 lineAt(value, type, oracle, top, offset) {
5644 if (type == QueryType.ByHeight)
5645 return this.blockAt(value, oracle, top, offset);
5646 if (type == QueryType.ByPosNoHeight) {
5647 let { from, to } = oracle.doc.lineAt(value);
5648 return new BlockInfo(from, to - from, 0, 0, 0);
5649 }
5650 let { firstLine, perLine, perChar } = this.heightMetrics(oracle, offset);
5651 let line = oracle.doc.lineAt(value), lineHeight = perLine + line.length * perChar;
5652 let linesAbove = line.number - firstLine;
5653 let lineTop = top + perLine * linesAbove + perChar * (line.from - offset - linesAbove);
5654 return new BlockInfo(line.from, line.length, Math.max(top, Math.min(lineTop, top + this.height - lineHeight)), lineHeight, 0);
5655 }
5656 forEachLine(from, to, oracle, top, offset, f) {
5657 from = Math.max(from, offset);
5658 to = Math.min(to, offset + this.length);
5659 let { firstLine, perLine, perChar } = this.heightMetrics(oracle, offset);
5660 for (let pos = from, lineTop = top; pos <= to;) {
5661 let line = oracle.doc.lineAt(pos);
5662 if (pos == from) {
5663 let linesAbove = line.number - firstLine;
5664 lineTop += perLine * linesAbove + perChar * (from - offset - linesAbove);
5665 }
5666 let lineHeight = perLine + perChar * line.length;
5667 f(new BlockInfo(line.from, line.length, lineTop, lineHeight, 0));
5668 lineTop += lineHeight;
5669 pos = line.to + 1;
5670 }
5671 }
5672 replace(from, to, nodes) {
5673 let after = this.length - to;
5674 if (after > 0) {
5675 let last = nodes[nodes.length - 1];
5676 if (last instanceof HeightMapGap)
5677 nodes[nodes.length - 1] = new HeightMapGap(last.length + after);
5678 else
5679 nodes.push(null, new HeightMapGap(after - 1));
5680 }
5681 if (from > 0) {
5682 let first = nodes[0];
5683 if (first instanceof HeightMapGap)
5684 nodes[0] = new HeightMapGap(from + first.length);
5685 else
5686 nodes.unshift(new HeightMapGap(from - 1), null);
5687 }
5688 return HeightMap.of(nodes);
5689 }
5690 decomposeLeft(to, result) {
5691 result.push(new HeightMapGap(to - 1), null);
5692 }
5693 decomposeRight(from, result) {
5694 result.push(null, new HeightMapGap(this.length - from - 1));
5695 }
5696 updateHeight(oracle, offset = 0, force = false, measured) {
5697 let end = offset + this.length;
5698 if (measured && measured.from <= offset + this.length && measured.more) {
5699 // Fill in part of this gap with measured lines. We know there
5700 // can't be widgets or collapsed ranges in those lines, because
5701 // they would already have been added to the heightmap (gaps
5702 // only contain plain text).
5703 let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
5704 if (measured.from > offset)
5705 nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
5706 while (pos <= end && measured.more) {
5707 let len = oracle.doc.lineAt(pos).length;
5708 if (nodes.length)
5709 nodes.push(null);
5710 let height = measured.heights[measured.index++], above = 0;
5711 if (height < 0) {
5712 above = -height;
5713 height = measured.heights[measured.index++];
5714 }
5715 if (singleHeight == -1)
5716 singleHeight = height;
5717 else if (Math.abs(height - singleHeight) >= Epsilon)
5718 singleHeight = -2;
5719 let line = new HeightMapText(len, height, above);
5720 line.outdated = false;
5721 nodes.push(line);
5722 pos += len + 1;
5723 }
5724 if (pos <= end)
5725 nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
5726 let result = HeightMap.of(nodes);
5727 if (singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
5728 Math.abs(singleHeight - this.heightMetrics(oracle, offset).perLine) >= Epsilon)
5729 heightChangeFlag = true;
5730 return replace(this, result);
5731 }
5732 else if (force || this.outdated) {
5733 this.setHeight(oracle.heightForGap(offset, offset + this.length));
5734 this.outdated = false;
5735 }
5736 return this;
5737 }
5738 toString() { return `gap(${this.length})`; }
5739}
5740class HeightMapBranch extends HeightMap {
5741 constructor(left, brk, right) {
5742 super(left.length + brk + right.length, left.height + right.height, brk | (left.outdated || right.outdated ? 2 /* Flag.Outdated */ : 0));
5743 this.left = left;
5744 this.right = right;
5745 this.size = left.size + right.size;
5746 }
5747 get break() { return this.flags & 1 /* Flag.Break */; }
5748 blockAt(height, oracle, top, offset) {
5749 let mid = top + this.left.height;
5750 return height < mid ? this.left.blockAt(height, oracle, top, offset)
5751 : this.right.blockAt(height, oracle, mid, offset + this.left.length + this.break);
5752 }
5753 lineAt(value, type, oracle, top, offset) {
5754 let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
5755 let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
5756 let base = left ? this.left.lineAt(value, type, oracle, top, offset)
5757 : this.right.lineAt(value, type, oracle, rightTop, rightOffset);
5758 if (this.break || (left ? base.to < rightOffset : base.from > rightOffset))
5759 return base;
5760 let subQuery = type == QueryType.ByPosNoHeight ? QueryType.ByPosNoHeight : QueryType.ByPos;
5761 if (left)
5762 return base.join(this.right.lineAt(rightOffset, subQuery, oracle, rightTop, rightOffset));
5763 else
5764 return this.left.lineAt(rightOffset, subQuery, oracle, top, offset).join(base);
5765 }
5766 forEachLine(from, to, oracle, top, offset, f) {
5767 let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
5768 if (this.break) {
5769 if (from < rightOffset)
5770 this.left.forEachLine(from, to, oracle, top, offset, f);
5771 if (to >= rightOffset)
5772 this.right.forEachLine(from, to, oracle, rightTop, rightOffset, f);
5773 }
5774 else {
5775 let mid = this.lineAt(rightOffset, QueryType.ByPos, oracle, top, offset);
5776 if (from < mid.from)
5777 this.left.forEachLine(from, mid.from - 1, oracle, top, offset, f);
5778 if (mid.to >= from && mid.from <= to)
5779 f(mid);
5780 if (to > mid.to)
5781 this.right.forEachLine(mid.to + 1, to, oracle, rightTop, rightOffset, f);
5782 }
5783 }
5784 replace(from, to, nodes) {
5785 let rightStart = this.left.length + this.break;
5786 if (to < rightStart)
5787 return this.balanced(this.left.replace(from, to, nodes), this.right);
5788 if (from > this.left.length)
5789 return this.balanced(this.left, this.right.replace(from - rightStart, to - rightStart, nodes));
5790 let result = [];
5791 if (from > 0)
5792 this.decomposeLeft(from, result);
5793 let left = result.length;
5794 for (let node of nodes)
5795 result.push(node);
5796 if (from > 0)
5797 mergeGaps(result, left - 1);
5798 if (to < this.length) {
5799 let right = result.length;
5800 this.decomposeRight(to, result);
5801 mergeGaps(result, right);
5802 }
5803 return HeightMap.of(result);
5804 }
5805 decomposeLeft(to, result) {
5806 let left = this.left.length;
5807 if (to <= left)
5808 return this.left.decomposeLeft(to, result);
5809 result.push(this.left);
5810 if (this.break) {
5811 left++;
5812 if (to >= left)
5813 result.push(null);
5814 }
5815 if (to > left)
5816 this.right.decomposeLeft(to - left, result);
5817 }
5818 decomposeRight(from, result) {
5819 let left = this.left.length, right = left + this.break;
5820 if (from >= right)
5821 return this.right.decomposeRight(from - right, result);
5822 if (from < left)
5823 this.left.decomposeRight(from, result);
5824 if (this.break && from < right)
5825 result.push(null);
5826 result.push(this.right);
5827 }
5828 balanced(left, right) {
5829 if (left.size > 2 * right.size || right.size > 2 * left.size)
5830 return HeightMap.of(this.break ? [left, null, right] : [left, right]);
5831 this.left = replace(this.left, left);
5832 this.right = replace(this.right, right);
5833 this.setHeight(left.height + right.height);
5834 this.outdated = left.outdated || right.outdated;
5835 this.size = left.size + right.size;
5836 this.length = left.length + this.break + right.length;
5837 return this;
5838 }
5839 updateHeight(oracle, offset = 0, force = false, measured) {
5840 let { left, right } = this, rightStart = offset + left.length + this.break, rebalance = null;
5841 if (measured && measured.from <= offset + left.length && measured.more)
5842 rebalance = left = left.updateHeight(oracle, offset, force, measured);
5843 else
5844 left.updateHeight(oracle, offset, force);
5845 if (measured && measured.from <= rightStart + right.length && measured.more)
5846 rebalance = right = right.updateHeight(oracle, rightStart, force, measured);
5847 else
5848 right.updateHeight(oracle, rightStart, force);
5849 if (rebalance)
5850 return this.balanced(left, right);
5851 this.height = this.left.height + this.right.height;
5852 this.outdated = false;
5853 return this;
5854 }
5855 toString() { return this.left + (this.break ? " " : "-") + this.right; }
5856}
5857function mergeGaps(nodes, around) {
5858 let before, after;
5859 if (nodes[around] == null &&
5860 (before = nodes[around - 1]) instanceof HeightMapGap &&
5861 (after = nodes[around + 1]) instanceof HeightMapGap)
5862 nodes.splice(around - 1, 3, new HeightMapGap(before.length + 1 + after.length));
5863}
5864const relevantWidgetHeight = 5;
5865class NodeBuilder {
5866 constructor(pos, oracle) {
5867 this.pos = pos;
5868 this.oracle = oracle;
5869 this.nodes = [];
5870 this.lineStart = -1;
5871 this.lineEnd = -1;
5872 this.covering = null;
5873 this.writtenTo = pos;
5874 }
5875 get isCovered() {
5876 return this.covering && this.nodes[this.nodes.length - 1] == this.covering;
5877 }
5878 span(_from, to) {
5879 if (this.lineStart > -1) {
5880 let end = Math.min(to, this.lineEnd), last = this.nodes[this.nodes.length - 1];
5881 if (last instanceof HeightMapText)
5882 last.length += end - this.pos;
5883 else if (end > this.pos || !this.isCovered)
5884 this.nodes.push(new HeightMapText(end - this.pos, -1, 0));
5885 this.writtenTo = end;
5886 if (to > end) {
5887 this.nodes.push(null);
5888 this.writtenTo++;
5889 this.lineStart = -1;
5890 }
5891 }
5892 this.pos = to;
5893 }
5894 point(from, to, deco) {
5895 if (from < to || deco.heightRelevant) {
5896 let height = deco.widget ? deco.widget.estimatedHeight : 0;
5897 let breaks = deco.widget ? deco.widget.lineBreaks : 0;
5898 if (height < 0)
5899 height = this.oracle.lineHeight;
5900 let len = to - from;
5901 if (deco.block) {
5902 this.addBlock(new HeightMapBlock(len, height, deco));
5903 }
5904 else if (len || breaks || height >= relevantWidgetHeight) {
5905 this.addLineDeco(height, breaks, len);
5906 }
5907 }
5908 else if (to > from) {
5909 this.span(from, to);
5910 }
5911 if (this.lineEnd > -1 && this.lineEnd < this.pos)
5912 this.lineEnd = this.oracle.doc.lineAt(this.pos).to;
5913 }
5914 enterLine() {
5915 if (this.lineStart > -1)
5916 return;
5917 let { from, to } = this.oracle.doc.lineAt(this.pos);
5918 this.lineStart = from;
5919 this.lineEnd = to;
5920 if (this.writtenTo < from) {
5921 if (this.writtenTo < from - 1 || this.nodes[this.nodes.length - 1] == null)
5922 this.nodes.push(this.blankContent(this.writtenTo, from - 1));
5923 this.nodes.push(null);
5924 }
5925 if (this.pos > from)
5926 this.nodes.push(new HeightMapText(this.pos - from, -1, 0));
5927 this.writtenTo = this.pos;
5928 }
5929 blankContent(from, to) {
5930 let gap = new HeightMapGap(to - from);
5931 if (this.oracle.doc.lineAt(from).to == to)
5932 gap.flags |= 4 /* Flag.SingleLine */;
5933 return gap;
5934 }
5935 ensureLine() {
5936 this.enterLine();
5937 let last = this.nodes.length ? this.nodes[this.nodes.length - 1] : null;
5938 if (last instanceof HeightMapText)
5939 return last;
5940 let line = new HeightMapText(0, -1, 0);
5941 this.nodes.push(line);
5942 return line;
5943 }
5944 addBlock(block) {
5945 this.enterLine();
5946 let deco = block.deco;
5947 if (deco && deco.startSide > 0 && !this.isCovered)
5948 this.ensureLine();
5949 this.nodes.push(block);
5950 this.writtenTo = this.pos = this.pos + block.length;
5951 if (deco && deco.endSide > 0)
5952 this.covering = block;
5953 }
5954 addLineDeco(height, breaks, length) {
5955 let line = this.ensureLine();
5956 line.length += length;
5957 line.collapsed += length;
5958 line.widgetHeight = Math.max(line.widgetHeight, height);
5959 line.breaks += breaks;
5960 this.writtenTo = this.pos = this.pos + length;
5961 }
5962 finish(from) {
5963 let last = this.nodes.length == 0 ? null : this.nodes[this.nodes.length - 1];
5964 if (this.lineStart > -1 && !(last instanceof HeightMapText) && !this.isCovered)
5965 this.nodes.push(new HeightMapText(0, -1, 0));
5966 else if (this.writtenTo < this.pos || last == null)
5967 this.nodes.push(this.blankContent(this.writtenTo, this.pos));
5968 let pos = from;
5969 for (let node of this.nodes) {
5970 if (node instanceof HeightMapText)
5971 node.updateHeight(this.oracle, pos);
5972 pos += node ? node.length : 1;
5973 }
5974 return this.nodes;
5975 }
5976 // Always called with a region that on both sides either stretches
5977 // to a line break or the end of the document.
5978 // The returned array uses null to indicate line breaks, but never
5979 // starts or ends in a line break, or has multiple line breaks next
5980 // to each other.
5981 static build(oracle, decorations, from, to) {
5982 let builder = new NodeBuilder(from, oracle);
5983 RangeSet.spans(decorations, from, to, builder, 0);
5984 return builder.finish(from);
5985 }
5986}
5987function heightRelevantDecoChanges(a, b, diff) {
5988 let comp = new DecorationComparator;
5989 RangeSet.compare(a, b, diff, comp, 0);
5990 return comp.changes;
5991}
5992class DecorationComparator {
5993 constructor() {
5994 this.changes = [];
5995 }
5996 compareRange() { }
5997 comparePoint(from, to, a, b) {
5998 if (from < to || a && a.heightRelevant || b && b.heightRelevant)
5999 addRange(from, to, this.changes, 5);
6000 }
6001}
6002
6003function visiblePixelRange(dom, paddingTop) {
6004 let rect = dom.getBoundingClientRect();
6005 let doc = dom.ownerDocument, win = doc.defaultView || window;
6006 let left = Math.max(0, rect.left), right = Math.min(win.innerWidth, rect.right);
6007 let top = Math.max(0, rect.top), bottom = Math.min(win.innerHeight, rect.bottom);
6008 for (let parent = dom.parentNode; parent && parent != doc.body;) {
6009 if (parent.nodeType == 1) {
6010 let elt = parent;
6011 let style = window.getComputedStyle(elt);
6012 if ((elt.scrollHeight > elt.clientHeight || elt.scrollWidth > elt.clientWidth) &&
6013 style.overflow != "visible") {
6014 let parentRect = elt.getBoundingClientRect();
6015 left = Math.max(left, parentRect.left);
6016 right = Math.min(right, parentRect.right);
6017 top = Math.max(top, parentRect.top);
6018 bottom = Math.min(parent == dom.parentNode ? win.innerHeight : bottom, parentRect.bottom);
6019 }
6020 parent = style.position == "absolute" || style.position == "fixed" ? elt.offsetParent : elt.parentNode;
6021 }
6022 else if (parent.nodeType == 11) { // Shadow root
6023 parent = parent.host;
6024 }
6025 else {
6026 break;
6027 }
6028 }
6029 return { left: left - rect.left, right: Math.max(left, right) - rect.left,
6030 top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
6031}
6032function inWindow(elt) {
6033 let rect = elt.getBoundingClientRect(), win = elt.ownerDocument.defaultView || window;
6034 return rect.left < win.innerWidth && rect.right > 0 &&
6035 rect.top < win.innerHeight && rect.bottom > 0;
6036}
6037function fullPixelRange(dom, paddingTop) {
6038 let rect = dom.getBoundingClientRect();
6039 return { left: 0, right: rect.right - rect.left,
6040 top: paddingTop, bottom: rect.bottom - (rect.top + paddingTop) };
6041}
6042// Line gaps are placeholder widgets used to hide pieces of overlong
6043// lines within the viewport, as a kludge to keep the editor
6044// responsive when a ridiculously long line is loaded into it.
6045class LineGap {
6046 constructor(from, to, size, displaySize) {
6047 this.from = from;
6048 this.to = to;
6049 this.size = size;
6050 this.displaySize = displaySize;
6051 }
6052 static same(a, b) {
6053 if (a.length != b.length)
6054 return false;
6055 for (let i = 0; i < a.length; i++) {
6056 let gA = a[i], gB = b[i];
6057 if (gA.from != gB.from || gA.to != gB.to || gA.size != gB.size)
6058 return false;
6059 }
6060 return true;
6061 }
6062 draw(viewState, wrapping) {
6063 return Decoration.replace({
6064 widget: new LineGapWidget(this.displaySize * (wrapping ? viewState.scaleY : viewState.scaleX), wrapping)
6065 }).range(this.from, this.to);
6066 }
6067}
6068class LineGapWidget extends WidgetType {
6069 constructor(size, vertical) {
6070 super();
6071 this.size = size;
6072 this.vertical = vertical;
6073 }
6074 eq(other) { return other.size == this.size && other.vertical == this.vertical; }
6075 toDOM() {
6076 let elt = document.createElement("div");
6077 if (this.vertical) {
6078 elt.style.height = this.size + "px";
6079 }
6080 else {
6081 elt.style.width = this.size + "px";
6082 elt.style.height = "2px";
6083 elt.style.display = "inline-block";
6084 }
6085 return elt;
6086 }
6087 get estimatedHeight() { return this.vertical ? this.size : -1; }
6088}
6089class ViewState {
6090 constructor(view, state) {
6091 this.view = view;
6092 this.state = state;
6093 // These are contentDOM-local coordinates
6094 this.pixelViewport = { left: 0, right: window.innerWidth, top: 0, bottom: 0 };
6095 this.inView = true;
6096 this.paddingTop = 0; // Padding above the document, scaled
6097 this.paddingBottom = 0; // Padding below the document, scaled
6098 this.contentDOMWidth = 0; // contentDOM.getBoundingClientRect().width
6099 this.contentDOMHeight = 0; // contentDOM.getBoundingClientRect().height
6100 this.editorHeight = 0; // scrollDOM.clientHeight, unscaled
6101 this.editorWidth = 0; // scrollDOM.clientWidth, unscaled
6102 // The CSS-transformation scale of the editor (transformed size /
6103 // concrete size)
6104 this.scaleX = 1;
6105 this.scaleY = 1;
6106 // Last seen vertical offset of the element at the top of the scroll
6107 // container, or top of the window if there's no wrapping scroller
6108 this.scrollOffset = 0;
6109 this.scrolledToBottom = false;
6110 // The vertical position (document-relative) to which to anchor the
6111 // scroll position. -1 means anchor to the end of the document.
6112 this.scrollAnchorPos = 0;
6113 // The height at the anchor position. Set by the DOM update phase.
6114 // -1 means no height available.
6115 this.scrollAnchorHeight = -1;
6116 // See VP.MaxDOMHeight
6117 this.scaler = IdScaler;
6118 this.scrollTarget = null;
6119 // Briefly set to true when printing, to disable viewport limiting
6120 this.printing = false;
6121 // Flag set when editor content was redrawn, so that the next
6122 // measure stage knows it must read DOM layout
6123 this.mustMeasureContent = true;
6124 this.defaultTextDirection = Direction.LTR;
6125 this.visibleRanges = [];
6126 // Cursor 'assoc' is only significant when the cursor is on a line
6127 // wrap point, where it must stick to the character that it is
6128 // associated with. Since browsers don't provide a reasonable
6129 // interface to set or query this, when a selection is set that
6130 // might cause this to be significant, this flag is set. The next
6131 // measure phase will check whether the cursor is on a line-wrapping
6132 // boundary and, if so, reset it to make sure it is positioned in
6133 // the right place.
6134 this.mustEnforceCursorAssoc = false;
6135 let guessWrapping = state.facet(contentAttributes).some(v => typeof v != "function" && v.class == "cm-lineWrapping");
6136 this.heightOracle = new HeightOracle(guessWrapping);
6137 this.stateDeco = staticDeco(state);
6138 this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
6139 for (let i = 0; i < 2; i++) {
6140 this.viewport = this.getViewport(0, null);
6141 if (!this.updateForViewport())
6142 break;
6143 }
6144 this.updateViewportLines();
6145 this.lineGaps = this.ensureLineGaps([]);
6146 this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(this, false)));
6147 this.scrollParent = view.scrollDOM;
6148 this.computeVisibleRanges();
6149 }
6150 updateForViewport() {
6151 let viewports = [this.viewport], { main } = this.state.selection;
6152 for (let i = 0; i <= 1; i++) {
6153 let pos = i ? main.head : main.anchor;
6154 if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
6155 let { from, to } = this.lineBlockAt(pos);
6156 viewports.push(new Viewport(from, to));
6157 }
6158 }
6159 this.viewports = viewports.sort((a, b) => a.from - b.from);
6160 return this.updateScaler();
6161 }
6162 updateScaler() {
6163 let scaler = this.scaler;
6164 this.scaler = this.heightMap.height <= 7000000 /* VP.MaxDOMHeight */ ? IdScaler :
6165 new BigScaler(this.heightOracle, this.heightMap, this.viewports);
6166 return scaler.eq(this.scaler) ? 0 : 2 /* UpdateFlag.Height */;
6167 }
6168 updateViewportLines() {
6169 this.viewportLines = [];
6170 this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.heightOracle.setDoc(this.state.doc), 0, 0, block => {
6171 this.viewportLines.push(scaleBlock(block, this.scaler));
6172 });
6173 }
6174 update(update, scrollTarget = null) {
6175 this.state = update.state;
6176 let prevDeco = this.stateDeco;
6177 this.stateDeco = staticDeco(this.state);
6178 let contentChanges = update.changedRanges;
6179 let heightChanges = ChangedRange.extendWithRanges(contentChanges, heightRelevantDecoChanges(prevDeco, this.stateDeco, update ? update.changes : ChangeSet.empty(this.state.doc.length)));
6180 let prevHeight = this.heightMap.height;
6181 let scrollAnchor = this.scrolledToBottom ? null : this.scrollAnchorAt(this.scrollOffset);
6182 clearHeightChangeFlag();
6183 this.heightMap = this.heightMap.applyChanges(this.stateDeco, update.startState.doc, this.heightOracle.setDoc(this.state.doc), heightChanges);
6184 if (this.heightMap.height != prevHeight || heightChangeFlag)
6185 update.flags |= 2 /* UpdateFlag.Height */;
6186 if (scrollAnchor) {
6187 this.scrollAnchorPos = update.changes.mapPos(scrollAnchor.from, -1);
6188 this.scrollAnchorHeight = scrollAnchor.top;
6189 }
6190 else {
6191 this.scrollAnchorPos = -1;
6192 this.scrollAnchorHeight = prevHeight;
6193 }
6194 let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
6195 if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
6196 !this.viewportIsAppropriate(viewport))
6197 viewport = this.getViewport(0, scrollTarget);
6198 let viewportChange = viewport.from != this.viewport.from || viewport.to != this.viewport.to;
6199 this.viewport = viewport;
6200 update.flags |= this.updateForViewport();
6201 if (viewportChange || !update.changes.empty || (update.flags & 2 /* UpdateFlag.Height */))
6202 this.updateViewportLines();
6203 if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
6204 this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
6205 update.flags |= this.computeVisibleRanges(update.changes);
6206 if (scrollTarget)
6207 this.scrollTarget = scrollTarget;
6208 if (!this.mustEnforceCursorAssoc && (update.selectionSet || update.focusChanged) && update.view.lineWrapping &&
6209 update.state.selection.main.empty && update.state.selection.main.assoc &&
6210 !update.state.facet(nativeSelectionHidden))
6211 this.mustEnforceCursorAssoc = true;
6212 }
6213 measure() {
6214 let { view } = this, dom = view.contentDOM, style = window.getComputedStyle(dom);
6215 let oracle = this.heightOracle;
6216 let whiteSpace = style.whiteSpace;
6217 this.defaultTextDirection = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
6218 let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent === "refresh";
6219 let domRect = dom.getBoundingClientRect();
6220 let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
6221 this.contentDOMHeight = domRect.height;
6222 this.mustMeasureContent = false;
6223 let result = 0, bias = 0;
6224 if (domRect.width && domRect.height) {
6225 let { scaleX, scaleY } = getScale(dom, domRect);
6226 if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
6227 scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
6228 this.scaleX = scaleX;
6229 this.scaleY = scaleY;
6230 result |= 16 /* UpdateFlag.Geometry */;
6231 refresh = measureContent = true;
6232 }
6233 }
6234 // Vertical padding
6235 let paddingTop = (parseInt(style.paddingTop) || 0) * this.scaleY;
6236 let paddingBottom = (parseInt(style.paddingBottom) || 0) * this.scaleY;
6237 if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
6238 this.paddingTop = paddingTop;
6239 this.paddingBottom = paddingBottom;
6240 result |= 16 /* UpdateFlag.Geometry */ | 2 /* UpdateFlag.Height */;
6241 }
6242 if (this.editorWidth != view.scrollDOM.clientWidth) {
6243 if (oracle.lineWrapping)
6244 measureContent = true;
6245 this.editorWidth = view.scrollDOM.clientWidth;
6246 result |= 16 /* UpdateFlag.Geometry */;
6247 }
6248 let scrollParent = scrollableParents(this.view.contentDOM, false).y;
6249 if (scrollParent != this.scrollParent) {
6250 this.scrollParent = scrollParent;
6251 this.scrollAnchorHeight = -1;
6252 this.scrollOffset = 0;
6253 }
6254 let scrollOffset = this.getScrollOffset();
6255 if (this.scrollOffset != scrollOffset) {
6256 this.scrollAnchorHeight = -1;
6257 this.scrollOffset = scrollOffset;
6258 }
6259 this.scrolledToBottom = isScrolledToBottom(this.scrollParent || view.win);
6260 // Pixel viewport
6261 let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
6262 let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
6263 this.pixelViewport = pixelViewport;
6264 let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
6265 if (inView != this.inView) {
6266 this.inView = inView;
6267 if (inView)
6268 measureContent = true;
6269 }
6270 if (!this.inView && !this.scrollTarget && !inWindow(view.dom))
6271 return 0;
6272 let contentWidth = domRect.width;
6273 if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
6274 this.contentDOMWidth = domRect.width;
6275 this.editorHeight = view.scrollDOM.clientHeight;
6276 result |= 16 /* UpdateFlag.Geometry */;
6277 }
6278 if (measureContent) {
6279 let lineHeights = view.docView.measureVisibleLineHeights(this.viewport);
6280 if (oracle.mustRefreshForHeights(lineHeights))
6281 refresh = true;
6282 if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
6283 let { lineHeight, charWidth, textHeight } = view.docView.measureTextSize();
6284 refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, Math.max(5, contentWidth / charWidth), lineHeights);
6285 if (refresh) {
6286 view.docView.minWidth = 0;
6287 result |= 16 /* UpdateFlag.Geometry */;
6288 }
6289 }
6290 if (dTop > 0 && dBottom > 0)
6291 bias = Math.max(dTop, dBottom);
6292 else if (dTop < 0 && dBottom < 0)
6293 bias = Math.min(dTop, dBottom);
6294 clearHeightChangeFlag();
6295 for (let vp of this.viewports) {
6296 let heights = vp.from == this.viewport.from ? lineHeights : view.docView.measureVisibleLineHeights(vp);
6297 this.heightMap = (refresh ? HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle, [new ChangedRange(0, 0, 0, view.state.doc.length)]) : this.heightMap).updateHeight(oracle, 0, refresh, new MeasuredHeights(vp.from, heights));
6298 }
6299 if (heightChangeFlag)
6300 result |= 2 /* UpdateFlag.Height */;
6301 }
6302 let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
6303 this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from ||
6304 this.scrollTarget.range.head > this.viewport.to);
6305 if (viewportChange) {
6306 if (result & 2 /* UpdateFlag.Height */)
6307 result |= this.updateScaler();
6308 this.viewport = this.getViewport(bias, this.scrollTarget);
6309 result |= this.updateForViewport();
6310 }
6311 if ((result & 2 /* UpdateFlag.Height */) || viewportChange)
6312 this.updateViewportLines();
6313 if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
6314 this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps, view));
6315 result |= this.computeVisibleRanges();
6316 if (this.mustEnforceCursorAssoc) {
6317 this.mustEnforceCursorAssoc = false;
6318 // This is done in the read stage, because moving the selection
6319 // to a line end is going to trigger a layout anyway, so it
6320 // can't be a pure write. It should be rare that it does any
6321 // writing.
6322 view.docView.enforceCursorAssoc();
6323 }
6324 return result;
6325 }
6326 get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
6327 get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
6328 getViewport(bias, scrollTarget) {
6329 // This will divide VP.Margin between the top and the
6330 // bottom, depending on the bias (the change in viewport position
6331 // since the last update). It'll hold a number between 0 and 1
6332 let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* VP.Margin */ / 2));
6333 let map = this.heightMap, oracle = this.heightOracle;
6334 let { visibleTop, visibleBottom } = this;
6335 let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* VP.Margin */, QueryType.ByHeight, oracle, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* VP.Margin */, QueryType.ByHeight, oracle, 0, 0).to);
6336 // If scrollTarget is given, make sure the viewport includes that position
6337 if (scrollTarget) {
6338 let { head } = scrollTarget.range;
6339 if (head < viewport.from || head > viewport.to) {
6340 let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
6341 let block = map.lineAt(head, QueryType.ByPos, oracle, 0, 0), topPos;
6342 if (scrollTarget.y == "center")
6343 topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
6344 else if (scrollTarget.y == "start" || scrollTarget.y == "nearest" && head < viewport.from)
6345 topPos = block.top;
6346 else
6347 topPos = block.bottom - viewHeight;
6348 viewport = new Viewport(map.lineAt(topPos - 1000 /* VP.Margin */ / 2, QueryType.ByHeight, oracle, 0, 0).from, map.lineAt(topPos + viewHeight + 1000 /* VP.Margin */ / 2, QueryType.ByHeight, oracle, 0, 0).to);
6349 }
6350 }
6351 return viewport;
6352 }
6353 mapViewport(viewport, changes) {
6354 let from = changes.mapPos(viewport.from, -1), to = changes.mapPos(viewport.to, 1);
6355 return new Viewport(this.heightMap.lineAt(from, QueryType.ByPos, this.heightOracle, 0, 0).from, this.heightMap.lineAt(to, QueryType.ByPos, this.heightOracle, 0, 0).to);
6356 }
6357 // Checks if a given viewport covers the visible part of the
6358 // document and not too much beyond that.
6359 viewportIsAppropriate({ from, to }, bias = 0) {
6360 if (!this.inView)
6361 return true;
6362 let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.heightOracle, 0, 0);
6363 let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.heightOracle, 0, 0);
6364 let { visibleTop, visibleBottom } = this;
6365 return (from == 0 || top <= visibleTop - Math.max(10 /* VP.MinCoverMargin */, Math.min(-bias, 250 /* VP.MaxCoverMargin */))) &&
6366 (to == this.state.doc.length ||
6367 bottom >= visibleBottom + Math.max(10 /* VP.MinCoverMargin */, Math.min(bias, 250 /* VP.MaxCoverMargin */))) &&
6368 (top > visibleTop - 2 * 1000 /* VP.Margin */ && bottom < visibleBottom + 2 * 1000 /* VP.Margin */);
6369 }
6370 mapLineGaps(gaps, changes) {
6371 if (!gaps.length || changes.empty)
6372 return gaps;
6373 let mapped = [];
6374 for (let gap of gaps)
6375 if (!changes.touchesRange(gap.from, gap.to))
6376 mapped.push(new LineGap(changes.mapPos(gap.from), changes.mapPos(gap.to), gap.size, gap.displaySize));
6377 return mapped;
6378 }
6379 // Computes positions in the viewport where the start or end of a
6380 // line should be hidden, trying to reuse existing line gaps when
6381 // appropriate to avoid unneccesary redraws.
6382 // Uses crude character-counting for the positioning and sizing,
6383 // since actual DOM coordinates aren't always available and
6384 // predictable. Relies on generous margins (see LG.Margin) to hide
6385 // the artifacts this might produce from the user.
6386 ensureLineGaps(current, mayMeasure) {
6387 let wrapping = this.heightOracle.lineWrapping;
6388 let margin = wrapping ? 10000 /* LG.MarginWrap */ : 2000 /* LG.Margin */, halfMargin = margin >> 1, doubleMargin = margin << 1;
6389 // The non-wrapping logic won't work at all in predominantly right-to-left text.
6390 if (this.defaultTextDirection != Direction.LTR && !wrapping)
6391 return [];
6392 let gaps = [];
6393 let addGap = (from, to, line, structure) => {
6394 if (to - from < halfMargin)
6395 return;
6396 let sel = this.state.selection.main, avoid = [sel.from];
6397 if (!sel.empty)
6398 avoid.push(sel.to);
6399 for (let pos of avoid) {
6400 if (pos > from && pos < to) {
6401 addGap(from, pos - 10 /* LG.SelectionMargin */, line, structure);
6402 addGap(pos + 10 /* LG.SelectionMargin */, to, line, structure);
6403 return;
6404 }
6405 }
6406 let gap = find(current, gap => gap.from >= line.from && gap.to <= line.to &&
6407 Math.abs(gap.from - from) < halfMargin && Math.abs(gap.to - to) < halfMargin &&
6408 !avoid.some(pos => gap.from < pos && gap.to > pos));
6409 if (!gap) {
6410 // When scrolling down, snap gap ends to line starts to avoid shifts in wrapping
6411 if (to < line.to && mayMeasure && wrapping &&
6412 mayMeasure.visibleRanges.some(r => r.from <= to && r.to >= to)) {
6413 let lineStart = mayMeasure.moveToLineBoundary(EditorSelection.cursor(to), false, true).head;
6414 if (lineStart > from)
6415 to = lineStart;
6416 }
6417 let size = this.gapSize(line, from, to, structure);
6418 let displaySize = wrapping || size < 2000000 /* VP.MaxHorizGap */ ? size : 2000000 /* VP.MaxHorizGap */;
6419 gap = new LineGap(from, to, size, displaySize);
6420 }
6421 gaps.push(gap);
6422 };
6423 let checkLine = (line) => {
6424 if (line.length < doubleMargin || line.type != BlockType.Text)
6425 return;
6426 let structure = lineStructure(line.from, line.to, this.stateDeco);
6427 if (structure.total < doubleMargin)
6428 return;
6429 let target = this.scrollTarget ? this.scrollTarget.range.head : null;
6430 let viewFrom, viewTo;
6431 if (wrapping) {
6432 let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
6433 let top, bot;
6434 if (target != null) {
6435 let targetFrac = findFraction(structure, target);
6436 let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
6437 top = targetFrac - spaceFrac;
6438 bot = targetFrac + spaceFrac;
6439 }
6440 else {
6441 top = (this.visibleTop - line.top - marginHeight) / line.height;
6442 bot = (this.visibleBottom - line.top + marginHeight) / line.height;
6443 }
6444 viewFrom = findPosition(structure, top);
6445 viewTo = findPosition(structure, bot);
6446 }
6447 else {
6448 let totalWidth = structure.total * this.heightOracle.charWidth;
6449 let marginWidth = margin * this.heightOracle.charWidth;
6450 let horizOffset = 0;
6451 if (totalWidth > 2000000 /* VP.MaxHorizGap */)
6452 for (let old of current) {
6453 if (old.from >= line.from && old.from < line.to && old.size != old.displaySize &&
6454 old.from * this.heightOracle.charWidth + horizOffset < this.pixelViewport.left)
6455 horizOffset = old.size - old.displaySize;
6456 }
6457 let pxLeft = this.pixelViewport.left + horizOffset, pxRight = this.pixelViewport.right + horizOffset;
6458 let left, right;
6459 if (target != null) {
6460 let targetFrac = findFraction(structure, target);
6461 let spaceFrac = ((pxRight - pxLeft) / 2 + marginWidth) / totalWidth;
6462 left = targetFrac - spaceFrac;
6463 right = targetFrac + spaceFrac;
6464 }
6465 else {
6466 left = (pxLeft - marginWidth) / totalWidth;
6467 right = (pxRight + marginWidth) / totalWidth;
6468 }
6469 viewFrom = findPosition(structure, left);
6470 viewTo = findPosition(structure, right);
6471 }
6472 if (viewFrom > line.from)
6473 addGap(line.from, viewFrom, line, structure);
6474 if (viewTo < line.to)
6475 addGap(viewTo, line.to, line, structure);
6476 };
6477 for (let line of this.viewportLines) {
6478 if (Array.isArray(line.type))
6479 line.type.forEach(checkLine);
6480 else
6481 checkLine(line);
6482 }
6483 return gaps;
6484 }
6485 gapSize(line, from, to, structure) {
6486 let fraction = findFraction(structure, to) - findFraction(structure, from);
6487 if (this.heightOracle.lineWrapping) {
6488 return line.height * fraction;
6489 }
6490 else {
6491 return structure.total * this.heightOracle.charWidth * fraction;
6492 }
6493 }
6494 updateLineGaps(gaps) {
6495 if (!LineGap.same(gaps, this.lineGaps)) {
6496 this.lineGaps = gaps;
6497 this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this, this.heightOracle.lineWrapping)));
6498 }
6499 }
6500 computeVisibleRanges(changes) {
6501 let deco = this.stateDeco;
6502 if (this.lineGaps.length)
6503 deco = deco.concat(this.lineGapDeco);
6504 let ranges = [];
6505 RangeSet.spans(deco, this.viewport.from, this.viewport.to, {
6506 span(from, to) { ranges.push({ from, to }); },
6507 point() { }
6508 }, 20);
6509 let changed = 0;
6510 if (ranges.length != this.visibleRanges.length) {
6511 changed = 8 /* UpdateFlag.ViewportMoved */ | 4 /* UpdateFlag.Viewport */;
6512 }
6513 else {
6514 for (let i = 0; i < ranges.length && !(changed & 8 /* UpdateFlag.ViewportMoved */); i++) {
6515 let old = this.visibleRanges[i], nw = ranges[i];
6516 if (old.from != nw.from || old.to != nw.to) {
6517 changed |= 4 /* UpdateFlag.Viewport */;
6518 if (!(changes && changes.mapPos(old.from, -1) == nw.from && changes.mapPos(old.to, 1) == nw.to))
6519 changed |= 8 /* UpdateFlag.ViewportMoved */;
6520 }
6521 }
6522 }
6523 this.visibleRanges = ranges;
6524 return changed;
6525 }
6526 lineBlockAt(pos) {
6527 return (pos >= this.viewport.from && pos <= this.viewport.to &&
6528 this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
6529 scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.heightOracle, 0, 0), this.scaler);
6530 }
6531 lineBlockAtHeight(height) {
6532 return (height >= this.viewportLines[0].top && height <= this.viewportLines[this.viewportLines.length - 1].bottom &&
6533 this.viewportLines.find(l => l.top <= height && l.bottom >= height)) ||
6534 scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.heightOracle, 0, 0), this.scaler);
6535 }
6536 getScrollOffset() {
6537 let base = this.scrollParent == this.view.scrollDOM ? this.scrollParent.scrollTop
6538 : (this.scrollParent ? this.scrollParent.getBoundingClientRect().top : 0) - this.view.contentDOM.getBoundingClientRect().top;
6539 return base * this.scaleY;
6540 }
6541 scrollAnchorAt(scrollOffset) {
6542 let block = this.lineBlockAtHeight(scrollOffset + 8);
6543 return block.from >= this.viewport.from || this.viewportLines[0].top - scrollOffset > 200 ? block : this.viewportLines[0];
6544 }
6545 elementAtHeight(height) {
6546 return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.heightOracle, 0, 0), this.scaler);
6547 }
6548 get docHeight() {
6549 return this.scaler.toDOM(this.heightMap.height);
6550 }
6551 get contentHeight() {
6552 return this.docHeight + this.paddingTop + this.paddingBottom;
6553 }
6554}
6555class Viewport {
6556 constructor(from, to) {
6557 this.from = from;
6558 this.to = to;
6559 }
6560}
6561function lineStructure(from, to, stateDeco) {
6562 let ranges = [], pos = from, total = 0;
6563 RangeSet.spans(stateDeco, from, to, {
6564 span() { },
6565 point(from, to) {
6566 if (from > pos) {
6567 ranges.push({ from: pos, to: from });
6568 total += from - pos;
6569 }
6570 pos = to;
6571 }
6572 }, 20); // We're only interested in collapsed ranges of a significant size
6573 if (pos < to) {
6574 ranges.push({ from: pos, to });
6575 total += to - pos;
6576 }
6577 return { total, ranges };
6578}
6579function findPosition({ total, ranges }, ratio) {
6580 if (ratio <= 0)
6581 return ranges[0].from;
6582 if (ratio >= 1)
6583 return ranges[ranges.length - 1].to;
6584 let dist = Math.floor(total * ratio);
6585 for (let i = 0;; i++) {
6586 let { from, to } = ranges[i], size = to - from;
6587 if (dist <= size)
6588 return from + dist;
6589 dist -= size;
6590 }
6591}
6592function findFraction(structure, pos) {
6593 let counted = 0;
6594 for (let { from, to } of structure.ranges) {
6595 if (pos <= to) {
6596 counted += pos - from;
6597 break;
6598 }
6599 counted += to - from;
6600 }
6601 return counted / structure.total;
6602}
6603function find(array, f) {
6604 for (let val of array)
6605 if (f(val))
6606 return val;
6607 return undefined;
6608}
6609// Don't scale when the document height is within the range of what
6610// the DOM can handle.
6611const IdScaler = {
6612 toDOM(n) { return n; },
6613 fromDOM(n) { return n; },
6614 scale: 1,
6615 eq(other) { return other == this; }
6616};
6617function staticDeco(state) {
6618 let deco = state.facet(decorations).filter(d => typeof d != "function");
6619 let outer = state.facet(outerDecorations).filter(d => typeof d != "function");
6620 if (outer.length)
6621 deco.push(RangeSet.join(outer));
6622 return deco;
6623}
6624// When the height is too big (> VP.MaxDOMHeight), scale down the
6625// regions outside the viewports so that the total height is
6626// VP.MaxDOMHeight.
6627class BigScaler {
6628 constructor(oracle, heightMap, viewports) {
6629 let vpHeight = 0, base = 0, domBase = 0;
6630 this.viewports = viewports.map(({ from, to }) => {
6631 let top = heightMap.lineAt(from, QueryType.ByPos, oracle, 0, 0).top;
6632 let bottom = heightMap.lineAt(to, QueryType.ByPos, oracle, 0, 0).bottom;
6633 vpHeight += bottom - top;
6634 return { from, to, top, bottom, domTop: 0, domBottom: 0 };
6635 });
6636 this.scale = (7000000 /* VP.MaxDOMHeight */ - vpHeight) / (heightMap.height - vpHeight);
6637 for (let obj of this.viewports) {
6638 obj.domTop = domBase + (obj.top - base) * this.scale;
6639 domBase = obj.domBottom = obj.domTop + (obj.bottom - obj.top);
6640 base = obj.bottom;
6641 }
6642 }
6643 toDOM(n) {
6644 for (let i = 0, base = 0, domBase = 0;; i++) {
6645 let vp = i < this.viewports.length ? this.viewports[i] : null;
6646 if (!vp || n < vp.top)
6647 return domBase + (n - base) * this.scale;
6648 if (n <= vp.bottom)
6649 return vp.domTop + (n - vp.top);
6650 base = vp.bottom;
6651 domBase = vp.domBottom;
6652 }
6653 }
6654 fromDOM(n) {
6655 for (let i = 0, base = 0, domBase = 0;; i++) {
6656 let vp = i < this.viewports.length ? this.viewports[i] : null;
6657 if (!vp || n < vp.domTop)
6658 return base + (n - domBase) / this.scale;
6659 if (n <= vp.domBottom)
6660 return vp.top + (n - vp.domTop);
6661 base = vp.bottom;
6662 domBase = vp.domBottom;
6663 }
6664 }
6665 eq(other) {
6666 if (!(other instanceof BigScaler))
6667 return false;
6668 return this.scale == other.scale && this.viewports.length == other.viewports.length &&
6669 this.viewports.every((vp, i) => vp.from == other.viewports[i].from && vp.to == other.viewports[i].to);
6670 }
6671}
6672function scaleBlock(block, scaler) {
6673 if (scaler.scale == 1)
6674 return block;
6675 let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
6676 return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block._content) ? block._content.map(b => scaleBlock(b, scaler)) : block._content);
6677}
6678
6679const theme = /*@__PURE__*/Facet.define({ combine: strs => strs.join(" ") });
6680const darkTheme = /*@__PURE__*/Facet.define({ combine: values => values.indexOf(true) > -1 });
6681const baseThemeID = /*@__PURE__*/StyleModule.newName(), baseLightID = /*@__PURE__*/StyleModule.newName(), baseDarkID = /*@__PURE__*/StyleModule.newName();
6682const lightDarkIDs = { "&light": "." + baseLightID, "&dark": "." + baseDarkID };
6683function buildTheme(main, spec, scopes) {
6684 return new StyleModule(spec, {
6685 finish(sel) {
6686 return /&/.test(sel) ? sel.replace(/&\w*/, m => {
6687 if (m == "&")
6688 return main;
6689 if (!scopes || !scopes[m])
6690 throw new RangeError(`Unsupported selector: ${m}`);
6691 return scopes[m];
6692 }) : main + " " + sel;
6693 }
6694 });
6695}
6696const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
6697 "&": {
6698 position: "relative !important",
6699 boxSizing: "border-box",
6700 "&.cm-focused": {
6701 // Provide a simple default outline to make sure a focused
6702 // editor is visually distinct. Can't leave the default behavior
6703 // because that will apply to the content element, which is
6704 // inside the scrollable container and doesn't include the
6705 // gutters. We also can't use an 'auto' outline, since those
6706 // are, for some reason, drawn behind the element content, which
6707 // will cause things like the active line background to cover
6708 // the outline (#297).
6709 outline: "1px dotted #212121"
6710 },
6711 display: "flex !important",
6712 flexDirection: "column"
6713 },
6714 ".cm-scroller": {
6715 display: "flex !important",
6716 alignItems: "flex-start !important",
6717 fontFamily: "monospace",
6718 lineHeight: 1.4,
6719 height: "100%",
6720 overflowX: "auto",
6721 position: "relative",
6722 zIndex: 0,
6723 overflowAnchor: "none",
6724 },
6725 ".cm-content": {
6726 margin: 0,
6727 flexGrow: 2,
6728 flexShrink: 0,
6729 display: "block",
6730 whiteSpace: "pre",
6731 wordWrap: "normal", // https://github.com/codemirror/dev/issues/456
6732 boxSizing: "border-box",
6733 minHeight: "100%",
6734 padding: "4px 0",
6735 outline: "none",
6736 "&[contenteditable=true]": {
6737 WebkitUserModify: "read-write-plaintext-only",
6738 }
6739 },
6740 ".cm-lineWrapping": {
6741 whiteSpace_fallback: "pre-wrap", // For IE
6742 whiteSpace: "break-spaces",
6743 wordBreak: "break-word", // For Safari, which doesn't support overflow-wrap: anywhere
6744 overflowWrap: "anywhere",
6745 flexShrink: 1
6746 },
6747 "&light .cm-content": { caretColor: "black" },
6748 "&dark .cm-content": { caretColor: "white" },
6749 ".cm-line": {
6750 display: "block",
6751 padding: "0 2px 0 6px"
6752 },
6753 ".cm-layer": {
6754 position: "absolute",
6755 left: 0,
6756 top: 0,
6757 contain: "size style",
6758 "& > *": {
6759 position: "absolute"
6760 }
6761 },
6762 "&light .cm-selectionBackground": {
6763 background: "#d9d9d9"
6764 },
6765 "&dark .cm-selectionBackground": {
6766 background: "#222"
6767 },
6768 "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
6769 background: "#d7d4f0"
6770 },
6771 "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
6772 background: "#233"
6773 },
6774 ".cm-cursorLayer": {
6775 pointerEvents: "none"
6776 },
6777 "&.cm-focused > .cm-scroller > .cm-cursorLayer": {
6778 animation: "steps(1) cm-blink 1.2s infinite"
6779 },
6780 // Two animations defined so that we can switch between them to
6781 // restart the animation without forcing another style
6782 // recomputation.
6783 "@keyframes cm-blink": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
6784 "@keyframes cm-blink2": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
6785 ".cm-cursor, .cm-dropCursor": {
6786 borderLeft: "1.2px solid black",
6787 marginLeft: "-0.6px",
6788 pointerEvents: "none",
6789 },
6790 ".cm-cursor": {
6791 display: "none"
6792 },
6793 "&dark .cm-cursor": {
6794 borderLeftColor: "#ddd"
6795 },
6796 ".cm-dropCursor": {
6797 position: "absolute"
6798 },
6799 "&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor": {
6800 display: "block"
6801 },
6802 ".cm-iso": {
6803 unicodeBidi: "isolate"
6804 },
6805 ".cm-announced": {
6806 position: "fixed",
6807 top: "-10000px"
6808 },
6809 "@media print": {
6810 ".cm-announced": { display: "none" }
6811 },
6812 "&light .cm-activeLine": { backgroundColor: "#cceeff44" },
6813 "&dark .cm-activeLine": { backgroundColor: "#99eeff33" },
6814 "&light .cm-specialChar": { color: "red" },
6815 "&dark .cm-specialChar": { color: "#f78" },
6816 ".cm-gutters": {
6817 flexShrink: 0,
6818 display: "flex",
6819 height: "100%",
6820 boxSizing: "border-box",
6821 zIndex: 200,
6822 },
6823 ".cm-gutters-before": { insetInlineStart: 0 },
6824 ".cm-gutters-after": { insetInlineEnd: 0 },
6825 "&light .cm-gutters": {
6826 backgroundColor: "#f5f5f5",
6827 color: "#6c6c6c",
6828 border: "0px solid #ddd",
6829 "&.cm-gutters-before": { borderRightWidth: "1px" },
6830 "&.cm-gutters-after": { borderLeftWidth: "1px" },
6831 },
6832 "&dark .cm-gutters": {
6833 backgroundColor: "#333338",
6834 color: "#ccc"
6835 },
6836 ".cm-gutter": {
6837 display: "flex !important", // Necessary -- prevents margin collapsing
6838 flexDirection: "column",
6839 flexShrink: 0,
6840 boxSizing: "border-box",
6841 minHeight: "100%",
6842 overflow: "hidden"
6843 },
6844 ".cm-gutterElement": {
6845 boxSizing: "border-box"
6846 },
6847 ".cm-lineNumbers .cm-gutterElement": {
6848 padding: "0 3px 0 5px",
6849 minWidth: "20px",
6850 textAlign: "right",
6851 whiteSpace: "nowrap"
6852 },
6853 "&light .cm-activeLineGutter": {
6854 backgroundColor: "#e2f2ff"
6855 },
6856 "&dark .cm-activeLineGutter": {
6857 backgroundColor: "#222227"
6858 },
6859 ".cm-panels": {
6860 boxSizing: "border-box",
6861 position: "sticky",
6862 left: 0,
6863 right: 0,
6864 zIndex: 300
6865 },
6866 "&light .cm-panels": {
6867 backgroundColor: "#f5f5f5",
6868 color: "black"
6869 },
6870 "&light .cm-panels-top": {
6871 borderBottom: "1px solid #ddd"
6872 },
6873 "&light .cm-panels-bottom": {
6874 borderTop: "1px solid #ddd"
6875 },
6876 "&dark .cm-panels": {
6877 backgroundColor: "#333338",
6878 color: "white"
6879 },
6880 ".cm-dialog": {
6881 padding: "2px 19px 4px 6px",
6882 position: "relative",
6883 "& label": { fontSize: "80%" },
6884 },
6885 ".cm-dialog-close": {
6886 position: "absolute",
6887 top: "3px",
6888 right: "4px",
6889 backgroundColor: "inherit",
6890 border: "none",
6891 font: "inherit",
6892 fontSize: "14px",
6893 padding: "0"
6894 },
6895 ".cm-tab": {
6896 display: "inline-block",
6897 overflow: "hidden",
6898 verticalAlign: "bottom"
6899 },
6900 ".cm-widgetBuffer": {
6901 verticalAlign: "text-top",
6902 height: "1em",
6903 width: 0,
6904 display: "inline"
6905 },
6906 ".cm-placeholder": {
6907 color: "#888",
6908 display: "inline-block",
6909 verticalAlign: "top",
6910 userSelect: "none"
6911 },
6912 ".cm-highlightSpace": {
6913 backgroundImage: "radial-gradient(circle at 50% 55%, #aaa 20%, transparent 5%)",
6914 backgroundPosition: "center",
6915 },
6916 ".cm-highlightTab": {
6917 backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23888" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,
6918 backgroundSize: "auto 100%",
6919 backgroundPosition: "right 90%",
6920 backgroundRepeat: "no-repeat"
6921 },
6922 ".cm-trailingSpace": {
6923 backgroundColor: "#ff332255"
6924 },
6925 ".cm-button": {
6926 verticalAlign: "middle",
6927 color: "inherit",
6928 fontSize: "70%",
6929 padding: ".2em 1em",
6930 borderRadius: "1px"
6931 },
6932 "&light .cm-button": {
6933 backgroundImage: "linear-gradient(#eff1f5, #d9d9df)",
6934 border: "1px solid #888",
6935 "&:active": {
6936 backgroundImage: "linear-gradient(#b4b4b4, #d0d3d6)"
6937 }
6938 },
6939 "&dark .cm-button": {
6940 backgroundImage: "linear-gradient(#393939, #111)",
6941 border: "1px solid #888",
6942 "&:active": {
6943 backgroundImage: "linear-gradient(#111, #333)"
6944 }
6945 },
6946 ".cm-textfield": {
6947 verticalAlign: "middle",
6948 color: "inherit",
6949 fontSize: "70%",
6950 border: "1px solid silver",
6951 padding: ".2em .5em"
6952 },
6953 "&light .cm-textfield": {
6954 backgroundColor: "white"
6955 },
6956 "&dark .cm-textfield": {
6957 border: "1px solid #555",
6958 backgroundColor: "inherit"
6959 }
6960}, lightDarkIDs);
6961
6962const observeOptions = {
6963 childList: true,
6964 characterData: true,
6965 subtree: true,
6966 attributes: true,
6967 characterDataOldValue: true
6968};
6969// IE11 has very broken mutation observers, so we also listen to
6970// DOMCharacterDataModified there
6971const useCharData = browser.ie && browser.ie_version <= 11;
6972class DOMObserver {
6973 constructor(view) {
6974 this.view = view;
6975 this.active = false;
6976 this.editContext = null;
6977 // The known selection. Kept in our own object, as opposed to just
6978 // directly accessing the selection because:
6979 // - Safari doesn't report the right selection in shadow DOM
6980 // - Reading from the selection forces a DOM layout
6981 // - This way, we can ignore selectionchange events if we have
6982 // already seen the 'new' selection
6983 this.selectionRange = new DOMSelectionState;
6984 // Set when a selection change is detected, cleared on flush
6985 this.selectionChanged = false;
6986 this.delayedFlush = -1;
6987 this.resizeTimeout = -1;
6988 this.queue = [];
6989 this.delayedAndroidKey = null;
6990 this.flushingAndroidKey = -1;
6991 this.lastChange = 0;
6992 this.scrollTargets = [];
6993 this.intersection = null;
6994 this.resizeScroll = null;
6995 this.intersecting = false;
6996 this.gapIntersection = null;
6997 this.gaps = [];
6998 this.printQuery = null;
6999 // Timeout for scheduling check of the parents that need scroll handlers
7000 this.parentCheck = -1;
7001 this.dom = view.contentDOM;
7002 this.observer = new MutationObserver(mutations => {
7003 for (let mut of mutations)
7004 this.queue.push(mut);
7005 // IE11 will sometimes (on typing over a selection or
7006 // backspacing out a single character text node) call the
7007 // observer callback before actually updating the DOM.
7008 //
7009 // Unrelatedly, iOS Safari will, when ending a composition,
7010 // sometimes first clear it, deliver the mutations, and then
7011 // reinsert the finished text. CodeMirror's handling of the
7012 // deletion will prevent the reinsertion from happening,
7013 // breaking composition.
7014 if ((browser.ie && browser.ie_version <= 11 || browser.ios && view.composing) &&
7015 mutations.some(m => m.type == "childList" && m.removedNodes.length ||
7016 m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
7017 this.flushSoon();
7018 else
7019 this.flush();
7020 });
7021 if (window.EditContext && browser.android && view.constructor.EDIT_CONTEXT !== false &&
7022 // Chrome <126 doesn't support inverted selections in edit context (#1392)
7023 !(browser.chrome && browser.chrome_version < 126)) {
7024 this.editContext = new EditContextManager(view);
7025 if (view.state.facet(editable))
7026 view.contentDOM.editContext = this.editContext.editContext;
7027 }
7028 if (useCharData)
7029 this.onCharData = (event) => {
7030 this.queue.push({ target: event.target,
7031 type: "characterData",
7032 oldValue: event.prevValue });
7033 this.flushSoon();
7034 };
7035 this.onSelectionChange = this.onSelectionChange.bind(this);
7036 this.onResize = this.onResize.bind(this);
7037 this.onPrint = this.onPrint.bind(this);
7038 this.onScroll = this.onScroll.bind(this);
7039 if (window.matchMedia)
7040 this.printQuery = window.matchMedia("print");
7041 if (typeof ResizeObserver == "function") {
7042 this.resizeScroll = new ResizeObserver(() => {
7043 var _a;
7044 if (((_a = this.view.docView) === null || _a === void 0 ? void 0 : _a.lastUpdate) < Date.now() - 75)
7045 this.onResize();
7046 });
7047 this.resizeScroll.observe(view.scrollDOM);
7048 }
7049 this.addWindowListeners(this.win = view.win);
7050 this.start();
7051 if (typeof IntersectionObserver == "function") {
7052 this.intersection = new IntersectionObserver(entries => {
7053 if (this.parentCheck < 0)
7054 this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
7055 if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
7056 this.intersecting = !this.intersecting;
7057 if (this.intersecting != this.view.inView)
7058 this.onScrollChanged(document.createEvent("Event"));
7059 }
7060 }, { threshold: [0, .001] });
7061 this.intersection.observe(this.dom);
7062 this.gapIntersection = new IntersectionObserver(entries => {
7063 if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
7064 this.onScrollChanged(document.createEvent("Event"));
7065 }, {});
7066 }
7067 this.listenForScroll();
7068 this.readSelectionRange();
7069 }
7070 onScrollChanged(e) {
7071 this.view.inputState.runHandlers("scroll", e);
7072 if (this.intersecting)
7073 this.view.measure();
7074 }
7075 onScroll(e) {
7076 if (this.intersecting)
7077 this.flush(false);
7078 if (this.editContext)
7079 this.view.requestMeasure(this.editContext.measureReq);
7080 this.onScrollChanged(e);
7081 }
7082 onResize() {
7083 if (this.resizeTimeout < 0)
7084 this.resizeTimeout = setTimeout(() => {
7085 this.resizeTimeout = -1;
7086 this.view.requestMeasure();
7087 }, 50);
7088 }
7089 onPrint(event) {
7090 if ((event.type == "change" || !event.type) && !event.matches)
7091 return;
7092 this.view.viewState.printing = true;
7093 this.view.measure();
7094 setTimeout(() => {
7095 this.view.viewState.printing = false;
7096 this.view.requestMeasure();
7097 }, 500);
7098 }
7099 updateGaps(gaps) {
7100 if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
7101 this.gapIntersection.disconnect();
7102 for (let gap of gaps)
7103 this.gapIntersection.observe(gap);
7104 this.gaps = gaps;
7105 }
7106 }
7107 onSelectionChange(event) {
7108 let wasChanged = this.selectionChanged;
7109 if (!this.readSelectionRange() || this.delayedAndroidKey)
7110 return;
7111 let { view } = this, sel = this.selectionRange;
7112 if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(this.dom, sel))
7113 return;
7114 let context = sel.anchorNode && view.docView.tile.nearest(sel.anchorNode);
7115 if (context && context.isWidget() && context.widget.ignoreEvent(event)) {
7116 if (!wasChanged)
7117 this.selectionChanged = false;
7118 return;
7119 }
7120 // Deletions on IE11 fire their events in the wrong order, giving
7121 // us a selection change event before the DOM changes are
7122 // reported.
7123 // Chrome Android has a similar issue when backspacing out a
7124 // selection (#645).
7125 if ((browser.ie && browser.ie_version <= 11 || browser.android && browser.chrome) && !view.state.selection.main.empty &&
7126 // (Selection.isCollapsed isn't reliable on IE)
7127 sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
7128 this.flushSoon();
7129 else
7130 this.flush(false);
7131 }
7132 readSelectionRange() {
7133 let { view } = this;
7134 // The Selection object is broken in shadow roots in Safari. See
7135 // https://github.com/codemirror/dev/issues/414
7136 let selection = getSelection(view.root);
7137 if (!selection)
7138 return false;
7139 let range = browser.safari && view.root.nodeType == 11 &&
7140 view.root.activeElement == this.dom &&
7141 safariSelectionRangeHack(this.view, selection) || selection;
7142 if (!range || this.selectionRange.eq(range))
7143 return false;
7144 let local = hasSelection(this.dom, range);
7145 // Detect the situation where the browser has, on focus, moved the
7146 // selection to the start of the content element. Reset it to the
7147 // position from the editor state.
7148 if (local && !this.selectionChanged &&
7149 view.inputState.lastFocusTime > Date.now() - 200 &&
7150 view.inputState.lastTouchTime < Date.now() - 300 &&
7151 atElementStart(this.dom, range)) {
7152 this.view.inputState.lastFocusTime = 0;
7153 view.docView.updateSelection();
7154 return false;
7155 }
7156 this.selectionRange.setRange(range);
7157 if (local)
7158 this.selectionChanged = true;
7159 return true;
7160 }
7161 setSelectionRange(anchor, head) {
7162 this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
7163 this.selectionChanged = false;
7164 }
7165 clearSelectionRange() {
7166 this.selectionRange.set(null, 0, null, 0);
7167 }
7168 listenForScroll() {
7169 this.parentCheck = -1;
7170 let i = 0, changed = null;
7171 for (let dom = this.dom; dom;) {
7172 if (dom.nodeType == 1) {
7173 if (!changed && i < this.scrollTargets.length && this.scrollTargets[i] == dom)
7174 i++;
7175 else if (!changed)
7176 changed = this.scrollTargets.slice(0, i);
7177 if (changed)
7178 changed.push(dom);
7179 dom = dom.assignedSlot || dom.parentNode;
7180 }
7181 else if (dom.nodeType == 11) { // Shadow root
7182 dom = dom.host;
7183 }
7184 else {
7185 break;
7186 }
7187 }
7188 if (i < this.scrollTargets.length && !changed)
7189 changed = this.scrollTargets.slice(0, i);
7190 if (changed) {
7191 for (let dom of this.scrollTargets)
7192 dom.removeEventListener("scroll", this.onScroll);
7193 for (let dom of this.scrollTargets = changed)
7194 dom.addEventListener("scroll", this.onScroll);
7195 }
7196 }
7197 ignore(f) {
7198 if (!this.active)
7199 return f();
7200 try {
7201 this.stop();
7202 return f();
7203 }
7204 finally {
7205 this.start();
7206 this.clear();
7207 }
7208 }
7209 start() {
7210 if (this.active)
7211 return;
7212 this.observer.observe(this.dom, observeOptions);
7213 if (useCharData)
7214 this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
7215 this.active = true;
7216 }
7217 stop() {
7218 if (!this.active)
7219 return;
7220 this.active = false;
7221 this.observer.disconnect();
7222 if (useCharData)
7223 this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
7224 }
7225 // Throw away any pending changes
7226 clear() {
7227 this.processRecords();
7228 this.queue.length = 0;
7229 this.selectionChanged = false;
7230 }
7231 // Chrome Android, especially in combination with GBoard, not only
7232 // doesn't reliably fire regular key events, but also often
7233 // surrounds the effect of enter or backspace with a bunch of
7234 // composition events that, when interrupted, cause text duplication
7235 // or other kinds of corruption. This hack makes the editor back off
7236 // from handling DOM changes for a moment when such a key is
7237 // detected (via beforeinput or keydown), and then tries to flush
7238 // them or, if that has no effect, dispatches the given key.
7239 delayAndroidKey(key, keyCode) {
7240 var _a;
7241 if (!this.delayedAndroidKey) {
7242 let flush = () => {
7243 let key = this.delayedAndroidKey;
7244 if (key) {
7245 this.clearDelayedAndroidKey();
7246 this.view.inputState.lastKeyCode = key.keyCode;
7247 this.view.inputState.lastKeyTime = Date.now();
7248 let flushed = this.flush();
7249 if (!flushed && key.force)
7250 dispatchKey(this.dom, key.key, key.keyCode);
7251 }
7252 };
7253 this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
7254 }
7255 // Since backspace beforeinput is sometimes signalled spuriously,
7256 // Enter always takes precedence.
7257 if (!this.delayedAndroidKey || key == "Enter")
7258 this.delayedAndroidKey = {
7259 key, keyCode,
7260 // Only run the key handler when no changes are detected if
7261 // this isn't coming right after another change, in which case
7262 // it is probably part of a weird chain of updates, and should
7263 // be ignored if it returns the DOM to its previous state.
7264 force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
7265 };
7266 }
7267 clearDelayedAndroidKey() {
7268 this.win.cancelAnimationFrame(this.flushingAndroidKey);
7269 this.delayedAndroidKey = null;
7270 this.flushingAndroidKey = -1;
7271 }
7272 flushSoon() {
7273 if (this.delayedFlush < 0)
7274 this.delayedFlush = this.view.win.requestAnimationFrame(() => { this.delayedFlush = -1; this.flush(); });
7275 }
7276 forceFlush() {
7277 if (this.delayedFlush >= 0) {
7278 this.view.win.cancelAnimationFrame(this.delayedFlush);
7279 this.delayedFlush = -1;
7280 }
7281 this.flush();
7282 }
7283 pendingRecords() {
7284 for (let mut of this.observer.takeRecords())
7285 this.queue.push(mut);
7286 return this.queue;
7287 }
7288 processRecords() {
7289 let records = this.pendingRecords();
7290 if (records.length)
7291 this.queue = [];
7292 let from = -1, to = -1, typeOver = false;
7293 for (let record of records) {
7294 let range = this.readMutation(record);
7295 if (!range)
7296 continue;
7297 if (range.typeOver)
7298 typeOver = true;
7299 if (from == -1) {
7300 ({ from, to } = range);
7301 }
7302 else {
7303 from = Math.min(range.from, from);
7304 to = Math.max(range.to, to);
7305 }
7306 }
7307 return { from, to, typeOver };
7308 }
7309 readChange() {
7310 let { from, to, typeOver } = this.processRecords();
7311 let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
7312 if (from < 0 && !newSel)
7313 return null;
7314 if (from > -1)
7315 this.lastChange = Date.now();
7316 this.view.inputState.lastFocusTime = 0;
7317 this.selectionChanged = false;
7318 let change = new DOMChange(this.view, from, to, typeOver);
7319 this.view.docView.domChanged = { newSel: change.newSel ? change.newSel.main : null };
7320 return change;
7321 }
7322 // Apply pending changes, if any
7323 flush(readSelection = true) {
7324 // Completely hold off flushing when pending keys are set—the code
7325 // managing those will make sure processRecords is called and the
7326 // view is resynchronized after
7327 if (this.delayedFlush >= 0 || this.delayedAndroidKey)
7328 return false;
7329 if (readSelection)
7330 this.readSelectionRange();
7331 let domChange = this.readChange();
7332 if (!domChange) {
7333 this.view.requestMeasure();
7334 return false;
7335 }
7336 let startState = this.view.state;
7337 let handled = applyDOMChange(this.view, domChange);
7338 // The view wasn't updated but DOM/selection changes were seen. Reset the view.
7339 if (this.view.state == startState &&
7340 (domChange.domChanged || domChange.newSel && !sameSelPos(this.view.state.selection, domChange.newSel.main)))
7341 this.view.update([]);
7342 return handled;
7343 }
7344 readMutation(rec) {
7345 let tile = this.view.docView.tile.nearest(rec.target);
7346 if (!tile || tile.isWidget())
7347 return null;
7348 tile.markDirty(rec.type == "attributes");
7349 if (rec.type == "childList") {
7350 let childBefore = findChild(tile, rec.previousSibling || rec.target.previousSibling, -1);
7351 let childAfter = findChild(tile, rec.nextSibling || rec.target.nextSibling, 1);
7352 return { from: childBefore ? tile.posAfter(childBefore) : tile.posAtStart,
7353 to: childAfter ? tile.posBefore(childAfter) : tile.posAtEnd, typeOver: false };
7354 }
7355 else if (rec.type == "characterData") {
7356 return { from: tile.posAtStart, to: tile.posAtEnd, typeOver: rec.target.nodeValue == rec.oldValue };
7357 }
7358 else {
7359 return null;
7360 }
7361 }
7362 setWindow(win) {
7363 if (win != this.win) {
7364 this.removeWindowListeners(this.win);
7365 this.win = win;
7366 this.addWindowListeners(this.win);
7367 }
7368 }
7369 addWindowListeners(win) {
7370 win.addEventListener("resize", this.onResize);
7371 if (this.printQuery) {
7372 if (this.printQuery.addEventListener)
7373 this.printQuery.addEventListener("change", this.onPrint);
7374 else
7375 this.printQuery.addListener(this.onPrint);
7376 }
7377 else
7378 win.addEventListener("beforeprint", this.onPrint);
7379 win.addEventListener("scroll", this.onScroll);
7380 win.document.addEventListener("selectionchange", this.onSelectionChange);
7381 }
7382 removeWindowListeners(win) {
7383 win.removeEventListener("scroll", this.onScroll);
7384 win.removeEventListener("resize", this.onResize);
7385 if (this.printQuery) {
7386 if (this.printQuery.removeEventListener)
7387 this.printQuery.removeEventListener("change", this.onPrint);
7388 else
7389 this.printQuery.removeListener(this.onPrint);
7390 }
7391 else
7392 win.removeEventListener("beforeprint", this.onPrint);
7393 win.document.removeEventListener("selectionchange", this.onSelectionChange);
7394 }
7395 update(update) {
7396 if (this.editContext) {
7397 this.editContext.update(update);
7398 if (update.startState.facet(editable) != update.state.facet(editable))
7399 update.view.contentDOM.editContext = update.state.facet(editable) ? this.editContext.editContext : null;
7400 }
7401 }
7402 destroy() {
7403 var _a, _b, _c;
7404 this.stop();
7405 (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
7406 (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
7407 (_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
7408 for (let dom of this.scrollTargets)
7409 dom.removeEventListener("scroll", this.onScroll);
7410 this.removeWindowListeners(this.win);
7411 clearTimeout(this.parentCheck);
7412 clearTimeout(this.resizeTimeout);
7413 this.win.cancelAnimationFrame(this.delayedFlush);
7414 this.win.cancelAnimationFrame(this.flushingAndroidKey);
7415 if (this.editContext) {
7416 this.view.contentDOM.editContext = null;
7417 this.editContext.destroy();
7418 }
7419 }
7420}
7421function findChild(tile, dom, dir) {
7422 while (dom) {
7423 let curTile = Tile.get(dom);
7424 if (curTile && curTile.parent == tile)
7425 return curTile;
7426 let parent = dom.parentNode;
7427 dom = parent != tile.dom ? parent : dir > 0 ? dom.nextSibling : dom.previousSibling;
7428 }
7429 return null;
7430}
7431function buildSelectionRangeFromRange(view, range) {
7432 let anchorNode = range.startContainer, anchorOffset = range.startOffset;
7433 let focusNode = range.endContainer, focusOffset = range.endOffset;
7434 let curAnchor = view.docView.domAtPos(view.state.selection.main.anchor, 1);
7435 // Since such a range doesn't distinguish between anchor and head,
7436 // use a heuristic that flips it around if its end matches the
7437 // current anchor.
7438 if (isEquivalentPosition(curAnchor.node, curAnchor.offset, focusNode, focusOffset))
7439 [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
7440 return { anchorNode, anchorOffset, focusNode, focusOffset };
7441}
7442// Used to work around a Safari Selection/shadow DOM bug (#414)
7443function safariSelectionRangeHack(view, selection) {
7444 if (selection.getComposedRanges) {
7445 let range = selection.getComposedRanges(view.root)[0];
7446 if (range)
7447 return buildSelectionRangeFromRange(view, range);
7448 }
7449 let found = null;
7450 // Because Safari (at least in 2018-2021) doesn't provide regular
7451 // access to the selection inside a shadowroot, we have to perform a
7452 // ridiculous hack to get at it—using `execCommand` to trigger a
7453 // `beforeInput` event so that we can read the target range from the
7454 // event.
7455 function read(event) {
7456 event.preventDefault();
7457 event.stopImmediatePropagation();
7458 found = event.getTargetRanges()[0];
7459 }
7460 view.contentDOM.addEventListener("beforeinput", read, true);
7461 view.dom.ownerDocument.execCommand("indent");
7462 view.contentDOM.removeEventListener("beforeinput", read, true);
7463 return found ? buildSelectionRangeFromRange(view, found) : null;
7464}
7465class EditContextManager {
7466 constructor(view) {
7467 // The document window for which the text in the context is
7468 // maintained. For large documents, this may be smaller than the
7469 // editor document. This window always includes the selection head.
7470 this.from = 0;
7471 this.to = 0;
7472 // When applying a transaction, this is used to compare the change
7473 // made to the context content to the change in the transaction in
7474 // order to make the minimal changes to the context (since touching
7475 // that sometimes breaks series of multiple edits made for a single
7476 // user action on some Android keyboards)
7477 this.pendingContextChange = null;
7478 this.handlers = Object.create(null);
7479 // Kludge to work around the fact that EditContext does not respond
7480 // well to having its content updated during a composition (see #1472)
7481 this.composing = null;
7482 this.resetRange(view.state);
7483 let context = this.editContext = new window.EditContext({
7484 text: view.state.doc.sliceString(this.from, this.to),
7485 selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
7486 selectionEnd: this.toContextPos(view.state.selection.main.head)
7487 });
7488 this.handlers.textupdate = e => {
7489 let main = view.state.selection.main, { anchor, head } = main;
7490 let from = this.toEditorPos(e.updateRangeStart), to = this.toEditorPos(e.updateRangeEnd);
7491 if (view.inputState.composing >= 0 && !this.composing)
7492 this.composing = { contextBase: e.updateRangeStart, editorBase: from, drifted: false };
7493 let deletes = to - from > e.text.length;
7494 // If the window doesn't include the anchor, assume changes
7495 // adjacent to a side go up to the anchor.
7496 if (from == this.from && anchor < this.from)
7497 from = anchor;
7498 else if (to == this.to && anchor > this.to)
7499 to = anchor;
7500 let diff = findDiff(view.state.sliceDoc(from, to), e.text, (deletes ? main.from : main.to) - from, deletes ? "end" : null);
7501 // Edit contexts sometimes fire empty changes
7502 if (!diff) {
7503 let newSel = EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
7504 if (!sameSelPos(newSel, main))
7505 view.dispatch({ selection: newSel, userEvent: "select" });
7506 return;
7507 }
7508 let change = { from: diff.from + from, to: diff.toA + from,
7509 insert: Text.of(e.text.slice(diff.from, diff.toB).split("\n")) };
7510 if ((browser.mac || browser.android) && change.from == head - 1 &&
7511 /^\. ?$/.test(e.text) && view.contentDOM.getAttribute("autocorrect") == "off")
7512 change = { from, to, insert: Text.of([e.text.replace(".", " ")]) };
7513 this.pendingContextChange = change;
7514 if (!view.state.readOnly) {
7515 let newLen = this.to - this.from + (change.to - change.from + change.insert.length);
7516 applyDOMChangeInner(view, change, EditorSelection.single(this.toEditorPos(e.selectionStart, newLen), this.toEditorPos(e.selectionEnd, newLen)));
7517 }
7518 // If the transaction didn't flush our change, revert it so
7519 // that the context is in sync with the editor state again.
7520 if (this.pendingContextChange) {
7521 this.revertPending(view.state);
7522 this.setSelection(view.state);
7523 }
7524 // Work around missed compositionend events. See https://discuss.codemirror.net/t/a/9514
7525 if (change.from < change.to && !change.insert.length && view.inputState.composing >= 0 &&
7526 !/[\\p{Alphabetic}\\p{Number}_]/.test(context.text.slice(Math.max(0, e.updateRangeStart - 1), Math.min(context.text.length, e.updateRangeStart + 1))))
7527 this.handlers.compositionend(e);
7528 };
7529 this.handlers.characterboundsupdate = e => {
7530 let rects = [], prev = null;
7531 for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
7532 let rect = view.coordsForChar(i);
7533 prev = (rect && new DOMRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top))
7534 || prev || new DOMRect;
7535 rects.push(prev);
7536 }
7537 context.updateCharacterBounds(e.rangeStart, rects);
7538 };
7539 this.handlers.textformatupdate = e => {
7540 let deco = [];
7541 for (let format of e.getTextFormats()) {
7542 let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
7543 if (!/none/i.test(lineStyle) && !/none/i.test(thickness)) {
7544 let from = this.toEditorPos(format.rangeStart), to = this.toEditorPos(format.rangeEnd);
7545 if (from < to) {
7546 // These values changed from capitalized custom strings to lower-case CSS keywords in 2025
7547 let style = `text-decoration: underline ${/^[a-z]/.test(lineStyle) ? lineStyle + " " : lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${/thin/i.test(thickness) ? 1 : 2}px`;
7548 deco.push(Decoration.mark({ attributes: { style } }).range(from, to));
7549 }
7550 }
7551 }
7552 view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
7553 };
7554 this.handlers.compositionstart = () => {
7555 if (view.inputState.composing < 0) {
7556 view.inputState.composing = 0;
7557 view.inputState.compositionFirstChange = true;
7558 }
7559 };
7560 this.handlers.compositionend = () => {
7561 view.inputState.composing = -1;
7562 view.inputState.compositionFirstChange = null;
7563 if (this.composing) {
7564 let { drifted } = this.composing;
7565 this.composing = null;
7566 if (drifted)
7567 this.reset(view.state);
7568 }
7569 };
7570 for (let event in this.handlers)
7571 context.addEventListener(event, this.handlers[event]);
7572 this.measureReq = { read: view => {
7573 this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
7574 let sel = getSelection(view.root);
7575 if (sel && sel.rangeCount)
7576 this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
7577 } };
7578 }
7579 applyEdits(update) {
7580 let off = 0, abort = false, pending = this.pendingContextChange;
7581 update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
7582 if (abort)
7583 return;
7584 let dLen = insert.length - (toA - fromA);
7585 if (pending && toA >= pending.to) {
7586 if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
7587 pending = this.pendingContextChange = null; // Match
7588 off += dLen;
7589 this.to += dLen;
7590 return;
7591 }
7592 else { // Mismatch, revert
7593 pending = null;
7594 this.revertPending(update.state);
7595 }
7596 }
7597 fromA += off;
7598 toA += off;
7599 if (toA <= this.from) { // Before the window
7600 this.from += dLen;
7601 this.to += dLen;
7602 }
7603 else if (fromA < this.to) { // Overlaps with window
7604 if (fromA < this.from || toA > this.to || (this.to - this.from) + insert.length > 30000 /* CxVp.MaxSize */) {
7605 abort = true;
7606 return;
7607 }
7608 this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
7609 this.to += dLen;
7610 }
7611 off += dLen;
7612 });
7613 if (pending && !abort)
7614 this.revertPending(update.state);
7615 return !abort;
7616 }
7617 update(update) {
7618 let reverted = this.pendingContextChange, startSel = update.startState.selection.main;
7619 if (this.composing &&
7620 (this.composing.drifted ||
7621 (!update.changes.touchesRange(startSel.from, startSel.to) &&
7622 update.transactions.some(tr => !tr.isUserEvent("input.type") && tr.changes.touchesRange(this.from, this.to))))) {
7623 this.composing.drifted = true;
7624 this.composing.editorBase = update.changes.mapPos(this.composing.editorBase);
7625 }
7626 else if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
7627 this.pendingContextChange = null;
7628 this.reset(update.state);
7629 }
7630 else if (update.docChanged || update.selectionSet || reverted) {
7631 this.setSelection(update.state);
7632 }
7633 if (update.geometryChanged || update.docChanged || update.selectionSet)
7634 update.view.requestMeasure(this.measureReq);
7635 }
7636 resetRange(state) {
7637 let { head } = state.selection.main;
7638 this.from = Math.max(0, head - 10000 /* CxVp.Margin */);
7639 this.to = Math.min(state.doc.length, head + 10000 /* CxVp.Margin */);
7640 }
7641 reset(state) {
7642 this.resetRange(state);
7643 this.editContext.updateText(0, this.editContext.text.length, state.doc.sliceString(this.from, this.to));
7644 this.setSelection(state);
7645 }
7646 revertPending(state) {
7647 let pending = this.pendingContextChange;
7648 this.pendingContextChange = null;
7649 this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.from + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
7650 }
7651 setSelection(state) {
7652 let { main } = state.selection;
7653 let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
7654 let end = this.toContextPos(main.head);
7655 if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
7656 this.editContext.updateSelection(start, end);
7657 }
7658 rangeIsValid(state) {
7659 let { head } = state.selection.main;
7660 return !(this.from > 0 && head - this.from < 500 /* CxVp.MinMargin */ ||
7661 this.to < state.doc.length && this.to - head < 500 /* CxVp.MinMargin */ ||
7662 this.to - this.from > 10000 /* CxVp.Margin */ * 3);
7663 }
7664 toEditorPos(contextPos, clipLen = this.to - this.from) {
7665 contextPos = Math.min(contextPos, clipLen);
7666 let c = this.composing;
7667 return c && c.drifted ? c.editorBase + (contextPos - c.contextBase) : contextPos + this.from;
7668 }
7669 toContextPos(editorPos) {
7670 let c = this.composing;
7671 return c && c.drifted ? c.contextBase + (editorPos - c.editorBase) : editorPos - this.from;
7672 }
7673 destroy() {
7674 for (let event in this.handlers)
7675 this.editContext.removeEventListener(event, this.handlers[event]);
7676 }
7677}
7678
7679// The editor's update state machine looks something like this:
7680//
7681// Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
7682// ↑ ↓
7683// Updating (measure)
7684//
7685// The difference between 'Idle' and 'Idle (unchecked)' lies in
7686// whether a layout check has been scheduled. A regular update through
7687// the `update` method updates the DOM in a write-only fashion, and
7688// relies on a check (scheduled with `requestAnimationFrame`) to make
7689// sure everything is where it should be and the viewport covers the
7690// visible code. That check continues to measure and then optionally
7691// update until it reaches a coherent state.
7692/**
7693An editor view represents the editor's user interface. It holds
7694the editable DOM surface, and possibly other elements such as the
7695line number gutter. It handles events and dispatches state
7696transactions for editing actions.
7697*/
7698class EditorView {
7699 /**
7700 The current editor state.
7701 */
7702 get state() { return this.viewState.state; }
7703 /**
7704 To be able to display large documents without consuming too much
7705 memory or overloading the browser, CodeMirror only draws the
7706 code that is visible (plus a margin around it) to the DOM. This
7707 property tells you the extent of the current drawn viewport, in
7708 document positions.
7709 */
7710 get viewport() { return this.viewState.viewport; }
7711 /**
7712 When there are, for example, large collapsed ranges in the
7713 viewport, its size can be a lot bigger than the actual visible
7714 content. Thus, if you are doing something like styling the
7715 content in the viewport, it is preferable to only do so for
7716 these ranges, which are the subset of the viewport that is
7717 actually drawn.
7718 */
7719 get visibleRanges() { return this.viewState.visibleRanges; }
7720 /**
7721 Returns false when the editor is entirely scrolled out of view
7722 or otherwise hidden.
7723 */
7724 get inView() { return this.viewState.inView; }
7725 /**
7726 Indicates whether the user is currently composing text via
7727 [IME](https://en.wikipedia.org/wiki/Input_method), and at least
7728 one change has been made in the current composition.
7729 */
7730 get composing() { return !!this.inputState && this.inputState.composing > 0; }
7731 /**
7732 Indicates whether the user is currently in composing state. Note
7733 that on some platforms, like Android, this will be the case a
7734 lot, since just putting the cursor on a word starts a
7735 composition there.
7736 */
7737 get compositionStarted() { return !!this.inputState && this.inputState.composing >= 0; }
7738 /**
7739 The document or shadow root that the view lives in.
7740 */
7741 get root() { return this._root; }
7742 /**
7743 @internal
7744 */
7745 get win() { return this.dom.ownerDocument.defaultView || window; }
7746 /**
7747 Construct a new view. You'll want to either provide a `parent`
7748 option, or put `view.dom` into your document after creating a
7749 view, so that the user can see the editor.
7750 */
7751 constructor(config = {}) {
7752 var _a;
7753 this.plugins = [];
7754 this.pluginMap = new Map;
7755 this.editorAttrs = {};
7756 this.contentAttrs = {};
7757 this.bidiCache = [];
7758 this.destroyed = false;
7759 /**
7760 @internal
7761 */
7762 this.updateState = 2 /* UpdateState.Updating */;
7763 /**
7764 @internal
7765 */
7766 this.measureScheduled = -1;
7767 /**
7768 @internal
7769 */
7770 this.measureRequests = [];
7771 this.contentDOM = document.createElement("div");
7772 this.scrollDOM = document.createElement("div");
7773 this.scrollDOM.tabIndex = -1;
7774 this.scrollDOM.className = "cm-scroller";
7775 this.scrollDOM.appendChild(this.contentDOM);
7776 this.announceDOM = document.createElement("div");
7777 this.announceDOM.className = "cm-announced";
7778 this.announceDOM.setAttribute("aria-live", "polite");
7779 this.dom = document.createElement("div");
7780 this.dom.appendChild(this.announceDOM);
7781 this.dom.appendChild(this.scrollDOM);
7782 if (config.parent)
7783 config.parent.appendChild(this.dom);
7784 let { dispatch } = config;
7785 this.dispatchTransactions = config.dispatchTransactions ||
7786 (dispatch && ((trs) => trs.forEach(tr => dispatch(tr, this)))) ||
7787 ((trs) => this.update(trs));
7788 this.dispatch = this.dispatch.bind(this);
7789 this._root = (config.root || getRoot(config.parent) || document);
7790 this.viewState = new ViewState(this, config.state || EditorState.create(config));
7791 if (config.scrollTo && config.scrollTo.is(scrollIntoView))
7792 this.viewState.scrollTarget = config.scrollTo.value.clip(this.viewState.state);
7793 this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
7794 for (let plugin of this.plugins)
7795 plugin.update(this);
7796 this.observer = new DOMObserver(this);
7797 this.inputState = new InputState(this);
7798 this.inputState.ensureHandlers(this.plugins);
7799 this.docView = new DocView(this);
7800 this.mountStyles();
7801 this.updateAttrs();
7802 this.updateState = 0 /* UpdateState.Idle */;
7803 this.requestMeasure();
7804 if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
7805 document.fonts.ready.then(() => {
7806 this.viewState.mustMeasureContent = "refresh";
7807 this.requestMeasure();
7808 });
7809 }
7810 dispatch(...input) {
7811 let trs = input.length == 1 && input[0] instanceof Transaction ? input
7812 : input.length == 1 && Array.isArray(input[0]) ? input[0]
7813 : [this.state.update(...input)];
7814 this.dispatchTransactions(trs, this);
7815 }
7816 /**
7817 Update the view for the given array of transactions. This will
7818 update the visible document and selection to match the state
7819 produced by the transactions, and notify view plugins of the
7820 change. You should usually call
7821 [`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead, which uses this
7822 as a primitive.
7823 */
7824 update(transactions) {
7825 if (this.updateState != 0 /* UpdateState.Idle */)
7826 throw new Error("Calls to EditorView.update are not allowed while an update is in progress");
7827 let redrawn = false, attrsChanged = false, update;
7828 let state = this.state;
7829 for (let tr of transactions) {
7830 if (tr.startState != state)
7831 throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
7832 state = tr.state;
7833 }
7834 if (this.destroyed) {
7835 this.viewState.state = state;
7836 return;
7837 }
7838 let focus = this.hasFocus, focusFlag = 0, dispatchFocus = null;
7839 if (transactions.some(tr => tr.annotation(isFocusChange))) {
7840 this.inputState.notifiedFocused = focus;
7841 // If a focus-change transaction is being dispatched, set this update flag.
7842 focusFlag = 1 /* UpdateFlag.Focus */;
7843 }
7844 else if (focus != this.inputState.notifiedFocused) {
7845 this.inputState.notifiedFocused = focus;
7846 // Schedule a separate focus transaction if necessary, otherwise
7847 // add a flag to this update
7848 dispatchFocus = focusChangeTransaction(state, focus);
7849 if (!dispatchFocus)
7850 focusFlag = 1 /* UpdateFlag.Focus */;
7851 }
7852 // If there was a pending DOM change, eagerly read it and try to
7853 // apply it after the given transactions.
7854 let pendingKey = this.observer.delayedAndroidKey, domChange = null;
7855 if (pendingKey) {
7856 this.observer.clearDelayedAndroidKey();
7857 domChange = this.observer.readChange();
7858 // Only try to apply DOM changes if the transactions didn't
7859 // change the doc or selection.
7860 if (domChange && !this.state.doc.eq(state.doc) || !this.state.selection.eq(state.selection))
7861 domChange = null;
7862 }
7863 else {
7864 this.observer.clear();
7865 }
7866 // When the phrases change, redraw the editor
7867 if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
7868 return this.setState(state);
7869 update = ViewUpdate.create(this, state, transactions);
7870 update.flags |= focusFlag;
7871 let scrollTarget = this.viewState.scrollTarget;
7872 try {
7873 this.updateState = 2 /* UpdateState.Updating */;
7874 for (let tr of transactions) {
7875 if (scrollTarget)
7876 scrollTarget = scrollTarget.map(tr.changes);
7877 if (tr.scrollIntoView) {
7878 let { main } = tr.state.selection;
7879 scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
7880 }
7881 for (let e of tr.effects)
7882 if (e.is(scrollIntoView))
7883 scrollTarget = e.value.clip(this.state);
7884 }
7885 this.viewState.update(update, scrollTarget);
7886 this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
7887 if (!update.empty) {
7888 this.updatePlugins(update);
7889 this.inputState.update(update);
7890 }
7891 redrawn = this.docView.update(update);
7892 if (this.state.facet(styleModule) != this.styleModules)
7893 this.mountStyles();
7894 attrsChanged = this.updateAttrs();
7895 this.showAnnouncements(transactions);
7896 this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
7897 }
7898 finally {
7899 this.updateState = 0 /* UpdateState.Idle */;
7900 }
7901 if (update.startState.facet(theme) != update.state.facet(theme))
7902 this.viewState.mustMeasureContent = true;
7903 if (redrawn || attrsChanged || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
7904 this.requestMeasure();
7905 if (redrawn)
7906 this.docViewUpdate();
7907 if (!update.empty)
7908 for (let listener of this.state.facet(updateListener)) {
7909 try {
7910 listener(update);
7911 }
7912 catch (e) {
7913 logException(this.state, e, "update listener");
7914 }
7915 }
7916 if (dispatchFocus || domChange)
7917 Promise.resolve().then(() => {
7918 if (dispatchFocus && this.state == dispatchFocus.startState)
7919 this.dispatch(dispatchFocus);
7920 if (domChange) {
7921 if (!applyDOMChange(this, domChange) && pendingKey.force)
7922 dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
7923 }
7924 });
7925 }
7926 /**
7927 Reset the view to the given state. (This will cause the entire
7928 document to be redrawn and all view plugins to be reinitialized,
7929 so you should probably only use it when the new state isn't
7930 derived from the old state. Otherwise, use
7931 [`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead.)
7932 */
7933 setState(newState) {
7934 if (this.updateState != 0 /* UpdateState.Idle */)
7935 throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
7936 if (this.destroyed) {
7937 this.viewState.state = newState;
7938 return;
7939 }
7940 this.updateState = 2 /* UpdateState.Updating */;
7941 let hadFocus = this.hasFocus;
7942 try {
7943 for (let plugin of this.plugins)
7944 plugin.destroy(this);
7945 this.viewState = new ViewState(this, newState);
7946 this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec));
7947 this.pluginMap.clear();
7948 for (let plugin of this.plugins)
7949 plugin.update(this);
7950 this.docView.destroy();
7951 this.docView = new DocView(this);
7952 this.inputState.ensureHandlers(this.plugins);
7953 this.mountStyles();
7954 this.updateAttrs();
7955 this.bidiCache = [];
7956 }
7957 finally {
7958 this.updateState = 0 /* UpdateState.Idle */;
7959 }
7960 if (hadFocus)
7961 this.focus();
7962 this.requestMeasure();
7963 }
7964 updatePlugins(update) {
7965 let prevSpecs = update.startState.facet(viewPlugin), specs = update.state.facet(viewPlugin);
7966 if (prevSpecs != specs) {
7967 let newPlugins = [];
7968 for (let spec of specs) {
7969 let found = prevSpecs.indexOf(spec);
7970 if (found < 0) {
7971 newPlugins.push(new PluginInstance(spec));
7972 }
7973 else {
7974 let plugin = this.plugins[found];
7975 plugin.mustUpdate = update;
7976 newPlugins.push(plugin);
7977 }
7978 }
7979 for (let plugin of this.plugins)
7980 if (plugin.mustUpdate != update)
7981 plugin.destroy(this);
7982 this.plugins = newPlugins;
7983 this.pluginMap.clear();
7984 }
7985 else {
7986 for (let p of this.plugins)
7987 p.mustUpdate = update;
7988 }
7989 for (let i = 0; i < this.plugins.length; i++)
7990 this.plugins[i].update(this);
7991 if (prevSpecs != specs)
7992 this.inputState.ensureHandlers(this.plugins);
7993 }
7994 docViewUpdate() {
7995 for (let plugin of this.plugins) {
7996 let val = plugin.value;
7997 if (val && val.docViewUpdate) {
7998 try {
7999 val.docViewUpdate(this);
8000 }
8001 catch (e) {
8002 logException(this.state, e, "doc view update listener");
8003 }
8004 }
8005 }
8006 }
8007 /**
8008 @internal
8009 */
8010 measure(flush = true) {
8011 if (this.destroyed)
8012 return;
8013 if (this.measureScheduled > -1)
8014 this.win.cancelAnimationFrame(this.measureScheduled);
8015 if (this.observer.delayedAndroidKey) {
8016 this.measureScheduled = -1;
8017 this.requestMeasure();
8018 return;
8019 }
8020 this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
8021 if (flush)
8022 this.observer.forceFlush();
8023 let updated = null;
8024 let scroll = this.viewState.scrollParent, scrollOffset = this.viewState.getScrollOffset();
8025 let { scrollAnchorPos, scrollAnchorHeight } = this.viewState;
8026 if (Math.abs(scrollOffset - this.viewState.scrollOffset) > 1)
8027 scrollAnchorHeight = -1;
8028 this.viewState.scrollAnchorHeight = -1;
8029 try {
8030 for (let i = 0;; i++) {
8031 if (scrollAnchorHeight < 0) {
8032 if (isScrolledToBottom(scroll || this.win)) {
8033 scrollAnchorPos = -1;
8034 scrollAnchorHeight = this.viewState.heightMap.height;
8035 }
8036 else {
8037 let block = this.viewState.scrollAnchorAt(scrollOffset);
8038 scrollAnchorPos = block.from;
8039 scrollAnchorHeight = block.top;
8040 }
8041 }
8042 this.updateState = 1 /* UpdateState.Measuring */;
8043 let changed = this.viewState.measure();
8044 if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
8045 break;
8046 if (i > 5) {
8047 console.warn(this.measureRequests.length
8048 ? "Measure loop restarted more than 5 times"
8049 : "Viewport failed to stabilize");
8050 break;
8051 }
8052 let measuring = [];
8053 // Only run measure requests in this cycle when the viewport didn't change
8054 if (!(changed & 4 /* UpdateFlag.Viewport */))
8055 [this.measureRequests, measuring] = [measuring, this.measureRequests];
8056 let measured = measuring.map(m => {
8057 try {
8058 return m.read(this);
8059 }
8060 catch (e) {
8061 logException(this.state, e);
8062 return BadMeasure;
8063 }
8064 });
8065 let update = ViewUpdate.create(this, this.state, []), redrawn = false;
8066 update.flags |= changed;
8067 if (!updated)
8068 updated = update;
8069 else
8070 updated.flags |= changed;
8071 this.updateState = 2 /* UpdateState.Updating */;
8072 if (!update.empty) {
8073 this.updatePlugins(update);
8074 this.inputState.update(update);
8075 this.updateAttrs();
8076 redrawn = this.docView.update(update);
8077 if (redrawn)
8078 this.docViewUpdate();
8079 }
8080 for (let i = 0; i < measuring.length; i++)
8081 if (measured[i] != BadMeasure) {
8082 try {
8083 let m = measuring[i];
8084 if (m.write)
8085 m.write(measured[i], this);
8086 }
8087 catch (e) {
8088 logException(this.state, e);
8089 }
8090 }
8091 if (redrawn)
8092 this.docView.updateSelection(true);
8093 if (!update.viewportChanged && this.measureRequests.length == 0) {
8094 if (this.viewState.editorHeight) {
8095 if (this.viewState.scrollTarget) {
8096 this.docView.scrollIntoView(this.viewState.scrollTarget);
8097 this.viewState.scrollTarget = null;
8098 scrollAnchorHeight = -1;
8099 continue;
8100 }
8101 else {
8102 let newAnchorHeight = scrollAnchorPos < 0 ? this.viewState.heightMap.height :
8103 this.viewState.lineBlockAt(scrollAnchorPos).top;
8104 let diff = (newAnchorHeight - scrollAnchorHeight) / this.scaleY;
8105 if ((diff > 1 || diff < -1) &&
8106 (scroll == this.scrollDOM || this.hasFocus ||
8107 Math.max(this.inputState.lastWheelEvent, this.inputState.lastTouchTime) > Date.now() - 100)) {
8108 scrollOffset = scrollOffset + diff;
8109 if (scroll)
8110 scroll.scrollTop += diff;
8111 else
8112 this.win.scrollBy(0, diff);
8113 scrollAnchorHeight = -1;
8114 continue;
8115 }
8116 }
8117 }
8118 break;
8119 }
8120 }
8121 }
8122 finally {
8123 this.updateState = 0 /* UpdateState.Idle */;
8124 this.measureScheduled = -1;
8125 }
8126 if (updated && !updated.empty)
8127 for (let listener of this.state.facet(updateListener))
8128 listener(updated);
8129 }
8130 /**
8131 Get the CSS classes for the currently active editor themes.
8132 */
8133 get themeClasses() {
8134 return baseThemeID + " " +
8135 (this.state.facet(darkTheme) ? baseDarkID : baseLightID) + " " +
8136 this.state.facet(theme);
8137 }
8138 updateAttrs() {
8139 let editorAttrs = attrsFromFacet(this, editorAttributes, {
8140 class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
8141 });
8142 let contentAttrs = {
8143 spellcheck: "false",
8144 autocorrect: "off",
8145 autocapitalize: "off",
8146 writingsuggestions: "false",
8147 translate: "no",
8148 contenteditable: !this.state.facet(editable) ? "false" : "true",
8149 class: "cm-content",
8150 style: `${browser.tabSize}: ${this.state.tabSize}`,
8151 role: "textbox",
8152 "aria-multiline": "true"
8153 };
8154 if (this.state.readOnly)
8155 contentAttrs["aria-readonly"] = "true";
8156 attrsFromFacet(this, contentAttributes, contentAttrs);
8157 let changed = this.observer.ignore(() => {
8158 let changedContent = updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
8159 let changedEditor = updateAttrs(this.dom, this.editorAttrs, editorAttrs);
8160 return changedContent || changedEditor;
8161 });
8162 this.editorAttrs = editorAttrs;
8163 this.contentAttrs = contentAttrs;
8164 return changed;
8165 }
8166 showAnnouncements(trs) {
8167 let first = true;
8168 for (let tr of trs)
8169 for (let effect of tr.effects)
8170 if (effect.is(EditorView.announce)) {
8171 if (first)
8172 this.announceDOM.textContent = "";
8173 first = false;
8174 let div = this.announceDOM.appendChild(document.createElement("div"));
8175 div.textContent = effect.value;
8176 }
8177 }
8178 mountStyles() {
8179 this.styleModules = this.state.facet(styleModule);
8180 let nonce = this.state.facet(EditorView.cspNonce);
8181 StyleModule.mount(this.root, this.styleModules.concat(baseTheme$1).reverse(), nonce ? { nonce } : undefined);
8182 }
8183 readMeasured() {
8184 if (this.updateState == 2 /* UpdateState.Updating */)
8185 throw new Error("Reading the editor layout isn't allowed during an update");
8186 if (this.updateState == 0 /* UpdateState.Idle */ && this.measureScheduled > -1)
8187 this.measure(false);
8188 }
8189 /**
8190 Schedule a layout measurement, optionally providing callbacks to
8191 do custom DOM measuring followed by a DOM write phase. Using
8192 this is preferable reading DOM layout directly from, for
8193 example, an event handler, because it'll make sure measuring and
8194 drawing done by other components is synchronized, avoiding
8195 unnecessary DOM layout computations.
8196 */
8197 requestMeasure(request) {
8198 if (this.measureScheduled < 0)
8199 this.measureScheduled = this.win.requestAnimationFrame(() => this.measure());
8200 if (request) {
8201 if (this.measureRequests.indexOf(request) > -1)
8202 return;
8203 if (request.key != null)
8204 for (let i = 0; i < this.measureRequests.length; i++) {
8205 if (this.measureRequests[i].key === request.key) {
8206 this.measureRequests[i] = request;
8207 return;
8208 }
8209 }
8210 this.measureRequests.push(request);
8211 }
8212 }
8213 /**
8214 Get the value of a specific plugin, if present. Note that
8215 plugins that crash can be dropped from a view, so even when you
8216 know you registered a given plugin, it is recommended to check
8217 the return value of this method.
8218 */
8219 plugin(plugin) {
8220 let known = this.pluginMap.get(plugin);
8221 if (known === undefined || known && known.plugin != plugin)
8222 this.pluginMap.set(plugin, known = this.plugins.find(p => p.plugin == plugin) || null);
8223 return known && known.update(this).value;
8224 }
8225 /**
8226 The top position of the document, in screen coordinates. This
8227 may be negative when the editor is scrolled down. Points
8228 directly to the top of the first line, not above the padding.
8229 */
8230 get documentTop() {
8231 return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
8232 }
8233 /**
8234 Reports the padding above and below the document.
8235 */
8236 get documentPadding() {
8237 return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
8238 }
8239 /**
8240 If the editor is transformed with CSS, this provides the scale
8241 along the X axis. Otherwise, it will just be 1. Note that
8242 transforms other than translation and scaling are not supported.
8243 */
8244 get scaleX() { return this.viewState.scaleX; }
8245 /**
8246 Provide the CSS transformed scale along the Y axis.
8247 */
8248 get scaleY() { return this.viewState.scaleY; }
8249 /**
8250 Find the text line or block widget at the given vertical
8251 position (which is interpreted as relative to the [top of the
8252 document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)).
8253 */
8254 elementAtHeight(height) {
8255 this.readMeasured();
8256 return this.viewState.elementAtHeight(height);
8257 }
8258 /**
8259 Find the line block (see
8260 [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt)) at the given
8261 height, again interpreted relative to the [top of the
8262 document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop).
8263 */
8264 lineBlockAtHeight(height) {
8265 this.readMeasured();
8266 return this.viewState.lineBlockAtHeight(height);
8267 }
8268 /**
8269 Get the extent and vertical position of all [line
8270 blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
8271 are relative to the [top of the
8272 document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
8273 */
8274 get viewportLineBlocks() {
8275 return this.viewState.viewportLines;
8276 }
8277 /**
8278 Find the line block around the given document position. A line
8279 block is a range delimited on both sides by either a
8280 non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^replace) line break, or the
8281 start/end of the document. It will usually just hold a line of
8282 text, but may be broken into multiple textblocks by block
8283 widgets.
8284 */
8285 lineBlockAt(pos) {
8286 return this.viewState.lineBlockAt(pos);
8287 }
8288 /**
8289 The editor's total content height.
8290 */
8291 get contentHeight() {
8292 return this.viewState.contentHeight;
8293 }
8294 /**
8295 Move a cursor position by [grapheme
8296 cluster](https://codemirror.net/6/docs/ref/#state.findClusterBreak). `forward` determines whether
8297 the motion is away from the line start, or towards it. In
8298 bidirectional text, the line is traversed in visual order, using
8299 the editor's [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
8300 When the start position was the last one on the line, the
8301 returned position will be across the line break. If there is no
8302 further line, the original position is returned.
8303
8304 By default, this method moves over a single cluster. The
8305 optional `by` argument can be used to move across more. It will
8306 be called with the first cluster as argument, and should return
8307 a predicate that determines, for each subsequent cluster,
8308 whether it should also be moved over.
8309 */
8310 moveByChar(start, forward, by) {
8311 return skipAtoms(this, start, moveByChar(this, start, forward, by));
8312 }
8313 /**
8314 Move a cursor position across the next group of either
8315 [letters](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) or non-letter
8316 non-whitespace characters.
8317 */
8318 moveByGroup(start, forward) {
8319 return skipAtoms(this, start, moveByChar(this, start, forward, initial => byGroup(this, start.head, initial)));
8320 }
8321 /**
8322 Get the cursor position visually at the start or end of a line.
8323 Note that this may differ from the _logical_ position at its
8324 start or end (which is simply at `line.from`/`line.to`) if text
8325 at the start or end goes against the line's base text direction.
8326 */
8327 visualLineSide(line, end) {
8328 let order = this.bidiSpans(line), dir = this.textDirectionAt(line.from);
8329 let span = order[end ? order.length - 1 : 0];
8330 return EditorSelection.cursor(span.side(end, dir) + line.from, span.forward(!end, dir) ? 1 : -1);
8331 }
8332 /**
8333 Move to the next line boundary in the given direction. If
8334 `includeWrap` is true, line wrapping is on, and there is a
8335 further wrap point on the current line, the wrap point will be
8336 returned. Otherwise this function will return the start or end
8337 of the line.
8338 */
8339 moveToLineBoundary(start, forward, includeWrap = true) {
8340 return moveToLineBoundary(this, start, forward, includeWrap);
8341 }
8342 /**
8343 Move a cursor position vertically. When `distance` isn't given,
8344 it defaults to moving to the next line (including wrapped
8345 lines). Otherwise, `distance` should provide a positive distance
8346 in pixels.
8347
8348 When `start` has a
8349 [`goalColumn`](https://codemirror.net/6/docs/ref/#state.SelectionRange.goalColumn), the vertical
8350 motion will use that as a target horizontal position. Otherwise,
8351 the cursor's own horizontal position is used. The returned
8352 cursor will have its goal column set to whichever column was
8353 used.
8354 */
8355 moveVertically(start, forward, distance) {
8356 return skipAtoms(this, start, moveVertically(this, start, forward, distance));
8357 }
8358 /**
8359 Find the DOM parent node and offset (child offset if `node` is
8360 an element, character offset when it is a text node) at the
8361 given document position.
8362
8363 Note that for positions that aren't currently in
8364 `visibleRanges`, the resulting DOM position isn't necessarily
8365 meaningful (it may just point before or after a placeholder
8366 element).
8367 */
8368 domAtPos(pos, side = 1) {
8369 return this.docView.domAtPos(pos, side);
8370 }
8371 /**
8372 Find the document position at the given DOM node. Can be useful
8373 for associating positions with DOM events. Will raise an error
8374 when `node` isn't part of the editor content.
8375 */
8376 posAtDOM(node, offset = 0) {
8377 return this.docView.posFromDOM(node, offset);
8378 }
8379 posAtCoords(coords, precise = true) {
8380 this.readMeasured();
8381 let found = posAtCoords(this, coords, precise);
8382 return found && found.pos;
8383 }
8384 posAndSideAtCoords(coords, precise = true) {
8385 this.readMeasured();
8386 return posAtCoords(this, coords, precise);
8387 }
8388 /**
8389 Get the screen coordinates at the given document position.
8390 `side` determines whether the coordinates are based on the
8391 element before (-1) or after (1) the position (if no element is
8392 available on the given side, the method will transparently use
8393 another strategy to get reasonable coordinates).
8394 */
8395 coordsAtPos(pos, side = 1) {
8396 this.readMeasured();
8397 let rect = this.docView.coordsAt(pos, side);
8398 if (!rect || rect.left == rect.right)
8399 return rect;
8400 let line = this.state.doc.lineAt(pos), order = this.bidiSpans(line);
8401 let span = order[BidiSpan.find(order, pos - line.from, -1, side)];
8402 return flattenRect(rect, (span.dir == Direction.LTR) == (side > 0));
8403 }
8404 /**
8405 Return the rectangle around a given character. If `pos` does not
8406 point in front of a character that is in the viewport and
8407 rendered (i.e. not replaced, not a line break), this will return
8408 null. For space characters that are a line wrap point, this will
8409 return the position before the line break.
8410 */
8411 coordsForChar(pos) {
8412 this.readMeasured();
8413 return this.docView.coordsForChar(pos);
8414 }
8415 /**
8416 The default width of a character in the editor. May not
8417 accurately reflect the width of all characters (given variable
8418 width fonts or styling of invididual ranges).
8419 */
8420 get defaultCharacterWidth() { return this.viewState.heightOracle.charWidth; }
8421 /**
8422 The default height of a line in the editor. May not be accurate
8423 for all lines.
8424 */
8425 get defaultLineHeight() { return this.viewState.heightOracle.lineHeight; }
8426 /**
8427 The text direction
8428 ([`direction`](https://developer.mozilla.org/en-US/docs/Web/CSS/direction)
8429 CSS property) of the editor's content element.
8430 */
8431 get textDirection() { return this.viewState.defaultTextDirection; }
8432 /**
8433 Find the text direction of the block at the given position, as
8434 assigned by CSS. If
8435 [`perLineTextDirection`](https://codemirror.net/6/docs/ref/#view.EditorView^perLineTextDirection)
8436 isn't enabled, or the given position is outside of the viewport,
8437 this will always return the same as
8438 [`textDirection`](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection). Note that
8439 this may trigger a DOM layout.
8440 */
8441 textDirectionAt(pos) {
8442 let perLine = this.state.facet(perLineTextDirection);
8443 if (!perLine || pos < this.viewport.from || pos > this.viewport.to)
8444 return this.textDirection;
8445 this.readMeasured();
8446 return this.docView.textDirectionAt(pos);
8447 }
8448 /**
8449 Whether this editor [wraps lines](https://codemirror.net/6/docs/ref/#view.EditorView.lineWrapping)
8450 (as determined by the
8451 [`white-space`](https://developer.mozilla.org/en-US/docs/Web/CSS/white-space)
8452 CSS property of its content element).
8453 */
8454 get lineWrapping() { return this.viewState.heightOracle.lineWrapping; }
8455 /**
8456 Returns the bidirectional text structure of the given line
8457 (which should be in the current document) as an array of span
8458 objects. The order of these spans matches the [text
8459 direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection)—if that is
8460 left-to-right, the leftmost spans come first, otherwise the
8461 rightmost spans come first.
8462 */
8463 bidiSpans(line) {
8464 if (line.length > MaxBidiLine)
8465 return trivialOrder(line.length);
8466 let dir = this.textDirectionAt(line.from), isolates;
8467 for (let entry of this.bidiCache) {
8468 if (entry.from == line.from && entry.dir == dir &&
8469 (entry.fresh || isolatesEq(entry.isolates, isolates = getIsolatedRanges(this, line))))
8470 return entry.order;
8471 }
8472 if (!isolates)
8473 isolates = getIsolatedRanges(this, line);
8474 let order = computeOrder(line.text, dir, isolates);
8475 this.bidiCache.push(new CachedOrder(line.from, line.to, dir, isolates, true, order));
8476 return order;
8477 }
8478 /**
8479 Check whether the editor has focus.
8480 */
8481 get hasFocus() {
8482 var _a;
8483 // Safari return false for hasFocus when the context menu is open
8484 // or closing, which leads us to ignore selection changes from the
8485 // context menu because it looks like the editor isn't focused.
8486 // This kludges around that.
8487 return (this.dom.ownerDocument.hasFocus() || browser.safari && ((_a = this.inputState) === null || _a === void 0 ? void 0 : _a.lastContextMenu) > Date.now() - 3e4) &&
8488 this.root.activeElement == this.contentDOM;
8489 }
8490 /**
8491 Put focus on the editor.
8492 */
8493 focus() {
8494 this.observer.ignore(() => {
8495 focusPreventScroll(this.contentDOM);
8496 this.docView.updateSelection();
8497 });
8498 }
8499 /**
8500 Update the [root](https://codemirror.net/6/docs/ref/##view.EditorViewConfig.root) in which the editor lives. This is only
8501 necessary when moving the editor's existing DOM to a new window or shadow root.
8502 */
8503 setRoot(root) {
8504 if (this._root != root) {
8505 this._root = root;
8506 this.observer.setWindow((root.nodeType == 9 ? root : root.ownerDocument).defaultView || window);
8507 this.mountStyles();
8508 }
8509 }
8510 /**
8511 Clean up this editor view, removing its element from the
8512 document, unregistering event handlers, and notifying
8513 plugins. The view instance can no longer be used after
8514 calling this.
8515 */
8516 destroy() {
8517 if (this.root.activeElement == this.contentDOM)
8518 this.contentDOM.blur();
8519 for (let plugin of this.plugins)
8520 plugin.destroy(this);
8521 this.plugins = [];
8522 this.inputState.destroy();
8523 this.docView.destroy();
8524 this.dom.remove();
8525 this.observer.destroy();
8526 if (this.measureScheduled > -1)
8527 this.win.cancelAnimationFrame(this.measureScheduled);
8528 this.destroyed = true;
8529 }
8530 /**
8531 Returns an effect that can be
8532 [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
8533 cause it to scroll the given position or range into view.
8534 */
8535 static scrollIntoView(pos, options = {}) {
8536 return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
8537 }
8538 /**
8539 Return an effect that resets the editor to its current (at the
8540 time this method was called) scroll position. Note that this
8541 only affects the editor's own scrollable element, not parents.
8542 See also
8543 [`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo).
8544
8545 The effect should be used with a document identical to the one
8546 it was created for. Failing to do so is not an error, but may
8547 not scroll to the expected position. You can
8548 [map](https://codemirror.net/6/docs/ref/#state.StateEffect.map) the effect to account for changes.
8549 */
8550 scrollSnapshot() {
8551 let { scrollTop, scrollLeft } = this.scrollDOM;
8552 let ref = this.viewState.scrollAnchorAt(scrollTop);
8553 return scrollIntoView.of(new ScrollTarget(EditorSelection.cursor(ref.from), "start", "start", ref.top - scrollTop, scrollLeft, true));
8554 }
8555 /**
8556 Enable or disable tab-focus mode, which disables key bindings
8557 for Tab and Shift-Tab, letting the browser's default
8558 focus-changing behavior go through instead. This is useful to
8559 prevent trapping keyboard users in your editor.
8560
8561 Without argument, this toggles the mode. With a boolean, it
8562 enables (true) or disables it (false). Given a number, it
8563 temporarily enables the mode until that number of milliseconds
8564 have passed or another non-Tab key is pressed.
8565 */
8566 setTabFocusMode(to) {
8567 if (to == null)
8568 this.inputState.tabFocusMode = this.inputState.tabFocusMode < 0 ? 0 : -1;
8569 else if (typeof to == "boolean")
8570 this.inputState.tabFocusMode = to ? 0 : -1;
8571 else if (this.inputState.tabFocusMode != 0)
8572 this.inputState.tabFocusMode = Date.now() + to;
8573 }
8574 /**
8575 Returns an extension that can be used to add DOM event handlers.
8576 The value should be an object mapping event names to handler
8577 functions. For any given event, such functions are ordered by
8578 extension precedence, and the first handler to return true will
8579 be assumed to have handled that event, and no other handlers or
8580 built-in behavior will be activated for it. These are registered
8581 on the [content element](https://codemirror.net/6/docs/ref/#view.EditorView.contentDOM), except
8582 for `scroll` handlers, which will be called any time the
8583 editor's [scroll element](https://codemirror.net/6/docs/ref/#view.EditorView.scrollDOM) or one of
8584 its parent nodes is scrolled.
8585 */
8586 static domEventHandlers(handlers) {
8587 return ViewPlugin.define(() => ({}), { eventHandlers: handlers });
8588 }
8589 /**
8590 Create an extension that registers DOM event observers. Contrary
8591 to event [handlers](https://codemirror.net/6/docs/ref/#view.EditorView^domEventHandlers),
8592 observers can't be prevented from running by a higher-precedence
8593 handler returning true. They also don't prevent other handlers
8594 and observers from running when they return true, and should not
8595 call `preventDefault`.
8596 */
8597 static domEventObservers(observers) {
8598 return ViewPlugin.define(() => ({}), { eventObservers: observers });
8599 }
8600 /**
8601 Create a theme extension. The first argument can be a
8602 [`style-mod`](https://github.com/marijnh/style-mod#documentation)
8603 style spec providing the styles for the theme. These will be
8604 prefixed with a generated class for the style.
8605
8606 Because the selectors will be prefixed with a scope class, rule
8607 that directly match the editor's [wrapper
8608 element](https://codemirror.net/6/docs/ref/#view.EditorView.dom)—to which the scope class will be
8609 added—need to be explicitly differentiated by adding an `&` to
8610 the selector for that element—for example
8611 `&.cm-focused`.
8612
8613 When `dark` is set to true, the theme will be marked as dark,
8614 which will cause the `&dark` rules from [base
8615 themes](https://codemirror.net/6/docs/ref/#view.EditorView^baseTheme) to be used (as opposed to
8616 `&light` when a light theme is active).
8617 */
8618 static theme(spec, options) {
8619 let prefix = StyleModule.newName();
8620 let result = [theme.of(prefix), styleModule.of(buildTheme(`.${prefix}`, spec))];
8621 if (options && options.dark)
8622 result.push(darkTheme.of(true));
8623 return result;
8624 }
8625 /**
8626 Create an extension that adds styles to the base theme. Like
8627 with [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme), use `&` to indicate the
8628 place of the editor wrapper element when directly targeting
8629 that. You can also use `&dark` or `&light` instead to only
8630 target editors with a dark or light theme.
8631 */
8632 static baseTheme(spec) {
8633 return Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
8634 }
8635 /**
8636 Retrieve an editor view instance from the view's DOM
8637 representation.
8638 */
8639 static findFromDOM(dom) {
8640 var _a;
8641 let content = dom.querySelector(".cm-content");
8642 let tile = content && Tile.get(content) || Tile.get(dom);
8643 return ((_a = tile === null || tile === void 0 ? void 0 : tile.root) === null || _a === void 0 ? void 0 : _a.view) || null;
8644 }
8645}
8646/**
8647Facet to add a [style
8648module](https://github.com/marijnh/style-mod#documentation) to
8649an editor view. The view will ensure that the module is
8650mounted in its [document
8651root](https://codemirror.net/6/docs/ref/#view.EditorView.constructor^config.root).
8652*/
8653EditorView.styleModule = styleModule;
8654/**
8655An input handler can override the way changes to the editable
8656DOM content are handled. Handlers are passed the document
8657positions between which the change was found, and the new
8658content. When one returns true, no further input handlers are
8659called and the default behavior is prevented.
8660
8661The `insert` argument can be used to get the default transaction
8662that would be applied for this input. This can be useful when
8663dispatching the custom behavior as a separate transaction.
8664*/
8665EditorView.inputHandler = inputHandler;
8666/**
8667Functions provided in this facet will be used to transform text
8668pasted or dropped into the editor.
8669*/
8670EditorView.clipboardInputFilter = clipboardInputFilter;
8671/**
8672Transform text copied or dragged from the editor.
8673*/
8674EditorView.clipboardOutputFilter = clipboardOutputFilter;
8675/**
8676Scroll handlers can override how things are scrolled into view.
8677If they return `true`, no further handling happens for the
8678scrolling. If they return false, the default scroll behavior is
8679applied. Scroll handlers should never initiate editor updates.
8680*/
8681EditorView.scrollHandler = scrollHandler;
8682/**
8683This facet can be used to provide functions that create effects
8684to be dispatched when the editor's focus state changes.
8685*/
8686EditorView.focusChangeEffect = focusChangeEffect;
8687/**
8688By default, the editor assumes all its content has the same
8689[text direction](https://codemirror.net/6/docs/ref/#view.Direction). Configure this with a `true`
8690value to make it read the text direction of every (rendered)
8691line separately.
8692*/
8693EditorView.perLineTextDirection = perLineTextDirection;
8694/**
8695Allows you to provide a function that should be called when the
8696library catches an exception from an extension (mostly from view
8697plugins, but may be used by other extensions to route exceptions
8698from user-code-provided callbacks). This is mostly useful for
8699debugging and logging. See [`logException`](https://codemirror.net/6/docs/ref/#view.logException).
8700*/
8701EditorView.exceptionSink = exceptionSink;
8702/**
8703A facet that can be used to register a function to be called
8704every time the view updates.
8705*/
8706EditorView.updateListener = updateListener;
8707/**
8708Facet that controls whether the editor content DOM is editable.
8709When its highest-precedence value is `false`, the element will
8710not have its `contenteditable` attribute set. (Note that this
8711doesn't affect API calls that change the editor content, even
8712when those are bound to keys or buttons. See the
8713[`readOnly`](https://codemirror.net/6/docs/ref/#state.EditorState.readOnly) facet for that.)
8714*/
8715EditorView.editable = editable;
8716/**
8717Allows you to influence the way mouse selection happens. The
8718functions in this facet will be called for a `mousedown` event
8719on the editor, and can return an object that overrides the way a
8720selection is computed from that mouse click or drag.
8721*/
8722EditorView.mouseSelectionStyle = mouseSelectionStyle;
8723/**
8724Facet used to configure whether a given selection drag event
8725should move or copy the selection. The given predicate will be
8726called with the `mousedown` event, and can return `true` when
8727the drag should move the content.
8728*/
8729EditorView.dragMovesSelection = dragMovesSelection$1;
8730/**
8731Facet used to configure whether a given selecting click adds a
8732new range to the existing selection or replaces it entirely. The
8733default behavior is to check `event.metaKey` on macOS, and
8734`event.ctrlKey` elsewhere.
8735*/
8736EditorView.clickAddsSelectionRange = clickAddsSelectionRange;
8737/**
8738A facet that determines which [decorations](https://codemirror.net/6/docs/ref/#view.Decoration)
8739are shown in the view. Decorations can be provided in two
8740ways—directly, or via a function that takes an editor view.
8741
8742Only decoration sets provided directly are allowed to influence
8743the editor's vertical layout structure. The ones provided as
8744functions are called _after_ the new viewport has been computed,
8745and thus **must not** introduce block widgets or replacing
8746decorations that cover line breaks.
8747
8748If you want decorated ranges to behave like atomic units for
8749cursor motion and deletion purposes, also provide the range set
8750containing the decorations to
8751[`EditorView.atomicRanges`](https://codemirror.net/6/docs/ref/#view.EditorView^atomicRanges).
8752*/
8753EditorView.decorations = decorations;
8754/**
8755[Block wrappers](https://codemirror.net/6/docs/ref/#view.BlockWrapper) provide a way to add DOM
8756structure around editor lines and block widgets. Sets of
8757wrappers are provided in a similar way to decorations, and are
8758nested in a similar way when they overlap. A wrapper affects all
8759lines and block widgets that start inside its range.
8760*/
8761EditorView.blockWrappers = blockWrappers;
8762/**
8763Facet that works much like
8764[`decorations`](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), but puts its
8765inputs at the very bottom of the precedence stack, meaning mark
8766decorations provided here will only be split by other, partially
8767overlapping `outerDecorations` ranges, and wrap around all
8768regular decorations. Use this for mark elements that should, as
8769much as possible, remain in one piece.
8770*/
8771EditorView.outerDecorations = outerDecorations;
8772/**
8773Used to provide ranges that should be treated as atoms as far as
8774cursor motion is concerned. This causes methods like
8775[`moveByChar`](https://codemirror.net/6/docs/ref/#view.EditorView.moveByChar) and
8776[`moveVertically`](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) (and the
8777commands built on top of them) to skip across such regions when
8778a selection endpoint would enter them. This does _not_ prevent
8779direct programmatic [selection
8780updates](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection) from moving into such
8781regions.
8782*/
8783EditorView.atomicRanges = atomicRanges;
8784/**
8785When range decorations add a `unicode-bidi: isolate` style, they
8786should also include a
8787[`bidiIsolate`](https://codemirror.net/6/docs/ref/#view.MarkDecorationSpec.bidiIsolate) property
8788in their decoration spec, and be exposed through this facet, so
8789that the editor can compute the proper text order. (Other values
8790for `unicode-bidi`, except of course `normal`, are not
8791supported.)
8792*/
8793EditorView.bidiIsolatedRanges = bidiIsolatedRanges;
8794/**
8795Facet that allows extensions to provide additional scroll
8796margins (space around the sides of the scrolling element that
8797should be considered invisible). This can be useful when the
8798plugin introduces elements that cover part of that element (for
8799example a horizontally fixed gutter).
8800*/
8801EditorView.scrollMargins = scrollMargins;
8802/**
8803This facet records whether a dark theme is active. The extension
8804returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
8805includes an instance of this when the `dark` option is set to
8806true.
8807*/
8808EditorView.darkTheme = darkTheme;
8809/**
8810Provides a Content Security Policy nonce to use when creating
8811the style sheets for the editor. Holds the empty string when no
8812nonce has been provided.
8813*/
8814EditorView.cspNonce = /*@__PURE__*/Facet.define({ combine: values => values.length ? values[0] : "" });
8815/**
8816Facet that provides additional DOM attributes for the editor's
8817editable DOM element.
8818*/
8819EditorView.contentAttributes = contentAttributes;
8820/**
8821Facet that provides DOM attributes for the editor's outer
8822element.
8823*/
8824EditorView.editorAttributes = editorAttributes;
8825/**
8826An extension that enables line wrapping in the editor (by
8827setting CSS `white-space` to `pre-wrap` in the content).
8828*/
8829EditorView.lineWrapping = /*@__PURE__*/EditorView.contentAttributes.of({ "class": "cm-lineWrapping" });
8830/**
8831State effect used to include screen reader announcements in a
8832transaction. These will be added to the DOM in a visually hidden
8833element with `aria-live="polite"` set, and should be used to
8834describe effects that are visually obvious but may not be
8835noticed by screen reader users (such as moving to the next
8836search match).
8837*/
8838EditorView.announce = /*@__PURE__*/StateEffect.define();
8839// Maximum line length for which we compute accurate bidi info
8840const MaxBidiLine = 4096;
8841const BadMeasure = {};
8842class CachedOrder {
8843 constructor(from, to, dir, isolates, fresh, order) {
8844 this.from = from;
8845 this.to = to;
8846 this.dir = dir;
8847 this.isolates = isolates;
8848 this.fresh = fresh;
8849 this.order = order;
8850 }
8851 static update(cache, changes) {
8852 if (changes.empty && !cache.some(c => c.fresh))
8853 return cache;
8854 let result = [], lastDir = cache.length ? cache[cache.length - 1].dir : Direction.LTR;
8855 for (let i = Math.max(0, cache.length - 10); i < cache.length; i++) {
8856 let entry = cache[i];
8857 if (entry.dir == lastDir && !changes.touchesRange(entry.from, entry.to))
8858 result.push(new CachedOrder(changes.mapPos(entry.from, 1), changes.mapPos(entry.to, -1), entry.dir, entry.isolates, false, entry.order));
8859 }
8860 return result;
8861 }
8862}
8863function attrsFromFacet(view, facet, base) {
8864 for (let sources = view.state.facet(facet), i = sources.length - 1; i >= 0; i--) {
8865 let source = sources[i], value = typeof source == "function" ? source(view) : source;
8866 if (value)
8867 combineAttrs(value, base);
8868 }
8869 return base;
8870}
8871
8872const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
8873function normalizeKeyName(name, platform) {
8874 const parts = name.split(/-(?!$)/);
8875 let result = parts[parts.length - 1];
8876 if (result == "Space")
8877 result = " ";
8878 let alt, ctrl, shift, meta;
8879 for (let i = 0; i < parts.length - 1; ++i) {
8880 const mod = parts[i];
8881 if (/^(cmd|meta|m)$/i.test(mod))
8882 meta = true;
8883 else if (/^a(lt)?$/i.test(mod))
8884 alt = true;
8885 else if (/^(c|ctrl|control)$/i.test(mod))
8886 ctrl = true;
8887 else if (/^s(hift)?$/i.test(mod))
8888 shift = true;
8889 else if (/^mod$/i.test(mod)) {
8890 if (platform == "mac")
8891 meta = true;
8892 else
8893 ctrl = true;
8894 }
8895 else
8896 throw new Error("Unrecognized modifier name: " + mod);
8897 }
8898 if (alt)
8899 result = "Alt-" + result;
8900 if (ctrl)
8901 result = "Ctrl-" + result;
8902 if (meta)
8903 result = "Meta-" + result;
8904 if (shift)
8905 result = "Shift-" + result;
8906 return result;
8907}
8908function modifiers(name, event, shift) {
8909 if (event.altKey)
8910 name = "Alt-" + name;
8911 if (event.ctrlKey)
8912 name = "Ctrl-" + name;
8913 if (event.metaKey)
8914 name = "Meta-" + name;
8915 if (shift !== false && event.shiftKey)
8916 name = "Shift-" + name;
8917 return name;
8918}
8919const handleKeyEvents = /*@__PURE__*/Prec.default(/*@__PURE__*/EditorView.domEventHandlers({
8920 keydown(event, view) {
8921 return runHandlers(getKeymap(view.state), event, view, "editor");
8922 }
8923}));
8924/**
8925Facet used for registering keymaps.
8926
8927You can add multiple keymaps to an editor. Their priorities
8928determine their precedence (the ones specified early or with high
8929priority get checked first). When a handler has returned `true`
8930for a given key, no further handlers are called.
8931*/
8932const keymap = /*@__PURE__*/Facet.define({ enables: handleKeyEvents });
8933const Keymaps = /*@__PURE__*/new WeakMap();
8934// This is hidden behind an indirection, rather than directly computed
8935// by the facet, to keep internal types out of the facet's type.
8936function getKeymap(state) {
8937 let bindings = state.facet(keymap);
8938 let map = Keymaps.get(bindings);
8939 if (!map)
8940 Keymaps.set(bindings, map = buildKeymap(bindings.reduce((a, b) => a.concat(b), [])));
8941 return map;
8942}
8943/**
8944Run the key handlers registered for a given scope. The event
8945object should be a `"keydown"` event. Returns true if any of the
8946handlers handled it.
8947*/
8948function runScopeHandlers(view, event, scope) {
8949 return runHandlers(getKeymap(view.state), event, view, scope);
8950}
8951let storedPrefix = null;
8952const PrefixTimeout = 4000;
8953function buildKeymap(bindings, platform = currentPlatform) {
8954 let bound = Object.create(null);
8955 let isPrefix = Object.create(null);
8956 let checkPrefix = (name, is) => {
8957 let current = isPrefix[name];
8958 if (current == null)
8959 isPrefix[name] = is;
8960 else if (current != is)
8961 throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
8962 };
8963 let add = (scope, key, command, preventDefault, stopPropagation) => {
8964 var _a, _b;
8965 let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
8966 let parts = key.split(/ (?!$)/).map(k => normalizeKeyName(k, platform));
8967 for (let i = 1; i < parts.length; i++) {
8968 let prefix = parts.slice(0, i).join(" ");
8969 checkPrefix(prefix, true);
8970 if (!scopeObj[prefix])
8971 scopeObj[prefix] = {
8972 preventDefault: true,
8973 stopPropagation: false,
8974 run: [(view) => {
8975 let ourObj = storedPrefix = { view, prefix, scope };
8976 setTimeout(() => { if (storedPrefix == ourObj)
8977 storedPrefix = null; }, PrefixTimeout);
8978 return true;
8979 }]
8980 };
8981 }
8982 let full = parts.join(" ");
8983 checkPrefix(full, false);
8984 let binding = scopeObj[full] || (scopeObj[full] = {
8985 preventDefault: false,
8986 stopPropagation: false,
8987 run: ((_b = (_a = scopeObj._any) === null || _a === void 0 ? void 0 : _a.run) === null || _b === void 0 ? void 0 : _b.slice()) || []
8988 });
8989 if (command)
8990 binding.run.push(command);
8991 if (preventDefault)
8992 binding.preventDefault = true;
8993 if (stopPropagation)
8994 binding.stopPropagation = true;
8995 };
8996 for (let b of bindings) {
8997 let scopes = b.scope ? b.scope.split(" ") : ["editor"];
8998 if (b.any)
8999 for (let scope of scopes) {
9000 let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
9001 if (!scopeObj._any)
9002 scopeObj._any = { preventDefault: false, stopPropagation: false, run: [] };
9003 let { any } = b;
9004 for (let key in scopeObj)
9005 scopeObj[key].run.push(view => any(view, currentKeyEvent));
9006 }
9007 let name = b[platform] || b.key;
9008 if (!name)
9009 continue;
9010 for (let scope of scopes) {
9011 add(scope, name, b.run, b.preventDefault, b.stopPropagation);
9012 if (b.shift)
9013 add(scope, "Shift-" + name, b.shift, b.preventDefault, b.stopPropagation);
9014 }
9015 }
9016 return bound;
9017}
9018let currentKeyEvent = null;
9019function runHandlers(map, event, view, scope) {
9020 currentKeyEvent = event;
9021 let name = keyName(event);
9022 let charCode = codePointAt(name, 0), isChar = codePointSize(charCode) == name.length && name != " ";
9023 let prefix = "", handled = false, prevented = false, stopPropagation = false;
9024 if (storedPrefix && storedPrefix.view == view && storedPrefix.scope == scope) {
9025 prefix = storedPrefix.prefix + " ";
9026 if (modifierCodes.indexOf(event.keyCode) < 0) {
9027 prevented = true;
9028 storedPrefix = null;
9029 }
9030 }
9031 let ran = new Set;
9032 let runFor = (binding) => {
9033 if (binding) {
9034 for (let cmd of binding.run)
9035 if (!ran.has(cmd)) {
9036 ran.add(cmd);
9037 if (cmd(view)) {
9038 if (binding.stopPropagation)
9039 stopPropagation = true;
9040 return true;
9041 }
9042 }
9043 if (binding.preventDefault) {
9044 if (binding.stopPropagation)
9045 stopPropagation = true;
9046 prevented = true;
9047 }
9048 }
9049 return false;
9050 };
9051 let scopeObj = map[scope], baseName, shiftName;
9052 if (scopeObj) {
9053 if (runFor(scopeObj[prefix + modifiers(name, event, !isChar)])) {
9054 handled = true;
9055 }
9056 else if (isChar && (event.altKey || event.metaKey || event.ctrlKey) &&
9057 // Ctrl-Alt may be used for AltGr on Windows
9058 !(browser.windows && event.ctrlKey && event.altKey) &&
9059 // Alt-combinations on macOS tend to be typed characters
9060 !(browser.mac && event.altKey && !(event.ctrlKey || event.metaKey)) &&
9061 (baseName = base[event.keyCode]) && baseName != name) {
9062 if (runFor(scopeObj[prefix + modifiers(baseName, event, true)])) {
9063 handled = true;
9064 }
9065 else if (event.shiftKey && (shiftName = shift[event.keyCode]) != name && shiftName != baseName &&
9066 runFor(scopeObj[prefix + modifiers(shiftName, event, false)])) {
9067 handled = true;
9068 }
9069 }
9070 else if (isChar && event.shiftKey &&
9071 runFor(scopeObj[prefix + modifiers(name, event, true)])) {
9072 handled = true;
9073 }
9074 if (!handled && runFor(scopeObj._any))
9075 handled = true;
9076 }
9077 if (prevented)
9078 handled = true;
9079 if (handled && stopPropagation)
9080 event.stopPropagation();
9081 currentKeyEvent = null;
9082 return handled;
9083}
9084
9085/**
9086Implementation of [`LayerMarker`](https://codemirror.net/6/docs/ref/#view.LayerMarker) that creates
9087a rectangle at a given set of coordinates.
9088*/
9089class RectangleMarker {
9090 /**
9091 Create a marker with the given class and dimensions. If `width`
9092 is null, the DOM element will get no width style.
9093 */
9094 constructor(className,
9095 /**
9096 The left position of the marker (in pixels, document-relative).
9097 */
9098 left,
9099 /**
9100 The top position of the marker.
9101 */
9102 top,
9103 /**
9104 The width of the marker, or null if it shouldn't get a width assigned.
9105 */
9106 width,
9107 /**
9108 The height of the marker.
9109 */
9110 height) {
9111 this.className = className;
9112 this.left = left;
9113 this.top = top;
9114 this.width = width;
9115 this.height = height;
9116 }
9117 draw() {
9118 let elt = document.createElement("div");
9119 elt.className = this.className;
9120 this.adjust(elt);
9121 return elt;
9122 }
9123 update(elt, prev) {
9124 if (prev.className != this.className)
9125 return false;
9126 this.adjust(elt);
9127 return true;
9128 }
9129 adjust(elt) {
9130 elt.style.left = this.left + "px";
9131 elt.style.top = this.top + "px";
9132 if (this.width != null)
9133 elt.style.width = this.width + "px";
9134 elt.style.height = this.height + "px";
9135 }
9136 eq(p) {
9137 return this.left == p.left && this.top == p.top && this.width == p.width && this.height == p.height &&
9138 this.className == p.className;
9139 }
9140 /**
9141 Create a set of rectangles for the given selection range,
9142 assigning them theclass`className`. Will create a single
9143 rectangle for empty ranges, and a set of selection-style
9144 rectangles covering the range's content (in a bidi-aware
9145 way) for non-empty ones.
9146 */
9147 static forRange(view, className, range) {
9148 if (range.empty) {
9149 let pos = view.coordsAtPos(range.head, range.assoc || 1);
9150 if (!pos)
9151 return [];
9152 let base = getBase(view);
9153 return [new RectangleMarker(className, pos.left - base.left, pos.top - base.top, null, pos.bottom - pos.top)];
9154 }
9155 else {
9156 return rectanglesForRange(view, className, range);
9157 }
9158 }
9159}
9160function getBase(view) {
9161 let rect = view.scrollDOM.getBoundingClientRect();
9162 let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth * view.scaleX;
9163 return { left: left - view.scrollDOM.scrollLeft * view.scaleX, top: rect.top - view.scrollDOM.scrollTop * view.scaleY };
9164}
9165function wrappedLine(view, pos, side, inside) {
9166 let coords = view.coordsAtPos(pos, side * 2);
9167 if (!coords)
9168 return inside;
9169 let editorRect = view.dom.getBoundingClientRect();
9170 let y = (coords.top + coords.bottom) / 2;
9171 let left = view.posAtCoords({ x: editorRect.left + 1, y });
9172 let right = view.posAtCoords({ x: editorRect.right - 1, y });
9173 if (left == null || right == null)
9174 return inside;
9175 return { from: Math.max(inside.from, Math.min(left, right)), to: Math.min(inside.to, Math.max(left, right)) };
9176}
9177function rectanglesForRange(view, className, range) {
9178 if (range.to <= view.viewport.from || range.from >= view.viewport.to)
9179 return [];
9180 let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
9181 let ltr = view.textDirection == Direction.LTR;
9182 let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
9183 let lineElt = content.querySelector(".cm-line"), lineStyle = lineElt && window.getComputedStyle(lineElt);
9184 let leftSide = contentRect.left +
9185 (lineStyle ? parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent)) : 0);
9186 let rightSide = contentRect.right - (lineStyle ? parseInt(lineStyle.paddingRight) : 0);
9187 let startBlock = blockAt(view, from, 1), endBlock = blockAt(view, to, -1);
9188 let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
9189 let visualEnd = endBlock.type == BlockType.Text ? endBlock : null;
9190 if (visualStart && (view.lineWrapping || startBlock.widgetLineBreaks))
9191 visualStart = wrappedLine(view, from, 1, visualStart);
9192 if (visualEnd && (view.lineWrapping || endBlock.widgetLineBreaks))
9193 visualEnd = wrappedLine(view, to, -1, visualEnd);
9194 if (visualStart && visualEnd && visualStart.from == visualEnd.from && visualStart.to == visualEnd.to) {
9195 return pieces(drawForLine(range.from, range.to, visualStart));
9196 }
9197 else {
9198 let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
9199 let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
9200 let between = [];
9201 if ((visualStart || startBlock).to < (visualEnd || endBlock).from - (visualStart && visualEnd ? 1 : 0) ||
9202 startBlock.widgetLineBreaks > 1 && top.bottom + view.defaultLineHeight / 2 < bottom.top)
9203 between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
9204 else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
9205 top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
9206 return pieces(top).concat(between).concat(pieces(bottom));
9207 }
9208 function piece(left, top, right, bottom) {
9209 return new RectangleMarker(className, left - base.left, top - base.top, Math.max(0, right - left), bottom - top);
9210 }
9211 function pieces({ top, bottom, horizontal }) {
9212 let pieces = [];
9213 for (let i = 0; i < horizontal.length; i += 2)
9214 pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
9215 return pieces;
9216 }
9217 // Gets passed from/to in line-local positions
9218 function drawForLine(from, to, line) {
9219 let top = 1e9, bottom = -1e9, horizontal = [];
9220 function addSpan(from, fromOpen, to, toOpen, dir) {
9221 // Passing 2/-2 is a kludge to force the view to return
9222 // coordinates on the proper side of block widgets, since
9223 // normalizing the side there, though appropriate for most
9224 // coordsAtPos queries, would break selection drawing.
9225 let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
9226 let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
9227 if (!fromCoords || !toCoords)
9228 return;
9229 top = Math.min(fromCoords.top, toCoords.top, top);
9230 bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
9231 if (dir == Direction.LTR)
9232 horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
9233 else
9234 horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
9235 }
9236 let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
9237 // Split the range by visible range and document line
9238 for (let r of view.visibleRanges)
9239 if (r.to > start && r.from < end) {
9240 for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
9241 let docLine = view.state.doc.lineAt(pos);
9242 for (let span of view.bidiSpans(docLine)) {
9243 let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
9244 if (spanFrom >= endPos)
9245 break;
9246 if (spanTo > pos)
9247 addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
9248 }
9249 pos = docLine.to + 1;
9250 if (pos >= endPos)
9251 break;
9252 }
9253 }
9254 if (horizontal.length == 0)
9255 addSpan(start, from == null, end, to == null, view.textDirection);
9256 return { top, bottom, horizontal };
9257 }
9258 function drawForWidget(block, top) {
9259 let y = contentRect.top + (top ? block.top : block.bottom);
9260 return { top: y, bottom: y, horizontal: [] };
9261 }
9262}
9263function sameMarker(a, b) {
9264 return a.constructor == b.constructor && a.eq(b);
9265}
9266class LayerView {
9267 constructor(view, layer) {
9268 this.view = view;
9269 this.layer = layer;
9270 this.drawn = [];
9271 this.scaleX = 1;
9272 this.scaleY = 1;
9273 this.measureReq = { read: this.measure.bind(this), write: this.draw.bind(this) };
9274 this.dom = view.scrollDOM.appendChild(document.createElement("div"));
9275 this.dom.classList.add("cm-layer");
9276 if (layer.above)
9277 this.dom.classList.add("cm-layer-above");
9278 if (layer.class)
9279 this.dom.classList.add(layer.class);
9280 this.scale();
9281 this.dom.setAttribute("aria-hidden", "true");
9282 this.setOrder(view.state);
9283 view.requestMeasure(this.measureReq);
9284 if (layer.mount)
9285 layer.mount(this.dom, view);
9286 }
9287 update(update) {
9288 if (update.startState.facet(layerOrder) != update.state.facet(layerOrder))
9289 this.setOrder(update.state);
9290 if (this.layer.update(update, this.dom) || update.geometryChanged) {
9291 this.scale();
9292 update.view.requestMeasure(this.measureReq);
9293 }
9294 }
9295 docViewUpdate(view) {
9296 if (this.layer.updateOnDocViewUpdate !== false)
9297 view.requestMeasure(this.measureReq);
9298 }
9299 setOrder(state) {
9300 let pos = 0, order = state.facet(layerOrder);
9301 while (pos < order.length && order[pos] != this.layer)
9302 pos++;
9303 this.dom.style.zIndex = String((this.layer.above ? 150 : -1) - pos);
9304 }
9305 measure() {
9306 return this.layer.markers(this.view);
9307 }
9308 scale() {
9309 let { scaleX, scaleY } = this.view;
9310 if (scaleX != this.scaleX || scaleY != this.scaleY) {
9311 this.scaleX = scaleX;
9312 this.scaleY = scaleY;
9313 this.dom.style.transform = `scale(${1 / scaleX}, ${1 / scaleY})`;
9314 }
9315 }
9316 draw(markers) {
9317 if (markers.length != this.drawn.length || markers.some((p, i) => !sameMarker(p, this.drawn[i]))) {
9318 let old = this.dom.firstChild, oldI = 0;
9319 for (let marker of markers) {
9320 if (marker.update && old && marker.constructor && this.drawn[oldI].constructor &&
9321 marker.update(old, this.drawn[oldI])) {
9322 old = old.nextSibling;
9323 oldI++;
9324 }
9325 else {
9326 this.dom.insertBefore(marker.draw(), old);
9327 }
9328 }
9329 while (old) {
9330 let next = old.nextSibling;
9331 old.remove();
9332 old = next;
9333 }
9334 this.drawn = markers;
9335 if (browser.safari && browser.safari_version >= 26) // Issue #1600, 1627
9336 this.dom.style.display = this.dom.firstChild ? "" : "none";
9337 }
9338 }
9339 destroy() {
9340 if (this.layer.destroy)
9341 this.layer.destroy(this.dom, this.view);
9342 this.dom.remove();
9343 }
9344}
9345const layerOrder = /*@__PURE__*/Facet.define();
9346/**
9347Define a layer.
9348*/
9349function layer(config) {
9350 return [
9351 ViewPlugin.define(v => new LayerView(v, config)),
9352 layerOrder.of(config)
9353 ];
9354}
9355
9356const selectionConfig = /*@__PURE__*/Facet.define({
9357 combine(configs) {
9358 return combineConfig(configs, {
9359 cursorBlinkRate: 1200,
9360 drawRangeCursor: true
9361 }, {
9362 cursorBlinkRate: (a, b) => Math.min(a, b),
9363 drawRangeCursor: (a, b) => a || b
9364 });
9365 }
9366});
9367/**
9368Returns an extension that hides the browser's native selection and
9369cursor, replacing the selection with a background behind the text
9370(with the `cm-selectionBackground` class), and the
9371cursors with elements overlaid over the code (using
9372`cm-cursor-primary` and `cm-cursor-secondary`).
9373
9374This allows the editor to display secondary selection ranges, and
9375tends to produce a type of selection more in line with that users
9376expect in a text editor (the native selection styling will often
9377leave gaps between lines and won't fill the horizontal space after
9378a line when the selection continues past it).
9379
9380It does have a performance cost, in that it requires an extra DOM
9381layout cycle for many updates (the selection is drawn based on DOM
9382layout information that's only available after laying out the
9383content).
9384*/
9385function drawSelection(config = {}) {
9386 return [
9387 selectionConfig.of(config),
9388 cursorLayer,
9389 selectionLayer,
9390 hideNativeSelection,
9391 nativeSelectionHidden.of(true)
9392 ];
9393}
9394/**
9395Retrieve the [`drawSelection`](https://codemirror.net/6/docs/ref/#view.drawSelection) configuration
9396for this state. (Note that this will return a set of defaults even
9397if `drawSelection` isn't enabled.)
9398*/
9399function getDrawSelectionConfig(state) {
9400 return state.facet(selectionConfig);
9401}
9402function configChanged(update) {
9403 return update.startState.facet(selectionConfig) != update.state.facet(selectionConfig);
9404}
9405const cursorLayer = /*@__PURE__*/layer({
9406 above: true,
9407 markers(view) {
9408 let { state } = view, conf = state.facet(selectionConfig);
9409 let cursors = [];
9410 for (let r of state.selection.ranges) {
9411 let prim = r == state.selection.main;
9412 if (r.empty || conf.drawRangeCursor) {
9413 let className = prim ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary";
9414 let cursor = r.empty ? r : EditorSelection.cursor(r.head, r.head > r.anchor ? -1 : 1);
9415 for (let piece of RectangleMarker.forRange(view, className, cursor))
9416 cursors.push(piece);
9417 }
9418 }
9419 return cursors;
9420 },
9421 update(update, dom) {
9422 if (update.transactions.some(tr => tr.selection))
9423 dom.style.animationName = dom.style.animationName == "cm-blink" ? "cm-blink2" : "cm-blink";
9424 let confChange = configChanged(update);
9425 if (confChange)
9426 setBlinkRate(update.state, dom);
9427 return update.docChanged || update.selectionSet || confChange;
9428 },
9429 mount(dom, view) {
9430 setBlinkRate(view.state, dom);
9431 },
9432 class: "cm-cursorLayer"
9433});
9434function setBlinkRate(state, dom) {
9435 dom.style.animationDuration = state.facet(selectionConfig).cursorBlinkRate + "ms";
9436}
9437const selectionLayer = /*@__PURE__*/layer({
9438 above: false,
9439 markers(view) {
9440 return view.state.selection.ranges.map(r => r.empty ? [] : RectangleMarker.forRange(view, "cm-selectionBackground", r))
9441 .reduce((a, b) => a.concat(b));
9442 },
9443 update(update, dom) {
9444 return update.docChanged || update.selectionSet || update.viewportChanged || configChanged(update);
9445 },
9446 class: "cm-selectionLayer"
9447});
9448const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme({
9449 ".cm-line": {
9450 "& ::selection, &::selection": { backgroundColor: "transparent !important" },
9451 caretColor: "transparent !important"
9452 },
9453 ".cm-content": {
9454 caretColor: "transparent !important",
9455 "& :focus": {
9456 caretColor: "initial !important",
9457 "&::selection, & ::selection": {
9458 backgroundColor: "Highlight !important"
9459 }
9460 }
9461 }
9462}));
9463
9464const setDropCursorPos = /*@__PURE__*/StateEffect.define({
9465 map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
9466});
9467const dropCursorPos = /*@__PURE__*/StateField.define({
9468 create() { return null; },
9469 update(pos, tr) {
9470 if (pos != null)
9471 pos = tr.changes.mapPos(pos);
9472 return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
9473 }
9474});
9475const drawDropCursor = /*@__PURE__*/ViewPlugin.fromClass(class {
9476 constructor(view) {
9477 this.view = view;
9478 this.cursor = null;
9479 this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
9480 }
9481 update(update) {
9482 var _a;
9483 let cursorPos = update.state.field(dropCursorPos);
9484 if (cursorPos == null) {
9485 if (this.cursor != null) {
9486 (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
9487 this.cursor = null;
9488 }
9489 }
9490 else {
9491 if (!this.cursor) {
9492 this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
9493 this.cursor.className = "cm-dropCursor";
9494 }
9495 if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
9496 this.view.requestMeasure(this.measureReq);
9497 }
9498 }
9499 readPos() {
9500 let { view } = this;
9501 let pos = view.state.field(dropCursorPos);
9502 let rect = pos != null && view.coordsAtPos(pos);
9503 if (!rect)
9504 return null;
9505 let outer = view.scrollDOM.getBoundingClientRect();
9506 return {
9507 left: rect.left - outer.left + view.scrollDOM.scrollLeft * view.scaleX,
9508 top: rect.top - outer.top + view.scrollDOM.scrollTop * view.scaleY,
9509 height: rect.bottom - rect.top
9510 };
9511 }
9512 drawCursor(pos) {
9513 if (this.cursor) {
9514 let { scaleX, scaleY } = this.view;
9515 if (pos) {
9516 this.cursor.style.left = pos.left / scaleX + "px";
9517 this.cursor.style.top = pos.top / scaleY + "px";
9518 this.cursor.style.height = pos.height / scaleY + "px";
9519 }
9520 else {
9521 this.cursor.style.left = "-100000px";
9522 }
9523 }
9524 }
9525 destroy() {
9526 if (this.cursor)
9527 this.cursor.remove();
9528 }
9529 setDropPos(pos) {
9530 if (this.view.state.field(dropCursorPos) != pos)
9531 this.view.dispatch({ effects: setDropCursorPos.of(pos) });
9532 }
9533}, {
9534 eventObservers: {
9535 dragover(event) {
9536 this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
9537 },
9538 dragleave(event) {
9539 if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
9540 this.setDropPos(null);
9541 },
9542 dragend() {
9543 this.setDropPos(null);
9544 },
9545 drop() {
9546 this.setDropPos(null);
9547 }
9548 }
9549});
9550/**
9551Draws a cursor at the current drop position when something is
9552dragged over the editor.
9553*/
9554function dropCursor() {
9555 return [dropCursorPos, drawDropCursor];
9556}
9557
9558function iterMatches(doc, re, from, to, f) {
9559 re.lastIndex = 0;
9560 for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
9561 if (!cursor.lineBreak)
9562 while (m = re.exec(cursor.value))
9563 f(pos + m.index, m);
9564 }
9565}
9566function matchRanges(view, maxLength) {
9567 let visible = view.visibleRanges;
9568 if (visible.length == 1 && visible[0].from == view.viewport.from &&
9569 visible[0].to == view.viewport.to)
9570 return visible;
9571 let result = [];
9572 for (let { from, to } of visible) {
9573 from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
9574 to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
9575 if (result.length && result[result.length - 1].to >= from)
9576 result[result.length - 1].to = to;
9577 else
9578 result.push({ from, to });
9579 }
9580 return result;
9581}
9582/**
9583Helper class used to make it easier to maintain decorations on
9584visible code that matches a given regular expression. To be used
9585in a [view plugin](https://codemirror.net/6/docs/ref/#view.ViewPlugin). Instances of this object
9586represent a matching configuration.
9587*/
9588class MatchDecorator {
9589 /**
9590 Create a decorator.
9591 */
9592 constructor(config) {
9593 const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
9594 if (!regexp.global)
9595 throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
9596 this.regexp = regexp;
9597 if (decorate) {
9598 this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
9599 }
9600 else if (typeof decoration == "function") {
9601 this.addMatch = (match, view, from, add) => {
9602 let deco = decoration(match, view, from);
9603 if (deco)
9604 add(from, from + match[0].length, deco);
9605 };
9606 }
9607 else if (decoration) {
9608 this.addMatch = (match, _view, from, add) => add(from, from + match[0].length, decoration);
9609 }
9610 else {
9611 throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
9612 }
9613 this.boundary = boundary;
9614 this.maxLength = maxLength;
9615 }
9616 /**
9617 Compute the full set of decorations for matches in the given
9618 view's viewport. You'll want to call this when initializing your
9619 plugin.
9620 */
9621 createDeco(view) {
9622 let build = new RangeSetBuilder(), add = build.add.bind(build);
9623 for (let { from, to } of matchRanges(view, this.maxLength))
9624 iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
9625 return build.finish();
9626 }
9627 /**
9628 Update a set of decorations for a view update. `deco` _must_ be
9629 the set of decorations produced by _this_ `MatchDecorator` for
9630 the view state before the update.
9631 */
9632 updateDeco(update, deco) {
9633 let changeFrom = 1e9, changeTo = -1;
9634 if (update.docChanged)
9635 update.changes.iterChanges((_f, _t, from, to) => {
9636 if (to >= update.view.viewport.from && from <= update.view.viewport.to) {
9637 changeFrom = Math.min(from, changeFrom);
9638 changeTo = Math.max(to, changeTo);
9639 }
9640 });
9641 if (update.viewportMoved || changeTo - changeFrom > 1000)
9642 return this.createDeco(update.view);
9643 if (changeTo > -1)
9644 return this.updateRange(update.view, deco.map(update.changes), changeFrom, changeTo);
9645 return deco;
9646 }
9647 updateRange(view, deco, updateFrom, updateTo) {
9648 for (let r of view.visibleRanges) {
9649 let from = Math.max(r.from, updateFrom), to = Math.min(r.to, updateTo);
9650 if (to >= from) {
9651 let fromLine = view.state.doc.lineAt(from), toLine = fromLine.to < to ? view.state.doc.lineAt(to) : fromLine;
9652 let start = Math.max(r.from, fromLine.from), end = Math.min(r.to, toLine.to);
9653 if (this.boundary) {
9654 for (; from > fromLine.from; from--)
9655 if (this.boundary.test(fromLine.text[from - 1 - fromLine.from])) {
9656 start = from;
9657 break;
9658 }
9659 for (; to < toLine.to; to++)
9660 if (this.boundary.test(toLine.text[to - toLine.from])) {
9661 end = to;
9662 break;
9663 }
9664 }
9665 let ranges = [], m;
9666 let add = (from, to, deco) => ranges.push(deco.range(from, to));
9667 if (fromLine == toLine) {
9668 this.regexp.lastIndex = start - fromLine.from;
9669 while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
9670 this.addMatch(m, view, m.index + fromLine.from, add);
9671 }
9672 else {
9673 iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
9674 }
9675 deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
9676 }
9677 }
9678 return deco;
9679 }
9680}
9681
9682const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
9683const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
9684const Names = {
9685 0: "null",
9686 7: "bell",
9687 8: "backspace",
9688 10: "newline",
9689 11: "vertical tab",
9690 13: "carriage return",
9691 27: "escape",
9692 8203: "zero width space",
9693 8204: "zero width non-joiner",
9694 8205: "zero width joiner",
9695 8206: "left-to-right mark",
9696 8207: "right-to-left mark",
9697 8232: "line separator",
9698 8237: "left-to-right override",
9699 8238: "right-to-left override",
9700 8294: "left-to-right isolate",
9701 8295: "right-to-left isolate",
9702 8297: "pop directional isolate",
9703 8233: "paragraph separator",
9704 65279: "zero width no-break space",
9705 65532: "object replacement"
9706};
9707let _supportsTabSize = null;
9708function supportsTabSize() {
9709 var _a;
9710 if (_supportsTabSize == null && typeof document != "undefined" && document.body) {
9711 let styles = document.body.style;
9712 _supportsTabSize = ((_a = styles.tabSize) !== null && _a !== void 0 ? _a : styles.MozTabSize) != null;
9713 }
9714 return _supportsTabSize || false;
9715}
9716const specialCharConfig = /*@__PURE__*/Facet.define({
9717 combine(configs) {
9718 let config = combineConfig(configs, {
9719 render: null,
9720 specialChars: Specials,
9721 addSpecialChars: null
9722 });
9723 if (config.replaceTabs = !supportsTabSize())
9724 config.specialChars = new RegExp("\t|" + config.specialChars.source, UnicodeRegexpSupport);
9725 if (config.addSpecialChars)
9726 config.specialChars = new RegExp(config.specialChars.source + "|" + config.addSpecialChars.source, UnicodeRegexpSupport);
9727 return config;
9728 }
9729});
9730/**
9731Returns an extension that installs highlighting of special
9732characters.
9733*/
9734function highlightSpecialChars(
9735/**
9736Configuration options.
9737*/
9738config = {}) {
9739 return [specialCharConfig.of(config), specialCharPlugin()];
9740}
9741let _plugin = null;
9742function specialCharPlugin() {
9743 return _plugin || (_plugin = ViewPlugin.fromClass(class {
9744 constructor(view) {
9745 this.view = view;
9746 this.decorations = Decoration.none;
9747 this.decorationCache = Object.create(null);
9748 this.decorator = this.makeDecorator(view.state.facet(specialCharConfig));
9749 this.decorations = this.decorator.createDeco(view);
9750 }
9751 makeDecorator(conf) {
9752 return new MatchDecorator({
9753 regexp: conf.specialChars,
9754 decoration: (m, view, pos) => {
9755 let { doc } = view.state;
9756 let code = codePointAt(m[0], 0);
9757 if (code == 9) {
9758 let line = doc.lineAt(pos);
9759 let size = view.state.tabSize, col = countColumn(line.text, size, pos - line.from);
9760 return Decoration.replace({
9761 widget: new TabWidget((size - (col % size)) * this.view.defaultCharacterWidth / this.view.scaleX)
9762 });
9763 }
9764 return this.decorationCache[code] ||
9765 (this.decorationCache[code] = Decoration.replace({ widget: new SpecialCharWidget(conf, code) }));
9766 },
9767 boundary: conf.replaceTabs ? undefined : /[^]/
9768 });
9769 }
9770 update(update) {
9771 let conf = update.state.facet(specialCharConfig);
9772 if (update.startState.facet(specialCharConfig) != conf) {
9773 this.decorator = this.makeDecorator(conf);
9774 this.decorations = this.decorator.createDeco(update.view);
9775 }
9776 else {
9777 this.decorations = this.decorator.updateDeco(update, this.decorations);
9778 }
9779 }
9780 }, {
9781 decorations: v => v.decorations
9782 }));
9783}
9784const DefaultPlaceholder = "\u2022";
9785// Assigns placeholder characters from the Control Pictures block to
9786// ASCII control characters
9787function placeholder$1(code) {
9788 if (code >= 32)
9789 return DefaultPlaceholder;
9790 if (code == 10)
9791 return "\u2424";
9792 return String.fromCharCode(9216 + code);
9793}
9794class SpecialCharWidget extends WidgetType {
9795 constructor(options, code) {
9796 super();
9797 this.options = options;
9798 this.code = code;
9799 }
9800 eq(other) { return other.code == this.code; }
9801 toDOM(view) {
9802 let ph = placeholder$1(this.code);
9803 let desc = view.state.phrase("Control character") + " " + (Names[this.code] || "0x" + this.code.toString(16));
9804 let custom = this.options.render && this.options.render(this.code, desc, ph);
9805 if (custom)
9806 return custom;
9807 let span = document.createElement("span");
9808 span.textContent = ph;
9809 span.title = desc;
9810 span.setAttribute("aria-label", desc);
9811 span.className = "cm-specialChar";
9812 return span;
9813 }
9814 ignoreEvent() { return false; }
9815}
9816class TabWidget extends WidgetType {
9817 constructor(width) {
9818 super();
9819 this.width = width;
9820 }
9821 eq(other) { return other.width == this.width; }
9822 toDOM() {
9823 let span = document.createElement("span");
9824 span.textContent = "\t";
9825 span.className = "cm-tab";
9826 span.style.width = this.width + "px";
9827 return span;
9828 }
9829 ignoreEvent() { return false; }
9830}
9831
9832const plugin = /*@__PURE__*/ViewPlugin.fromClass(class {
9833 constructor() {
9834 this.height = 1000;
9835 this.attrs = { style: "padding-bottom: 1000px" };
9836 }
9837 update(update) {
9838 let { view } = update;
9839 let height = view.viewState.editorHeight -
9840 view.defaultLineHeight - view.documentPadding.top - 0.5;
9841 if (height >= 0 && height != this.height) {
9842 this.height = height;
9843 this.attrs = { style: `padding-bottom: ${height}px` };
9844 }
9845 }
9846});
9847/**
9848Returns an extension that makes sure the content has a bottom
9849margin equivalent to the height of the editor, minus one line
9850height, so that every line in the document can be scrolled to the
9851top of the editor.
9852
9853This is only meaningful when the editor is scrollable, and should
9854not be enabled in editors that take the size of their content.
9855*/
9856function scrollPastEnd() {
9857 return [plugin, contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.attrs) || null; })];
9858}
9859
9860/**
9861Mark lines that have a cursor on them with the `"cm-activeLine"`
9862DOM class.
9863*/
9864function highlightActiveLine() {
9865 return activeLineHighlighter;
9866}
9867const lineDeco = /*@__PURE__*/Decoration.line({ class: "cm-activeLine" });
9868const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
9869 constructor(view) {
9870 this.decorations = this.getDeco(view);
9871 }
9872 update(update) {
9873 if (update.docChanged || update.selectionSet)
9874 this.decorations = this.getDeco(update.view);
9875 }
9876 getDeco(view) {
9877 let lastLineStart = -1, deco = [];
9878 for (let r of view.state.selection.ranges) {
9879 let line = view.lineBlockAt(r.head);
9880 if (line.from > lastLineStart) {
9881 deco.push(lineDeco.range(line.from));
9882 lastLineStart = line.from;
9883 }
9884 }
9885 return Decoration.set(deco);
9886 }
9887}, {
9888 decorations: v => v.decorations
9889});
9890
9891class Placeholder extends WidgetType {
9892 constructor(content) {
9893 super();
9894 this.content = content;
9895 }
9896 toDOM(view) {
9897 let wrap = document.createElement("span");
9898 wrap.className = "cm-placeholder";
9899 wrap.style.pointerEvents = "none";
9900 wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) :
9901 typeof this.content == "function" ? this.content(view) :
9902 this.content.cloneNode(true));
9903 wrap.setAttribute("aria-hidden", "true");
9904 return wrap;
9905 }
9906 coordsAt(dom) {
9907 let rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
9908 if (!rects.length)
9909 return null;
9910 let style = window.getComputedStyle(dom.parentNode);
9911 let rect = flattenRect(rects[0], style.direction != "rtl");
9912 let lineHeight = parseInt(style.lineHeight);
9913 if (rect.bottom - rect.top > lineHeight * 1.5)
9914 return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.top + lineHeight };
9915 return rect;
9916 }
9917 ignoreEvent() { return false; }
9918}
9919/**
9920Extension that enables a placeholder—a piece of example content
9921to show when the editor is empty.
9922*/
9923function placeholder(content) {
9924 let plugin = ViewPlugin.fromClass(class {
9925 constructor(view) {
9926 this.view = view;
9927 this.placeholder = content
9928 ? Decoration.set([Decoration.widget({ widget: new Placeholder(content), side: 1 }).range(0)])
9929 : Decoration.none;
9930 }
9931 get decorations() { return this.view.state.doc.length ? Decoration.none : this.placeholder; }
9932 }, { decorations: v => v.decorations });
9933 return typeof content == "string" ? [
9934 plugin, EditorView.contentAttributes.of({ "aria-placeholder": content })
9935 ] : plugin;
9936}
9937
9938// Don't compute precise column positions for line offsets above this
9939// (since it could get expensive). Assume offset==column for them.
9940const MaxOff = 2000;
9941function rectangleFor(state, a, b) {
9942 let startLine = Math.min(a.line, b.line), endLine = Math.max(a.line, b.line);
9943 let ranges = [];
9944 if (a.off > MaxOff || b.off > MaxOff || a.col < 0 || b.col < 0) {
9945 let startOff = Math.min(a.off, b.off), endOff = Math.max(a.off, b.off);
9946 for (let i = startLine; i <= endLine; i++) {
9947 let line = state.doc.line(i);
9948 if (line.length <= endOff)
9949 ranges.push(EditorSelection.range(line.from + startOff, line.to + endOff));
9950 }
9951 }
9952 else {
9953 let startCol = Math.min(a.col, b.col), endCol = Math.max(a.col, b.col);
9954 for (let i = startLine; i <= endLine; i++) {
9955 let line = state.doc.line(i);
9956 let start = findColumn(line.text, startCol, state.tabSize, true);
9957 if (start < 0) {
9958 ranges.push(EditorSelection.cursor(line.to));
9959 }
9960 else {
9961 let end = findColumn(line.text, endCol, state.tabSize);
9962 ranges.push(EditorSelection.range(line.from + start, line.from + end));
9963 }
9964 }
9965 }
9966 return ranges;
9967}
9968function absoluteColumn(view, x) {
9969 let ref = view.coordsAtPos(view.viewport.from);
9970 return ref ? Math.round(Math.abs((ref.left - x) / view.defaultCharacterWidth)) : -1;
9971}
9972function getPos(view, event) {
9973 let offset = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
9974 let line = view.state.doc.lineAt(offset), off = offset - line.from;
9975 let col = off > MaxOff ? -1
9976 : off == line.length ? absoluteColumn(view, event.clientX)
9977 : countColumn(line.text, view.state.tabSize, offset - line.from);
9978 return { line: line.number, col, off };
9979}
9980function rectangleSelectionStyle(view, event) {
9981 let start = getPos(view, event), startSel = view.state.selection;
9982 if (!start)
9983 return null;
9984 return {
9985 update(update) {
9986 if (update.docChanged) {
9987 let newStart = update.changes.mapPos(update.startState.doc.line(start.line).from);
9988 let newLine = update.state.doc.lineAt(newStart);
9989 start = { line: newLine.number, col: start.col, off: Math.min(start.off, newLine.length) };
9990 startSel = startSel.map(update.changes);
9991 }
9992 },
9993 get(event, _extend, multiple) {
9994 let cur = getPos(view, event);
9995 if (!cur)
9996 return startSel;
9997 let ranges = rectangleFor(view.state, start, cur);
9998 if (!ranges.length)
9999 return startSel;
10000 if (multiple)
10001 return EditorSelection.create(ranges.concat(startSel.ranges));
10002 else
10003 return EditorSelection.create(ranges);
10004 }
10005 };
10006}
10007/**
10008Create an extension that enables rectangular selections. By
10009default, it will react to left mouse drag with the Alt key held
10010down. When such a selection occurs, the text within the rectangle
10011that was dragged over will be selected, as one selection
10012[range](https://codemirror.net/6/docs/ref/#state.SelectionRange) per line.
10013*/
10014function rectangularSelection(options) {
10015 let filter = (options === null || options === void 0 ? void 0 : options.eventFilter) || (e => e.altKey && e.button == 0);
10016 return EditorView.mouseSelectionStyle.of((view, event) => filter(event) ? rectangleSelectionStyle(view, event) : null);
10017}
10018const keys = {
10019 Alt: [18, e => !!e.altKey],
10020 Control: [17, e => !!e.ctrlKey],
10021 Shift: [16, e => !!e.shiftKey],
10022 Meta: [91, e => !!e.metaKey]
10023};
10024const showCrosshair = { style: "cursor: crosshair" };
10025/**
10026Returns an extension that turns the pointer cursor into a
10027crosshair when a given modifier key, defaulting to Alt, is held
10028down. Can serve as a visual hint that rectangular selection is
10029going to happen when paired with
10030[`rectangularSelection`](https://codemirror.net/6/docs/ref/#view.rectangularSelection).
10031*/
10032function crosshairCursor(options = {}) {
10033 let [code, getter] = keys[options.key || "Alt"];
10034 let plugin = ViewPlugin.fromClass(class {
10035 constructor(view) {
10036 this.view = view;
10037 this.isDown = false;
10038 }
10039 set(isDown) {
10040 if (this.isDown != isDown) {
10041 this.isDown = isDown;
10042 this.view.update([]);
10043 }
10044 }
10045 }, {
10046 eventObservers: {
10047 keydown(e) {
10048 this.set(e.keyCode == code || getter(e));
10049 },
10050 keyup(e) {
10051 if (e.keyCode == code || !getter(e))
10052 this.set(false);
10053 },
10054 mousemove(e) {
10055 this.set(getter(e));
10056 }
10057 }
10058 });
10059 return [
10060 plugin,
10061 EditorView.contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.isDown) ? showCrosshair : null; })
10062 ];
10063}
10064
10065const Outside = "-10000px";
10066class TooltipViewManager {
10067 constructor(view, facet, createTooltipView, removeTooltipView) {
10068 this.facet = facet;
10069 this.createTooltipView = createTooltipView;
10070 this.removeTooltipView = removeTooltipView;
10071 this.input = view.state.facet(facet);
10072 this.tooltips = this.input.filter(t => t);
10073 let prev = null;
10074 this.tooltipViews = this.tooltips.map(t => prev = createTooltipView(t, prev));
10075 }
10076 update(update, above) {
10077 var _a;
10078 let input = update.state.facet(this.facet);
10079 let tooltips = input.filter(x => x);
10080 if (input === this.input) {
10081 for (let t of this.tooltipViews)
10082 if (t.update)
10083 t.update(update);
10084 return false;
10085 }
10086 let tooltipViews = [], newAbove = above ? [] : null;
10087 for (let i = 0; i < tooltips.length; i++) {
10088 let tip = tooltips[i], known = -1;
10089 if (!tip)
10090 continue;
10091 for (let i = 0; i < this.tooltips.length; i++) {
10092 let other = this.tooltips[i];
10093 if (other && other.create == tip.create)
10094 known = i;
10095 }
10096 if (known < 0) {
10097 tooltipViews[i] = this.createTooltipView(tip, i ? tooltipViews[i - 1] : null);
10098 if (newAbove)
10099 newAbove[i] = !!tip.above;
10100 }
10101 else {
10102 let tooltipView = tooltipViews[i] = this.tooltipViews[known];
10103 if (newAbove)
10104 newAbove[i] = above[known];
10105 if (tooltipView.update)
10106 tooltipView.update(update);
10107 }
10108 }
10109 for (let t of this.tooltipViews)
10110 if (tooltipViews.indexOf(t) < 0) {
10111 this.removeTooltipView(t);
10112 (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
10113 }
10114 if (above) {
10115 newAbove.forEach((val, i) => above[i] = val);
10116 above.length = newAbove.length;
10117 }
10118 this.input = input;
10119 this.tooltips = tooltips;
10120 this.tooltipViews = tooltipViews;
10121 return true;
10122 }
10123}
10124/**
10125Creates an extension that configures tooltip behavior.
10126*/
10127function tooltips(config = {}) {
10128 return tooltipConfig.of(config);
10129}
10130function windowSpace(view) {
10131 let docElt = view.dom.ownerDocument.documentElement;
10132 return { top: 0, left: 0, bottom: docElt.clientHeight, right: docElt.clientWidth };
10133}
10134const tooltipConfig = /*@__PURE__*/Facet.define({
10135 combine: values => {
10136 var _a, _b, _c;
10137 return ({
10138 position: browser.ios ? "absolute" : ((_a = values.find(conf => conf.position)) === null || _a === void 0 ? void 0 : _a.position) || "fixed",
10139 parent: ((_b = values.find(conf => conf.parent)) === null || _b === void 0 ? void 0 : _b.parent) || null,
10140 tooltipSpace: ((_c = values.find(conf => conf.tooltipSpace)) === null || _c === void 0 ? void 0 : _c.tooltipSpace) || windowSpace,
10141 });
10142 }
10143});
10144const knownHeight = /*@__PURE__*/new WeakMap();
10145const tooltipPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
10146 constructor(view) {
10147 this.view = view;
10148 this.above = [];
10149 this.inView = true;
10150 this.madeAbsolute = false;
10151 this.lastTransaction = 0;
10152 this.measureTimeout = -1;
10153 let config = view.state.facet(tooltipConfig);
10154 this.position = config.position;
10155 this.parent = config.parent;
10156 this.classes = view.themeClasses;
10157 this.createContainer();
10158 this.measureReq = { read: this.readMeasure.bind(this), write: this.writeMeasure.bind(this), key: this };
10159 this.resizeObserver = typeof ResizeObserver == "function" ? new ResizeObserver(() => this.measureSoon()) : null;
10160 this.manager = new TooltipViewManager(view, showTooltip, (t, p) => this.createTooltip(t, p), t => {
10161 if (this.resizeObserver)
10162 this.resizeObserver.unobserve(t.dom);
10163 t.dom.remove();
10164 });
10165 this.above = this.manager.tooltips.map(t => !!t.above);
10166 this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver(entries => {
10167 if (Date.now() > this.lastTransaction - 50 &&
10168 entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
10169 this.measureSoon();
10170 }, { threshold: [1] }) : null;
10171 this.observeIntersection();
10172 view.win.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
10173 this.maybeMeasure();
10174 }
10175 createContainer() {
10176 if (this.parent) {
10177 this.container = document.createElement("div");
10178 this.container.style.position = "relative";
10179 this.container.className = this.view.themeClasses;
10180 this.parent.appendChild(this.container);
10181 }
10182 else {
10183 this.container = this.view.dom;
10184 }
10185 }
10186 observeIntersection() {
10187 if (this.intersectionObserver) {
10188 this.intersectionObserver.disconnect();
10189 for (let tooltip of this.manager.tooltipViews)
10190 this.intersectionObserver.observe(tooltip.dom);
10191 }
10192 }
10193 measureSoon() {
10194 if (this.measureTimeout < 0)
10195 this.measureTimeout = setTimeout(() => {
10196 this.measureTimeout = -1;
10197 this.maybeMeasure();
10198 }, 50);
10199 }
10200 update(update) {
10201 if (update.transactions.length)
10202 this.lastTransaction = Date.now();
10203 let updated = this.manager.update(update, this.above);
10204 if (updated)
10205 this.observeIntersection();
10206 let shouldMeasure = updated || update.geometryChanged;
10207 let newConfig = update.state.facet(tooltipConfig);
10208 if (newConfig.position != this.position && !this.madeAbsolute) {
10209 this.position = newConfig.position;
10210 for (let t of this.manager.tooltipViews)
10211 t.dom.style.position = this.position;
10212 shouldMeasure = true;
10213 }
10214 if (newConfig.parent != this.parent) {
10215 if (this.parent)
10216 this.container.remove();
10217 this.parent = newConfig.parent;
10218 this.createContainer();
10219 for (let t of this.manager.tooltipViews)
10220 this.container.appendChild(t.dom);
10221 shouldMeasure = true;
10222 }
10223 else if (this.parent && this.view.themeClasses != this.classes) {
10224 this.classes = this.container.className = this.view.themeClasses;
10225 }
10226 if (shouldMeasure)
10227 this.maybeMeasure();
10228 }
10229 createTooltip(tooltip, prev) {
10230 let tooltipView = tooltip.create(this.view);
10231 let before = prev ? prev.dom : null;
10232 tooltipView.dom.classList.add("cm-tooltip");
10233 if (tooltip.arrow && !tooltipView.dom.querySelector(".cm-tooltip > .cm-tooltip-arrow")) {
10234 let arrow = document.createElement("div");
10235 arrow.className = "cm-tooltip-arrow";
10236 tooltipView.dom.appendChild(arrow);
10237 }
10238 tooltipView.dom.style.position = this.position;
10239 tooltipView.dom.style.top = Outside;
10240 tooltipView.dom.style.left = "0px";
10241 this.container.insertBefore(tooltipView.dom, before);
10242 if (tooltipView.mount)
10243 tooltipView.mount(this.view);
10244 if (this.resizeObserver)
10245 this.resizeObserver.observe(tooltipView.dom);
10246 return tooltipView;
10247 }
10248 destroy() {
10249 var _a, _b, _c;
10250 this.view.win.removeEventListener("resize", this.measureSoon);
10251 for (let tooltipView of this.manager.tooltipViews) {
10252 tooltipView.dom.remove();
10253 (_a = tooltipView.destroy) === null || _a === void 0 ? void 0 : _a.call(tooltipView);
10254 }
10255 if (this.parent)
10256 this.container.remove();
10257 (_b = this.resizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
10258 (_c = this.intersectionObserver) === null || _c === void 0 ? void 0 : _c.disconnect();
10259 clearTimeout(this.measureTimeout);
10260 }
10261 readMeasure() {
10262 let scaleX = 1, scaleY = 1, makeAbsolute = false;
10263 if (this.position == "fixed" && this.manager.tooltipViews.length) {
10264 let { dom } = this.manager.tooltipViews[0];
10265 if (browser.safari) {
10266 // Safari always sets offsetParent to null, even if a fixed
10267 // element is positioned relative to a transformed parent. So
10268 // we use this kludge to try and detect this.
10269 let rect = dom.getBoundingClientRect();
10270 makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
10271 }
10272 else {
10273 // More conforming browsers will set offsetParent to the
10274 // transformed element.
10275 makeAbsolute = !!dom.offsetParent && dom.offsetParent != this.container.ownerDocument.body;
10276 }
10277 }
10278 if (makeAbsolute || this.position == "absolute") {
10279 if (this.parent) {
10280 let rect = this.parent.getBoundingClientRect();
10281 if (rect.width && rect.height) {
10282 scaleX = rect.width / this.parent.offsetWidth;
10283 scaleY = rect.height / this.parent.offsetHeight;
10284 }
10285 }
10286 else {
10287 ({ scaleX, scaleY } = this.view.viewState);
10288 }
10289 }
10290 let visible = this.view.scrollDOM.getBoundingClientRect(), margins = getScrollMargins(this.view);
10291 return {
10292 visible: {
10293 left: visible.left + margins.left, top: visible.top + margins.top,
10294 right: visible.right - margins.right, bottom: visible.bottom - margins.bottom
10295 },
10296 parent: this.parent ? this.container.getBoundingClientRect() : this.view.dom.getBoundingClientRect(),
10297 pos: this.manager.tooltips.map((t, i) => {
10298 let tv = this.manager.tooltipViews[i];
10299 return tv.getCoords ? tv.getCoords(t.pos) : this.view.coordsAtPos(t.pos);
10300 }),
10301 size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
10302 space: this.view.state.facet(tooltipConfig).tooltipSpace(this.view),
10303 scaleX, scaleY, makeAbsolute
10304 };
10305 }
10306 writeMeasure(measured) {
10307 var _a;
10308 if (measured.makeAbsolute) {
10309 this.madeAbsolute = true;
10310 this.position = "absolute";
10311 for (let t of this.manager.tooltipViews)
10312 t.dom.style.position = "absolute";
10313 }
10314 let { visible, space, scaleX, scaleY } = measured;
10315 let others = [];
10316 for (let i = 0; i < this.manager.tooltips.length; i++) {
10317 let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
10318 let pos = measured.pos[i], size = measured.size[i];
10319 // Hide tooltips that are outside of the editor.
10320 if (!pos || tooltip.clip !== false && (pos.bottom <= Math.max(visible.top, space.top) ||
10321 pos.top >= Math.min(visible.bottom, space.bottom) ||
10322 pos.right < Math.max(visible.left, space.left) - .1 ||
10323 pos.left > Math.min(visible.right, space.right) + .1)) {
10324 dom.style.top = Outside;
10325 continue;
10326 }
10327 let arrow = tooltip.arrow ? tView.dom.querySelector(".cm-tooltip-arrow") : null;
10328 let arrowHeight = arrow ? 7 /* Arrow.Size */ : 0;
10329 let width = size.right - size.left, height = (_a = knownHeight.get(tView)) !== null && _a !== void 0 ? _a : size.bottom - size.top;
10330 let offset = tView.offset || noOffset, ltr = this.view.textDirection == Direction.LTR;
10331 let left = size.width > space.right - space.left
10332 ? (ltr ? space.left : space.right - size.width)
10333 : ltr ? Math.max(space.left, Math.min(pos.left - (arrow ? 14 /* Arrow.Offset */ : 0) + offset.x, space.right - width))
10334 : Math.min(Math.max(space.left, pos.left - width + (arrow ? 14 /* Arrow.Offset */ : 0) - offset.x), space.right - width);
10335 let above = this.above[i];
10336 if (!tooltip.strictSide && (above
10337 ? pos.top - height - arrowHeight - offset.y < space.top
10338 : pos.bottom + height + arrowHeight + offset.y > space.bottom) &&
10339 above == (space.bottom - pos.bottom > pos.top - space.top))
10340 above = this.above[i] = !above;
10341 let spaceVert = (above ? pos.top - space.top : space.bottom - pos.bottom) - arrowHeight;
10342 if (spaceVert < height && tView.resize !== false) {
10343 if (spaceVert < this.view.defaultLineHeight) {
10344 dom.style.top = Outside;
10345 continue;
10346 }
10347 knownHeight.set(tView, height);
10348 dom.style.height = (height = spaceVert) / scaleY + "px";
10349 }
10350 else if (dom.style.height) {
10351 dom.style.height = "";
10352 }
10353 let top = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
10354 let right = left + width;
10355 if (tView.overlap !== true)
10356 for (let r of others)
10357 if (r.left < right && r.right > left && r.top < top + height && r.bottom > top)
10358 top = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
10359 if (this.position == "absolute") {
10360 dom.style.top = (top - measured.parent.top) / scaleY + "px";
10361 setLeftStyle(dom, (left - measured.parent.left) / scaleX);
10362 }
10363 else {
10364 dom.style.top = top / scaleY + "px";
10365 setLeftStyle(dom, left / scaleX);
10366 }
10367 if (arrow) {
10368 let arrowLeft = pos.left + (ltr ? offset.x : -offset.x) - (left + 14 /* Arrow.Offset */ - 7 /* Arrow.Size */);
10369 arrow.style.left = arrowLeft / scaleX + "px";
10370 }
10371 if (tView.overlap !== true)
10372 others.push({ left, top, right, bottom: top + height });
10373 dom.classList.toggle("cm-tooltip-above", above);
10374 dom.classList.toggle("cm-tooltip-below", !above);
10375 if (tView.positioned)
10376 tView.positioned(measured.space);
10377 }
10378 }
10379 maybeMeasure() {
10380 if (this.manager.tooltips.length) {
10381 if (this.view.inView)
10382 this.view.requestMeasure(this.measureReq);
10383 if (this.inView != this.view.inView) {
10384 this.inView = this.view.inView;
10385 if (!this.inView)
10386 for (let tv of this.manager.tooltipViews)
10387 tv.dom.style.top = Outside;
10388 }
10389 }
10390 }
10391}, {
10392 eventObservers: {
10393 scroll() { this.maybeMeasure(); }
10394 }
10395});
10396function setLeftStyle(elt, value) {
10397 let current = parseInt(elt.style.left, 10);
10398 if (isNaN(current) || Math.abs(value - current) > 1)
10399 elt.style.left = value + "px";
10400}
10401const baseTheme = /*@__PURE__*/EditorView.baseTheme({
10402 ".cm-tooltip": {
10403 zIndex: 500,
10404 boxSizing: "border-box"
10405 },
10406 "&light .cm-tooltip": {
10407 border: "1px solid #bbb",
10408 backgroundColor: "#f5f5f5"
10409 },
10410 "&light .cm-tooltip-section:not(:first-child)": {
10411 borderTop: "1px solid #bbb",
10412 },
10413 "&dark .cm-tooltip": {
10414 backgroundColor: "#333338",
10415 color: "white"
10416 },
10417 ".cm-tooltip-arrow": {
10418 height: `${7 /* Arrow.Size */}px`,
10419 width: `${7 /* Arrow.Size */ * 2}px`,
10420 position: "absolute",
10421 zIndex: -1,
10422 overflow: "hidden",
10423 "&:before, &:after": {
10424 content: "''",
10425 position: "absolute",
10426 width: 0,
10427 height: 0,
10428 borderLeft: `${7 /* Arrow.Size */}px solid transparent`,
10429 borderRight: `${7 /* Arrow.Size */}px solid transparent`,
10430 },
10431 ".cm-tooltip-above &": {
10432 bottom: `-${7 /* Arrow.Size */}px`,
10433 "&:before": {
10434 borderTop: `${7 /* Arrow.Size */}px solid #bbb`,
10435 },
10436 "&:after": {
10437 borderTop: `${7 /* Arrow.Size */}px solid #f5f5f5`,
10438 bottom: "1px"
10439 }
10440 },
10441 ".cm-tooltip-below &": {
10442 top: `-${7 /* Arrow.Size */}px`,
10443 "&:before": {
10444 borderBottom: `${7 /* Arrow.Size */}px solid #bbb`,
10445 },
10446 "&:after": {
10447 borderBottom: `${7 /* Arrow.Size */}px solid #f5f5f5`,
10448 top: "1px"
10449 }
10450 },
10451 },
10452 "&dark .cm-tooltip .cm-tooltip-arrow": {
10453 "&:before": {
10454 borderTopColor: "#333338",
10455 borderBottomColor: "#333338"
10456 },
10457 "&:after": {
10458 borderTopColor: "transparent",
10459 borderBottomColor: "transparent"
10460 }
10461 }
10462});
10463const noOffset = { x: 0, y: 0 };
10464/**
10465Facet to which an extension can add a value to show a tooltip.
10466*/
10467const showTooltip = /*@__PURE__*/Facet.define({
10468 enables: [tooltipPlugin, baseTheme]
10469});
10470const showHoverTooltip = /*@__PURE__*/Facet.define({
10471 combine: inputs => inputs.reduce((a, i) => a.concat(i), [])
10472});
10473class HoverTooltipHost {
10474 // Needs to be static so that host tooltip instances always match
10475 static create(view) {
10476 return new HoverTooltipHost(view);
10477 }
10478 constructor(view) {
10479 this.view = view;
10480 this.mounted = false;
10481 this.dom = document.createElement("div");
10482 this.dom.classList.add("cm-tooltip-hover");
10483 this.manager = new TooltipViewManager(view, showHoverTooltip, (t, p) => this.createHostedView(t, p), t => t.dom.remove());
10484 }
10485 createHostedView(tooltip, prev) {
10486 let hostedView = tooltip.create(this.view);
10487 hostedView.dom.classList.add("cm-tooltip-section");
10488 this.dom.insertBefore(hostedView.dom, prev ? prev.dom.nextSibling : this.dom.firstChild);
10489 if (this.mounted && hostedView.mount)
10490 hostedView.mount(this.view);
10491 return hostedView;
10492 }
10493 mount(view) {
10494 for (let hostedView of this.manager.tooltipViews) {
10495 if (hostedView.mount)
10496 hostedView.mount(view);
10497 }
10498 this.mounted = true;
10499 }
10500 positioned(space) {
10501 for (let hostedView of this.manager.tooltipViews) {
10502 if (hostedView.positioned)
10503 hostedView.positioned(space);
10504 }
10505 }
10506 update(update) {
10507 this.manager.update(update);
10508 }
10509 destroy() {
10510 var _a;
10511 for (let t of this.manager.tooltipViews)
10512 (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
10513 }
10514 passProp(name) {
10515 let value = undefined;
10516 for (let view of this.manager.tooltipViews) {
10517 let given = view[name];
10518 if (given !== undefined) {
10519 if (value === undefined)
10520 value = given;
10521 else if (value !== given)
10522 return undefined;
10523 }
10524 }
10525 return value;
10526 }
10527 get offset() { return this.passProp("offset"); }
10528 get getCoords() { return this.passProp("getCoords"); }
10529 get overlap() { return this.passProp("overlap"); }
10530 get resize() { return this.passProp("resize"); }
10531}
10532const showHoverTooltipHost = /*@__PURE__*/showTooltip.compute([showHoverTooltip], state => {
10533 let tooltips = state.facet(showHoverTooltip);
10534 if (tooltips.length === 0)
10535 return null;
10536 return {
10537 pos: Math.min(...tooltips.map(t => t.pos)),
10538 end: Math.max(...tooltips.map(t => { var _a; return (_a = t.end) !== null && _a !== void 0 ? _a : t.pos; })),
10539 create: HoverTooltipHost.create,
10540 above: tooltips[0].above,
10541 arrow: tooltips.some(t => t.arrow),
10542 };
10543});
10544class HoverPlugin {
10545 constructor(view, source, field, setHover, hoverTime) {
10546 this.view = view;
10547 this.source = source;
10548 this.field = field;
10549 this.setHover = setHover;
10550 this.hoverTime = hoverTime;
10551 this.hoverTimeout = -1;
10552 this.restartTimeout = -1;
10553 this.pending = null;
10554 this.lastMove = { x: 0, y: 0, target: view.dom, time: 0 };
10555 this.checkHover = this.checkHover.bind(this);
10556 view.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
10557 view.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
10558 }
10559 update() {
10560 if (this.pending) {
10561 this.pending = null;
10562 clearTimeout(this.restartTimeout);
10563 this.restartTimeout = setTimeout(() => this.startHover(), 20);
10564 }
10565 }
10566 get active() {
10567 return this.view.state.field(this.field);
10568 }
10569 checkHover() {
10570 this.hoverTimeout = -1;
10571 if (this.active.length)
10572 return;
10573 let hovered = Date.now() - this.lastMove.time;
10574 if (hovered < this.hoverTime)
10575 this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
10576 else
10577 this.startHover();
10578 }
10579 startHover() {
10580 clearTimeout(this.restartTimeout);
10581 let { view, lastMove } = this;
10582 let tile = view.docView.tile.nearest(lastMove.target);
10583 if (!tile)
10584 return;
10585 let pos, side = 1;
10586 if (tile.isWidget()) {
10587 pos = tile.posAtStart;
10588 }
10589 else {
10590 pos = view.posAtCoords(lastMove);
10591 if (pos == null)
10592 return;
10593 let posCoords = view.coordsAtPos(pos);
10594 if (!posCoords ||
10595 lastMove.y < posCoords.top || lastMove.y > posCoords.bottom ||
10596 lastMove.x < posCoords.left - view.defaultCharacterWidth ||
10597 lastMove.x > posCoords.right + view.defaultCharacterWidth)
10598 return;
10599 let bidi = view.bidiSpans(view.state.doc.lineAt(pos)).find(s => s.from <= pos && s.to >= pos);
10600 let rtl = bidi && bidi.dir == Direction.RTL ? -1 : 1;
10601 side = (lastMove.x < posCoords.left ? -rtl : rtl);
10602 }
10603 let open = this.source(view, pos, side);
10604 if (open === null || open === void 0 ? void 0 : open.then) {
10605 let pending = this.pending = { pos };
10606 open.then(result => {
10607 if (this.pending == pending) {
10608 this.pending = null;
10609 if (result && !(Array.isArray(result) && !result.length))
10610 view.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
10611 }
10612 }, e => logException(view.state, e, "hover tooltip"));
10613 }
10614 else if (open && !(Array.isArray(open) && !open.length)) {
10615 view.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
10616 }
10617 }
10618 get tooltip() {
10619 let plugin = this.view.plugin(tooltipPlugin);
10620 let index = plugin ? plugin.manager.tooltips.findIndex(t => t.create == HoverTooltipHost.create) : -1;
10621 return index > -1 ? plugin.manager.tooltipViews[index] : null;
10622 }
10623 mousemove(event) {
10624 var _a, _b;
10625 this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
10626 if (this.hoverTimeout < 0)
10627 this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
10628 let { active, tooltip } = this;
10629 if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
10630 let { pos } = active[0] || this.pending, end = (_b = (_a = active[0]) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : pos;
10631 if ((pos == end ? this.view.posAtCoords(this.lastMove) != pos
10632 : !isOverRange(this.view, pos, end, event.clientX, event.clientY))) {
10633 this.view.dispatch({ effects: this.setHover.of([]) });
10634 this.pending = null;
10635 }
10636 }
10637 }
10638 mouseleave(event) {
10639 clearTimeout(this.hoverTimeout);
10640 this.hoverTimeout = -1;
10641 let { active } = this;
10642 if (active.length) {
10643 let { tooltip } = this;
10644 let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
10645 if (!inTooltip)
10646 this.view.dispatch({ effects: this.setHover.of([]) });
10647 else
10648 this.watchTooltipLeave(tooltip.dom);
10649 }
10650 }
10651 watchTooltipLeave(tooltip) {
10652 let watch = (event) => {
10653 tooltip.removeEventListener("mouseleave", watch);
10654 if (this.active.length && !this.view.dom.contains(event.relatedTarget))
10655 this.view.dispatch({ effects: this.setHover.of([]) });
10656 };
10657 tooltip.addEventListener("mouseleave", watch);
10658 }
10659 destroy() {
10660 clearTimeout(this.hoverTimeout);
10661 clearTimeout(this.restartTimeout);
10662 this.view.dom.removeEventListener("mouseleave", this.mouseleave);
10663 this.view.dom.removeEventListener("mousemove", this.mousemove);
10664 }
10665}
10666const tooltipMargin = 4;
10667function isInTooltip(tooltip, event) {
10668 let { left, right, top, bottom } = tooltip.getBoundingClientRect(), arrow;
10669 if (arrow = tooltip.querySelector(".cm-tooltip-arrow")) {
10670 let arrowRect = arrow.getBoundingClientRect();
10671 top = Math.min(arrowRect.top, top);
10672 bottom = Math.max(arrowRect.bottom, bottom);
10673 }
10674 return event.clientX >= left - tooltipMargin && event.clientX <= right + tooltipMargin &&
10675 event.clientY >= top - tooltipMargin && event.clientY <= bottom + tooltipMargin;
10676}
10677function isOverRange(view, from, to, x, y, margin) {
10678 let rect = view.scrollDOM.getBoundingClientRect();
10679 let docBottom = view.documentTop + view.documentPadding.top + view.contentHeight;
10680 if (rect.left > x || rect.right < x || rect.top > y || Math.min(rect.bottom, docBottom) < y)
10681 return false;
10682 let pos = view.posAtCoords({ x, y }, false);
10683 return pos >= from && pos <= to;
10684}
10685/**
10686Set up a hover tooltip, which shows up when the pointer hovers
10687over ranges of text. The callback is called when the mouse hovers
10688over the document text. It should, if there is a tooltip
10689associated with position `pos`, return the tooltip description
10690(either directly or in a promise). The `side` argument indicates
10691on which side of the position the pointer is—it will be -1 if the
10692pointer is before the position, 1 if after the position.
10693
10694Note that all hover tooltips are hosted within a single tooltip
10695container element. This allows multiple tooltips over the same
10696range to be "merged" together without overlapping.
10697
10698The return value is a valid [editor extension](https://codemirror.net/6/docs/ref/#state.Extension)
10699but also provides an `active` property holding a state field that
10700can be used to read the currently active tooltips produced by this
10701extension.
10702*/
10703function hoverTooltip(source, options = {}) {
10704 let setHover = StateEffect.define();
10705 let hoverState = StateField.define({
10706 create() { return []; },
10707 update(value, tr) {
10708 if (value.length) {
10709 if (options.hideOnChange && (tr.docChanged || tr.selection))
10710 value = [];
10711 else if (options.hideOn)
10712 value = value.filter(v => !options.hideOn(tr, v));
10713 if (tr.docChanged) {
10714 let mapped = [];
10715 for (let tooltip of value) {
10716 let newPos = tr.changes.mapPos(tooltip.pos, -1, MapMode.TrackDel);
10717 if (newPos != null) {
10718 let copy = Object.assign(Object.create(null), tooltip);
10719 copy.pos = newPos;
10720 if (copy.end != null)
10721 copy.end = tr.changes.mapPos(copy.end);
10722 mapped.push(copy);
10723 }
10724 }
10725 value = mapped;
10726 }
10727 }
10728 for (let effect of tr.effects) {
10729 if (effect.is(setHover))
10730 value = effect.value;
10731 if (effect.is(closeHoverTooltipEffect))
10732 value = [];
10733 }
10734 return value;
10735 },
10736 provide: f => showHoverTooltip.from(f)
10737 });
10738 return {
10739 active: hoverState,
10740 extension: [
10741 hoverState,
10742 ViewPlugin.define(view => new HoverPlugin(view, source, hoverState, setHover, options.hoverTime || 300 /* Hover.Time */)),
10743 showHoverTooltipHost
10744 ]
10745 };
10746}
10747/**
10748Get the active tooltip view for a given tooltip, if available.
10749*/
10750function getTooltip(view, tooltip) {
10751 let plugin = view.plugin(tooltipPlugin);
10752 if (!plugin)
10753 return null;
10754 let found = plugin.manager.tooltips.indexOf(tooltip);
10755 return found < 0 ? null : plugin.manager.tooltipViews[found];
10756}
10757/**
10758Returns true if any hover tooltips are currently active.
10759*/
10760function hasHoverTooltips(state) {
10761 return state.facet(showHoverTooltip).some(x => x);
10762}
10763const closeHoverTooltipEffect = /*@__PURE__*/StateEffect.define();
10764/**
10765Transaction effect that closes all hover tooltips.
10766*/
10767const closeHoverTooltips = /*@__PURE__*/closeHoverTooltipEffect.of(null);
10768/**
10769Tell the tooltip extension to recompute the position of the active
10770tooltips. This can be useful when something happens (such as a
10771re-positioning or CSS change affecting the editor) that could
10772invalidate the existing tooltip positions.
10773*/
10774function repositionTooltips(view) {
10775 let plugin = view.plugin(tooltipPlugin);
10776 if (plugin)
10777 plugin.maybeMeasure();
10778}
10779
10780const panelConfig = /*@__PURE__*/Facet.define({
10781 combine(configs) {
10782 let topContainer, bottomContainer;
10783 for (let c of configs) {
10784 topContainer = topContainer || c.topContainer;
10785 bottomContainer = bottomContainer || c.bottomContainer;
10786 }
10787 return { topContainer, bottomContainer };
10788 }
10789});
10790/**
10791Configures the panel-managing extension.
10792*/
10793function panels(config) {
10794 return config ? [panelConfig.of(config)] : [];
10795}
10796/**
10797Get the active panel created by the given constructor, if any.
10798This can be useful when you need access to your panels' DOM
10799structure.
10800*/
10801function getPanel(view, panel) {
10802 let plugin = view.plugin(panelPlugin);
10803 let index = plugin ? plugin.specs.indexOf(panel) : -1;
10804 return index > -1 ? plugin.panels[index] : null;
10805}
10806const panelPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
10807 constructor(view) {
10808 this.input = view.state.facet(showPanel);
10809 this.specs = this.input.filter(s => s);
10810 this.panels = this.specs.map(spec => spec(view));
10811 let conf = view.state.facet(panelConfig);
10812 this.top = new PanelGroup(view, true, conf.topContainer);
10813 this.bottom = new PanelGroup(view, false, conf.bottomContainer);
10814 this.top.sync(this.panels.filter(p => p.top));
10815 this.bottom.sync(this.panels.filter(p => !p.top));
10816 for (let p of this.panels) {
10817 p.dom.classList.add("cm-panel");
10818 if (p.mount)
10819 p.mount();
10820 }
10821 }
10822 update(update) {
10823 let conf = update.state.facet(panelConfig);
10824 if (this.top.container != conf.topContainer) {
10825 this.top.sync([]);
10826 this.top = new PanelGroup(update.view, true, conf.topContainer);
10827 }
10828 if (this.bottom.container != conf.bottomContainer) {
10829 this.bottom.sync([]);
10830 this.bottom = new PanelGroup(update.view, false, conf.bottomContainer);
10831 }
10832 this.top.syncClasses();
10833 this.bottom.syncClasses();
10834 let input = update.state.facet(showPanel);
10835 if (input != this.input) {
10836 let specs = input.filter(x => x);
10837 let panels = [], top = [], bottom = [], mount = [];
10838 for (let spec of specs) {
10839 let known = this.specs.indexOf(spec), panel;
10840 if (known < 0) {
10841 panel = spec(update.view);
10842 mount.push(panel);
10843 }
10844 else {
10845 panel = this.panels[known];
10846 if (panel.update)
10847 panel.update(update);
10848 }
10849 panels.push(panel);
10850 (panel.top ? top : bottom).push(panel);
10851 }
10852 this.specs = specs;
10853 this.panels = panels;
10854 this.top.sync(top);
10855 this.bottom.sync(bottom);
10856 for (let p of mount) {
10857 p.dom.classList.add("cm-panel");
10858 if (p.mount)
10859 p.mount();
10860 }
10861 }
10862 else {
10863 for (let p of this.panels)
10864 if (p.update)
10865 p.update(update);
10866 }
10867 }
10868 destroy() {
10869 this.top.sync([]);
10870 this.bottom.sync([]);
10871 }
10872}, {
10873 provide: plugin => EditorView.scrollMargins.of(view => {
10874 let value = view.plugin(plugin);
10875 return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
10876 })
10877});
10878class PanelGroup {
10879 constructor(view, top, container) {
10880 this.view = view;
10881 this.top = top;
10882 this.container = container;
10883 this.dom = undefined;
10884 this.classes = "";
10885 this.panels = [];
10886 this.syncClasses();
10887 }
10888 sync(panels) {
10889 for (let p of this.panels)
10890 if (p.destroy && panels.indexOf(p) < 0)
10891 p.destroy();
10892 this.panels = panels;
10893 this.syncDOM();
10894 }
10895 syncDOM() {
10896 if (this.panels.length == 0) {
10897 if (this.dom) {
10898 this.dom.remove();
10899 this.dom = undefined;
10900 }
10901 return;
10902 }
10903 if (!this.dom) {
10904 this.dom = document.createElement("div");
10905 this.dom.className = this.top ? "cm-panels cm-panels-top" : "cm-panels cm-panels-bottom";
10906 this.dom.style[this.top ? "top" : "bottom"] = "0";
10907 let parent = this.container || this.view.dom;
10908 parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
10909 }
10910 let curDOM = this.dom.firstChild;
10911 for (let panel of this.panels) {
10912 if (panel.dom.parentNode == this.dom) {
10913 while (curDOM != panel.dom)
10914 curDOM = rm(curDOM);
10915 curDOM = curDOM.nextSibling;
10916 }
10917 else {
10918 this.dom.insertBefore(panel.dom, curDOM);
10919 }
10920 }
10921 while (curDOM)
10922 curDOM = rm(curDOM);
10923 }
10924 scrollMargin() {
10925 return !this.dom || this.container ? 0
10926 : Math.max(0, this.top ?
10927 this.dom.getBoundingClientRect().bottom - Math.max(0, this.view.scrollDOM.getBoundingClientRect().top) :
10928 Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
10929 }
10930 syncClasses() {
10931 if (!this.container || this.classes == this.view.themeClasses)
10932 return;
10933 for (let cls of this.classes.split(" "))
10934 if (cls)
10935 this.container.classList.remove(cls);
10936 for (let cls of (this.classes = this.view.themeClasses).split(" "))
10937 if (cls)
10938 this.container.classList.add(cls);
10939 }
10940}
10941function rm(node) {
10942 let next = node.nextSibling;
10943 node.remove();
10944 return next;
10945}
10946/**
10947Opening a panel is done by providing a constructor function for
10948the panel through this facet. (The panel is closed again when its
10949constructor is no longer provided.) Values of `null` are ignored.
10950*/
10951const showPanel = /*@__PURE__*/Facet.define({
10952 enables: panelPlugin
10953});
10954
10955/**
10956Show a panel above or below the editor to show the user a message
10957or prompt them for input. Returns an effect that can be dispatched
10958to close the dialog, and a promise that resolves when the dialog
10959is closed or a form inside of it is submitted.
10960
10961You are encouraged, if your handling of the result of the promise
10962dispatches a transaction, to include the `close` effect in it. If
10963you don't, this function will automatically dispatch a separate
10964transaction right after.
10965*/
10966function showDialog(view, config) {
10967 let resolve;
10968 let promise = new Promise(r => resolve = r);
10969 let panelCtor = (view) => createDialog(view, config, resolve);
10970 if (view.state.field(dialogField, false)) {
10971 view.dispatch({ effects: openDialogEffect.of(panelCtor) });
10972 }
10973 else {
10974 view.dispatch({ effects: StateEffect.appendConfig.of(dialogField.init(() => [panelCtor])) });
10975 }
10976 let close = closeDialogEffect.of(panelCtor);
10977 return { close, result: promise.then(form => {
10978 let queue = view.win.queueMicrotask || ((f) => view.win.setTimeout(f, 10));
10979 queue(() => {
10980 if (view.state.field(dialogField).indexOf(panelCtor) > -1)
10981 view.dispatch({ effects: close });
10982 });
10983 return form;
10984 }) };
10985}
10986/**
10987Find the [`Panel`](https://codemirror.net/6/docs/ref/#view.Panel) for an open dialog, using a class
10988name as identifier.
10989*/
10990function getDialog(view, className) {
10991 let dialogs = view.state.field(dialogField, false) || [];
10992 for (let open of dialogs) {
10993 let panel = getPanel(view, open);
10994 if (panel && panel.dom.classList.contains(className))
10995 return panel;
10996 }
10997 return null;
10998}
10999const dialogField = /*@__PURE__*/StateField.define({
11000 create() { return []; },
11001 update(dialogs, tr) {
11002 for (let e of tr.effects) {
11003 if (e.is(openDialogEffect))
11004 dialogs = [e.value].concat(dialogs);
11005 else if (e.is(closeDialogEffect))
11006 dialogs = dialogs.filter(d => d != e.value);
11007 }
11008 return dialogs;
11009 },
11010 provide: f => showPanel.computeN([f], state => state.field(f))
11011});
11012const openDialogEffect = /*@__PURE__*/StateEffect.define();
11013const closeDialogEffect = /*@__PURE__*/StateEffect.define();
11014function createDialog(view, config, result) {
11015 let content = config.content ? config.content(view, () => done(null)) : null;
11016 if (!content) {
11017 content = elt("form");
11018 if (config.input) {
11019 let input = elt("input", config.input);
11020 if (/^(text|password|number|email|tel|url)$/.test(input.type))
11021 input.classList.add("cm-textfield");
11022 if (!input.name)
11023 input.name = "input";
11024 content.appendChild(elt("label", (config.label || "") + ": ", input));
11025 }
11026 else {
11027 content.appendChild(document.createTextNode(config.label || ""));
11028 }
11029 content.appendChild(document.createTextNode(" "));
11030 content.appendChild(elt("button", { class: "cm-button", type: "submit" }, config.submitLabel || "OK"));
11031 }
11032 let forms = content.nodeName == "FORM" ? [content] : content.querySelectorAll("form");
11033 for (let i = 0; i < forms.length; i++) {
11034 let form = forms[i];
11035 form.addEventListener("keydown", (event) => {
11036 if (event.keyCode == 27) { // Escape
11037 event.preventDefault();
11038 done(null);
11039 }
11040 else if (event.keyCode == 13) { // Enter
11041 event.preventDefault();
11042 done(form);
11043 }
11044 });
11045 form.addEventListener("submit", (event) => {
11046 event.preventDefault();
11047 done(form);
11048 });
11049 }
11050 let panel = elt("div", content, elt("button", {
11051 onclick: () => done(null),
11052 "aria-label": view.state.phrase("close"),
11053 class: "cm-dialog-close",
11054 type: "button"
11055 }, ["×"]));
11056 if (config.class)
11057 panel.className = config.class;
11058 panel.classList.add("cm-dialog");
11059 function done(form) {
11060 if (panel.contains(panel.ownerDocument.activeElement))
11061 view.focus();
11062 result(form);
11063 }
11064 return {
11065 dom: panel,
11066 top: config.top,
11067 mount: () => {
11068 if (config.focus) {
11069 let focus;
11070 if (typeof config.focus == "string")
11071 focus = content.querySelector(config.focus);
11072 else
11073 focus = content.querySelector("input") || content.querySelector("button");
11074 if (focus && "select" in focus)
11075 focus.select();
11076 else if (focus && "focus" in focus)
11077 focus.focus();
11078 }
11079 }
11080 };
11081}
11082
11083/**
11084A gutter marker represents a bit of information attached to a line
11085in a specific gutter. Your own custom markers have to extend this
11086class.
11087*/
11088class GutterMarker extends RangeValue {
11089 /**
11090 @internal
11091 */
11092 compare(other) {
11093 return this == other || this.constructor == other.constructor && this.eq(other);
11094 }
11095 /**
11096 Compare this marker to another marker of the same type.
11097 */
11098 eq(other) { return false; }
11099 /**
11100 Called if the marker has a `toDOM` method and its representation
11101 was removed from a gutter.
11102 */
11103 destroy(dom) { }
11104}
11105GutterMarker.prototype.elementClass = "";
11106GutterMarker.prototype.toDOM = undefined;
11107GutterMarker.prototype.mapMode = MapMode.TrackBefore;
11108GutterMarker.prototype.startSide = GutterMarker.prototype.endSide = -1;
11109GutterMarker.prototype.point = true;
11110/**
11111Facet used to add a class to all gutter elements for a given line.
11112Markers given to this facet should _only_ define an
11113[`elementclass`](https://codemirror.net/6/docs/ref/#view.GutterMarker.elementClass), not a
11114[`toDOM`](https://codemirror.net/6/docs/ref/#view.GutterMarker.toDOM) (or the marker will appear
11115in all gutters for the line).
11116*/
11117const gutterLineClass = /*@__PURE__*/Facet.define();
11118/**
11119Facet used to add a class to all gutter elements next to a widget.
11120Should not provide widgets with a `toDOM` method.
11121*/
11122const gutterWidgetClass = /*@__PURE__*/Facet.define();
11123const defaults = {
11124 class: "",
11125 renderEmptyElements: false,
11126 elementStyle: "",
11127 markers: () => RangeSet.empty,
11128 lineMarker: () => null,
11129 widgetMarker: () => null,
11130 lineMarkerChange: null,
11131 initialSpacer: null,
11132 updateSpacer: null,
11133 domEventHandlers: {},
11134 side: "before"
11135};
11136const activeGutters = /*@__PURE__*/Facet.define();
11137/**
11138Define an editor gutter. The order in which the gutters appear is
11139determined by their extension priority.
11140*/
11141function gutter(config) {
11142 return [gutters(), activeGutters.of({ ...defaults, ...config })];
11143}
11144const unfixGutters = /*@__PURE__*/Facet.define({
11145 combine: values => values.some(x => x)
11146});
11147/**
11148The gutter-drawing plugin is automatically enabled when you add a
11149gutter, but you can use this function to explicitly configure it.
11150
11151Unless `fixed` is explicitly set to `false`, the gutters are
11152fixed, meaning they don't scroll along with the content
11153horizontally (except on Internet Explorer, which doesn't support
11154CSS [`position:
11155sticky`](https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky)).
11156*/
11157function gutters(config) {
11158 let result = [
11159 gutterView,
11160 ];
11161 if (config && config.fixed === false)
11162 result.push(unfixGutters.of(true));
11163 return result;
11164}
11165const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
11166 constructor(view) {
11167 this.view = view;
11168 this.domAfter = null;
11169 this.prevViewport = view.viewport;
11170 this.dom = document.createElement("div");
11171 this.dom.className = "cm-gutters cm-gutters-before";
11172 this.dom.setAttribute("aria-hidden", "true");
11173 this.dom.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
11174 this.gutters = view.state.facet(activeGutters).map(conf => new SingleGutterView(view, conf));
11175 this.fixed = !view.state.facet(unfixGutters);
11176 for (let gutter of this.gutters) {
11177 if (gutter.config.side == "after")
11178 this.getDOMAfter().appendChild(gutter.dom);
11179 else
11180 this.dom.appendChild(gutter.dom);
11181 }
11182 if (this.fixed) {
11183 // FIXME IE11 fallback, which doesn't support position: sticky,
11184 // by using position: relative + event handlers that realign the
11185 // gutter (or just force fixed=false on IE11?)
11186 this.dom.style.position = "sticky";
11187 }
11188 this.syncGutters(false);
11189 view.scrollDOM.insertBefore(this.dom, view.contentDOM);
11190 }
11191 getDOMAfter() {
11192 if (!this.domAfter) {
11193 this.domAfter = document.createElement("div");
11194 this.domAfter.className = "cm-gutters cm-gutters-after";
11195 this.domAfter.setAttribute("aria-hidden", "true");
11196 this.domAfter.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
11197 this.domAfter.style.position = this.fixed ? "sticky" : "";
11198 this.view.scrollDOM.appendChild(this.domAfter);
11199 }
11200 return this.domAfter;
11201 }
11202 update(update) {
11203 if (this.updateGutters(update)) {
11204 // Detach during sync when the viewport changed significantly
11205 // (such as during scrolling), since for large updates that is
11206 // faster.
11207 let vpA = this.prevViewport, vpB = update.view.viewport;
11208 let vpOverlap = Math.min(vpA.to, vpB.to) - Math.max(vpA.from, vpB.from);
11209 this.syncGutters(vpOverlap < (vpB.to - vpB.from) * 0.8);
11210 }
11211 if (update.geometryChanged) {
11212 let min = (this.view.contentHeight / this.view.scaleY) + "px";
11213 this.dom.style.minHeight = min;
11214 if (this.domAfter)
11215 this.domAfter.style.minHeight = min;
11216 }
11217 if (this.view.state.facet(unfixGutters) != !this.fixed) {
11218 this.fixed = !this.fixed;
11219 this.dom.style.position = this.fixed ? "sticky" : "";
11220 if (this.domAfter)
11221 this.domAfter.style.position = this.fixed ? "sticky" : "";
11222 }
11223 this.prevViewport = update.view.viewport;
11224 }
11225 syncGutters(detach) {
11226 let after = this.dom.nextSibling;
11227 if (detach) {
11228 this.dom.remove();
11229 if (this.domAfter)
11230 this.domAfter.remove();
11231 }
11232 let lineClasses = RangeSet.iter(this.view.state.facet(gutterLineClass), this.view.viewport.from);
11233 let classSet = [];
11234 let contexts = this.gutters.map(gutter => new UpdateContext(gutter, this.view.viewport, -this.view.documentPadding.top));
11235 for (let line of this.view.viewportLineBlocks) {
11236 if (classSet.length)
11237 classSet = [];
11238 if (Array.isArray(line.type)) {
11239 let first = true;
11240 for (let b of line.type) {
11241 if (b.type == BlockType.Text && first) {
11242 advanceCursor(lineClasses, classSet, b.from);
11243 for (let cx of contexts)
11244 cx.line(this.view, b, classSet);
11245 first = false;
11246 }
11247 else if (b.widget) {
11248 for (let cx of contexts)
11249 cx.widget(this.view, b);
11250 }
11251 }
11252 }
11253 else if (line.type == BlockType.Text) {
11254 advanceCursor(lineClasses, classSet, line.from);
11255 for (let cx of contexts)
11256 cx.line(this.view, line, classSet);
11257 }
11258 else if (line.widget) {
11259 for (let cx of contexts)
11260 cx.widget(this.view, line);
11261 }
11262 }
11263 for (let cx of contexts)
11264 cx.finish();
11265 if (detach) {
11266 this.view.scrollDOM.insertBefore(this.dom, after);
11267 if (this.domAfter)
11268 this.view.scrollDOM.appendChild(this.domAfter);
11269 }
11270 }
11271 updateGutters(update) {
11272 let prev = update.startState.facet(activeGutters), cur = update.state.facet(activeGutters);
11273 let change = update.docChanged || update.heightChanged || update.viewportChanged ||
11274 !RangeSet.eq(update.startState.facet(gutterLineClass), update.state.facet(gutterLineClass), update.view.viewport.from, update.view.viewport.to);
11275 if (prev == cur) {
11276 for (let gutter of this.gutters)
11277 if (gutter.update(update))
11278 change = true;
11279 }
11280 else {
11281 change = true;
11282 let gutters = [];
11283 for (let conf of cur) {
11284 let known = prev.indexOf(conf);
11285 if (known < 0) {
11286 gutters.push(new SingleGutterView(this.view, conf));
11287 }
11288 else {
11289 this.gutters[known].update(update);
11290 gutters.push(this.gutters[known]);
11291 }
11292 }
11293 for (let g of this.gutters) {
11294 g.dom.remove();
11295 if (gutters.indexOf(g) < 0)
11296 g.destroy();
11297 }
11298 for (let g of gutters) {
11299 if (g.config.side == "after")
11300 this.getDOMAfter().appendChild(g.dom);
11301 else
11302 this.dom.appendChild(g.dom);
11303 }
11304 this.gutters = gutters;
11305 }
11306 return change;
11307 }
11308 destroy() {
11309 for (let view of this.gutters)
11310 view.destroy();
11311 this.dom.remove();
11312 if (this.domAfter)
11313 this.domAfter.remove();
11314 }
11315}, {
11316 provide: plugin => EditorView.scrollMargins.of(view => {
11317 let value = view.plugin(plugin);
11318 if (!value || value.gutters.length == 0 || !value.fixed)
11319 return null;
11320 let before = value.dom.offsetWidth * view.scaleX, after = value.domAfter ? value.domAfter.offsetWidth * view.scaleX : 0;
11321 return view.textDirection == Direction.LTR
11322 ? { left: before, right: after }
11323 : { right: before, left: after };
11324 })
11325});
11326function asArray(val) { return (Array.isArray(val) ? val : [val]); }
11327function advanceCursor(cursor, collect, pos) {
11328 while (cursor.value && cursor.from <= pos) {
11329 if (cursor.from == pos)
11330 collect.push(cursor.value);
11331 cursor.next();
11332 }
11333}
11334class UpdateContext {
11335 constructor(gutter, viewport, height) {
11336 this.gutter = gutter;
11337 this.height = height;
11338 this.i = 0;
11339 this.cursor = RangeSet.iter(gutter.markers, viewport.from);
11340 }
11341 addElement(view, block, markers) {
11342 let { gutter } = this, above = (block.top - this.height) / view.scaleY, height = block.height / view.scaleY;
11343 if (this.i == gutter.elements.length) {
11344 let newElt = new GutterElement(view, height, above, markers);
11345 gutter.elements.push(newElt);
11346 gutter.dom.appendChild(newElt.dom);
11347 }
11348 else {
11349 gutter.elements[this.i].update(view, height, above, markers);
11350 }
11351 this.height = block.bottom;
11352 this.i++;
11353 }
11354 line(view, line, extraMarkers) {
11355 let localMarkers = [];
11356 advanceCursor(this.cursor, localMarkers, line.from);
11357 if (extraMarkers.length)
11358 localMarkers = localMarkers.concat(extraMarkers);
11359 let forLine = this.gutter.config.lineMarker(view, line, localMarkers);
11360 if (forLine)
11361 localMarkers.unshift(forLine);
11362 let gutter = this.gutter;
11363 if (localMarkers.length == 0 && !gutter.config.renderEmptyElements)
11364 return;
11365 this.addElement(view, line, localMarkers);
11366 }
11367 widget(view, block) {
11368 let marker = this.gutter.config.widgetMarker(view, block.widget, block), markers = marker ? [marker] : null;
11369 for (let cls of view.state.facet(gutterWidgetClass)) {
11370 let marker = cls(view, block.widget, block);
11371 if (marker)
11372 (markers || (markers = [])).push(marker);
11373 }
11374 if (markers)
11375 this.addElement(view, block, markers);
11376 }
11377 finish() {
11378 let gutter = this.gutter;
11379 while (gutter.elements.length > this.i) {
11380 let last = gutter.elements.pop();
11381 gutter.dom.removeChild(last.dom);
11382 last.destroy();
11383 }
11384 }
11385}
11386class SingleGutterView {
11387 constructor(view, config) {
11388 this.view = view;
11389 this.config = config;
11390 this.elements = [];
11391 this.spacer = null;
11392 this.dom = document.createElement("div");
11393 this.dom.className = "cm-gutter" + (this.config.class ? " " + this.config.class : "");
11394 for (let prop in config.domEventHandlers) {
11395 this.dom.addEventListener(prop, (event) => {
11396 let target = event.target, y;
11397 if (target != this.dom && this.dom.contains(target)) {
11398 while (target.parentNode != this.dom)
11399 target = target.parentNode;
11400 let rect = target.getBoundingClientRect();
11401 y = (rect.top + rect.bottom) / 2;
11402 }
11403 else {
11404 y = event.clientY;
11405 }
11406 let line = view.lineBlockAtHeight(y - view.documentTop);
11407 if (config.domEventHandlers[prop](view, line, event))
11408 event.preventDefault();
11409 });
11410 }
11411 this.markers = asArray(config.markers(view));
11412 if (config.initialSpacer) {
11413 this.spacer = new GutterElement(view, 0, 0, [config.initialSpacer(view)]);
11414 this.dom.appendChild(this.spacer.dom);
11415 this.spacer.dom.style.cssText += "visibility: hidden; pointer-events: none";
11416 }
11417 }
11418 update(update) {
11419 let prevMarkers = this.markers;
11420 this.markers = asArray(this.config.markers(update.view));
11421 if (this.spacer && this.config.updateSpacer) {
11422 let updated = this.config.updateSpacer(this.spacer.markers[0], update);
11423 if (updated != this.spacer.markers[0])
11424 this.spacer.update(update.view, 0, 0, [updated]);
11425 }
11426 let vp = update.view.viewport;
11427 return !RangeSet.eq(this.markers, prevMarkers, vp.from, vp.to) ||
11428 (this.config.lineMarkerChange ? this.config.lineMarkerChange(update) : false);
11429 }
11430 destroy() {
11431 for (let elt of this.elements)
11432 elt.destroy();
11433 }
11434}
11435class GutterElement {
11436 constructor(view, height, above, markers) {
11437 this.height = -1;
11438 this.above = 0;
11439 this.markers = [];
11440 this.dom = document.createElement("div");
11441 this.dom.className = "cm-gutterElement";
11442 this.update(view, height, above, markers);
11443 }
11444 update(view, height, above, markers) {
11445 if (this.height != height) {
11446 this.height = height;
11447 this.dom.style.height = height + "px";
11448 }
11449 if (this.above != above)
11450 this.dom.style.marginTop = (this.above = above) ? above + "px" : "";
11451 if (!sameMarkers(this.markers, markers))
11452 this.setMarkers(view, markers);
11453 }
11454 setMarkers(view, markers) {
11455 let cls = "cm-gutterElement", domPos = this.dom.firstChild;
11456 for (let iNew = 0, iOld = 0;;) {
11457 let skipTo = iOld, marker = iNew < markers.length ? markers[iNew++] : null, matched = false;
11458 if (marker) {
11459 let c = marker.elementClass;
11460 if (c)
11461 cls += " " + c;
11462 for (let i = iOld; i < this.markers.length; i++)
11463 if (this.markers[i].compare(marker)) {
11464 skipTo = i;
11465 matched = true;
11466 break;
11467 }
11468 }
11469 else {
11470 skipTo = this.markers.length;
11471 }
11472 while (iOld < skipTo) {
11473 let next = this.markers[iOld++];
11474 if (next.toDOM) {
11475 next.destroy(domPos);
11476 let after = domPos.nextSibling;
11477 domPos.remove();
11478 domPos = after;
11479 }
11480 }
11481 if (!marker)
11482 break;
11483 if (marker.toDOM) {
11484 if (matched)
11485 domPos = domPos.nextSibling;
11486 else
11487 this.dom.insertBefore(marker.toDOM(view), domPos);
11488 }
11489 if (matched)
11490 iOld++;
11491 }
11492 this.dom.className = cls;
11493 this.markers = markers;
11494 }
11495 destroy() {
11496 this.setMarkers(null, []); // First argument not used unless creating markers
11497 }
11498}
11499function sameMarkers(a, b) {
11500 if (a.length != b.length)
11501 return false;
11502 for (let i = 0; i < a.length; i++)
11503 if (!a[i].compare(b[i]))
11504 return false;
11505 return true;
11506}
11507/**
11508Facet used to provide markers to the line number gutter.
11509*/
11510const lineNumberMarkers = /*@__PURE__*/Facet.define();
11511/**
11512Facet used to create markers in the line number gutter next to widgets.
11513*/
11514const lineNumberWidgetMarker = /*@__PURE__*/Facet.define();
11515const lineNumberConfig = /*@__PURE__*/Facet.define({
11516 combine(values) {
11517 return combineConfig(values, { formatNumber: String, domEventHandlers: {} }, {
11518 domEventHandlers(a, b) {
11519 let result = Object.assign({}, a);
11520 for (let event in b) {
11521 let exists = result[event], add = b[event];
11522 result[event] = exists ? (view, line, event) => exists(view, line, event) || add(view, line, event) : add;
11523 }
11524 return result;
11525 }
11526 });
11527 }
11528});
11529class NumberMarker extends GutterMarker {
11530 constructor(number) {
11531 super();
11532 this.number = number;
11533 }
11534 eq(other) { return this.number == other.number; }
11535 toDOM() { return document.createTextNode(this.number); }
11536}
11537function formatNumber(view, number) {
11538 return view.state.facet(lineNumberConfig).formatNumber(number, view.state);
11539}
11540const lineNumberGutter = /*@__PURE__*/activeGutters.compute([lineNumberConfig], state => ({
11541 class: "cm-lineNumbers",
11542 renderEmptyElements: false,
11543 markers(view) { return view.state.facet(lineNumberMarkers); },
11544 lineMarker(view, line, others) {
11545 if (others.some(m => m.toDOM))
11546 return null;
11547 return new NumberMarker(formatNumber(view, view.state.doc.lineAt(line.from).number));
11548 },
11549 widgetMarker: (view, widget, block) => {
11550 for (let m of view.state.facet(lineNumberWidgetMarker)) {
11551 let result = m(view, widget, block);
11552 if (result)
11553 return result;
11554 }
11555 return null;
11556 },
11557 lineMarkerChange: update => update.startState.facet(lineNumberConfig) != update.state.facet(lineNumberConfig),
11558 initialSpacer(view) {
11559 return new NumberMarker(formatNumber(view, maxLineNumber(view.state.doc.lines)));
11560 },
11561 updateSpacer(spacer, update) {
11562 let max = formatNumber(update.view, maxLineNumber(update.view.state.doc.lines));
11563 return max == spacer.number ? spacer : new NumberMarker(max);
11564 },
11565 domEventHandlers: state.facet(lineNumberConfig).domEventHandlers,
11566 side: "before"
11567}));
11568/**
11569Create a line number gutter extension.
11570*/
11571function lineNumbers(config = {}) {
11572 return [
11573 lineNumberConfig.of(config),
11574 gutters(),
11575 lineNumberGutter
11576 ];
11577}
11578function maxLineNumber(lines) {
11579 let last = 9;
11580 while (last < lines)
11581 last = last * 10 + 9;
11582 return last;
11583}
11584const activeLineGutterMarker = /*@__PURE__*/new class extends GutterMarker {
11585 constructor() {
11586 super(...arguments);
11587 this.elementClass = "cm-activeLineGutter";
11588 }
11589};
11590const activeLineGutterHighlighter = /*@__PURE__*/gutterLineClass.compute(["selection"], state => {
11591 let marks = [], last = -1;
11592 for (let range of state.selection.ranges) {
11593 let linePos = state.doc.lineAt(range.head).from;
11594 if (linePos > last) {
11595 last = linePos;
11596 marks.push(activeLineGutterMarker.range(linePos));
11597 }
11598 }
11599 return RangeSet.of(marks);
11600});
11601/**
11602Returns an extension that adds a `cm-activeLineGutter` class to
11603all gutter elements on the [active
11604line](https://codemirror.net/6/docs/ref/#view.highlightActiveLine).
11605*/
11606function highlightActiveLineGutter() {
11607 return activeLineGutterHighlighter;
11608}
11609
11610function matcher(decorator) {
11611 return ViewPlugin.define(view => ({
11612 decorations: decorator.createDeco(view),
11613 update(u) {
11614 this.decorations = decorator.updateDeco(u, this.decorations);
11615 },
11616 }), {
11617 decorations: v => v.decorations
11618 });
11619}
11620const tabDeco = /*@__PURE__*/Decoration.mark({ class: "cm-highlightTab" });
11621const spaceDeco = /*@__PURE__*/Decoration.mark({ class: "cm-highlightSpace" });
11622const whitespaceHighlighter = /*@__PURE__*/matcher(/*@__PURE__*/new MatchDecorator({
11623 regexp: /\t| /g,
11624 decoration: match => match[0] == "\t" ? tabDeco : spaceDeco,
11625 boundary: /\S/,
11626}));
11627/**
11628Returns an extension that highlights whitespace, adding a
11629`cm-highlightSpace` class to stretches of spaces, and a
11630`cm-highlightTab` class to individual tab characters. By default,
11631the former are shown as faint dots, and the latter as arrows.
11632*/
11633function highlightWhitespace() {
11634 return whitespaceHighlighter;
11635}
11636const trailingHighlighter = /*@__PURE__*/matcher(/*@__PURE__*/new MatchDecorator({
11637 regexp: /\s+$/g,
11638 decoration: /*@__PURE__*/Decoration.mark({ class: "cm-trailingSpace" })
11639}));
11640/**
11641Returns an extension that adds a `cm-trailingSpace` class to all
11642trailing whitespace.
11643*/
11644function highlightTrailingWhitespace() {
11645 return trailingHighlighter;
11646}
11647
11648/**
11649@internal
11650*/
11651const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder,
11652 moveVisually, clearHeightChangeFlag, getHeightChangeFlag: () => heightChangeFlag };
11653
11654export { BidiSpan, BlockInfo, BlockType, BlockWrapper, Decoration, Direction, EditorView, GutterMarker, MatchDecorator, RectangleMarker, ViewPlugin, ViewUpdate, WidgetType, __test, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getDialog, getDrawSelectionConfig, getPanel, getTooltip, gutter, gutterLineClass, gutterWidgetClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumberWidgetMarker, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showDialog, showPanel, showTooltip, tooltips };